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  }