github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-internal/lockedfile/internal/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 // +build !js,!plan9 6 7 package filelock_test 8 9 import ( 10 "fmt" 11 "github.com/gagliardetto/golang-go/not-internal/testenv" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "runtime" 17 "testing" 18 "time" 19 20 "github.com/gagliardetto/golang-go/cmd/go/not-internal/lockedfile/internal/filelock" 21 ) 22 23 func lock(t *testing.T, f *os.File) { 24 t.Helper() 25 err := filelock.Lock(f) 26 t.Logf("Lock(fd %d) = %v", f.Fd(), err) 27 if err != nil { 28 t.Fail() 29 } 30 } 31 32 func rLock(t *testing.T, f *os.File) { 33 t.Helper() 34 err := filelock.RLock(f) 35 t.Logf("RLock(fd %d) = %v", f.Fd(), err) 36 if err != nil { 37 t.Fail() 38 } 39 } 40 41 func unlock(t *testing.T, f *os.File) { 42 t.Helper() 43 err := filelock.Unlock(f) 44 t.Logf("Unlock(fd %d) = %v", f.Fd(), err) 45 if err != nil { 46 t.Fail() 47 } 48 } 49 50 func mustTempFile(t *testing.T) (f *os.File, remove func()) { 51 t.Helper() 52 53 base := filepath.Base(t.Name()) 54 f, err := ioutil.TempFile("", base) 55 if err != nil { 56 t.Fatalf(`ioutil.TempFile("", %q) = %v`, base, err) 57 } 58 t.Logf("fd %d = %s", f.Fd(), f.Name()) 59 60 return f, func() { 61 f.Close() 62 os.Remove(f.Name()) 63 } 64 } 65 66 func mustOpen(t *testing.T, name string) *os.File { 67 t.Helper() 68 69 f, err := os.OpenFile(name, os.O_RDWR, 0) 70 if err != nil { 71 t.Fatalf("os.Open(%q) = %v", name, err) 72 } 73 74 t.Logf("fd %d = os.Open(%q)", f.Fd(), name) 75 return f 76 } 77 78 const ( 79 quiescent = 10 * time.Millisecond 80 probablyStillBlocked = 10 * time.Second 81 ) 82 83 func mustBlock(t *testing.T, op string, f *os.File) (wait func(*testing.T)) { 84 t.Helper() 85 86 desc := fmt.Sprintf("%s(fd %d)", op, f.Fd()) 87 88 done := make(chan struct{}) 89 go func() { 90 t.Helper() 91 switch op { 92 case "Lock": 93 lock(t, f) 94 case "RLock": 95 rLock(t, f) 96 default: 97 panic("invalid op: " + op) 98 } 99 close(done) 100 }() 101 102 select { 103 case <-done: 104 t.Fatalf("%s unexpectedly did not block", desc) 105 return nil 106 107 case <-time.After(quiescent): 108 t.Logf("%s is blocked (as expected)", desc) 109 return func(t *testing.T) { 110 t.Helper() 111 select { 112 case <-time.After(probablyStillBlocked): 113 t.Fatalf("%s is unexpectedly still blocked", desc) 114 case <-done: 115 } 116 } 117 } 118 } 119 120 func TestLockExcludesLock(t *testing.T) { 121 t.Parallel() 122 123 f, remove := mustTempFile(t) 124 defer remove() 125 126 other := mustOpen(t, f.Name()) 127 defer other.Close() 128 129 lock(t, f) 130 lockOther := mustBlock(t, "Lock", other) 131 unlock(t, f) 132 lockOther(t) 133 unlock(t, other) 134 } 135 136 func TestLockExcludesRLock(t *testing.T) { 137 t.Parallel() 138 139 f, remove := mustTempFile(t) 140 defer remove() 141 142 other := mustOpen(t, f.Name()) 143 defer other.Close() 144 145 lock(t, f) 146 rLockOther := mustBlock(t, "RLock", other) 147 unlock(t, f) 148 rLockOther(t) 149 unlock(t, other) 150 } 151 152 func TestRLockExcludesOnlyLock(t *testing.T) { 153 t.Parallel() 154 155 f, remove := mustTempFile(t) 156 defer remove() 157 rLock(t, f) 158 159 f2 := mustOpen(t, f.Name()) 160 defer f2.Close() 161 162 doUnlockTF := false 163 switch runtime.GOOS { 164 case "aix", "illumos", "solaris": 165 // When using POSIX locks (as on Solaris), we can't safely read-lock the 166 // same inode through two different descriptors at the same time: when the 167 // first descriptor is closed, the second descriptor would still be open but 168 // silently unlocked. So a second RLock must block instead of proceeding. 169 lockF2 := mustBlock(t, "RLock", f2) 170 unlock(t, f) 171 lockF2(t) 172 default: 173 rLock(t, f2) 174 doUnlockTF = true 175 } 176 177 other := mustOpen(t, f.Name()) 178 defer other.Close() 179 lockOther := mustBlock(t, "Lock", other) 180 181 unlock(t, f2) 182 if doUnlockTF { 183 unlock(t, f) 184 } 185 lockOther(t) 186 unlock(t, other) 187 } 188 189 func TestLockNotDroppedByExecCommand(t *testing.T) { 190 testenv.MustHaveExec(t) 191 192 f, remove := mustTempFile(t) 193 defer remove() 194 195 lock(t, f) 196 197 other := mustOpen(t, f.Name()) 198 defer other.Close() 199 200 // Some kinds of file locks are dropped when a duplicated or forked file 201 // descriptor is unlocked. Double-check that the approach used by os/exec does 202 // not accidentally drop locks. 203 cmd := exec.Command(os.Args[0], "-test.run=^$") 204 if err := cmd.Run(); err != nil { 205 t.Fatalf("exec failed: %v", err) 206 } 207 208 lockOther := mustBlock(t, "Lock", other) 209 unlock(t, f) 210 lockOther(t) 211 unlock(t, other) 212 }