github.com/gedevops/x@v1.0.3/watcherx/file_test.go (about) 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) //#nosec:G304 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) //#nosec:G304 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) //#nosec:G304 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) //#nosec:G304 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) //#nosec:G304 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) //#nosec:G304 134 require.NoError(t, err) 135 require.NoError(t, f1.Close()) 136 f2, err := os.Create(fileTwo) //#nosec:G304 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) //#nosec:G304 166 require.NoError(t, err) 167 require.NoError(t, f.Close()) 168 169 assertChange(t, <-c, "", fileName) 170 }) 171 172 // https://github.com/kubernetes/kubernetes/issues/93686 173 //t.Run("case=kubernetes atomic writer create", func(t *testing.T) { 174 // ctx, c, dir, cancel := setup(t) 175 // defer cancel() 176 // 177 // fileName := "example.file" 178 // filePath := path.Join(dir, fileName) 179 // 180 // require.NoError(t, WatchFile(ctx, filePath, c)) 181 // 182 // kubernetesAtomicWrite(t, dir, fileName, "foobarx") 183 // 184 // assertChange(t, <-c, "foobarx", filePath) 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 // buffered channel to allow usage of DispatchNow().done 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 // wait for d.DispatchNow to be done 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 }