diff options
Diffstat (limited to 'watcher_test_helper.go')
| -rw-r--r-- | watcher_test_helper.go | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/watcher_test_helper.go b/watcher_test_helper.go new file mode 100644 index 0000000..519d3f6 --- /dev/null +++ b/watcher_test_helper.go @@ -0,0 +1,142 @@ +//go:build !windows + +package main + +import ( + "context" + "os" + "path/filepath" + "sync" + "time" + + "github.com/fsnotify/fsnotify" +) + +// watchForTests uses inotify directly for integration testing. +// This is only used in tests - production uses socket-based events from Windows watcher. +func watchForTests(ctx context.Context, hostDir, dataDir string, manifest *Manifest, stack *GitignoreStack, dryRun bool) error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + defer watcher.Close() + + var mu sync.Mutex + debounceTimers := make(map[string]*time.Timer) + debounceOps := make(map[string]fsnotify.Op) + + if err := addWatchersForTests(watcher, hostDir, stack); err != nil { + return err + } + + for { + select { + case <-ctx.Done(): + return nil + + case event, ok := <-watcher.Events: + if !ok { + return nil + } + + relPath, err := filepath.Rel(hostDir, event.Name) + if err != nil { + continue + } + + if isGitDir(relPath) { + continue + } + + if stack.IsIgnored(relPath) { + continue + } + + if event.Op&fsnotify.Create != 0 { + srcPath := filepath.Join(hostDir, relPath) + info, err := os.Lstat(srcPath) + if err == nil && info.IsDir() { + if hasGitignore(srcPath) { + stack.Push(srcPath) + } + watcher.Add(srcPath) + filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error { + if err != nil || !info.IsDir() || path == srcPath { + return nil + } + watcher.Add(path) + return nil + }) + } + } + + mu.Lock() + if existing, exists := debounceOps[relPath]; exists { + debounceTimers[relPath].Stop() + debounceOps[relPath] = existing | event.Op + } else { + debounceOps[relPath] = event.Op + } + path := relPath + debounceTimers[relPath] = time.AfterFunc(200*time.Millisecond, func() { + mu.Lock() + op := debounceOps[path] + delete(debounceOps, path) + delete(debounceTimers, path) + mu.Unlock() + + destPath := filepath.Join(dataDir, path) + srcPath := filepath.Join(hostDir, path) + + switch { + case op&fsnotify.Create != 0: + syncCreate(srcPath, destPath, path, manifest, stack, dryRun, watcher) + case op&fsnotify.Write != 0: + syncWrite(srcPath, destPath, path, manifest, dryRun) + case op&fsnotify.Remove != 0: + syncRemove(destPath, path, manifest, dryRun) + case op&fsnotify.Rename != 0: + syncRemove(destPath, path, manifest, dryRun) + } + }) + mu.Unlock() + + case err, ok := <-watcher.Errors: + if !ok { + return nil + } + _ = err + } + } +} + +func addWatchersForTests(watcher *fsnotify.Watcher, root string, stack *GitignoreStack) error { + return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + relPath, err := filepath.Rel(root, path) + if err != nil { + return err + } + + if isGitDir(relPath) { + if info.IsDir() { + return filepath.SkipDir + } + return nil + } + + if info.IsDir() { + if hasGitignore(path) { + stack.Push(path) + } + if !stack.IsIgnored(relPath) { + return watcher.Add(path) + } + } + + return nil + }) +} |
