1
2
3
4
5
6
7 package filelock_test
8
9 import (
10 "fmt"
11 "internal/testenv"
12 "os"
13 "path/filepath"
14 "runtime"
15 "testing"
16 "time"
17
18 "cmd/go/internal/lockedfile/internal/filelock"
19 )
20
21 func lock(t *testing.T, f *os.File) {
22 t.Helper()
23 err := filelock.Lock(f)
24 t.Logf("Lock(fd %d) = %v", f.Fd(), err)
25 if err != nil {
26 t.Fail()
27 }
28 }
29
30 func rLock(t *testing.T, f *os.File) {
31 t.Helper()
32 err := filelock.RLock(f)
33 t.Logf("RLock(fd %d) = %v", f.Fd(), err)
34 if err != nil {
35 t.Fail()
36 }
37 }
38
39 func unlock(t *testing.T, f *os.File) {
40 t.Helper()
41 err := filelock.Unlock(f)
42 t.Logf("Unlock(fd %d) = %v", f.Fd(), err)
43 if err != nil {
44 t.Fail()
45 }
46 }
47
48 func mustTempFile(t *testing.T) (f *os.File, remove func()) {
49 t.Helper()
50
51 base := filepath.Base(t.Name())
52 f, err := os.CreateTemp("", base)
53 if err != nil {
54 t.Fatalf(`os.CreateTemp("", %q) = %v`, base, err)
55 }
56 t.Logf("fd %d = %s", f.Fd(), f.Name())
57
58 return f, func() {
59 f.Close()
60 os.Remove(f.Name())
61 }
62 }
63
64 func mustOpen(t *testing.T, name string) *os.File {
65 t.Helper()
66
67 f, err := os.OpenFile(name, os.O_RDWR, 0)
68 if err != nil {
69 t.Fatalf("os.OpenFile(%q) = %v", name, err)
70 }
71
72 t.Logf("fd %d = os.OpenFile(%q)", f.Fd(), name)
73 return f
74 }
75
76 const (
77 quiescent = 10 * time.Millisecond
78 probablyStillBlocked = 10 * time.Second
79 )
80
81 func mustBlock(t *testing.T, op string, f *os.File) (wait func(*testing.T)) {
82 t.Helper()
83
84 desc := fmt.Sprintf("%s(fd %d)", op, f.Fd())
85
86 done := make(chan struct{})
87 go func() {
88 t.Helper()
89 switch op {
90 case "Lock":
91 lock(t, f)
92 case "RLock":
93 rLock(t, f)
94 default:
95 panic("invalid op: " + op)
96 }
97 close(done)
98 }()
99
100 select {
101 case <-done:
102 t.Fatalf("%s unexpectedly did not block", desc)
103 return nil
104
105 case <-time.After(quiescent):
106 t.Logf("%s is blocked (as expected)", desc)
107 return func(t *testing.T) {
108 t.Helper()
109 select {
110 case <-time.After(probablyStillBlocked):
111 t.Fatalf("%s is unexpectedly still blocked", desc)
112 case <-done:
113 }
114 }
115 }
116 }
117
118 func TestLockExcludesLock(t *testing.T) {
119 t.Parallel()
120
121 f, remove := mustTempFile(t)
122 defer remove()
123
124 other := mustOpen(t, f.Name())
125 defer other.Close()
126
127 lock(t, f)
128 lockOther := mustBlock(t, "Lock", other)
129 unlock(t, f)
130 lockOther(t)
131 unlock(t, other)
132 }
133
134 func TestLockExcludesRLock(t *testing.T) {
135 t.Parallel()
136
137 f, remove := mustTempFile(t)
138 defer remove()
139
140 other := mustOpen(t, f.Name())
141 defer other.Close()
142
143 lock(t, f)
144 rLockOther := mustBlock(t, "RLock", other)
145 unlock(t, f)
146 rLockOther(t)
147 unlock(t, other)
148 }
149
150 func TestRLockExcludesOnlyLock(t *testing.T) {
151 t.Parallel()
152
153 f, remove := mustTempFile(t)
154 defer remove()
155 rLock(t, f)
156
157 f2 := mustOpen(t, f.Name())
158 defer f2.Close()
159
160 doUnlockTF := false
161 switch runtime.GOOS {
162 case "aix", "solaris":
163
164
165
166
167 lockF2 := mustBlock(t, "RLock", f2)
168 unlock(t, f)
169 lockF2(t)
170 default:
171 rLock(t, f2)
172 doUnlockTF = true
173 }
174
175 other := mustOpen(t, f.Name())
176 defer other.Close()
177 lockOther := mustBlock(t, "Lock", other)
178
179 unlock(t, f2)
180 if doUnlockTF {
181 unlock(t, f)
182 }
183 lockOther(t)
184 unlock(t, other)
185 }
186
187 func TestLockNotDroppedByExecCommand(t *testing.T) {
188 testenv.MustHaveExec(t)
189
190 f, remove := mustTempFile(t)
191 defer remove()
192
193 lock(t, f)
194
195 other := mustOpen(t, f.Name())
196 defer other.Close()
197
198
199
200
201 cmd := testenv.Command(t, os.Args[0], "-test.run=^$")
202 if err := cmd.Run(); err != nil {
203 t.Fatalf("exec failed: %v", err)
204 }
205
206 lockOther := mustBlock(t, "Lock", other)
207 unlock(t, f)
208 lockOther(t)
209 unlock(t, other)
210 }
211
View as plain text