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