diff options
| author | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2026-07-06 21:54:00 +0200 |
|---|---|---|
| committer | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2026-07-06 21:54:00 +0200 |
| commit | 5a2d70be3ce8524c366f4dd303b950974f05aa7c (patch) | |
| tree | f2a8507b287738043f50098f20fc74a0259fa3de /watcher_test.go | |
| parent | eb87e0acea1f3f3bb31eb8a32fb1de1fc4d3bc7a (diff) | |
| download | sourcewatch-5a2d70be3ce8524c366f4dd303b950974f05aa7c.tar.gz sourcewatch-5a2d70be3ce8524c366f4dd303b950974f05aa7c.zip | |
Add symlink watcher support with tests
- syncCreate: remove existing file before creating symlink
- syncWrite: handle symlink target changes (remove + recreate)
- Added 4 new tests:
- CreateSymlink: new symlink synced to /data
- UpdateSymlinkTarget: symlink target change updates /data
- RemoveSymlink: symlink removal synced
- ModifySymlinkTargetFile: target file change preserves symlink
- 39 tests passing
Diffstat (limited to 'watcher_test.go')
| -rw-r--r-- | watcher_test.go | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/watcher_test.go b/watcher_test.go index db0defb..8fac419 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -318,6 +318,155 @@ func TestWatcher_NestedGitignore_CreateUnignore(t *testing.T) { assertFileEventually(t, data, "sub/important.txt", "keep me", 2*time.Second) } +func TestWatcher_CreateSymlink(t *testing.T) { + host := t.TempDir() + data := t.TempDir() + + writeFile(t, host, "target.txt", "target content") + + stack := NewGitignoreStack(host) + manifest, err := initialSync(host, data, stack, false) + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go watch(ctx, host, data, manifest, stack, false, false) + + time.Sleep(100 * time.Millisecond) + + if err := os.Symlink("target.txt", filepath.Join(host, "link.txt")); err != nil { + t.Fatal(err) + } + + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + info, err := os.Lstat(filepath.Join(data, "link.txt")) + if err == nil && info.Mode()&os.ModeSymlink != 0 { + target, _ := os.Readlink(filepath.Join(data, "link.txt")) + if target == "target.txt" { + return + } + } + time.Sleep(50 * time.Millisecond) + } + t.Error("symlink not created in /data within timeout") +} + +func TestWatcher_UpdateSymlinkTarget(t *testing.T) { + host := t.TempDir() + data := t.TempDir() + + writeFile(t, host, "v1.txt", "version 1") + writeFile(t, host, "v2.txt", "version 2") + if err := os.Symlink("v1.txt", filepath.Join(host, "link.txt")); err != nil { + t.Fatal(err) + } + + stack := NewGitignoreStack(host) + manifest, err := initialSync(host, data, stack, false) + if err != nil { + t.Fatal(err) + } + + // Verify initial symlink + linkInfo, err := os.Lstat(filepath.Join(data, "link.txt")) + if err != nil || linkInfo.Mode()&os.ModeSymlink == 0 { + t.Fatal("initial symlink not created") + } + target, _ := os.Readlink(filepath.Join(data, "link.txt")) + if target != "v1.txt" { + t.Fatalf("initial symlink target = %q, want %q", target, "v1.txt") + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go watch(ctx, host, data, manifest, stack, false, false) + + time.Sleep(100 * time.Millisecond) + + // Update symlink to point to v2.txt + os.Remove(filepath.Join(host, "link.txt")) + os.Symlink("v2.txt", filepath.Join(host, "link.txt")) + + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + info, err := os.Lstat(filepath.Join(data, "link.txt")) + if err == nil && info.Mode()&os.ModeSymlink != 0 { + tgt, _ := os.Readlink(filepath.Join(data, "link.txt")) + if tgt == "v2.txt" { + return + } + } + time.Sleep(50 * time.Millisecond) + } + t.Error("symlink target not updated within timeout") +} + +func TestWatcher_RemoveSymlink(t *testing.T) { + host := t.TempDir() + data := t.TempDir() + + writeFile(t, host, "target.txt", "content") + if err := os.Symlink("target.txt", filepath.Join(host, "link.txt")); err != nil { + t.Fatal(err) + } + + stack := NewGitignoreStack(host) + manifest, err := initialSync(host, data, stack, false) + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go watch(ctx, host, data, manifest, stack, false, false) + + time.Sleep(100 * time.Millisecond) + + os.Remove(filepath.Join(host, "link.txt")) + + assertFileRemovedEventually(t, data, "link.txt", 2*time.Second) +} + +func TestWatcher_ModifySymlinkTargetFile(t *testing.T) { + host := t.TempDir() + data := t.TempDir() + + writeFile(t, host, "target.txt", "original") + if err := os.Symlink("target.txt", filepath.Join(host, "link.txt")); err != nil { + t.Fatal(err) + } + + stack := NewGitignoreStack(host) + manifest, err := initialSync(host, data, stack, false) + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go watch(ctx, host, data, manifest, stack, false, false) + + time.Sleep(100 * time.Millisecond) + + // Modify the target file, not the symlink itself + writeFile(t, host, "target.txt", "modified") + + // The symlink should still exist and point to the target + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + info, err := os.Lstat(filepath.Join(data, "link.txt")) + if err == nil && info.Mode()&os.ModeSymlink != 0 { + // Symlink still exists, which is correct + return + } + time.Sleep(50 * time.Millisecond) + } + t.Error("symlink lost after target file modification") +} + // Helpers func assertFileEventually(t *testing.T, dir, path, expectedContent string, timeout time.Duration) { |
