package tmux import ( "os/exec" "strings" "testing" ) func hasTmux() bool { _, err := exec.LookPath("tmux") return err == nil } func TestListSessionsNoServer(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() sessions, err := tm.ListSessions() // Should not error even if no server running if err != nil { t.Fatalf("ListSessions: %v", err) } // Result may be nil or empty slice _ = sessions } func TestHasSessionNoServer(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() has, err := tm.HasSession("nonexistent-session-xyz") if err != nil { t.Fatalf("HasSession: %v", err) } if has { t.Error("expected session to not exist") } } func TestSessionLifecycle(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() sessionName := "gt-test-session-" + t.Name() // Clean up any existing session _ = tm.KillSession(sessionName) // Create session if err := tm.NewSession(sessionName, ""); err != nil { t.Fatalf("NewSession: %v", err) } defer func() { _ = tm.KillSession(sessionName) }() // Verify exists has, err := tm.HasSession(sessionName) if err != nil { t.Fatalf("HasSession: %v", err) } if !has { t.Error("expected session to exist after creation") } // List should include it sessions, err := tm.ListSessions() if err != nil { t.Fatalf("ListSessions: %v", err) } found := false for _, s := range sessions { if s == sessionName { found = true break } } if !found { t.Error("session not found in list") } // Kill session if err := tm.KillSession(sessionName); err != nil { t.Fatalf("KillSession: %v", err) } // Verify gone has, err = tm.HasSession(sessionName) if err != nil { t.Fatalf("HasSession after kill: %v", err) } if has { t.Error("expected session to not exist after kill") } } func TestDuplicateSession(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() sessionName := "gt-test-dup-" + t.Name() // Clean up any existing session _ = tm.KillSession(sessionName) // Create session if err := tm.NewSession(sessionName, ""); err != nil { t.Fatalf("NewSession: %v", err) } defer func() { _ = tm.KillSession(sessionName) }() // Try to create duplicate err := tm.NewSession(sessionName, "") if err != ErrSessionExists { t.Errorf("expected ErrSessionExists, got %v", err) } } func TestSendKeysAndCapture(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() sessionName := "gt-test-keys-" + t.Name() // Clean up any existing session _ = tm.KillSession(sessionName) // Create session if err := tm.NewSession(sessionName, ""); err != nil { t.Fatalf("NewSession: %v", err) } defer func() { _ = tm.KillSession(sessionName) }() // Send echo command if err := tm.SendKeys(sessionName, "echo HELLO_TEST_MARKER"); err != nil { t.Fatalf("SendKeys: %v", err) } // Give it a moment to execute // In real tests you'd wait for output, but for basic test we just capture output, err := tm.CapturePane(sessionName, 50) if err != nil { t.Fatalf("CapturePane: %v", err) } // Should contain our marker (might not if shell is slow, but usually works) if !strings.Contains(output, "echo HELLO_TEST_MARKER") { t.Logf("captured output: %s", output) // Don't fail, just note - timing issues possible } } func TestGetSessionInfo(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() sessionName := "gt-test-info-" + t.Name() // Clean up any existing session _ = tm.KillSession(sessionName) // Create session if err := tm.NewSession(sessionName, ""); err != nil { t.Fatalf("NewSession: %v", err) } defer func() { _ = tm.KillSession(sessionName) }() info, err := tm.GetSessionInfo(sessionName) if err != nil { t.Fatalf("GetSessionInfo: %v", err) } if info.Name != sessionName { t.Errorf("Name = %q, want %q", info.Name, sessionName) } if info.Windows < 1 { t.Errorf("Windows = %d, want >= 1", info.Windows) } } func TestWrapError(t *testing.T) { tm := NewTmux() tests := []struct { stderr string want error }{ {"no server running on /tmp/tmux-...", ErrNoServer}, {"error connecting to /tmp/tmux-...", ErrNoServer}, {"duplicate session: test", ErrSessionExists}, {"session not found: test", ErrSessionNotFound}, {"can't find session: test", ErrSessionNotFound}, } for _, tt := range tests { err := tm.wrapError(nil, tt.stderr, []string{"test"}) if err != tt.want { t.Errorf("wrapError(%q) = %v, want %v", tt.stderr, err, tt.want) } } } func TestEnsureSessionFresh_NoExistingSession(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() sessionName := "gt-test-fresh-" + t.Name() // Clean up any existing session _ = tm.KillSession(sessionName) // EnsureSessionFresh should create a new session if err := tm.EnsureSessionFresh(sessionName, ""); err != nil { t.Fatalf("EnsureSessionFresh: %v", err) } defer func() { _ = tm.KillSession(sessionName) }() // Verify session exists has, err := tm.HasSession(sessionName) if err != nil { t.Fatalf("HasSession: %v", err) } if !has { t.Error("expected session to exist after EnsureSessionFresh") } } func TestEnsureSessionFresh_ZombieSession(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() sessionName := "gt-test-zombie-" + t.Name() // Clean up any existing session _ = tm.KillSession(sessionName) // Create a zombie session (session exists but no Claude/node running) // A normal tmux session with bash/zsh is a "zombie" for our purposes if err := tm.NewSession(sessionName, ""); err != nil { t.Fatalf("NewSession: %v", err) } defer func() { _ = tm.KillSession(sessionName) }() // Verify it's a zombie (not running Claude/node) if tm.IsClaudeRunning(sessionName) { t.Skip("session unexpectedly has Claude running - can't test zombie case") } // EnsureSessionFresh should kill the zombie and create fresh session // This should NOT error with "session already exists" if err := tm.EnsureSessionFresh(sessionName, ""); err != nil { t.Fatalf("EnsureSessionFresh on zombie: %v", err) } // Session should still exist has, err := tm.HasSession(sessionName) if err != nil { t.Fatalf("HasSession: %v", err) } if !has { t.Error("expected session to exist after EnsureSessionFresh on zombie") } } func TestEnsureSessionFresh_IdempotentOnZombie(t *testing.T) { if !hasTmux() { t.Skip("tmux not installed") } tm := NewTmux() sessionName := "gt-test-idem-" + t.Name() // Clean up any existing session _ = tm.KillSession(sessionName) // Call EnsureSessionFresh multiple times - should work each time for i := 0; i < 3; i++ { if err := tm.EnsureSessionFresh(sessionName, ""); err != nil { t.Fatalf("EnsureSessionFresh attempt %d: %v", i+1, err) } } defer func() { _ = tm.KillSession(sessionName) }() // Session should exist has, err := tm.HasSession(sessionName) if err != nil { t.Fatalf("HasSession: %v", err) } if !has { t.Error("expected session to exist after multiple EnsureSessionFresh calls") } }