From d0c711b5001568fd8cbd9a9e9074741d275de94c Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 4 Apr 2024 09:47:59 +0200 Subject: [PATCH] Improve detecting renames in file watcher. --- file_watcher.go | 8 ++-- file_watcher_test.go | 95 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/file_watcher.go b/file_watcher.go index 13d5c44..be4d375 100644 --- a/file_watcher.go +++ b/file_watcher.go @@ -73,11 +73,9 @@ func NewFileWatcher(filename string, callback FileWatcherCallback) (*FileWatcher return nil, err } - if filename != realFilename { - if err := watcher.Add(path.Dir(filename)); err != nil { - watcher.Close() // nolint - return nil, err - } + if err := watcher.Add(path.Dir(filename)); err != nil { + watcher.Close() // nolint + return nil, err } w := &FileWatcher{ diff --git a/file_watcher_test.go b/file_watcher_test.go index f64fdca..268a68e 100644 --- a/file_watcher_test.go +++ b/file_watcher_test.go @@ -91,6 +91,51 @@ func TestFileWatcher_File(t *testing.T) { } } +func TestFileWatcher_Rename(t *testing.T) { + tmpdir := t.TempDir() + filename := path.Join(tmpdir, "test.txt") + if err := os.WriteFile(filename, []byte("Hello world!"), 0644); err != nil { + t.Fatal(err) + } + + modified := make(chan struct{}) + w, err := NewFileWatcher(filename, func(filename string) { + modified <- struct{}{} + }) + if err != nil { + t.Fatal(err) + } + defer w.Close() + + filename2 := path.Join(tmpdir, "test.txt.tmp") + if err := os.WriteFile(filename2, []byte("Updated"), 0644); err != nil { + t.Fatal(err) + } + + ctxTimeout, cancel := context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + t.Error("should not have received another event") + case <-ctxTimeout.Done(): + } + + if err := os.Rename(filename2, filename); err != nil { + t.Fatal(err) + } + <-modified + + ctxTimeout, cancel = context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + t.Error("should not have received another event") + case <-ctxTimeout.Done(): + } +} + func TestFileWatcher_Symlink(t *testing.T) { tmpdir := t.TempDir() sourceFilename := path.Join(tmpdir, "test1.txt") @@ -211,3 +256,53 @@ func TestFileWatcher_OtherSymlink(t *testing.T) { case <-ctxTimeout.Done(): } } + +func TestFileWatcher_RenameSymlinkTarget(t *testing.T) { + tmpdir := t.TempDir() + sourceFilename1 := path.Join(tmpdir, "test1.txt") + if err := os.WriteFile(sourceFilename1, []byte("Hello world!"), 0644); err != nil { + t.Fatal(err) + } + + filename := path.Join(tmpdir, "test.txt") + if err := os.Symlink(sourceFilename1, filename); err != nil { + t.Fatal(err) + } + + modified := make(chan struct{}) + w, err := NewFileWatcher(filename, func(filename string) { + modified <- struct{}{} + }) + if err != nil { + t.Fatal(err) + } + defer w.Close() + + sourceFilename2 := path.Join(tmpdir, "test1.txt.tmp") + if err := os.WriteFile(sourceFilename2, []byte("Updated"), 0644); err != nil { + t.Fatal(err) + } + + ctxTimeout, cancel := context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + t.Error("should not have received another event") + case <-ctxTimeout.Done(): + } + + if err := os.Rename(sourceFilename2, sourceFilename1); err != nil { + t.Fatal(err) + } + <-modified + + ctxTimeout, cancel = context.WithTimeout(context.Background(), testWatcherNoEventTimeout) + defer cancel() + + select { + case <-modified: + t.Error("should not have received another event") + case <-ctxTimeout.Done(): + } +}