1 package watcherx
2
3 import (
4 "context"
5 "fmt"
6 "io/ioutil"
7 "os"
8 "path/filepath"
9 "runtime"
10 "testing"
11 "time"
12
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 )
16
17 func setup(t *testing.T) (context.Context, chan Event, string, context.CancelFunc) {
18 c := make(chan Event)
19 ctx, cancel := context.WithCancel(context.Background())
20 dir := t.TempDir()
21 return ctx, c, dir, cancel
22 }
23
24 func assertChange(t *testing.T, e Event, expectedData, src string) {
25 _, ok := e.(*ChangeEvent)
26 require.True(t, ok, "%T: %+v", e, e)
27 data, err := ioutil.ReadAll(e.Reader())
28 require.NoError(t, err)
29 assert.Equal(t, expectedData, string(data))
30 assert.Equal(t, src, e.Source())
31 }
32
33 func assertRemove(t *testing.T, e Event, src string) {
34 assert.Equal(t, &RemoveEvent{source(src)}, e)
35 }
36
37 func TestFileWatcher(t *testing.T) {
38 t.Run("case=notifies on file write", func(t *testing.T) {
39 ctx, c, dir, cancel := setup(t)
40 defer cancel()
41
42 exampleFile := filepath.Join(dir, "example.file")
43 f, err := os.Create(exampleFile)
44 require.NoError(t, err)
45
46 _, err = WatchFile(ctx, exampleFile, c)
47 require.NoError(t, err)
48
49 _, err = fmt.Fprintf(f, "foo")
50 require.NoError(t, err)
51 require.NoError(t, f.Close())
52
53 assertChange(t, <-c, "foo", exampleFile)
54 })
55
56 t.Run("case=notifies on file create", func(t *testing.T) {
57 ctx, c, dir, cancel := setup(t)
58 defer cancel()
59
60 exampleFile := filepath.Join(dir, "example.file")
61 _, err := WatchFile(ctx, exampleFile, c)
62 require.NoError(t, err)
63
64 f, err := os.Create(exampleFile)
65 require.NoError(t, err)
66 require.NoError(t, f.Close())
67
68 assertChange(t, <-c, "", exampleFile)
69 })
70
71 t.Run("case=notifies after file delete about recreate", func(t *testing.T) {
72 ctx, c, dir, cancel := setup(t)
73 defer cancel()
74
75 exampleFile := filepath.Join(dir, "example.file")
76 f, err := os.Create(exampleFile)
77 require.NoError(t, err)
78 require.NoError(t, f.Close())
79
80 _, err = WatchFile(ctx, exampleFile, c)
81 require.NoError(t, err)
82
83 require.NoError(t, os.Remove(exampleFile))
84
85 assertRemove(t, <-c, exampleFile)
86
87 f, err = os.Create(exampleFile)
88 require.NoError(t, err)
89 require.NoError(t, f.Close())
90
91 assertChange(t, <-c, "", exampleFile)
92 })
93
94 t.Run("case=notifies about changes in the linked file", func(t *testing.T) {
95 if runtime.GOOS != "linux" {
96 t.Skip("skipping test because watching symlinks on windows and macOS is not working properly")
97 }
98
99 ctx, c, dir, cancel := setup(t)
100 defer cancel()
101
102 otherDir, err := ioutil.TempDir("", "*")
103 require.NoError(t, err)
104 origFileName := filepath.Join(otherDir, "original")
105 f, err := os.Create(origFileName)
106 require.NoError(t, err)
107
108 linkFileName := filepath.Join(dir, "slink")
109 require.NoError(t, os.Symlink(origFileName, linkFileName))
110
111 _, err = WatchFile(ctx, linkFileName, c)
112 require.NoError(t, err)
113
114 _, err = fmt.Fprintf(f, "content")
115 require.NoError(t, err)
116 require.NoError(t, f.Close())
117
118 assertChange(t, <-c, "content", linkFileName)
119 })
120
121 t.Run("case=notifies about symlink change", func(t *testing.T) {
122 if runtime.GOOS != "linux" {
123 t.Skip("skipping test because watching symlinks on windows and macOS is not working properly")
124 }
125
126 ctx, c, dir, cancel := setup(t)
127 defer cancel()
128
129 otherDir, err := ioutil.TempDir("", "*")
130 require.NoError(t, err)
131 fileOne := filepath.Join(otherDir, "fileOne")
132 fileTwo := filepath.Join(otherDir, "fileTwo")
133 f1, err := os.Create(fileOne)
134 require.NoError(t, err)
135 require.NoError(t, f1.Close())
136 f2, err := os.Create(fileTwo)
137 require.NoError(t, err)
138 _, err = fmt.Fprintf(f2, "file two")
139 require.NoError(t, err)
140 require.NoError(t, f2.Close())
141
142 linkFileName := filepath.Join(dir, "slink")
143 require.NoError(t, os.Symlink(fileOne, linkFileName))
144
145 _, err = WatchFile(ctx, linkFileName, c)
146 require.NoError(t, err)
147
148 require.NoError(t, os.Remove(linkFileName))
149 assertRemove(t, <-c, linkFileName)
150
151 require.NoError(t, os.Symlink(fileTwo, linkFileName))
152 assertChange(t, <-c, "file two", linkFileName)
153 })
154
155 t.Run("case=watch relative file path", func(t *testing.T) {
156 ctx, c, dir, cancel := setup(t)
157 defer cancel()
158
159 require.NoError(t, os.Chdir(dir))
160
161 fileName := "example.file"
162 _, err := WatchFile(ctx, fileName, c)
163 require.NoError(t, err)
164
165 f, err := os.Create(fileName)
166 require.NoError(t, err)
167 require.NoError(t, f.Close())
168
169 assertChange(t, <-c, "", fileName)
170 })
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187 t.Run("case=kubernetes atomic writer update", func(t *testing.T) {
188 if runtime.GOOS != "linux" {
189 t.Skip("skipping test because watching symlinks on windows and macOS is not working properly")
190 }
191
192 ctx, c, dir, cancel := setup(t)
193 defer cancel()
194
195 fileName := "example.file"
196 filePath := filepath.Join(dir, fileName)
197 kubernetesAtomicWrite(t, dir, fileName, "foobar")
198
199 _, err := WatchFile(ctx, filePath, c)
200 require.NoError(t, err)
201
202 kubernetesAtomicWrite(t, dir, fileName, "foobarx")
203
204 assertChange(t, <-c, "foobarx", filePath)
205 })
206
207 t.Run("case=sends event when requested", func(t *testing.T) {
208 ctx, c, dir, cancel := setup(t)
209 defer cancel()
210
211
212 c = make(EventChannel, 1)
213
214 fn := filepath.Join(dir, "example.file")
215 initialContent := "initial content"
216 require.NoError(t, ioutil.WriteFile(fn, []byte(initialContent), 0600))
217
218 d, err := WatchFile(ctx, fn, c)
219 require.NoError(t, err)
220 done, err := d.DispatchNow()
221 require.NoError(t, err)
222
223
224 select {
225 case <-time.After(time.Second):
226 t.Log("Waiting for done timed out.")
227 t.FailNow()
228 case eventsSend := <-done:
229 assert.Equal(t, 1, eventsSend)
230 }
231
232 assertChange(t, <-c, initialContent, fn)
233 })
234 }
235
View as plain text