github.com/FabianKramm/notify@v0.9.3-0.20210719135015-4705c29227a1/notify_test.go (about)

     1  // Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
     2  // Use of this source code is governed by the MIT license that can be
     3  // found in the LICENSE file.
     4  
     5  // +build darwin linux freebsd dragonfly netbsd openbsd windows solaris
     6  
     7  package notify
     8  
     9  import (
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"testing"
    14  	"time"
    15  )
    16  
    17  func TestNotifyExample(t *testing.T) {
    18  	n := NewNotifyTest(t, "testdata/vfs.txt")
    19  	defer n.Close()
    20  
    21  	ch := NewChans(3)
    22  
    23  	// Watch-points can be set explicitly via Watch/Stop calls...
    24  	n.Watch("src/github.com/rjeczalik/fs", ch[0], Write)
    25  	n.Watch("src/github.com/pblaszczyk/qttu", ch[0], Write)
    26  	n.Watch("src/github.com/pblaszczyk/qttu/...", ch[1], Create)
    27  	n.Watch("src/github.com/rjeczalik/fs/cmd/...", ch[2], Remove)
    28  
    29  	cases := []NCase{
    30  		// i=0
    31  		{
    32  			Event:    write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
    33  			Receiver: Chans{ch[0]},
    34  		},
    35  		// TODO(rjeczalik): #62
    36  		// i=1
    37  		// {
    38  		//	Event:    write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")),
    39  		//	Receiver: Chans{ch[0]},
    40  		// },
    41  		// i=2
    42  		{
    43  			Event:    write(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go", []byte("XD")),
    44  			Receiver: nil,
    45  		},
    46  		// i=3
    47  		{
    48  			Event:    create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swp"),
    49  			Receiver: Chans{ch[1]},
    50  		},
    51  		// i=4
    52  		{
    53  			Event:    create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swo"),
    54  			Receiver: Chans{ch[1]},
    55  		},
    56  		// i=5
    57  		{
    58  			Event:    remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go"),
    59  			Receiver: Chans{ch[2]},
    60  		},
    61  	}
    62  
    63  	n.ExpectNotifyEvents(cases, ch)
    64  
    65  	// ...or using Call structures.
    66  	stops := [...]Call{
    67  		// i=0
    68  		{
    69  			F: FuncStop,
    70  			C: ch[0],
    71  		},
    72  		// i=1
    73  		{
    74  			F: FuncStop,
    75  			C: ch[1],
    76  		},
    77  	}
    78  
    79  	n.Call(stops[:]...)
    80  
    81  	cases = []NCase{
    82  		// i=0
    83  		{
    84  			Event:    write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
    85  			Receiver: nil,
    86  		},
    87  		// i=1
    88  		{
    89  			Event:    write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")),
    90  			Receiver: nil,
    91  		},
    92  		// i=2
    93  		{
    94  			Event:    create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swr"),
    95  			Receiver: nil,
    96  		},
    97  		// i=3
    98  		{
    99  			Event:    remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/main.go"),
   100  			Receiver: Chans{ch[2]},
   101  		},
   102  	}
   103  
   104  	n.ExpectNotifyEvents(cases, ch)
   105  }
   106  
   107  func TestStop(t *testing.T) {
   108  	t.Skip("TODO(rjeczalik)")
   109  }
   110  
   111  func TestRenameInRoot(t *testing.T) {
   112  	tmpDir, err := ioutil.TempDir("", "notify_test-")
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  	defer os.RemoveAll(tmpDir)
   117  
   118  	c := make(chan EventInfo, 100)
   119  	first := filepath.Join(tmpDir, "foo")
   120  	second := filepath.Join(tmpDir, "bar")
   121  	file := filepath.Join(second, "file")
   122  
   123  	mustT(t, os.Mkdir(first, 0777))
   124  
   125  	if err := Watch(tmpDir+"/...", c, All); err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	defer Stop(c)
   129  
   130  	mustT(t, os.Rename(first, second))
   131  	time.Sleep(50 * time.Millisecond) // Need some time to process rename.
   132  	fd, err := os.Create(file)
   133  	mustT(t, err)
   134  	fd.Close()
   135  
   136  	timeout := time.After(time.Second)
   137  	for {
   138  		select {
   139  		case ev := <-c:
   140  			if ev.Path() == file {
   141  				return
   142  			}
   143  			t.Log(ev.Path())
   144  		case <-timeout:
   145  			t.Fatal("timed out before receiving event")
   146  		}
   147  	}
   148  }
   149  
   150  func prepareTestDir(t *testing.T) string {
   151  	tmpDir, err := ioutil.TempDir("", "notify_test-")
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  	
   156  	// resolve paths on OSX
   157  	s, err := filepath.EvalSymlinks(tmpDir)
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  
   162  	// create test dir
   163  	err = os.MkdirAll(filepath.Join(s, "a/b/c"), 0755)
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	
   168  	return s
   169  }
   170  
   171  func mustWatch(t *testing.T, path string) chan EventInfo {
   172  	c := make(chan EventInfo, 1)
   173  	err := Watch(path+"...", c, All)
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  
   178  	return c
   179  }
   180  
   181  func TestAddParentAfterStop(t *testing.T) {
   182  	tmpDir := prepareTestDir(t)
   183  	defer os.RemoveAll(tmpDir)
   184  
   185  	// watch a child and parent path across multiple channels.
   186  	// this can happen in any order.
   187  	ch1 := mustWatch(t, filepath.Join(tmpDir, "a/b"))
   188  	ch2 := mustWatch(t, filepath.Join(tmpDir, "a/b/c"))
   189  	defer Stop(ch2)
   190  
   191  	// unwatch ./a/b -- this is what causes the panic on the next line.
   192  	// note that this also fails if we notify.Stop(ch1) instead.
   193  	Stop(ch1)
   194  	
   195  	// add parent watchpoint
   196  	ch3 := mustWatch(t, filepath.Join(tmpDir, "a"))
   197  	defer Stop(ch3)
   198  
   199  	// fire an event
   200  	filePath := filepath.Join(tmpDir, "a/b/c/d")
   201  	go func() { _ = ioutil.WriteFile(filePath, []byte("X"), 0664) }()
   202  
   203  	timeout := time.After(5 * time.Second)
   204  	for {
   205  		select {
   206  		case ev := <-ch2:
   207  			t.Log(ev.Path(), ev.Event())
   208  			if ev.Path() == filePath && (ev.Event() == Create || ev.Event() == Write) {
   209  				return
   210  			}
   211  		case <-timeout:
   212  			t.Fatal("timed out before receiving event")
   213  		}
   214  	}
   215  }
   216  
   217  func TestStopChild(t *testing.T) {
   218  	tmpDir := prepareTestDir(t)
   219  	defer os.RemoveAll(tmpDir)
   220  
   221  	// watch a child and parent path across multiple channels.
   222  	// this can happen in any order.
   223  	ch1 := mustWatch(t, filepath.Join(tmpDir, "a"))
   224  	defer Stop(ch1)
   225  	ch2 := mustWatch(t, filepath.Join(tmpDir, "a/b/c"))
   226  
   227  	// this leads to tmpDir/a being unwatched
   228  	Stop(ch2)
   229  
   230  	// fire an event
   231  	filePath := filepath.Join(tmpDir, "a/b/c/d")
   232  	go func() { _ = ioutil.WriteFile(filePath, []byte("X"), 0664) }()
   233  
   234  	timeout := time.After(5 * time.Second)
   235  	for {
   236  		select {
   237  		case ev := <-ch1:
   238  			t.Log(ev.Path(), ev.Event())
   239  			if ev.Path() == filePath && (ev.Event() == Create || ev.Event() == Write) {
   240  				return
   241  			}
   242  		case <-timeout:
   243  			t.Fatal("timed out before receiving event")
   244  		}
   245  	}
   246  }
   247  
   248  func TestRecreated(t *testing.T) {
   249  	tmpDir, err := ioutil.TempDir("", "notify_test-")
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	defer os.RemoveAll(tmpDir)
   254  
   255  	dir := filepath.Join(tmpDir, "folder")
   256  	file := filepath.Join(dir, "file")
   257  
   258  	// Start watching
   259  	eventChan := make(chan EventInfo, 1000)
   260  	mustT(t, Watch(tmpDir+"/...", eventChan, All))
   261  	defer Stop(eventChan)
   262  
   263  	recreateFolder := func() {
   264  		// Give the sync some time to process events
   265  		_ = os.RemoveAll(dir)
   266  		mustT(t, os.Mkdir(dir, 0777))
   267  		time.Sleep(100 * time.Millisecond)
   268  
   269  		// Create a file
   270  		mustT(t, ioutil.WriteFile(file, []byte("abc"), 0666))
   271  	}
   272  	timeout := time.After(5 * time.Second)
   273  	checkCreated := func() {
   274  		for {
   275  			select {
   276  			case ev := <-eventChan:
   277  				t.Log(ev.Path(), ev.Event())
   278  				if ev.Path() == file && ev.Event() == Create {
   279  					return
   280  				}
   281  			case <-timeout:
   282  				t.Fatal("timed out before receiving event")
   283  			}
   284  		}
   285  	}
   286  
   287  	// 1. Create a folder and a file within it
   288  	// This will create a node in the internal tree for the subfolder test/folder
   289  	// Will create a new inotify watch for the folder
   290  	t.Log("######## First ########")
   291  	recreateFolder()
   292  	checkCreated()
   293  
   294  	// 2. Create a folder and a file within it again
   295  	// This will set the events for the subfolder test/folder in the internal tree
   296  	// Will create a new inotify watch for the folder because events differ
   297  	t.Log("######## Second ########")
   298  	recreateFolder()
   299  	checkCreated()
   300  
   301  	// 3. Create a folder and a file within it yet again
   302  	// This time no new inotify watch will be created, because the events
   303  	// and node already exist in the internal tree and all subsequent events
   304  	// are lost, hence there is no event for the created file here anymore
   305  	t.Log("######## Third ########")
   306  	recreateFolder()
   307  	checkCreated()
   308  }
   309  
   310  func mustT(t testing.TB, err error) {
   311  	t.Helper()
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  }