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