github.com/knieriem/gointernal@v0.2.0-pre2/cmd/go/internal/lockedfile/lockedfile_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 // js does not support inter-process file locking. 6 //go:build !js 7 8 package lockedfile_test 9 10 import ( 11 "fmt" 12 "github.com/knieriem/gointernal/internal/testenv" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "testing" 17 "time" 18 19 "github.com/knieriem/gointernal/cmd/go/internal/lockedfile" 20 ) 21 22 func mustTempDir(t *testing.T) (dir string, remove func()) { 23 t.Helper() 24 25 dir, err := os.MkdirTemp("", filepath.Base(t.Name())) 26 if err != nil { 27 t.Fatal(err) 28 } 29 return dir, func() { os.RemoveAll(dir) } 30 } 31 32 const ( 33 quiescent = 10 * time.Millisecond 34 probablyStillBlocked = 10 * time.Second 35 ) 36 37 func mustBlock(t *testing.T, desc string, f func()) (wait func(*testing.T)) { 38 t.Helper() 39 40 done := make(chan struct{}) 41 go func() { 42 f() 43 close(done) 44 }() 45 46 select { 47 case <-done: 48 t.Fatalf("%s unexpectedly did not block", desc) 49 return nil 50 51 case <-time.After(quiescent): 52 return func(t *testing.T) { 53 t.Helper() 54 select { 55 case <-time.After(probablyStillBlocked): 56 t.Fatalf("%s is unexpectedly still blocked after %v", desc, probablyStillBlocked) 57 case <-done: 58 } 59 } 60 } 61 } 62 63 func TestMutexExcludes(t *testing.T) { 64 t.Parallel() 65 66 dir, remove := mustTempDir(t) 67 defer remove() 68 69 path := filepath.Join(dir, "lock") 70 71 mu := lockedfile.MutexAt(path) 72 t.Logf("mu := MutexAt(_)") 73 74 unlock, err := mu.Lock() 75 if err != nil { 76 t.Fatalf("mu.Lock: %v", err) 77 } 78 t.Logf("unlock, _ := mu.Lock()") 79 80 mu2 := lockedfile.MutexAt(mu.Path) 81 t.Logf("mu2 := MutexAt(mu.Path)") 82 83 wait := mustBlock(t, "mu2.Lock()", func() { 84 unlock2, err := mu2.Lock() 85 if err != nil { 86 t.Errorf("mu2.Lock: %v", err) 87 return 88 } 89 t.Logf("unlock2, _ := mu2.Lock()") 90 t.Logf("unlock2()") 91 unlock2() 92 }) 93 94 t.Logf("unlock()") 95 unlock() 96 wait(t) 97 } 98 99 func TestReadWaitsForLock(t *testing.T) { 100 t.Parallel() 101 102 dir, remove := mustTempDir(t) 103 defer remove() 104 105 path := filepath.Join(dir, "timestamp.txt") 106 107 f, err := lockedfile.Create(path) 108 if err != nil { 109 t.Fatalf("Create: %v", err) 110 } 111 defer f.Close() 112 113 const ( 114 part1 = "part 1\n" 115 part2 = "part 2\n" 116 ) 117 _, err = f.WriteString(part1) 118 if err != nil { 119 t.Fatalf("WriteString: %v", err) 120 } 121 t.Logf("WriteString(%q) = <nil>", part1) 122 123 wait := mustBlock(t, "Read", func() { 124 b, err := lockedfile.Read(path) 125 if err != nil { 126 t.Errorf("Read: %v", err) 127 return 128 } 129 130 const want = part1 + part2 131 got := string(b) 132 if got == want { 133 t.Logf("Read(_) = %q", got) 134 } else { 135 t.Errorf("Read(_) = %q, _; want %q", got, want) 136 } 137 }) 138 139 _, err = f.WriteString(part2) 140 if err != nil { 141 t.Errorf("WriteString: %v", err) 142 } else { 143 t.Logf("WriteString(%q) = <nil>", part2) 144 } 145 f.Close() 146 147 wait(t) 148 } 149 150 func TestCanLockExistingFile(t *testing.T) { 151 t.Parallel() 152 153 dir, remove := mustTempDir(t) 154 defer remove() 155 path := filepath.Join(dir, "existing.txt") 156 157 if err := os.WriteFile(path, []byte("ok"), 0777); err != nil { 158 t.Fatalf("os.WriteFile: %v", err) 159 } 160 161 f, err := lockedfile.Edit(path) 162 if err != nil { 163 t.Fatalf("first Edit: %v", err) 164 } 165 166 wait := mustBlock(t, "Edit", func() { 167 other, err := lockedfile.Edit(path) 168 if err != nil { 169 t.Errorf("second Edit: %v", err) 170 } 171 other.Close() 172 }) 173 174 f.Close() 175 wait(t) 176 } 177 178 // TestSpuriousEDEADLK verifies that the spurious EDEADLK reported in 179 // https://golang.org/issue/32817 no longer occurs. 180 func TestSpuriousEDEADLK(t *testing.T) { 181 // P.1 locks file A. 182 // Q.3 locks file B. 183 // Q.3 blocks on file A. 184 // P.2 blocks on file B. (Spurious EDEADLK occurs here.) 185 // P.1 unlocks file A. 186 // Q.3 unblocks and locks file A. 187 // Q.3 unlocks files A and B. 188 // P.2 unblocks and locks file B. 189 // P.2 unlocks file B. 190 191 testenv.MustHaveExec(t) 192 193 dirVar := t.Name() + "DIR" 194 195 if dir := os.Getenv(dirVar); dir != "" { 196 // Q.3 locks file B. 197 b, err := lockedfile.Edit(filepath.Join(dir, "B")) 198 if err != nil { 199 t.Fatal(err) 200 } 201 defer b.Close() 202 203 if err := os.WriteFile(filepath.Join(dir, "locked"), []byte("ok"), 0666); err != nil { 204 t.Fatal(err) 205 } 206 207 // Q.3 blocks on file A. 208 a, err := lockedfile.Edit(filepath.Join(dir, "A")) 209 // Q.3 unblocks and locks file A. 210 if err != nil { 211 t.Fatal(err) 212 } 213 defer a.Close() 214 215 // Q.3 unlocks files A and B. 216 return 217 } 218 219 dir, remove := mustTempDir(t) 220 defer remove() 221 222 // P.1 locks file A. 223 a, err := lockedfile.Edit(filepath.Join(dir, "A")) 224 if err != nil { 225 t.Fatal(err) 226 } 227 228 cmd := exec.Command(os.Args[0], "-test.run="+t.Name()) 229 cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir)) 230 231 qDone := make(chan struct{}) 232 waitQ := mustBlock(t, "Edit A and B in subprocess", func() { 233 out, err := cmd.CombinedOutput() 234 if err != nil { 235 t.Errorf("%v:\n%s", err, out) 236 } 237 close(qDone) 238 }) 239 240 // Wait until process Q has either failed or locked file B. 241 // Otherwise, P.2 might not block on file B as intended. 242 locked: 243 for { 244 if _, err := os.Stat(filepath.Join(dir, "locked")); !os.IsNotExist(err) { 245 break locked 246 } 247 select { 248 case <-qDone: 249 break locked 250 case <-time.After(1 * time.Millisecond): 251 } 252 } 253 254 waitP2 := mustBlock(t, "Edit B", func() { 255 // P.2 blocks on file B. (Spurious EDEADLK occurs here.) 256 b, err := lockedfile.Edit(filepath.Join(dir, "B")) 257 // P.2 unblocks and locks file B. 258 if err != nil { 259 t.Error(err) 260 return 261 } 262 // P.2 unlocks file B. 263 b.Close() 264 }) 265 266 // P.1 unlocks file A. 267 a.Close() 268 269 waitQ(t) 270 waitP2(t) 271 }