commit 3290272c523f71239e030be9e15a7732264381bd
parent 681a0ad0de494ad4672acbf9019395131b04d73d
Author: Erik Loualiche <eloualic@umn.edu>
Date: Sun, 22 Mar 2026 10:13:45 -0500
feat: add findBrokenSymlinks helper for detecting dangling symlinks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 67 insertions(+), 0 deletions(-)
diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go
@@ -13,6 +13,43 @@ import (
)
// ---------------------------------------------------------------------------
+// BrokenSymlink
+// ---------------------------------------------------------------------------
+
+// BrokenSymlink records a symlink whose target does not exist.
+type BrokenSymlink struct {
+ Path string // absolute path to the symlink
+ Target string // what the symlink points to
+}
+
+// findBrokenSymlinks scans a directory for symlinks whose targets do not exist.
+func findBrokenSymlinks(dir string) []BrokenSymlink {
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return nil
+ }
+ var broken []BrokenSymlink
+ for _, e := range entries {
+ if e.Type()&os.ModeSymlink == 0 {
+ continue
+ }
+ full := filepath.Join(dir, e.Name())
+ target, err := os.Readlink(full)
+ if err != nil {
+ continue
+ }
+ // Resolve relative targets against the directory
+ if !filepath.IsAbs(target) {
+ target = filepath.Join(dir, target)
+ }
+ if _, err := os.Stat(target); err != nil {
+ broken = append(broken, BrokenSymlink{Path: full, Target: target})
+ }
+ }
+ return broken
+}
+
+// ---------------------------------------------------------------------------
// EventHandler
// ---------------------------------------------------------------------------
diff --git a/internal/watcher/watcher_test.go b/internal/watcher/watcher_test.go
@@ -1,6 +1,8 @@
package watcher
import (
+ "os"
+ "path/filepath"
"sync/atomic"
"testing"
"time"
@@ -165,3 +167,31 @@ func TestShouldIncludeEmptyMeansAll(t *testing.T) {
}
}
}
+
+// ---------------------------------------------------------------------------
+// 7. TestFindBrokenSymlinks — detects broken symlinks in a directory
+// ---------------------------------------------------------------------------
+func TestFindBrokenSymlinks(t *testing.T) {
+ dir := t.TempDir()
+
+ // Create a valid file
+ os.WriteFile(filepath.Join(dir, "good.txt"), []byte("ok"), 0644)
+
+ // Create a broken symlink
+ os.Symlink("/nonexistent/target", filepath.Join(dir, "bad.txt"))
+
+ // Create a valid symlink
+ os.Symlink(filepath.Join(dir, "good.txt"), filepath.Join(dir, "also-good.txt"))
+
+ broken := findBrokenSymlinks(dir)
+
+ if len(broken) != 1 {
+ t.Fatalf("findBrokenSymlinks found %d, want 1", len(broken))
+ }
+ if broken[0].Target != "/nonexistent/target" {
+ t.Errorf("target = %q, want %q", broken[0].Target, "/nonexistent/target")
+ }
+ if filepath.Base(broken[0].Path) != "bad.txt" {
+ t.Errorf("path base = %q, want %q", filepath.Base(broken[0].Path), "bad.txt")
+ }
+}