diff --git a/internal/project/basestatus_test.go b/internal/project/basestatus_test.go index d0c22dcc..7f373003 100644 --- a/internal/project/basestatus_test.go +++ b/internal/project/basestatus_test.go @@ -68,6 +68,7 @@ func commitFile(t *testing.T, dir string, git func(args ...string) string, name, } func TestBaseStatusNoRemote(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if got := New(nil).BaseStatus(repo); got != nil { t.Fatalf("want nil for a repository with no origin, got %+v", got) @@ -75,12 +76,14 @@ func TestBaseStatusNoRemote(t *testing.T) { } func TestBaseStatusNotARepository(t *testing.T) { + t.Parallel() if got := New(nil).BaseStatus(t.TempDir()); got != nil { t.Fatalf("want nil outside a repository, got %+v", got) } } func TestBaseStatusUpToDate(t *testing.T) { + t.Parallel() clone, cloneGit, _ := initClone(t) cloneGit("checkout", "-b", "feature") commitFile(t, clone, cloneGit, "b.txt", "two\n", "add b") @@ -103,6 +106,7 @@ func TestBaseStatusUpToDate(t *testing.T) { } func TestBaseStatusBehindWithoutConflict(t *testing.T) { + t.Parallel() clone, cloneGit, originGit := initClone(t) cloneGit("checkout", "-b", "feature") commitFile(t, clone, cloneGit, "b.txt", "two\n", "add b") @@ -125,6 +129,7 @@ func TestBaseStatusBehindWithoutConflict(t *testing.T) { } func TestBaseStatusConflict(t *testing.T) { + t.Parallel() clone, cloneGit, originGit := initClone(t) cloneGit("checkout", "-b", "feature") commitFile(t, clone, cloneGit, "a.txt", "mine\n", "edit a") @@ -147,6 +152,7 @@ func TestBaseStatusConflict(t *testing.T) { // stale one pins the badge to a state the checkout has left, which is the only // way this cache can be wrong. Both keys are moved, one at a time. func TestBaseStatusRecomputes(t *testing.T) { + t.Parallel() clone, cloneGit, originGit := initClone(t) cloneGit("checkout", "-b", "feature") commitFile(t, clone, cloneGit, "b.txt", "two\n", "add b") @@ -175,6 +181,7 @@ func TestBaseStatusRecomputes(t *testing.T) { // the parser's own idea of them: the tree OID, the conflicted paths, an empty // entry, then the informational messages that must not be read as paths. func TestConflictPaths(t *testing.T) { + t.Parallel() tests := []struct { name string out string @@ -210,6 +217,7 @@ func TestConflictPaths(t *testing.T) { } func TestClaimFetchThrottles(t *testing.T) { + t.Parallel() var cache baseCache now := time.Now() if !cache.claimFetch("/repo/.git", now) { diff --git a/internal/project/blob_test.go b/internal/project/blob_test.go index 09c834d8..267bdab0 100644 --- a/internal/project/blob_test.go +++ b/internal/project/blob_test.go @@ -16,6 +16,7 @@ import ( const pngBytes = "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR" func TestBlobWorkTree(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "img/logo.PNG", pngBytes) @@ -34,6 +35,7 @@ func TestBlobWorkTree(t *testing.T) { // The review's two sides: HEAD for a working-tree diff, a tree oid for a turn. // Each reads its own revision while the working tree has moved on. func TestBlobAtRevision(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "doc.pdf", "%PDF-old\x00") git("add", "doc.pdf") @@ -56,6 +58,7 @@ func TestBlobAtRevision(t *testing.T) { // An added file has no old side and a deleted one no new side; both must read // as absent, which the page draws as no column at all. func TestBlobMissingSideIsNotExist(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) svc := New(nil) for _, ref := range []string{"", "HEAD"} { @@ -66,6 +69,7 @@ func TestBlobMissingSideIsNotExist(t *testing.T) { } func TestBlobRefusals(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "assets/x.png", pngBytes) write(t, repo, "notes.txt", "text\n") @@ -93,6 +97,7 @@ func TestBlobRefusals(t *testing.T) { } func TestBlobDirectoryNamedLikeAnImage(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "shots.png/a.txt", "x\n") git("add", ".") @@ -109,6 +114,7 @@ func TestBlobDirectoryNamedLikeAnImage(t *testing.T) { // The limit is pinned to a literal and probed on both sides, in the working // tree and in git's object store, which are read by different code. func TestBlobSizeLimit(t *testing.T) { + t.Parallel() if maxPreviewBytes != 20<<20 { t.Fatalf("maxPreviewBytes = %d, want 20 MiB", maxPreviewBytes) } @@ -141,6 +147,7 @@ func serveBlob(t *testing.T, method string, query url.Values, header http.Header } func TestServeBlob(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "logo.png", pngBytes) @@ -164,6 +171,7 @@ func TestServeBlob(t *testing.T) { // Chromium's PDF viewer fetches a large document in ranges. func TestServeBlobHonoursRange(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "doc.pdf", "%PDF-1.4\x00rest") @@ -175,6 +183,7 @@ func TestServeBlobHonoursRange(t *testing.T) { } func TestServeBlobStatuses(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "big.png", strings.Repeat("\x00", maxPreviewBytes+1)) @@ -203,6 +212,7 @@ func TestServeBlobStatuses(t *testing.T) { // The page prints these bodies verbatim, so their words are the contract. func TestServeBlobRefusalWording(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "big.png", strings.Repeat("\x00", maxPreviewBytes+1)) diff --git a/internal/project/branchrules_test.go b/internal/project/branchrules_test.go index a2056357..22ab0801 100644 --- a/internal/project/branchrules_test.go +++ b/internal/project/branchrules_test.go @@ -6,6 +6,7 @@ import ( ) func TestParseBranchRules(t *testing.T) { + t.Parallel() t.Run("a ruleset that narrows the merge methods", func(t *testing.T) { out := []byte(`[ {"type":"deletion","parameters":null}, @@ -73,6 +74,7 @@ func TestParseBranchRules(t *testing.T) { } func TestBranchRulesFlow(t *testing.T) { + t.Parallel() t.Run("the branch is addressed in the path, escaped", func(t *testing.T) { gh := &fakeGH{out: []byte(`[]`)} if _, err := withGH(gh).BranchRules("/repo", "feat/IMPEX-728"); err != nil { diff --git a/internal/project/carry_test.go b/internal/project/carry_test.go index 65e89006..f0959eb3 100644 --- a/internal/project/carry_test.go +++ b/internal/project/carry_test.go @@ -11,6 +11,7 @@ import ( // untracked file in a directory that does not exist in dst yet, and a binary // change. What git ignores stays behind, and everything arrives unstaged. func TestCarryUncommitted(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "mod.txt", "one\n") write(t, repo, "del.txt", "gone\n") @@ -60,6 +61,7 @@ func TestCarryUncommitted(t *testing.T) { // TestCarryUncommittedClean proves a clean source is a no-op rather than an // error — the fork of a committed checkout goes through the same call. func TestCarryUncommittedClean(t *testing.T) { + t.Parallel() repo, git := initRepo(t) dst := filepath.Join(t.TempDir(), "fork") git("worktree", "add", "-b", "fork", dst, "main") @@ -75,6 +77,7 @@ func TestCarryUncommittedClean(t *testing.T) { // TestCarryUncommittedNotRepo proves a source git cannot read is reported // rather than silently carrying nothing. func TestCarryUncommittedNotRepo(t *testing.T) { + t.Parallel() if err := New(nil).CarryUncommitted(t.TempDir(), t.TempDir()); err == nil { t.Fatal("CarryUncommitted accepted a directory that is not a checkout") } @@ -93,6 +96,7 @@ func read(t *testing.T, dir, name string) string { // is stepped over rather than dereferenced or reported: the rest of the carry // still lands. func TestCarryUncommittedSkipsNonRegular(t *testing.T) { + t.Parallel() repo, git := initRepo(t) if err := os.Symlink("a.txt", filepath.Join(repo, "link.txt")); err != nil { t.Skipf("symlinks unavailable: %v", err) @@ -117,6 +121,7 @@ func TestCarryUncommittedSkipsNonRegular(t *testing.T) { // the listing and the copy are two calls, and a build in the source checkout // deletes its own scratch between them — is not a failure. func TestCarryFileVanished(t *testing.T) { + t.Parallel() dir := t.TempDir() if err := carryFile(filepath.Join(dir, "gone.txt"), filepath.Join(dir, "copy.txt")); err != nil { t.Fatalf("carryFile on a missing source: %v", err) @@ -129,6 +134,7 @@ func TestCarryFileVanished(t *testing.T) { // which Go maps onto fs.ErrNotExist — the same error a vanished source gives. // Caught by the Windows runner after passing here (#616). func TestCarryFileReportsRealFailure(t *testing.T) { + t.Parallel() dir := t.TempDir() write(t, dir, "from.txt", "data\n") // The destination's parent is a file, so MkdirAll cannot make the directory. diff --git a/internal/project/difftext_test.go b/internal/project/difftext_test.go index 0b3a02dd..245ba764 100644 --- a/internal/project/difftext_test.go +++ b/internal/project/difftext_test.go @@ -11,6 +11,7 @@ import ( // TestDiffText proves tracked edits (staged and unstaged) and untracked files // all land in one unified diff. func TestDiffText(t *testing.T) { + t.Parallel() repo, git := initRepo(t) if err := os.WriteFile(filepath.Join(repo, "a.txt"), []byte("uno\n"), 0o644); err != nil { t.Fatal(err) @@ -41,6 +42,7 @@ func TestDiffText(t *testing.T) { // TestDiffTextNoHead proves a repository without commits diffs staged and // untracked files against the empty tree instead of erroring on HEAD. func TestDiffTextNoHead(t *testing.T) { + t.Parallel() repo := t.TempDir() if out, err := exec.Command("git", "init", "-b", "main", repo).CombinedOutput(); err != nil { t.Skipf("git init unavailable: %v (%s)", err, out) @@ -69,6 +71,7 @@ func TestDiffTextNoHead(t *testing.T) { // TestDiffTextClean proves a clean repository yields an empty diff, and a // non-repository path an error. func TestDiffTextClean(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) out, err := New(nil).DiffText(repo) if err != nil { @@ -89,6 +92,7 @@ func TestDiffTextClean(t *testing.T) { // itself cannot open it — the last one is the "any other failure" branch, which // has to be told apart from git's exit 1 for "the files differ". func TestUntrackedDiffYieldsNothingItCannotRender(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if got := untrackedDiff(repo, "vanished.txt"); got != "" { @@ -119,6 +123,7 @@ func TestUntrackedDiffYieldsNothingItCannotRender(t *testing.T) { // TestDiffTextBinaryUntracked proves an untracked binary shows up as git's // "Binary files ... differ" stanza rather than a textual hunk. func TestDiffTextBinaryUntracked(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if err := os.WriteFile(filepath.Join(repo, "blob.bin"), []byte{0, 1, 2, 0}, 0o644); err != nil { t.Fatal(err) @@ -136,6 +141,7 @@ func TestDiffTextBinaryUntracked(t *testing.T) { // TestDiffTextDecodesLatin1 covers a file saved in ISO-8859-1 next to a UTF-8 // one: each line reaches the review as UTF-8 instead of U+FFFD. func TestDiffTextDecodesLatin1(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if err := os.WriteFile(filepath.Join(repo, "latin.java"), []byte("// servi\xe7os padr\xe3o\n"), 0o644); err != nil { t.Fatal(err) diff --git a/internal/project/discard_test.go b/internal/project/discard_test.go index 0474f6ac..ac119b55 100644 --- a/internal/project/discard_test.go +++ b/internal/project/discard_test.go @@ -9,6 +9,7 @@ import ( // TestDiscardFileTracked proves a modified tracked file is restored to its // HEAD content, staged or not. func TestDiscardFileTracked(t *testing.T) { + t.Parallel() repo, git := initRepo(t) if err := os.WriteFile(filepath.Join(repo, "a.txt"), []byte("uno\n"), 0o644); err != nil { t.Fatal(err) @@ -33,6 +34,7 @@ func TestDiscardFileTracked(t *testing.T) { // TestDiscardFileNew proves untracked and staged-new files are removed from // disk and index. func TestDiscardFileNew(t *testing.T) { + t.Parallel() repo, git := initRepo(t) if err := os.WriteFile(filepath.Join(repo, "staged.txt"), []byte("s\n"), 0o644); err != nil { t.Fatal(err) @@ -58,6 +60,7 @@ func TestDiscardFileNew(t *testing.T) { // TestDiscardFileRejectsEscape proves traversal and absolute paths never reach // the filesystem. func TestDiscardFileRejectsEscape(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) for _, rel := range []string{"../outside.txt", "/etc/passwd", "a/../../b"} { if err := New(nil).DiscardFile(repo, rel); err == nil { diff --git a/internal/project/filelines_test.go b/internal/project/filelines_test.go index 28e42d19..498830a4 100644 --- a/internal/project/filelines_test.go +++ b/internal/project/filelines_test.go @@ -19,6 +19,7 @@ func numbered(n int) string { } func TestSliceLines(t *testing.T) { + t.Parallel() text := "a\nb\nc\nd\n" tests := []struct { name string @@ -46,6 +47,7 @@ func TestSliceLines(t *testing.T) { // The cap is pinned to a literal and probed on both sides of it, so a change to // maxExpandLines has to move this test rather than ride along with it. func TestSliceLinesCap(t *testing.T) { + t.Parallel() if maxExpandLines != 500 { t.Fatalf("maxExpandLines = %d, want 500 — the wire contract moved", maxExpandLines) } @@ -67,6 +69,7 @@ func TestSliceLinesCap(t *testing.T) { } func TestIsOID(t *testing.T) { + t.Parallel() valid := []string{"a4dbc1f", strings.Repeat("0", 40), "533f1af9fc740d2335cb3b4aae0e6481acfc5211"} for _, ref := range valid { if !isOID(ref) { @@ -82,6 +85,7 @@ func TestIsOID(t *testing.T) { } func TestContentsPath(t *testing.T) { + t.Parallel() got := contentsPath("internal/rpc/rpc go.md", "abcdef1234567890") want := "repos/{owner}/{repo}/contents/internal/rpc/rpc%20go.md?ref=abcdef1234567890" if got != want { @@ -90,6 +94,7 @@ func TestContentsPath(t *testing.T) { } func TestFileLinesWorkTree(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "src/app.ts", "one\ntwo\nthree\nfour\n") @@ -120,6 +125,7 @@ func TestFileLinesWorkTree(t *testing.T) { // A local object is read by git, and never costs a gh call — the turn diff's // snapshot tree is the case, and it is the reason the local read comes first. func TestFileLinesLocalObject(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "a.txt", "one\ntwo\nthree\n") git("add", "a.txt") @@ -146,6 +152,7 @@ func TestFileLinesLocalObject(t *testing.T) { } func TestFileLinesLocalObjectRejectsADirectory(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "src/a.txt", "one\n") git("add", "src/a.txt") @@ -162,6 +169,7 @@ func TestFileLinesLocalObjectRejectsADirectory(t *testing.T) { } func TestFileLinesLocalObjectRejectsBinary(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "blob.bin", "text\x00more") git("add", "blob.bin") @@ -176,6 +184,7 @@ func TestFileLinesLocalObjectRejectsBinary(t *testing.T) { // An oid the clone does not have is a pull request's head: GitHub answers for // it, through the contents API rather than through git. func TestFileLinesMissingObjectAsksGitHub(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) missing := strings.Repeat("b", 40) @@ -197,6 +206,7 @@ func TestFileLinesMissingObjectAsksGitHub(t *testing.T) { } func TestFileLinesGitHubRejectsOversize(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) gh := &fakeGH{out: []byte(strings.Repeat("x", maxReadFileSize+1))} @@ -206,6 +216,7 @@ func TestFileLinesGitHubRejectsOversize(t *testing.T) { } func TestFileLinesGitHubFailureSurfaces(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) gh := &fakeGH{err: errors.New("gh: not found")} if _, err := withGH(gh).FileLines(repo, "a.txt", strings.Repeat("c", 40), 1, 5); err == nil { diff --git a/internal/project/freshness_test.go b/internal/project/freshness_test.go index 711a110e..8b363b34 100644 --- a/internal/project/freshness_test.go +++ b/internal/project/freshness_test.go @@ -28,6 +28,7 @@ func commitIn(t *testing.T, dir string, git func(args ...string) string, content } func TestBaseFreshnessWithoutUpstream(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) got, err := New(nil).BaseFreshness(repo, "main") @@ -42,6 +43,7 @@ func TestBaseFreshnessWithoutUpstream(t *testing.T) { // TestBaseFreshnessBehindFetchesFirst proves the count is taken after a fetch: // the commit merged on origin since the last fetch is what makes main behind. func TestBaseFreshnessBehindFetchesFirst(t *testing.T) { + t.Parallel() repo, git := initRepo(t) origin, originGit := originWithMain(t, git) commitIn(t, origin, originGit, "merged\n") @@ -57,6 +59,7 @@ func TestBaseFreshnessBehindFetchesFirst(t *testing.T) { } func TestBaseFreshnessAhead(t *testing.T) { + t.Parallel() repo, git := initRepo(t) originWithMain(t, git) commitIn(t, repo, git, "local\n") @@ -75,6 +78,7 @@ func TestBaseFreshnessAhead(t *testing.T) { // the answer, not as an error: the dialog still opens the worktree, from the // local branch, and says why. func TestBaseFreshnessFetchFailure(t *testing.T) { + t.Parallel() repo, git := initRepo(t) originWithMain(t, git) git("remote", "set-url", "origin", filepath.Join(t.TempDir(), "unreachable")) @@ -89,6 +93,7 @@ func TestBaseFreshnessFetchFailure(t *testing.T) { } func TestBaseFreshnessNotABranch(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if _, err := New(nil).BaseFreshness(repo, "nope"); err == nil { diff --git a/internal/project/ghaccount_test.go b/internal/project/ghaccount_test.go index baf05ef3..2dc392ef 100644 --- a/internal/project/ghaccount_test.go +++ b/internal/project/ghaccount_test.go @@ -8,6 +8,7 @@ import ( ) func TestParseGHAccounts(t *testing.T) { + t.Parallel() tests := []struct { name string out string @@ -44,6 +45,7 @@ func TestParseGHAccounts(t *testing.T) { } func TestGitHubAccounts(t *testing.T) { + t.Parallel() gh := &fakeGH{out: []byte(" ✓ Logged in to github.com account omartelo (keyring)\n")} got, err := withGH(gh).GitHubAccounts() if err != nil { @@ -65,6 +67,7 @@ func TestGitHubAccounts(t *testing.T) { // TestAccountTokenReachesGH proves the configured account governs the gh call: // its token is resolved once and carried by the pull request lookup. func TestAccountTokenReachesGH(t *testing.T) { + t.Parallel() var seen []string gh := &accountGH{token: "gho_secret", pr: `{"number":7,"state":"OPEN"}`, calls: &seen} svc := &Service{runner: gh.run} @@ -85,6 +88,7 @@ func TestAccountTokenReachesGH(t *testing.T) { // TestNoAccountRunsAsActive proves both no-override paths run gh with no token, // which is gh answering as its own active account. func TestNoAccountRunsAsActive(t *testing.T) { + t.Parallel() tests := []struct { name string lookup accountLookup @@ -114,6 +118,7 @@ func TestNoAccountRunsAsActive(t *testing.T) { // is not authenticated as that account anymore) degrades to the active account // instead of failing the lookup. func TestAccountTokenFailureFallsBack(t *testing.T) { + t.Parallel() gh := &accountGH{tokenErr: errTest, pr: `{"number":7,"state":"OPEN"}`} svc := &Service{runner: gh.run} svc.SetAccounts(func(string) string { return "github.com/gone" }) @@ -153,6 +158,7 @@ func (g *accountGH) run(_ time.Duration, _, token string, args ...string) ([]byt // looks the login up on its default host, answers "no oauth token found", and // tokenFor falls back to the active account without a word. func TestTokenArgs(t *testing.T) { + t.Parallel() tests := []struct { name string account string @@ -190,6 +196,7 @@ func TestTokenArgs(t *testing.T) { // session that answered as whichever account gh has active would push and // comment as the wrong person. func TestGHTokenNamesTheAccount(t *testing.T) { + t.Parallel() gh := &fakeGH{out: []byte("gho_project_account\n")} if got := ghToken(gh.run, "github.com/marcelo-filho_snk"); got != "gho_project_account" { t.Errorf("ghToken = %q, want the account's token", got) @@ -214,6 +221,7 @@ func TestGHTokenNamesTheAccount(t *testing.T) { // session without a token rather than fail it over a credential it may never // use. func TestGHTokenSwallowsEveryFailure(t *testing.T) { + t.Parallel() gh := &fakeGH{err: errTest} if got := ghToken(gh.run, "github.com/nobody"); got != "" { t.Errorf("ghToken on failure = %q, want \"\"", got) diff --git a/internal/project/gherror_test.go b/internal/project/gherror_test.go index 53d0c492..55652055 100644 --- a/internal/project/gherror_test.go +++ b/internal/project/gherror_test.go @@ -12,6 +12,7 @@ import ( // failure comes back as a sentence about what happened, and gh's own words are // never part of it. func TestGHMessage(t *testing.T) { + t.Parallel() tests := []struct { name string stderr string @@ -133,6 +134,7 @@ func TestGHMessage(t *testing.T) { // TestGHMessageLeaksNothing is the rule itself rather than one case of it: no // message may carry gh's vocabulary onto the screen. func TestGHMessageLeaksNothing(t *testing.T) { + t.Parallel() leaks := []string{"graphql", "stderr", "exit status", "http 4", "http 5", "gh: "} stderrs := []string{ "GraphQL: Could not resolve to a Repository with the name 'acme/private'. (repository)", @@ -152,6 +154,7 @@ func TestGHMessageLeaksNothing(t *testing.T) { } func TestErrUnreadableAnswer(t *testing.T) { + t.Parallel() err := errUnreadableAnswer(errors.New("invalid character 'n' looking for beginning of value")) if want := "GitHub's answer could not be read."; err.Error() != want { t.Errorf("errUnreadableAnswer() = %q, want %q", err, want) @@ -162,6 +165,7 @@ func TestErrUnreadableAnswer(t *testing.T) { // request flows actually call: the error that reaches the screen is ghMessage's // sentence, and gh's own words stay behind in the log. func TestGHFailureCarriesTheMessageAndNotTheStderr(t *testing.T) { + t.Parallel() stderr := "GraphQL: Could not resolve to a Repository with the name 'acme/private'. (repository)" err := ghFailure([]string{"pr", "view"}, stderr, errTest, nil) if err == nil { @@ -179,6 +183,7 @@ func TestGHFailureCarriesTheMessageAndNotTheStderr(t *testing.T) { // it still has to come back as a sentence rather than as "context deadline // exceeded". func TestGHFailureAnswersATimeout(t *testing.T) { + t.Parallel() err := ghFailure([]string{"pr", "merge"}, "", errTest, context.DeadlineExceeded) if err == nil { t.Fatal("ghFailure() = nil, want the screen's message") diff --git a/internal/project/git_test.go b/internal/project/git_test.go index d478c492..219ce780 100644 --- a/internal/project/git_test.go +++ b/internal/project/git_test.go @@ -115,6 +115,7 @@ func TestRunGitStillReportsRealFailures(t *testing.T) { // -z output the way git writes it: a NUL after every entry, so the last one is // a terminator and never an empty path. func TestSplitNULDropsTheTrailingTerminator(t *testing.T) { + t.Parallel() got := splitNUL("a.txt\x00dir/b.txt\x00") if len(got) != 2 || got[0] != "a.txt" || got[1] != "dir/b.txt" { t.Errorf("splitNUL = %q, want [a.txt dir/b.txt]", got) @@ -129,6 +130,7 @@ func TestSplitNULDropsTheTrailingTerminator(t *testing.T) { // directory, so a sandbox that mounts the worktree and not the common directory // hands the agent a checkout git refuses to read. func TestGitCommonDirOfARepositoryAndItsWorktree(t *testing.T) { + t.Parallel() repo := initBareRepo(t) if err := os.WriteFile(filepath.Join(repo, "a.txt"), []byte("a\n"), 0o644); err != nil { t.Fatal(err) @@ -172,6 +174,7 @@ func TestGitCommonDirOfARepositoryAndItsWorktree(t *testing.T) { // A directory that is not in a repository is not an error: a session can be // opened anywhere, and the sandbox simply has no common directory to mount. func TestGitCommonDirOutsideARepository(t *testing.T) { + t.Parallel() if got := GitCommonDir(t.TempDir()); got != "" { t.Errorf("GitCommonDir outside a repository = %q, want \"\"", got) } @@ -184,6 +187,7 @@ func TestGitCommonDirOutsideARepository(t *testing.T) { // top and the main checkout it was made from; the main checkout names itself // twice; a directory outside a repository names nothing. func TestCheckoutRootsOfARepositoryAndItsWorktree(t *testing.T) { + t.Parallel() repo := initBareRepo(t) commit := exec.Command("git", "-C", repo, "-c", "user.email=t@example.com", "-c", "user.name=t", "commit", "--allow-empty", "-m", "first") diff --git a/internal/project/giterror_test.go b/internal/project/giterror_test.go index 565552c8..e81eebc2 100644 --- a/internal/project/giterror_test.go +++ b/internal/project/giterror_test.go @@ -7,6 +7,7 @@ import ( ) func TestGitMessage(t *testing.T) { + t.Parallel() tests := []struct { name string stderr string @@ -99,6 +100,7 @@ func TestGitMessage(t *testing.T) { // TestGitMessageLeaksNothing is the rule rather than one case of it: git's own // vocabulary never reaches the screen. func TestGitMessageLeaksNothing(t *testing.T) { + t.Parallel() leaks := []string{"fatal:", "error:", "exit status", "usage:", "hint:", "--force"} stderrs := []string{ "fatal: not a git repository (or any of the parent directories): .git", @@ -120,6 +122,7 @@ func TestGitMessageLeaksNothing(t *testing.T) { // TestRunGitMessage runs the real git against a directory that is not a // repository: the whole path, from stderr to the sentence the screen shows. func TestRunGitMessage(t *testing.T) { + t.Parallel() if _, err := exec.LookPath("git"); err != nil { t.Skip("git unavailable") } diff --git a/internal/project/identify_test.go b/internal/project/identify_test.go index 0f073951..c0b5fec0 100644 --- a/internal/project/identify_test.go +++ b/internal/project/identify_test.go @@ -12,6 +12,7 @@ import ( // what a project's sessions and its worktree directory hang off, so a second one // for the same directory is a second project on top of the first. func TestIdentifyMatchesThePicker(t *testing.T) { + t.Parallel() dir := t.TempDir() p, err := Identify(dir) @@ -69,6 +70,7 @@ func TestIdentifyNormalizes(t *testing.T) { // window's own directory, not the caller's, so accepting it would silently open // somewhere else. func TestIdentifyRefuses(t *testing.T) { + t.Parallel() dir := t.TempDir() file := filepath.Join(dir, "README.md") if err := os.WriteFile(file, []byte("x"), 0o600); err != nil { diff --git a/internal/project/issue_test.go b/internal/project/issue_test.go index a99b442e..e68e13da 100644 --- a/internal/project/issue_test.go +++ b/internal/project/issue_test.go @@ -7,6 +7,7 @@ import ( ) func TestIssue(t *testing.T) { + t.Parallel() t.Run("decodes and asks gh for the number", func(t *testing.T) { gh := &fakeGH{out: []byte(`{"number":381,"title":"Sandbox backend","body":"why","url":"https://github.com/o/l/issues/381"}`)} issue, err := withGH(gh).Issue("/repo", 381) @@ -80,6 +81,7 @@ func TestIssue(t *testing.T) { } func TestTruncateRunes(t *testing.T) { + t.Parallel() // The cap is pinned as a literal and crossed by one, rather than derived // from the constant: a test that follows the constant proves nothing about // where the boundary is. @@ -108,6 +110,7 @@ func TestTruncateRunes(t *testing.T) { // the sentence the dialog shows. The literal is what gh answered in the field — // a reword there is a fallback message on screen, never a wrong one. func TestIssueNotFoundMessage(t *testing.T) { + t.Parallel() const stderr = "GraphQL: Could not resolve to an issue or pull request with the number of 99999. (repository.issue)" got := ghMessage(stderr, errors.New("exit status 1"), nil) if got != "GitHub has no issue with that number." { diff --git a/internal/project/names_test.go b/internal/project/names_test.go index 8b784fe2..d9a08d6b 100644 --- a/internal/project/names_test.go +++ b/internal/project/names_test.go @@ -10,6 +10,7 @@ import ( // free names come out as adjective-noun, and persistent collisions fall back // to a numeric suffix instead of looping forever. func TestRandomWorktreeName(t *testing.T) { + t.Parallel() pair := regexp.MustCompile(`^[a-z]+-[a-z]+$`) suffixed := regexp.MustCompile(`^[a-z]+-[a-z]+-\d+$`) diff --git a/internal/project/pr_test.go b/internal/project/pr_test.go index 7104f60e..bf541c67 100644 --- a/internal/project/pr_test.go +++ b/internal/project/pr_test.go @@ -9,6 +9,7 @@ import ( ) func TestParsePRDetail(t *testing.T) { + t.Parallel() t.Run("open PR with mixed checks", func(t *testing.T) { out := []byte(`{ "number": 88, @@ -126,6 +127,7 @@ func TestParsePRDetail(t *testing.T) { } func TestReduceChecks(t *testing.T) { + t.Parallel() tests := []struct { name string items []checkItem @@ -183,6 +185,7 @@ func TestReduceChecks(t *testing.T) { } func TestToCheckItems(t *testing.T) { + t.Parallel() t.Run("no checks yields no list", func(t *testing.T) { if got := toCheckItems(nil); got != nil { t.Errorf("toCheckItems(nil) = %+v, want nil", got) @@ -263,6 +266,7 @@ func TestToCheckItems(t *testing.T) { } func TestToCommits(t *testing.T) { + t.Parallel() t.Run("no commits yields no list", func(t *testing.T) { if got := toCommits(nil); got != nil { t.Errorf("toCommits(nil) = %+v, want nil", got) @@ -327,6 +331,7 @@ func TestToCommits(t *testing.T) { } func TestMergeArgs(t *testing.T) { + t.Parallel() // An unrecognised method must be refused before any gh shell-out. if _, err := mergeArgs(0, "force", "", "", false); err == nil { t.Error("expected an error for an unknown merge method") @@ -400,6 +405,7 @@ func TestMergeArgs(t *testing.T) { } func TestIsNoPullRequest(t *testing.T) { + t.Parallel() tests := []struct { stderr string want bool @@ -466,6 +472,7 @@ func (f *fakeGH) run(timeout time.Duration, dir, token string, args ...string) ( func withGH(f *fakeGH) *Service { return &Service{runner: f.run} } func TestPullRequestDetailFlow(t *testing.T) { + t.Parallel() t.Run("open PR decodes, scoped to the path", func(t *testing.T) { gh := &fakeGH{out: []byte(`{"number":7,"state":"OPEN","title":"t"}`)} pr, err := withGH(gh).PullRequestDetail("/repo", 0) @@ -521,6 +528,7 @@ func TestPullRequestDetailFlow(t *testing.T) { // the rest: it used to shell out to gh on its own, which left it running as // whatever account gh had active while the screen beside it used the project's. func TestPullRequestBadgeRunsThroughTheRunner(t *testing.T) { + t.Parallel() gh := &fakeGH{out: []byte(`{"number":7,"url":"https://github.com/o/r/pull/7","state":"OPEN"}`)} pr := withGH(gh).PullRequest("/repo") if pr == nil || pr.Number != 7 { @@ -551,6 +559,7 @@ func TestPullRequestBadgeRunsThroughTheRunner(t *testing.T) { } func TestMergePullRequestFlow(t *testing.T) { + t.Parallel() t.Run("the built args reach gh", func(t *testing.T) { gh := &fakeGH{} if err := withGH(gh).MergePullRequest("/repo", 0, "squash", "", "", false); err != nil { @@ -587,6 +596,7 @@ func TestMergePullRequestFlow(t *testing.T) { } func TestCreatePullRequestFlow(t *testing.T) { + t.Parallel() t.Run("opens the web flow in the path", func(t *testing.T) { gh := &fakeGH{} if err := withGH(gh).CreatePullRequest("/repo"); err != nil { @@ -613,6 +623,7 @@ func TestCreatePullRequestFlow(t *testing.T) { } func TestPullRequestDiffFlow(t *testing.T) { + t.Parallel() t.Run("plain diff text is returned verbatim", func(t *testing.T) { gh := &fakeGH{out: []byte("diff --git a/a.txt b/a.txt\n")} text, err := withGH(gh).PullRequestDiff("/repo", 0) @@ -662,6 +673,7 @@ func TestPullRequestDiffFlow(t *testing.T) { } func TestPRArgs(t *testing.T) { + t.Parallel() tests := []struct { name string number int @@ -683,6 +695,7 @@ func TestPRArgs(t *testing.T) { } func TestPullRequestDetailByNumber(t *testing.T) { + t.Parallel() gh := &fakeGH{out: []byte(`{"number":42,"state":"OPEN","title":"t"}`)} pr, err := withGH(gh).PullRequestDetail("/repo", 42) if err != nil { @@ -697,6 +710,7 @@ func TestPullRequestDetailByNumber(t *testing.T) { } func TestParsePRList(t *testing.T) { + t.Parallel() t.Run("rows carry the author, the fork flag and the rollup", func(t *testing.T) { out := []byte(`[ { @@ -767,6 +781,7 @@ func TestParsePRList(t *testing.T) { } func TestListPullRequestsFlow(t *testing.T) { + t.Parallel() t.Run("the call is scoped and bounded", func(t *testing.T) { gh := &fakeGH{out: []byte(`[{"number":1,"title":"t"}]`)} list, err := withGH(gh).ListPullRequests("/repo", "") @@ -822,6 +837,7 @@ func TestListPullRequestsFlow(t *testing.T) { // TestParsePRDetailReview proves the aggregate review verdict survives the // decode — the field the Pulls header reads to say a PR is approved. func TestParsePRDetailReview(t *testing.T) { + t.Parallel() tests := []struct { name string raw string @@ -855,6 +871,7 @@ func TestParsePRDetailReview(t *testing.T) { // merged PR must not keep showing on the badge — but a row the list offers has // to open, and the list can be asked for merged and closed ones. func TestPullRequestDetailByNumberIgnoresState(t *testing.T) { + t.Parallel() merged := []byte(`{"number":13960,"state":"MERGED","title":"Add macos keyring security doc"}`) pr, err := withGH(&fakeGH{out: merged}).PullRequestDetail("/repo", 13960) diff --git a/internal/project/prchanged_test.go b/internal/project/prchanged_test.go index a1e1ddfa..6ab84cae 100644 --- a/internal/project/prchanged_test.go +++ b/internal/project/prchanged_test.go @@ -16,6 +16,7 @@ func (l *changeLog) emit(name string, data any) { } func TestPullRequestWritesAnnounceTheChange(t *testing.T) { + t.Parallel() writes := map[string]func(s *Service) error{ "merge": func(s *Service) error { return s.MergePullRequest("/repo", 7, "squash", "", "", false) }, "edit body": func(s *Service) error { return s.EditPullRequestBody("/repo", 7, "body") }, @@ -56,6 +57,7 @@ func TestPullRequestWritesAnnounceTheChange(t *testing.T) { } func TestPullRequestChangeNamesTheNumber(t *testing.T) { + t.Parallel() log := &changeLog{} s := withGH(&fakeGH{}) s.SetEmit(log.emit) @@ -68,6 +70,7 @@ func TestPullRequestChangeNamesTheNumber(t *testing.T) { } func TestPullRequestWriteWithoutEmitter(t *testing.T) { + t.Parallel() if err := withGH(&fakeGH{}).CommentOnPullRequest("/repo", 7, "hi"); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/internal/project/prchecks_test.go b/internal/project/prchecks_test.go index 055d2801..0975ec67 100644 --- a/internal/project/prchecks_test.go +++ b/internal/project/prchecks_test.go @@ -7,6 +7,7 @@ import "testing" // break a build — it turns a broken check green on the panel, and a merge // affordance opens on a red pull request. func TestIsFailureConclusion(t *testing.T) { + t.Parallel() tests := map[string]bool{ "FAILURE": true, "TIMED_OUT": true, diff --git a/internal/project/prconflicts_test.go b/internal/project/prconflicts_test.go index 8be204c6..3aeaf7e3 100644 --- a/internal/project/prconflicts_test.go +++ b/internal/project/prconflicts_test.go @@ -25,6 +25,7 @@ func pullRequestOrigin(t *testing.T, number int) (clone, origin string, originGi } func TestPullRequestConflictsNamesTheFiles(t *testing.T) { + t.Parallel() clone, origin, originGit := pullRequestOrigin(t, 7) commitFile(t, origin, originGit, "a.txt", "the base branch's line\n", "base edits a") @@ -38,6 +39,7 @@ func TestPullRequestConflictsNamesTheFiles(t *testing.T) { } func TestPullRequestConflictsNoneWhenTheMergeIsClean(t *testing.T) { + t.Parallel() clone, origin, originGit := pullRequestOrigin(t, 7) commitFile(t, origin, originGit, "b.txt", "elsewhere\n", "base adds b") @@ -51,6 +53,7 @@ func TestPullRequestConflictsNoneWhenTheMergeIsClean(t *testing.T) { } func TestPullRequestConflictsRefusesAnUnaddressedPullRequest(t *testing.T) { + t.Parallel() clone, _, _ := pullRequestOrigin(t, 7) if _, err := New(nil).PullRequestConflicts(clone, 0, "main", ""); err == nil { @@ -62,6 +65,7 @@ func TestPullRequestConflictsRefusesAnUnaddressedPullRequest(t *testing.T) { } func TestPullRequestConflictsReportsAFetchItCannotMake(t *testing.T) { + t.Parallel() clone, _, _ := pullRequestOrigin(t, 7) if _, err := New(nil).PullRequestConflicts(clone, 404, "main", ""); err == nil { @@ -74,12 +78,14 @@ func TestPullRequestConflictsReportsAFetchItCannotMake(t *testing.T) { // no repository is the reachable stand-in for the git too old to know // --write-tree, which exits the same way — anything but 0 or 1. func TestMergeConflictsReportsAMergeItCannotCompute(t *testing.T) { + t.Parallel() if _, ok := mergeConflicts(t.TempDir(), "refs/heads/main", "refs/heads/pr"); ok { t.Error("want ok=false where merge-tree could not run — a clean merge and an unanswered one must not read alike") } } func TestPullRequestConflictsFetchesTheRemoteTheURLNames(t *testing.T) { + t.Parallel() clone, origin, originGit := pullRequestOrigin(t, 7) commitFile(t, origin, originGit, "a.txt", "the base branch's line\n", "base edits a") // The clone of a fork lich has to answer for: the pull request lives on a @@ -99,6 +105,7 @@ func TestPullRequestConflictsFetchesTheRemoteTheURLNames(t *testing.T) { } func TestPRRemoteFallsBackToOrigin(t *testing.T) { + t.Parallel() clone, _, _ := pullRequestOrigin(t, 7) if got := prRemote(clone, ""); got != "origin" { @@ -110,6 +117,7 @@ func TestPRRemoteFallsBackToOrigin(t *testing.T) { } func TestRemoteRepoReadsBothSpellings(t *testing.T) { + t.Parallel() for _, tc := range []struct{ url, want string }{ {"git@github.com:Owner/Repo.git (fetch)", "owner/repo"}, {"https://github.com/owner/repo.git (push)", "owner/repo"}, @@ -127,6 +135,7 @@ func TestRemoteRepoReadsBothSpellings(t *testing.T) { } func TestRepoFromPRURLRefusesWhatItCannotRead(t *testing.T) { + t.Parallel() for _, bad := range []string{"", "https://github.com/owner", "not a url at all", "://"} { if got := repoFromPRURL(bad); got != "" { t.Errorf("repoFromPRURL(%q) = %q, want empty", bad, got) @@ -138,6 +147,7 @@ func TestRepoFromPRURLRefusesWhatItCannotRead(t *testing.T) { // their repository to carry: left behind they outlive the answer, and a // `git push --mirror` would publish them. func TestPullRequestConflictsLeavesNoRefsBehind(t *testing.T) { + t.Parallel() clone, origin, originGit := pullRequestOrigin(t, 7) commitFile(t, origin, originGit, "a.txt", "the base branch's line\n", "base edits a") lichRefs := func() string { diff --git a/internal/project/project_test.go b/internal/project/project_test.go index 52a07eb3..3b815eda 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -36,6 +36,7 @@ func (p *pickerStub) PickSaveFile(title, defaultName string) (string, error) { } func TestPickFileUsesRequestedTitle(t *testing.T) { + t.Parallel() picker := &pickerStub{filePath: "/tmp/theme.json"} path, err := New(picker).PickFile("Import Theme") if err != nil || path != picker.filePath || picker.fileTitle != "Import Theme" { @@ -49,6 +50,7 @@ func TestPickFileUsesRequestedTitle(t *testing.T) { } func TestPickSaveFileSeedsTheDefaultName(t *testing.T) { + t.Parallel() picker := &pickerStub{filePath: "/tmp/lich-theme-template.json"} path, err := New(picker).PickSaveFile("Save Theme Template", "lich-theme-template.json") if err != nil || path != picker.filePath { @@ -69,6 +71,7 @@ func TestPickSaveFileSeedsTheDefaultName(t *testing.T) { // has to come back as nil with no error — an error there would put a failure // dialog on a user who simply closed the picker. func TestOpen(t *testing.T) { + t.Parallel() dir := t.TempDir() picker := &pickerStub{dirPath: dir} p, err := New(picker).Open() @@ -100,6 +103,7 @@ func TestOpen(t *testing.T) { // TestProjectID proves the ID is deterministic per path and differs across // paths. func TestProjectID(t *testing.T) { + t.Parallel() a := projectID("/tmp/alpha") again := projectID("/tmp/alpha") b := projectID("/tmp/beta") @@ -118,6 +122,7 @@ func TestProjectID(t *testing.T) { // TestNewProject proves the path→Project mapping the dialog feeds into: Name is // the base directory, Path is verbatim, ID matches projectID. func TestNewProject(t *testing.T) { + t.Parallel() p := newProject("/tmp/some/alpha") if p.Name != "alpha" { t.Errorf("Name = %q, want alpha", p.Name) @@ -151,6 +156,7 @@ func TestHome(t *testing.T) { // TestBranch proves Branch reads the checked-out branch of a git work tree and // returns "" for a directory that is not a repository. func TestBranch(t *testing.T) { + t.Parallel() repo := t.TempDir() if out, err := exec.Command("git", "init", "-b", "trunk", repo).CombinedOutput(); err != nil { t.Skipf("git init unavailable: %v (%s)", err, out) @@ -170,6 +176,7 @@ func TestBranch(t *testing.T) { // from a missing key, so a path mapped to "" would draw an empty badge instead // of none at all. func TestBranchesOf(t *testing.T) { + t.Parallel() repo := t.TempDir() if out, err := exec.Command("git", "init", "-b", "trunk", repo).CombinedOutput(); err != nil { t.Skipf("git init unavailable: %v (%s)", err, out) @@ -187,6 +194,7 @@ func TestBranchesOf(t *testing.T) { // it on every palette opening, including the one where no session has ever been // closed. func TestBranchesOfNothing(t *testing.T) { + t.Parallel() if got := New(nil).BranchesOf(nil); got == nil || len(got) != 0 { t.Errorf("BranchesOf(nil) = %v, want an empty map", got) } @@ -196,6 +204,7 @@ func TestBranchesOfNothing(t *testing.T) { // and URL, while malformed JSON, an empty object, and a PR missing its number or // URL all collapse to nil so the badge hides instead of showing garbage. func TestParsePullRequest(t *testing.T) { + t.Parallel() tests := []struct { name string out string @@ -227,6 +236,7 @@ func TestParsePullRequest(t *testing.T) { // TestDiff proves Diff counts dirty files and added/deleted lines against HEAD, // and returns the zero value for clean repositories and non-repositories. func TestDiff(t *testing.T) { + t.Parallel() repo := t.TempDir() git := func(args ...string) { t.Helper() @@ -312,6 +322,7 @@ func TestDiff(t *testing.T) { // the collapse saves — so the detached case has to hold here too, not only in // Branch. func TestDiffCarriesTheBranch(t *testing.T) { + t.Parallel() repo, git := initRepo(t) if got := New(nil).Diff(repo); got.Branch != "main" { @@ -327,6 +338,7 @@ func TestDiffCarriesTheBranch(t *testing.T) { // against HEAD fails, and the untracked additions must still be counted rather // than the whole stat collapsing to +0 -0. func TestDiffNoCommits(t *testing.T) { + t.Parallel() repo := t.TempDir() if out, err := exec.Command("git", "init", repo).CombinedOutput(); err != nil { t.Skipf("git init unavailable: %v (%s)", err, out) @@ -429,6 +441,7 @@ func sparseTextFile(t *testing.T, name string, size int64) { // untracked file past it adds no lines to the diff counters rather than being // read whole into memory. func TestCountFileLinesRefusesAFileOverTheCap(t *testing.T) { + t.Parallel() dir := t.TempDir() small := filepath.Join(dir, "small.txt") if err := os.WriteFile(small, []byte("one\ntwo\n"), 0o644); err != nil { @@ -448,6 +461,7 @@ func TestCountFileLinesRefusesAFileOverTheCap(t *testing.T) { // TestExistsAcceptsOnlyDirectories: a project path that now names a file is as // unopenable as one that names nothing, so both have to read as gone. func TestExistsAcceptsOnlyDirectories(t *testing.T) { + t.Parallel() svc := New(nil) dir := t.TempDir() file := filepath.Join(dir, "file.txt") @@ -469,6 +483,7 @@ func TestExistsAcceptsOnlyDirectories(t *testing.T) { // TestMissingNamesOnlyTheGoneDirectories: the answer is what the lists mark, so // a path that is still there must never appear in it. func TestMissingNamesOnlyTheGoneDirectories(t *testing.T) { + t.Parallel() svc := New(nil) dir := t.TempDir() gone := filepath.Join(dir, "gone") @@ -487,6 +502,7 @@ func TestMissingNamesOnlyTheGoneDirectories(t *testing.T) { // would hand back a project the store has never seen, and the sessions and the // worktree directory of the one that moved would stay with the dead id. func TestRelocateKeepsTheProjectIdentity(t *testing.T) { + t.Parallel() moved := filepath.Join(t.TempDir(), "renamed") picker := &pickerStub{dirPath: moved} svc := New(picker) @@ -514,6 +530,7 @@ func TestRelocateKeepsTheProjectIdentity(t *testing.T) { // on one directory under different ids is a workspace no path-addressed lookup // can read — and no undo puts the sessions back afterwards. func TestRelocateRefusesADirectoryAnotherProjectHolds(t *testing.T) { + t.Parallel() taken := filepath.Join(t.TempDir(), "taken") svc := New(&pickerStub{dirPath: taken}) svc.SetProjects(func(path string) (string, string) { @@ -537,6 +554,7 @@ func TestRelocateRefusesADirectoryAnotherProjectHolds(t *testing.T) { // is the relocation being repeated, not a collision, and refusing it would // dead-end the only flow that can fix a project. func TestRelocateAcceptsTheProjectsOwnDirectory(t *testing.T) { + t.Parallel() own := filepath.Join(t.TempDir(), "own") svc := New(&pickerStub{dirPath: own}) svc.SetProjects(func(string) (string, string) { return "stored-id", "own" }) @@ -550,6 +568,7 @@ func TestRelocateAcceptsTheProjectsOwnDirectory(t *testing.T) { // TestRelocateCancelledChangesNothing: a cancelled dialog must not read as a // chosen directory — the caller writes whatever comes back over the stored row. func TestRelocateCancelledChangesNothing(t *testing.T) { + t.Parallel() project, err := New(&pickerStub{}).Relocate("stored-id") if err != nil || project != nil { t.Fatalf("Relocate(cancelled) = %+v, %v; want nil, nil", project, err) diff --git a/internal/project/prreview_test.go b/internal/project/prreview_test.go index 37138a7d..daba5487 100644 --- a/internal/project/prreview_test.go +++ b/internal/project/prreview_test.go @@ -10,6 +10,7 @@ import ( ) func TestReviewPayload(t *testing.T) { + t.Parallel() t.Run("a plain approval carries no body and no comments", func(t *testing.T) { payload, err := reviewPayload("approve", "", "abc123", nil) if err != nil { @@ -79,6 +80,7 @@ func TestReviewPayload(t *testing.T) { } func TestSubmitReviewFlow(t *testing.T) { + t.Parallel() t.Run("the review reaches gh as a JSON body, and the file does not outlive it", func(t *testing.T) { gh := &fakeGH{} comments := []ReviewComment{{Path: "a.go", Line: 4, Body: "why"}} @@ -151,6 +153,7 @@ func TestSubmitReviewFlow(t *testing.T) { } func TestReplyToReviewThreadFlow(t *testing.T) { + t.Parallel() t.Run("a reply is addressed to the comment's database id", func(t *testing.T) { gh := &fakeGH{} if err := withGH(gh).ReplyToReviewThread("/repo", 42, 900001, "dropped it"); err != nil { @@ -178,6 +181,7 @@ func TestReplyToReviewThreadFlow(t *testing.T) { } func TestResolveReviewThreadFlow(t *testing.T) { + t.Parallel() t.Run("resolving and reopening are two mutations, one node id", func(t *testing.T) { gh := &fakeGH{} if err := withGH(gh).ResolveReviewThread("/repo", "PRRT_kw1", true); err != nil { @@ -208,6 +212,7 @@ func TestResolveReviewThreadFlow(t *testing.T) { } func TestDismissReviewFlow(t *testing.T) { + t.Parallel() t.Run("the node id and the reason both reach the mutation", func(t *testing.T) { gh := &fakeGH{} if err := withGH(gh).DismissReview("/repo", "PRR_kw1", "fixed in 4a1c2f0"); err != nil { @@ -245,6 +250,7 @@ func TestDismissReviewFlow(t *testing.T) { } func TestCommentOnPullRequestFlow(t *testing.T) { + t.Parallel() t.Run("a numbered pull request is addressed by number", func(t *testing.T) { gh := &fakeGH{} if err := withGH(gh).CommentOnPullRequest("/repo", 42, "rebased"); err != nil { diff --git a/internal/project/prreviewers_test.go b/internal/project/prreviewers_test.go index 0b0da08c..a0409319 100644 --- a/internal/project/prreviewers_test.go +++ b/internal/project/prreviewers_test.go @@ -7,6 +7,7 @@ import ( ) func TestToReviewers(t *testing.T) { + t.Parallel() t.Run("the two halves merge into one roster", func(t *testing.T) { reviewers := toReviewers( []ghLatestReview{{Author: ghPRAuthor{Login: "ana"}, State: "APPROVED"}}, @@ -62,6 +63,7 @@ func TestToReviewers(t *testing.T) { } func TestParsePRDetailReviewers(t *testing.T) { + t.Parallel() out := []byte(`{ "number": 7, "state": "OPEN", @@ -86,6 +88,7 @@ func TestParsePRDetailReviewers(t *testing.T) { } func TestAssignableReviewers(t *testing.T) { + t.Parallel() t.Run("the nodes are the picker's list", func(t *testing.T) { gh := &fakeGH{out: []byte(`{"data":{"repository":{"assignableUsers":{"nodes":[ {"login":"ana","name":"Ana"},{"login":"bo","name":""}]}}}}`)} @@ -125,6 +128,7 @@ func TestAssignableReviewers(t *testing.T) { } func TestRequestReview(t *testing.T) { + t.Parallel() t.Run("asking adds the reviewer to the numbered pull request", func(t *testing.T) { gh := &fakeGH{} if err := withGH(gh).RequestReview("/repo", 12, "ana", true); err != nil { @@ -161,6 +165,7 @@ func TestRequestReview(t *testing.T) { } func TestEditPullRequestBody(t *testing.T) { + t.Parallel() t.Run("the body replaces the description of the numbered PR", func(t *testing.T) { gh := &fakeGH{} if err := withGH(gh).EditPullRequestBody("/repo", 12, "what it does"); err != nil { diff --git a/internal/project/prthreads_test.go b/internal/project/prthreads_test.go index fab3903a..4c64341c 100644 --- a/internal/project/prthreads_test.go +++ b/internal/project/prthreads_test.go @@ -38,6 +38,7 @@ const conversationPayload = `{"data":{"repository":{"pullRequest":{ }}}}` func TestParseConversation(t *testing.T) { + t.Parallel() convo, err := parseConversation([]byte(conversationPayload)) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -127,6 +128,7 @@ func TestParseConversation(t *testing.T) { } func TestParseConversationEdges(t *testing.T) { + t.Parallel() t.Run("a pull request nobody has said anything about", func(t *testing.T) { convo, err := parseConversation([]byte(`{"data":{"repository":{"pullRequest":{ "headRefOid":"abc","reviews":{"nodes":[]},"comments":{"nodes":[]},"reviewThreads":{"nodes":[]}}}}}`)) @@ -150,6 +152,7 @@ func TestParseConversationEdges(t *testing.T) { } func TestPullRequestConversationFlow(t *testing.T) { + t.Parallel() t.Run("the query is addressed by number and scoped to the path", func(t *testing.T) { gh := &fakeGH{out: []byte(conversationPayload)} convo, err := withGH(gh).PullRequestConversation("/repo", 104) diff --git a/internal/project/revertlines_test.go b/internal/project/revertlines_test.go index 23be8552..babd11bb 100644 --- a/internal/project/revertlines_test.go +++ b/internal/project/revertlines_test.go @@ -40,6 +40,7 @@ func readFile(t *testing.T, repo, rel string) string { } func TestRevertLinesAddition(t *testing.T) { + t.Parallel() repo, git := revertRepo(t, strings.Replace(revertBase, "bravo\n", "bravo\nNEW1\n", 1)+"TAIL\n") _, err := New(nil).RevertLines(repo, "code.txt", []RevertLine{{Side: "new", Line: 3, Text: "NEW1"}}) @@ -57,6 +58,7 @@ func TestRevertLinesAddition(t *testing.T) { // TestRevertLinesLatin1 picks a line by the text the review shows, decoded // from Latin-1, while git's diff holds the raw bytes. func TestRevertLinesLatin1(t *testing.T) { + t.Parallel() for _, staged := range []bool{false, true} { repo, git := revertRepo(t, strings.Replace(revertBase, "bravo\n", "bravo\nservi\xe7os\n", 1)) if staged { @@ -76,6 +78,7 @@ func TestRevertLinesLatin1(t *testing.T) { // as it comes back from a client: through JSON, which would turn each raw byte // into U+FFFD if the patch traveled as text. func TestRestoreLinesLatin1AfterTheWire(t *testing.T) { + t.Parallel() changed := strings.Replace(revertBase, "bravo\n", "bravo\nservi\xe7os\n", 1) repo, git := revertRepo(t, changed) git("add", "code.txt") @@ -104,6 +107,7 @@ func TestRestoreLinesLatin1AfterTheWire(t *testing.T) { } func TestRevertLinesDeletion(t *testing.T) { + t.Parallel() repo, _ := revertRepo(t, strings.Replace(revertBase, "delta\n", "", 1)) _, err := New(nil).RevertLines(repo, "code.txt", []RevertLine{{Side: "old", Line: 4, Text: "delta"}}) @@ -118,6 +122,7 @@ func TestRevertLinesDeletion(t *testing.T) { // TestRevertLinesPartOfABlock reverts the additions of a replace block and keeps // its replacement line: the unpicked deletion must not come back. func TestRevertLinesPartOfABlock(t *testing.T) { + t.Parallel() edited := strings.Replace(revertBase, "charlie\n", "CHARLIE\nextra1\nextra2\n", 1) repo, _ := revertRepo(t, edited) @@ -137,6 +142,7 @@ func TestRevertLinesPartOfABlock(t *testing.T) { // TestRevertLinesStagedChange reverts a fully staged change in the index too, so // it cannot ride the next commit invisibly. func TestRevertLinesStagedChange(t *testing.T) { + t.Parallel() repo, git := revertRepo(t, strings.Replace(revertBase, "echo\n", "ECHO\n", 1)) git("add", "code.txt") @@ -158,6 +164,7 @@ func TestRevertLinesStagedChange(t *testing.T) { // TestRevertLinesOtherHunkStaged keeps the index out when what is staged is a // different change of the same file. func TestRevertLinesOtherHunkStaged(t *testing.T) { + t.Parallel() repo, git := revertRepo(t, strings.Replace(revertBase, "alpha\n", "ALPHA\n", 1)) git("add", "code.txt") writeFile(t, repo, "code.txt", strings.Replace(readFile(t, repo, "code.txt"), "hotel\n", "HOTEL\n", 1)) @@ -178,6 +185,7 @@ func TestRevertLinesOtherHunkStaged(t *testing.T) { } func TestRevertLinesStagedDiverged(t *testing.T) { + t.Parallel() repo, git := revertRepo(t, strings.Replace(revertBase, "golf\n", "GOLF-staged\n", 1)) git("add", "code.txt") edited := strings.Replace(revertBase, "golf\n", "GOLF-edited\n", 1) @@ -196,6 +204,7 @@ func TestRevertLinesStagedDiverged(t *testing.T) { } func TestRevertLinesStale(t *testing.T) { + t.Parallel() repo, _ := revertRepo(t, strings.Replace(revertBase, "bravo\n", "bravo\nNEW1\n", 1)) cases := map[string]RevertLine{ "text moved": {Side: "new", Line: 3, Text: "something else"}, @@ -212,6 +221,7 @@ func TestRevertLinesStale(t *testing.T) { } func TestRevertLinesRejectsBadInput(t *testing.T) { + t.Parallel() repo, _ := revertRepo(t, revertBase+"TAIL\n") s := New(nil) if _, err := s.RevertLines(repo, "../escape.txt", []RevertLine{{Side: "new", Line: 9, Text: "TAIL"}}); err == nil { @@ -226,6 +236,7 @@ func TestRevertLinesRejectsBadInput(t *testing.T) { } func TestRevertLinesUntracked(t *testing.T) { + t.Parallel() repo, git := initRepo(t) writeFile(t, repo, "new file.txt", "one\ntwo\nthree\n") @@ -242,6 +253,7 @@ func TestRevertLinesUntracked(t *testing.T) { } func TestRevertLinesNoNewlineAtEOF(t *testing.T) { + t.Parallel() repo, _ := revertRepo(t, strings.TrimSuffix(revertBase, "\n")+"\nindia") _, err := New(nil).RevertLines(repo, "code.txt", []RevertLine{{Side: "new", Line: 9, Text: "india"}}) @@ -256,6 +268,7 @@ func TestRevertLinesNoNewlineAtEOF(t *testing.T) { // TestRestoreLinesUndoesRevert round-trips both a working-tree-only and a staged // revert back to where they started. func TestRestoreLinesUndoesRevert(t *testing.T) { + t.Parallel() for _, staged := range []bool{false, true} { edited := strings.Replace(revertBase, "foxtrot\n", "FOXTROT\n", 1) repo, git := revertRepo(t, edited) @@ -284,6 +297,7 @@ func TestRestoreLinesUndoesRevert(t *testing.T) { } func TestRestoreLinesRefusesForeignPatch(t *testing.T) { + t.Parallel() repo, _ := revertRepo(t, revertBase) s := New(nil) foreign := RevertResult{Patch: patchFileHeader("a.txt") + "@@ -1 +1 @@\n-one\n+two\n"} @@ -302,6 +316,7 @@ func TestRestoreLinesRefusesForeignPatch(t *testing.T) { // TestRevertLinesStagedWithUnstagedNeighbour reverts a staged change whose hunk // also holds an unstaged one: both sides are reverted, and the neighbour stays. func TestRevertLinesStagedWithUnstagedNeighbour(t *testing.T) { + t.Parallel() repo, git := revertRepo(t, strings.Replace(revertBase, "bravo\n", "BRAVO\n", 1)) git("add", "code.txt") writeFile(t, repo, "code.txt", strings.Replace(readFile(t, repo, "code.txt"), "delta\n", "delta\nextra\n", 1)) @@ -327,6 +342,7 @@ func TestRevertLinesStagedWithUnstagedNeighbour(t *testing.T) { // TestRevertLinesStagedNewFile reverts a line of a file HEAD never had but the // index does. func TestRevertLinesStagedNewFile(t *testing.T) { + t.Parallel() repo, git := initRepo(t) writeFile(t, repo, "fresh.txt", "one\ntwo\n") git("add", "fresh.txt") @@ -347,6 +363,7 @@ func TestRevertLinesStagedNewFile(t *testing.T) { // committed: the file has nothing left to diff, and the line the panel names // still sits there with the same text. It must be refused, not deleted. func TestRevertLinesCleanTrackedFile(t *testing.T) { + t.Parallel() repo, _ := revertRepo(t, revertBase) _, err := New(nil).RevertLines(repo, "code.txt", []RevertLine{{Side: "new", Line: 2, Text: "bravo"}}) @@ -361,6 +378,7 @@ func TestRevertLinesCleanTrackedFile(t *testing.T) { // TestRevertLinesGitFailure keeps a git that cannot read the checkout from // passing for an empty diff. func TestRevertLinesGitFailure(t *testing.T) { + t.Parallel() repo, _ := revertRepo(t, strings.Replace(revertBase, "bravo\n", "bravo\nNEW1\n", 1)) if err := os.WriteFile(filepath.Join(repo, ".git", "index"), []byte("not an index"), 0o644); err != nil { t.Fatal(err) diff --git a/internal/project/revertpatch_test.go b/internal/project/revertpatch_test.go index 7ded8136..6e5c61eb 100644 --- a/internal/project/revertpatch_test.go +++ b/internal/project/revertpatch_test.go @@ -20,6 +20,7 @@ index 1111111..2222222 100644 ` func TestParseHunks(t *testing.T) { + t.Parallel() hunks, err := parseHunks(sampleDiff) if err != nil { t.Fatal(err) @@ -37,6 +38,7 @@ func TestParseHunks(t *testing.T) { } func TestParseHunksMalformed(t *testing.T) { + t.Parallel() for name, diff := range map[string]string{ "short hunk": "@@ -1,3 +1,3 @@\n one\n", "bad header": "@@ -x +1 @@\n one\n", @@ -49,6 +51,7 @@ func TestParseHunksMalformed(t *testing.T) { } func TestPartialPatch(t *testing.T) { + t.Parallel() hunks, err := parseHunks(sampleDiff) if err != nil { t.Fatal(err) @@ -64,6 +67,7 @@ func TestPartialPatch(t *testing.T) { } func TestOldLineOf(t *testing.T) { + t.Parallel() hunks, err := parseHunks("@@ -2,2 +2,3 @@\n b\n+x\n c\n@@ -10,2 +11,1 @@\n j\n-k\n") if err != nil { t.Fatal(err) @@ -80,6 +84,7 @@ func TestOldLineOf(t *testing.T) { } func TestPartialPatchEmptySide(t *testing.T) { + t.Parallel() hunks, err := parseHunks("@@ -0,0 +1,2 @@\n+a\n+b\n") if err != nil { t.Fatal(err) @@ -95,6 +100,7 @@ func TestPartialPatchEmptySide(t *testing.T) { } func TestPartialPatchStale(t *testing.T) { + t.Parallel() hunks, _ := parseHunks(sampleDiff) if _, err := partialPatch("f.txt", hunks, []RevertLine{{Side: "old", Line: 2, Text: "zwei"}}); !errors.Is(err, ErrLinesMoved) { t.Errorf("err = %v, want ErrLinesMoved", err) diff --git a/internal/project/search_test.go b/internal/project/search_test.go index 280fc4ec..3d75d1fe 100644 --- a/internal/project/search_test.go +++ b/internal/project/search_test.go @@ -20,6 +20,7 @@ func matchKeys(matches []SearchMatch) string { // listing: tracked and untracked files hit, an ignored one never does, in the // tree's order, with 1-based lines and the indentation dropped. func TestSearchCoversWhatTheTreeLists(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "src/main.go", "package main\n\n\tfunc Needle() {}\n") write(t, repo, ".gitignore", "ignored.txt\n") @@ -49,6 +50,7 @@ func TestSearchCoversWhatTheTreeLists(t *testing.T) { // TestSearchIsLiteral proves a query is text, not a pattern: regexp // metacharacters match themselves. func TestSearchIsLiteral(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "a.txt", "a.b\naxb\n") got, err := New(nil).Search(repo, "a.b") @@ -62,6 +64,7 @@ func TestSearchIsLiteral(t *testing.T) { // TestSearchWalksPlainFolder proves search works where the tree does, without git. func TestSearchWalksPlainFolder(t *testing.T) { + t.Parallel() dir := t.TempDir() write(t, dir, "notes.md", "find me\n") write(t, dir, "node_modules/dep.js", "find me\n") @@ -77,6 +80,7 @@ func TestSearchWalksPlainFolder(t *testing.T) { // TestSearchSkipsWhatThePreviewRefuses proves every hit is a file the preview // opens: binaries, oversize files and symlinks never match. func TestSearchSkipsWhatThePreviewRefuses(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "bin", "needle\x00") write(t, repo, "big", "needle"+strings.Repeat("x", maxReadFileSize)) @@ -99,6 +103,7 @@ func TestSearchSkipsWhatThePreviewRefuses(t *testing.T) { // TestSearchStopsAtTheCap proves a loose query stops at maxSearchMatches and // says so. func TestSearchStopsAtTheCap(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "many.txt", strings.Repeat("hit\n", maxSearchMatches+10)) write(t, repo, "zz.txt", "hit\n") @@ -113,6 +118,7 @@ func TestSearchStopsAtTheCap(t *testing.T) { // TestSearchRefusesEmptyQuery proves a blank query is an error, not every line. func TestSearchRefusesEmptyQuery(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if _, err := New(nil).Search(repo, " "); err == nil { t.Error("Search(blank): want error, got nil") @@ -122,6 +128,7 @@ func TestSearchRefusesEmptyQuery(t *testing.T) { // TestSearchReportsMissingDir proves a checkout that is gone is an error, not // "no matches". func TestSearchReportsMissingDir(t *testing.T) { + t.Parallel() if _, err := New(nil).Search(filepath.Join(t.TempDir(), "gone"), "x"); err == nil { t.Error("Search(missing dir): want error, got nil") } @@ -131,6 +138,7 @@ func TestSearchReportsMissingDir(t *testing.T) { // what precedes it survives, the cut never splits a rune, and each cut side is // marked. func TestExcerpt(t *testing.T) { + t.Parallel() short := "short line" if got := excerpt(short, 0); got != short { t.Errorf("excerpt(short) = %q, want it whole", got) diff --git a/internal/project/seed_test.go b/internal/project/seed_test.go index 71ffe3dd..6fa9c4a0 100644 --- a/internal/project/seed_test.go +++ b/internal/project/seed_test.go @@ -13,6 +13,7 @@ import ( // repo-relative path, and an invalid pattern matches nothing instead of // panicking. func TestMatchesInclude(t *testing.T) { + t.Parallel() tests := []struct { name string rel string @@ -41,6 +42,7 @@ func TestMatchesInclude(t *testing.T) { // default, a present file governs alone (comments and blanks skipped), and an // empty file means seed nothing. func TestIncludePatterns(t *testing.T) { + t.Parallel() dir := t.TempDir() if got := includePatterns(dir); !slices.Equal(got, defaultIncludes) { t.Errorf("no file: patterns = %v, want %v", got, defaultIncludes) @@ -69,6 +71,7 @@ func TestIncludePatterns(t *testing.T) { // off a file nobody could read would copy exactly what the override may exist // to keep out of the worktree. func TestIncludePatternsRefusesAnUnreadableFile(t *testing.T) { + t.Parallel() dir := t.TempDir() path := filepath.Join(dir, includeFile) if err := os.WriteFile(path, []byte("config/*.json\n"), 0o644); err != nil { diff --git a/internal/project/setup_test.go b/internal/project/setup_test.go index 5e651450..b83688d1 100644 --- a/internal/project/setup_test.go +++ b/internal/project/setup_test.go @@ -21,6 +21,7 @@ func writeSetup(t *testing.T, dir, script string) { // repository without one answers "" — which is what tells wrapSetup not to // wrap at all. func TestSetupScriptReadsTheRepoFile(t *testing.T) { + t.Parallel() dir := t.TempDir() if got := SetupScript(dir); got != "" { t.Errorf("missing file: got %q, want empty", got) @@ -34,6 +35,7 @@ func TestSetupScriptReadsTheRepoFile(t *testing.T) { // A whitespace-only file must read as "no script": wrapping the spawn to run // nothing would still make the session wait out a marker for an empty setup. func TestSetupScriptTreatsBlankFileAsAbsent(t *testing.T) { + t.Parallel() dir := t.TempDir() writeSetup(t, dir, "\n \n") if got := SetupScript(dir); got != "" { @@ -44,6 +46,7 @@ func TestSetupScriptTreatsBlankFileAsAbsent(t *testing.T) { // TestWorktreeSetupPrefersTheFile: with a file present the dialog shows it and // no suggestion is offered — detection only speaks when nothing is configured. func TestWorktreeSetupPrefersTheFile(t *testing.T) { + t.Parallel() svc := &Service{} dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, "pnpm-lock.yaml"), nil, 0o644); err != nil { @@ -63,6 +66,7 @@ func TestWorktreeSetupPrefersTheFile(t *testing.T) { // TestWorktreeSetupDetection pins the probe table's visible behaviour: which // root files speak, what they propose, and that multiple hits chain. func TestWorktreeSetupDetection(t *testing.T) { + t.Parallel() svc := &Service{} tests := []struct { name string @@ -103,6 +107,7 @@ func TestWorktreeSetupDetection(t *testing.T) { // with its directory, read back by SetupScript, and an empty save removes it // instead of pinning an empty setup. func TestSaveWorktreeSetup(t *testing.T) { + t.Parallel() svc := &Service{} dir := t.TempDir() diff --git a/internal/project/status_test.go b/internal/project/status_test.go index feb943f3..a0d2eec8 100644 --- a/internal/project/status_test.go +++ b/internal/project/status_test.go @@ -25,6 +25,7 @@ func v2(records ...string) string { // with no commits "(initial)" where the diff base is git's empty tree — mapped // straight through, the badge would read a literal "(detached)". func TestParseWorkTreeBranchAndHead(t *testing.T) { + t.Parallel() oid := "6d19bac07168c90c890aab654501e68498ac4d57" tests := []struct { name string @@ -84,6 +85,7 @@ func TestParseWorkTreeBranchAndHead(t *testing.T) { // original path as a second record of its own, so a naive split counts one // renamed file twice. func TestParseWorkTreeCountsEveryDirtyEntryOnce(t *testing.T) { + t.Parallel() oid := "6d19bac07168c90c890aab654501e68498ac4d57" blobs := "45b983be36b73c0788dc9cbcb76cbb80fc7bb057 45b983be36b73c0788dc9cbcb76cbb80fc7bb057" tests := []struct { @@ -153,6 +155,7 @@ func TestParseWorkTreeCountsEveryDirtyEntryOnce(t *testing.T) { // record it cannot read has to cost that record alone. Nothing here may panic, // and the headers must still land. func TestParseWorkTreeSurvivesMalformedOutput(t *testing.T) { + t.Parallel() tests := []struct { name string out string @@ -183,6 +186,7 @@ func TestParseWorkTreeSurvivesMalformedOutput(t *testing.T) { // pair diffBase did, so DiffText's `git diff ` still fails as a git error // rather than on some new shape. func TestReadWorkTreeOnANonRepository(t *testing.T) { + t.Parallel() got := readWorkTree(t.TempDir()) if got.branch != "" || got.head != "" || got.files != 0 || got.untracked != nil { t.Errorf("readWorkTree(non-repo) = %+v, want the zero readout", got) @@ -196,6 +200,7 @@ func TestReadWorkTreeOnANonRepository(t *testing.T) { // the table tests above pin shapes captured from git 2.55, and this is what // notices when a git in the wild stops writing them. func TestReadWorkTreeAgainstRealGit(t *testing.T) { + t.Parallel() repo, git := initRepo(t) clean := readWorkTree(repo) @@ -271,6 +276,7 @@ func subRepo(t *testing.T) (repo, sub string) { // scoped to it, and both callers still join these paths onto the project path, // so the readout has to answer the way ls-files did. func TestReadWorkTreeFromASubdirectory(t *testing.T) { + t.Parallel() _, sub := subRepo(t) got := readWorkTree(sub) @@ -291,6 +297,7 @@ func TestReadWorkTreeFromASubdirectory(t *testing.T) { // overwhelmingly common case, and every entry must come back byte for byte as // git wrote it. func TestReadWorkTreeAtTheRepositoryRootIsUntouched(t *testing.T) { + t.Parallel() repo, _ := subRepo(t) got := readWorkTree(repo) @@ -348,6 +355,7 @@ func TestReadWorkTreeSpendsNoPrefixCallOnACleanTree(t *testing.T) { // on its card: the untracked lines under it are counted, and the untracked file // beside it — outside the directory the caller asked about — is not. func TestDiffFromASubdirectory(t *testing.T) { + t.Parallel() _, sub := subRepo(t) got := New(nil).Diff(sub) @@ -365,6 +373,7 @@ func TestDiffFromASubdirectory(t *testing.T) { // project renders its untracked files as new-file hunks, and shows nothing from // outside the directory. func TestDiffTextFromASubdirectory(t *testing.T) { + t.Parallel() _, sub := subRepo(t) out, err := New(nil).DiffText(sub) diff --git a/internal/project/tree_test.go b/internal/project/tree_test.go index 2cf60b43..fa3e38f5 100644 --- a/internal/project/tree_test.go +++ b/internal/project/tree_test.go @@ -15,6 +15,7 @@ import ( // repo-relative and slash-separated, untracked-but-not-ignored files included, // .gitignore'd files excluded, all sorted. func TestTree(t *testing.T) { + t.Parallel() repo, git := initRepo(t) mkdir(t, repo, "internal/rpc") write(t, repo, "internal/rpc/rpc.go", "package rpc\n") @@ -43,6 +44,7 @@ func TestTree(t *testing.T) { // TestTreeDropsDeleted proves a tracked file removed from disk (but not yet // staged) disappears from the tree, so the list is not frozen at HEAD. func TestTreeDropsDeleted(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if err := os.Remove(filepath.Join(repo, "a.txt")); err != nil { t.Fatal(err) @@ -61,6 +63,7 @@ func TestTreeDropsDeleted(t *testing.T) { // and .git skipped whole. The contract changed here — a non-repository path used // to be an error, matching DiffText; browsing files is not a git feature. func TestTreeWalksNonRepo(t *testing.T) { + t.Parallel() dir := t.TempDir() write(t, dir, "b.txt", "b\n") write(t, dir, "src/main.go", "package main\n") @@ -85,6 +88,7 @@ func TestTreeWalksNonRepo(t *testing.T) { // rev-parse, so its ls-files failure is the answer. Walking it instead would // draw a plausible tree over a repository nobody can list. func TestTreeReportsBrokenRepo(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, ".git/index", "not an index") tree, err := New(nil).Tree(repo) @@ -96,6 +100,7 @@ func TestTreeReportsBrokenRepo(t *testing.T) { // TestTreeMissingDir proves an absent directory is still an error, so the panel // says so instead of drawing an empty tree over a folder that is gone. func TestTreeMissingDir(t *testing.T) { + t.Parallel() if _, err := New(nil).Tree(filepath.Join(t.TempDir(), "nope")); err == nil { t.Error("Tree on missing dir: want error, got nil") } @@ -107,6 +112,7 @@ func TestTreeMissingDir(t *testing.T) { // listing is partial. walkLimit's own value is pinned as a literal (deriving // it would make the test follow the constant instead of pinning it). func TestWalkFilesStopsAtLimit(t *testing.T) { + t.Parallel() if walkLimit != 20000 { t.Errorf("walkLimit = %d, want 20000", walkLimit) } @@ -131,6 +137,7 @@ func TestWalkFilesStopsAtLimit(t *testing.T) { // name matches an ignored directory is a file, and a directory whose name only // contains one is not the one being skipped. func TestWalkIgnoresDependencyDirs(t *testing.T) { + t.Parallel() dir := t.TempDir() write(t, dir, "main.go", "package main\n") write(t, dir, "node_modules/left-pad/index.js", "x") @@ -167,6 +174,7 @@ func TestWalkIgnoresDependencyDirs(t *testing.T) { // in it that is not on disk sends the reader looking for a directory that was // never there. func TestWalkHiddenNamesOnlyWhatIsThere(t *testing.T) { + t.Parallel() dir := t.TempDir() write(t, dir, "main.go", "package main\n") write(t, dir, "dist/out.js", "x") @@ -195,6 +203,7 @@ func TestWalkHiddenNamesOnlyWhatIsThere(t *testing.T) { // in the panel, and a name silently added is a directory of somebody's source // gone from it. func TestWalkIgnoreNames(t *testing.T) { + t.Parallel() want := []string{ ".cache", ".git", ".venv", "__pycache__", "build", "dist", "node_modules", "target", "vendor", "venv", @@ -207,6 +216,7 @@ func TestWalkIgnoreNames(t *testing.T) { // TestReadFile proves a tracked text file's bytes come back verbatim. func TestReadFile(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) got, err := New(nil).ReadFile(repo, "a.txt") if err != nil { @@ -220,6 +230,7 @@ func TestReadFile(t *testing.T) { // TestReadFileDecodesLatin1 covers a file saved in ISO-8859-1: its accented // bytes are not UTF-8, and handed over raw they reach the viewer as U+FFFD. func TestReadFileDecodesLatin1(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "menu.xml", "Log\xedstica Servi\xe7os Requisi\xe7\xe3o\n") got, err := New(nil).ReadFile(repo, "menu.xml") @@ -234,6 +245,7 @@ func TestReadFileDecodesLatin1(t *testing.T) { // TestReadFileRejectsEscape proves traversal and absolute paths never reach the // filesystem, mirroring DiscardFile's guard. func TestReadFileRejectsEscape(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) for _, rel := range []string{"../outside.txt", "/etc/passwd", "a/../../b"} { if _, err := New(nil).ReadFile(repo, rel); err == nil { @@ -250,6 +262,7 @@ func TestReadFileRejectsEscape(t *testing.T) { // genuinely ship — git records it as its target text, and a preview that // followed it would print a file the diff beside it never mentions. func TestReadFileRejectsALinkOutOfTheCheckout(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) outside := filepath.Join(t.TempDir(), "secret.txt") if err := os.WriteFile(outside, []byte("not the repository's\n"), 0o600); err != nil { @@ -266,6 +279,7 @@ func TestReadFileRejectsALinkOutOfTheCheckout(t *testing.T) { // TestReadFileRejectsBinary proves a NUL-bearing file is refused rather than // streamed into the text preview. func TestReadFileRejectsBinary(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "bin", "abc\x00def") if _, err := New(nil).ReadFile(repo, "bin"); err == nil { @@ -275,6 +289,7 @@ func TestReadFileRejectsBinary(t *testing.T) { // TestReadFileRejectsLarge proves a file above the size cap is refused. func TestReadFileRejectsLarge(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) write(t, repo, "big", strings.Repeat("x", maxReadFileSize+1)) if _, err := New(nil).ReadFile(repo, "big"); err == nil { @@ -288,6 +303,7 @@ func TestReadFileRejectsLarge(t *testing.T) { // 8000 on purpose — deriving them from binarySniffBytes would make the test // follow the constant instead of pinning it. func TestIsBinaryWindow(t *testing.T) { + t.Parallel() nulAt := func(offset int) []byte { data := bytes.Repeat([]byte("x"), offset+1) data[offset] = 0 @@ -315,6 +331,7 @@ func TestIsBinaryWindow(t *testing.T) { // TestReadFileMissing proves an absent (but path-valid) file is an error. func TestReadFileMissing(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if _, err := New(nil).ReadFile(repo, "nope.txt"); err == nil { t.Error("ReadFile(missing): want error, got nil") diff --git a/internal/project/turnsnap_test.go b/internal/project/turnsnap_test.go index b3e044ac..a0947823 100644 --- a/internal/project/turnsnap_test.go +++ b/internal/project/turnsnap_test.go @@ -29,6 +29,7 @@ func digest(t *testing.T, path string) string { // The whole point of the pair: every kind of change a turn can make to the // working tree reaches the rendered diff, whether or not the user staged it. func TestSnapshotPairRendersEveryChange(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "sub/b.txt", "keep\n") git("add", "-A") @@ -72,6 +73,7 @@ func TestSnapshotPairRendersEveryChange(t *testing.T) { // The one comparison the panel makes without running a diff: a turn that left // the checkout as it found it has to be recognisable by the oids alone. func TestSnapshotTreeIsStableWhenNothingChanged(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) index := snapIndex(t) @@ -92,6 +94,7 @@ func TestSnapshotTreeIsStableWhenNothingChanged(t *testing.T) { // halfway through staging, and moving HEAD or their index would be data loss // they never asked for. func TestSnapshotTreeLeavesTheRepositoryAlone(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "staged.txt", "s\n") git("add", "staged.txt") @@ -125,6 +128,7 @@ func TestSnapshotTreeLeavesTheRepositoryAlone(t *testing.T) { // in one view and missing from the other with nothing saying why. DiffText's // untracked pass obeys .gitignore; `add -A` does too, and this pins it. func TestSnapshotTreeSkipsIgnoredFiles(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, ".gitignore", "build/\n") git("add", "-A") @@ -151,6 +155,7 @@ func TestSnapshotTreeSkipsIgnoredFiles(t *testing.T) { // repository it promised not to touch. Refused rather than resolved, because // the caller that passed one is the bug. func TestSnapshotTreeRefusesARelativeIndex(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) if _, err := SnapshotTree(repo, "turn.idx"); err == nil { t.Fatal("a relative index path was accepted") @@ -160,6 +165,7 @@ func TestSnapshotTreeRefusesARelativeIndex(t *testing.T) { // The index lives under lich's own directory, which need not exist yet on a // fresh install — the first snapshot of the first session is what creates it. func TestSnapshotTreeCreatesItsOwnDirectory(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) index := filepath.Join(t.TempDir(), "turns", "s1.idx") @@ -175,6 +181,7 @@ func TestSnapshotTreeCreatesItsOwnDirectory(t *testing.T) { // has no trees to compare, and the panel has to hear that as an error rather // than as a turn that changed nothing. func TestSnapshotTreeOutsideARepository(t *testing.T) { + t.Parallel() if _, err := SnapshotTree(t.TempDir(), snapIndex(t)); err == nil { t.Fatal("SnapshotTree outside a repository: want an error, got nil") } @@ -184,6 +191,7 @@ func TestSnapshotTreeOutsideARepository(t *testing.T) { // `git diff` behind DiffText does. Without -M the two sources would describe the // same change differently — a rename in one, a delete plus an add in the other. func TestTreeDiffDetectsARename(t *testing.T) { + t.Parallel() repo, git := initRepo(t) write(t, repo, "long.txt", strings.Repeat("a stable line of content\n", 20)) git("add", "-A") @@ -215,6 +223,7 @@ func TestTreeDiffDetectsARename(t *testing.T) { // the pair would otherwise look taken and the panel would report a turn that // changed nothing when what actually happened is that nothing was recorded. func TestSnapshotTreeReportsAnUnusableIndexLocation(t *testing.T) { + t.Parallel() repo, _ := initRepo(t) // A file where the index's directory should be, which is the shape a // misconfigured config directory takes on disk. diff --git a/internal/project/waitdelay_test.go b/internal/project/waitdelay_test.go index c2a9c4b3..2f8260f4 100644 --- a/internal/project/waitdelay_test.go +++ b/internal/project/waitdelay_test.go @@ -18,6 +18,7 @@ const helperEnv = "LICH_WAITDELAY_HELPER" // exits at once; as "grandchild" it holds that pipe open far past any budget // the parent could set. A plain run does nothing. func TestSubprocessHelper(t *testing.T) { + t.Parallel() switch os.Getenv(helperEnv) { case "child": grandchild := exec.Command(os.Args[0], "-test.run=TestSubprocessHelper") @@ -38,6 +39,7 @@ func TestSubprocessHelper(t *testing.T) { // seconds: with nothing closing that pipe, Output waits all ten and reports // success, deadline or none. func TestSubprocessBudgetOutlivesAGrandchild(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() cmd := commandContext(ctx, os.Args[0], "-test.run=TestSubprocessHelper") diff --git a/internal/project/worktree_test.go b/internal/project/worktree_test.go index 476863b4..e83d8bcd 100644 --- a/internal/project/worktree_test.go +++ b/internal/project/worktree_test.go @@ -162,6 +162,7 @@ func TestListBranches(t *testing.T) { // TestParseWorktrees proves the porcelain parser skips the main worktree and // bare/detached entries and keeps linked worktrees on a branch. func TestParseWorktrees(t *testing.T) { + t.Parallel() tests := []struct { name string out string @@ -864,6 +865,7 @@ func TestCreateWorktreeFromPR(t *testing.T) { // TestPullRequestHead covers what gh can answer with that is not a head branch: // the flow has to stop at the lookup rather than build a worktree named "". func TestPullRequestHead(t *testing.T) { + t.Parallel() tests := []struct { name string gh *scriptedGH