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