tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/watchfs/watcher/watcher_test.go (about)

     1  package watcher
     2  
     3  import (
     4  	"io/fs"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  )
    13  
    14  // setup creates all required files and folders for
    15  // the tests and returns a function that is used as
    16  // a teardown function when the tests are done.
    17  func setup(t testing.TB) (string, func()) {
    18  	testDir, err := ioutil.TempDir(".", "")
    19  	if err != nil {
    20  		t.Fatal(err)
    21  	}
    22  
    23  	err = ioutil.WriteFile(filepath.Join(testDir, "file.txt"),
    24  		[]byte{}, 0755)
    25  	if err != nil {
    26  		t.Fatal(err)
    27  	}
    28  
    29  	files := []string{"file_1.txt", "file_2.txt", "file_3.txt"}
    30  
    31  	for _, f := range files {
    32  		filePath := filepath.Join(testDir, f)
    33  		if err := ioutil.WriteFile(filePath, []byte{}, 0755); err != nil {
    34  			t.Fatal(err)
    35  		}
    36  	}
    37  
    38  	err = ioutil.WriteFile(filepath.Join(testDir, ".dotfile"),
    39  		[]byte{}, 0755)
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	testDirTwo := filepath.Join(testDir, "testDirTwo")
    45  	err = os.Mkdir(testDirTwo, 0755)
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  
    50  	err = ioutil.WriteFile(filepath.Join(testDirTwo, "file_recursive.txt"),
    51  		[]byte{}, 0755)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  
    56  	abs, err := filepath.Abs(testDir)
    57  	if err != nil {
    58  		os.RemoveAll(testDir)
    59  		t.Fatal(err)
    60  	}
    61  	if abs[0] == '/' {
    62  		abs = abs[1:]
    63  	}
    64  	return abs, func() {
    65  		if os.RemoveAll(testDir); err != nil {
    66  			t.Fatal(err)
    67  		}
    68  	}
    69  }
    70  
    71  func TestEventString(t *testing.T) {
    72  	e := &Event{Op: Create, Path: "/fake/path"}
    73  
    74  	testCases := []struct {
    75  		info     fs.FileInfo
    76  		expected string
    77  	}{
    78  		{nil, "???"},
    79  		{
    80  			&fileInfo{name: "f1", dir: true},
    81  			"DIRECTORY \"f1\" CREATE [/fake/path]",
    82  		},
    83  		{
    84  			&fileInfo{name: "f2", dir: false},
    85  			"FILE \"f2\" CREATE [/fake/path]",
    86  		},
    87  	}
    88  
    89  	for _, tc := range testCases {
    90  		e.FileInfo = tc.info
    91  		if e.String() != tc.expected {
    92  			t.Errorf("expected e.String() to be %s, got %s", tc.expected, e.String())
    93  		}
    94  	}
    95  }
    96  
    97  func TestFileInfo(t *testing.T) {
    98  	modTime := time.Now()
    99  
   100  	fInfo := &fileInfo{
   101  		name:    "finfo",
   102  		size:    1,
   103  		mode:    fs.ModeDir,
   104  		modTime: modTime,
   105  		sys:     nil,
   106  		dir:     true,
   107  	}
   108  
   109  	// Test file info methods.
   110  	if fInfo.Name() != "finfo" {
   111  		t.Fatalf("expected fInfo.Name() to be 'finfo', got %s", fInfo.Name())
   112  	}
   113  	if fInfo.IsDir() != true {
   114  		t.Fatalf("expected fInfo.IsDir() to be true, got %t", fInfo.IsDir())
   115  	}
   116  	if fInfo.Size() != 1 {
   117  		t.Fatalf("expected fInfo.Size() to be 1, got %d", fInfo.Size())
   118  	}
   119  	if fInfo.Sys() != nil {
   120  		t.Fatalf("expected fInfo.Sys() to be nil, got %v", fInfo.Sys())
   121  	}
   122  	if fInfo.ModTime() != modTime {
   123  		t.Fatalf("expected fInfo.ModTime() to be %v, got %v", modTime, fInfo.ModTime())
   124  	}
   125  	if fInfo.Mode() != fs.ModeDir {
   126  		t.Fatalf("expected fInfo.Mode() to be fs.ModeDir, got %#v", fInfo.Mode())
   127  	}
   128  
   129  	w := New(os.DirFS("/"))
   130  
   131  	w.wg.Done() // Set the waitgroup to done.
   132  
   133  	go func() {
   134  		// Trigger an event with the file info.
   135  		w.TriggerEvent(Create, fInfo)
   136  	}()
   137  
   138  	e := <-w.Event
   139  
   140  	if e.FileInfo != fInfo {
   141  		t.Fatal("expected e.FileInfo to be equal to fInfo")
   142  	}
   143  }
   144  
   145  func TestRenameWatchedSubdir(t *testing.T) {
   146  	testDir, teardown := setup(t)
   147  	defer teardown()
   148  
   149  	w := New(os.DirFS("/"))
   150  
   151  	if err := w.Add(testDir); err != nil {
   152  		t.Fatal(err)
   153  	}
   154  
   155  	subdir := filepath.Join(testDir, "testDirTwo")
   156  	if err := w.Add(subdir); err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	go func() {
   161  		// Start the watching process.
   162  		if err := w.Start(time.Millisecond); err != nil {
   163  			t.Fatal(err)
   164  		}
   165  	}()
   166  
   167  	os.Rename("/"+subdir, filepath.Join("/"+testDir, "testDirTwoNew"))
   168  
   169  	// go func() {
   170  	// 	for err := range w.Error {
   171  	// 		fmt.Println(err)
   172  	// 	}
   173  	// }()
   174  
   175  	<-w.Event // WRITE event to dir
   176  	rename := <-w.Event
   177  	if rename.Op != Rename {
   178  		t.Error("expected event for rename")
   179  	}
   180  }
   181  
   182  func TestWatcherAdd(t *testing.T) {
   183  	testDir, teardown := setup(t)
   184  	defer teardown()
   185  
   186  	w := New(os.DirFS("/"))
   187  
   188  	// Try to add a non-existing path.
   189  	err := w.Add("-")
   190  	if err == nil {
   191  		t.Error("expected error to not be nil")
   192  	}
   193  
   194  	if err := w.Add(testDir); err != nil {
   195  		t.Fatal(err)
   196  	}
   197  
   198  	if len(w.files) != 7 {
   199  		t.Errorf("expected len(w.files) to be 7, got %d", len(w.files))
   200  	}
   201  
   202  	// Make sure w.names contains testDir
   203  	if _, found := w.names[testDir]; !found {
   204  		t.Errorf("expected w.names to contain testDir")
   205  	}
   206  
   207  	if _, found := w.files[testDir]; !found {
   208  		t.Errorf("expected to find %s", testDir)
   209  	}
   210  
   211  	if w.files[testDir].Name() != filepath.Base(testDir) {
   212  		t.Errorf("expected w.files[%q].Name() to be %s, got %s",
   213  			testDir, testDir, w.files[testDir].Name())
   214  	}
   215  
   216  	dotFile := filepath.Join(testDir, ".dotfile")
   217  	if _, found := w.files[dotFile]; !found {
   218  		t.Errorf("expected to find %s", dotFile)
   219  	}
   220  
   221  	if w.files[dotFile].Name() != ".dotfile" {
   222  		t.Errorf("expected w.files[%q].Name() to be .dotfile, got %s",
   223  			dotFile, w.files[dotFile].Name())
   224  	}
   225  
   226  	fileRecursive := filepath.Join(testDir, "testDirTwo", "file_recursive.txt")
   227  	if _, found := w.files[fileRecursive]; found {
   228  		t.Errorf("expected to not find %s", fileRecursive)
   229  	}
   230  
   231  	fileTxt := filepath.Join(testDir, "file.txt")
   232  	if _, found := w.files[fileTxt]; !found {
   233  		t.Errorf("expected to find %s", fileTxt)
   234  	}
   235  
   236  	if w.files[fileTxt].Name() != "file.txt" {
   237  		t.Errorf("expected w.files[%q].Name() to be file.txt, got %s",
   238  			fileTxt, w.files[fileTxt].Name())
   239  	}
   240  
   241  	dirTwo := filepath.Join(testDir, "testDirTwo")
   242  	if _, found := w.files[dirTwo]; !found {
   243  		t.Errorf("expected to find %s directory", dirTwo)
   244  	}
   245  
   246  	if w.files[dirTwo].Name() != "testDirTwo" {
   247  		t.Errorf("expected w.files[%q].Name() to be testDirTwo, got %s",
   248  			dirTwo, w.files[dirTwo].Name())
   249  	}
   250  }
   251  
   252  func TestIgnore(t *testing.T) {
   253  	testDir, teardown := setup(t)
   254  	defer teardown()
   255  
   256  	w := New(os.DirFS("/"))
   257  
   258  	err := w.Add(testDir)
   259  	if err != nil {
   260  		t.Errorf("expected error to be nil, got %s", err)
   261  	}
   262  	if len(w.files) != 7 {
   263  		t.Errorf("expected len(w.files) to be 7, got %d", len(w.files))
   264  	}
   265  
   266  	err = w.Ignore(testDir)
   267  	if err != nil {
   268  		t.Errorf("expected error to be nil, got %s", err)
   269  	}
   270  	if len(w.files) != 0 {
   271  		t.Errorf("expected len(w.files) to be 0, got %d", len(w.files))
   272  	}
   273  
   274  	// Now try to add the ignored directory.
   275  	err = w.Add(testDir)
   276  	if err != nil {
   277  		t.Errorf("expected error to be nil, got %s", err)
   278  	}
   279  	if len(w.files) != 0 {
   280  		t.Errorf("expected len(w.files) to be 0, got %d", len(w.files))
   281  	}
   282  }
   283  
   284  func TestRemove(t *testing.T) {
   285  	testDir, teardown := setup(t)
   286  	defer teardown()
   287  
   288  	w := New(os.DirFS("/"))
   289  
   290  	err := w.Add(testDir)
   291  	if err != nil {
   292  		t.Errorf("expected error to be nil, got %s", err)
   293  	}
   294  	if len(w.files) != 7 {
   295  		t.Errorf("expected len(w.files) to be 7, got %d", len(w.files))
   296  	}
   297  
   298  	err = w.Remove(testDir)
   299  	if err != nil {
   300  		t.Errorf("expected error to be nil, got %s", err)
   301  	}
   302  	if len(w.files) != 0 {
   303  		t.Errorf("expected len(w.files) to be 0, got %d", len(w.files))
   304  	}
   305  
   306  	// TODO: Test remove single file.
   307  }
   308  
   309  // TODO: Test remove recursive function.
   310  
   311  func TestIgnoreHiddenFilesRecursive(t *testing.T) {
   312  	// TODO: Write tests for ignore hidden on windows.
   313  	if runtime.GOOS == "windows" {
   314  		return
   315  	}
   316  
   317  	testDir, teardown := setup(t)
   318  	defer teardown()
   319  
   320  	w := New(os.DirFS("/"))
   321  	w.IgnoreHiddenFiles(true)
   322  
   323  	if err := w.AddRecursive(testDir); err != nil {
   324  		t.Fatal(err)
   325  	}
   326  
   327  	if len(w.files) != 7 {
   328  		t.Errorf("expected len(w.files) to be 7, got %d", len(w.files))
   329  	}
   330  
   331  	// Make sure w.names contains testDir
   332  	if _, found := w.names[testDir]; !found {
   333  		t.Errorf("expected w.names to contain testDir")
   334  	}
   335  
   336  	if _, found := w.files[testDir]; !found {
   337  		t.Errorf("expected to find %s", testDir)
   338  	}
   339  
   340  	if w.files[testDir].Name() != filepath.Base(testDir) {
   341  		t.Errorf("expected w.files[%q].Name() to be %s, got %s",
   342  			testDir, filepath.Base(testDir), w.files[testDir].Name())
   343  	}
   344  
   345  	fileRecursive := filepath.Join(testDir, "testDirTwo", "file_recursive.txt")
   346  	if _, found := w.files[fileRecursive]; !found {
   347  		t.Errorf("expected to find %s", fileRecursive)
   348  	}
   349  
   350  	if _, found := w.files[filepath.Join(testDir, ".dotfile")]; found {
   351  		t.Error("expected to not find .dotfile")
   352  	}
   353  
   354  	fileTxt := filepath.Join(testDir, "file.txt")
   355  	if _, found := w.files[fileTxt]; !found {
   356  		t.Errorf("expected to find %s", fileTxt)
   357  	}
   358  
   359  	if w.files[fileTxt].Name() != "file.txt" {
   360  		t.Errorf("expected w.files[%q].Name() to be file.txt, got %s",
   361  			fileTxt, w.files[fileTxt].Name())
   362  	}
   363  
   364  	dirTwo := filepath.Join(testDir, "testDirTwo")
   365  	if _, found := w.files[dirTwo]; !found {
   366  		t.Errorf("expected to find %s directory", dirTwo)
   367  	}
   368  
   369  	if w.files[dirTwo].Name() != "testDirTwo" {
   370  		t.Errorf("expected w.files[%q].Name() to be testDirTwo, got %s",
   371  			dirTwo, w.files[dirTwo].Name())
   372  	}
   373  }
   374  
   375  func TestIgnoreHiddenFiles(t *testing.T) {
   376  	// TODO: Write tests for ignore hidden on windows.
   377  	if runtime.GOOS == "windows" {
   378  		return
   379  	}
   380  
   381  	testDir, teardown := setup(t)
   382  	defer teardown()
   383  
   384  	w := New(os.DirFS("/"))
   385  	w.IgnoreHiddenFiles(true)
   386  
   387  	if err := w.Add(testDir); err != nil {
   388  		t.Fatal(err)
   389  	}
   390  
   391  	if len(w.files) != 6 {
   392  		t.Errorf("expected len(w.files) to be 6, got %d", len(w.files))
   393  	}
   394  
   395  	// Make sure w.names contains testDir
   396  	if _, found := w.names[testDir]; !found {
   397  		t.Errorf("expected w.names to contain testDir")
   398  	}
   399  
   400  	if _, found := w.files[testDir]; !found {
   401  		t.Errorf("expected to find %s", testDir)
   402  	}
   403  
   404  	if w.files[testDir].Name() != filepath.Base(testDir) {
   405  		t.Errorf("expected w.files[%q].Name() to be %s, got %s",
   406  			testDir, filepath.Base(testDir), w.files[testDir].Name())
   407  	}
   408  
   409  	if _, found := w.files[filepath.Join(testDir, ".dotfile")]; found {
   410  		t.Error("expected to not find .dotfile")
   411  	}
   412  
   413  	fileRecursive := filepath.Join(testDir, "testDirTwo", "file_recursive.txt")
   414  	if _, found := w.files[fileRecursive]; found {
   415  		t.Errorf("expected to not find %s", fileRecursive)
   416  	}
   417  
   418  	fileTxt := filepath.Join(testDir, "file.txt")
   419  	if _, found := w.files[fileTxt]; !found {
   420  		t.Errorf("expected to find %s", fileTxt)
   421  	}
   422  
   423  	if w.files[fileTxt].Name() != "file.txt" {
   424  		t.Errorf("expected w.files[%q].Name() to be file.txt, got %s",
   425  			fileTxt, w.files[fileTxt].Name())
   426  	}
   427  
   428  	dirTwo := filepath.Join(testDir, "testDirTwo")
   429  	if _, found := w.files[dirTwo]; !found {
   430  		t.Errorf("expected to find %s directory", dirTwo)
   431  	}
   432  
   433  	if w.files[dirTwo].Name() != "testDirTwo" {
   434  		t.Errorf("expected w.files[%q].Name() to be testDirTwo, got %s",
   435  			dirTwo, w.files[dirTwo].Name())
   436  	}
   437  }
   438  
   439  func TestWatcherAddRecursive(t *testing.T) {
   440  	testDir, teardown := setup(t)
   441  	defer teardown()
   442  
   443  	w := New(os.DirFS("/"))
   444  
   445  	if err := w.AddRecursive(testDir); err != nil {
   446  		t.Fatal(err)
   447  	}
   448  
   449  	// Make sure len(w.files) is 8.
   450  	if len(w.files) != 8 {
   451  		t.Errorf("expected 8 files, found %d", len(w.files))
   452  	}
   453  
   454  	// Make sure w.names contains testDir
   455  	if _, found := w.names[testDir]; !found {
   456  		t.Errorf("expected w.names to contain testDir")
   457  	}
   458  
   459  	dirTwo := filepath.Join(testDir, "testDirTwo")
   460  	if _, found := w.files[dirTwo]; !found {
   461  		t.Errorf("expected to find %s directory", dirTwo)
   462  	}
   463  
   464  	if w.files[dirTwo].Name() != "testDirTwo" {
   465  		t.Errorf("expected w.files[%q].Name() to be testDirTwo, got %s",
   466  			"testDirTwo", w.files[dirTwo].Name())
   467  	}
   468  
   469  	fileRecursive := filepath.Join(dirTwo, "file_recursive.txt")
   470  	if _, found := w.files[fileRecursive]; !found {
   471  		t.Errorf("expected to find %s directory", fileRecursive)
   472  	}
   473  
   474  	if w.files[fileRecursive].Name() != "file_recursive.txt" {
   475  		t.Errorf("expected w.files[%q].Name() to be file_recursive.txt, got %s",
   476  			fileRecursive, w.files[fileRecursive].Name())
   477  	}
   478  }
   479  
   480  func TestWatcherAddNotFound(t *testing.T) {
   481  	w := New(os.DirFS("/"))
   482  
   483  	// Make sure there is an error when adding a
   484  	// non-existent file/folder.
   485  	if err := w.AddRecursive("random_filename.txt"); err == nil {
   486  		t.Error("expected a file not found error")
   487  	}
   488  }
   489  
   490  func TestWatcherRemoveRecursive(t *testing.T) {
   491  	testDir, teardown := setup(t)
   492  	defer teardown()
   493  
   494  	w := New(os.DirFS("/"))
   495  
   496  	// Add the testDir to the watchlist.
   497  	if err := w.AddRecursive(testDir); err != nil {
   498  		t.Fatal(err)
   499  	}
   500  
   501  	// Make sure len(w.files) is 8.
   502  	if len(w.files) != 8 {
   503  		t.Errorf("expected 8 files, found %d", len(w.files))
   504  	}
   505  
   506  	// Now remove the folder from the watchlist.
   507  	if err := w.RemoveRecursive(testDir); err != nil {
   508  		t.Error(err)
   509  	}
   510  
   511  	// Now check that there is nothing being watched.
   512  	if len(w.files) != 0 {
   513  		t.Errorf("expected len(w.files) to be 0, got %d", len(w.files))
   514  	}
   515  
   516  	// Make sure len(w.names) is now 0.
   517  	if len(w.names) != 0 {
   518  		t.Errorf("expected len(w.names) to be empty, len(w.names): %d", len(w.names))
   519  	}
   520  }
   521  
   522  func TestListFiles(t *testing.T) {
   523  	testDir, teardown := setup(t)
   524  	defer teardown()
   525  
   526  	w := New(os.DirFS("/"))
   527  	w.AddRecursive(testDir)
   528  
   529  	fileList := w.retrieveFileList()
   530  	if fileList == nil {
   531  		t.Error("expected file list to not be empty")
   532  	}
   533  
   534  	// Make sure fInfoTest contains the correct fs.FileInfo names.
   535  	fname := filepath.Join(testDir, "file.txt")
   536  	if fileList[fname].Name() != "file.txt" {
   537  		t.Errorf("expected fileList[%s].Name() to be file.txt, got %s",
   538  			fname, fileList[fname].Name())
   539  	}
   540  
   541  	// Try to call list on a file that's not a directory.
   542  	fileList, err := w.list(fname)
   543  	if err != nil {
   544  		t.Error("expected err to be nil")
   545  	}
   546  	if len(fileList) != 1 {
   547  		t.Errorf("expected len of file list to be 1, got %d", len(fileList))
   548  	}
   549  }
   550  
   551  func TestTriggerEvent(t *testing.T) {
   552  	w := New(os.DirFS("/"))
   553  
   554  	var wg sync.WaitGroup
   555  	wg.Add(1)
   556  
   557  	go func() {
   558  		defer wg.Done()
   559  
   560  		select {
   561  		case event := <-w.Event:
   562  			if event.Name() != "triggered event" {
   563  				t.Errorf("expected event file name to be triggered event, got %s",
   564  					event.Name())
   565  			}
   566  		case <-time.After(time.Millisecond * 250):
   567  			t.Fatal("received no event from Event channel")
   568  		}
   569  	}()
   570  
   571  	go func() {
   572  		// Start the watching process.
   573  		if err := w.Start(time.Millisecond * 100); err != nil {
   574  			t.Fatal(err)
   575  		}
   576  	}()
   577  
   578  	w.TriggerEvent(Create, nil)
   579  
   580  	wg.Wait()
   581  }
   582  
   583  func TestEventAddFile(t *testing.T) {
   584  	testDir, teardown := setup(t)
   585  	defer teardown()
   586  
   587  	w := New(os.DirFS("/"))
   588  	w.FilterOps(Create)
   589  
   590  	// Add the testDir to the watchlist.
   591  	if err := w.AddRecursive(testDir); err != nil {
   592  		t.Fatal(err)
   593  	}
   594  
   595  	files := map[string]bool{
   596  		"newfile_1.txt": false,
   597  		"newfile_2.txt": false,
   598  		"newfile_3.txt": false,
   599  	}
   600  
   601  	for f := range files {
   602  		filePath := filepath.Join("/"+testDir, f)
   603  		if err := ioutil.WriteFile(filePath, []byte{}, 0755); err != nil {
   604  			t.Error(err)
   605  		}
   606  	}
   607  
   608  	var wg sync.WaitGroup
   609  	wg.Add(1)
   610  
   611  	go func() {
   612  		defer wg.Done()
   613  
   614  		events := 0
   615  		for {
   616  			select {
   617  			case event := <-w.Event:
   618  				if event.Op != Create {
   619  					t.Errorf("expected event to be Create, got %s", event.Op)
   620  				}
   621  
   622  				files[event.Name()] = true
   623  				events++
   624  
   625  				// Check Path and OldPath content
   626  				newFile := filepath.Join(testDir, event.Name())
   627  				if event.Path != newFile {
   628  					t.Errorf("Event.Path should be %s but got %s", newFile, event.Path)
   629  				}
   630  				if event.OldPath != "" {
   631  					t.Errorf("Event.OldPath should be empty on create, but got %s", event.OldPath)
   632  				}
   633  
   634  				if events == len(files) {
   635  					return
   636  				}
   637  			case <-time.After(time.Millisecond * 250):
   638  				for f, e := range files {
   639  					if !e {
   640  						t.Errorf("received no event for file %s", f)
   641  					}
   642  				}
   643  				return
   644  			}
   645  		}
   646  	}()
   647  
   648  	go func() {
   649  		// Start the watching process.
   650  		if err := w.Start(time.Millisecond * 100); err != nil {
   651  			t.Fatal(err)
   652  		}
   653  	}()
   654  
   655  	wg.Wait()
   656  }
   657  
   658  // TODO: TestIgnoreFiles
   659  func TestIgnoreFiles(t *testing.T) {}
   660  
   661  func TestEventDeleteFile(t *testing.T) {
   662  	testDir, teardown := setup(t)
   663  	defer teardown()
   664  
   665  	w := New(os.DirFS("/"))
   666  	w.FilterOps(Remove)
   667  
   668  	// Add the testDir to the watchlist.
   669  	if err := w.AddRecursive(testDir); err != nil {
   670  		t.Fatal(err)
   671  	}
   672  
   673  	files := map[string]bool{
   674  		"file_1.txt": false,
   675  		"file_2.txt": false,
   676  		"file_3.txt": false,
   677  	}
   678  
   679  	for f := range files {
   680  		filePath := filepath.Join("/"+testDir, f)
   681  		if err := os.Remove(filePath); err != nil {
   682  			t.Error(err)
   683  		}
   684  	}
   685  
   686  	var wg sync.WaitGroup
   687  	wg.Add(1)
   688  
   689  	go func() {
   690  		defer wg.Done()
   691  
   692  		events := 0
   693  		for {
   694  			select {
   695  			case event := <-w.Event:
   696  				if event.Op != Remove {
   697  					t.Errorf("expected event to be Remove, got %s", event.Op)
   698  				}
   699  
   700  				files[event.Name()] = true
   701  				events++
   702  
   703  				if events == len(files) {
   704  					return
   705  				}
   706  			case <-time.After(time.Millisecond * 250):
   707  				for f, e := range files {
   708  					if !e {
   709  						t.Errorf("received no event for file %s", f)
   710  					}
   711  				}
   712  				return
   713  			}
   714  		}
   715  	}()
   716  
   717  	go func() {
   718  		// Start the watching process.
   719  		if err := w.Start(time.Millisecond * 100); err != nil {
   720  			t.Fatal(err)
   721  		}
   722  	}()
   723  
   724  	wg.Wait()
   725  }
   726  
   727  func TestEventRenameFile(t *testing.T) {
   728  	testDir, teardown := setup(t)
   729  	defer teardown()
   730  
   731  	srcFilename := "file.txt"
   732  	dstFilename := "file1.txt"
   733  
   734  	w := New(os.DirFS("/"))
   735  	w.FilterOps(Rename)
   736  
   737  	// Add the testDir to the watchlist.
   738  	if err := w.AddRecursive(testDir); err != nil {
   739  		t.Fatal(err)
   740  	}
   741  
   742  	// Rename a file.
   743  	if err := os.Rename(
   744  		filepath.Join("/"+testDir, srcFilename),
   745  		filepath.Join("/"+testDir, dstFilename),
   746  	); err != nil {
   747  		t.Error(err)
   748  	}
   749  
   750  	var wg sync.WaitGroup
   751  	wg.Add(1)
   752  
   753  	go func() {
   754  		defer wg.Done()
   755  
   756  		select {
   757  		case event := <-w.Event:
   758  			if event.Op != Rename {
   759  				t.Errorf("expected event to be Rename, got %s", event.Op)
   760  			}
   761  
   762  			// Check Path and OldPath content
   763  			oldFile := filepath.Join(testDir, srcFilename)
   764  			newFile := filepath.Join(testDir, dstFilename)
   765  			if event.Path != newFile {
   766  				t.Errorf("Event.Path should be %s but got %s", newFile, event.Path)
   767  			}
   768  			if event.OldPath != oldFile {
   769  				t.Errorf("Event.OldPath should %s but got %s", oldFile, event.OldPath)
   770  			}
   771  
   772  		case <-time.After(time.Millisecond * 250):
   773  			t.Fatal("received no rename event")
   774  		}
   775  	}()
   776  
   777  	go func() {
   778  		// Start the watching process.
   779  		if err := w.Start(time.Millisecond * 100); err != nil {
   780  			t.Fatal(err)
   781  		}
   782  	}()
   783  
   784  	wg.Wait()
   785  }
   786  
   787  func TestEventChmodFile(t *testing.T) {
   788  	// Chmod is not supported under windows.
   789  	if runtime.GOOS == "windows" {
   790  		return
   791  	}
   792  
   793  	testDir, teardown := setup(t)
   794  	defer teardown()
   795  
   796  	w := New(os.DirFS("/"))
   797  	w.FilterOps(Chmod)
   798  
   799  	// Add the testDir to the watchlist.
   800  	if err := w.Add(testDir); err != nil {
   801  		t.Fatal(err)
   802  	}
   803  
   804  	files := map[string]bool{
   805  		"file_1.txt": false,
   806  		"file_2.txt": false,
   807  		"file_3.txt": false,
   808  	}
   809  
   810  	for f := range files {
   811  		filePath := filepath.Join("/"+testDir, f)
   812  		if err := os.Chmod(filePath, os.ModePerm); err != nil {
   813  			t.Error(err)
   814  		}
   815  	}
   816  
   817  	var wg sync.WaitGroup
   818  	wg.Add(1)
   819  
   820  	go func() {
   821  		defer wg.Done()
   822  
   823  		events := 0
   824  		for {
   825  			select {
   826  			case event := <-w.Event:
   827  				if event.Op != Chmod {
   828  					t.Errorf("expected event to be Remove, got %s", event.Op)
   829  				}
   830  
   831  				files[event.Name()] = true
   832  				events++
   833  
   834  				if events == len(files) {
   835  					return
   836  				}
   837  			case <-time.After(time.Millisecond * 250):
   838  				for f, e := range files {
   839  					if !e {
   840  						t.Errorf("received no event for file %s", f)
   841  					}
   842  				}
   843  				return
   844  			}
   845  		}
   846  	}()
   847  
   848  	go func() {
   849  		// Start the watching process.
   850  		if err := w.Start(time.Millisecond * 100); err != nil {
   851  			t.Fatal(err)
   852  		}
   853  	}()
   854  
   855  	wg.Wait()
   856  }
   857  
   858  func TestWatcherStartWithInvalidDuration(t *testing.T) {
   859  	w := New(os.DirFS("/"))
   860  
   861  	err := w.Start(0)
   862  	if err != ErrDurationTooShort {
   863  		t.Fatalf("expected ErrDurationTooShort error, got %s", err.Error())
   864  	}
   865  }
   866  
   867  func TestWatcherStartWhenAlreadyRunning(t *testing.T) {
   868  	w := New(os.DirFS("/"))
   869  
   870  	go func() {
   871  		err := w.Start(time.Millisecond * 100)
   872  		if err != nil {
   873  			t.Fatal(err)
   874  		}
   875  	}()
   876  	w.Wait()
   877  
   878  	err := w.Start(time.Millisecond * 100)
   879  	if err != ErrWatcherRunning {
   880  		t.Fatalf("expected ErrWatcherRunning error, got %s", err.Error())
   881  	}
   882  }
   883  
   884  func BenchmarkEventRenameFile(b *testing.B) {
   885  	testDir, teardown := setup(b)
   886  	defer teardown()
   887  
   888  	w := New(os.DirFS("/"))
   889  	w.FilterOps(Rename)
   890  
   891  	// Add the testDir to the watchlist.
   892  	if err := w.AddRecursive(testDir); err != nil {
   893  		b.Fatal(err)
   894  	}
   895  
   896  	go func() {
   897  		// Start the watching process.
   898  		if err := w.Start(time.Millisecond); err != nil {
   899  			b.Fatal(err)
   900  		}
   901  	}()
   902  
   903  	var filenameFrom = filepath.Join("/"+testDir, "file.txt")
   904  	var filenameTo = filepath.Join("/"+testDir, "file1.txt")
   905  
   906  	for i := 0; i < b.N; i++ {
   907  		// Rename a file.
   908  		if err := os.Rename(
   909  			filenameFrom,
   910  			filenameTo,
   911  		); err != nil {
   912  			b.Error(err)
   913  		}
   914  
   915  		select {
   916  		case event := <-w.Event:
   917  			if event.Op != Rename {
   918  				b.Errorf("expected event to be Rename, got %s", event.Op)
   919  			}
   920  		case <-time.After(time.Millisecond * 250):
   921  			b.Fatal("received no rename event")
   922  		}
   923  
   924  		filenameFrom, filenameTo = filenameTo, filenameFrom
   925  	}
   926  }
   927  
   928  func BenchmarkListFiles(b *testing.B) {
   929  	testDir, teardown := setup(b)
   930  	defer teardown()
   931  
   932  	w := New(os.DirFS("/"))
   933  	err := w.AddRecursive(testDir)
   934  	if err != nil {
   935  		b.Fatal(err)
   936  	}
   937  
   938  	for i := 0; i < b.N; i++ {
   939  		fileList := w.retrieveFileList()
   940  		if fileList == nil {
   941  			b.Fatal("expected file list to not be empty")
   942  		}
   943  	}
   944  }
   945  
   946  func TestClose(t *testing.T) {
   947  	testDir, teardown := setup(t)
   948  	defer teardown()
   949  
   950  	w := New(os.DirFS("/"))
   951  
   952  	err := w.Add(testDir)
   953  	if err != nil {
   954  		t.Fatal(err)
   955  	}
   956  
   957  	wf := w.WatchedFiles()
   958  	fileList := w.retrieveFileList()
   959  
   960  	if len(wf) != len(fileList) {
   961  		t.Fatalf("expected len of wf to be %d, got %d", len(fileList), len(wf))
   962  	}
   963  
   964  	// Call close on the watcher even though it's not running.
   965  	w.Close()
   966  
   967  	wf = w.WatchedFiles()
   968  	fileList = w.retrieveFileList()
   969  
   970  	// Close will be a no-op so there will still be len(fileList) files.
   971  	if len(wf) != len(fileList) {
   972  		t.Fatalf("expected len of wf to be %d, got %d", len(fileList), len(wf))
   973  	}
   974  
   975  	// Set running to true.
   976  	w.running = true
   977  
   978  	// Now close the watcher.
   979  	go func() {
   980  		// Receive from the w.close channel to avoid a deadlock.
   981  		<-w.close
   982  	}()
   983  
   984  	w.Close()
   985  
   986  	wf = w.WatchedFiles()
   987  
   988  	// Close will be a no-op so there will still be len(fileList) files.
   989  	if len(wf) != 0 {
   990  		t.Fatalf("expected len of wf to be 0, got %d", len(wf))
   991  	}
   992  
   993  }
   994  
   995  func TestWatchedFiles(t *testing.T) {
   996  	testDir, teardown := setup(t)
   997  	defer teardown()
   998  
   999  	w := New(os.DirFS("/"))
  1000  
  1001  	err := w.Add(testDir)
  1002  	if err != nil {
  1003  		t.Fatal(err)
  1004  	}
  1005  
  1006  	wf := w.WatchedFiles()
  1007  	fileList := w.retrieveFileList()
  1008  
  1009  	if len(wf) != len(fileList) {
  1010  		t.Fatalf("expected len of wf to be %d, got %d", len(fileList), len(wf))
  1011  	}
  1012  
  1013  	for path := range fileList {
  1014  		if _, found := wf[path]; !found {
  1015  			t.Fatalf("%s not found in watched file's list", path)
  1016  		}
  1017  	}
  1018  }
  1019  
  1020  func TestSetMaxEvents(t *testing.T) {
  1021  	w := New(os.DirFS("/"))
  1022  
  1023  	if w.maxEvents != 0 {
  1024  		t.Fatalf("expected max events to be 0, got %d", w.maxEvents)
  1025  	}
  1026  
  1027  	w.SetMaxEvents(3)
  1028  
  1029  	if w.maxEvents != 3 {
  1030  		t.Fatalf("expected max events to be 3, got %d", w.maxEvents)
  1031  	}
  1032  }
  1033  
  1034  func TestOpsString(t *testing.T) {
  1035  	testCases := []struct {
  1036  		want     Op
  1037  		expected string
  1038  	}{
  1039  		{Create, "CREATE"},
  1040  		{Write, "WRITE"},
  1041  		{Remove, "REMOVE"},
  1042  		{Rename, "RENAME"},
  1043  		{Chmod, "CHMOD"},
  1044  		{Move, "MOVE"},
  1045  		{Op(10), "???"},
  1046  	}
  1047  
  1048  	for _, tc := range testCases {
  1049  		if tc.want.String() != tc.expected {
  1050  			t.Errorf("expected %s, got %s", tc.expected, tc.want.String())
  1051  		}
  1052  	}
  1053  }