github.com/icyphox/x@v0.0.355-0.20220311094250-029bd783e8b8/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)
    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  	// 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  }