diff --git a/internal/cli/ask_test.go b/internal/cli/ask_test.go index faa96015..2cc5ef70 100644 --- a/internal/cli/ask_test.go +++ b/internal/cli/ask_test.go @@ -14,6 +14,7 @@ func answered(answer string) string { } func TestAskPostsTheQuestionAndPrintsTheAnswer(t *testing.T) { + t.Parallel() f := newFakeLich(t, answered("Fixing the login test.")) code, stdout, stderr := run(t, f, "ask", "--project", "lich", "auth-fix", "what", "are you on?") if code != 0 { @@ -33,6 +34,7 @@ func TestAskPostsTheQuestionAndPrintsTheAnswer(t *testing.T) { } func TestAskJSON(t *testing.T) { + t.Parallel() f := newFakeLich(t, answered("Fixing it.")) code, stdout, _ := run(t, f, "ask", "--json", "auth-fix", "status?") if code != 0 { @@ -48,6 +50,7 @@ func TestAskJSON(t *testing.T) { } func TestAskRefusesAMalformedCall(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) if code, _, stderr := run(t, f, "ask", "auth-fix"); code != 1 || !strings.Contains(stderr, "usage: lich ask") { t.Errorf("exit = %d, stderr %q; want 1 with the usage line", code, stderr) @@ -58,6 +61,7 @@ func TestAskRefusesAMalformedCall(t *testing.T) { } func TestAskReportsTheRefusal(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"error":"\"Session 3\" is this session, and a session cannot ask itself"}`) f.status = 400 code, stdout, stderr := run(t, f, "ask", "Session 3", "why?") @@ -67,6 +71,7 @@ func TestAskReportsTheRefusal(t *testing.T) { } func TestMCPAskSessionAnswersWithTheAnswer(t *testing.T) { + t.Parallel() f := newFakeLich(t, answered("Fixing the login test.")) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": {"name":"ask_session","arguments":{"session":"auth-fix","question":"what are you on?"}}}`) diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 0df26dfc..0c505f58 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -143,6 +143,7 @@ func noInstance() (*singleton.Info, error) { return nil, nil } // The app's own arguments still fall through to it. A word that is not a // subcommand no longer does — see TestAnUnknownCommandDoesNotOpenTheApp. func TestTheAppsOwnArgumentsAreNotACommand(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) for _, args := range [][]string{ @@ -161,6 +162,7 @@ func TestTheAppsOwnArgumentsAreNotACommand(t *testing.T) { // a window that says nothing about the command that was run is not an answer to // a typo. func TestAnUnknownCommandDoesNotOpenTheApp(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, _, stderr := run(t, f, "sesions") @@ -187,6 +189,7 @@ func TestAnUnknownCommandDoesNotOpenTheApp(t *testing.T) { } func TestVersionPrintsTheBuild(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) for _, args := range [][]string{{"version"}, {"--version"}, {"-v"}} { @@ -204,6 +207,7 @@ func TestVersionPrintsTheBuild(t *testing.T) { // package's "flag: help requested" on stderr, which is what a user asking how to // run something used to be handed — as a failure, exit 1. func TestEveryCommandPrintsItsOwnHelp(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) for _, cmd := range commands { @@ -228,6 +232,7 @@ func TestEveryCommandPrintsItsOwnHelp(t *testing.T) { // cannot drift: a flag added to a command shows up here without anyone editing // prose. func TestPerCommandHelpListsTheFlags(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) _, stdout, _ := run(t, f, "send", "--help") @@ -247,6 +252,7 @@ func TestPerCommandHelpListsTheFlags(t *testing.T) { } func TestHelpPrintsEveryCommand(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, stdout, _ := run(t, f, "help") @@ -264,6 +270,7 @@ func TestHelpPrintsEveryCommand(t *testing.T) { } func TestSessionsListsPeers(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[{"label":"docs","name":"lich-s2","project":"lich","kind":"codex","state":"waiting"}, {"label":"api","name":"revu-s5","project":"revu","kind":"crush"}]`) @@ -295,6 +302,7 @@ func TestSessionsListsPeers(t *testing.T) { // The lich id rides --json last, so a mod holding a recorded LICH_SESSION_ID // can find that session's card. func TestSessionsJSONCarriesTheLichID(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[{"label":"docs","name":"lich-s2","project":"lich","kind":"codex","state":"done","id":"s2",`+ `"path":"/src/lich","projectPath":"/src/lich"}]`) @@ -311,6 +319,7 @@ func TestSessionsJSONCarriesTheLichID(t *testing.T) { // whoami is the one entry sessions leaves out, asked for by the caller's own id. func TestWhoamiPrintsTheCallersOwnSession(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"label":"Session 3","name":"lich-s1","project":"lich","kind":"claude","state":"busy","id":"s1"}`) code, stdout, stderr := run(t, f, "whoami") @@ -327,6 +336,7 @@ func TestWhoamiPrintsTheCallersOwnSession(t *testing.T) { } func TestWhoamiJSONIsThePeerObject(t *testing.T) { + t.Parallel() body := `{"label":"Session 3","name":"lich-s1","project":"lich","kind":"claude","state":"","id":"s1",` + `"path":"/src/lich","projectPath":"/src/lich"}` f := newFakeLich(t, body) @@ -341,6 +351,7 @@ func TestWhoamiJSONIsThePeerObject(t *testing.T) { } func TestSessionsSaysWhenThereAreNone(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[]`) code, stdout, _ := run(t, f, "sessions") @@ -353,6 +364,7 @@ func TestSessionsSaysWhenThereAreNone(t *testing.T) { } func TestSendPrintsTheAnswer(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"answered","answer":"3 failures"}`) code, stdout, stderr := run(t, f, "send", "docs", "run the tests") @@ -374,6 +386,7 @@ func TestSendPrintsTheAnswer(t *testing.T) { } func TestSendPassesProjectAndTimeout(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"api","status":"answered","answer":"ok"}`) code, _, stderr := run(t, f, "send", "--project", "revu", "--timeout", "5", "api", "hello") @@ -391,6 +404,7 @@ func TestSendPassesProjectAndTimeout(t *testing.T) { } func TestSendPointsAtTheTicketWhenTheWaitRunsOut(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"pending","answer":""}`) // Contract change: a ticket used to exit 0, which a script could not tell @@ -414,6 +428,7 @@ func TestSendPointsAtTheTicketWhenTheWaitRunsOut(t *testing.T) { } func TestWaitPicksUpAnAnswer(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"answered","answer":"late but here"}`) code, stdout, _ := run(t, f, "wait", "a1b2c3d4") @@ -436,6 +451,7 @@ func TestWaitPicksUpAnAnswer(t *testing.T) { // Without a ticket, wait collects everything that is ready — the command the // nudge at a sender's prompt names. func TestWaitWithoutATicketCollectsEverything(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"results":[ {"ticket":"t1","target":"auth","status":"answered","answer":"all green"}], "open":["docs"]}`) @@ -460,6 +476,7 @@ func TestWaitWithoutATicketCollectsEverything(t *testing.T) { } func TestReplySendsTheAnswerHome(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, stdout, stderr := run(t, f, "reply", "a1b2c3d4", "3 failures in foo_test") @@ -483,6 +500,7 @@ func TestReplySendsTheAnswerHome(t *testing.T) { // context was compacted past the message no longer has, and the session it runs // in names the errand for it. func TestReplyWithoutATicketSendsTheSessionsOwnErrandHome(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, stdout, stderr := run(t, f, "reply", "3 failures in foo_test") @@ -503,6 +521,7 @@ func TestReplyWithoutATicketSendsTheSessionsOwnErrandHome(t *testing.T) { // refusal is the only thing that tells the agent which tickets it has to choose // between — so it has to reach the terminal whole, list and newlines included. func TestTheRefusalOfATicketlessAnswerReachesTheAgent(t *testing.T) { + t.Parallel() refusal := "2 requests are open against this session, and an answer that names no ticket " + "would close the wrong one. Name the ticket the answer belongs to:\n" + " lich reply a1b2c3d4 \"\" — run the tests\n" + @@ -529,6 +548,7 @@ func TestTheRefusalOfATicketlessAnswerReachesTheAgent(t *testing.T) { } func TestWrongArgumentCountsFailWithUsage(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) tests := [][]string{ @@ -567,6 +587,7 @@ func TestWrongArgumentCountsFailWithUsage(t *testing.T) { } func TestAnUnknownFlagFailsWithoutCallingTheApp(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, _, stderr := run(t, f, "send", "--nonsense", "docs", "hello") @@ -582,6 +603,7 @@ func TestAnUnknownFlagFailsWithoutCallingTheApp(t *testing.T) { } func TestTheAppsErrorReachesTheUser(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"error":"no live session named \"ghost\""}`) f.status = http.StatusInternalServerError @@ -595,6 +617,7 @@ func TestTheAppsErrorReachesTheUser(t *testing.T) { } func TestAFailureWithNoEnvelopeFallsBackToTheStatus(t *testing.T) { + t.Parallel() f := newFakeLich(t, `gateway`) f.status = http.StatusBadGateway @@ -608,6 +631,7 @@ func TestAFailureWithNoEnvelopeFallsBackToTheStatus(t *testing.T) { } func TestWithNoLichAnywhereEveryCommandSaysSo(t *testing.T) { + t.Parallel() bare := func(string) string { return "" } for _, args := range [][]string{ @@ -630,6 +654,7 @@ func TestWithNoLichAnywhereEveryCommandSaysSo(t *testing.T) { } func TestOutsideASessionTheRuntimeFileIsUsed(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[{"label":"docs","project":"lich","kind":"codex"}]`) port := f.server.Listener.Addr().(*net.TCPAddr).Port @@ -657,6 +682,7 @@ func TestOutsideASessionTheRuntimeFileIsUsed(t *testing.T) { } func TestTheEnvironmentWinsOverTheRuntimeFile(t *testing.T) { + t.Parallel() session := newFakeLich(t, `[]`) other := newFakeLich(t, `[]`) otherPort := other.server.Listener.Addr().(*net.TCPAddr).Port @@ -685,6 +711,7 @@ func TestTheEnvironmentWinsOverTheRuntimeFile(t *testing.T) { // listener refuses from then on — measured on a background agent's `lich mcp`, // which the harness keeps alive in its own daemon across a lich restart. func TestARefusedTokenIsRetriedWithTheRecordedOne(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[{"label":"docs","project":"lich","kind":"claude"}]`) f.token = "fresh" port := f.server.Listener.Addr().(*net.TCPAddr).Port @@ -718,6 +745,7 @@ func TestARefusedTokenIsRetriedWithTheRecordedOne(t *testing.T) { // `task dev` build at once, and a message delivered to the wrong one lands in a // window this caller cannot see. func TestARefusedTokenIsNotRetriedElsewhere(t *testing.T) { + t.Parallel() for _, tc := range []struct { name string running func(port int) *singleton.Info @@ -765,6 +793,7 @@ func TestARefusedTokenIsNotRetriedElsewhere(t *testing.T) { } func TestTheRuntimeFileFailureIsReported(t *testing.T) { + t.Parallel() var stdout, stderr bytes.Buffer c := &client{ env: func(string) string { return "" }, @@ -781,6 +810,7 @@ func TestTheRuntimeFileFailureIsReported(t *testing.T) { } func TestJSONOutputIsMachineReadable(t *testing.T) { + t.Parallel() peers := newFakeLich(t, `[{"label":"docs","project":"lich","kind":"codex","state":"waiting"}]`) code, stdout, stderr := run(t, peers, "sessions", "--json") if code != 0 { @@ -814,6 +844,7 @@ func TestJSONOutputIsMachineReadable(t *testing.T) { } func TestEmptyPeersAreAnEmptyJSONArray(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, stdout, _ := run(t, f, "sessions", "--json") @@ -831,6 +862,7 @@ const openedBody = `{"id":"9f8e","projectId":"p1","project":"lich","label":"auth "name":"auth-fix-9f8e","kind":"claude","path":"/wt/auth-fix","nextSeq":5}` func TestOpenNamesBothWaysToAddressTheNewSession(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) code, stdout, stderr := run(t, f, "open", "--worktree", "auth-fix") @@ -857,6 +889,7 @@ func TestOpenNamesBothWaysToAddressTheNewSession(t *testing.T) { } func TestOpenPassesEveryFlagThrough(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) code, _, stderr := run(t, f, @@ -878,6 +911,7 @@ func TestOpenPassesEveryFlagThrough(t *testing.T) { } func TestOpenJSONIsMachineReadable(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) code, stdout, stderr := run(t, f, "open", "--json") @@ -901,6 +935,7 @@ func TestOpenJSONIsMachineReadable(t *testing.T) { // The whole point of the flag: opening a worker for a task is one command // rather than two, exactly as the open_session tool has been. func TestOpenHandsOverTheTaskItCameWith(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) f.answers = map[string]answer{ "relay.Send": {status: 200, body: `{"ticket":"a1b2c3d4","target":"auth-fix","status":"pending"}`}, @@ -932,6 +967,7 @@ func TestOpenHandsOverTheTaskItCameWith(t *testing.T) { } func TestOpenJSONCarriesTheHandOffBesideTheSession(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) f.answers = map[string]answer{ "relay.Send": {status: 200, body: `{"ticket":"a1b2c3d4","target":"auth-fix","status":"pending"}`}, @@ -969,6 +1005,7 @@ func TestOpenJSONCarriesTheHandOffBesideTheSession(t *testing.T) { // exit code has to say the task did not land, or a script reading 0 believes it // did. func TestOpenKeepsTheSessionWhenTheTaskDoesNotLand(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) f.answers = map[string]answer{ "relay.Send": {status: 500, body: `{"error":"the setup script is still running"}`}, @@ -1008,6 +1045,7 @@ func TestOpenKeepsTheSessionWhenTheTaskDoesNotLand(t *testing.T) { } func TestOpenReportsTheAppsRefusal(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"error":"no open project named \"nope\""}`) f.status = 400 @@ -1024,6 +1062,7 @@ func TestOpenReportsTheAppsRefusal(t *testing.T) { } func TestSendSaysWhenTheTaskWasNeverPickedUp(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"unread"}`) code, stdout, stderr := run(t, f, "send", "docs", "run the tests") @@ -1043,6 +1082,7 @@ func TestSendSaysWhenTheTaskWasNeverPickedUp(t *testing.T) { } func TestSendSaysWhenTheTaskNeverReachedAPrompt(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"undelivered"}`) code, stdout, stderr := run(t, f, "send", "docs", "run the tests") @@ -1063,6 +1103,7 @@ func TestSendSaysWhenTheTaskNeverReachedAPrompt(t *testing.T) { } func TestCloseSaysWhatWentWithTheSession(t *testing.T) { + t.Parallel() kept := newFakeLich(t, `{"id":"9f8e","projectId":"p1","project":"lich","label":"auth-fix", "worktree":"/wt/auth-fix","kept":true}`) code, stdout, stderr := run(t, kept, "close", "--worktree", "keep", "auth-fix") @@ -1097,6 +1138,7 @@ func TestCloseSaysWhatWentWithTheSession(t *testing.T) { } func TestCloseReportsTheRefusalToDecideForYou(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"error":"\"auth-fix\" is the last session in the worktree /wt/auth-fix"}`) f.status = 400 @@ -1113,6 +1155,7 @@ func TestCloseReportsTheRefusalToDecideForYou(t *testing.T) { } func TestWorktreesListsWhatIsInEach(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[{"name":"auth-fix","path":"/wt/auth-fix","dirty":true,"sessions":["auth-fix"]}, {"name":"idle","path":"/wt/idle","dirty":false,"sessions":[]}]`) @@ -1131,6 +1174,7 @@ func TestWorktreesListsWhatIsInEach(t *testing.T) { } func TestWorktreesSaysWhenThereAreNone(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, stdout, _ := run(t, f, "worktrees") @@ -1152,6 +1196,7 @@ func TestWorktreesSaysWhenThereAreNone(t *testing.T) { // session is what lich did before the sandbox existed, and a line about it on // every open is a line nobody reads by the third one. func TestOpenSaysWhenTheSessionRunsConfined(t *testing.T) { + t.Parallel() unconfined := newFakeLich(t, openedBody) _, plain, _ := run(t, unconfined, "open", "--worktree", "auth-fix") if strings.Contains(plain, "confined") { @@ -1176,6 +1221,7 @@ func TestOpenSaysWhenTheSessionRunsConfined(t *testing.T) { // what a script branches on, so the flag that changes the output must not // change the status. Pinned as literals because the numbers are the contract. func TestSendAndWaitExitOnTheirOutcome(t *testing.T) { + t.Parallel() for status, want := range map[string]int{ "answered": 0, "pending": 2, "unread": 3, "unanswered": 3, "undelivered": 3, "stopped": 3, "expired": 3, @@ -1199,6 +1245,7 @@ func TestSendAndWaitExitOnTheirOutcome(t *testing.T) { // A collect that held the line and came back empty-handed is a timeout; one that // brought anything home, or found nothing open to wait on, is done. func TestCollectExitsPendingOnlyWhenItCameBackEmpty(t *testing.T) { + t.Parallel() for body, want := range map[string]int{ `{"results":[],"open":["docs"]}`: 2, `{"results":[{"ticket":"t1","target":"auth","status":"unread"}],"open":["docs"]}`: 0, @@ -1214,6 +1261,7 @@ func TestCollectExitsPendingOnlyWhenItCameBackEmpty(t *testing.T) { } func TestVersionReportsTheRunningServer(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"version":"v1.2.3","platform":"linux/amd64","logPath":"/x"}`) code, stdout, _ := run(t, f, "version") @@ -1235,6 +1283,7 @@ func TestVersionReportsTheRunningServer(t *testing.T) { // No lich at all is the normal case for `lich version`, not a failure. func TestVersionWorksWithNoServer(t *testing.T) { + t.Parallel() var stdout, stderr bytes.Buffer c := &client{ env: func(string) string { return "" }, version: testVersion, @@ -1264,6 +1313,7 @@ func runWithStdin(t *testing.T, f *fakeLich, stdin string, args ...string) (int, const insertedBody = `{"id":"s2","label":"docs","project":"lich","kind":"codex","bytes":5}` func TestInsertTakesTheTextFromItsArgument(t *testing.T) { + t.Parallel() f := newFakeLich(t, insertedBody) code, stdout, stderr := run(t, f, "insert", "--project", "/src/lich", "hello") @@ -1281,6 +1331,7 @@ func TestInsertTakesTheTextFromItsArgument(t *testing.T) { } func TestInsertReadsStdinWhenTheTextIsLeftOutOrADash(t *testing.T) { + t.Parallel() for _, args := range [][]string{ {"insert", "--session", "docs"}, {"insert", "--session", "docs", "-"}, } { @@ -1297,6 +1348,7 @@ func TestInsertReadsStdinWhenTheTextIsLeftOutOrADash(t *testing.T) { } func TestInsertJSONIsTheInsertedObject(t *testing.T) { + t.Parallel() f := newFakeLich(t, insertedBody) code, stdout, stderr := run(t, f, "insert", "--session", "docs", "--json", "hello") @@ -1309,6 +1361,7 @@ func TestInsertJSONIsTheInsertedObject(t *testing.T) { } func TestInsertNeedsAPlaceToGo(t *testing.T) { + t.Parallel() f := newFakeLich(t, insertedBody) for _, args := range [][]string{{"insert", "hello"}, {"insert", "--session", "docs", "a", "b"}} { @@ -1323,6 +1376,7 @@ func TestInsertNeedsAPlaceToGo(t *testing.T) { } func TestInsertWithNothingToReadStdinFrom(t *testing.T) { + t.Parallel() f := newFakeLich(t, insertedBody) code, _, stderr := run(t, f, "insert", "--session", "docs") @@ -1334,6 +1388,7 @@ func TestInsertWithNothingToReadStdinFrom(t *testing.T) { const focusedBody = `{"id":"s2","label":"docs","project":"lich"}` func TestFocusNamesTheSessionItBroughtUp(t *testing.T) { + t.Parallel() f := newFakeLich(t, focusedBody) code, stdout, stderr := run(t, f, "focus", "--project", "lich", "docs") @@ -1351,6 +1406,7 @@ func TestFocusNamesTheSessionItBroughtUp(t *testing.T) { } func TestFocusJSONIsTheFocusedObject(t *testing.T) { + t.Parallel() f := newFakeLich(t, focusedBody) code, stdout, stderr := run(t, f, "focus", "--json", "docs") @@ -1363,6 +1419,7 @@ func TestFocusJSONIsTheFocusedObject(t *testing.T) { } func TestFocusTakesExactlyOneSession(t *testing.T) { + t.Parallel() f := newFakeLich(t, focusedBody) for _, args := range [][]string{{"focus"}, {"focus", "docs", "extra"}} { diff --git a/internal/cli/control_test.go b/internal/cli/control_test.go index 60aa3f7e..f9a798e1 100644 --- a/internal/cli/control_test.go +++ b/internal/cli/control_test.go @@ -17,6 +17,7 @@ func controlled(state, action, value string) string { } func TestControlPostsTheActionAndItsArguments(t *testing.T) { + t.Parallel() f := newFakeLich(t, controlled(spawn.ControlDone, "command", "compact")) code, stdout, stderr := run(t, f, "control", "--project", "lich", "auth-fix", "command", "/compact", "keep the plan") if code != 0 { @@ -38,6 +39,7 @@ func TestControlPostsTheActionAndItsArguments(t *testing.T) { // Each outcome is an exit code a script can branch on, and a line that says // whether the command still goes through. func TestControlSaysHowFarTheCommandGot(t *testing.T) { + t.Parallel() tests := []struct { state, action, value string code int @@ -71,6 +73,7 @@ func TestControlSaysHowFarTheCommandGot(t *testing.T) { } func TestControlJSONKeepsTheExitCode(t *testing.T) { + t.Parallel() f := newFakeLich(t, controlled(spawn.ControlDelivered, "command", "compact")) code, stdout, _ := run(t, f, "control", "--json", "auth-fix", "command", "compact") if code != ExitPending { @@ -86,6 +89,7 @@ func TestControlJSONKeepsTheExitCode(t *testing.T) { } func TestControlRefusesAMalformedCall(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) for _, args := range [][]string{ {"control", "auth-fix"}, @@ -101,6 +105,7 @@ func TestControlRefusesAMalformedCall(t *testing.T) { } func TestControlReportsTheRefusal(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"error":"\"codex-run\" runs codex, and only a Claude Code session can be controlled"}`) f.status = 400 code, stdout, stderr := run(t, f, "control", "codex-run", "abort") @@ -114,6 +119,7 @@ func TestControlReportsTheRefusal(t *testing.T) { // A command still on its way is a result an agent reads, not a failed call. func TestMCPControlSessionReportsAPendingCommandAsAResult(t *testing.T) { + t.Parallel() f := newFakeLich(t, controlled(spawn.ControlDelivered, "command", "compact")) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": {"name":"control_session","arguments":{"session":"auth-fix","action":"command","value":"compact","args":"keep the plan"}}}`) diff --git a/internal/cli/cost_test.go b/internal/cli/cost_test.go index 66be3a3f..18cb9371 100644 --- a/internal/cli/cost_test.go +++ b/internal/cli/cost_test.go @@ -21,6 +21,7 @@ const costBody = `{"projects":[` + // it, because a sum over sessions lich could not price is a lower bound and a // reader has no other way to know. func TestCostPrintsTheTableAndWhatItLeavesOut(t *testing.T) { + t.Parallel() f := newFakeLich(t, costBody) code, stdout, stderr := run(t, f, "cost") @@ -48,6 +49,7 @@ func TestCostPrintsTheTableAndWhatItLeavesOut(t *testing.T) { // A total with nothing missing says so rather than staying silent: "no warning" // and "nothing to warn about" are the two readings an empty line leaves open. func TestACompleteTotalSaysItIsComplete(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"projects":[{"project":"lich","sessions":2,"unpriced":0,"source":"priced","costUsd":1}],`+ `"sessions":2,"unpriced":0,"priced":2,"source":"priced","costUsd":1,"readout":true}`) @@ -65,6 +67,7 @@ func TestACompleteTotalSaysItIsComplete(t *testing.T) { // a machine that spent nothing. The line says which it is, and where to change // it. func TestCostSaysWhenTheReadoutIsOff(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"projects":[{"project":"lich","sessions":2,"unpriced":2,"costUsd":0}],`+ `"sessions":2,"unpriced":2,"costUsd":0,"readout":false}`) @@ -76,6 +79,7 @@ func TestCostSaysWhenTheReadoutIsOff(t *testing.T) { } func TestCostReportsAnEmptyWorkspace(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"projects":[],"sessions":0,"unpriced":0,"costUsd":0,"readout":true}`) code, stdout, _ := run(t, f, "cost") @@ -88,6 +92,7 @@ func TestCostReportsAnEmptyWorkspace(t *testing.T) { // The filters ride to the store as they were typed; --since arrives as the // unix second the window opens at, which is the only form the query can use. func TestCostPassesItsFilters(t *testing.T) { + t.Parallel() f := newFakeLich(t, costBody) if code, _, stderr := run(t, f, "cost", "--project", "lich", "--provider", "codex", "--since", "7d"); code != 0 { @@ -107,6 +112,7 @@ func TestCostPassesItsFilters(t *testing.T) { // A window nothing can be made of stops before the call: a report over the // wrong stretch of time is a wrong number, and money is what this prints. func TestCostRefusesAWindowItCannotRead(t *testing.T) { + t.Parallel() for _, window := range []string{"last week", "7", "-3d", "0h", "d"} { f := newFakeLich(t, costBody) @@ -128,6 +134,7 @@ func TestCostRefusesAWindowItCannotRead(t *testing.T) { // flag included — so a script piping this anywhere gets the exclusion and the // arithmetic behind the money along with it. func TestCostEmitsJSON(t *testing.T) { + t.Parallel() f := newFakeLich(t, costBody) code, stdout, _ := run(t, f, "cost", "--json") @@ -149,6 +156,7 @@ func TestCostEmitsJSON(t *testing.T) { // the bound and whose arithmetic it is survive leaving lich. The exact figure // goes out, not the two places the table rounds to. func TestCostEmitsCSV(t *testing.T) { + t.Parallel() f := newFakeLich(t, costBody) code, stdout, _ := run(t, f, "cost", "--csv") @@ -166,6 +174,7 @@ func TestCostEmitsCSV(t *testing.T) { // Two formats is no format: there is one stdout and the answer would be half // of each. func TestCostRefusesTwoFormatsAtOnce(t *testing.T) { + t.Parallel() f := newFakeLich(t, costBody) code, _, stderr := run(t, f, "cost", "--json", "--csv") @@ -179,6 +188,7 @@ func TestCostRefusesTwoFormatsAtOnce(t *testing.T) { // table: the split line exists to warn that two kinds of arithmetic were summed, // and here they were not. func TestCostSplitsTheArithmeticOnlyWhenItIsMixed(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"projects":[{"project":"lich","sessions":2,"unpriced":0,"source":"priced","costUsd":1}],`+ `"sessions":2,"unpriced":0,"priced":2,"source":"priced","costUsd":1,"readout":true}`) @@ -195,6 +205,7 @@ func TestCostSplitsTheArithmeticOnlyWhenItIsMixed(t *testing.T) { // A row with no money to attribute reads as an absence, not as a blank cell a // reader has to guess at — and no rung is claimed for it. func TestCostMarksARowWithNoRung(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"projects":[{"project":"lich","sessions":2,"unpriced":2,"costUsd":0}],`+ `"sessions":2,"unpriced":2,"costUsd":0,"readout":true}`) diff --git a/internal/cli/doctor_test.go b/internal/cli/doctor_test.go index 631de592..b6d6ed5a 100644 --- a/internal/cli/doctor_test.go +++ b/internal/cli/doctor_test.go @@ -25,6 +25,7 @@ func doctorClient(t *testing.T, checks []doctor.Check, err error) (*client, *byt } func TestDoctorPrintsTheReportAndExitsZeroWhenNothingStopsALaunch(t *testing.T) { + t.Parallel() c, stdout, stderr := doctorClient(t, []doctor.Check{ {Name: "home", Status: doctor.OK, Detail: "/home/u/.config/lich is writable"}, {Name: "store", Status: doctor.Skip, Detail: "held by the running lich (pid 42)"}, @@ -44,6 +45,7 @@ func TestDoctorPrintsTheReportAndExitsZeroWhenNothingStopsALaunch(t *testing.T) } func TestDoctorExitsNonZeroOnACheckThatStopsALaunch(t *testing.T) { + t.Parallel() c, stdout, stderr := doctorClient(t, []doctor.Check{ {Name: "browser", Status: doctor.Fail, Detail: "no window beside the lich binary"}, }, nil) @@ -62,6 +64,7 @@ func TestDoctorExitsNonZeroOnACheckThatStopsALaunch(t *testing.T) { } func TestDoctorReportsAWalkItCouldNotStart(t *testing.T) { + t.Parallel() c, stdout, stderr := doctorClient(t, nil, errors.New("resolve config directory: no home")) if code := dispatch([]string{"doctor"}, c); code != 1 { @@ -76,6 +79,7 @@ func TestDoctorReportsAWalkItCouldNotStart(t *testing.T) { } func TestDoctorTakesNoArguments(t *testing.T) { + t.Parallel() c, stdout, stderr := doctorClient(t, []doctor.Check{{Name: "home", Status: doctor.OK}}, nil) if code := dispatch([]string{"doctor", "--everything"}, c); code != 1 { @@ -101,6 +105,7 @@ func TestDoctorTakesNoArguments(t *testing.T) { } func TestDoctorIsInTheHelp(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) _, stdout, _ := run(t, f, "help") if !strings.Contains(stdout, "lich doctor") { diff --git a/internal/cli/exe_ext_test.go b/internal/cli/exe_ext_test.go index dd8a5ace..31b2f6de 100644 --- a/internal/cli/exe_ext_test.go +++ b/internal/cli/exe_ext_test.go @@ -8,6 +8,7 @@ import ( // Windows runs only what carries .exe, so a companion beside lich.exe is // lich-native.exe. func TestCompanionFileNameCarriesTheExecutableExtension(t *testing.T) { + t.Parallel() want := "lich-native" if runtime.GOOS == "windows" { want = "lich-native.exe" diff --git a/internal/cli/folders_test.go b/internal/cli/folders_test.go index 9987b64a..04279d9a 100644 --- a/internal/cli/folders_test.go +++ b/internal/cli/folders_test.go @@ -10,6 +10,7 @@ import ( ) func TestMCPFileSessionPostsItsArgumentsInOrder(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"s2","project":"lich","label":"auth-fix","folder":"Apps","previous":""}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -35,6 +36,7 @@ func TestMCPFileSessionPostsItsArgumentsInOrder(t *testing.T) { // An empty folder is a real request (take the session out), so the tool // passes it through rather than reading it as missing. func TestMCPFileSessionIntoAnEmptyFolderTakesItOut(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"s1","project":"lich","label":"planner","folder":"","previous":"Apps"}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -55,6 +57,7 @@ func TestMCPFileSessionIntoAnEmptyFolderTakesItOut(t *testing.T) { // Because the empty name takes the session out, a call that left the folder // out entirely must not be read as one. func TestMCPFileSessionWithoutAFolderIsRefused(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -70,6 +73,7 @@ func TestMCPFileSessionWithoutAFolderIsRefused(t *testing.T) { } func TestMCPRenameFolderPostsItsArgumentsInOrder(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"project":"lich","from":"Apps","to":"Applications","sessions":["a","b"]}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -93,6 +97,7 @@ func TestMCPRenameFolderPostsItsArgumentsInOrder(t *testing.T) { } func TestMCPRenameFolderWithoutANewNameIsRefused(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -108,6 +113,7 @@ func TestMCPRenameFolderWithoutANewNameIsRefused(t *testing.T) { } func TestMCPColorFolderPostsItsArgumentsInOrder(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"project":"lich","folder":"Apps","color":"teal","sessions":["a","b"]}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -133,6 +139,7 @@ func TestMCPColorFolderPostsItsArgumentsInOrder(t *testing.T) { // An empty color clears the folder's, so a call that left the color out must // not be read as one. func TestMCPColorFolderWithoutAColorIsRefused(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -148,6 +155,7 @@ func TestMCPColorFolderWithoutAColorIsRefused(t *testing.T) { } func TestColorFolderNeedsAFolderAndAColor(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, _, stderr := run(t, f, "color-folder", "Apps") @@ -157,6 +165,7 @@ func TestColorFolderNeedsAFolderAndAColor(t *testing.T) { } func TestColoredTextClearingSaysSo(t *testing.T) { + t.Parallel() got := coloredText(spawn.Colored{Folder: "Apps", Sessions: []string{"a", "b"}}) if !strings.Contains(got, `Cleared the color of "a", "b" in folder "Apps".`) { t.Errorf("coloredText = %q", got) @@ -164,6 +173,7 @@ func TestColoredTextClearingSaysSo(t *testing.T) { } func TestMCPListFoldersReturnsThemAsJSON(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -182,6 +192,7 @@ func TestMCPListFoldersReturnsThemAsJSON(t *testing.T) { } func TestFoldersJSONIsAListEvenWhenThereAreNone(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, stdout, stderr := run(t, f, "folders", "--json") @@ -195,6 +206,7 @@ func TestFoldersJSONIsAListEvenWhenThereAreNone(t *testing.T) { } func TestFileTakesAtMostASessionAndAFolder(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, _, stderr := run(t, f, "file", "a", "b", "c") @@ -204,6 +216,7 @@ func TestFileTakesAtMostASessionAndAFolder(t *testing.T) { } func TestRenameFolderNeedsBothNames(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, _, stderr := run(t, f, "rename-folder", "Apps") @@ -213,6 +226,7 @@ func TestRenameFolderNeedsBothNames(t *testing.T) { } func TestFiledTextSaysWhereTheSessionIsNow(t *testing.T) { + t.Parallel() cases := map[string]struct { filed spawn.Filed want string @@ -231,6 +245,7 @@ func TestFiledTextSaysWhereTheSessionIsNow(t *testing.T) { } func TestRefiledTextTakingAFolderApartSaysItIsGone(t *testing.T) { + t.Parallel() got := refiledText(spawn.Refiled{From: "Apps", Sessions: []string{"a", "b"}}) if !strings.Contains(got, `Took folder "Apps" apart, taking out "a", "b".`) { t.Errorf("refiledText = %q", got) @@ -238,6 +253,7 @@ func TestRefiledTextTakingAFolderApartSaysItIsGone(t *testing.T) { } func TestFoldersWithNoneSaysSo(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[]`) code, stdout, stderr := run(t, f, "folders") @@ -247,6 +263,7 @@ func TestFoldersWithNoneSaysSo(t *testing.T) { } func TestFoldersTakesNoArguments(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[]`) code, _, stderr := run(t, f, "folders", "Apps") @@ -260,6 +277,7 @@ func TestFoldersTakesNoArguments(t *testing.T) { // --json hands a script the whole answer, not the sentence the text form prints. func TestFileAndRenameFolderPrintJSON(t *testing.T) { + t.Parallel() cases := map[string]struct { body string args []string @@ -304,6 +322,7 @@ func TestFileAndRenameFolderPrintJSON(t *testing.T) { // A refusal from lich reaches the caller as a failed exit with lich's own words, // on every folder command. func TestFolderCommandsReportWhatLichRefused(t *testing.T) { + t.Parallel() for _, args := range [][]string{ {"folders"}, {"file", "Apps"}, @@ -325,6 +344,7 @@ func TestFolderCommandsReportWhatLichRefused(t *testing.T) { // The same refusal, reaching an agent through the MCP tools, is a failed tool // result carrying lich's words rather than a result that reads as success. func TestMCPFolderToolsReportWhatLichRefused(t *testing.T) { + t.Parallel() for name, arguments := range map[string]string{ "list_folders": `{}`, "file_session": `{"folder":"Apps"}`, diff --git a/internal/cli/gallery_test.go b/internal/cli/gallery_test.go index 631796a9..b5476642 100644 --- a/internal/cli/gallery_test.go +++ b/internal/cli/gallery_test.go @@ -24,6 +24,7 @@ func runGallery(t *testing.T, env map[string]string, args ...string) (int, strin } func TestGalleryLaunchesThePinnedBuildWithNoBackend(t *testing.T) { + t.Parallel() pinned := filepath.Join(t.TempDir(), galleryBinary) if err := os.WriteFile(pinned, nil, 0o755); err != nil { t.Fatal(err) @@ -42,6 +43,7 @@ func TestGalleryLaunchesThePinnedBuildWithNoBackend(t *testing.T) { } func TestGalleryWithAMissingPinStartsNothing(t *testing.T) { + t.Parallel() missing := filepath.Join(t.TempDir(), galleryBinary) code, _, stderr, starts := runGallery(t, map[string]string{galleryEnv: missing}) @@ -56,6 +58,7 @@ func TestGalleryWithAMissingPinStartsNothing(t *testing.T) { // With no pin, the gallery is looked for beside the lich binary, which in a // test is the test binary, with no lich-gallery beside it. func TestGalleryWithNothingBesideLichSaysHowToBuildIt(t *testing.T) { + t.Parallel() code, _, stderr, starts := runGallery(t, nil) for _, want := range []string{"no " + galleryBinary + " beside", "./cmd/ui-gallery", galleryEnv} { if !strings.Contains(stderr, want) { @@ -68,6 +71,7 @@ func TestGalleryWithNothingBesideLichSaysHowToBuildIt(t *testing.T) { } func TestGalleryTakesNoArguments(t *testing.T) { + t.Parallel() code, _, stderr, starts := runGallery(t, nil, "now") if code != 1 || !strings.Contains(stderr, "usage: lich gallery") { t.Fatalf("exit = %d, stderr = %q, want the usage line", code, stderr) diff --git a/internal/cli/hangup_test.go b/internal/cli/hangup_test.go index c91026a2..caa51fd9 100644 --- a/internal/cli/hangup_test.go +++ b/internal/cli/hangup_test.go @@ -103,6 +103,7 @@ func answerAndCheckTheInbox(t *testing.T, env func(string) string, svc *relay.Se } func TestAWaitKilledMidCollectLeavesTheResultInTheInbox(t *testing.T) { + t.Parallel() env, term, svc, gate := wiredRelay(t) ticket := openErrand(t, env, term) @@ -134,6 +135,7 @@ func TestAWaitKilledMidCollectLeavesTheResultInTheInbox(t *testing.T) { } func TestACancelledWaitForAnswerLeavesTheResultInTheInbox(t *testing.T) { + t.Parallel() env, term, svc, gate := wiredRelay(t) ticket := openErrand(t, env, term) @@ -195,6 +197,7 @@ func (b *lockedBuffer) String() string { } func TestCancelsMatchesOnlyItsOwnRequest(t *testing.T) { + t.Parallel() id := json.RawMessage(`7`) for _, tt := range []struct { name string @@ -222,6 +225,7 @@ func TestCancelsMatchesOnlyItsOwnRequest(t *testing.T) { // A cancellation for some other request, or one that cannot be read, must leave // the call in flight alone: dropping it would lose a wait the client still wants. func TestAStrayCancellationLeavesTheCallInFlightAnswered(t *testing.T) { + t.Parallel() env, term, _, gate := wiredRelay(t) openErrand(t, env, term) diff --git a/internal/cli/mcp_test.go b/internal/cli/mcp_test.go index 8846c8ee..096c85b1 100644 --- a/internal/cli/mcp_test.go +++ b/internal/cli/mcp_test.go @@ -70,6 +70,7 @@ func textOf(t *testing.T, reply jsonRPC) (string, bool) { } func TestMCPHandshakeAdvertisesTools(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26"}}`) @@ -115,6 +116,7 @@ func TestMCPHandshakeAdvertisesTools(t *testing.T) { } func TestMCPHandshakeFallsBackToItsOwnVersion(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}`) @@ -131,6 +133,7 @@ func TestMCPHandshakeFallsBackToItsOwnVersion(t *testing.T) { // A literal would make every install of every release report the same thing, // which is the one number a bug report needs from the other side. func TestMCPHandshakeReportsTheBuildVersion(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) var stdout, stderr bytes.Buffer @@ -165,6 +168,7 @@ func TestMCPHandshakeReportsTheBuildVersion(t *testing.T) { // object per line, so a decoder that cannot read one has lost the stream and // carrying on would answer the wrong message with the wrong id. func TestMCPUnreadableInputEndsTheServer(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) var stdout, stderr bytes.Buffer @@ -187,6 +191,7 @@ func TestMCPUnreadableInputEndsTheServer(t *testing.T) { } func TestMCPNotificationsGetNoReply(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, @@ -202,6 +207,7 @@ func TestMCPNotificationsGetNoReply(t *testing.T) { } func TestMCPListsEveryTool(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/list"}`) @@ -270,6 +276,7 @@ func TestMCPListsEveryTool(t *testing.T) { } func TestMCPSendToSessionReturnsTheAnswer(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"answered","answer":"3 failures"}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -294,6 +301,7 @@ func TestMCPSendToSessionReturnsTheAnswer(t *testing.T) { } func TestMCPSendPointsAtTheTicketWhenItRunsOut(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"pending","answer":""}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -313,6 +321,7 @@ func TestMCPSendPointsAtTheTicketWhenItRunsOut(t *testing.T) { // Without a ticket, wait_for_answer is the drain: one call collects every // result the nudge announced, over relay.Collect. func TestMCPWaitWithoutATicketCollectsEverything(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"results":[ {"ticket":"t1","target":"auth","status":"answered","answer":"all green"}, {"ticket":"t2","target":"api","status":"unanswered","answer":""}, @@ -348,6 +357,7 @@ func TestMCPWaitWithoutATicketCollectsEverything(t *testing.T) { // A worker closed by someone other than its caller is filed as stopped, and a // drain has to say so: a result with no words reads as nothing at all. func TestMCPWaitWithoutATicketSaysAStoppedWorkerStopped(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"results":[ {"ticket":"t1","target":"docs","status":"stopped","answer":""}],"open":[]}`) @@ -364,6 +374,7 @@ func TestMCPWaitWithoutATicketSaysAStoppedWorkerStopped(t *testing.T) { } func TestMCPWaitWithNothingToCollectSaysSo(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"results":[],"open":[]}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -379,6 +390,7 @@ func TestMCPWaitWithNothingToCollectSaysSo(t *testing.T) { } func TestMCPWaitWithATicketWaitsOnIt(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"answered","answer":"done"}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -399,6 +411,7 @@ func TestMCPWaitWithATicketWaitsOnIt(t *testing.T) { // A worker its caller closed was stopped on purpose: the outcome says so and // sends nobody to a card that is gone. func TestMCPWaitOnAStoppedWorkerSaysItWasStopped(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"Session 3","status":"stopped"}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -414,6 +427,7 @@ func TestMCPWaitOnAStoppedWorkerSaysItWasStopped(t *testing.T) { } func TestMCPListSessionsReturnsThemAsJSON(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[{"label":"docs","project":"lich","kind":"codex","state":"waiting"}, {"label":"api","project":"lich","kind":"crush"}]`) @@ -443,6 +457,7 @@ func TestMCPListSessionsReturnsThemAsJSON(t *testing.T) { } func TestMCPReplyToSession(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -462,6 +477,7 @@ func TestMCPReplyToSession(t *testing.T) { // told which requests are open and what each asked, or it has nothing to retry // with. func TestMCPReplyWithoutATicketCarriesTheRefusal(t *testing.T) { + t.Parallel() refusal := "2 requests are open against this session, and an answer that names no ticket " + "would close the wrong one. Name the ticket the answer belongs to:\n" + " lich reply a1b2c3d4 \"\" — run the tests\n" + @@ -488,6 +504,7 @@ func TestMCPReplyWithoutATicketCarriesTheRefusal(t *testing.T) { } func TestMCPAFailedCallIsAToolErrorNotAProtocolError(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"error":"no live session named \"ghost\""}`) f.status = 500 @@ -507,6 +524,7 @@ func TestMCPAFailedCallIsAToolErrorNotAProtocolError(t *testing.T) { } func TestMCPRejectsUnknownMethodsAndTools(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, @@ -528,6 +546,7 @@ func TestMCPRejectsUnknownMethodsAndTools(t *testing.T) { } func TestMCPReadsLooseArgumentTypes(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"answered","answer":"ok"}`) // A model that sends the timeout as a string should not lose its turn. @@ -544,6 +563,7 @@ func TestMCPReadsLooseArgumentTypes(t *testing.T) { // seconds, so a caller asking for more than the cap gets the cap — the first // real run asked for 120 and then 180, and both stopped waiting. func TestMCPCapsTheWaitBelowTheClientsBackgroundThreshold(t *testing.T) { + t.Parallel() tests := []struct { name string asked any @@ -581,6 +601,7 @@ func TestMCPCapsTheWaitBelowTheClientsBackgroundThreshold(t *testing.T) { } func TestMCPStopsCleanlyAtEndOfInput(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) var stdout, stderr bytes.Buffer @@ -594,6 +615,7 @@ func TestMCPStopsCleanlyAtEndOfInput(t *testing.T) { } func TestMCPOpenSessionReturnsTheNamesItIsAddressedBy(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"9f8e","projectId":"p1","project":"lich","label":"auth-fix", "name":"auth-fix-9f8e","kind":"claude","path":"/wt/auth-fix","nextSeq":5}`) @@ -629,6 +651,7 @@ func TestMCPOpenSessionReturnsTheNamesItIsAddressedBy(t *testing.T) { // point of the prompt argument, since two calls per worker is what made the // harness's own subagents the cheaper thing to reach for. func TestMCPOpenSessionHandsOverTheTaskItCameWith(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"9f8e","projectId":"p1","project":"lich","label":"auth-fix", "name":"auth-fix-9f8e","kind":"claude","path":"/wt/auth-fix","nextSeq":5}`) f.answers = map[string]answer{ @@ -664,6 +687,7 @@ func TestMCPOpenSessionHandsOverTheTaskItCameWith(t *testing.T) { // answer: reporting a failed call would leave an agent that opened three // workers believing it has none. func TestMCPOpenSessionKeepsTheSessionWhenTheTaskDoesNotLand(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"9f8e","projectId":"p1","project":"lich","label":"auth-fix", "name":"auth-fix-9f8e","kind":"claude","path":"/wt/auth-fix","nextSeq":5}`) f.answers = map[string]answer{ @@ -685,6 +709,7 @@ func TestMCPOpenSessionKeepsTheSessionWhenTheTaskDoesNotLand(t *testing.T) { } func TestMCPOpenSessionDefaultsEveryArgument(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"9f8e","projectId":"p1","project":"lich","label":"Session 4", "name":"lich-9f8e","kind":"claude","path":"","nextSeq":5}`) @@ -702,6 +727,7 @@ func TestMCPOpenSessionDefaultsEveryArgument(t *testing.T) { } func TestMCPSendReportsATaskNobodyRead(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"unread"}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -721,6 +747,7 @@ func TestMCPSendReportsATaskNobodyRead(t *testing.T) { } func TestMCPSendReportsATaskThatNeverReachedAPrompt(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"ticket":"a1b2c3d4","target":"docs","status":"undelivered"}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -741,6 +768,7 @@ func TestMCPSendReportsATaskThatNeverReachedAPrompt(t *testing.T) { } func TestMCPCloseSessionPassesTheWorktreeAnswer(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"9f8e","projectId":"p1","project":"lich","label":"auth-fix", "worktree":"/wt/auth-fix","removed":true}`) @@ -758,6 +786,7 @@ func TestMCPCloseSessionPassesTheWorktreeAnswer(t *testing.T) { } func TestMCPRenameSessionPassesTheTargetAndTheName(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"9f8e","project":"lich","label":"the login bug","previous":"auth-fix"}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -781,6 +810,7 @@ func TestMCPRenameSessionPassesTheTargetAndTheName(t *testing.T) { // target is what the app reads as "the caller's own" — so it must be sent, not // dropped into the label's slot. func TestMCPRenameSessionWithoutATargetRenamesItsOwn(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"id":"s1","project":"lich","label":"planner","previous":"Session 1"}`) speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -797,6 +827,7 @@ func TestMCPRenameSessionWithoutATargetRenamesItsOwn(t *testing.T) { // counts: the boolean, or the one string that spells it. A model that sends // "false" — or "yes", which reads like consent and is not the word — must not. func TestMCPForceIsOnlyTrueWhenItSaysTrue(t *testing.T) { + t.Parallel() for sent, want := range map[string]bool{ `true`: true, `"true"`: true, @@ -815,6 +846,7 @@ func TestMCPForceIsOnlyTrueWhenItSaysTrue(t *testing.T) { } func TestMCPListWorktreesReturnsThemAsJSON(t *testing.T) { + t.Parallel() f := newFakeLich(t, `[{"name":"auth-fix","path":"/wt/auth-fix","dirty":true,"sessions":["auth-fix"]}]`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": diff --git a/internal/cli/native_test.go b/internal/cli/native_test.go index b712a423..476d0236 100644 --- a/internal/cli/native_test.go +++ b/internal/cli/native_test.go @@ -77,6 +77,7 @@ func runNative(t *testing.T, env map[string]string, running func() (*singleton.I } func TestNativeClosesTheWebWindowAndOpensOnTheRunningLich(t *testing.T) { + t.Parallel() srv, bodies := windowedLich(t, http.StatusOK, "null") native := nativeBuild(t) env := map[string]string{"LICH_PORT": portOf(srv), "LICH_TOKEN": "tok", nativeEnv: native} @@ -104,6 +105,7 @@ func TestNativeClosesTheWebWindowAndOpensOnTheRunningLich(t *testing.T) { // A lich with its window already closed (the tray, `lich native` run twice) // answers CloseWindow with ErrNoWindow's text, and that is nothing to stop for. func TestNativeOpensWhenNoWebWindowIsOpen(t *testing.T) { + t.Parallel() srv, _ := windowedLich(t, http.StatusInternalServerError, `{"error":"no lich window is open"}`) env := map[string]string{"LICH_PORT": portOf(srv), "LICH_TOKEN": "tok", nativeEnv: nativeBuild(t)} @@ -114,6 +116,7 @@ func TestNativeOpensWhenNoWebWindowIsOpen(t *testing.T) { } func TestNativeStopsWhenTheWebWindowWillNotClose(t *testing.T) { + t.Parallel() srv, _ := windowedLich(t, http.StatusInternalServerError, `{"error":"window: still starting"}`) env := map[string]string{"LICH_PORT": portOf(srv), "LICH_TOKEN": "tok", nativeEnv: nativeBuild(t)} @@ -129,6 +132,7 @@ func TestNativeStopsWhenTheWebWindowWillNotClose(t *testing.T) { // With no lich running, one is started without a window, and the native window // opens on it once its runtime file names a port that answers. func TestNativeStartsLichWithNoWindowWhenNoneRuns(t *testing.T) { + t.Parallel() srv, _ := windowedLich(t, http.StatusInternalServerError, `{"error":"no lich window is open"}`) native := nativeBuild(t) var started *singleton.Info @@ -149,6 +153,7 @@ func TestNativeStartsLichWithNoWindowWhenNoneRuns(t *testing.T) { } func TestNativeWithoutABuildStartsNothing(t *testing.T) { + t.Parallel() srv, bodies := windowedLich(t, http.StatusOK, "null") missing := filepath.Join(t.TempDir(), nativeBinary) env := map[string]string{"LICH_PORT": portOf(srv), "LICH_TOKEN": "tok", nativeEnv: missing} @@ -163,6 +168,7 @@ func TestNativeWithoutABuildStartsNothing(t *testing.T) { } func TestNativeTakesNoArguments(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, _, stderr := run(t, f, "native", "now") if code != 1 || !strings.Contains(stderr, "usage: lich native") { @@ -173,6 +179,7 @@ func TestNativeTakesNoArguments(t *testing.T) { // With no pin, the native window is looked for beside the lich binary, which in // a test is the test binary, with no lich-native beside it. func TestNativeWithNothingBesideLichSaysWhereToGetIt(t *testing.T) { + t.Parallel() srv, _ := windowedLich(t, http.StatusOK, "null") env := map[string]string{"LICH_PORT": portOf(srv), "LICH_TOKEN": "tok"} @@ -186,6 +193,7 @@ func TestNativeWithNothingBesideLichSaysWhereToGetIt(t *testing.T) { } func TestAwaitBackendGivesUpOnALichThatNeverAnswers(t *testing.T) { + t.Parallel() c := &client{env: func(string) string { return "" }, running: noInstance} if _, _, err := c.awaitBackend(3 * quitPoll); err == nil || !strings.Contains(err.Error(), "did not answer") { t.Fatalf("err = %v, want the timeout", err) diff --git a/internal/cli/nowait_test.go b/internal/cli/nowait_test.go index 7603c030..5e5fc481 100644 --- a/internal/cli/nowait_test.go +++ b/internal/cli/nowait_test.go @@ -14,6 +14,7 @@ import ( // would stall the work. func TestWaitNoWaitCollectsWhatIsReadyWithoutHoldingTheLine(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"results":[],"open":["docs"]}`) code, stdout, _ := run(t, f, "wait", "--no-wait") @@ -33,6 +34,7 @@ func TestWaitNoWaitCollectsWhatIsReadyWithoutHoldingTheLine(t *testing.T) { } func TestWaitNoWaitTakesNoTicket(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, _, stderr := run(t, f, "wait", "--no-wait", "a1b2c3d4") @@ -45,6 +47,7 @@ func TestWaitNoWaitTakesNoTicket(t *testing.T) { } func TestMCPWaitNoWaitCollectsWhatIsReady(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"results":[{"ticket":"t1","target":"auth","status":"answered","answer":"all green"}],"open":[]}`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -63,6 +66,7 @@ func TestMCPWaitNoWaitCollectsWhatIsReady(t *testing.T) { } func TestMCPWaitNoWaitRefusesATicket(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -81,6 +85,7 @@ func TestMCPWaitNoWaitRefusesATicket(t *testing.T) { // have to name the no-wait look, where to take it, and that looking again in a // loop is the polling they forbid. func TestMCPInstructionsTellWhenToLookWithoutWaiting(t *testing.T) { + t.Parallel() for _, want := range []string{"no_wait", "before delegating more", "before the final synthesis", "in a loop"} { if !strings.Contains(mcpInstructions(prompt.English), want) { t.Errorf("instructions are missing %q", want) @@ -89,6 +94,7 @@ func TestMCPInstructionsTellWhenToLookWithoutWaiting(t *testing.T) { } func TestCollectNowOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, term, _, _ := wiredRelay(t) openErrand(t, env, term) diff --git a/internal/cli/private_test.go b/internal/cli/private_test.go index d7807393..174ad8d9 100644 --- a/internal/cli/private_test.go +++ b/internal/cli/private_test.go @@ -8,6 +8,7 @@ import ( const privatePending = `{"ticket":"a1b2c3d4","target":"docs","status":"pending","private":true}` func TestSendPrivateCallsSendPrivate(t *testing.T) { + t.Parallel() f := newFakeLich(t, privatePending) code, stdout, _ := run(t, f, "send", "--private", "docs", "long errand") @@ -30,6 +31,7 @@ func TestSendPrivateCallsSendPrivate(t *testing.T) { } func TestOpenPrivateHandsOverPrivately(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) f.answers = map[string]answer{"relay.SendPrivate": {status: 200, body: privatePending}} @@ -43,6 +45,7 @@ func TestOpenPrivateHandsOverPrivately(t *testing.T) { } func TestOpenPrivateWithoutAPromptIsRefusedBeforeOpening(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) code, _, stderr := run(t, f, "open", "--worktree", "auth-fix", "--private") @@ -58,6 +61,7 @@ func TestOpenPrivateWithoutAPromptIsRefusedBeforeOpening(t *testing.T) { } func TestMCPSendPrivateCallsSendPrivate(t *testing.T) { + t.Parallel() f := newFakeLich(t, privatePending) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -81,6 +85,7 @@ func TestMCPSendPrivateCallsSendPrivate(t *testing.T) { } func TestMCPOpenPrivateHandsOverPrivately(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) f.answers = map[string]answer{"relay.SendPrivate": {status: 200, body: privatePending}} @@ -96,6 +101,7 @@ func TestMCPOpenPrivateHandsOverPrivately(t *testing.T) { } func TestMCPOpenPrivateWithoutAPromptIsRefusedBeforeOpening(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) replies := speak(t, f, `{"jsonrpc":"2.0","id":1,"method":"tools/call","params": @@ -113,6 +119,7 @@ func TestMCPOpenPrivateWithoutAPromptIsRefusedBeforeOpening(t *testing.T) { // A private hand-off that failed must not be retried in the open: the recovery // it names keeps the errand private. func TestAFailedPrivateHandOffNamesAPrivateRetry(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) f.answers = map[string]answer{"relay.SendPrivate": {status: 500, body: "boom"}} diff --git a/internal/cli/quit_test.go b/internal/cli/quit_test.go index b7f4437d..99b1cc7b 100644 --- a/internal/cli/quit_test.go +++ b/internal/cli/quit_test.go @@ -48,6 +48,7 @@ func runQuit(t *testing.T, srv *httptest.Server) (int, string, string) { } func TestQuitReturnsOnceLichIsGone(t *testing.T) { + t.Parallel() srv, bodies := quittingLich(t, func(w http.ResponseWriter) bool { _, _ = io.WriteString(w, "null") return true @@ -70,6 +71,7 @@ func TestQuitReturnsOnceLichIsGone(t *testing.T) { // The process can exit before its reply leaves: the call fails, and lich is // gone, which is what was asked. func TestQuitWhoseReplyIsLostToTheExitSucceeds(t *testing.T) { + t.Parallel() srv, _ := quittingLich(t, func(w http.ResponseWriter) bool { conn, _, err := w.(http.Hijacker).Hijack() if err != nil { @@ -85,6 +87,7 @@ func TestQuitWhoseReplyIsLostToTheExitSucceeds(t *testing.T) { } func TestQuitRefusedByARunningLichFails(t *testing.T) { + t.Parallel() srv, _ := quittingLich(t, func(w http.ResponseWriter) bool { w.WriteHeader(http.StatusInternalServerError) _, _ = io.WriteString(w, `{"error":"quit: lich is still starting"}`) @@ -100,6 +103,7 @@ func TestQuitRefusedByARunningLichFails(t *testing.T) { } func TestQuitTakesNoArguments(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) code, _, stderr := run(t, f, "quit", "now") if code != 1 || !strings.Contains(stderr, "usage: lich quit") { @@ -111,6 +115,7 @@ func TestQuitTakesNoArguments(t *testing.T) { } func TestGoneGivesUpOnAPortStillListening(t *testing.T) { + t.Parallel() srv := httptest.NewServer(http.NotFoundHandler()) defer srv.Close() if gone(strconv.Itoa(srv.Listener.Addr().(*net.TCPAddr).Port), 3*quitPoll) { diff --git a/internal/cli/rage_test.go b/internal/cli/rage_test.go index a146ccf4..a3feec41 100644 --- a/internal/cli/rage_test.go +++ b/internal/cli/rage_test.go @@ -58,6 +58,7 @@ func TestRageWritesATimestampedBundleAndSaysWhatItHolds(t *testing.T) { } func TestRageWritesWhereItWasTold(t *testing.T) { + t.Parallel() dir := t.TempDir() path := filepath.Join(dir, "report.tar.gz") var roots []string @@ -80,6 +81,7 @@ func TestRageWritesWhereItWasTold(t *testing.T) { } func TestRageLeavesNoHalfWrittenBundleBehind(t *testing.T) { + t.Parallel() dir := t.TempDir() path := filepath.Join(dir, "report.tar.gz") var roots []string @@ -113,6 +115,7 @@ func TestRageTakesNoArguments(t *testing.T) { } func TestRageIsInTheHelp(t *testing.T) { + t.Parallel() f := newFakeLich(t, `null`) _, stdout, _ := run(t, f, "help") if !strings.Contains(stdout, "lich rage") { @@ -121,6 +124,7 @@ func TestRageIsInTheHelp(t *testing.T) { } func TestArchiveRootStripsBothExtensions(t *testing.T) { + t.Parallel() cases := map[string]string{ "lich-rage-20260810-150405.tar.gz": "lich-rage-20260810-150405", "/tmp/report.tar.gz": "report", diff --git a/internal/cli/rpcshape_test.go b/internal/cli/rpcshape_test.go index 713bade4..0bd432e1 100644 --- a/internal/cli/rpcshape_test.go +++ b/internal/cli/rpcshape_test.go @@ -41,6 +41,7 @@ var cliServices = map[string]reflect.Type{ // like the version probe, has nothing to misalign and may take nothing; the day // it takes something, that something is the object. func TestEveryCallTheCLIPostsTakesOneOptionsObject(t *testing.T) { + t.Parallel() methods := postedMethods(t) if !methods["spawn.Open"] { t.Fatalf("found %v, want spawn.Open among them: the scan no longer sees the CLI's calls", methods) diff --git a/internal/cli/subagent_test.go b/internal/cli/subagent_test.go index 6f11e59d..102ee4f5 100644 --- a/internal/cli/subagent_test.go +++ b/internal/cli/subagent_test.go @@ -12,6 +12,7 @@ import ( const subagentPending = `{"ticket":"a1b2c3d4","target":"Session 4","status":"pending","answer":""}` func TestOpenSubagentOpensAndHandsOverAsASubagent(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) f.answers = map[string]answer{"relay.SendSubagent": {status: 200, body: subagentPending}} @@ -39,6 +40,7 @@ func TestOpenSubagentOpensAndHandsOverAsASubagent(t *testing.T) { } func TestOpenSubagentIsRefusedBeforeOpening(t *testing.T) { + t.Parallel() cases := map[string][]string{ "--prompt": {"open", "--subagent"}, "--project": {"open", "--subagent", "--prompt", "x", "--project", "lich"}, @@ -60,6 +62,7 @@ func TestOpenSubagentIsRefusedBeforeOpening(t *testing.T) { } func TestOpenSubagentNeedsACallingSession(t *testing.T) { + t.Parallel() f := newFakeLich(t, openedBody) outside := func(key string) string { if key == "LICH_SESSION_ID" { diff --git a/internal/cli/text_lang_test.go b/internal/cli/text_lang_test.go index 544da355..0a9848bd 100644 --- a/internal/cli/text_lang_test.go +++ b/internal/cli/text_lang_test.go @@ -13,6 +13,7 @@ import ( // Every text renders in every language with its arguments in place and the // literals agents match on untouched. func TestEveryTextRendersInEveryLanguage(t *testing.T) { + t.Parallel() opened := spawn.Session{Label: "docs", Name: "docs-1", Kind: "claude", Project: "p", Path: "/w", Confined: true} collected := relay.Collected{ Results: []relay.Result{ @@ -84,6 +85,7 @@ func TestEveryTextRendersInEveryLanguage(t *testing.T) { // The command line and the MCP server run outside lich, so the language comes // from the variable lich exported when it spawned the session. func TestTheClientReadsThePromptLanguageFromTheEnvironment(t *testing.T) { + t.Parallel() for tag, want := range map[string]prompt.Lang{ "": prompt.English, "en": prompt.English, @@ -103,6 +105,7 @@ func TestTheClientReadsThePromptLanguageFromTheEnvironment(t *testing.T) { } func TestTheHandshakeCarriesTheInstructionsInTheClientsLanguage(t *testing.T) { + t.Parallel() for _, lang := range prompt.Langs { got := mcpHandshake(nil, "test", lang)["instructions"] if got != mcpInstructions(lang) { @@ -112,6 +115,7 @@ func TestTheHandshakeCarriesTheInstructionsInTheClientsLanguage(t *testing.T) { } func TestTheClientReadsSpanishFromTheEnvironment(t *testing.T) { + t.Parallel() c := &client{env: func(key string) string { if key == prompt.EnvVar { return "es" @@ -124,6 +128,7 @@ func TestTheClientReadsSpanishFromTheEnvironment(t *testing.T) { } func TestTheClientReadsJapaneseFromTheEnvironment(t *testing.T) { + t.Parallel() c := &client{env: func(key string) string { if key == prompt.EnvVar { return "ja" @@ -136,6 +141,7 @@ func TestTheClientReadsJapaneseFromTheEnvironment(t *testing.T) { } func TestTheClientReadsKoreanFromTheEnvironment(t *testing.T) { + t.Parallel() c := &client{env: func(key string) string { if key == prompt.EnvVar { return "ko" @@ -148,6 +154,7 @@ func TestTheClientReadsKoreanFromTheEnvironment(t *testing.T) { } func TestTheClientReadsRussianFromTheEnvironment(t *testing.T) { + t.Parallel() c := &client{env: func(key string) string { if key == prompt.EnvVar { return "ru" diff --git a/internal/cli/theme_test.go b/internal/cli/theme_test.go index 407081d3..02f064d3 100644 --- a/internal/cli/theme_test.go +++ b/internal/cli/theme_test.go @@ -22,6 +22,7 @@ func themeLich(t *testing.T, selected string) *fakeLich { } func TestThemeListMarksTheThemeInUse(t *testing.T) { + t.Parallel() code, stdout, stderr := run(t, themeLich(t, "everforest-dark"), "theme", "list") if code != 0 { t.Fatalf("exit = %d, stderr = %q", code, stderr) @@ -36,6 +37,7 @@ func TestThemeListMarksTheThemeInUse(t *testing.T) { } func TestThemeListJSONNamesPairsAndTheOneInUse(t *testing.T) { + t.Parallel() code, stdout, _ := run(t, themeLich(t, "system"), "theme", "--json", "list") if code != 0 { t.Fatalf("exit = %d", code) @@ -48,6 +50,7 @@ func TestThemeListJSONNamesPairsAndTheOneInUse(t *testing.T) { } func TestThemeSwitchSelectsTheTheme(t *testing.T) { + t.Parallel() f := newFakeLich(t, "null") code, stdout, stderr := run(t, f, "theme", "switch", "everforest-light") if code != 0 { @@ -64,6 +67,7 @@ func TestThemeSwitchSelectsTheTheme(t *testing.T) { } func TestThemeSwitchReportsAThemeNotInstalled(t *testing.T) { + t.Parallel() f := newFakeLich(t, `{"error":"no theme \"nord\"; lich theme list shows the installed ones"}`) f.status = http.StatusInternalServerError code, _, stderr := run(t, f, "theme", "switch", "nord") @@ -73,6 +77,7 @@ func TestThemeSwitchReportsAThemeNotInstalled(t *testing.T) { } func TestThemeTakesListOrSwitchWithOneTheme(t *testing.T) { + t.Parallel() f := newFakeLich(t, "null") for _, args := range [][]string{{"theme"}, {"theme", "use", "x"}, {"theme", "switch"}, {"theme", "list", "x"}} { code, _, stderr := run(t, f, args...) diff --git a/internal/cli/wire_test.go b/internal/cli/wire_test.go index ecfce522..f0f23d10 100644 --- a/internal/cli/wire_test.go +++ b/internal/cli/wire_test.go @@ -119,6 +119,7 @@ func sessionEnv(port string) func(string) string { } func TestSessionsOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, _ := wiredLich(t) var stdout, stderr bytes.Buffer @@ -137,6 +138,7 @@ func TestSessionsOverTheRealDispatcher(t *testing.T) { // the text is pasted at the other session's prompt, bracketed so its newlines // are not submissions, and nothing presses Enter behind it. func TestInsertOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, term := wiredLich(t) var stdout, stderr bytes.Buffer @@ -149,6 +151,7 @@ func TestInsertOverTheRealDispatcher(t *testing.T) { } func TestFocusOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, _ := wiredLich(t) var stdout, stderr bytes.Buffer @@ -168,6 +171,7 @@ func TestFocusOverTheRealDispatcher(t *testing.T) { } func TestSendAndReplyOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, term := wiredLich(t) var stdout, stderr bytes.Buffer @@ -198,6 +202,7 @@ func TestSendAndReplyOverTheRealDispatcher(t *testing.T) { // waiting, a no-ticket wait from the same session finds nothing, and the ticket // still collects it. func TestSendPrivateOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, term := wiredLich(t) var stdout, stderr bytes.Buffer @@ -461,6 +466,7 @@ func wiredSpawn(t *testing.T, git *spawnGit) (func(string) string, *spawnStore, // spawn.Open's fields — a misnamed tag here would otherwise drop the option // in silence and open the session on its default. func TestOpenOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, rows, term := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -492,6 +498,7 @@ func TestOpenOverTheRealDispatcher(t *testing.T) { // TestOpenUltracodeOverTheRealDispatcher proves the trailing boolean lands on // spawn.Open's ultracode, the one argument the codex case above cannot carry. func TestOpenUltracodeOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, rows, term := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -513,6 +520,7 @@ func TestOpenUltracodeOverTheRealDispatcher(t *testing.T) { // TestOpenSubagentOverTheRealDispatcher proves the options `lich open // --subagent` posts land on spawn.OpenSubagent's fields. func TestOpenSubagentOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, rows, term := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -543,6 +551,7 @@ func TestOpenSubagentOverTheRealDispatcher(t *testing.T) { // resolves no session at all, and a --force that misses its own is a removal // refused rather than the one the caller asked for. func TestCloseOverTheRealDispatcher(t *testing.T) { + t.Parallel() git := &spawnGit{dirty: true} env, _, term := wiredSpawn(t, git) @@ -566,6 +575,7 @@ func TestCloseOverTheRealDispatcher(t *testing.T) { // on spawn.Rename's fields. Target and label are two strings side by side, and // swapped the command renames the wrong session to the name of the right one. func TestRenameOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, rows, _ := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -585,6 +595,7 @@ func TestRenameOverTheRealDispatcher(t *testing.T) { // form reads the label rather than a session: LICH_SESSION_ID is the target, and // a client that posted the name in the target's slot would resolve no session. func TestRenameWithoutATargetRenamesTheCallersOwnSession(t *testing.T) { + t.Parallel() env, rows, _ := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -600,6 +611,7 @@ func TestRenameWithoutATargetRenamesTheCallersOwnSession(t *testing.T) { // on spawn.Control's fields: the action, the name and its arguments are strings // side by side, and a misnamed tag would run the wrong command. func TestControlOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, _, term := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -620,6 +632,7 @@ func TestControlOverTheRealDispatcher(t *testing.T) { // spawn.Ask's fields: the target, the project and the question are strings side // by side. func TestAskOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, _, term := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -636,6 +649,7 @@ func TestAskOverTheRealDispatcher(t *testing.T) { // session and the project name in their own fields: swapped, the app resolves // the project by a session id and finds none. func TestWorktreesOverTheRealDispatcher(t *testing.T) { + t.Parallel() git := &spawnGit{dirty: true, checkouts: []project.Worktree{{Name: "auth-fix", Path: "/wt/auth-fix"}}} env, _, _ := wiredSpawn(t, git) @@ -652,6 +666,7 @@ func TestWorktreesOverTheRealDispatcher(t *testing.T) { // spawn.File's fields. Target and folder are two strings side by side, and // swapped the command files the wrong session under the name of the right one. func TestFileOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, rows, _ := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -670,6 +685,7 @@ func TestFileOverTheRealDispatcher(t *testing.T) { // TestFileWithoutATargetFilesTheCallersOwnSession proves the one-argument form // reads the folder rather than a session, as rename's does. func TestFileWithoutATargetFilesTheCallersOwnSession(t *testing.T) { + t.Parallel() env, rows, _ := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -684,6 +700,7 @@ func TestFileWithoutATargetFilesTheCallersOwnSession(t *testing.T) { // TestFoldersOverTheRealDispatcher proves `lich folders` posts the caller's // session and the project name in their own fields, as `lich worktrees` does. func TestFoldersOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, _, _ := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -699,6 +716,7 @@ func TestFoldersOverTheRealDispatcher(t *testing.T) { // posts land on spawn.RenameFolder's fields: the old name and the new one // swapped would match no folder. func TestRenameFolderOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, rows, _ := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -718,6 +736,7 @@ func TestRenameFolderOverTheRealDispatcher(t *testing.T) { // posts land on spawn.ColorFolder's fields: the folder and the color swapped // would be refused as no such folder. func TestColorFolderOverTheRealDispatcher(t *testing.T) { + t.Parallel() env, rows, _ := wiredSpawn(t, &spawnGit{}) var stdout, stderr bytes.Buffer @@ -739,6 +758,7 @@ func TestColorFolderOverTheRealDispatcher(t *testing.T) { // another release. An option the backend does not know yet is dropped, and one // the client does not know yet is its zero value. func TestOpenAcrossVersions(t *testing.T) { + t.Parallel() bodies := map[string]string{ "spawn.Open newer client": `[{"from":"s1","kind":"codex","folder":"Apps","optionFromTheFuture":true}]`, "spawn.Open older client": `[{"from":"s1","kind":"codex"}]`, @@ -780,6 +800,7 @@ func postRaw(t *testing.T, dispatcher *rpc.Handler, method, body string) string // spawn calls a session makes: each body carries a field the backend does not // know and leaves out one it does, and the call still lands on the target. func TestSpawnCallsAcrossVersions(t *testing.T) { + t.Parallel() cases := []struct { method, body string landed func(rows *spawnStore, term *spawnTerminal, answer string) bool @@ -857,6 +878,7 @@ func TestSpawnCallsAcrossVersions(t *testing.T) { // TestSendsAcrossVersions is the same for the three ways to send: a field the // backend does not know is dropped, and the project left out searches them all. func TestSendsAcrossVersions(t *testing.T) { + t.Parallel() for _, method := range []string{"relay.Send", "relay.SendPrivate", "relay.SendSubagent"} { t.Run(method, func(t *testing.T) { term := &wiredTerminal{} @@ -875,6 +897,7 @@ func TestSendsAcrossVersions(t *testing.T) { // TestRelayCallsAcrossVersions is the same for the rest of the relay calls a // session makes, each body with a field the backend does not know. func TestRelayCallsAcrossVersions(t *testing.T) { + t.Parallel() cases := []struct { method, body string landed func(term *wiredTerminal, answer string) bool @@ -909,6 +932,7 @@ func TestRelayCallsAcrossVersions(t *testing.T) { // as a client of another release would post them: the reply leaves the ticket // out, which answers the one errand open against the replier. func TestErrandAcrossVersions(t *testing.T) { + t.Parallel() dispatcher := rpc.New() dispatcher.Register("relay", relay.New(wiredSessions{}, &wiredTerminal{}, nil))