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) } }