From 4aedad521eb00e18062817718dbe2be3c62d5a51 Mon Sep 17 00:00:00 2001 From: omartelo Date: Sat, 17 Oct 2026 16:42:37 -0300 Subject: [PATCH] test(terminal): run the tests that touch no process-wide state in parallel --- internal/terminal/account_linux_test.go | 1 + internal/terminal/account_test.go | 1 + internal/terminal/agentname_test.go | 1 + internal/terminal/antigravity_trust_test.go | 4 ++ internal/terminal/coalescer_test.go | 11 ++++ internal/terminal/command_kiro_test.go | 3 + internal/terminal/command_ultracode_test.go | 1 + internal/terminal/conversations_test.go | 3 + internal/terminal/crush_init_test.go | 2 + internal/terminal/cursor_trust_test.go | 2 + internal/terminal/cwd_foreground_test.go | 5 ++ internal/terminal/cwd_track_test.go | 7 +++ internal/terminal/draft_test.go | 13 ++++ internal/terminal/duplicate_test.go | 2 + internal/terminal/entrypoint_test.go | 6 ++ internal/terminal/env_blocks_test.go | 2 + internal/terminal/env_self_test.go | 1 + internal/terminal/exit_test.go | 1 + internal/terminal/exit_watch_test.go | 1 + internal/terminal/fixtures_test.go | 4 ++ internal/terminal/handson_test.go | 14 +++++ internal/terminal/handson_wiring_test.go | 7 +++ internal/terminal/hookstate_test.go | 16 +++++ internal/terminal/index_test.go | 2 + internal/terminal/limit_test.go | 3 + internal/terminal/modanswer_test.go | 3 + internal/terminal/modanswer_unix_test.go | 2 + internal/terminal/modcontrol_test.go | 16 +++++ internal/terminal/modprompt_test.go | 8 +++ internal/terminal/modstatus_test.go | 4 ++ internal/terminal/modusage_test.go | 2 + internal/terminal/modwait_test.go | 13 ++++ internal/terminal/nested_test.go | 4 ++ internal/terminal/outbox_test.go | 3 + internal/terminal/paste_unfold_test.go | 4 ++ internal/terminal/pluginversion_test.go | 4 ++ internal/terminal/profile_unix_test.go | 1 + internal/terminal/prompt_language_test.go | 1 + .../terminal/prompt_language_unix_test.go | 5 ++ internal/terminal/pty_test.go | 1 + internal/terminal/pty_unix_test.go | 3 + internal/terminal/replay_test.go | 7 +++ internal/terminal/respawn_test.go | 5 ++ internal/terminal/resume_test.go | 2 + internal/terminal/said_test.go | 12 ++++ internal/terminal/sandbox_report_test.go | 2 + internal/terminal/sandbox_test.go | 16 +++++ internal/terminal/search_e2e_test.go | 1 + internal/terminal/search_test.go | 4 ++ internal/terminal/sessiondb_text_test.go | 4 ++ internal/terminal/setup_test.go | 6 ++ internal/terminal/shell_test.go | 1 + internal/terminal/shellenv_test.go | 6 ++ internal/terminal/shellenv_unix_test.go | 6 ++ internal/terminal/shellintegration_test.go | 1 + internal/terminal/spawn_test.go | 4 ++ internal/terminal/start_ultracode_test.go | 3 + internal/terminal/startgate_test.go | 9 +++ internal/terminal/subagent_cards_test.go | 2 + internal/terminal/terminal_test.go | 63 +++++++++++++++++++ internal/terminal/title_kiro_test.go | 2 + internal/terminal/transport_auth_test.go | 2 + internal/terminal/transport_replace_test.go | 2 + internal/terminal/transport_restart_test.go | 7 +++ internal/terminal/transport_stall_test.go | 1 + internal/terminal/transport_test.go | 29 +++++++++ internal/terminal/turnsnap_test.go | 19 ++++++ internal/terminal/turnstates_test.go | 7 +++ internal/terminal/turntodo_test.go | 5 ++ internal/terminal/turntodo_unix_test.go | 1 + internal/terminal/usage_claude_test.go | 6 ++ internal/terminal/usage_codex_test.go | 2 + internal/terminal/usage_cost_error_test.go | 2 + internal/terminal/usage_cost_test.go | 22 +++++++ internal/terminal/usage_kiro_test.go | 4 ++ internal/terminal/usage_tokenonly_test.go | 2 + internal/terminal/workdir_test.go | 1 + internal/terminal/worktreeport_test.go | 12 ++++ internal/terminal/write_test.go | 1 + internal/terminal/writequeue_test.go | 4 ++ 80 files changed, 472 insertions(+) diff --git a/internal/terminal/account_linux_test.go b/internal/terminal/account_linux_test.go index 8011652a..2c355e08 100644 --- a/internal/terminal/account_linux_test.go +++ b/internal/terminal/account_linux_test.go @@ -17,6 +17,7 @@ import ( // reads back is the environment the provider actually runs with — not the one // lich spawned the wrapper with. func TestSessionAccountReadsWhatTheWrapperExecsInto(t *testing.T) { + t.Parallel() dir := t.TempDir() bin := filepath.Join(dir, "claude-work.sh") script := "#!/bin/sh\nexport CLAUDE_CONFIG_DIR=" + dir + "\nexec sleep 30\n" diff --git a/internal/terminal/account_test.go b/internal/terminal/account_test.go index 92ea8fcc..a55d7f5d 100644 --- a/internal/terminal/account_test.go +++ b/internal/terminal/account_test.go @@ -14,6 +14,7 @@ import ( // A card whose PTY is not up has no environment to read, and reporting it as // read is what would serve it the machine-wide login's quota. func TestSessionAccountWithoutALiveProcess(t *testing.T) { + t.Parallel() svc := New(stubBins{bin: "/opt/claude-work.sh"}, nil, events.New()) env, read := svc.SessionAccount("s1") diff --git a/internal/terminal/agentname_test.go b/internal/terminal/agentname_test.go index cfd8b0a6..deb4f320 100644 --- a/internal/terminal/agentname_test.go +++ b/internal/terminal/agentname_test.go @@ -11,6 +11,7 @@ import ( // again every turn and on every /rename. The name a session answers to is // therefore the last record in the file, whichever of the two it is. func TestClaudeAgentNameReadsTheLastRecord(t *testing.T) { + t.Parallel() const ( head = `{"type":"last-prompt","leafUuid":"a"} {"type":"custom-title","customTitle":"lich-4f2a"} diff --git a/internal/terminal/antigravity_trust_test.go b/internal/terminal/antigravity_trust_test.go index dce87f00..8b7fdc52 100644 --- a/internal/terminal/antigravity_trust_test.go +++ b/internal/terminal/antigravity_trust_test.go @@ -51,6 +51,7 @@ func resolved(t *testing.T, dir string) string { } func TestAntigravityTrustCarriesARepositorysAnswerToItsWorktreeAndSubfolders(t *testing.T) { + t.Parallel() main, worktree := gitWorktree(t) sub := filepath.Join(main, "pkg") if err := os.Mkdir(sub, 0o755); err != nil { @@ -76,6 +77,7 @@ func TestAntigravityTrustCarriesARepositorysAnswerToItsWorktreeAndSubfolders(t * } func TestAntigravityTrustNeverTrustsWhatNobodyTrustedUnconfined(t *testing.T) { + t.Parallel() main, worktree := gitWorktree(t) home := t.TempDir() writeAntigravitySettings(t, home, map[string]any{"trustedWorkspaces": []string{filepath.Dir(main)}}) @@ -98,6 +100,7 @@ func TestAntigravityTrustNeverTrustsWhatNobodyTrustedUnconfined(t *testing.T) { // A confined session is trusted whatever the list says, and the file is made // when Antigravity has not written one yet. func TestAntigravityTrustsAConfinedFolder(t *testing.T) { + t.Parallel() plain := t.TempDir() home := t.TempDir() if err := trustAntigravityFolder(home, plain, true); err != nil { @@ -115,6 +118,7 @@ func TestAntigravityTrustsAConfinedFolder(t *testing.T) { } func TestAntigravityTrustRefusesSettingsItCannotRead(t *testing.T) { + t.Parallel() home := t.TempDir() path := antigravitySettingsFile(home) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { diff --git a/internal/terminal/coalescer_test.go b/internal/terminal/coalescer_test.go index 2b0d83ed..5c880482 100644 --- a/internal/terminal/coalescer_test.go +++ b/internal/terminal/coalescer_test.go @@ -21,6 +21,7 @@ func captureEmit(buf int) (func([]byte), chan []byte) { // hour-long hidden one, so the flush also proves the timer picked the visible // cadence. func TestCoalescerVisibleBatchesBurstOnShortTimer(t *testing.T) { + t.Parallel() emit, emits := captureEmit(3) c := newCoalescer(emit, 20*time.Millisecond, time.Hour) @@ -57,6 +58,7 @@ func TestCoalescerVisibleBatchesBurstOnShortTimer(t *testing.T) { // lone write on an idle visible terminal — a keystroke echo — has no burst to // collapse, so it must not pay the window. func TestCoalescerVisibleLeadingEdgeEmitsIdleWriteAtOnce(t *testing.T) { + t.Parallel() emit, emits := captureEmit(1) c := newCoalescer(emit, time.Hour, time.Hour) @@ -76,6 +78,7 @@ func TestCoalescerVisibleLeadingEdgeEmitsIdleWriteAtOnce(t *testing.T) { // write a whole window after the last emission is leading, one still inside // the window is not. func TestCoalescerLeadingEdgeWindowBoundary(t *testing.T) { + t.Parallel() const window = 20 * time.Millisecond atEdge, atEdgeEmits := captureEmit(1) @@ -111,6 +114,7 @@ func TestCoalescerLeadingEdgeWindowBoundary(t *testing.T) { // TestCoalescerHiddenNeverTakesLeadingEdge: a hidden terminal does not paint, // so it has no echo latency to protect and batches hard instead. func TestCoalescerHiddenNeverTakesLeadingEdge(t *testing.T) { + t.Parallel() emit, emits := captureEmit(1) c := newCoalescer(emit, time.Hour, time.Hour) c.SetVisible(false) @@ -127,6 +131,7 @@ func TestCoalescerHiddenNeverTakesLeadingEdge(t *testing.T) { // TestCoalescerHiddenBuffersThenFlushesOnTimer is the mirror image: an // hour-long visible interval with a short hidden one. func TestCoalescerHiddenBuffersThenFlushesOnTimer(t *testing.T) { + t.Parallel() emit, emits := captureEmit(1) c := newCoalescer(emit, time.Hour, 10*time.Millisecond) c.SetVisible(false) @@ -151,6 +156,7 @@ func TestCoalescerHiddenBuffersThenFlushesOnTimer(t *testing.T) { } func TestCoalescerTimerRearmsPerBurst(t *testing.T) { + t.Parallel() emit, emits := captureEmit(2) c := newCoalescer(emit, time.Hour, 10*time.Millisecond) c.SetVisible(false) @@ -172,6 +178,7 @@ func TestCoalescerTimerRearmsPerBurst(t *testing.T) { } func TestCoalescerSetVisibleFlushesPending(t *testing.T) { + t.Parallel() emit, emits := captureEmit(1) c := newCoalescer(emit, time.Hour, time.Hour) c.SetVisible(false) @@ -190,6 +197,7 @@ func TestCoalescerSetVisibleFlushesPending(t *testing.T) { } func TestCoalescerSetVisibleWithEmptyPendingEmitsNothing(t *testing.T) { + t.Parallel() emit, emits := captureEmit(1) c := newCoalescer(emit, time.Hour, time.Hour) c.SetVisible(false) @@ -204,6 +212,7 @@ func TestCoalescerSetVisibleWithEmptyPendingEmitsNothing(t *testing.T) { } func TestCoalescerOverflowForcesEarlyFlush(t *testing.T) { + t.Parallel() emit, emits := captureEmit(1) c := newCoalescer(emit, time.Hour, time.Hour) c.SetVisible(false) @@ -221,6 +230,7 @@ func TestCoalescerOverflowForcesEarlyFlush(t *testing.T) { } func TestCoalescerCloseFlushesOnceAndSeals(t *testing.T) { + t.Parallel() emit, emits := captureEmit(2) c := newCoalescer(emit, time.Hour, time.Hour) c.SetVisible(false) @@ -244,6 +254,7 @@ func TestCoalescerCloseFlushesOnceAndSeals(t *testing.T) { // multiple goroutines; under -race this checks the locking, and afterwards the // total emitted bytes must equal the total written. func TestCoalescerConcurrentWritesAndFlips(t *testing.T) { + t.Parallel() var mu sync.Mutex var emitted bytes.Buffer c := newCoalescer(func(data []byte) { diff --git a/internal/terminal/command_kiro_test.go b/internal/terminal/command_kiro_test.go index 11c5393f..4e9c21a8 100644 --- a/internal/terminal/command_kiro_test.go +++ b/internal/terminal/command_kiro_test.go @@ -82,6 +82,7 @@ func TestKiroPluginAgentIsNeverAskedForByAnotherProvider(t *testing.T) { // unusable name is dropped rather than passed on: a value starting with a dash // would be read by Kiro as another flag. func TestAgentArgsIsKiroOnly(t *testing.T) { + t.Parallel() tests := []struct { name, kind, agent string want []string @@ -108,6 +109,7 @@ func TestAgentArgsIsKiroOnly(t *testing.T) { // not conditional on which settings a session happens to carry — it is argv[0] // on every Kiro spawn, ahead of all of them. func TestKiroSpawnsTheChatSubcommandBeforeEveryFlag(t *testing.T) { + t.Parallel() tests := []struct { name string agent string @@ -148,6 +150,7 @@ func TestKiroSpawnsTheChatSubcommandBeforeEveryFlag(t *testing.T) { // argv[0] that the harness has no subcommand for is a spawn that dies before the // session exists, which is the same failure from the other direction. func TestSubcommandArgsIsKiroOnly(t *testing.T) { + t.Parallel() for _, kind := range []string{ providers.Claude, providers.Codex, providers.Antigravity, providers.OpenCode, providers.OMP, providers.Crush, providers.Cursor, KindShell, diff --git a/internal/terminal/command_ultracode_test.go b/internal/terminal/command_ultracode_test.go index 849d404e..fdf0e8c9 100644 --- a/internal/terminal/command_ultracode_test.go +++ b/internal/terminal/command_ultracode_test.go @@ -8,6 +8,7 @@ import ( ) func TestUltracodeArgs(t *testing.T) { + t.Parallel() if got := ultracodeArgs(providers.Claude, true); !slices.Equal(got, []string{"--settings", `{"ultracode":true}`}) { t.Errorf("claude, on: args = %v, want --settings with the ultracode key", got) } diff --git a/internal/terminal/conversations_test.go b/internal/terminal/conversations_test.go index c2d72c8a..7d733a1f 100644 --- a/internal/terminal/conversations_test.go +++ b/internal/terminal/conversations_test.go @@ -265,6 +265,7 @@ func TestConversationsReadsEveryProvider(t *testing.T) { } func TestFirstWorkspaceKeepsAWindowsDrive(t *testing.T) { + t.Parallel() if got := firstWorkspace(`["file:///C:/work/alpha"]`); got != filepath.FromSlash("C:/work/alpha") { t.Errorf("firstWorkspace = %q, want the drive kept", got) } @@ -274,6 +275,7 @@ func TestFirstWorkspaceKeepsAWindowsDrive(t *testing.T) { } func TestPromptTitleCutsALongPromptToOneLine(t *testing.T) { + t.Parallel() long := strings.Repeat("ã", conversationTitleRunes+10) if got := promptTitle(long); got != strings.Repeat("ã", conversationTitleRunes)+"…" { t.Errorf("promptTitle(long) = %q", got) @@ -286,6 +288,7 @@ func TestPromptTitleCutsALongPromptToOneLine(t *testing.T) { // TestHeadLinesDropsTheLineTheBoundCuts pins the bound: a record cut in half // by it would otherwise reach the parser as a different, shorter record. func TestHeadLinesDropsTheLineTheBoundCuts(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "long.jsonl") first := `{"type":"session"}` plant(t, path, first+"\n"+strings.Repeat("x", conversationHeadBytes)+"\n") diff --git a/internal/terminal/crush_init_test.go b/internal/terminal/crush_init_test.go index 595e45c4..a70c5e05 100644 --- a/internal/terminal/crush_init_test.go +++ b/internal/terminal/crush_init_test.go @@ -11,6 +11,7 @@ import ( ) func TestMarkCrushInitializedAtTheCheckoutsTop(t *testing.T) { + t.Parallel() _, worktree := gitWorktree(t) sub := filepath.Join(worktree, "pkg") if err := os.Mkdir(sub, 0o755); err != nil { @@ -34,6 +35,7 @@ func TestMarkCrushInitializedAtTheCheckoutsTop(t *testing.T) { } func TestMarkCrushInitializedKeepsWhatIsThere(t *testing.T) { + t.Parallel() dir := t.TempDir() crush := filepath.Join(dir, ".crush") if err := os.Mkdir(crush, 0o755); err != nil { diff --git a/internal/terminal/cursor_trust_test.go b/internal/terminal/cursor_trust_test.go index 4d0490cf..7370418d 100644 --- a/internal/terminal/cursor_trust_test.go +++ b/internal/terminal/cursor_trust_test.go @@ -84,6 +84,7 @@ func TestInheritCursorTrustNeverTrustsWhatNobodyTrusted(t *testing.T) { // The rule is the bundle's: one dash per run of anything outside [A-Za-z0-9], // none at either end. func TestCursorSlug(t *testing.T) { + t.Parallel() for in, want := range map[string]string{ "/home/u/.local/share/lich/worktrees/e48f04f46a4a/fuzzy-pine": "home-u-local-share-lich-worktrees-e48f04f46a4a-fuzzy-pine", `C:\Users\u\my repo_1\`: "C-Users-u-my-repo-1", @@ -97,6 +98,7 @@ func TestCursorSlug(t *testing.T) { // A marker on a parent counts only below home and three components deep, so // trusting the home (or / ) once does not trust every folder under it. func TestCursorTrustsAParentOnlyBelowHome(t *testing.T) { + t.Parallel() sep := string(filepath.Separator) home := filepath.Join(sep+"h", "u") deep := filepath.Join(home, "src", "app") diff --git a/internal/terminal/cwd_foreground_test.go b/internal/terminal/cwd_foreground_test.go index 3a3d4d51..2a3bc8c8 100644 --- a/internal/terminal/cwd_foreground_test.go +++ b/internal/terminal/cwd_foreground_test.go @@ -24,6 +24,7 @@ import ( // and e_tpgid on macOS are set by a shell handing the terminal to a job, and // nothing about that is reachable without one. func TestProcessCwdFollowsForegroundJob(t *testing.T) { + t.Parallel() outer := physical(t, t.TempDir()) inner := physical(t, t.TempDir()) @@ -78,6 +79,7 @@ func TestProcessCwdFollowsForegroundJob(t *testing.T) { // refuse. And that an ordinary command is not mistaken for a shell host, which // is the half of shellHost no list of names can assert on its own. func TestReadCommNamesTheProcess(t *testing.T) { + t.Parallel() comm := readComm(os.Getpid()) if comm == "" { t.Fatal(`readComm(self) = "", want the test binary's name`) @@ -94,6 +96,7 @@ func TestReadCommNamesTheProcess(t *testing.T) { // so processCwd falls through to the directory read rather than matching the // empty string against the host list. func TestReadCommOfDeadPidIsEmpty(t *testing.T) { + t.Parallel() // Far beyond any real PID space (Linux pid_max < 2^22, macOS ~1e5). if got := readComm(1 << 30); got != "" { t.Errorf("readComm(dead) = %q, want empty", got) @@ -111,6 +114,7 @@ func TestReadCommOfDeadPidIsEmpty(t *testing.T) { // under that name is one: it reads as a host to the very seam a real tmux would, // without a multiplexer, a socket or a server anywhere in the test. func TestProcessCwdNamesAShellHostInTheForeground(t *testing.T) { + t.Parallel() // A hard link to this test binary, which is the one executable that is both // nameable and runnable on both OSes. The two other shapes are dead ends, // and each was measured against the macOS runner: @@ -210,6 +214,7 @@ const ( // bound for the run where that does not arrive: three times the caller's own // deadline, so it can never be what a passing run is waiting on. func TestStandsInForAShellHost(t *testing.T) { + t.Parallel() if os.Getenv(hostStubVar) == "" { t.Skip("runs only as the hard-linked stand-in") } diff --git a/internal/terminal/cwd_track_test.go b/internal/terminal/cwd_track_test.go index 5706ce2d..2a581a4e 100644 --- a/internal/terminal/cwd_track_test.go +++ b/internal/terminal/cwd_track_test.go @@ -50,6 +50,7 @@ func waitEmit(t *testing.T, emits <-chan emitted) emitted { // empty buffer — the Windows walk reads into &buf[0], so a zero-length buffer // there panics and, on its bare goroutine, takes the whole app with it. func TestDosPathRunesRejectsUnusableLengths(t *testing.T) { + t.Parallel() cases := []struct { name string in uint16 @@ -74,6 +75,7 @@ func TestDosPathRunesRejectsUnusableLengths(t *testing.T) { // with no controlling terminal (tpgid -1, what every non-PTY child reports) // yields no group rather than a negative pid the cwd read would chase. func TestParseTpgidSurvivesComm(t *testing.T) { + t.Parallel() cases := []struct { name string in string @@ -95,6 +97,7 @@ func TestParseTpgidSurvivesComm(t *testing.T) { // TestProcessCwdReadsSelf proves the platform read resolves a live process's // working directory — exercised against the test process itself. func TestProcessCwdReadsSelf(t *testing.T) { + t.Parallel() wd, err := os.Getwd() if err != nil { t.Fatalf("Getwd: %v", err) @@ -111,6 +114,7 @@ func TestProcessCwdReadsSelf(t *testing.T) { // TestProcessCwdOfDeadPidIsEmpty proves an unresolvable process degrades to // "", which pollCwd skips instead of reporting. func TestProcessCwdOfDeadPidIsEmpty(t *testing.T) { + t.Parallel() // Far beyond any real PID space (Linux pid_max < 2^22, macOS ~1e5); Windows // simply finds no such process to open. if got, host := processCwd(1 << 30); got != "" || host != "" { @@ -178,6 +182,7 @@ func TestPollCwdEmitsOnlyOnChange(t *testing.T) { // it is a real path, and naming it is the whole failure this seam exists to // end. Driven off a hand-fed read, because the alternative is starting a tmux. func TestPollCwdPublishesTheHostInsteadOfAPath(t *testing.T) { + t.Parallel() readings := make(chan emitted, 8) tick := make(chan time.Time) done := make(chan struct{}) @@ -218,6 +223,7 @@ func TestPollCwdPublishesTheHostInsteadOfAPath(t *testing.T) { // answered with, an ordinary foreground job is not, and tmux's own comm — which // carries its role after a colon — still matches. func TestShellHostNamesOnlyKnownHosts(t *testing.T) { + t.Parallel() cases := []struct { in string want string @@ -257,6 +263,7 @@ func TestShellHostNamesOnlyKnownHosts(t *testing.T) { // exit and the card closing — a session's last visible state would be that it // is nowhere. func TestPollCwdSkipsAReadThatAnswersNothing(t *testing.T) { + t.Parallel() readings := make(chan emitted, 4) tick := make(chan time.Time) done := make(chan struct{}) diff --git a/internal/terminal/draft_test.go b/internal/terminal/draft_test.go index 7de45580..eaf5d6c2 100644 --- a/internal/terminal/draft_test.go +++ b/internal/terminal/draft_test.go @@ -27,6 +27,7 @@ func settled(t *testing.T) (*Service, *session) { // Enter is sent behind it, so one arriving while the user is mid-sentence sends // both halves as one submission and the user's half is gone. func TestTypingTakesThePromptBackFromTheRelay(t *testing.T) { + t.Parallel() svc, _ := settled(t) svc.noteInput("s1", []byte("what about the")) @@ -44,6 +45,7 @@ func TestTypingTakesThePromptBackFromTheRelay(t *testing.T) { // user's anymore, and a prompt held by a draft nobody has is a relay that never // delivers here again. func TestAnAbandonedLineReleasesThePrompt(t *testing.T) { + t.Parallel() svc, _ := settled(t) svc.noteInput("s1", []byte("half a thought")) @@ -57,6 +59,7 @@ func TestAnAbandonedLineReleasesThePrompt(t *testing.T) { // input, and the digits inside those reports are printable. Read as typing they // would hold the prompt forever without anybody touching a key. func TestPointerAndCursorInputIsNotADraft(t *testing.T) { + t.Parallel() svc, _ := settled(t) svc.noteInput("s1", []byte("\x1b[<35;42;7M\x1b[<35;43;7M")) @@ -78,6 +81,7 @@ func TestPointerAndCursorInputIsNotADraft(t *testing.T) { // half-written line is where it belongs, so it must not count against the // prompt, while a session that is not at one at all still does. func TestAtPromptIgnoresTheUsersDraft(t *testing.T) { + t.Parallel() svc, _ := settled(t) svc.noteInput("s1", []byte("explain this: ")) @@ -90,6 +94,7 @@ func TestAtPromptIgnoresTheUsersDraft(t *testing.T) { } func TestAtPromptIsFalseWhereThereIsNoPrompt(t *testing.T) { + t.Parallel() svc, sess := settled(t) svc.mu.Lock() @@ -107,6 +112,7 @@ func TestAtPromptIsFalseWhereThereIsNoPrompt(t *testing.T) { // the line itself, so an edit it cannot read as one would wedge the relay // silently. A draft nobody has touched goes stale and gives the prompt back. func TestAStaleDraftGivesThePromptBack(t *testing.T) { + t.Parallel() svc, sess := settled(t) svc.mu.Lock() @@ -125,6 +131,7 @@ func TestAStaleDraftGivesThePromptBack(t *testing.T) { } func TestEscapeSpan(t *testing.T) { + t.Parallel() cases := []struct { name string in string @@ -153,6 +160,7 @@ func TestEscapeSpan(t *testing.T) { // holding it back forever would leave the relay pasting into a line that has a // paragraph of the user's on it. func TestAnEscapeThatNeverEndsDoesNotSwallowTyping(t *testing.T) { + t.Parallel() svc, _ := settled(t) svc.noteInput("s1", []byte("\x1b[0123456789012345678901234567890123456789")) @@ -167,6 +175,7 @@ func TestAnEscapeThatNeverEndsDoesNotSwallowTyping(t *testing.T) { // pressed: a lone Ctrl+C or Escape, never a byte carried in by a paste and // never one inside an escape sequence. func TestInterruptIsReadFromALoneKey(t *testing.T) { + t.Parallel() tests := []struct { name string writes []string @@ -203,6 +212,7 @@ func TestInterruptIsReadFromALoneKey(t *testing.T) { // A session lich is not running has no turn to end and no prompt to hold. func TestInterruptForAnUnknownSessionIsANoOp(t *testing.T) { + t.Parallel() svc, _ := settled(t) if svc.noteInput("nobody", []byte{ctrlC}) { t.Fatal("a keystroke for a session lich does not run read as an interrupt") @@ -213,6 +223,7 @@ func TestInterruptForAnUnknownSessionIsANoOp(t *testing.T) { // at that keyboard types inside it would be submitted as part of a message they // never sent, so it is held and written at the prompt the Enter leaves behind. func TestKeystrokesDuringADeliveryLandAfterIt(t *testing.T) { + t.Parallel() svc, sess := settled(t) pty := &shortWritePTY{chunk: 4096} svc.mu.Lock() @@ -246,6 +257,7 @@ func TestKeystrokesDuringADeliveryLandAfterIt(t *testing.T) { // A hold on a session that has gone leaves nothing behind, and its release is // safe to call: a delivery races the PTY exiting under it like any other write. func TestHoldingAGoneSessionIsHarmless(t *testing.T) { + t.Parallel() svc, _ := settled(t) svc.HoldInput("gone")() } @@ -254,6 +266,7 @@ func TestHoldingAGoneSessionIsHarmless(t *testing.T) { // must not hand the keyboard back while the second delivery is still between // its own paste and Enter, or the keystrokes ride along with that one instead. func TestOverlappingDeliveriesKeepTheKeyboardHeld(t *testing.T) { + t.Parallel() svc, sess := settled(t) pty := &shortWritePTY{chunk: 4096} svc.mu.Lock() diff --git a/internal/terminal/duplicate_test.go b/internal/terminal/duplicate_test.go index 089a721d..741550cf 100644 --- a/internal/terminal/duplicate_test.go +++ b/internal/terminal/duplicate_test.go @@ -14,6 +14,7 @@ import ( // A card that reports a conversation another open card runs (a `/resume` // typed in its TUI) is the one the window warns about, naming the holder. func TestASessionStartOnAHeldConversationTellsTheWindow(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) svc := New(stubBins{holder: "holder"}, nil, hub) @@ -29,6 +30,7 @@ func TestASessionStartOnAHeldConversationTellsTheWindow(t *testing.T) { } func TestASessionStartOnItsOwnConversationRaisesNoWarning(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) svc := New(stubBins{}, nil, hub) diff --git a/internal/terminal/entrypoint_test.go b/internal/terminal/entrypoint_test.go index 3a35e895..7fab75ed 100644 --- a/internal/terminal/entrypoint_test.go +++ b/internal/terminal/entrypoint_test.go @@ -28,6 +28,7 @@ func entrypointSpec() ptySpec { // WHERE clause) must not rewrite that spawn, because a provider's argv is the // whole session. func TestWrapEntrypointLeavesOtherSessionsAlone(t *testing.T) { + t.Parallel() base := entrypointSpec() cases := []struct { name, kind, entrypoint, goos string @@ -56,6 +57,7 @@ func TestWrapEntrypointLeavesOtherSessionsAlone(t *testing.T) { // dying with it. The exec argument is quoted, so a shell installed under a path // with a space is still the shell the user lands in. func TestWrapEntrypointRunsThenExecsTheShell(t *testing.T) { + t.Parallel() spec := entrypointSpec() spec.bin = "/opt/my shells/zsh" @@ -86,6 +88,7 @@ func TestWrapEntrypointRunsThenExecsTheShell(t *testing.T) { // otherwise swallow the exec, and the card would die with the tool instead of // falling back to a prompt. func TestWrapEntrypointSurvivesATrailingComment(t *testing.T) { + t.Parallel() for _, entrypoint := range []string{"lazygit # the good one", "pnpm dev \\"} { script := wrapEntrypoint(entrypointSpec(), KindShell, entrypoint, "linux").args[1] if !strings.Contains(script, "\nexec ") { @@ -99,6 +102,7 @@ func TestWrapEntrypointSurvivesATrailingComment(t *testing.T) { // shell — and that wrapSetup's end marker still lands between the script and // everything the user asked for, so a session is never handed work mid-install. func TestWrapEntrypointComposesWithSetup(t *testing.T) { + t.Parallel() spec := wrapEntrypoint(entrypointSpec(), KindShell, "lazygit", "linux") spec, wrapped := wrapSetup(spec, "pnpm install", "linux") if !wrapped { @@ -128,6 +132,7 @@ func TestWrapEntrypointComposesWithSetup(t *testing.T) { // the POSIX branch loads no rc either. What that costs is proved on a real // shell by entrypoint_windows_test.go. func TestWrapEntrypointOnWindowsKeepsOneProcess(t *testing.T) { + t.Parallel() spec := entrypointSpec() spec.bin = `C:\Program Files\PowerShell\7\pwsh.exe` @@ -154,6 +159,7 @@ func TestWrapEntrypointOnWindowsKeepsOneProcess(t *testing.T) { // does not undo. Non-ASCII proves the UTF-16 half — PowerShell reads the bytes // as little-endian UTF-16, so a wrong width or order turns the command to noise. func TestEncodePwshCommandSurvivesQuotesAndUnicode(t *testing.T) { + t.Parallel() for _, script := range []string{ `pnpm run "dev server"`, "echo 'it''s'", diff --git a/internal/terminal/env_blocks_test.go b/internal/terminal/env_blocks_test.go index 670d9d02..b615acc8 100644 --- a/internal/terminal/env_blocks_test.go +++ b/internal/terminal/env_blocks_test.go @@ -8,6 +8,7 @@ import ( ) func TestProcArgsEnvironment(t *testing.T) { + t.Parallel() fixture := append([]byte{3, 0, 0, 0}, []byte("/bin/agent\x00\x00agent\x00CLAUDE_CONFIG_DIR=argument\x00\x00CLAUDE_CONFIG_DIR=/work\x00CLAUDE_SECURESTORAGE_CONFIG_DIR=\x00TOKEN=a=b\x00\x00apple=ignored\x00")...) want := map[string]string{"CLAUDE_CONFIG_DIR": "/work", "CLAUDE_SECURESTORAGE_CONFIG_DIR": "", "TOKEN": "a=b"} if got := parseProcArgsEnv(fixture); !reflect.DeepEqual(got, want) { @@ -37,6 +38,7 @@ func utf16EnvFixture(s string) []byte { } func TestUTF16Environment(t *testing.T) { + t.Parallel() data := utf16EnvFixture("=C:=C:\\work\x00CLAUDE_CONFIG_DIR=C:\\café😀\x00CLAUDE_SECURESTORAGE_CONFIG_DIR=\x00TOKEN=a=b\x00\x00") want := map[string]string{"CLAUDE_CONFIG_DIR": "C:\\café😀", "CLAUDE_SECURESTORAGE_CONFIG_DIR": "", "TOKEN": "a=b"} if got := parseUTF16Env(data); !reflect.DeepEqual(got, want) { diff --git a/internal/terminal/env_self_test.go b/internal/terminal/env_self_test.go index 7a45a3ae..c4d9a865 100644 --- a/internal/terminal/env_self_test.go +++ b/internal/terminal/env_self_test.go @@ -12,6 +12,7 @@ import ( // own: both platforms read a live process, so the fixture has to be a process, // and the child re-runs this same test to be one. func TestProcessEnvironmentReadsSelf(t *testing.T) { + t.Parallel() const marker = "LICH_TEST_PROCESS_ENV" const value = "café😀=account" if os.Getenv(marker) == value { diff --git a/internal/terminal/exit_test.go b/internal/terminal/exit_test.go index b51e6e04..1e8590ac 100644 --- a/internal/terminal/exit_test.go +++ b/internal/terminal/exit_test.go @@ -7,6 +7,7 @@ import "testing" // reads a code as the reason a session ended, so an invented one would explain a // crash as a successful exit. func TestExitPayloadReportsOnlyAnObservedStatus(t *testing.T) { + t.Parallel() tests := []struct { name string code int diff --git a/internal/terminal/exit_watch_test.go b/internal/terminal/exit_watch_test.go index 8b34b905..ed92fbc4 100644 --- a/internal/terminal/exit_watch_test.go +++ b/internal/terminal/exit_watch_test.go @@ -15,6 +15,7 @@ import ( // (relay.SessionExited). A close is not: it reaches the relay through the // close watcher instead. func TestAReapedSessionIsReportedToTheExitWatcher(t *testing.T) { + t.Parallel() hub, _ := newProbeHub(t) svc := New(stubBins{bin: exitBin(t, 0)}, nil, hub) var mu sync.Mutex diff --git a/internal/terminal/fixtures_test.go b/internal/terminal/fixtures_test.go index fae0b8b1..54ce9064 100644 --- a/internal/terminal/fixtures_test.go +++ b/internal/terminal/fixtures_test.go @@ -111,6 +111,7 @@ func loadHookFixtures(t *testing.T, file string) []hookFixtureCase { // in both directions: a contract without fixtures and a fixture file without a // contract are both failures. func TestHookFixturesCoverEveryContract(t *testing.T) { + t.Parallel() found, err := filepath.Glob(filepath.Join(hookFixtureDir, "*.jsonl")) if err != nil { t.Fatalf("glob fixtures: %v", err) @@ -134,6 +135,7 @@ func TestHookFixturesCoverEveryContract(t *testing.T) { // README, so a malformed case fails as itself rather than as a confusing // failure in one of the tests below. func TestHookFixturesAreWellFormed(t *testing.T) { + t.Parallel() for _, contract := range hookContracts { t.Run(contract.file, func(t *testing.T) { cases := loadHookFixtures(t, contract.file) @@ -177,6 +179,7 @@ func checkFixtureShape(t *testing.T, i int, c hookFixtureCase) { // a reject case must error, and an accept case must produce exactly the field // values the fixture names, after the contract's defaulting and trimming. func TestHookFixturesMatchParsers(t *testing.T) { + t.Parallel() for _, contract := range hookContracts { t.Run(contract.file, func(t *testing.T) { for _, c := range loadHookFixtures(t, contract.file) { @@ -227,6 +230,7 @@ func assertFixtureFields(t *testing.T, parsed any, want map[string]any) { // fixtures pin the wire contract a hook script actually meets — auth, body // limit, parse and apply — and not just the parser underneath it. func TestHookFixturesMatchEndpoints(t *testing.T) { + t.Parallel() tr, err := newTransport( func(string, []byte) {}, func(hookRequest) {}, diff --git a/internal/terminal/handson_test.go b/internal/terminal/handson_test.go index 54bf394f..b3bae85b 100644 --- a/internal/terminal/handson_test.go +++ b/internal/terminal/handson_test.go @@ -39,6 +39,7 @@ func drainSeconds(h *handsOn, id string) int64 { // credits nothing: there is no earlier beat to measure against, and inventing a // start would bill the session for however long lich had been running. func TestFirstBeatCountsNothing(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -52,6 +53,7 @@ func TestFirstBeatCountsNothing(t *testing.T) { // TestGapUnderIdleGapCounts proves the ordinary case: two beats a few minutes // apart are minutes at the keyboard, and the whole gap is credited. func TestGapUnderIdleGapCounts(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -69,6 +71,7 @@ func TestGapUnderIdleGapCounts(t *testing.T) { // so moving the constant fails this test instead of silently moving the rule // with it. func TestGapAtIdleGapCounts(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -85,6 +88,7 @@ func TestGapAtIdleGapCounts(t *testing.T) { // silence longer than the idle gap is time away, and it contributes zero rather // than a capped amount — a session left open overnight must not bill the night. func TestGapOverIdleGapCountsNothing(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -101,6 +105,7 @@ func TestGapOverIdleGapCountsNothing(t *testing.T) { // beat that ends it is the start of the next stretch, and the work after it is // counted in full. func TestSilenceStillRearmsTheChain(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -119,6 +124,7 @@ func TestSilenceStillRearmsTheChain(t *testing.T) { // closer together than minGap are dropped whole — neither counted nor recorded // as the previous beat — so a stream of them cannot chain its way to a total. func TestThrottleDropsCloseBeats(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -138,6 +144,7 @@ func TestThrottleDropsCloseBeats(t *testing.T) { // TestThrottleAdmitsBeatAtTheGap pins the throttle's own boundary: a beat // exactly minGap after the last one is admitted, not dropped. func TestThrottleAdmitsBeatAtTheGap(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -153,6 +160,7 @@ func TestThrottleAdmitsBeatAtTheGap(t *testing.T) { // TestBackwardsClockCountsNothing proves a clock that jumps back — an NTP step, // a laptop resuming — neither credits negative time nor corrupts the chain. func TestBackwardsClockCountsNothing(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -171,6 +179,7 @@ func TestBackwardsClockCountsNothing(t *testing.T) { // At two flushes a minute, throwing it away would lose most of a minute an hour // and the readout would drift quietly low. func TestDrainKeepsTheRemainder(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -191,6 +200,7 @@ func TestDrainKeepsTheRemainder(t *testing.T) { // TestDrainSkipsQuietSessions proves an app full of idle cards drains to // nothing, so the debounced write touches no row it has nothing to say about. func TestDrainSkipsQuietSessions(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -211,6 +221,7 @@ func TestDrainSkipsQuietSessions(t *testing.T) { // TestSessionsAreCountedApart proves the accumulator keys everything by session: // one card's beats never advance another's chain. func TestSessionsAreCountedApart(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -233,6 +244,7 @@ func TestSessionsAreCountedApart(t *testing.T) { // write: the debounce is armed by whoever crosses the line, so two beats a // millisecond apart cannot both spawn a writer. func TestFlushIsDueOncePerWindow(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now, lastFlush: clock.now()} @@ -251,6 +263,7 @@ func TestFlushIsDueOncePerWindow(t *testing.T) { // TestForgetBreaksTheChain proves a card closed and reopened under the same id // does not count the gap it was gone for. func TestForgetBreaksTheChain(t *testing.T) { + t.Parallel() clock := newFakeClock() h := &handsOn{now: clock.now} @@ -268,6 +281,7 @@ func TestForgetBreaksTheChain(t *testing.T) { // state every bare Service the tests build is in — a readout must never be the // reason a keystroke panics. func TestZeroValueAccumulatorWorks(t *testing.T) { + t.Parallel() var h handsOn h.beat("s1", 0) diff --git a/internal/terminal/handson_wiring_test.go b/internal/terminal/handson_wiring_test.go index fa763fdf..a65e7543 100644 --- a/internal/terminal/handson_wiring_test.go +++ b/internal/terminal/handson_wiring_test.go @@ -49,6 +49,7 @@ func newQuietService(store Store, clock *fakeClock) *Service { // server, a TUI repainting its spinner all draw forever — so it counts only // while the provider's own report says a turn is open. func TestOutputCountsOnlyWhileBusy(t *testing.T) { + t.Parallel() clock := newFakeClock() svc := newQuietService(stubBins{}, clock) sess := &session{} @@ -78,6 +79,7 @@ func TestOutputCountsOnlyWhileBusy(t *testing.T) { // accumulator: output arrives in fragments, and one beat per fragment would // chain gaps too small to ever look like a silence. func TestOutputBeatsAreThrottled(t *testing.T) { + t.Parallel() clock := newFakeClock() svc := newQuietService(stubBins{}, clock) svc.turns.report("s1", statusBusy) @@ -98,6 +100,7 @@ func TestOutputBeatsAreThrottled(t *testing.T) { // session has: a shell, and a provider that reports no state at all, are // counted from the user's own typing. func TestKeystrokesCountWithoutAnyProviderReport(t *testing.T) { + t.Parallel() clock := newFakeClock() svc := newQuietService(stubBins{}, clock) @@ -113,6 +116,7 @@ func TestKeystrokesCountWithoutAnyProviderReport(t *testing.T) { // TestFlushWritesThroughToTheStore proves the arrears reach the row, and reach // it as an addition — a second flush must not restate the first. func TestFlushWritesThroughToTheStore(t *testing.T) { + t.Parallel() clock := newFakeClock() store := stubBins{handsOn: map[string]int64{}} svc := newQuietService(store, clock) @@ -135,6 +139,7 @@ func TestFlushWritesThroughToTheStore(t *testing.T) { } func TestFlushRestoresTimeAfterAStoreFailure(t *testing.T) { + t.Parallel() clock := newFakeClock() store := &failingHandsOnStore{ stubBins: stubBins{handsOn: map[string]int64{}}, @@ -158,6 +163,7 @@ func TestFlushRestoresTimeAfterAStoreFailure(t *testing.T) { // window deletes the row: a write that loses that race is dropped silently, and // this is a closed card's last chance to keep its number. func TestClosingASessionWritesWhatItWasWorked(t *testing.T) { + t.Parallel() clock := newFakeClock() store := stubBins{handsOn: map[string]int64{}} svc := newQuietService(store, clock) @@ -186,6 +192,7 @@ func TestClosingASessionWritesWhatItWasWorked(t *testing.T) { // wiring is the whole claim: the beat has to survive being written into a // callback that is about something else. func TestEveryHookReportBeats(t *testing.T) { + t.Parallel() for _, tc := range []struct{ name, path, body string }{ {"session-start", "/session-start", `{"session_id":"s1","provider_session_id":"u9","provider":"crush"}`}, {"session-title", "/session-title", `{"session_id":"s1","title":"Explore this directory"}`}, diff --git a/internal/terminal/hookstate_test.go b/internal/terminal/hookstate_test.go index 64098a6e..92afeb6b 100644 --- a/internal/terminal/hookstate_test.go +++ b/internal/terminal/hookstate_test.go @@ -19,6 +19,7 @@ import ( // turnLog: inside a turn a human is blocking it, outside one the session is // merely sitting at its prompt. func TestTurnLogTellsBlockedFromIdle(t *testing.T) { + t.Parallel() tests := []struct { name string before []string @@ -51,6 +52,7 @@ func TestTurnLogTellsBlockedFromIdle(t *testing.T) { // Every state but `waiting` reaches the window as sent — the filter is one // distinction, not a gate on the stream. func TestTurnLogPublishesEveryOtherState(t *testing.T) { + t.Parallel() var log turnLog for _, state := range []string{statusBusy, statusCompacting, statusDone, statusIdle, statusBusy} { if !log.report("s1", state) { @@ -62,6 +64,7 @@ func TestTurnLogPublishesEveryOtherState(t *testing.T) { // One session's turn says nothing about another's: a permission prompt in a // busy session must not vouch for an idle one. func TestTurnLogKeepsSessionsApart(t *testing.T) { + t.Parallel() var log turnLog log.report("busy-one", statusBusy) if log.report("idle-one", statusWaiting) { @@ -76,6 +79,7 @@ func TestTurnLogKeepsSessionsApart(t *testing.T) { // provider left open died with it, and reading the new one against it would // badge its first idle notification as a block. func TestTurnLogForgetsARespawnedSession(t *testing.T) { + t.Parallel() var log turnLog log.report("s1", statusBusy) log.forget("s1") @@ -89,6 +93,7 @@ func TestTurnLogForgetsARespawnedSession(t *testing.T) { // same run covers both directions, because the second `waiting` is the one the // author saw badge a worker that had just answered. func TestHookPublishesOnlyABlockingWait(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) svc := New(hookStore{}, nil, hub) if svc.wsErr != nil { @@ -141,6 +146,7 @@ func (l *stateLog) seen() []string { // ending told twice, and it must neither ring the card again nor close an // errand delivered in the gap between the two. func TestACursorTurnReachesTheWindowAndTheRelayOnce(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) marks := &unreadStore{} svc := New(marks, nil, hub) @@ -177,6 +183,7 @@ func TestACursorTurnReachesTheWindowAndTheRelayOnce(t *testing.T) { // opens. The repeated `busy` is the point of the dedupe: a report per tool call // must not take the workspace database's write lock to change nothing. func TestHookRecordsTheUnreadMarkAtTurnEdges(t *testing.T) { + t.Parallel() hub, _ := newProbeHub(t) marks := &unreadStore{} svc := New(marks, nil, hub) @@ -198,6 +205,7 @@ func TestHookRecordsTheUnreadMarkAtTurnEdges(t *testing.T) { // sight, so a finished turn is written every time it happens: a memo of the mark // would hand back a card with no ring for the turn after the one that was read. func TestHookWritesEveryFinishedTurn(t *testing.T) { + t.Parallel() hub, _ := newProbeHub(t) marks := &unreadStore{} svc := New(marks, nil, hub) @@ -218,6 +226,7 @@ func TestHookWritesEveryFinishedTurn(t *testing.T) { // A session end clears the mark, the way it clears the ring: a card with no // provider left in it has nothing to come back to. func TestHookClearsTheUnreadMarkOnSessionEnd(t *testing.T) { + t.Parallel() hub, _ := newProbeHub(t) marks := &unreadStore{} svc := New(marks, nil, hub) @@ -289,6 +298,7 @@ func postHook(t *testing.T, svc *Service, id, state string) { // The turn an interrupt ends is one lich heard start. Ctrl+C at a prompt with // nothing running is how a line is thrown away, and it must say nothing at all. func TestTurnLogInterruptOnlyEndsAnOpenTurn(t *testing.T) { + t.Parallel() tests := []struct { name string before []string @@ -317,6 +327,7 @@ func TestTurnLogInterruptOnlyEndsAnOpenTurn(t *testing.T) { // session at its prompt, so a `waiting` that follows is the provider's "your // turn" nudge rather than a human being blocked. func TestTurnLogInterruptClosesTheTurn(t *testing.T) { + t.Parallel() var log turnLog log.report("s1", statusBusy) if !log.interrupt("s1") { @@ -332,6 +343,7 @@ func TestTurnLogInterruptClosesTheTurn(t *testing.T) { // One session's interrupt is not another's: the key was pressed at one prompt. func TestTurnLogInterruptKeepsSessionsApart(t *testing.T) { + t.Parallel() var log turnLog log.report("one", statusBusy) log.report("two", statusBusy) @@ -345,6 +357,7 @@ func TestTurnLogInterruptKeepsSessionsApart(t *testing.T) { // terminal reaches the window as the turn ending, and the provider's own report // still outranks it when one finally arrives. func TestInterruptedTurnReachesTheWindow(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) svc := New(hookStore{}, nil, hub) if svc.wsErr != nil { @@ -379,6 +392,7 @@ func TestInterruptedTurnReachesTheWindow(t *testing.T) { // The state lich raises itself is not one a hook may claim: it says something // only lich is in a position to know, and the endpoint stays closed to it. func TestHookRejectsTheInterruptedState(t *testing.T) { + t.Parallel() svc := New(hookStore{}, nil, events.New()) if svc.wsErr != nil { t.Fatalf("transport: %v", svc.wsErr) @@ -425,6 +439,7 @@ func sendInput(t *testing.T, svc *Service, id string, data []byte) { // whether or not it changed: the consumer reads a level, and a `waiting` or // a repeated interrupt repeats the count rather than going silent. func TestTurnLogReportsOpenCount(t *testing.T) { + t.Parallel() var got []int l := &turnLog{onOpen: func(n int) { got = append(got, n) }} l.report("a", statusBusy) @@ -444,6 +459,7 @@ func TestTurnLogReportsOpenCount(t *testing.T) { // A closed card takes its turn with it: the count that keeps the machine // awake must not remember a session that no longer exists. func TestCloseForgetsOpenTurn(t *testing.T) { + t.Parallel() s := &Service{sessions: map[string]*session{}, hub: events.New()} var last int s.turns.onOpen = func(n int) { last = n } diff --git a/internal/terminal/index_test.go b/internal/terminal/index_test.go index 2b7ed917..71715ff7 100644 --- a/internal/terminal/index_test.go +++ b/internal/terminal/index_test.go @@ -100,6 +100,7 @@ func TestTranscriptTextReadsTheCrushDatabase(t *testing.T) { // session's index holds. What it drops is the oldest end, and what it keeps is // whole: a half sentence in the index is a search hit nobody can read. func TestNewestBytesKeepsTheNewestWholeLines(t *testing.T) { + t.Parallel() text := "oldest line\nmiddle line\nnewest line\n" tests := []struct { name string @@ -128,6 +129,7 @@ func TestNewestBytesKeepsTheNewestWholeLines(t *testing.T) { // at: a single message longer than the cap, in a script where a rune is more // than one byte. A cut inside one would put invalid UTF-8 in the database. func TestNewestBytesNeverCutsARune(t *testing.T) { + t.Parallel() text := strings.Repeat("é", 40) for max := 1; max <= len(text); max++ { got, _ := newestBytes(text, max) diff --git a/internal/terminal/limit_test.go b/internal/terminal/limit_test.go index 6798babd..723158a3 100644 --- a/internal/terminal/limit_test.go +++ b/internal/terminal/limit_test.go @@ -13,6 +13,7 @@ import ( const claudeLimitLine = `{"type":"assistant","isSidechain":false,"message":{"model":"","role":"assistant","content":[{"type":"text","text":"You've hit your session limit · resets 9:10pm (America/Sao_Paulo)"}]},"quotaLimits":{"status":"rejected","resetsAt":1788653400,"rateLimitType":"five_hour","overageStatus":"rejected"},"error":"rate_limit","isApiErrorMessage":true,"apiErrorStatus":429}` func TestClaudeUsageLimitIsReadOffTheLastAssistantLine(t *testing.T) { + t.Parallel() tests := []struct { name string lines []string @@ -86,6 +87,7 @@ const ( ) func TestCodexUsageLimitIsReadOffTheTurnsEnd(t *testing.T) { + t.Parallel() weeklyToo := strings.Replace(codexLimitTokens, `"secondary":null`, `"secondary":{"used_percent":100.0,"window_minutes":10080,"resets_at":1791900000}`, 1) tests := []struct { @@ -138,6 +140,7 @@ func TestCodexUsageLimitIsReadOffTheTurnsEnd(t *testing.T) { } func TestTurnLogNamesEveryOpenTurn(t *testing.T) { + t.Parallel() var l turnLog l.report("a", statusBusy) l.report("b", statusBusy) diff --git a/internal/terminal/modanswer_test.go b/internal/terminal/modanswer_test.go index 05843a18..606050b5 100644 --- a/internal/terminal/modanswer_test.go +++ b/internal/terminal/modanswer_test.go @@ -9,6 +9,7 @@ import ( ) func TestModAnswersOnlyFromAPollingModOfARelease(t *testing.T) { + t.Parallel() tests := []struct { name string release string @@ -43,6 +44,7 @@ func TestModAnswersOnlyFromAPollingModOfARelease(t *testing.T) { // it, so the watcher hears who closed the session; the window's close names no // one. func TestCloseTellsTheWatcherWhoClosedTheSession(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "s1") runModSession(svc, "s2") @@ -64,6 +66,7 @@ func TestCloseTellsTheWatcherWhoClosedTheSession(t *testing.T) { // The relay hears about a close while the process still runs, so it can end a // worker's errand before the SessionEnd the dying CLI reports. func TestCloseTellsTheWatcherBeforeTheProcessDies(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) p := runModSession(svc, "s1") var order []string diff --git a/internal/terminal/modanswer_unix_test.go b/internal/terminal/modanswer_unix_test.go index f4c8f471..cc75ecc0 100644 --- a/internal/terminal/modanswer_unix_test.go +++ b/internal/terminal/modanswer_unix_test.go @@ -15,6 +15,7 @@ import ( // Unix-only with the stubBins it reads, which live in the PTY suite. func TestModAnswerEndpointHandsTheTextToTheRelay(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if svc.ws == nil { t.Fatalf("transport: %v", svc.wsErr) @@ -44,6 +45,7 @@ func TestModAnswerEndpointHandsTheTextToTheRelay(t *testing.T) { } func TestModAnswerEndpointHandsAnUnansweredTurnToTheRelay(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if svc.ws == nil { t.Fatalf("transport: %v", svc.wsErr) diff --git a/internal/terminal/modcontrol_test.go b/internal/terminal/modcontrol_test.go index 02a51bb7..7d449b2a 100644 --- a/internal/terminal/modcontrol_test.go +++ b/internal/terminal/modcontrol_test.go @@ -152,6 +152,7 @@ func postModAck(t *testing.T, tr *transport, body string) { } func TestModCommandsRefusesBadRequests(t *testing.T) { + t.Parallel() tr := newNilTransport(t) tests := []struct { name string @@ -186,6 +187,7 @@ func TestModCommandsRefusesBadRequests(t *testing.T) { // A poll that times out is what attaches the mod, and a command queued after it // is handed out exactly once. func TestModCommandIsDeliveredOnce(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "s1") if got := pollMod(t, svc.ws, "s1"); len(got) != 0 { @@ -207,6 +209,7 @@ func TestModCommandIsDeliveredOnce(t *testing.T) { // Commands queued between two polls come back in one response, in order, and // each gets an id of its own. func TestModCommandsArriveInOrder(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "s1") pollMod(t, svc.ws, "s1") @@ -231,6 +234,7 @@ func TestModCommandsArriveInOrder(t *testing.T) { // A parked poll answers as soon as a command is queued, not when its wait runs // out. func TestModParkedPollWakesOnEnqueue(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), time.Minute) runModSession(svc, "s1") got := pollInBackground(t, svc.ws, "s1") @@ -252,6 +256,7 @@ func TestModParkedPollWakesOnEnqueue(t *testing.T) { // A client that left takes nothing with it: the command it would have been // handed waits for the next poll. func TestModCancelledPollLeavesTheQueue(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), time.Minute) runModSession(svc, "s1") ctx, cancel := context.WithCancel(context.Background()) @@ -279,6 +284,7 @@ func TestModCancelledPollLeavesTheQueue(t *testing.T) { // Closing the session releases its parked poll empty-handed and detaches it. func TestModCloseReleasesTheParkedPoll(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), time.Minute) runModSession(svc, "s1") got := pollInBackground(t, svc.ws, "s1") @@ -300,6 +306,7 @@ func TestModCloseReleasesTheParkedPoll(t *testing.T) { } func TestEnqueueModCommandRefuses(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "attached") pollMod(t, svc.ws, "attached") @@ -354,6 +361,7 @@ func TestEnqueueModCommandRefuses(t *testing.T) { // A mod that stopped polling (disabled mid-session, or its process gone without // lich hearing) is detached once its re-poll grace has run out. func TestEnqueueModCommandRefusesAStaleMod(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "s1") pollMod(t, svc.ws, "s1") @@ -369,6 +377,7 @@ func TestEnqueueModCommandRefusesAStaleMod(t *testing.T) { // A command the mod never collected before it went quiet is not handed to the // mod that polls again later: by then it would land on an unrelated turn. func TestModCommandOutlivingTheAttachWindowIsDropped(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "s1") pollMod(t, svc.ws, "s1") @@ -387,6 +396,7 @@ func TestModCommandOutlivingTheAttachWindowIsDropped(t *testing.T) { // A poll whose client is already gone when it reaches the queue drains nothing: // the command stays for the next poll instead of vanishing unwritten. func TestModPollFromAGoneClientDrainsNothing(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "s1") pollMod(t, svc.ws, "s1") @@ -408,6 +418,7 @@ func TestModPollFromAGoneClientDrainsNothing(t *testing.T) { // The mod lives until Close kills its process, and the contract has it re-poll // at once. That last poll must not leave the closed session attached. func TestModPollWhileClosingDoesNotReattach(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) p := runModSession(svc, "s1") pollMod(t, svc.ws, "s1") @@ -427,6 +438,7 @@ func TestModPollWhileClosingDoesNotReattach(t *testing.T) { // A poll already on the wire when the session closed still lands. It must not // make the closed session take commands nobody will collect. func TestModPollLandingAfterCloseTakesNoCommands(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "s1") pollMod(t, svc.ws, "s1") @@ -440,6 +452,7 @@ func TestModPollLandingAfterCloseTakesNoCommands(t *testing.T) { } func TestEnqueueModCommandWithoutTransport(t *testing.T) { + t.Parallel() svc := &Service{wsErr: errors.New("listen failed")} if _, err := svc.EnqueueModCommand("s1", ModCommand{Kind: ModAbort}); err == nil { t.Fatal("enqueue succeeded with no transport") @@ -449,6 +462,7 @@ func TestEnqueueModCommandWithoutTransport(t *testing.T) { // Only an applied abort ends a turn: a failure ends nothing, and no other kind // is lich's to act on. func TestModAckCallsTheAbortSinkOnlyForAnAppliedAbort(t *testing.T) { + t.Parallel() tests := []struct { name string body string @@ -478,6 +492,7 @@ func TestModAckCallsTheAbortSinkOnlyForAnAppliedAbort(t *testing.T) { // The end of the wire for the gap this contract closes: Claude Code ends an // aborted turn without Stop, so the ack is what tells the window it is over. func TestModAbortAckEndsTheOpenTurn(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) svc := newModService(t, hub, modPollWait) postHook(t, svc, "s1", statusBusy) @@ -500,6 +515,7 @@ func TestModAbortAckEndsTheOpenTurn(t *testing.T) { // docs/hooks/fixtures/mod-commands.json, so every shape lich sends must encode // to exactly what that file says. func TestModCommandsMatchFixture(t *testing.T) { + t.Parallel() cmds := []ModCommand{ {ID: "m1", Kind: ModPrompt, Text: "run the tests"}, {ID: "m2", Kind: ModAbort}, diff --git a/internal/terminal/modprompt_test.go b/internal/terminal/modprompt_test.go index 5067ed45..ee748bab 100644 --- a/internal/terminal/modprompt_test.go +++ b/internal/terminal/modprompt_test.go @@ -11,6 +11,7 @@ import ( ) func TestSubmitPromptFindsNoModToHandItTo(t *testing.T) { + t.Parallel() unpolled := newModService(t, events.New(), 10*time.Millisecond) runModSession(unpolled, "s1") stopped := attachedModService(t) @@ -61,6 +62,7 @@ func receiptOf(t *testing.T, done <-chan relay.PromptReceipt) relay.PromptReceip } func TestSubmitPromptReportsTheAck(t *testing.T) { + t.Parallel() for _, want := range []relay.PromptReceipt{ {State: relay.PromptAcked, OK: true}, {State: relay.PromptAcked, Reason: "blocked by a hook"}, @@ -79,6 +81,7 @@ func TestSubmitPromptReportsTheAck(t *testing.T) { } func TestSubmitPromptNoPollCollectedIsWithdrawn(t *testing.T) { + t.Parallel() svc := attachedModService(t) ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -91,6 +94,7 @@ func TestSubmitPromptNoPollCollectedIsWithdrawn(t *testing.T) { } func TestSubmitPromptCollectedThenUnackedIsDelivered(t *testing.T) { + t.Parallel() svc := attachedModService(t) ctx, cancel := context.WithCancel(context.Background()) done := submitInBackground(t, ctx, svc) @@ -102,6 +106,7 @@ func TestSubmitPromptCollectedThenUnackedIsDelivered(t *testing.T) { } func TestSubmitPromptEndsWithTheSession(t *testing.T) { + t.Parallel() svc := attachedModService(t) done := submitInBackground(t, context.Background(), svc) collectMod(t, svc.ws, "s1") @@ -114,6 +119,7 @@ func TestSubmitPromptEndsWithTheSession(t *testing.T) { } func TestSubmitPromptIsCollectedOnceAPollCarriesIt(t *testing.T) { + t.Parallel() svc := attachedModService(t) handed, err := svc.SubmitPrompt("s1", "run the tests", nil) if err != nil { @@ -129,6 +135,7 @@ func TestSubmitPromptIsCollectedOnceAPollCarriesIt(t *testing.T) { } func TestModAttachedOnlyWhileAModPollsFromARunningSession(t *testing.T) { + t.Parallel() unpolled := newModService(t, events.New(), 10*time.Millisecond) runModSession(unpolled, "s1") stopped := attachedModService(t) @@ -155,6 +162,7 @@ func TestModAttachedOnlyWhileAModPollsFromARunningSession(t *testing.T) { } func TestSubmitPromptCarriesItsNotification(t *testing.T) { + t.Parallel() svc := attachedModService(t) note := &relay.Notification{Status: relay.NotifyCompleted, Summary: `lich session "docs" finished`} if _, err := svc.SubmitPrompt("s1", "the report", note); err != nil { diff --git a/internal/terminal/modstatus_test.go b/internal/terminal/modstatus_test.go index 1033aa7f..adad5456 100644 --- a/internal/terminal/modstatus_test.go +++ b/internal/terminal/modstatus_test.go @@ -34,6 +34,7 @@ func getModStatus(t *testing.T, url string) (int, []byte) { } func TestModStatusRefusesBadRequests(t *testing.T) { + t.Parallel() tr := newNilTransport(t) tests := []struct { name string @@ -68,6 +69,7 @@ func TestModStatusRefusesBadRequests(t *testing.T) { // The contract promises three lists, never null, even before the relay is // wired. func TestModStatusAnswersEmptyListsWithNothingWired(t *testing.T) { + t.Parallel() tr := newNilTransport(t) code, body := getModStatus(t, modStatusURL(tr, "s1")) @@ -81,6 +83,7 @@ func TestModStatusAnswersEmptyListsWithNothingWired(t *testing.T) { } func TestModStatusAnswersWithTheCallingSessionsErrands(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), modPollWait) var asked string svc.SetErrandStatus(func(id string) relay.Status { @@ -108,6 +111,7 @@ func TestModStatusAnswersWithTheCallingSessionsErrands(t *testing.T) { // docs/hooks/fixtures/mod-status.json, so the shape lich sends must encode to // exactly what that file says. func TestModStatusMatchesFixture(t *testing.T) { + t.Parallel() status := relay.Status{ Owed: []relay.OwedErrand{ {Ticket: "b19cb405", From: "Session 26", Asked: "Sou a sessão Claude do lich-plugin"}, diff --git a/internal/terminal/modusage_test.go b/internal/terminal/modusage_test.go index e01934e7..73c0b1fd 100644 --- a/internal/terminal/modusage_test.go +++ b/internal/terminal/modusage_test.go @@ -118,6 +118,7 @@ func TestTranscriptTokensNewerThanTheReportKeepMoving(t *testing.T) { // POSTed to /mod/usage is kept for its session and its rate limits reach the // plan gauge's reader. func TestModUsageEndpointHandsRateLimitsToTheGauge(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if svc.ws == nil { t.Fatalf("transport: %v", svc.wsErr) @@ -158,6 +159,7 @@ func TestModUsageEndpointHandsRateLimitsToTheGauge(t *testing.T) { // TestAClosedSessionDropsItsReport keeps a closed card's report from outliving // it: the id is never reused, and the map would only grow. func TestAClosedSessionDropsItsReport(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) svc.usageReports.set(reportOf("c1", 200_000)) diff --git a/internal/terminal/modwait_test.go b/internal/terminal/modwait_test.go index f9e9e179..e90e7039 100644 --- a/internal/terminal/modwait_test.go +++ b/internal/terminal/modwait_test.go @@ -68,6 +68,7 @@ func ackBody(session, id, kind string, ok bool, reason string) string { } func TestRunModCommandReportsTheAck(t *testing.T) { + t.Parallel() tests := []struct { name string cmd ModCommand @@ -100,6 +101,7 @@ func TestRunModCommandReportsTheAck(t *testing.T) { // A mod from before the command kind acks it as an unknown kind. What the user // can do about that is update the plugin, so that is what they are told. func TestRunModCommandNamesTheUpdateForAnOldMod(t *testing.T) { + t.Parallel() svc := attachedModService(t) done := runInBackground(context.Background(), svc, ModCommand{Kind: ModRunCommand, Name: "compact"}) cmd := collectMod(t, svc.ws, "s1") @@ -115,6 +117,7 @@ func TestRunModCommandNamesTheUpdateForAnOldMod(t *testing.T) { // nobody is told it is still coming, so it must never run. The contract // changed here: it used to stay queued for a poll inside the attach window. func TestRunModCommandTimingOutBeforeAPollWithdrawsIt(t *testing.T) { + t.Parallel() svc := attachedModService(t) ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -132,6 +135,7 @@ func TestRunModCommandTimingOutBeforeAPollWithdrawsIt(t *testing.T) { // Withdrawing one command leaves the others queued for the same session. func TestRunModCommandWithdrawsOnlyItsOwnCommand(t *testing.T) { + t.Parallel() svc := attachedModService(t) kept, err := svc.EnqueueModCommand("s1", ModCommand{Kind: ModPrompt, Text: "go"}) if err != nil { @@ -148,6 +152,7 @@ func TestRunModCommandWithdrawsOnlyItsOwnCommand(t *testing.T) { } func TestRunModCommandTimingOutAfterAPollIsDelivered(t *testing.T) { + t.Parallel() svc := attachedModService(t) ctx, cancel := context.WithCancel(context.Background()) done := runInBackground(ctx, svc, ModCommand{Kind: ModPrompt, Text: "go"}) @@ -163,6 +168,7 @@ func TestRunModCommandTimingOutAfterAPollIsDelivered(t *testing.T) { // An ack carries its session: another session's mod acking the same id does // not settle the wait. func TestRunModCommandIgnoresAnotherSessionsAck(t *testing.T) { + t.Parallel() svc := attachedModService(t) ctx, cancel := context.WithCancel(context.Background()) done := runInBackground(ctx, svc, ModCommand{Kind: ModAbort}) @@ -176,6 +182,7 @@ func TestRunModCommandIgnoresAnotherSessionsAck(t *testing.T) { } func TestRunModCommandEndsWithTheSession(t *testing.T) { + t.Parallel() svc := attachedModService(t) done := runInBackground(context.Background(), svc, ModCommand{Kind: ModAbort}) cmd := collectMod(t, svc.ws, "s1") @@ -192,6 +199,7 @@ func TestRunModCommandEndsWithTheSession(t *testing.T) { // A session whose process exited (or never started) is not running, which is // not the same fix as a session whose mod is missing. func TestRunModCommandRefusesASessionThatIsNotRunning(t *testing.T) { + t.Parallel() svc := attachedModService(t) if err := svc.Close("s1"); err != nil { t.Fatalf("close: %v", err) @@ -204,6 +212,7 @@ func TestRunModCommandRefusesASessionThatIsNotRunning(t *testing.T) { } func TestRunModCommandRefusesADetachedSession(t *testing.T) { + t.Parallel() svc := newModService(t, events.New(), 10*time.Millisecond) runModSession(svc, "s1") if _, err := svc.RunModCommand(context.Background(), "s1", ModCommand{Kind: ModAbort}); !errors.Is(err, errModDetached) { @@ -214,6 +223,7 @@ func TestRunModCommandRefusesADetachedSession(t *testing.T) { // An ack can land before anyone waits on it (a fast mod, a slow caller). It is // kept for the wait rather than lost. func TestModAckBeforeTheWaitIsKept(t *testing.T) { + t.Parallel() svc := attachedModService(t) w, err := svc.queueModCommand("s1", ModCommand{Kind: ModAbort}) if err != nil { @@ -228,6 +238,7 @@ func TestModAckBeforeTheWaitIsKept(t *testing.T) { } func TestModCommandDropsTheLeadingSlash(t *testing.T) { + t.Parallel() svc := attachedModService(t) id, err := svc.EnqueueModCommand("s1", ModCommand{Kind: ModRunCommand, Name: " /compact", Args: "keep the plan"}) if err != nil { @@ -242,6 +253,7 @@ func TestModCommandDropsTheLeadingSlash(t *testing.T) { // An ask's answer rides its ack, and an answer runs past the 4 KiB every other // hook body is held to. func TestRunModCommandReturnsAnAsksAnswer(t *testing.T) { + t.Parallel() svc := attachedModService(t) done := runInBackground(context.Background(), svc, ModCommand{Kind: ModAsk, Question: "what now?"}) cmd := collectMod(t, svc.ws, "s1") @@ -259,6 +271,7 @@ func TestRunModCommandReturnsAnAsksAnswer(t *testing.T) { } func TestModAckOverItsLimitIsRefused(t *testing.T) { + t.Parallel() svc := attachedModService(t) body := fmt.Sprintf(`{"session_id":"s1","id":"m1","kind":"ask","ok":true,"answer":%q}`, strings.Repeat("x", modAckBodyLimit)) diff --git a/internal/terminal/nested_test.go b/internal/terminal/nested_test.go index b81e0914..d58f4ef3 100644 --- a/internal/terminal/nested_test.go +++ b/internal/terminal/nested_test.go @@ -17,6 +17,7 @@ import ( // each report carries (docs/hooks/session-state.md, Nested agent CLIs). func TestHookDropsAReportFromAnotherConversation(t *testing.T) { + t.Parallel() svc, states := nestedRig(t, "host") postReport(t, svc, `{"session_id":"s1","state":"busy","provider_session_id":"nested"}`) @@ -32,6 +33,7 @@ func TestHookDropsAReportFromAnotherConversation(t *testing.T) { // The measured failure: a nested CLI exiting reported SessionEnd as the host's // `idle`, and the relay closed every errand delivered to the host. func TestNestedSessionEndDoesNotReachTheRelay(t *testing.T) { + t.Parallel() svc, states := nestedRig(t, "host") postReport(t, svc, `{"session_id":"s1","state":"busy","provider_session_id":"host"}`) @@ -51,6 +53,7 @@ func TestNestedSessionEndDoesNotReachTheRelay(t *testing.T) { // `/clear` (and `/resume`, `/new`) starts another conversation at the prompt, // with no turn open: that start is the session's own and moves the binding. func TestStartBetweenTurnsRebindsTheSession(t *testing.T) { + t.Parallel() svc, states := nestedRig(t, "host") postReport(t, svc, `{"session_id":"s1","state":"busy","provider_session_id":"host"}`) @@ -72,6 +75,7 @@ func TestStartBetweenTurnsRebindsTheSession(t *testing.T) { // The first start binds whenever it lands: a provider that reports its id late // (Crush, on its first tool call) has nothing bound to be confused with. func TestFirstStartBindsInsideATurn(t *testing.T) { + t.Parallel() svc, _ := nestedRig(t, "") postReport(t, svc, `{"session_id":"s1","state":"busy"}`) diff --git a/internal/terminal/outbox_test.go b/internal/terminal/outbox_test.go index 40d11fe7..136b361c 100644 --- a/internal/terminal/outbox_test.go +++ b/internal/terminal/outbox_test.go @@ -12,6 +12,7 @@ import ( // read loop behind them keeps draining and only this session — never its // neighbours — feels the backpressure. func TestOutboxQueuesWhileDeliveryIsStuck(t *testing.T) { + t.Parallel() release := make(chan struct{}) entered := make(chan struct{}, 1) box := newOutbox(func([]byte) { @@ -60,6 +61,7 @@ func TestOutboxQueuesWhileDeliveryIsStuck(t *testing.T) { // already queued: the exit event a caller emits after it can never overtake the // session's last bytes. func TestOutboxDeliversInOrderAndDrainsOnClose(t *testing.T) { + t.Parallel() var mu sync.Mutex var got []string box := newOutbox(func(data []byte) { @@ -83,6 +85,7 @@ func TestOutboxDeliversInOrderAndDrainsOnClose(t *testing.T) { // TestOutboxCopiesTheFrame proves a caller may reuse its buffer after push — // the coalescer hands over the slice it keeps appending to. func TestOutboxCopiesTheFrame(t *testing.T) { + t.Parallel() delivered := make(chan []byte, 1) box := newOutbox(func(data []byte) { delivered <- data }, 1) diff --git a/internal/terminal/paste_unfold_test.go b/internal/terminal/paste_unfold_test.go index 0e6d7fac..9314d1a3 100644 --- a/internal/terminal/paste_unfold_test.go +++ b/internal/terminal/paste_unfold_test.go @@ -18,6 +18,7 @@ func opencodeConfig(t *testing.T, env []string) map[string]any { } func TestPasteUnfoldEnvTurnsOffOpencodePasteSummary(t *testing.T) { + t.Parallel() env, err := pasteUnfoldEnv([]string{"HOME=/h"}, providers.OpenCode, true) if err != nil { t.Fatalf("pasteUnfoldEnv: %v", err) @@ -32,6 +33,7 @@ func TestPasteUnfoldEnvTurnsOffOpencodePasteSummary(t *testing.T) { } func TestPasteUnfoldEnvLeavesEveryOtherSpawnAlone(t *testing.T) { + t.Parallel() base := []string{"HOME=/h"} cases := []struct { kind string @@ -54,6 +56,7 @@ func TestPasteUnfoldEnvLeavesEveryOtherSpawnAlone(t *testing.T) { } func TestPasteUnfoldEnvExtendsTheUsersOwnInlineConfig(t *testing.T) { + t.Parallel() own := opencodeConfigContentVar + `={"model":"m","experimental":{"other":1}}` env, err := pasteUnfoldEnv([]string{own}, providers.OpenCode, true) if err != nil { @@ -70,6 +73,7 @@ func TestPasteUnfoldEnvExtendsTheUsersOwnInlineConfig(t *testing.T) { } func TestPasteUnfoldEnvRefusesAnInlineConfigItCannotExtend(t *testing.T) { + t.Parallel() for _, own := range []string{`not json`, `{"experimental":true}`} { _, err := pasteUnfoldEnv([]string{opencodeConfigContentVar + "=" + own}, providers.OpenCode, true) if err == nil { diff --git a/internal/terminal/pluginversion_test.go b/internal/terminal/pluginversion_test.go index 2661cabe..bf787f1d 100644 --- a/internal/terminal/pluginversion_test.go +++ b/internal/terminal/pluginversion_test.go @@ -26,6 +26,7 @@ func incompatibleRecorder(p *pluginVersions) func() []string { } func TestPluginVersionsRaisesOncePerSessionAndRelease(t *testing.T) { + t.Parallel() var p pluginVersions raised := incompatibleRecorder(&p) @@ -46,12 +47,14 @@ func TestPluginVersionsRaisesOncePerSessionAndRelease(t *testing.T) { } func TestPluginVersionsWithoutCallback(t *testing.T) { + t.Parallel() var p pluginVersions p.note("a", "9.0.0") // must not panic with nothing wired } // The header rides every hook endpoint, since servePost is where it is read. func TestHookEndpointsReadThePluginHeader(t *testing.T) { + t.Parallel() for _, e := range hookEndpoints { t.Run(e.path, func(t *testing.T) { tr := newNilTransport(t) @@ -79,6 +82,7 @@ func TestHookEndpointsReadThePluginHeader(t *testing.T) { } func TestPluginLabel(t *testing.T) { + t.Parallel() if got := pluginLabel(""); got != "absent" { t.Errorf("pluginLabel(\"\") = %q", got) } diff --git a/internal/terminal/profile_unix_test.go b/internal/terminal/profile_unix_test.go index 304d94d5..54de07a6 100644 --- a/internal/terminal/profile_unix_test.go +++ b/internal/terminal/profile_unix_test.go @@ -42,6 +42,7 @@ func spawnLines(t *testing.T, log string) []string { // TestSpawnLinesCountsOnlyWholeLines: `>>` creates the log before echo writes // to it, so a read in between sees an empty file, or half a line. func TestSpawnLinesCountsOnlyWholeLines(t *testing.T) { + t.Parallel() log := filepath.Join(t.TempDir(), "spawns") for _, c := range []struct{ data, want string }{ {"", "[]"}, diff --git a/internal/terminal/prompt_language_test.go b/internal/terminal/prompt_language_test.go index 562285e5..51230e97 100644 --- a/internal/terminal/prompt_language_test.go +++ b/internal/terminal/prompt_language_test.go @@ -10,6 +10,7 @@ import ( ) func TestTheBriefingIsWrittenInThePromptLanguage(t *testing.T) { + t.Parallel() for _, lang := range prompt.Langs { args := providerArgs(providers.Claude, "", "", "", "", "", "", false, false, false, relay.RouteSessions, lang) want := relay.SpawnBriefing(lang, providers.AcceptsMCPServer(providers.Claude), relay.RouteSessions) diff --git a/internal/terminal/prompt_language_unix_test.go b/internal/terminal/prompt_language_unix_test.go index 12d4a2ab..4705ae4c 100644 --- a/internal/terminal/prompt_language_unix_test.go +++ b/internal/terminal/prompt_language_unix_test.go @@ -13,6 +13,7 @@ import ( // has no way to read the setting, so every session carries the language it was // spawned with. Unix-only for its stubBins store, which lives in the Unix suite. func TestSessionEnvExportsThePromptLanguage(t *testing.T) { + t.Parallel() s := &Service{env: []string{"A=1"}, store: stubBins{}, ws: &transport{port: 4321, token: "tok"}} if env := s.sessionEnv("sess", "p1", ""); !slices.Contains(env, "LICH_PROMPT_LANG=en") { t.Errorf("an unwired spawn did not export English: %v", env) @@ -25,6 +26,7 @@ func TestSessionEnvExportsThePromptLanguage(t *testing.T) { } func TestSessionEnvExportsSpanish(t *testing.T) { + t.Parallel() s := &Service{env: []string{"A=1"}, store: stubBins{}, ws: &transport{port: 4321, token: "tok"}} s.SetPromptLanguage(func() prompt.Lang { return prompt.Spanish }) if env := s.sessionEnv("sess", "p1", ""); !slices.Contains(env, "LICH_PROMPT_LANG=es") { @@ -33,6 +35,7 @@ func TestSessionEnvExportsSpanish(t *testing.T) { } func TestSessionEnvExportsJapanese(t *testing.T) { + t.Parallel() s := &Service{env: []string{"A=1"}, store: stubBins{}, ws: &transport{port: 4321, token: "tok"}} s.SetPromptLanguage(func() prompt.Lang { return prompt.Japanese }) if env := s.sessionEnv("sess", "p1", ""); !slices.Contains(env, "LICH_PROMPT_LANG=ja") { @@ -41,6 +44,7 @@ func TestSessionEnvExportsJapanese(t *testing.T) { } func TestSessionEnvExportsKorean(t *testing.T) { + t.Parallel() s := &Service{env: []string{"A=1"}, store: stubBins{}, ws: &transport{port: 4321, token: "tok"}} s.SetPromptLanguage(func() prompt.Lang { return prompt.Korean }) if env := s.sessionEnv("sess", "p1", ""); !slices.Contains(env, "LICH_PROMPT_LANG=ko") { @@ -49,6 +53,7 @@ func TestSessionEnvExportsKorean(t *testing.T) { } func TestSessionEnvExportsRussian(t *testing.T) { + t.Parallel() s := &Service{env: []string{"A=1"}, store: stubBins{}, ws: &transport{port: 4321, token: "tok"}} s.SetPromptLanguage(func() prompt.Lang { return prompt.Russian }) if env := s.sessionEnv("sess", "p1", ""); !slices.Contains(env, "LICH_PROMPT_LANG=ru") { diff --git a/internal/terminal/pty_test.go b/internal/terminal/pty_test.go index 61c42bcf..65db03fa 100644 --- a/internal/terminal/pty_test.go +++ b/internal/terminal/pty_test.go @@ -11,6 +11,7 @@ import ( // This is the pure slice of commandLine; the LookPath/ComposeCommandLine // boundaries around it stay in pty_windows.go and run only under Windows CI. func TestWrapArgv(t *testing.T) { + t.Parallel() cases := []struct { name string path string diff --git a/internal/terminal/pty_unix_test.go b/internal/terminal/pty_unix_test.go index fc5e3e98..d59e17e0 100644 --- a/internal/terminal/pty_unix_test.go +++ b/internal/terminal/pty_unix_test.go @@ -57,6 +57,7 @@ func startTrapped(t *testing.T, trap string) ptyHandle { // run its exit path — Claude Code counts a session killed during its first ten // seconds as a failed fullscreen start (see closeGrace). func TestCloseHangsUpBeforeKilling(t *testing.T) { + t.Parallel() p := startTrapped(t, "exit 7") if err := p.Close(); err != nil { @@ -72,6 +73,7 @@ func TestCloseHangsUpBeforeKilling(t *testing.T) { // period and not a promise: a child that ignores SIGTERM is still gone when // Close returns, the way it was before the signal was ever sent. func TestCloseKillsAChildThatIgnoresTheHangUp(t *testing.T) { + t.Parallel() p := startTrapped(t, "") start := time.Now() @@ -100,6 +102,7 @@ func TestCloseKillsAChildThatIgnoresTheHangUp(t *testing.T) { // while stream reaps the same one the moment its child dies. Handing that error // to whichever lost failed the close its caller asked for. func TestUnixPTYCloseIsSingleShot(t *testing.T) { + t.Parallel() p := startTrapped(t, "exit 7") if err := p.Close(); err != nil { diff --git a/internal/terminal/replay_test.go b/internal/terminal/replay_test.go index 568c5c30..3e561615 100644 --- a/internal/terminal/replay_test.go +++ b/internal/terminal/replay_test.go @@ -8,6 +8,7 @@ import ( ) func TestReplayBufferKeepsOutputInOrder(t *testing.T) { + t.Parallel() b := newReplayBuffer(1024) b.append([]byte("hello ")) b.append([]byte("world")) @@ -17,6 +18,7 @@ func TestReplayBufferKeepsOutputInOrder(t *testing.T) { } func TestReplayBufferIgnoresEmptyAppend(t *testing.T) { + t.Parallel() b := newReplayBuffer(1024) b.append(nil) b.append([]byte{}) @@ -26,6 +28,7 @@ func TestReplayBufferIgnoresEmptyAppend(t *testing.T) { } func TestReplayBufferCopiesInput(t *testing.T) { + t.Parallel() b := newReplayBuffer(1024) buf := []byte("abc") b.append(buf) @@ -36,6 +39,7 @@ func TestReplayBufferCopiesInput(t *testing.T) { } func TestReplayBufferDropsOldestOnOverflow(t *testing.T) { + t.Parallel() b := newReplayBuffer(10) b.append([]byte("aaaaa")) // 5 b.append([]byte("bbbbb")) // 10, still within cap @@ -50,6 +54,7 @@ func TestReplayBufferDropsOldestOnOverflow(t *testing.T) { } func TestReplayBufferKeepsOneChunkLargerThanCap(t *testing.T) { + t.Parallel() b := newReplayBuffer(4) big := bytes.Repeat([]byte("x"), 100) b.append(big) @@ -61,6 +66,7 @@ func TestReplayBufferKeepsOneChunkLargerThanCap(t *testing.T) { // A bare Service with a hand-built session exercises Replay without a PTY, so // this stays OS-agnostic (no !windows build tag): Replay only reads the tail. func TestServiceReplayEncodesTheTail(t *testing.T) { + t.Parallel() b := newReplayBuffer(replayCapBytes) b.append([]byte("scrollback")) svc := &Service{sessions: map[string]*session{"s1": {replay: b}}} @@ -79,6 +85,7 @@ func TestServiceReplayEncodesTheTail(t *testing.T) { } func TestServiceReplayUnknownSessionIsEmpty(t *testing.T) { + t.Parallel() svc := &Service{sessions: map[string]*session{}} got, err := svc.Replay("nope") if err != nil { diff --git a/internal/terminal/respawn_test.go b/internal/terminal/respawn_test.go index 23ddc5ad..5aaea07d 100644 --- a/internal/terminal/respawn_test.go +++ b/internal/terminal/respawn_test.go @@ -59,6 +59,7 @@ func respawn(t *testing.T, svc *Service, id string) *session { // so one landing on a session respawned under the same id lies about a live // shell. func TestRespawnedSessionIsNotToldItExited(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) svc := New(stubBins{bin: echoBin(t)}, nil, hub) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -78,6 +79,7 @@ func TestRespawnedSessionIsNotToldItExited(t *testing.T) { // evicts only its own PTY, so after Close+Start under one id that id still // resolves to the new PTY and input still reaches it. func TestRespawnedSessionKeepsItsPTY(t *testing.T) { + t.Parallel() svc := New(stubBins{bin: echoBin(t)}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -105,6 +107,7 @@ func TestRespawnedSessionKeepsItsPTY(t *testing.T) { // The card reads it as the reason the session ended, and only a real spawn shows // whether the number survives the trip. func TestExitEventCarriesTheExitCode(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) svc := New(stubBins{bin: exitBin(t, 3)}, nil, hub) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -133,6 +136,7 @@ func TestExitEventCarriesTheExitCode(t *testing.T) { // the path that produces it: a child killed by a signal left no exit status, so // the event has to say nothing rather than report the 0 of a clean exit. func TestExitEventOmitsAnUnobservedStatus(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) // echoBin execs cat, so the pid below is the process holding the PTY — // a script that forked one would leave the slave open and never reach EOF. @@ -191,6 +195,7 @@ func replayOf(t *testing.T, svc *Service, id string) string { // under it, takes the turn with it: the count that keeps the machine awake // must fall on the reap, not wait for someone to close the card. func TestReapForgetsOpenTurn(t *testing.T) { + t.Parallel() svc := New(stubBins{bin: echoBin(t)}, nil, events.New()) var open atomic.Int64 svc.turns.onOpen = func(n int) { open.Store(int64(n)) } diff --git a/internal/terminal/resume_test.go b/internal/terminal/resume_test.go index e5a270ce..eab6e156 100644 --- a/internal/terminal/resume_test.go +++ b/internal/terminal/resume_test.go @@ -229,6 +229,7 @@ func TestCursorConfigDirPrecedence(t *testing.T) { // this repository's own checkout, so a change to how lich composes the key fails // here rather than by answering "conversation gone" to every Cursor resume. func TestCursorWorkspaceKey(t *testing.T) { + t.Parallel() // The literal pair was read off a real Cursor install, and it only answers // where that string is an absolute path: on Windows it is not one, Abs would // prefix the process's own drive and the hash would be of a different path @@ -260,6 +261,7 @@ func TestCursorWorkspaceKey(t *testing.T) { // fresh has lost a resume; one that errors has lost the session, inside the PTY, // with the provider's message in place of it. func TestSessionRowExistsSurvivesAnotherToolsSchema(t *testing.T) { + t.Parallel() dir := t.TempDir() renamed := filepath.Join(dir, "renamed.db") diff --git a/internal/terminal/said_test.go b/internal/terminal/said_test.go index fdf05c36..361ec807 100644 --- a/internal/terminal/said_test.go +++ b/internal/terminal/said_test.go @@ -69,6 +69,7 @@ func TestLastTurnSaidMissesAreSilent(t *testing.T) { // sub-agent — so a reader that matched too widely would fail here rather than in // front of a user. func TestSaidReadersPickTheLastProse(t *testing.T) { + t.Parallel() tests := []struct { name string read turnReader @@ -142,6 +143,7 @@ func TestSaidReadersPickTheLastProse(t *testing.T) { // TestSaidWalkSkipsAHalfLine pins that a line the seed tail cut in half costs // the cut line and nothing else. func TestSaidWalkSkipsAHalfLine(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "transcript.jsonl") body := `ontent":[{"type":"text","text":"cut in half"}]}}` + "\n" + `{"type":"assistant","message":{"content":[{"type":"text","text":"whole line"}]}}` + "\n" @@ -158,6 +160,7 @@ func TestSaidWalkSkipsAHalfLine(t *testing.T) { // format, and Kiro must reach the `.jsonl` rather than the `.json` its source // carries — the one arm that transforms the path it is given. func TestSaidRoutesByProvider(t *testing.T) { + t.Parallel() tests := []struct { kind string // name of the file to plant; the source's path is `file` unless the @@ -209,6 +212,7 @@ func TestSaidRoutesByProvider(t *testing.T) { // lives on the message row and the text on a part row beside it, so the join is // what keeps a tool result from being read as the agent's own words. func TestSaidReadsTheOpenCodeDatabase(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "opencode.db") db, err := sql.Open("sqlite", path) if err != nil { @@ -244,6 +248,7 @@ func TestSaidReadsTheOpenCodeDatabase(t *testing.T) { // a chat filed as content-addressed blobs is not a transcript to walk and not a // query to run, so the read is empty rather than wrong (docs/ceilings.md). func TestSaidIsEmptyForCursor(t *testing.T) { + t.Parallel() src := usageSource{kind: providers.Cursor, path: "/nope", id: "x"} if got := new(saidCursors).said("s1", src); got != "" { t.Errorf("said(cursor) = %q, want empty", got) @@ -254,6 +259,7 @@ func TestSaidIsEmptyForCursor(t *testing.T) { // on the file kiroSessionPath resolves, so no other provider's path can be // turned into a Kiro transcript by suffix alone. func TestKiroTranscriptPathAnswersOnlyForItsMetadata(t *testing.T) { + t.Parallel() if got := kiroTranscriptPath("/k/sessions/cli/abc.json"); got != "/k/sessions/cli/abc.jsonl" { t.Errorf("kiroTranscriptPath = %q, want the .jsonl beside it", got) } @@ -267,6 +273,7 @@ func TestKiroTranscriptPathAnswersOnlyForItsMetadata(t *testing.T) { // worth as much as the recap it opens with. Counted in runes, so the cut can // never land inside a character. func TestCapRunesKeepsBothEnds(t *testing.T) { + t.Parallel() text := strings.Repeat("á", 100) + "TAIL" got := capRunes(text, 30) if !strings.HasPrefix(got, strings.Repeat("á", 20)) { @@ -289,6 +296,7 @@ func TestCapRunesKeepsBothEnds(t *testing.T) { // tool output (the band used to go empty there, which is what a turn that // ended on a tool call looks like), and a new turn's words replace it. func TestSaidCursorContinuesWhereItStopped(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "t.jsonl") writeLines(t, path, saidLine("First turn.")) var cursors saidCursors @@ -313,6 +321,7 @@ func TestSaidCursorContinuesWhereItStopped(t *testing.T) { // cursor exists. Every other cursor test appends before it re-walks, so this is // the one that pins a walk with no growth behind it. func TestSaidCursorStandsOnAnUnchangedTranscript(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "t.jsonl") writeLines(t, path, saidLine("Three failures in parser_test.go.")) var cursors saidCursors @@ -333,6 +342,7 @@ func TestSaidCursorStandsOnAnUnchangedTranscript(t *testing.T) { // that ended on a tool call looks like. The cursor walks what was appended and // keeps the words it already read. func TestSaidCursorKeepsWordsBehindTheTailBound(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "t.jsonl") writeLines(t, path, saidLine("Three failures.")) var cursors saidCursors @@ -351,6 +361,7 @@ func TestSaidCursorKeepsWordsBehindTheTailBound(t *testing.T) { // tail holding no prose at all (one tool result larger than it) is walked // again from the start rather than answered empty. func TestSaidCursorSeedsPastAHugeToolResult(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "t.jsonl") writeLines(t, path, saidLine("Behind the wall."), saidPad(searchTailBytes+(1<<20))) @@ -363,6 +374,7 @@ func TestSaidCursorSeedsPastAHugeToolResult(t *testing.T) { // be trusted in: a file that shrank under it, and a file it was never counted // against: a conversation forked or rewritten (see saidCursor). func TestSaidCursorResetsOnANewTranscript(t *testing.T) { + t.Parallel() dir := t.TempDir() path := filepath.Join(dir, "t.jsonl") writeLines(t, path, saidLine("Long conversation."), saidPad(16<<10)) diff --git a/internal/terminal/sandbox_report_test.go b/internal/terminal/sandbox_report_test.go index 230de46d..ab1d31bb 100644 --- a/internal/terminal/sandbox_report_test.go +++ b/internal/terminal/sandbox_report_test.go @@ -18,6 +18,7 @@ import ( // are resolved once, by a spawn that outlives the page, so they have to reach // both the window watching now and the row a reload reads them back from. func TestReportSandboxRecordsAndAnnouncesTheSkippedLinks(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) recorded := map[string][]string{} svc := New(stubBins{sandboxLinks: recorded}, nil, hub) @@ -44,6 +45,7 @@ func TestReportSandboxRecordsAndAnnouncesTheSkippedLinks(t *testing.T) { // by then, so the window watching now still has the line; refusing to open a // terminal over what its tooltip says would be the worse trade. func TestReportSandboxSurvivesARowThatRefusesTheWrite(t *testing.T) { + t.Parallel() hub, rec := newProbeHub(t) svc := New(stubBins{sandboxLinksErr: errors.New("database is locked")}, nil, hub) diff --git a/internal/terminal/sandbox_test.go b/internal/terminal/sandbox_test.go index a0df9a35..a7e8e68f 100644 --- a/internal/terminal/sandbox_test.go +++ b/internal/terminal/sandbox_test.go @@ -35,6 +35,7 @@ func (s stubSandboxStore) SetSessionSandbox(_, sandbox string) error { // when the rung is turned off under it, and one opened on the machine is not // confined later because the rung moved. func TestConfinedPrefersTheSessionRow(t *testing.T) { + t.Parallel() tests := []struct { row string rung bool @@ -64,6 +65,7 @@ func baseSpec() ptySpec { const t0dir = "/tmp" func TestWrapSandboxLeavesAnUnconfinedSpawnAlone(t *testing.T) { + t.Parallel() original := baseSpec() for _, tt := range []struct { name string @@ -81,6 +83,7 @@ func TestWrapSandboxLeavesAnUnconfinedSpawnAlone(t *testing.T) { } func TestWrapSandboxKeepsTheCommandAndItsArguments(t *testing.T) { + t.Parallel() if !sandbox.Available() { t.Skip("no sandbox backend on this machine") } @@ -123,6 +126,7 @@ func lastIndex(args []string, value string) int { // binary case is BinaryDirs' own (TestBinaryDirsOfAMissingBinary); what is here // is that this caller asks for both binaries and passes the answer through. func TestExecutablesAreDirectoriesOnDisk(t *testing.T) { + t.Parallel() dirs := executables("sh") if len(dirs) == 0 { t.Fatal("sh resolved to nothing") @@ -158,6 +162,7 @@ func TestExecutablesAreDirectoriesOnDisk(t *testing.T) { // already — a bind of a source that does not exist is dropped from the spec, // and the copy would then land where the session cannot read it. func TestSessionDropDirCreatesTheDirectory(t *testing.T) { + t.Parallel() copies := t.TempDir() got := sessionDropDir(copies, "s1", true) @@ -174,6 +179,7 @@ func TestSessionDropDirCreatesTheDirectory(t *testing.T) { // its card. An unconfined one wants no directory at all — it opens the copy at // the path it was written to, like any other file. func TestSessionDropDirWithoutABase(t *testing.T) { + t.Parallel() for _, tt := range []struct { base, session string confined bool @@ -196,6 +202,7 @@ func TestSessionDropDirWithoutABase(t *testing.T) { // dir, which the sandbox mounts read-only, and a session that cannot write its // own config dir cannot keep a conversation. func TestWrapSandboxBindsTheClaudeProfile(t *testing.T) { + t.Parallel() if !sandbox.Available() { t.Skip("no sandbox backend on this machine") } @@ -211,6 +218,7 @@ func TestWrapSandboxBindsTheClaudeProfile(t *testing.T) { // version's .in_use marker through that link, so the store is bound writable // too or a confined session cannot mark the plugins it runs. func TestWrapSandboxBindsTheClaudePluginStore(t *testing.T) { + t.Parallel() if !sandbox.Available() { t.Skip("no sandbox backend on this machine") } @@ -232,6 +240,7 @@ func TestWrapSandboxBindsTheClaudePluginStore(t *testing.T) { // path pasted at the prompt only names something the agent can open if the // directory holding it is bound in. func TestWrapSandboxMountsTheSessionsCopies(t *testing.T) { + t.Parallel() if !sandbox.Available() { t.Skip("no sandbox backend on this machine") } @@ -253,6 +262,7 @@ func TestWrapSandboxMountsTheSessionsCopies(t *testing.T) { // is readable by every process on the machine, so a token passed through // bubblewrap's --setenv would be handed to more than the session it is for. func TestWrapSandboxKeepsTheTokenOutOfTheArguments(t *testing.T) { + t.Parallel() if _, err := exec.LookPath("bwrap"); err != nil { t.Skip("bubblewrap is not installed") } @@ -270,6 +280,7 @@ func TestWrapSandboxKeepsTheTokenOutOfTheArguments(t *testing.T) { // No flag, no credential: the sandbox as it shipped puts nothing in the // environment, so a session that was never granted a token cannot find one. func TestWrapSandboxAddsNoTokenWithoutOne(t *testing.T) { + t.Parallel() if _, err := exec.LookPath("bwrap"); err != nil { t.Skip("bubblewrap is not installed") } @@ -287,6 +298,7 @@ func TestWrapSandboxAddsNoTokenWithoutOne(t *testing.T) { // wrapSandbox hand back nil and leave a session unable to find its own // ~/.gitconfig with nothing to read about why. func TestWrapSandboxNamesTheLinksItSkipped(t *testing.T) { + t.Parallel() if !sandbox.Available() { t.Skip("no sandbox backend on this machine") } @@ -310,6 +322,7 @@ func TestWrapSandboxNamesTheLinksItSkipped(t *testing.T) { // the sandbox's own answer, and reporting one for a session running on the // machine would put a warning on a card that has no sandbox to warn about. func TestWrapSandboxSkipsNoLinksWhenUnconfined(t *testing.T) { + t.Parallel() if _, links := wrapSandbox(baseSpec(), providers.Claude, t.TempDir(), "", "", false, sandboxCreds{}); links != nil { t.Errorf("an unconfined spawn reported skipped links %v", links) } @@ -320,6 +333,7 @@ func TestWrapSandboxSkipsNoLinksWhenUnconfined(t *testing.T) { // question a session lich opened unattended would otherwise stop on. Another // provider is told nothing it would not understand. func TestWrapSandboxSkipsClaudesTrustQuestion(t *testing.T) { + t.Parallel() if !sandbox.Available() { t.Skip("no sandbox backend on this machine") } @@ -342,6 +356,7 @@ func TestWrapSandboxSkipsClaudesTrustQuestion(t *testing.T) { // trusted, as the inline table Codex accepts, and a confined Cursor CLI gets // --trust; an unconfined spawn and a provider without a flag get nothing. func TestConfinedTrustArgs(t *testing.T) { + t.Parallel() main, worktree := gitWorktree(t) want := []string{"-c", "projects={" + strconv.Quote(main) + `={trust_level="trusted"}}`} if got := confinedTrustArgs(providers.Codex, worktree, true); !slices.Equal(got, want) { @@ -365,6 +380,7 @@ func TestConfinedTrustArgs(t *testing.T) { } func TestWrapSandboxSkipsOMPSetup(t *testing.T) { + t.Parallel() if !sandbox.Available() { t.Skip("no sandbox backend on this machine") } diff --git a/internal/terminal/search_e2e_test.go b/internal/terminal/search_e2e_test.go index 860aa190..ffd8c6cb 100644 --- a/internal/terminal/search_e2e_test.go +++ b/internal/terminal/search_e2e_test.go @@ -126,6 +126,7 @@ func plantCodexRollout(t *testing.T, providerSessionID, body string) { // snippet: lowercasing "Ⱥ" yields a longer rune, so a match offset taken from a // lowered copy runs past the end of the text it is meant to index. func TestSnippetAroundSurvivesGrowingCaseFolding(t *testing.T) { + t.Parallel() for _, text := range []string{ strings.Repeat("Ⱥ", 200) + " worktree", strings.Repeat("İ", 200) + " worktree", diff --git a/internal/terminal/search_test.go b/internal/terminal/search_test.go index c62983a1..cfb16e8c 100644 --- a/internal/terminal/search_test.go +++ b/internal/terminal/search_test.go @@ -19,6 +19,7 @@ func assistantText(text string) string { } func TestSearchTranscript(t *testing.T) { + t.Parallel() toolCall := `{"type":"assistant","message":{"content":[{"type":"tool_use","name":"Bash",` + `"input":{"command":"grep -r worktree ."}}]}}` toolResult := `{"type":"user","message":{"content":[{"type":"tool_result",` + @@ -119,6 +120,7 @@ func TestSearchTranscript(t *testing.T) { } func TestSearchTranscriptsRejectsAShortQuery(t *testing.T) { + t.Parallel() // Two characters match nearly every conversation, so the scan is refused // before any transcript is opened — a bare Service (nil store) proves it // never reaches one. @@ -135,6 +137,7 @@ func TestSearchTranscriptsRejectsAShortQuery(t *testing.T) { // provider writes around them, because a search is asked from either side: the // words somebody remembers are as often their own prompt as the agent's answer. func TestSearchTranscriptReadsEveryProviderFormat(t *testing.T) { + t.Parallel() tests := []struct { name string read turnReader @@ -202,6 +205,7 @@ func TestSearchTranscriptReadsEveryProviderFormat(t *testing.T) { // one docs/ceilings.md carries: opencode files its messages in SQLite and Cursor // CLI in a blob store, so neither has a line for a reader to walk. func TestTranscriptReaderForNamesTheProvidersWithAJSONL(t *testing.T) { + t.Parallel() for _, kind := range []string{ providers.Claude, providers.Codex, providers.OMP, providers.Kiro, providers.Antigravity, } { diff --git a/internal/terminal/sessiondb_text_test.go b/internal/terminal/sessiondb_text_test.go index 220187fa..14145c67 100644 --- a/internal/terminal/sessiondb_text_test.go +++ b/internal/terminal/sessiondb_text_test.go @@ -89,6 +89,7 @@ func crushMessageDB(t *testing.T) string { // newest assistant message: the thinking that preceded it is not something the // agent said, and another conversation's closing words are not this one's. func TestSaidReadsTheCrushDatabase(t *testing.T) { + t.Parallel() src := usageSource{kind: providers.Crush, path: crushMessageDB(t), id: "crush-1"} if got := new(saidCursors).said("s1", src); got != "The worktree port is a hash." { t.Errorf("said = %q, want the newest assistant text part", got) @@ -100,6 +101,7 @@ func TestSaidReadsTheCrushDatabase(t *testing.T) { // in a tool's arguments is not, and the newest match is what the palette row // shows. func TestSearchSourceCountsAndWindowsADatabase(t *testing.T) { + t.Parallel() for _, tc := range []struct { kind string path string @@ -125,6 +127,7 @@ func TestSearchSourceCountsAndWindowsADatabase(t *testing.T) { // reverse: opencode files each sub-agent as a session of its own, and a hit in // one points at a message this session never showed. func TestSearchSourceLeavesOpenCodeSubAgentsOut(t *testing.T) { + t.Parallel() path := opencodeMessageDB(t) parent, ok := searchSource(usageSource{kind: providers.OpenCode, path: path, id: "ses_1"}, "worktree") if !ok || parent.Count != 3 { @@ -142,6 +145,7 @@ func TestSearchSourceLeavesOpenCodeSubAgentsOut(t *testing.T) { // nothing in it — never as an error at the card, which is what the cost readers // promise too. func TestSessionDBTextsIsSilentForASchemaThatMoved(t *testing.T) { + t.Parallel() path := writeMessageDB(t, `CREATE TABLE messages_v2 (id TEXT PRIMARY KEY, prose TEXT)`, `INSERT INTO messages_v2 VALUES ('m1','a worktree line nothing here can reach')`) for _, kind := range []string{providers.OpenCode, providers.Crush} { diff --git a/internal/terminal/setup_test.go b/internal/terminal/setup_test.go index a7464ce4..ed019933 100644 --- a/internal/terminal/setup_test.go +++ b/internal/terminal/setup_test.go @@ -12,6 +12,7 @@ import ( // subshell and the original argv exec'd after it, quoted against spaces and // embedded quotes. func TestWrapSetup(t *testing.T) { + t.Parallel() base := ptySpec{ bin: "/opt/claude bin/claude", args: []string{"--resume", "it's-an-id"}, @@ -52,6 +53,7 @@ func TestWrapSetup(t *testing.T) { // the PTY and the pid are the same across the exec — and a session still // running its setup must not be handed work (see Service.Ready). func TestSetupWrapperMarksItsEnd(t *testing.T) { + t.Parallel() spec, _ := wrapSetup(ptySpec{bin: "claude", args: []string{"--name", "x"}}, "pnpm install", "linux") script := spec.args[1] @@ -72,6 +74,7 @@ func TestSetupWrapperMarksItsEnd(t *testing.T) { // The escaped form printf receives has to produce the bytes the service scans // for; two spellings of one sequence would mean a marker nobody ever matches. func TestSetupMarkerSpellingsAgree(t *testing.T) { + t.Parallel() unescaped := strings.ReplaceAll(setupDoneEscaped, `\033`, "\x1b") unescaped = strings.ReplaceAll(unescaped, `\007`, "\x07") if unescaped != setupDone { @@ -84,6 +87,7 @@ func TestSetupMarkerSpellingsAgree(t *testing.T) { // wrapSetup refuses is exactly the script this announces, so neither OS can end // up both skipping the setup and saying nothing about it. func TestSetupSkippedNotice(t *testing.T) { + t.Parallel() for _, tt := range []struct{ script, goos string }{ {"pnpm i", "linux"}, {"pnpm i", "darwin"}, @@ -132,6 +136,7 @@ func TestSetupSkippedNotice(t *testing.T) { // Unix-only, so a test that went through spawnSession would assert this // nowhere. func TestAnnounceReachesBothTheReplayAndTheWindow(t *testing.T) { + t.Parallel() var written []byte sess := &session{ replay: newReplayBuffer(replayCapBytes), @@ -153,6 +158,7 @@ func TestAnnounceReachesBothTheReplayAndTheWindow(t *testing.T) { // notice, and a blank line pushed into a fresh session's scrollback would be a // line the user has to account for. func TestAnnounceWritesNothingWithoutALine(t *testing.T) { + t.Parallel() emitted := false sess := &session{ replay: newReplayBuffer(replayCapBytes), diff --git a/internal/terminal/shell_test.go b/internal/terminal/shell_test.go index c71bdf97..88679ace 100644 --- a/internal/terminal/shell_test.go +++ b/internal/terminal/shell_test.go @@ -11,6 +11,7 @@ import ( // that reads COMSPEC, which names it on every box — fails here rather than on a // user's machine. func TestWindowsShellsPreferPowerShell7(t *testing.T) { + t.Parallel() if len(windowsShells) != 2 || windowsShells[0] != "pwsh.exe" || windowsShells[1] != "powershell.exe" { t.Errorf("windowsShells = %v, want pwsh.exe before powershell.exe", windowsShells) } diff --git a/internal/terminal/shellenv_test.go b/internal/terminal/shellenv_test.go index 3322c722..f1a279c8 100644 --- a/internal/terminal/shellenv_test.go +++ b/internal/terminal/shellenv_test.go @@ -11,6 +11,7 @@ import ( ) func TestParseShellEnvDump(t *testing.T) { + t.Parallel() tests := []struct { name string out string @@ -68,6 +69,7 @@ func TestParseShellEnvDump(t *testing.T) { } func TestValidEnvKey(t *testing.T) { + t.Parallel() valid := []string{"PATH", "_", "_X", "A1", "MY_VAR_2"} invalid := []string{"", "1ABC", "MY-VAR", "MY VAR", "="} for _, k := range valid { @@ -83,6 +85,7 @@ func TestValidEnvKey(t *testing.T) { } func TestMergeEnv(t *testing.T) { + t.Parallel() base := []string{"PATH=/orig", "BASE_ONLY=1"} extra := []string{"PATH=/shell/bin", "TOKEN=secret"} got := mergeEnv(base, extra) @@ -93,6 +96,7 @@ func TestMergeEnv(t *testing.T) { } func TestMergeEnvBaseUntouchedWithoutExtra(t *testing.T) { + t.Parallel() base := []string{"A=1", "B=2"} if got := mergeEnv(base, nil); !slices.Equal(got, base) { t.Fatalf("mergeEnv(base, nil) = %v, want %v", got, base) @@ -262,6 +266,7 @@ func TestReresolveShellEnvWithoutShellRepinsLaunchPATH(t *testing.T) { } func TestShellDumpDigestRedactsAssignments(t *testing.T) { + t.Parallel() got := shellDumpDigest("zsh: no such file\nAWS_SECRET_ACCESS_KEY=hunter2\n") if strings.Contains(got, "hunter2") { t.Fatalf("digest leaked a value: %q", got) @@ -276,6 +281,7 @@ func TestShellDumpDigestRedactsAssignments(t *testing.T) { } func TestShellDumpDigestBounds(t *testing.T) { + t.Parallel() if got := shellDumpDigest(""); got != "printed nothing" { t.Errorf("empty dump = %q", got) } diff --git a/internal/terminal/shellenv_unix_test.go b/internal/terminal/shellenv_unix_test.go index a9900d05..14a16f11 100644 --- a/internal/terminal/shellenv_unix_test.go +++ b/internal/terminal/shellenv_unix_test.go @@ -18,6 +18,7 @@ import ( // script, a shell dropped into its own prompt) must still be cut off by ctx // rather than hanging ResolveShellEnv past its timeout ceiling. func TestRunShellDumpTimeoutDegrades(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() @@ -36,6 +37,7 @@ func TestRunShellDumpTimeoutDegrades(t *testing.T) { // only the shell process killed on ctx, the read did not return until the // backgrounded sleep itself did, 30s later, ignoring a 200ms ctx entirely. func TestRunShellDumpBackgroundJobDoesNotBlockDeadline(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() @@ -51,6 +53,7 @@ func TestRunShellDumpBackgroundJobDoesNotBlockDeadline(t *testing.T) { // printed before the job is backgrounded, so it, not the far longer ctx, is // what ends the read, and what it returns still carries what the shell said. func TestRunShellDumpBackgroundJobStillYieldsOutput(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -73,6 +76,7 @@ func TestRunShellDumpBackgroundJobStillYieldsOutput(t *testing.T) { // privacy-protected folder) before printing. Measured on a user's Mac before // this test: a 300ms silence window returned the sentinel alone, 20 bytes. func TestRunShellDumpWaitsOutSilenceBeforeEnd(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -87,6 +91,7 @@ func TestRunShellDumpWaitsOutSilenceBeforeEnd(t *testing.T) { } func TestRunShellDumpSurfacesShellFailure(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -144,6 +149,7 @@ func TestReresolveShellEnvSingleFlightWhileReaderParked(t *testing.T) { // the read does. Whatever holds the pty letting go is the only thing that ends // one, and nothing reports it — so the next attempt is where it is noticed. func TestShellDumpParkedClearsWhenCollected(t *testing.T) { + t.Parallel() t.Cleanup(func() { noteParkedReader(nil) }) collected := make(chan struct{}) noteParkedReader(collected) diff --git a/internal/terminal/shellintegration_test.go b/internal/terminal/shellintegration_test.go index 44f9cd14..738b8239 100644 --- a/internal/terminal/shellintegration_test.go +++ b/internal/terminal/shellintegration_test.go @@ -11,6 +11,7 @@ import ( // program that draws its own screen, and its binary is no shell to point at // scripts. func TestShellIntegrationReachesShellSessionsOnly(t *testing.T) { + t.Parallel() spec := ptySpec{bin: "/bin/bash", args: []string{"-l"}, env: []string{"A=1"}} got := withShellIntegration(spec, KindShell, "/cfg/lich/shell-integration") if !slices.Contains(got.args, "--rcfile") { diff --git a/internal/terminal/spawn_test.go b/internal/terminal/spawn_test.go index 985f1404..42889758 100644 --- a/internal/terminal/spawn_test.go +++ b/internal/terminal/spawn_test.go @@ -59,6 +59,7 @@ func TestASetupSpawnWithAScriptWaitsForItsMarker(t *testing.T) { // a marker matches nothing, so the session would stay settingUp for good and // refuse every message it is ever sent. func TestTheSetupMarkerSurvivesASplitRead(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) sess := &session{settingUp: true} svc.mu.Lock() @@ -87,6 +88,7 @@ func TestTheSetupMarkerSurvivesASplitRead(t *testing.T) { // forever: what is kept is a bounded tail, and it never ends the setup on its // own. func TestAPartialMarkerNeverEndsTheSetup(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) sess := &session{settingUp: true} @@ -108,6 +110,7 @@ func TestAPartialMarkerNeverEndsTheSetup(t *testing.T) { // error, because the frontend surfaces it and "no such file" alone names // neither the session nor what lich was trying to run. func TestStartReportsAPtyThatCannotSpawn(t *testing.T) { + t.Parallel() svc := New(stubBins{bin: "/nonexistent/lich-test-provider"}, nil, events.New()) err := svc.Start("s1", "p1", t.TempDir(), "claude", "", "", false, false, 80, 24) @@ -127,6 +130,7 @@ func TestStartReportsAPtyThatCannotSpawn(t *testing.T) { // the effective directory is what the card shows and what the cwd poller // degrades back to. func TestASpawnWithNoDirectoryStartsAtHome(t *testing.T) { + t.Parallel() svc := New(stubBins{bin: stayAliveBin(t)}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) diff --git a/internal/terminal/start_ultracode_test.go b/internal/terminal/start_ultracode_test.go index 93449adc..558263f6 100644 --- a/internal/terminal/start_ultracode_test.go +++ b/internal/terminal/start_ultracode_test.go @@ -15,6 +15,7 @@ import ( // argv ahead of --mcp-config, and that a resume carries it too: Claude Code // forgets ultracode on --resume, unlike the model and effort. func TestStartPassesUltracodeToTheProcess(t *testing.T) { + t.Parallel() tests := []struct { name string store stubBins @@ -52,6 +53,7 @@ func TestStartPassesUltracodeToTheProcess(t *testing.T) { // spawns with it: the fork's row is a fresh insert, and Claude Code drops // ultracode on --resume --fork-session as on a plain resume. func TestStartCarriesUltracodeOntoAFork(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) store := stubBins{ bin: bin, @@ -78,6 +80,7 @@ func TestStartCarriesUltracodeOntoAFork(t *testing.T) { } func TestStartLeavesUltracodeOffByDefault(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) diff --git a/internal/terminal/startgate_test.go b/internal/terminal/startgate_test.go index 72c238c2..75d0260e 100644 --- a/internal/terminal/startgate_test.go +++ b/internal/terminal/startgate_test.go @@ -26,6 +26,7 @@ func quietSession(svc *Service, id string, awaitsStart bool) *session { // its provider sends only from the agent's own prompt, however long the screen // has been still. func TestReadyHoldsASessionUntilItsProviderReportsStart(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) quietSession(svc, "s1", true) @@ -42,6 +43,7 @@ func TestReadyHoldsASessionUntilItsProviderReportsStart(t *testing.T) { // be found quiet before the hold is recorded on it (holdForStart), and that // latch must not let work through. func TestReadyHoldsASessionWhoseQuietWasAlreadyLatched(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) sess := quietSession(svc, "s1", false) if !svc.Ready("s1") { @@ -56,6 +58,7 @@ func TestReadyHoldsASessionWhoseQuietWasAlreadyLatched(t *testing.T) { } func TestReadyDoesNotHoldASessionThatReportsNoStart(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) quietSession(svc, "s1", false) @@ -65,6 +68,7 @@ func TestReadyDoesNotHoldASessionThatReportsNoStart(t *testing.T) { } func TestAwaitsStartOnlyForProvidersThatReportFromTheirPrompt(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if svc.awaitsStart(providers.Claude, "") { t.Error("a session was held with nothing wired to say whether hooks run") @@ -88,6 +92,7 @@ func TestAwaitsStartOnlyForProvidersThatReportFromTheirPrompt(t *testing.T) { // Without lich's hooks in a provider no report ever comes, and waiting for one // would leave its sessions unable to take work at all. func TestAwaitsStartIsOffWhereTheHooksAreNotInstalled(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) svc.SetStartReports(func(provider string) bool { return provider != providers.Kiro }) @@ -102,6 +107,7 @@ func TestAwaitsStartIsOffWhereTheHooksAreNotInstalled(t *testing.T) { // Only a Claude Code profile has a plugin set of its own to ask about; an // oh-my-pi profile links Personal's extension, so Personal's answer is its. func TestAwaitsStartInAProfileAsksTheProvidersOwnPluginSet(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) svc.SetStartReports(func(provider string) bool { return provider == providers.OMP }) @@ -114,6 +120,7 @@ func TestAwaitsStartInAProfileAsksTheProvidersOwnPluginSet(t *testing.T) { } func TestMarkStartedIgnoresASessionThatIsNotRunning(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) svc.markStarted("ghost") if svc.Ready("ghost") { @@ -124,6 +131,7 @@ func TestMarkStartedIgnoresASessionThatIsNotRunning(t *testing.T) { // A respawn is a new session under the same id: the report its predecessor // made does not release it. func TestHoldForStartIgnoresASessionThatWasReplaced(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) old := quietSession(svc, "s1", false) quietSession(svc, "s1", false) @@ -138,6 +146,7 @@ func TestHoldForStartIgnoresASessionThatWasReplaced(t *testing.T) { // Through the real spawn: the session settles, stays held, and is released by // the provider's report coming through the same path the hook's POST takes. func TestStartHoldsAProviderSessionUntilItsReport(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) svc.SetStartReports(func(string) bool { return true }) diff --git a/internal/terminal/subagent_cards_test.go b/internal/terminal/subagent_cards_test.go index 12e0d63a..d1553948 100644 --- a/internal/terminal/subagent_cards_test.go +++ b/internal/terminal/subagent_cards_test.go @@ -34,6 +34,7 @@ func withClaudePlugin(t *testing.T, version string) { // tell a worker, and LICH_SUBAGENT_CARDS=off only where its subagents stay // native, so a session whose subagents become cards carries no off at all. func TestSubagentEnv(t *testing.T) { + t.Parallel() base := []string{"A=1"} cases := []struct { name string @@ -97,6 +98,7 @@ func TestSubagentRouteWithoutTheCardPlugin(t *testing.T) { // A Claude Code spawn whose subagents become lich cards is briefed to fan out // through its own Agent tool; every other spawn keeps the line against it. func TestBriefingFollowsSubagentCards(t *testing.T) { + t.Parallel() cards := providerArgs(providers.Claude, "", "", "", "", "/usr/bin/lich", "", false, false, false, relay.RouteCards, prompt.English) plain := providerArgs(providers.Claude, "", "", "", "", "/usr/bin/lich", "", false, false, false, relay.RouteSessions, prompt.English) briefing := func(args []string) string { diff --git a/internal/terminal/terminal_test.go b/internal/terminal/terminal_test.go index ff307435..ba912df5 100644 --- a/internal/terminal/terminal_test.go +++ b/internal/terminal/terminal_test.go @@ -318,6 +318,7 @@ func (s stubBins) TurnRecord(sessionID string) (store.TurnRecord, bool, error) { // TestChildEnvStripsAppImageVars proves the AppImage runtime variables that break // mise/asdf shims are dropped while the real user environment is passed through. func TestChildEnvStripsAppImageVars(t *testing.T) { + t.Parallel() in := []string{ "PATH=/usr/bin", "ARGV0=lich.AppImage", @@ -343,6 +344,7 @@ func TestChildEnvStripsAppImageVars(t *testing.T) { // APPDIR nothing is dropped or rewritten, even values that look like mount // paths or AppImage-ish keys the user happens to have set. func TestChildEnvOutsideAppImageIsUntouched(t *testing.T) { + t.Parallel() in := []string{ "HOME=/home/user", "LD_LIBRARY_PATH=/opt/lib:/tmp/.mount_other/usr/lib", @@ -358,6 +360,7 @@ func TestChildEnvOutsideAppImageIsUntouched(t *testing.T) { // the AppImage mount: user-set entries survive, values reduced to nothing are // dropped, and unrelated values are never rewritten. func TestChildEnvScrubsMountPaths(t *testing.T) { + t.Parallel() const mount = "/tmp/.mount_lich" in := []string{ "APPDIR=" + mount, @@ -405,6 +408,7 @@ func TestChildEnvScrubsMountPaths(t *testing.T) { // TestChildEnvKeepsUserLibraryPath proves a user's own LD_LIBRARY_PATH suffix // survives the scrub — AppRun prepends the mount to whatever was already set. func TestChildEnvKeepsUserLibraryPath(t *testing.T) { + t.Parallel() in := []string{ "APPDIR=/tmp/.mount_lich", "LD_LIBRARY_PATH=/tmp/.mount_lich/usr/lib:/opt/cuda/lib64", @@ -418,6 +422,7 @@ func TestChildEnvKeepsUserLibraryPath(t *testing.T) { // TestNewSessionEnv proves the service derives its session environment at // construction: cleaned of AppImage leakage and terminated by TERM. func TestNewSessionEnv(t *testing.T) { + t.Parallel() svc := New(stubBins{}, []string{"APPDIR=/tmp/.mount_lich", "ARGV0=lich.AppImage", "HOME=/home/user"}, events.New()) got := strings.Join(svc.env, "\n") if strings.Contains(got, "ARGV0=") || strings.Contains(got, "APPDIR=") { @@ -431,6 +436,7 @@ func TestNewSessionEnv(t *testing.T) { // TestOperationsOnUnknownSessionAreNoops proves Write/Resize/Close on a session // that was never started return nil instead of panicking on a missing PTY. func TestOperationsOnUnknownSessionAreNoops(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if err := svc.Write("ghost", "hi"); err != nil { t.Errorf("Write unknown = %v, want nil", err) @@ -450,6 +456,7 @@ func TestOperationsOnUnknownSessionAreNoops(t *testing.T) { // the session's coalescer: output buffered while hidden is flushed when the // session is made visible. func TestSetVisibleReachesCoalescer(t *testing.T) { + t.Parallel() emit, emits := captureEmit(1) out := newCoalescer(emit, time.Hour, time.Hour) out.SetVisible(false) @@ -488,6 +495,7 @@ func spawnSession(t *testing.T) *session { // TestWriteResizeCloseOnLiveSession drives a real session end to end: input is // written, the window is resized and Close reaps the shell and drops it. func TestWriteResizeCloseOnLiveSession(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) svc.sessions["s1"] = spawnSession(t) @@ -508,6 +516,7 @@ func TestWriteResizeCloseOnLiveSession(t *testing.T) { // TestStartIsNoopWhenAlreadyRunning proves Start returns without spawning a // second shell for a session ID that is already tracked. func TestStartIsNoopWhenAlreadyRunning(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) sess := spawnSession(t) svc.sessions["s1"] = sess @@ -560,6 +569,7 @@ func spawnedArgs(t *testing.T, svc *Service, id string) []string { // TestStartPassesResumeToTheProcess proves the resume id reaches the spawned // binary's argv — the wiring resumeArgs' unit test cannot see. func TestStartPassesResumeToTheProcess(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -583,6 +593,7 @@ func TestStartPassesResumeToTheProcess(t *testing.T) { // parent's id — and that the fork is *named*, which is the one place a spawn // carrying a resume id still gets --name (nameArgs). func TestStartForksIntoItsOwnConversation(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -608,6 +619,7 @@ func TestStartForksIntoItsOwnConversation(t *testing.T) { // otherwise reopen the parent's own conversation under a second card, which // is the one outcome a fork promises against. Nothing is spawned. func TestStartRefusesAForkTheProviderCannotSpell(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -628,6 +640,7 @@ func TestStartRefusesAForkTheProviderCannotSpell(t *testing.T) { // it is not a fork — without one resumeArgs would start fresh and the card // would claim a conversation it never copied. func TestStartRefusesAForkWithNothingToBranch(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -643,6 +656,7 @@ func TestStartRefusesAForkWithNothingToBranch(t *testing.T) { // TestStartWithoutResumeSpawnsBare proves a session with no id to resume spawns // the binary alone, with no dangling flag. func TestStartWithoutResumeSpawnsBare(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -667,6 +681,7 @@ func TestStartWithoutResumeSpawnsBare(t *testing.T) { // goes through spawnPins, which drops the briefing before comparing — so // nothing else here would notice it being lost between the two. func TestStartCarriesTheBriefingIntoTheSpawn(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -692,6 +707,7 @@ func TestStartCarriesTheBriefingIntoTheSpawn(t *testing.T) { // through sh -c with the project's script ahead of the exec'd provider — the // wiring wrapSetup's unit test cannot see. func TestStartWithSetupWrapsTheSpawn(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin, projectPath: projectWithSetup(t, "echo setup-ran")}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -717,6 +733,7 @@ func TestStartWithSetupWrapsTheSpawn(t *testing.T) { // TestStartWithSetupButNoScriptSpawnsBare proves the flag is inert for a // project with no setup script configured — no sh indirection sneaks in. func TestStartWithSetupButNoScriptSpawnsBare(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -739,6 +756,7 @@ func TestStartWithSetupButNoScriptSpawnsBare(t *testing.T) { // default binary (and to defaultBin for an unknown kind), while a configured // path is passed through unchanged. func TestResolveBin(t *testing.T) { + t.Parallel() if got := resolveBin("claude", ""); got != defaultBin { t.Errorf("resolveBin(claude, %q) = %q, want %q", "", got, defaultBin) } @@ -756,6 +774,7 @@ func TestResolveBin(t *testing.T) { // TestResolveCommand proves kind selects between the user's shell and the // Claude Code binary, with fallbacks when either source is empty. func TestResolveCommand(t *testing.T) { + t.Parallel() cases := []struct { name, kind, bin, shell, want string }{ @@ -782,6 +801,7 @@ func TestResolveCommand(t *testing.T) { // `claude` from inside a Cursor session. What lich spawned wins; only a shell, // where lich genuinely does not know what is running inside, wears the report. func TestProviderKindOutranksTheReport(t *testing.T) { + t.Parallel() svc := &Service{} for id, kind := range map[string]string{ "cursor-card": providers.Cursor, @@ -812,6 +832,7 @@ func TestProviderKindOutranksTheReport(t *testing.T) { // session runs would hold the report — and with it the agent's next step — for // however long another session takes to spawn. func TestAReportNeverQueuesBehindASpawn(t *testing.T) { + t.Parallel() svc := &Service{} svc.spawns.Store("s1", spawn{kind: providers.Cursor}) @@ -834,6 +855,7 @@ func TestAReportNeverQueuesBehindASpawn(t *testing.T) { // A kind that outlived its PTY would have providerKind answering for a session // that is gone, which is the one case the report is the better answer. func TestClosingASessionDropsItsKind(t *testing.T) { + t.Parallel() svc := &Service{sessions: map[string]*session{}} svc.spawns.Store("s1", spawn{kind: providers.Cursor}) if err := svc.Close("s1"); err != nil { @@ -849,6 +871,7 @@ func TestClosingASessionDropsItsKind(t *testing.T) { // for Codex, one they happen to share for opencode and Crush — and that a kind // with none wired never grows one: a shell must not be handed a stray id. func TestResumeArgs(t *testing.T) { + t.Parallel() cases := []struct { name, kind, resume string fork bool @@ -909,6 +932,7 @@ func TestResumeArgs(t *testing.T) { // from the transcript, and naming it again would overwrite a /rename the user // typed inside the session. func TestNameArgs(t *testing.T) { + t.Parallel() cases := []struct { name, kind, peer, resume string fork bool @@ -947,6 +971,7 @@ func TestNameArgs(t *testing.T) { // test that follows the map would keep passing while the map handed opencode // Claude Code's flag. A kind with no spelling — a shell — gets none. func TestSkipPermissionArgs(t *testing.T) { + t.Parallel() cases := []struct { name, kind string skip bool @@ -990,6 +1015,7 @@ func TestSkipPermissionArgs(t *testing.T) { // see, and the one that decides whether a user who ticked the box actually gets // an agent that stops asking. func TestStartPassesSkipPermissionsToTheProcess(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin, skipPerms: true}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -1079,6 +1105,7 @@ func spawnName(got []string) string { // name and a provider/model pair all reach the argv untouched. lich knows no // model names, so none of these is a catalogue entry to keep up to date. func TestModelArgs(t *testing.T) { + t.Parallel() cases := []struct { name, kind, model string want []string @@ -1111,6 +1138,7 @@ func TestModelArgs(t *testing.T) { // a model for a provider lich cannot pass one to hears about it instead of // getting a session quietly running the provider's default. func TestSupportsModel(t *testing.T) { + t.Parallel() for _, kind := range []string{ providers.Claude, providers.Codex, providers.Antigravity, providers.OpenCode, providers.OMP, } { @@ -1128,6 +1156,7 @@ func TestSupportsModel(t *testing.T) { // TestEffortArgs pins each provider's own spelling, literally, for TestModelArgs' // reason, and the kinds that must never be handed one. func TestEffortArgs(t *testing.T) { + t.Parallel() cases := []struct { name, kind, effort string want []string @@ -1158,6 +1187,7 @@ func TestEffortArgs(t *testing.T) { // TestSupportsEffort pins what the spawn service rejects on, as TestSupportsModel does. func TestSupportsEffort(t *testing.T) { + t.Parallel() for _, kind := range []string{ providers.Claude, providers.Codex, providers.Antigravity, providers.OMP, providers.Kiro, } { @@ -1175,6 +1205,7 @@ func TestSupportsEffort(t *testing.T) { // TestStartPassesTheStoredEffortToTheProcess proves the effort on the row reaches // argv, read from the store like the model so a respawn keeps it. func TestStartPassesTheStoredEffortToTheProcess(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin, model: "opus", effort: "high"}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -1195,6 +1226,7 @@ func TestStartPassesTheStoredEffortToTheProcess(t *testing.T) { // passed to Start precisely so it survives a respawn, and this is the wiring // that carries it there. func TestStartPassesTheStoredModelToTheProcess(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin, model: "opus"}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -1217,6 +1249,7 @@ func TestStartPassesTheStoredModelToTheProcess(t *testing.T) { // under a PTY and its output is readable. If this platform's startPTY breaks, // this fails. func TestPTYEcho(t *testing.T) { + t.Parallel() const marker = "lich-pty-test" p, err := startPTY(ptySpec{ @@ -1250,6 +1283,7 @@ func TestPTYEcho(t *testing.T) { // a port keeps it untouched — probing would read its own running dev server as // "taken" and move the checkout off the port it is serving on. func TestReserveWorktreePortKeepsReservation(t *testing.T) { + t.Parallel() store := stubBins{ports: map[string]int{"/src/repo": 24007}} s := &Service{store: store} if got := s.reserveWorktreePort("/src/repo/"); got != 24007 { @@ -1264,6 +1298,7 @@ func TestReserveWorktreePortKeepsReservation(t *testing.T) { // written into the table under its cleaned path — without that write the next // checkout to collide with it would never learn the port is spoken for. func TestReserveWorktreePortRecordsAllocation(t *testing.T) { + t.Parallel() store := stubBins{ports: map[string]int{}} s := &Service{store: store} got := s.reserveWorktreePort("/src/repo/") @@ -1279,6 +1314,7 @@ func TestReserveWorktreePortRecordsAllocation(t *testing.T) { // point of the reservation: two checkouts that hash to one port do not both // leave with it. func TestReserveWorktreePortSeparatesCollidingCheckouts(t *testing.T) { + t.Parallel() first := "/src/repo/feature-a" // A path is not free to choose — it has to hash onto first's port — so the // collision is manufactured by seeding the table with first's number under @@ -1296,6 +1332,7 @@ func TestReserveWorktreePortSeparatesCollidingCheckouts(t *testing.T) { // the one the checkout holds in the reservation table, not a number recomputed // here. func TestSessionEnvInjectsCoordinates(t *testing.T) { + t.Parallel() store := stubBins{projectPath: "/src/repo", ports: map[string]int{"/src/repo/wt": 24007}} s := &Service{env: []string{"A=1"}, store: store, ws: &transport{port: 4321, token: "tok"}} env := s.sessionEnv("sess", "p1", "/src/repo/wt") @@ -1348,6 +1385,7 @@ func spawnPins(t *testing.T, got []string, want ...string) { // TestProviderArgsRegistersTheMCPServer proves each provider that can be handed // an MCP server at spawn is handed lich's, spelled its own way. func TestProviderArgsRegistersTheMCPServer(t *testing.T) { + t.Parallel() const bin = "/usr/bin/lich" claude := providerArgs(providers.Claude, "", "", "", "", bin, "", false, false, false, relay.RouteSessions, prompt.English) @@ -1390,6 +1428,7 @@ func TestProviderArgsRegistersTheMCPServer(t *testing.T) { // follows it, and Codex reads resume as a subcommand that every global option // must precede. func TestProviderArgsOrdersEachProvidersConstraint(t *testing.T) { + t.Parallel() claude := providerArgs(providers.Claude, "lich-4f2a", "conv-1", "", "", "/usr/bin/lich", "", false, true, false, relay.RouteSessions, prompt.English) if claude[len(claude)-2] != "--mcp-config" { t.Errorf("--mcp-config is not last, so it eats what follows: %v", claude) @@ -1429,6 +1468,7 @@ func TestProviderArgsOrdersEachProvidersConstraint(t *testing.T) { // model and effort are the user's, and a `/model` or `/effort` typed inside it // must survive the next resume (the contract nameArgs already keeps for names). func TestModelAndEffortAreBirthValues(t *testing.T) { + t.Parallel() cases := []struct { kind, model, effort string born []string @@ -1467,6 +1507,7 @@ func TestModelAndEffortAreBirthValues(t *testing.T) { // hold for the two that take a briefing without taking an MCP server — for // those, what is proven is that no registration rode along with it. func TestProviderArgsWithoutARegistration(t *testing.T) { + t.Parallel() tests := []struct { name string kind string @@ -1500,6 +1541,7 @@ func TestProviderArgsWithoutARegistration(t *testing.T) { // line, because naming a tool it does not have is the one thing that leaves it // with no route at all. func TestTheBriefingGoesToTheProvidersThatTakeOne(t *testing.T) { + t.Parallel() briefed := map[string]string{ providers.Claude: "tools in your list", providers.OMP: "lich open --worktree", @@ -1532,6 +1574,7 @@ func TestTheBriefingGoesToTheProvidersThatTakeOne(t *testing.T) { // (internal/cli), and on a machine running an installed lich beside a dev build // the bare name would resolve to the wrong one. func TestSessionEnvExportsTheBinary(t *testing.T) { + t.Parallel() exe, err := os.Executable() if err != nil { t.Skipf("no executable path on this platform: %v", err) @@ -1554,6 +1597,7 @@ func TestSessionEnvExportsTheBinary(t *testing.T) { // the project directory and the checkout's dev-server port, which owe the // transport nothing, still land. func TestSessionEnvNoTransport(t *testing.T) { + t.Parallel() store := stubBins{projectPath: "/src/repo", ports: map[string]int{"/src/repo/wt": 24007}} s := &Service{env: []string{"A=1"}, store: store} env := s.sessionEnv("sess", "p1", "/src/repo/wt") @@ -1572,6 +1616,7 @@ func TestSessionEnvNoTransport(t *testing.T) { // TestSessionEnvWithoutCwd proves a session with no start directory names no // checkout, so no port is invented for it. func TestSessionEnvWithoutCwd(t *testing.T) { + t.Parallel() s := &Service{env: []string{"A=1"}, store: stubBins{}} env := s.sessionEnv("sess", "p1", "") if len(env) != 1 || env[0] != "A=1" { @@ -1583,6 +1628,7 @@ func TestSessionEnvWithoutCwd(t *testing.T) { // resolve is left without LICH_PROJECT_DIR rather than handed an empty one: a // setup script reading it would `cd` to the filesystem root. func TestSessionEnvWithoutProject(t *testing.T) { + t.Parallel() s := &Service{env: []string{"A=1"}, store: stubBins{}} for _, e := range s.sessionEnv("sess", "gone", "") { if strings.HasPrefix(e, "LICH_PROJECT_DIR=") { @@ -1597,6 +1643,7 @@ func TestSessionEnvWithoutProject(t *testing.T) { // look exactly like a feature that simply never fires — which is what an // unanswered request looked like on the first real run. func TestSessionStateReachesItsWatcher(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if svc.ws == nil { t.Skip("no transport on this machine") @@ -1630,6 +1677,7 @@ func TestSessionStateReachesItsWatcher(t *testing.T) { // TestSessionStateWithoutAWatcherIsHarmless proves the default: nothing is wired // until main wires it, and a report arriving first must not take the hook down. func TestSessionStateWithoutAWatcherIsHarmless(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if svc.ws == nil { t.Skip("no transport on this machine") @@ -1652,6 +1700,7 @@ func TestSessionStateWithoutAWatcherIsHarmless(t *testing.T) { // is gone from the screen (see sizeFor). func TestSizeForKeepsAMeasuredSize(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) svc.mu.Lock() @@ -1664,6 +1713,7 @@ func TestSizeForKeepsAMeasuredSize(t *testing.T) { } func TestSizeForFallsBackWhenNothingHasBeenMeasured(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) svc.mu.Lock() @@ -1678,6 +1728,7 @@ func TestSizeForFallsBackWhenNothingHasBeenMeasured(t *testing.T) { } func TestSizeForCopiesTheWindowsLastSize(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) svc.mu.Lock() @@ -1694,6 +1745,7 @@ func TestSizeForCopiesTheWindowsLastSize(t *testing.T) { // lich has — including for the session an agent opens next, whose own PTY // nobody is looking at. func TestResizeRecordsTheWindowsSize(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if err := svc.Resize("ghost", 174, 51); err != nil { @@ -1711,6 +1763,7 @@ func TestResizeRecordsTheWindowsSize(t *testing.T) { // TestStartWithoutASizeCopiesTheWindow drives the whole path: a card the window // measured, then a session opened with no terminal of its own. func TestStartWithoutASizeCopiesTheWindow(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1"); _ = svc.Close("s2") }) @@ -1734,6 +1787,7 @@ func TestStartWithoutASizeCopiesTheWindow(t *testing.T) { // the project's setup script is live but has nothing on the other end that can // read a message, until the wrapper's marker comes through its own output. func TestReadyWaitsForTheSetupScript(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin, projectPath: projectWithSetup(t, "echo installing")}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -1769,6 +1823,7 @@ func TestReadyWaitsForTheSetupScript(t *testing.T) { // script at all: the provider still takes the terminal over, and a message // written before it does is lost the same way. func TestReadyWaitsForTheProviderToStopDrawing(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) sess := &session{} @@ -1790,6 +1845,7 @@ func TestReadyWaitsForTheProviderToStopDrawing(t *testing.T) { // continuously, and a target mid-turn has always been written to — its provider // queues the input and answers a turn later. func TestReadyStaysTrueThroughABusyTurn(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) sess := &session{lastOut: time.Now().Add(-time.Second)} svc.mu.Lock() @@ -1812,6 +1868,7 @@ func TestReadyStaysTrueThroughABusyTurn(t *testing.T) { // splash would clear the settle, and the message the relay then types lands in a // TUI that has not taken the terminal yet. func TestTheWaitForTheProviderToStartIsNotItsQuiet(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) sess := &session{settingUp: true} svc.mu.Lock() @@ -1839,6 +1896,7 @@ func TestTheWaitForTheProviderToStartIsNotItsQuiet(t *testing.T) { // like a provider that never took the terminal, and the message it was sent is // refused instead of queued. func TestReadyRemembersAQuietThatAlreadyPassed(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) sess := &session{} svc.mu.Lock() @@ -1858,6 +1916,7 @@ func TestReadyRemembersAQuietThatAlreadyPassed(t *testing.T) { } func TestReadyWaitsOnlyTheSettleWithoutASetupScript(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1") }) @@ -1874,6 +1933,7 @@ func TestReadyWaitsOnlyTheSettleWithoutASetupScript(t *testing.T) { } func TestReadyIsFalseForASessionThatIsNotRunning(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if svc.Ready("ghost") { @@ -1885,6 +1945,7 @@ func TestReadyIsFalseForASessionThatIsNotRunning(t *testing.T) { // gh for a token there would spend a subprocess to hand over something the // session never lacked. func TestSandboxCredentialsAreOnlyForConfinedSessions(t *testing.T) { + t.Parallel() svc := New(stubBins{sshAgent: true, ghToken: true, ghAccount: "github.com/someone"}, nil, events.New()) if got := svc.sandboxCredentials("p1", "/repo", false); got != (sandboxCreds{}) { t.Errorf("an unconfined session was handed %+v, want nothing", got) @@ -1898,6 +1959,7 @@ func TestSandboxCredentialsAreOnlyForConfinedSessions(t *testing.T) { // session opened after the re-check is spawned with the resolved environment, // not the one lich booted with (providers.Service.RefreshPath). func TestSetEnvMovesTheSpawnBase(t *testing.T) { + t.Parallel() s := &Service{env: sessionBaseEnv([]string{"PATH=/orig"}), store: stubBins{}} s.SetEnv([]string{"PATH=/late/install:/orig"}) @@ -1917,6 +1979,7 @@ func TestSetEnvMovesTheSpawnBase(t *testing.T) { // TestLiveCountCountsTheSessionsWithAProcess is what the tray reads: a session // counts while its process runs and stops counting once it is closed. func TestLiveCountCountsTheSessionsWithAProcess(t *testing.T) { + t.Parallel() bin := stayAliveBin(t) svc := New(stubBins{bin: bin}, nil, events.New()) t.Cleanup(func() { _ = svc.Close("s1"); _ = svc.Close("s2") }) diff --git a/internal/terminal/title_kiro_test.go b/internal/terminal/title_kiro_test.go index b73a8cb5..e3a25d8c 100644 --- a/internal/terminal/title_kiro_test.go +++ b/internal/terminal/title_kiro_test.go @@ -11,6 +11,7 @@ import ( // absent reads as silence (a card keeping the name lich gave it) rather than as // a title of "". func TestKiroMetadataTitle(t *testing.T) { + t.Parallel() tests := []struct { name string body string @@ -48,6 +49,7 @@ func TestKiroMetadataTitle(t *testing.T) { // A conversation whose metadata file is not there yet, or was pruned, is the // common absence, and it must not reach the card as an error. func TestKiroMetadataTitleAbsentFile(t *testing.T) { + t.Parallel() if got, ok := kiroMetadataTitle(filepath.Join(t.TempDir(), "absent.json")); ok { t.Errorf("kiroMetadataTitle(absent) = %q, true, want a miss", got) } diff --git a/internal/terminal/transport_auth_test.go b/internal/terminal/transport_auth_test.go index 96fc6bc7..639af763 100644 --- a/internal/terminal/transport_auth_test.go +++ b/internal/terminal/transport_auth_test.go @@ -43,6 +43,7 @@ func status(t *testing.T, tr *transport, path, token string) int { } func TestMountRequiresTheToken(t *testing.T) { + t.Parallel() tr := mountedTransport(t) if code := status(t, tr, "/gated", ""); code != http.StatusForbidden { t.Errorf("no token: want 403, got %d", code) @@ -56,6 +57,7 @@ func TestMountRequiresTheToken(t *testing.T) { } func TestMountPublicServesWithoutTheToken(t *testing.T) { + t.Parallel() tr := mountedTransport(t) if code := status(t, tr, "/open", ""); code != http.StatusOK { t.Errorf("public without token: want 200, got %d", code) diff --git a/internal/terminal/transport_replace_test.go b/internal/terminal/transport_replace_test.go index 1f16c152..88f3e4d8 100644 --- a/internal/terminal/transport_replace_test.go +++ b/internal/terminal/transport_replace_test.go @@ -15,6 +15,7 @@ import ( // transport that forgets it has a client sends every session's output down the // /events fallback instead, for a socket that is still open. func TestReplacedClientDoesNotSilenceTransport(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err) @@ -87,6 +88,7 @@ const stallCeiling = 2 * time.Second // the read loop starts — so a replaced client that is alive but not reading // would hold the new client's input hostage for the length of that handshake. func TestReplacingAClientDoesNotStallTheNewOne(t *testing.T) { + t.Parallel() arrived := make(chan struct{}, 1) tr, err := newTransport(func(string, []byte) { arrived <- struct{}{} }, nil, nil, nil, nil) if err != nil { diff --git a/internal/terminal/transport_restart_test.go b/internal/terminal/transport_restart_test.go index fb5b3f23..48c1346d 100644 --- a/internal/terminal/transport_restart_test.go +++ b/internal/terminal/transport_restart_test.go @@ -20,6 +20,7 @@ func newRestartTransport(t *testing.T) *transport { } func TestRestartTriggersCallback(t *testing.T) { + t.Parallel() tr := newRestartTransport(t) fired := make(chan struct{}, 1) tr.setRestart(func() error { fired <- struct{}{}; return nil }) @@ -41,6 +42,7 @@ func TestRestartTriggersCallback(t *testing.T) { } func TestRestartRejectsBadToken(t *testing.T) { + t.Parallel() tr := newRestartTransport(t) fired := make(chan struct{}, 1) tr.setRestart(func() error { fired <- struct{}{}; return nil }) @@ -62,6 +64,7 @@ func TestRestartRejectsBadToken(t *testing.T) { } func TestRestartRejectsGet(t *testing.T) { + t.Parallel() tr := newRestartTransport(t) tr.setRestart(func() error { return nil }) @@ -77,6 +80,7 @@ func TestRestartRejectsGet(t *testing.T) { } func TestRestartUnavailableWhenUnset(t *testing.T) { + t.Parallel() tr := newRestartTransport(t) // restart left nil url := fmt.Sprintf("http://127.0.0.1:%d/restart?token=%s", tr.port, tr.token) @@ -91,6 +95,7 @@ func TestRestartUnavailableWhenUnset(t *testing.T) { } func TestListenGivesUpAfterBindTimeoutWithoutWaitMarker(t *testing.T) { + t.Parallel() busy, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("occupy port: %v", err) @@ -111,6 +116,7 @@ func TestListenGivesUpAfterBindTimeoutWithoutWaitMarker(t *testing.T) { } func TestListenBailsOutFastOnUnretryableError(t *testing.T) { + t.Parallel() // An out-of-range port can never free itself; retrying it only delays the // real error (seen in the field as LICH_LISTEN_PORT=321654 burning the // whole bindTimeout before reporting "invalid port"). @@ -126,6 +132,7 @@ func TestListenBailsOutFastOnUnretryableError(t *testing.T) { } func TestListenRetriesUntilPortFreesWithoutWaitMarker(t *testing.T) { + t.Parallel() busy, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("occupy port: %v", err) diff --git a/internal/terminal/transport_stall_test.go b/internal/terminal/transport_stall_test.go index 3acac17f..17128fe7 100644 --- a/internal/terminal/transport_stall_test.go +++ b/internal/terminal/transport_stall_test.go @@ -35,6 +35,7 @@ const ( // the socket and stay there. A quiet session's send must still return promptly // — it is handing a frame to the connection's writer, not waiting on the wire. func TestStalledSessionDoesNotBlockAnother(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err) diff --git a/internal/terminal/transport_test.go b/internal/terminal/transport_test.go index 21ff1b36..0321f488 100644 --- a/internal/terminal/transport_test.go +++ b/internal/terminal/transport_test.go @@ -42,6 +42,7 @@ func TestMain(m *testing.M) { } func TestFrameRoundTrip(t *testing.T) { + t.Parallel() frame, err := encodeFrame("sess-1", []byte("hello")) if err != nil { t.Fatalf("encodeFrame: %v", err) @@ -56,6 +57,7 @@ func TestFrameRoundTrip(t *testing.T) { } func TestFrameEmptyPayload(t *testing.T) { + t.Parallel() frame, err := encodeFrame("s", nil) if err != nil { t.Fatalf("encodeFrame: %v", err) @@ -70,6 +72,7 @@ func TestFrameEmptyPayload(t *testing.T) { } func TestFrameErrors(t *testing.T) { + t.Parallel() if _, err := encodeFrame("", []byte("x")); err == nil { t.Fatal("expected error for empty id") } @@ -101,6 +104,7 @@ func dial(t *testing.T, tr *transport, token string) (*websocket.Conn, error) { } func TestTransportRejectsBadToken(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err) @@ -111,6 +115,7 @@ func TestTransportRejectsBadToken(t *testing.T) { } func TestTransportInputReachesService(t *testing.T) { + t.Parallel() got := make(chan string, 1) tr, err := newTransport(func(id string, data []byte) { got <- id + ":" + string(data) @@ -167,6 +172,7 @@ func sendWhenRegistered(t *testing.T, tr *transport, id string, data []byte) boo } func TestTransportSendAndFallback(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err) @@ -203,6 +209,7 @@ func TestTransportSendAndFallback(t *testing.T) { } func TestParseHookRequest(t *testing.T) { + t.Parallel() tests := []struct { name string body string @@ -242,6 +249,7 @@ func TestParseHookRequest(t *testing.T) { // that derives its input from the constant passes whatever the constant becomes, // which is exactly the mutation it exists to catch. func TestParseHookRequestCapsToolText(t *testing.T) { + t.Parallel() tests := []struct { name string tool string @@ -280,6 +288,7 @@ func TestParseHookRequestCapsToolText(t *testing.T) { } func TestPingAnswersWithToken(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err) @@ -295,6 +304,7 @@ func TestPingAnswersWithToken(t *testing.T) { } func TestPingRejectsBadToken(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err) @@ -310,6 +320,7 @@ func TestPingRejectsBadToken(t *testing.T) { } func TestGoroutineDumpAnswersWithTheStacks(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err) @@ -345,6 +356,7 @@ func TestGoroutineDumpAnswersWithTheStacks(t *testing.T) { } func TestGoroutineDumpRejectsBadToken(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err) @@ -360,6 +372,7 @@ func TestGoroutineDumpRejectsBadToken(t *testing.T) { } func TestHookForwardsStatus(t *testing.T) { + t.Parallel() got := make(chan hookRequest, 1) tr, err := newTransport(func(string, []byte) {}, func(req hookRequest) { got <- req @@ -389,6 +402,7 @@ func TestHookForwardsStatus(t *testing.T) { } func TestHookRejectsBadToken(t *testing.T) { + t.Parallel() fired := make(chan struct{}, 1) tr, err := newTransport(func(string, []byte) {}, func(hookRequest) { fired <- struct{}{} }, nil, nil, nil) if err != nil { @@ -411,6 +425,7 @@ func TestHookRejectsBadToken(t *testing.T) { } func TestParseSessionStart(t *testing.T) { + t.Parallel() tests := []struct { name string body string @@ -477,6 +492,7 @@ func TestParseSessionStart(t *testing.T) { } func TestSessionStartLinksSession(t *testing.T) { + t.Parallel() got := make(chan string, 1) tr, err := newTransport(func(string, []byte) {}, nil, func(id, providerID, provider string) error { got <- id + ":" + providerID + ":" + provider @@ -506,6 +522,7 @@ func TestSessionStartLinksSession(t *testing.T) { } func TestSessionStartRejectsBadToken(t *testing.T) { + t.Parallel() fired := make(chan struct{}, 1) tr, err := newTransport(func(string, []byte) {}, nil, func(string, string, string) error { fired <- struct{}{} @@ -532,6 +549,7 @@ func TestSessionStartRejectsBadToken(t *testing.T) { } func TestParseSessionTitle(t *testing.T) { + t.Parallel() tests := []struct { name string body string @@ -566,6 +584,7 @@ func TestParseSessionTitle(t *testing.T) { } func TestSessionTitleApplies(t *testing.T) { + t.Parallel() got := make(chan string, 1) tr, err := newTransport(func(string, []byte) {}, nil, nil, func(id, title string) error { got <- id + ":" + title @@ -595,6 +614,7 @@ func TestSessionTitleApplies(t *testing.T) { } func TestSessionTitleRejectsBadToken(t *testing.T) { + t.Parallel() fired := make(chan struct{}, 1) tr, err := newTransport(func(string, []byte) {}, nil, nil, func(string, string) error { fired <- struct{}{} @@ -621,6 +641,7 @@ func TestSessionTitleRejectsBadToken(t *testing.T) { } func TestParseSessionTouched(t *testing.T) { + t.Parallel() if req, err := parseSessionTouched([]byte(`{"session_id":"s1"}`)); err != nil || req.SessionID != "s1" { t.Fatalf("got (%q,%v), want (s1,nil)", req.SessionID, err) } @@ -633,6 +654,7 @@ func TestParseSessionTouched(t *testing.T) { } func TestSessionTouchedForwards(t *testing.T) { + t.Parallel() got := make(chan string, 1) tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, func(id string) { got <- id @@ -660,6 +682,7 @@ func TestSessionTouchedForwards(t *testing.T) { } func TestSessionTouchedRejectsBadToken(t *testing.T) { + t.Parallel() fired := make(chan struct{}, 1) tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, func(string) { fired <- struct{}{} @@ -709,6 +732,7 @@ func newNilTransport(t *testing.T) *transport { } func TestHookEndpointsRejectNonPost(t *testing.T) { + t.Parallel() tr := newNilTransport(t) for _, e := range hookEndpoints { t.Run(e.path, func(t *testing.T) { @@ -725,6 +749,7 @@ func TestHookEndpointsRejectNonPost(t *testing.T) { } func TestHookEndpointsRejectInvalidJSON(t *testing.T) { + t.Parallel() tr := newNilTransport(t) for _, e := range hookEndpoints { t.Run(e.path, func(t *testing.T) { @@ -745,6 +770,7 @@ func TestHookEndpointsRejectInvalidJSON(t *testing.T) { // nothing is wired to receive it — the hook must never see an error for a // report lich simply has no use for. func TestHookEndpointsWithNilCallbacks(t *testing.T) { + t.Parallel() tr := newNilTransport(t) for _, e := range hookEndpoints { t.Run(e.path, func(t *testing.T) { @@ -765,6 +791,7 @@ func TestHookEndpointsWithNilCallbacks(t *testing.T) { // rather than being swallowed. These are the only two hooks that write, and the // 500 is the sole signal that the write was lost. func TestStoreFailuresReport500(t *testing.T) { + t.Parallel() boom := errors.New("store is down") tests := []struct { name string @@ -814,6 +841,7 @@ func TestStoreFailuresReport500(t *testing.T) { // memory: the JSON is valid but padded past hookBodyLimit, so the cut lands // mid-document and the parse fails. Without the limit this would be a 204. func TestHookBodyLimit(t *testing.T) { + t.Parallel() fired := make(chan struct{}, 1) tr, err := newTransport(func(string, []byte) {}, func(hookRequest) { fired <- struct{}{} }, nil, nil, nil) if err != nil { @@ -837,6 +865,7 @@ func TestHookBodyLimit(t *testing.T) { } func TestTransportInfoZeroWithoutTransport(t *testing.T) { + t.Parallel() s := &Service{} if info := s.Transport(); info.Port != 0 || info.Token != "" { t.Fatalf("expected zero TransportInfo, got %+v", info) diff --git a/internal/terminal/turnsnap_test.go b/internal/terminal/turnsnap_test.go index 6cf2b61e..3006db1b 100644 --- a/internal/terminal/turnsnap_test.go +++ b/internal/terminal/turnsnap_test.go @@ -109,6 +109,7 @@ func closeAndWait(t *testing.T, svc *Service, id, want string) LastTurn { // The feature itself: what the checkout gained between a turn starting and // ending, rendered as the unified diff the panel already knows how to draw. func TestLastTurnDiffRendersTheWindow(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -138,6 +139,7 @@ func TestLastTurnDiffRendersTheWindow(t *testing.T) { // The distinction the whole contract exists for: a turn that ran and touched // nothing is an answer, and it must not wear the words for having no answer. func TestATurnThatChangedNothingIsEmpty(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -157,6 +159,7 @@ func TestATurnThatChangedNothingIsEmpty(t *testing.T) { // opening snapshot forward through the turn it is supposed to precede, so the // panel would show only whatever happened after the last tool. func TestARepeatBusyDoesNotMoveTheWindow(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -178,6 +181,7 @@ func TestARepeatBusyDoesNotMoveTheWindow(t *testing.T) { // report itself as quiet — offering the earlier turn's diff instead would // answer a question nobody asked, and would do it convincingly. func TestAQuietTurnDoesNotFallBackToTheOneBefore(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -196,6 +200,7 @@ func TestAQuietTurnDoesNotFallBackToTheOneBefore(t *testing.T) { // record instead of leaving the previous turn standing as if it were this one, // and says a turn ran here rather than that none did. func TestATurnWithNoOpeningSnapshotReadsAsLost(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -219,6 +224,7 @@ func TestATurnWithNoOpeningSnapshotReadsAsLost(t *testing.T) { // for a turn that ran and changed files. It has to read as a turn whose record // was lost — never as a session that has not had one. func TestASnapshotTheQueueDroppedReadsAsLost(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -259,6 +265,7 @@ func TestASnapshotTheQueueDroppedReadsAsLost(t *testing.T) { // session lich never tracked has nothing to show either. Both are the absence // of an answer, never a quiet turn. func TestNoClosedTurnIsUnavailable(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) turn, err := svc.LastTurnDiff("never-tracked") @@ -287,6 +294,7 @@ func TestNoClosedTurnIsUnavailable(t *testing.T) { // stopped turn still changed files. Without this the panel would hold the turn // before it open until some later one finished. func TestAnInterruptClosesTheWindow(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -304,6 +312,7 @@ func TestAnInterruptClosesTheWindow(t *testing.T) { // so that turn is abandoned — but the last one that did close is still the last // one that closed, and the session ending does not un-happen it. func TestSessionEndAbandonsTheOpenTurnAndKeepsTheClosedOne(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -338,6 +347,7 @@ func TestSessionEndAbandonsTheOpenTurnAndKeepsTheClosedOne(t *testing.T) { // A closed card's row is deleted, so its accounting is dead weight — and its // index file would outlive it under lich's config directory. func TestForgetDropsTheRecordAndTheIndex(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -366,6 +376,7 @@ func TestForgetDropsTheRecordAndTheIndex(t *testing.T) { // A respawn under the same id is a new session's turns. Whatever the provider // that left had recorded belongs to it, not to the one now typing. func TestRespawnDropsThePreviousProvidersTurn(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -387,6 +398,7 @@ func TestRespawnDropsThePreviousProvidersTurn(t *testing.T) { // A `done` for a session with no turn open is what every idle prompt produces, // and it must not invent a window out of two unrelated snapshots. func TestADoneWithNoTurnOpenRecordsNothing(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -404,6 +416,7 @@ func TestADoneWithNoTurnOpenRecordsNothing(t *testing.T) { // terminal says so, and the user closes the dead card some time later. The // accounting and the index file have to go with it either way. func TestCloseForgetsASessionWhosePTYAlreadyExited(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.sessions = map[string]*session{} svc.snaps.track("s1", repo) @@ -436,6 +449,7 @@ func TestCloseForgetsASessionWhosePTYAlreadyExited(t *testing.T) { // The panel is told when the record lands, not when the turn ends: the two are // different moments, and only the first one has an answer to give. func TestFilingATurnNotifiesOnce(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) filed := make(chan string, 4) svc.snaps.filed = func(id string) { filed <- id } @@ -463,6 +477,7 @@ func TestFilingATurnNotifiesOnce(t *testing.T) { // A close still on the worker must not leave the turn before it on screen // wearing this turn's name: until the snapshot lands there is no last turn. func TestACloseInFlightNeverShowsTheTurnBefore(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) svc.snaps.track("s1", repo) @@ -497,6 +512,7 @@ func TestACloseInFlightNeverShowsTheTurnBefore(t *testing.T) { // A session opened outside a repository has nothing to snapshot. It is dropped // on the warm-up rather than warned about twice a turn for the rest of its life. func TestACheckoutGitCannotSnapshotIsDropped(t *testing.T) { + t.Parallel() svc, _ := snapRepo(t) plain := t.TempDir() svc.snaps.track("s1", plain) @@ -562,6 +578,7 @@ func relaunch(t *testing.T, db *memTurns) *Service { // session read as unrecorded until its next turn ended — which, for a session // nobody is typing at, is never. func TestARestoredSessionAnswersItsLastTurn(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) db := &memTurns{records: map[string]store.TurnRecord{}} svc.snaps.store = db @@ -600,6 +617,7 @@ func TestARestoredSessionAnswersItsLastTurn(t *testing.T) { // has to reach the database too — otherwise the next launch would answer with a // turn this lich had already taken back. func TestARetractedTurnIsNotRestored(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) db := &memTurns{records: map[string]store.TurnRecord{}} svc.snaps.store = db @@ -635,6 +653,7 @@ func TestARetractedTurnIsNotRestored(t *testing.T) { // under an id this run is already tracking is a new session's turns, so the one // the provider that left recorded stays where it is: on disk, not on screen. func TestARespawnDoesNotReadTheStoredTurnBack(t *testing.T) { + t.Parallel() svc, repo := snapRepo(t) db := &memTurns{records: map[string]store.TurnRecord{}} svc.snaps.store = db diff --git a/internal/terminal/turnstates_test.go b/internal/terminal/turnstates_test.go index 0595dfcc..0f2b3c90 100644 --- a/internal/terminal/turnstates_test.go +++ b/internal/terminal/turnstates_test.go @@ -36,6 +36,7 @@ func turnStateOf(t *testing.T, svc *Service, id string) TurnState { // A window that was away reads its cards from this: the last report of each // live session, with the tool a pre-tool report named. func TestTurnStatesCarryTheLastReportOfEachLiveSession(t *testing.T) { + t.Parallel() svc := serviceWithSessions(t, hookStore{}, "s1", "s2") svc.onHookState(hookRequest{SessionID: "s1", State: statusBusy}) svc.onHookState(hookRequest{SessionID: "s1", State: statusBusy, Tool: "Bash", Detail: "go test ./..."}) @@ -55,6 +56,7 @@ func TestTurnStatesCarryTheLastReportOfEachLiveSession(t *testing.T) { // A report the window was never told (a `waiting` outside a turn) is not the // session's state either. func TestTurnStatesHoldBackWhatTheWindowWasNotTold(t *testing.T) { + t.Parallel() svc := serviceWithSessions(t, hookStore{}, "s1") svc.onHookState(hookRequest{SessionID: "s1", State: statusBusy}) svc.onHookState(hookRequest{SessionID: "s1", State: statusDone}) @@ -66,6 +68,7 @@ func TestTurnStatesHoldBackWhatTheWindowWasNotTold(t *testing.T) { } func TestTurnStatesRecordAnInterruptedTurn(t *testing.T) { + t.Parallel() svc := serviceWithSessions(t, hookStore{}, "s1") svc.onHookState(hookRequest{SessionID: "s1", State: statusBusy}) svc.noteInterrupt("s1") @@ -78,6 +81,7 @@ func TestTurnStatesRecordAnInterruptedTurn(t *testing.T) { // Since moves on a transition only: the hook repeats a state through a turn and // a session has been busy since it started. func TestTurnStatesStampTheTransitionNotTheRepeat(t *testing.T) { + t.Parallel() svc := serviceWithSessions(t, hookStore{}, "s1") clock := time.UnixMilli(1_000) svc.statuses.now = func() time.Time { return clock } @@ -96,6 +100,7 @@ func TestTurnStatesStampTheTransitionNotTheRepeat(t *testing.T) { // A dead session answers for nothing: a closed card, and a PTY that exited, // are not in the snapshot, and a respawn under the same id starts blank. func TestTurnStatesForgetASessionThatLeft(t *testing.T) { + t.Parallel() svc := serviceWithSessions(t, hookStore{}, "closed", "respawned") svc.onHookState(hookRequest{SessionID: "closed", State: statusBusy}) svc.onHookState(hookRequest{SessionID: "respawned", State: statusBusy}) @@ -129,6 +134,7 @@ func TestTurnStatesCarryTheUsageReadout(t *testing.T) { // With no transcript behind it a session has no usage to report, and reports // its state alone. func TestTurnStatesLeaveOutUsageThereIsNoneOf(t *testing.T) { + t.Parallel() svc := serviceWithSessions(t, hookStore{}, "s1") svc.onHookState(hookRequest{SessionID: "s1", State: statusBusy}) @@ -140,6 +146,7 @@ func TestTurnStatesLeaveOutUsageThereIsNoneOf(t *testing.T) { // A window that attached after a cd reads where the session went, and the host // it went into, rather than its checkout. func TestTurnStatesCarryWhereTheSessionLastWas(t *testing.T) { + t.Parallel() svc := serviceWithSessions(t, hookStore{}, "moved", "hosted") svc.publishCwd(cwdEvent{ID: "moved", Cwd: "/repo"}) svc.publishCwd(cwdEvent{ID: "moved", Cwd: "/elsewhere"}) diff --git a/internal/terminal/turntodo_test.go b/internal/terminal/turntodo_test.go index 03c77b2f..65c07824 100644 --- a/internal/terminal/turntodo_test.go +++ b/internal/terminal/turntodo_test.go @@ -26,6 +26,7 @@ func todoItemJSON(status string) string { // the array the tool's schema describes, and the string Claude Code actually // writes. A version that stops double-encoding must not stop the count. func TestClaudeTodoReadsBothEncodings(t *testing.T) { + t.Parallel() tests := []struct { name string line string @@ -98,6 +99,7 @@ func TestClaudeTodoReadsBothEncodings(t *testing.T) { // task list lich can read, and every other provider answers with nothing rather // than with a reader that finds nothing. func TestTodoReaderForClaudeOnly(t *testing.T) { + t.Parallel() if _, _, ok := todoReaderFor(usageSource{kind: providers.Claude, path: "/t.jsonl"}); !ok { t.Error("Claude should have a reader") } @@ -116,6 +118,7 @@ func TestTodoReaderForClaudeOnly(t *testing.T) { // no new line. A report lands on every tool call, so a re-emitted pair would // re-render the card through a whole turn to say nothing. func TestTodoReadReportsOnlyChanges(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "transcript.jsonl") write := func(lines ...string) { f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) @@ -157,6 +160,7 @@ func TestTodoReadReportsOnlyChanges(t *testing.T) { // happens on the first report of every session instead of on a panel somebody // opened, and the transcripts it would walk run to hundreds of MB. func TestTodoReadLooksNoFurtherBackThanTheTail(t *testing.T) { + t.Parallel() filler := strings.Repeat( `{"type":"assistant","message":{"content":[{"type":"tool_use","name":"Read","input":{}}]}}`+"\n", searchTailBytes/90+40, @@ -194,6 +198,7 @@ func TestTodoReadLooksNoFurtherBackThanTheTail(t *testing.T) { // for another file (a forked conversation), and the same file rewritten shorter // than the cursor had already reached. func TestTodoReadRereadsAReplacedTranscript(t *testing.T) { + t.Parallel() dir := t.TempDir() path := filepath.Join(dir, "transcript.jsonl") src := usageSource{kind: providers.Claude, path: path, id: "x"} diff --git a/internal/terminal/turntodo_unix_test.go b/internal/terminal/turntodo_unix_test.go index b698ccb8..95d782c5 100644 --- a/internal/terminal/turntodo_unix_test.go +++ b/internal/terminal/turntodo_unix_test.go @@ -74,6 +74,7 @@ func TestSessionTodoIsSilentWithoutAList(t *testing.T) { // readouts share: a session whose provider has not reported its conversation // yet, which is every session until its first hook lands. func TestTranscriptSourceIsSilentWithoutAProviderSession(t *testing.T) { + t.Parallel() svc := New(stubBins{}, nil, events.New()) if _, ok := svc.transcriptSource("s1"); ok { t.Error("transcriptSource: want false without a provider session") diff --git a/internal/terminal/usage_claude_test.go b/internal/terminal/usage_claude_test.go index ea881c5d..6a62f7e0 100644 --- a/internal/terminal/usage_claude_test.go +++ b/internal/terminal/usage_claude_test.go @@ -36,6 +36,7 @@ func boundaryLine(trigger string, preTokens, postTokens int) string { } func TestParseContextUsage(t *testing.T) { + t.Parallel() tests := []struct { name string tail string @@ -195,6 +196,7 @@ func TestParseContextUsage(t *testing.T) { } func TestParseContextUsageEffort(t *testing.T) { + t.Parallel() // effort is a top-level field on the entry, beside type/isSidechain. withEffort := `{"type":"assistant","effort":"xhigh","message":{"model":"` + modelOpus + `","usage":{"input_tokens":2,"cache_read_input_tokens":20000,"cache_creation_input_tokens":0}}}` @@ -210,6 +212,7 @@ func TestParseContextUsageEffort(t *testing.T) { } func TestWindowForModel(t *testing.T) { + t.Parallel() tests := []struct { model string tokens int @@ -233,6 +236,7 @@ func TestWindowForModel(t *testing.T) { } func TestReadTailReturnsLastBytes(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "t.jsonl") if err := os.WriteFile(path, []byte("0123456789"), 0o600); err != nil { t.Fatal(err) @@ -247,6 +251,7 @@ func TestReadTailReturnsLastBytes(t *testing.T) { } func TestReadTailWholeFileWhenSmaller(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "t.jsonl") if err := os.WriteFile(path, []byte("abc"), 0o600); err != nil { t.Fatal(err) @@ -258,6 +263,7 @@ func TestReadTailWholeFileWhenSmaller(t *testing.T) { } func TestReadTailMissingFile(t *testing.T) { + t.Parallel() if _, ok := readTail(filepath.Join(t.TempDir(), "nope.jsonl"), 16); ok { t.Error("readTail on a missing file should be not-ok") } diff --git a/internal/terminal/usage_codex_test.go b/internal/terminal/usage_codex_test.go index c510a5f2..73e6308a 100644 --- a/internal/terminal/usage_codex_test.go +++ b/internal/terminal/usage_codex_test.go @@ -8,6 +8,7 @@ import ( ) func TestScanCodexContextUsage(t *testing.T) { + t.Parallel() path := filepath.Join(t.TempDir(), "rollout.jsonl") body := `{"type":"turn_context","payload":{"model":"gpt-5.6-sol","effort":"high"}}` + "\n" + `{"type":"response_item","payload":{"text":"` + strings.Repeat("x", 2*int(codexScanBlockBytes)) + `"}}` + "\n" + @@ -31,6 +32,7 @@ func TestScanCodexContextUsage(t *testing.T) { } func TestCodexContextUsageMisses(t *testing.T) { + t.Parallel() for _, body := range []string{ `{"type":"event_msg","payload":{"type":"token_count","info":null}}`, `{"type":"event_msg","payload":{"type":"token_count","info":{"model_context_window":258400}}}`, diff --git a/internal/terminal/usage_cost_error_test.go b/internal/terminal/usage_cost_error_test.go index 3d9cf8f3..ee8d0860 100644 --- a/internal/terminal/usage_cost_error_test.go +++ b/internal/terminal/usage_cost_error_test.go @@ -35,6 +35,7 @@ func (r *haltingReader) Read(p []byte) (int, error) { // lines it did reach add up to a total that is simply too small. Reported as // complete it becomes a money number the user reads as the session's cost. func TestAFailedReadIsNotACompleteScan(t *testing.T) { + t.Parallel() body := costLineJSON("m1", modelOpus, 100, 0, 0, 0) + "\n" r := &haltingReader{body: []byte(body), err: errors.New("input/output error")} @@ -51,6 +52,7 @@ func TestAFailedReadIsNotACompleteScan(t *testing.T) { // The end of the file is the one error that means the scan is done — the same // reader, stopping the same way, but with io.EOF. func TestReachingTheEndIsACompleteScan(t *testing.T) { + t.Parallel() body := costLineJSON("m1", modelOpus, 100, 0, 0, 0) + "\n" r := &haltingReader{body: []byte(body), err: io.EOF} diff --git a/internal/terminal/usage_cost_test.go b/internal/terminal/usage_cost_test.go index 0f2b7402..4b262790 100644 --- a/internal/terminal/usage_cost_test.go +++ b/internal/terminal/usage_cost_test.go @@ -78,6 +78,7 @@ func writeFile(t *testing.T, body string) string { // context gauge does not: the whole file, not its tail, and output tokens // included — they are the expensive half of a bill. func TestCostSumsEveryAssistantLine(t *testing.T) { + t.Parallel() path := writeFile(t, costLineJSON("m1", modelOpus, 100, 10, 0, 0)+"\n"+ `{"type":"user","message":{"content":"hi"}}`+"\n"+ @@ -99,6 +100,7 @@ func TestCostSumsEveryAssistantLine(t *testing.T) { // the file each time would make a long session's readout cost more than the // turn it reports on. func TestCostResumesWhereItStopped(t *testing.T) { + t.Parallel() first := costLineJSON("m1", modelOpus, 100, 0, 0, 0) + "\n" path := writeFile(t, first) @@ -122,6 +124,7 @@ func TestCostResumesWhereItStopped(t *testing.T) { // appends while lich reads, so a tail with no newline is content that is not // there yet. Counting it would bill a half-written number. func TestAPartialLineIsLeftForTheNextScan(t *testing.T) { + t.Parallel() whole := costLineJSON("m1", modelOpus, 100, 0, 0, 0) + "\n" path := writeFile(t, whole+`{"type":"assistant","message":{"id":"m2"`) @@ -142,6 +145,7 @@ func TestAPartialLineIsLeftForTheNextScan(t *testing.T) { // one assistant message across several lines, each repeating that message's // cumulative usage; counting the repeat doubles the turn. func TestOneMessageWrittenTwiceIsBilledOnce(t *testing.T) { + t.Parallel() line := costLineJSON("m1", modelOpus, 100, 0, 0, 0) path := writeFile(t, line+"\n"+line+"\n") @@ -156,6 +160,7 @@ func TestOneMessageWrittenTwiceIsBilledOnce(t *testing.T) { // repeat can land in the next scan, so the last billed message id is part of // what the ledger remembers. func TestTheDedupSurvivesAScanBoundary(t *testing.T) { + t.Parallel() line := costLineJSON("m1", modelOpus, 100, 0, 0, 0) path := writeFile(t, line+"\n") @@ -175,6 +180,7 @@ func TestTheDedupSurvivesAScanBoundary(t *testing.T) { // turn. The scan stops there, the readout goes absent, and the same line is // retried once a price for it lands. func TestAnUnpricedModelStopsTheCount(t *testing.T) { + t.Parallel() path := writeFile(t, costLineJSON("m1", modelOpus, 100, 0, 0, 0)+"\n"+ costLineJSON("m2", "model-from-the-future", 900, 0, 0, 0)+"\n"+ @@ -209,6 +215,7 @@ func TestAnUnpricedModelStopsTheCount(t *testing.T) { // — no tokens, and a placeholder where the model id goes — neither cost // anything nor block the count behind a model that will never have a price. func TestASyntheticLineIsNotAModelToPrice(t *testing.T) { + t.Parallel() path := writeFile(t, costLineJSON("m1", "", 0, 0, 0, 0)+"\n"+ costLineJSON("m2", modelOpus, 100, 0, 0, 0)+"\n") @@ -229,6 +236,7 @@ func TestASyntheticLineIsNotAModelToPrice(t *testing.T) { // rate reads a session as cheaper than it was. The wants are written out, not // rebuilt from testRate, so a rate applied to the wrong counter fails here. func TestACacheWriteIsPricedByHowLongItIsKept(t *testing.T) { + t.Parallel() tests := []struct { name string cacheCreate int @@ -265,6 +273,7 @@ func TestACacheWriteIsPricedByHowLongItIsKept(t *testing.T) { // billed at the five-minute one, because that total is wrong in the direction // that flatters the bill. func TestAnUnpricedCacheLifetimeStopsTheCount(t *testing.T) { + t.Parallel() fiveMinuteOnly := fixedRates{modelOpus: {Input: 0.001, Output: 0.01, CacheRead: 0.001, CacheWrite: 0.001}} path := writeFile(t, cacheLineJSON("m1", modelOpus, 1000, 1000)+"\n") @@ -289,6 +298,7 @@ func TestAnUnpricedCacheLifetimeStopsTheCount(t *testing.T) { // counted; where the same tokens live now is // TestASubAgentTranscriptIsBilledIntoTheSession. func TestASubAgentsTokensAreBilled(t *testing.T) { + t.Parallel() sidechain := `{"type":"assistant","isSidechain":true,"message":{"id":"m2","model":"` + modelOpus + `","usage":{"input_tokens":100,"output_tokens":0,"cache_read_input_tokens":0,"cache_creation_input_tokens":0}}}` path := writeFile(t, costLineJSON("m1", modelOpus, 100, 0, 0, 0)+"\n"+sidechain+"\n") @@ -304,6 +314,7 @@ func TestASubAgentsTokensAreBilled(t *testing.T) { // shorter than what was already counted is not the file that was counted, so // its row is rebuilt rather than resumed into content that is gone. func TestAReplacedTranscriptIsRecounted(t *testing.T) { + t.Parallel() path := writeFile(t, costLineJSON("m1", modelOpus, 500, 0, 0, 0)+"\n") ledger, _, _ := scanTranscriptCost(path, costLedger{}, testRate) @@ -324,6 +335,7 @@ func TestAReplacedTranscriptIsRecounted(t *testing.T) { // TestAnUnreadableTranscriptIsAMiss keeps the readout on the same "degrade, // never break" contract as the context gauge. func TestAnUnreadableTranscriptIsAMiss(t *testing.T) { + t.Parallel() if _, _, ok := scanTranscriptCost(filepath.Join(t.TempDir(), "nope.jsonl"), costLedger{}, testRate); ok { t.Error("scan: want a miss for a transcript that does not exist") } @@ -492,6 +504,7 @@ func codexTurnContextJSON(model string) string { // the turn, and cached_input_tokens is a subset of input_tokens, so only the // uncached share prices at the input rate — summing both would double-count. func TestCodexTranscriptCostPricesTheRunningTotal(t *testing.T) { + t.Parallel() path := writeFile(t, codexTurnContextJSON("gpt-5.1-codex")+"\n"+codexTokenLineJSON(1000, 400, 25, 10)+"\n") @@ -511,6 +524,7 @@ func TestCodexTranscriptCostPricesTheRunningTotal(t *testing.T) { // whole new total, not a delta to add to the first, so only the last one read // backward from the end may count. func TestCodexTranscriptCostIsCumulativeNotSummed(t *testing.T) { + t.Parallel() path := writeFile(t, codexTurnContextJSON("gpt-5.1-codex")+"\n"+ codexTokenLineJSON(500, 0, 0, 5)+"\n"+ @@ -532,6 +546,7 @@ func TestCodexTranscriptCostIsCumulativeNotSummed(t *testing.T) { // over from the Claude scan: a model nothing can price yields no number, // never a guess. func TestCodexTranscriptCostWithholdsForAnUnpricedModel(t *testing.T) { + t.Parallel() path := writeFile(t, codexTurnContextJSON("model-from-the-future")+"\n"+codexTokenLineJSON(1000, 0, 0, 10)+"\n") @@ -550,6 +565,7 @@ func TestCodexTranscriptCostWithholdsForAnUnpricedModel(t *testing.T) { // either rate bills some of those tokens wrong. Absent beats a guess — the same // answer an unpriced line gets. func TestCodexTranscriptCostWithholdsAcrossAModelSwitch(t *testing.T) { + t.Parallel() rates := fixedRates{ "gpt-5.1-codex": {Input: 0.001, Output: 0.01, CacheRead: 0.0005, CacheWrite: 0.002}, "gpt-5.1-codex-mini": {Input: 0.0001, Output: 0.001, CacheRead: 0.00005, CacheWrite: 0.0002}, @@ -573,6 +589,7 @@ func TestCodexTranscriptCostWithholdsAcrossAModelSwitch(t *testing.T) { // the walk that reaches the start of the file without finding a second is what // proves it — not a failure to price. func TestCodexTranscriptCostPricesTheWholeTotalForOneModel(t *testing.T) { + t.Parallel() path := writeFile(t, codexTurnContextJSON("gpt-5.1-codex")+"\n"+ codexTokenLineJSON(500, 0, 0, 5)+"\n"+ @@ -594,6 +611,7 @@ func TestCodexTranscriptCostPricesTheWholeTotalForOneModel(t *testing.T) { } func TestCodexTranscriptCostMissesWithoutATokenLine(t *testing.T) { + t.Parallel() path := writeFile(t, codexTurnContextJSON("gpt-5.1-codex")+"\n") _, miss, ok := codexTranscriptCost(path, codexTestRate) @@ -779,6 +797,7 @@ func TestAPricedSessionCarriesNoMissReason(t *testing.T) { // about to count that stretch a second time, and what it had cost the first time // is what has to come back off. func TestAForkRecordsWhatItsParentHadCost(t *testing.T) { + t.Parallel() store := newCostStore("") store.bin = stayAliveBin(t) store.forkOffsets = map[string]float64{} @@ -800,6 +819,7 @@ func TestAForkRecordsWhatItsParentHadCost(t *testing.T) { // own figure and starts a forked session's at zero, so netting anything off it // would hide what the fork itself spent. func TestAForkOfAProviderThatPricesItselfOffsetsNothing(t *testing.T) { + t.Parallel() store := newCostStore("") store.bin = stayAliveBin(t) store.forkOffsets = map[string]float64{} @@ -822,6 +842,7 @@ func TestAForkOfAProviderThatPricesItselfOffsetsNothing(t *testing.T) { // that only resumes must record no offset — one that did would bill the session // backwards for its own history. func TestAResumeIsNotAFork(t *testing.T) { + t.Parallel() store := newCostStore("") store.bin = stayAliveBin(t) store.forkOffsets = map[string]float64{} @@ -843,6 +864,7 @@ func TestAResumeIsNotAFork(t *testing.T) { // session, and a readout that is too high is not worth losing it, so the // failure is logged and the spawn goes ahead with no offset recorded. func TestAForkWhoseOffsetCannotBeSavedStillStarts(t *testing.T) { + t.Parallel() store := newCostStore("") store.bin = stayAliveBin(t) store.forkOffsets = map[string]float64{} diff --git a/internal/terminal/usage_kiro_test.go b/internal/terminal/usage_kiro_test.go index 5c01f7a7..88bfbf0f 100644 --- a/internal/terminal/usage_kiro_test.go +++ b/internal/terminal/usage_kiro_test.go @@ -25,6 +25,7 @@ func kiroSessionFile(t *testing.T, state string) string { // the percent lich shows is the one Kiro's own TUI drew: a session carrying // 0.74200004 against a 200k window was displayed as "1%". func TestKiroContextUsageReadsThePercentage(t *testing.T) { + t.Parallel() path := kiroSessionFile(t, `{"rts_model_state":{ "model_info":{"model_id":"auto","context_window_tokens":200000}, "context_usage_percentage":0.74200004}}`) @@ -54,6 +55,7 @@ func TestKiroContextUsageReadsThePercentage(t *testing.T) { // the same expression the code uses: a half-percent rounds up, and a window // share too small to reach one percent still reports the tokens it spent. func TestKiroContextUsageRounding(t *testing.T) { + t.Parallel() tests := []struct { name string percent string @@ -85,6 +87,7 @@ func TestKiroContextUsageRounding(t *testing.T) { // not yet asked anything writes both fields as null, which is the common one: // repainting the ring at 0% there would blank a figure the last turn earned. func TestKiroContextUsageMisses(t *testing.T) { + t.Parallel() tests := []struct { name string state string @@ -107,6 +110,7 @@ func TestKiroContextUsageMisses(t *testing.T) { // miss rather than a panic — a session file is written by another process and // can be read mid-write. func TestKiroContextUsageUnreadable(t *testing.T) { + t.Parallel() if _, ok := kiroContextUsage(filepath.Join(t.TempDir(), "absent.json")); ok { t.Errorf("kiroContextUsage(absent) = _, true, want a miss") } diff --git a/internal/terminal/usage_tokenonly_test.go b/internal/terminal/usage_tokenonly_test.go index 1ed7dce7..c136ba6c 100644 --- a/internal/terminal/usage_tokenonly_test.go +++ b/internal/terminal/usage_tokenonly_test.go @@ -227,6 +227,7 @@ func TestOMPCostIgnoresALineStillBeingWritten(t *testing.T) { // TestOMPCostOnAMissingFile keeps the reader on the same "degrade, never break" // contract as every other transcript read. func TestOMPCostOnAMissingFile(t *testing.T) { + t.Parallel() cost, miss, ok := ompTranscriptCost(filepath.Join(t.TempDir(), "nope.jsonl")) if ok { t.Errorf("ompTranscriptCost = %v, want a miss", cost) @@ -452,6 +453,7 @@ func TestTheCostOnlyRungSurvivesAStoreFailure(t *testing.T) { // that reaches the cost switch anyway must read as "nothing to show" rather than // as free. func TestAProviderWithNoRecordsIsPricedByNobody(t *testing.T) { + t.Parallel() svc := New(newCostStore("agy-1"), nil, events.New()) svc.prices = testRate diff --git a/internal/terminal/workdir_test.go b/internal/terminal/workdir_test.go index 0effc1af..cc2d54d3 100644 --- a/internal/terminal/workdir_test.go +++ b/internal/terminal/workdir_test.go @@ -10,6 +10,7 @@ import ( // absence answers true, because a false answer costs a spawn that reports its // own failure while a true one closes the user's session. func TestWorkdirMissing(t *testing.T) { + t.Parallel() dir := t.TempDir() file := filepath.Join(dir, "not-a-dir") if err := os.WriteFile(file, []byte("x"), 0o600); err != nil { diff --git a/internal/terminal/worktreeport_test.go b/internal/terminal/worktreeport_test.go index f5991b3d..69c156d8 100644 --- a/internal/terminal/worktreeport_test.go +++ b/internal/terminal/worktreeport_test.go @@ -14,6 +14,7 @@ func neverFree(int) bool { return false } // port is a function of the path and nothing else, so a worktree keeps its // number across restarts of the session and of lich. func TestWorktreePortIsStable(t *testing.T) { + t.Parallel() path := filepath.Join("src", "repo", "feature-a") first := worktreePort(path) for range 100 { @@ -27,6 +28,7 @@ func TestWorktreePortIsStable(t *testing.T) { // the same port — otherwise a trailing slash would hand the same checkout two // numbers depending on which caller asked. func TestWorktreePortIgnoresPathNoise(t *testing.T) { + t.Parallel() clean := filepath.Join("src", "repo") noisy := filepath.Join("src", "nested", "..", "repo") + string(filepath.Separator) if worktreePort(clean) != worktreePort(noisy) { @@ -40,6 +42,7 @@ func TestWorktreePortIgnoresPathNoise(t *testing.T) { // server at once. Not a guarantee for arbitrary paths (the hash may collide), // which is why these are named rather than generated. func TestWorktreePortSeparatesCheckouts(t *testing.T) { + t.Parallel() seen := map[int]string{} for _, name := range []string{"repo", "repo-feature-a", "repo-feature-b", "repo-fix"} { path := filepath.Join("src", name) @@ -62,6 +65,7 @@ const lichListenPort = 47821 // lich's own listener — and comparing against the constants under test would // only restate the arithmetic. func TestWorktreePortStaysInRange(t *testing.T) { + t.Parallel() for i := range 5000 { path := filepath.Join("src", "repo", string(rune('a'+i%26)), string(rune(i))) port := worktreePort(path) @@ -75,6 +79,7 @@ func TestWorktreePortStaysInRange(t *testing.T) { // pinned listener, not merely the paths the range test happens to sample: a // checkout handed the app's own port would collide with lich itself. func TestWorktreePortWindowClearsListener(t *testing.T) { + t.Parallel() if lichListenPort >= worktreePortBase && lichListenPort < worktreePortBase+worktreePortCount { t.Fatalf("window [%d, %d) contains lich's listener %d", worktreePortBase, worktreePortBase+worktreePortCount, lichListenPort) @@ -85,6 +90,7 @@ func TestWorktreePortWindowClearsListener(t *testing.T) { // caller is told there is no port to export rather than handed a number every // pathless session would share. func TestWorktreePortWithoutPath(t *testing.T) { + t.Parallel() if got := worktreePort(""); got != 0 { t.Fatalf("worktreePort(\"\") = %d, want 0", got) } @@ -93,6 +99,7 @@ func TestWorktreePortWithoutPath(t *testing.T) { // TestPickWorktreePortPrefersHash proves an uncontested checkout still gets its // hashed number, which is what keeps a worktree's port stable for free. func TestPickWorktreePortPrefersHash(t *testing.T) { + t.Parallel() path := filepath.Join("src", "repo", "feature-a") if got := pickWorktreePort(path, map[int]bool{}, alwaysFree); got != worktreePort(path) { t.Fatalf("pickWorktreePort(%q) = %d, want the hashed %d", path, got, worktreePort(path)) @@ -104,6 +111,7 @@ func TestPickWorktreePortPrefersHash(t *testing.T) { // holds is moved off it rather than handed a number two dev servers would fight // over. func TestPickWorktreePortWalksPastReserved(t *testing.T) { + t.Parallel() path := filepath.Join("src", "repo", "feature-a") hashed := worktreePort(path) got := pickWorktreePort(path, map[int]bool{hashed: true}, alwaysFree) @@ -119,6 +127,7 @@ func TestPickWorktreePortWalksPastReserved(t *testing.T) { // cannot see: a port some unrelated process on this machine is already // listening on is skipped too. func TestPickWorktreePortWalksPastBoundPort(t *testing.T) { + t.Parallel() path := filepath.Join("src", "repo", "feature-a") hashed := worktreePort(path) free := func(port int) bool { return port != hashed } @@ -131,6 +140,7 @@ func TestPickWorktreePortWalksPastBoundPort(t *testing.T) { // port from the hashed one to the end of the window taken, the checkout is // served from the start of the window rather than out of it. func TestPickWorktreePortWrapsAtWindowEnd(t *testing.T) { + t.Parallel() path := filepath.Join("src", "repo", "feature-a") hashed := worktreePort(path) inUse := map[int]bool{} @@ -148,6 +158,7 @@ func TestPickWorktreePortWrapsAtWindowEnd(t *testing.T) { // session nothing it did not already have: it gets its hashed number back, // which is the collision the caller lived with before any of this. func TestPickWorktreePortExhaustedFallsBackToHash(t *testing.T) { + t.Parallel() path := filepath.Join("src", "repo", "feature-a") if got := pickWorktreePort(path, map[int]bool{}, neverFree); got != worktreePort(path) { t.Fatalf("pickWorktreePort(%q) = %d, want the hashed %d", path, got, worktreePort(path)) @@ -157,6 +168,7 @@ func TestPickWorktreePortExhaustedFallsBackToHash(t *testing.T) { // TestPickWorktreePortWithoutPath proves a pathless session is not walked into // a port: it has no checkout to own one. func TestPickWorktreePortWithoutPath(t *testing.T) { + t.Parallel() if got := pickWorktreePort("", map[int]bool{}, alwaysFree); got != 0 { t.Fatalf("pickWorktreePort(\"\") = %d, want 0", got) } diff --git a/internal/terminal/write_test.go b/internal/terminal/write_test.go index eb1bcb01..6670b420 100644 --- a/internal/terminal/write_test.go +++ b/internal/terminal/write_test.go @@ -31,6 +31,7 @@ func (p *shortWritePTY) Close() error { return nil } // of a write still receives every byte: writeBytes has to loop rather than // trust the first call, which is what a large paste needs on Windows. func TestWriteBytesSurvivesShortWrites(t *testing.T) { + t.Parallel() fake := &shortWritePTY{chunk: 3} s := New(nil, nil, events.New()) s.sessions["s1"] = &session{pty: fake} diff --git a/internal/terminal/writequeue_test.go b/internal/terminal/writequeue_test.go index ae559b5b..0ac21bb4 100644 --- a/internal/terminal/writequeue_test.go +++ b/internal/terminal/writequeue_test.go @@ -22,6 +22,7 @@ func frameFor(t *testing.T, id, payload string) []byte { // and only a full queue makes a session wait — for the socket it is filling, // never for another session's. func TestWriteQueueBlocksOnlyOnceFull(t *testing.T) { + t.Parallel() q := newWriteQueue() filled := make(chan struct{}) @@ -65,6 +66,7 @@ func TestWriteQueueBlocksOnlyOnceFull(t *testing.T) { // itself, so a push may only report failure after the frames queued ahead of it // are out — otherwise the newest frame would reach the client first. func TestWriteQueueDrainsBeforeRejectingAPush(t *testing.T) { + t.Parallel() q := newWriteQueue() q.push(frameFor(t, "sess", "a")) q.push(frameFor(t, "sess", "b")) @@ -113,6 +115,7 @@ func TestWriteQueueDrainsBeforeRejectingAPush(t *testing.T) { // after flush returns, and a frame the writer has taken but not yet put on the // socket would otherwise be overtaken by the banner. func TestWriteQueueFlushWaitsForTheFrameInTheWritersHands(t *testing.T) { + t.Parallel() q := newWriteQueue() q.push(frameFor(t, "sess", "last")) @@ -145,6 +148,7 @@ func TestWriteQueueFlushWaitsForTheFrameInTheWritersHands(t *testing.T) { // the /events bridge instead, in the order the sessions produced them, and the // transport is told once that the client is gone. func TestWriteQueueTakesRefusedFramesToTheFallback(t *testing.T) { + t.Parallel() tr, err := newTransport(func(string, []byte) {}, nil, nil, nil, nil) if err != nil { t.Fatalf("newTransport: %v", err)