github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/filesystem/watching/watch_test.go (about)

     1  package watching
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"runtime"
     7  	"testing"
     8  	"time"
     9  )
    10  
    11  const (
    12  	// maximumEventWaitTime is the maximum amount of time that verifyWatchEvent
    13  	// will wait for an event to be received.
    14  	maximumEventWaitTime = 5 * time.Second
    15  )
    16  
    17  // verifyWatchEvent is a helper function to verify that events are received by a
    18  // watcher. It accepts a RecursiveWatcher, but can also be used for a
    19  // NonRecursiveWatcher (since the interface semantics are compatible). The paths
    20  // map may be modified by this function and should thus not be reused.
    21  func verifyWatchEvent(t *testing.T, watcher RecursiveWatcher, paths map[string]bool) {
    22  	// Indicate that this is a helper function.
    23  	t.Helper()
    24  
    25  	// Create a deadline for event reception and ensure its cancellation.
    26  	deadline := time.NewTimer(maximumEventWaitTime)
    27  	defer deadline.Stop()
    28  
    29  	// Perform the waiting operation.
    30  	for len(paths) > 0 {
    31  		select {
    32  		case path := <-watcher.Events():
    33  			delete(paths, path)
    34  		case err := <-watcher.Errors():
    35  			t.Fatal("watcher error:", err)
    36  		case <-deadline.C:
    37  			t.Fatal("event reception deadline exceeded:", paths)
    38  		}
    39  	}
    40  }
    41  
    42  // TestRecursiveWatcher tests the platform's RecursiveWatcher implementation (if
    43  // any) with a simple set of filesystem operations.
    44  func TestRecursiveWatcher(t *testing.T) {
    45  	// Skip this test if recursive watchig is unsupported.
    46  	if !RecursiveWatchingSupported {
    47  		t.Skip()
    48  	}
    49  
    50  	// Create a temporary directory (that will be automatically removed).
    51  	directory := t.TempDir()
    52  
    53  	// Create the watcher and defer its termination.
    54  	watcher, err := NewRecursiveWatcher(directory)
    55  	if err != nil {
    56  		t.Fatal("unable to establish watch:", err)
    57  	}
    58  	defer watcher.Terminate()
    59  
    60  	// Create a subdirectory.
    61  	subdirectoryRelative := "subdirectory"
    62  	subdirectoryAbsolute := filepath.Join(directory, subdirectoryRelative)
    63  	if err := os.Mkdir(subdirectoryAbsolute, 0700); err != nil {
    64  		t.Fatal("unable to create subdirectory:", err)
    65  	}
    66  	verifyWatchEvent(t, watcher, map[string]bool{subdirectoryRelative: true})
    67  
    68  	// Create a file inside the subdirectory.
    69  	fileRelative := "subdirectory/file"
    70  	fileAbsolute := filepath.Join(directory, fileRelative)
    71  	if err := os.WriteFile(fileAbsolute, nil, 0600); err != nil {
    72  		t.Fatal("unable to create test file:", err)
    73  	}
    74  	verifyWatchEvent(t, watcher, map[string]bool{fileRelative: true})
    75  
    76  	// Modify the test file.
    77  	if err := os.WriteFile(fileAbsolute, []byte("data"), 0600); err != nil {
    78  		t.Fatal("unable to modify test file:", err)
    79  	}
    80  	verifyWatchEvent(t, watcher, map[string]bool{fileRelative: true})
    81  
    82  	// If we're not on Windows, test that we detect permissions changes.
    83  	if runtime.GOOS != "windows" {
    84  		if err := os.Chmod(fileAbsolute, 0700); err != nil {
    85  			t.Fatal("unable to change file permissions:", err)
    86  		}
    87  		verifyWatchEvent(t, watcher, map[string]bool{fileRelative: true})
    88  	}
    89  
    90  	// Remove the test file.
    91  	if err := os.Remove(fileAbsolute); err != nil {
    92  		t.Fatal("unable to remove test file:", err)
    93  	}
    94  	verifyWatchEvent(t, watcher, map[string]bool{fileRelative: true})
    95  }
    96  
    97  // TestNonRecursiveWatcher tests the platform's NonRecursiveWatcher
    98  // implementation (if any) with a simple set of filesystem operations.
    99  func TestNonRecursiveWatcher(t *testing.T) {
   100  	// Skip this test if non-recursive watchig is unsupported.
   101  	if !NonRecursiveWatchingSupported {
   102  		t.Skip()
   103  	}
   104  
   105  	// Create a temporary directory (that will be automatically removed).
   106  	directory := t.TempDir()
   107  
   108  	// Create the watcher and defer its termination.
   109  	watcher, err := NewNonRecursiveWatcher()
   110  	if err != nil {
   111  		t.Fatal("unable to create watcher:", err)
   112  	}
   113  	watcher.Watch(directory)
   114  	defer watcher.Terminate()
   115  
   116  	// Create a subdirectory.
   117  	subdirectoryPath := filepath.Join(directory, "subdirectory")
   118  	if err := os.Mkdir(subdirectoryPath, 0700); err != nil {
   119  		t.Fatal("unable to create subdirectory:", err)
   120  	}
   121  	verifyWatchEvent(t, watcher, map[string]bool{subdirectoryPath: true})
   122  
   123  	// Create a file.
   124  	filePath := filepath.Join(directory, "file")
   125  	if err := os.WriteFile(filePath, nil, 0600); err != nil {
   126  		t.Fatal("unable to create test file:", err)
   127  	}
   128  	verifyWatchEvent(t, watcher, map[string]bool{filePath: true})
   129  
   130  	// Modify the test file.
   131  	if err := os.WriteFile(filePath, []byte("data"), 0600); err != nil {
   132  		t.Fatal("unable to modify test file:", err)
   133  	}
   134  	verifyWatchEvent(t, watcher, map[string]bool{filePath: true})
   135  
   136  	// If we're not on Windows, test that we detect permissions changes.
   137  	if runtime.GOOS != "windows" {
   138  		if err := os.Chmod(filePath, 0700); err != nil {
   139  			t.Fatal("unable to change file permissions:", err)
   140  		}
   141  		verifyWatchEvent(t, watcher, map[string]bool{filePath: true})
   142  	}
   143  
   144  	// Remove the test file.
   145  	if err := os.Remove(filePath); err != nil {
   146  		t.Fatal("unable to remove test file:", err)
   147  	}
   148  	verifyWatchEvent(t, watcher, map[string]bool{filePath: true})
   149  }