diff --git a/proc_test.go b/proc_test.go new file mode 100644 index 0000000..9f5eb64 --- /dev/null +++ b/proc_test.go @@ -0,0 +1,134 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +type fakeProcEntry struct { + cmdline string // raw content with \x00 separators + cwd string // symlink target +} + +// createFakeProc builds a temporary /proc-like directory structure. +func createFakeProc(t *testing.T, entries map[int]fakeProcEntry) string { + t.Helper() + dir := t.TempDir() + + for pid, entry := range entries { + pidDir := filepath.Join(dir, fmt.Sprintf("%d", pid)) + if err := os.MkdirAll(pidDir, 0o755); err != nil { + t.Fatal(err) + } + + // Write cmdline file + if err := os.WriteFile(filepath.Join(pidDir, "cmdline"), []byte(entry.cmdline), 0o644); err != nil { + t.Fatal(err) + } + + // Create cwd symlink + if err := os.Symlink(entry.cwd, filepath.Join(pidDir, "cwd")); err != nil { + t.Fatal(err) + } + } + + return dir +} + +func TestFindClaudeProcesses(t *testing.T) { + fakeProc := createFakeProc(t, map[int]fakeProcEntry{ + 100: {cmdline: "claude\x00-c\x00", cwd: "/home/user/project-a"}, + 200: {cmdline: "claude\x00", cwd: "/home/user/project-b"}, + 300: {cmdline: "bash\x00-l\x00", cwd: "/home/user"}, + }) + + procs, err := FindClaudeProcesses(fakeProc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(procs) != 2 { + t.Fatalf("expected 2 claude processes, got %d", len(procs)) + } + + // Build a map by PID for order-independent assertions + byPID := make(map[int]Process) + for _, p := range procs { + byPID[p.PID] = p + } + + p100, ok := byPID[100] + if !ok { + t.Fatal("expected process with PID 100") + } + if p100.Cwd != "/home/user/project-a" { + t.Errorf("PID 100 cwd = %q, want %q", p100.Cwd, "/home/user/project-a") + } + if len(p100.Cmd) != 2 || p100.Cmd[0] != "claude" || p100.Cmd[1] != "-c" { + t.Errorf("PID 100 cmd = %v, want [claude -c]", p100.Cmd) + } + + p200, ok := byPID[200] + if !ok { + t.Fatal("expected process with PID 200") + } + if p200.Cwd != "/home/user/project-b" { + t.Errorf("PID 200 cwd = %q, want %q", p200.Cwd, "/home/user/project-b") + } + + if _, ok := byPID[300]; ok { + t.Error("bash process (PID 300) should not be in results") + } +} + +func TestFindClaudeProcesses_PermissionDenied(t *testing.T) { + dir := t.TempDir() + + // Create a PID directory with unreadable cmdline + pidDir := filepath.Join(dir, "999") + if err := os.MkdirAll(pidDir, 0o755); err != nil { + t.Fatal(err) + } + cmdlinePath := filepath.Join(pidDir, "cmdline") + if err := os.WriteFile(cmdlinePath, []byte("claude\x00"), 0o000); err != nil { + t.Fatal(err) + } + + procs, err := FindClaudeProcesses(dir) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(procs) != 0 { + t.Errorf("expected 0 processes (permission denied), got %d", len(procs)) + } +} + +func TestFindClaudeProcesses_EmptyProc(t *testing.T) { + dir := t.TempDir() + + procs, err := FindClaudeProcesses(dir) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(procs) != 0 { + t.Errorf("expected 0 processes, got %d", len(procs)) + } +} + +func TestEncodePath(t *testing.T) { + got := EncodePath("/home/pierre/Code/vibe/vmux") + want := "-home-pierre-Code-vibe-vmux" + if got != want { + t.Errorf("EncodePath = %q, want %q", got, want) + } +} + +func TestEncodePath_DotDir(t *testing.T) { + got := EncodePath("/home/pierre/.config/app") + want := "-home-pierre--config-app" + if got != want { + t.Errorf("EncodePath = %q, want %q", got, want) + } +}