github.com/searKing/golang/go@v1.2.117/sync/filelock/filelock_test.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !js && !plan9 && !wasip1 6 7 package filelock_test 8 9 import ( 10 "fmt" 11 "os" 12 "path/filepath" 13 "runtime" 14 "testing" 15 "time" 16 17 "github.com/searKing/golang/go/sync/filelock" 18 ) 19 20 func lock(t *testing.T, f *os.File) { 21 t.Helper() 22 err := filelock.Lock(f) 23 t.Logf("Lock(fd %d) = %v", f.Fd(), err) 24 if err != nil { 25 t.Fail() 26 } 27 } 28 29 func rLock(t *testing.T, f *os.File) { 30 t.Helper() 31 err := filelock.RLock(f) 32 t.Logf("RLock(fd %d) = %v", f.Fd(), err) 33 if err != nil { 34 t.Fail() 35 } 36 } 37 38 func unlock(t *testing.T, f *os.File) { 39 t.Helper() 40 err := filelock.Unlock(f) 41 t.Logf("Unlock(fd %d) = %v", f.Fd(), err) 42 if err != nil { 43 t.Fail() 44 } 45 } 46 47 func mustTempFile(t *testing.T) (f *os.File, remove func()) { 48 t.Helper() 49 50 base := filepath.Base(t.Name()) 51 f, err := os.CreateTemp("", base) 52 if err != nil { 53 t.Fatalf(`os.CreateTemp("", %q) = %v`, base, err) 54 } 55 t.Logf("fd %d = %s", f.Fd(), f.Name()) 56 57 return f, func() { 58 f.Close() 59 os.Remove(f.Name()) 60 } 61 } 62 63 func mustOpen(t *testing.T, name string) *os.File { 64 t.Helper() 65 66 f, err := os.OpenFile(name, os.O_RDWR, 0) 67 if err != nil { 68 t.Fatalf("os.Open(%q) = %v", name, err) 69 } 70 71 t.Logf("fd %d = os.Open(%q)", f.Fd(), name) 72 return f 73 } 74 75 const ( 76 quiescent = 10 * time.Millisecond 77 probablyStillBlocked = 10 * time.Second 78 ) 79 80 func mustBlock(t *testing.T, op string, f *os.File) (wait func(*testing.T)) { 81 t.Helper() 82 83 desc := fmt.Sprintf("%s(fd %d)", op, f.Fd()) 84 85 done := make(chan struct{}) 86 go func() { 87 t.Helper() 88 switch op { 89 case "Lock": 90 lock(t, f) 91 case "RLock": 92 rLock(t, f) 93 default: 94 panic("invalid op: " + op) 95 } 96 close(done) 97 }() 98 99 select { 100 case <-done: 101 t.Fatalf("%s unexpectedly did not block", desc) 102 return nil 103 104 case <-time.After(quiescent): 105 t.Logf("%s is blocked (as expected)", desc) 106 return func(t *testing.T) { 107 t.Helper() 108 select { 109 case <-time.After(probablyStillBlocked): 110 t.Fatalf("%s is unexpectedly still blocked", desc) 111 case <-done: 112 } 113 } 114 } 115 } 116 117 func TestLockExcludesLock(t *testing.T) { 118 t.Parallel() 119 120 f, remove := mustTempFile(t) 121 defer remove() 122 123 other := mustOpen(t, f.Name()) 124 defer other.Close() 125 126 lock(t, f) 127 lockOther := mustBlock(t, "Lock", other) 128 unlock(t, f) 129 lockOther(t) 130 unlock(t, other) 131 } 132 133 func TestLockExcludesRLock(t *testing.T) { 134 t.Parallel() 135 136 f, remove := mustTempFile(t) 137 defer remove() 138 139 other := mustOpen(t, f.Name()) 140 defer other.Close() 141 142 lock(t, f) 143 rLockOther := mustBlock(t, "RLock", other) 144 unlock(t, f) 145 rLockOther(t) 146 unlock(t, other) 147 } 148 149 func TestRLockExcludesOnlyLock(t *testing.T) { 150 t.Parallel() 151 152 f, remove := mustTempFile(t) 153 defer remove() 154 rLock(t, f) 155 156 f2 := mustOpen(t, f.Name()) 157 defer f2.Close() 158 159 doUnlockTF := false 160 switch runtime.GOOS { 161 case "aix", "solaris": 162 // When using POSIX locks (as on Solaris), we can't safely read-lock the 163 // same inode through two different descriptors at the same time: when the 164 // first descriptor is closed, the second descriptor would still be open but 165 // silently unlocked. So a second RLock must block instead of proceeding. 166 lockF2 := mustBlock(t, "RLock", f2) 167 unlock(t, f) 168 lockF2(t) 169 default: 170 rLock(t, f2) 171 doUnlockTF = true 172 } 173 174 other := mustOpen(t, f.Name()) 175 defer other.Close() 176 lockOther := mustBlock(t, "Lock", other) 177 178 unlock(t, f2) 179 if doUnlockTF { 180 unlock(t, f) 181 } 182 lockOther(t) 183 unlock(t, other) 184 }