Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Run the integration tests in parallel: 168s of tests becomes 16s

44530e746def72216bf02e88716f121936576ab7 · Keenan Nemetz · 17d ago · parent 6c03e461

Run the integration tests in parallel: 168s of tests becomes 16s

The suite was serial, and it is the wrong shape to be serial. 95 tests in
internal/server each start a server, push over SSH and shell out to git, so
they spend nearly all their time waiting on child processes and almost none
on CPU: the package took 148s wall for 23s of user time. Sixteen cores were
sitting idle while tests waited for git.

Every test now calls t.Parallel(). Nothing else about the fixtures had to
change, because they were already isolated the way parallelism needs: each
gets t.TempDir(), binds 127.0.0.1:0, and passes its environment through
cmd.Env rather than the process's. binPath is built once by TestMain and is
read-only.

Measured on 16 cores: internal/server 148.4s -> 12.8s, the whole suite
168s -> 16s, and `scripts/ci.sh` (gofmt, vet, build, link check, then the
whole suite under -race) 19.5s end to end. Verified stable across six
repeated runs, two `-shuffle=on` runs and two `-race` runs, with no data
races reported, so the parallelism is exposing isolation the fixtures
already had rather than luck.

The critical path is now one test, TestKilledRunnerIsRequeued, because it is
genuinely a timing test: it waits for a lease to expire and for the requeue
sweep. Its fixture workflow held the step for `sleep 8`, which also runs on
the retry, so eight of its fourteen seconds were the fixture waiting for
itself. Three seconds keeps the property it is testing — the runner must
still be working when it is killed, and the kill happens as soon as the lease
is visible — and takes the test to 9.7s.

What is left is fixture setup, and most of it is SQLite schema creation:
runs.Open is ~89ms and index.Open ~58ms on a *fresh* file. Measured, that is
not the eleven ALTER TABLE statements that are expected to fail on "duplicate
column" — on a warm connection those cost 2us for all eleven. It is the
CREATE TABLE / CREATE INDEX work itself, and modernc's pure-Go engine is much
slower at DDL than C SQLite. Reopening an already-created database is 408us,
so this is a cost paid once per database file, not once per open: in
production it is `vole init` and nowhere else. Tests pay it 95 times because
every fixture starts from an empty data directory.

Left alone deliberately, for two reasons. It is ~1s of wall time across the
suite, and the only fixes available are test-side (build the empty databases
once in TestMain and copy the files per fixture) or would mean changing
storage engines — which is the wrong conclusion from 150ms, and is not what
those 89ms are about.

The invariant is written down at the top of internal/server's test file, with
the rules a new test has to keep: no t.Setenv, no fixed paths, no shared
state beyond binPath, no ordering assumptions, and the shortest sleep a
fixture can get away with — because a test's own wait sets the floor for the
whole package and parallelism cannot hide it.

internal/gitx and cmd/vole keep `t.Setenv` tests, which cannot be parallel,
so those packages are unchanged. They take 0.2s and 0.5s.

internal/server/integration_test.go +73−1

@@ -38,8 +38,24 @@ import (
38 38 "github.com/rodent-software/vole/internal/server"
39 39 )
40 40
41 // binPath is the binary under test, built once by TestMain. It is read by every test
42 // and written by none, which is what makes it safe for them to run concurrently.
41 43 var binPath string
42 44
45 // Every test here calls t.Parallel(). These are integration tests: each one starts a
46 // server, pushes over SSH and runs `git` subprocesses, so it spends almost all of its
47 // time waiting on child processes and almost none of it on CPU. Serially that is ~150
48 // seconds; sixteen at a time it is ~16. Anything added here must keep that possible:
49 //
50 // - no t.Setenv — it panics in a parallel test, and it is the wrong tool anyway.
51 // Pass environment through cmd.Env (see ciEnv) so tests cannot see each other.
52 // - no fixed paths. t.TempDir() and 127.0.0.1:0 give each test its own instance.
53 // - no shared package-level state beyond the read-only binPath.
54 // - no ordering assumptions between tests. `go test -shuffle=on` must pass.
55 //
56 // A genuinely slow *wait* is the one thing parallelism cannot hide, because it sets the
57 // floor for the whole package: prefer the shortest sleep a fixture can get away with,
58 // and say in a comment what it is waiting for.
43 59 func TestMain(m *testing.M) {
44 60 dir, err := os.MkdirTemp("", "vole-int-bin-")
45 61 if err != nil {
@@ -326,6 +342,7 @@ func (f *fixture) cli(dir string, args ...string) (string, string, error) {
326 342 // TestPushOverSSHThenReadOverBothTransports is the core loop: writes need a key,
327 343 // reads do not.
328 344 func TestPushOverSSHThenReadOverBothTransports(t *testing.T) {
345 t.Parallel()
329 346 f := newFixture(t)
330 347 f.publish(f.repo)
331 348
@@ -362,6 +379,7 @@ func TestPushOverSSHThenReadOverBothTransports(t *testing.T) {
362 379 // TestPrivateRepoIsInvisible covers the visibility rule: private means absent,
363 380 // not forbidden.
364 381 func TestPrivateRepoIsInvisible(t *testing.T) {
382 t.Parallel()
365 383 f := newFixture(t)
366 384 work := workRepo(t, map[string]string{"secret.md": "classified\n"})
367 385 if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil {
@@ -397,6 +415,7 @@ func TestPrivateRepoIsInvisible(t *testing.T) {
397 415 }
398 416
399 417 func TestPushOverHTTPRefused(t *testing.T) {
418 t.Parallel()
400 419 f := newFixture(t)
401 420 f.publish(f.repo)
402 421 work := workRepo(t, map[string]string{"a.txt": "a\n"})
@@ -406,6 +425,7 @@ func TestPushOverHTTPRefused(t *testing.T) {
406 425 }
407 426
408 427 func TestRebuildIndexAfterDelete(t *testing.T) {
428 t.Parallel()
409 429 f := newFixture(t)
410 430 f.publish(f.repo)
411 431 before, err := f.srv.Index.List()
@@ -452,6 +472,7 @@ func TestRebuildIndexAfterDelete(t *testing.T) {
452 472 // one SQLite file, a consistent copy of it is complete, and no system repository
453 473 // or key directory exists beside it.
454 474 func TestRosterIsOneDatabaseFile(t *testing.T) {
475 t.Parallel()
455 476 f := newFixture(t)
456 477 f.publish(f.repo)
457 478 work := workRepo(t, map[string]string{"readme.md": "x\n"})
@@ -486,6 +507,7 @@ func TestRosterIsOneDatabaseFile(t *testing.T) {
486 507 // a hand-edited key is accepted, because there is no signed history to compare
487 508 // against. What we keep is that the change is unambiguous and reloadable.
488 509 func TestRosterIsReloadable(t *testing.T) {
510 t.Parallel()
489 511 f := newFixture(t)
490 512 // A key dropped in by hand is picked up on reload.
491 513 pub, _ := keypair(t, filepath.Join(f.cfg.DataDir, "id_second"))
@@ -544,6 +566,7 @@ func issueFiles(id, title, desc string) map[string]string {
544 566 // TestIssuePushedThenRendered is the whole option-A loop: a user with an editor
545 567 // and a key files a bug, and the read-only web shows it.
546 568 func TestIssuePushedThenRendered(t *testing.T) {
569 t.Parallel()
547 570 f := newFixture(t)
548 571 f.publish(f.repo)
549 572
@@ -588,6 +611,7 @@ func TestIssuePushedThenRendered(t *testing.T) {
588 611 // TestIssueMarkdownIsSanitised: issue bodies arrive by push, so an issue is
589 612 // untrusted input rendered into a public page.
590 613 func TestIssueMarkdownIsSanitised(t *testing.T) {
614 t.Parallel()
591 615 f := newFixture(t)
592 616 f.publish(f.repo)
593 617 desc := "hello\n\n<script>alert('xss')</script>\n\n[click](javascript:alert(1))\n"
@@ -610,6 +634,7 @@ func TestIssueMarkdownIsSanitised(t *testing.T) {
610 634 // TestInvalidIssuePushRejected proves validation happens at the boundary: the
611 635 // push fails, the message says why, and the branch does not move.
612 636 func TestInvalidIssuePushRejected(t *testing.T) {
637 t.Parallel()
613 638 f := newFixture(t)
614 639 f.publish(f.repo)
615 640
@@ -654,6 +679,7 @@ func TestInvalidIssuePushRejected(t *testing.T) {
654 679 // TestIssuesSurviveIndexRebuild: issue rows are derived, so deleting the index
655 680 // must not lose them.
656 681 func TestIssuesSurviveIndexRebuild(t *testing.T) {
682 t.Parallel()
657 683 f := newFixture(t)
658 684 f.publish(f.repo)
659 685 work := f.issuesRepo(t, issueFiles(testIssueID, "persisted", "body\n"))
@@ -686,6 +712,7 @@ func TestIssuesSurviveIndexRebuild(t *testing.T) {
686 712 // TestIssueCLIEndToEnd drives the sugar path: a clone with a remote, the CLI
687 713 // writing files and committing, and the push landing on the server.
688 714 func TestIssueCLIEndToEnd(t *testing.T) {
715 t.Parallel()
689 716 f := newFixture(t)
690 717 f.publish(f.repo)
691 718 // A commit on main so the clone is not empty and has a default branch.
@@ -858,6 +885,7 @@ func TestIssueCLIEndToEnd(t *testing.T) {
858 885 // on every issue it filed, silently. The key is the identity; the instance is the
859 886 // only thing that knows which account it belongs to.
860 887 func TestWhoAmIAnswersFromTheInstance(t *testing.T) {
888 t.Parallel()
861 889 f := newFixtureWith(t, func(c *config.Config) { c.PublicURL = "http://forge.test" })
862 890 f.publish(f.repo)
863 891 work := workRepo(t, map[string]string{"readme.md": "hi\n"})
@@ -934,6 +962,7 @@ func TestWhoAmIAnswersFromTheInstance(t *testing.T) {
934 962 // TestReadCommandsNeedNoIdentity: `issue list` and `issue show` read the branch,
935 963 // so they must keep working where the instance cannot be reached at all.
936 964 func TestReadCommandsNeedNoIdentity(t *testing.T) {
965 t.Parallel()
937 966 f := newFixture(t)
938 967 f.publish(f.repo)
939 968 work := workRepo(t, map[string]string{"readme.md": "hi\n"})
@@ -963,6 +992,7 @@ func TestReadCommandsNeedNoIdentity(t *testing.T) {
963 992 // and there is no flag for saying otherwise. `--author` was a name you could typo
964 993 // into a round trip; the key already answers the question.
965 994 func TestRequestAuthorComesFromTheKey(t *testing.T) {
995 t.Parallel()
966 996 f := newFixture(t)
967 997 f.publish(f.repo)
968 998 work := workRepo(t, map[string]string{"readme.md": "hi\n"})
@@ -1010,6 +1040,7 @@ func TestRequestAuthorComesFromTheKey(t *testing.T) {
1010 1040 // tip it had never updated, and the retry reported a "lost race" that never
1011 1041 // happened and re-applied onto the same stale base.
1012 1042 func TestAURLRemoteIsFetchedBeforeWriting(t *testing.T) {
1043 t.Parallel()
1013 1044 f := newFixture(t)
1014 1045 f.publish(f.repo)
1015 1046 fresh := func() string {
@@ -1039,6 +1070,7 @@ func TestAURLRemoteIsFetchedBeforeWriting(t *testing.T) {
1039 1070 // minute with the instance, and four of the five things they might want to do are
1040 1071 // writes the web cannot take. The page has to hand them the command.
1041 1072 func TestEmptyStatesPrintAWorkingCommand(t *testing.T) {
1073 t.Parallel()
1042 1074 f := newFixture(t)
1043 1075 f.publish(f.repo)
1044 1076 for _, c := range []struct{ path, want string }{
@@ -1060,6 +1092,7 @@ func TestEmptyStatesPrintAWorkingCommand(t *testing.T) {
1060 1092 // answer comes from `git merge-tree` in the repository — nothing is written — and
1061 1093 // it is only claimed when the question could actually be asked.
1062 1094 func TestRequestPageSaysWhenItWouldConflict(t *testing.T) {
1095 t.Parallel()
1063 1096 f := newFixture(t)
1064 1097 f.publish(f.repo)
1065 1098
@@ -1171,6 +1204,7 @@ func TestRequestPageSaysWhenItWouldConflict(t *testing.T) {
1171 1204 // at the boundary, and owned by whoever wrote it. Without this, the only ways to
1172 1205 // say something about a request are editing its body or approving it.
1173 1206 func TestRequestDiscussionIsPushedAndOwned(t *testing.T) {
1207 t.Parallel()
1174 1208 f := newFixture(t)
1175 1209 writerKey := f.addWriter(t) // wilbur: write access, not admin
1176 1210 f.publish(f.repo)
@@ -1270,6 +1304,7 @@ func TestRequestDiscussionIsPushedAndOwned(t *testing.T) {
1270 1304 // moment the file changes, instead of sliding to a line that now says something
1271 1305 // else.
1272 1306 func TestReviewCommentAnchorsALine(t *testing.T) {
1307 t.Parallel()
1273 1308 f := newFixture(t)
1274 1309 f.publish(f.repo)
1275 1310
@@ -1388,6 +1423,7 @@ func (f *fixture) serverIssues(t *testing.T) string {
1388 1423
1389 1424 // TestIssueFiltersAndSearch covers the SQL paths behind the list page.
1390 1425 func TestIssueFiltersAndSearch(t *testing.T) {
1426 t.Parallel()
1391 1427 f := newFixture(t)
1392 1428 f.publish(f.repo)
1393 1429 files := map[string]string{}
@@ -1431,6 +1467,7 @@ func TestIssueFiltersAndSearch(t *testing.T) {
1431 1467 // click, a filter that narrows the list, and — because it names a person — a
1432 1468 // value the push boundary refuses when it could never be an account.
1433 1469 func TestIssueAssigneesRenderedFilteredAndValidated(t *testing.T) {
1470 t.Parallel()
1434 1471 f := newFixture(t)
1435 1472 f.publish(f.repo)
1436 1473 const (
@@ -1519,6 +1556,7 @@ func commentFile(author, created, body string) string {
1519 1556 // file. Closing and relabelling somebody else's issue is triage, not authorship,
1520 1557 // and refusing it would make the shared fields useless.
1521 1558 func TestCommentEditsBelongToTheirAuthor(t *testing.T) {
1559 t.Parallel()
1522 1560 f := newFixture(t)
1523 1561 writerKey := f.addWriter(t) // wilbur: write access, not admin
1524 1562 f.publish(f.repo)
@@ -1603,6 +1641,7 @@ func TestCommentEditsBelongToTheirAuthor(t *testing.T) {
1603 1641 // TestCrossReferencesLinksIssues: a #<ulid> in a body becomes a link, and a
1604 1642 // commit mentioning an issue shows as a backlink.
1605 1643 func TestCrossReferencesLinksIssues(t *testing.T) {
1644 t.Parallel()
1606 1645 f := newFixture(t)
1607 1646 f.publish(f.repo)
1608 1647 const target = "01M5JR2JK29KKMAA1T9MDNTPC9"
@@ -1662,6 +1701,7 @@ func min(a, b int) int {
1662 1701 // TestMetadataRejectsMergeCommits: metadata history is linear by policy, so a
1663 1702 // merge node is refused rather than quietly accepted.
1664 1703 func TestMetadataRejectsMergeCommits(t *testing.T) {
1704 t.Parallel()
1665 1705 f := newFixture(t)
1666 1706 f.publish(f.repo)
1667 1707
@@ -1699,6 +1739,7 @@ func TestMetadataRejectsMergeCommits(t *testing.T) {
1699 1739 // TestMetadataForcePushIsAdminOnly: rewriting discussion content is a redaction
1700 1740 // tool, not a normal operation.
1701 1741 func TestMetadataForcePushIsAdminOnly(t *testing.T) {
1742 t.Parallel()
1702 1743 f := newFixture(t)
1703 1744 f.publish(f.repo)
1704 1745 writerKey := f.addWriter(t)
@@ -1739,6 +1780,7 @@ func TestMetadataForcePushIsAdminOnly(t *testing.T) {
1739 1780 // TestMetadataRejectsUnknownTopLevel: the layout is closed, so a typo is caught
1740 1781 // rather than silently ignored by the web.
1741 1782 func TestMetadataRejectsUnknownTopLevel(t *testing.T) {
1783 t.Parallel()
1742 1784 f := newFixture(t)
1743 1785 f.publish(f.repo)
1744 1786 work := t.TempDir()
@@ -1769,6 +1811,7 @@ func TestMetadataRejectsUnknownTopLevel(t *testing.T) {
1769 1811 // TestConcurrentCLIWritesAllLand: two people filing issues at once must both
1770 1812 // succeed. The CLI re-applies on a lost race instead of failing.
1771 1813 func TestConcurrentCLIWritesAllLand(t *testing.T) {
1814 t.Parallel()
1772 1815 f := newFixture(t)
1773 1816 f.publish(f.repo)
1774 1817 if _, errs, err := f.git(workRepo(t, map[string]string{"readme.md": "hi\n"}),
@@ -2022,6 +2065,7 @@ func issueFile(title, author, state, created string, labels []string, body strin
2022 2065 // writer cannot land on a protected branch until a change request for that exact
2023 2066 // commit has the required approvals, and an admin still bypasses.
2024 2067 func TestProtectedBranchRequiresApprovedRequest(t *testing.T) {
2068 t.Parallel()
2025 2069 f := newFixture(t)
2026 2070 writerKey := f.addWriter(t)
2027 2071 f.publish(f.repo)
@@ -2091,6 +2135,7 @@ func TestProtectedBranchRequiresApprovedRequest(t *testing.T) {
2091 2135 // TestApprovalIsBoundToTheCommit is the property that makes approvals mean
2092 2136 // something: approve A, amend to B, and the approval no longer applies.
2093 2137 func TestApprovalIsBoundToTheCommit(t *testing.T) {
2138 t.Parallel()
2094 2139 f := newFixture(t)
2095 2140 writerKey := f.addWriter(t)
2096 2141 f.publish(f.repo)
@@ -2132,6 +2177,7 @@ func TestApprovalIsBoundToTheCommit(t *testing.T) {
2132 2177 // TestSelfApprovalRejected: the metadata push itself fails, so the approval never
2133 2178 // exists to be counted.
2134 2179 func TestSelfApprovalRejected(t *testing.T) {
2180 t.Parallel()
2135 2181 f := newFixture(t)
2136 2182 writerKey := f.addWriter(t)
2137 2183 f.publish(f.repo)
@@ -2159,6 +2205,7 @@ func TestSelfApprovalRejected(t *testing.T) {
2159 2205 // the branch tip and the approved head is allowed; smuggling an extra commit into
2160 2206 // that merge is not.
2161 2207 func TestMergeCommitShapeAllowed(t *testing.T) {
2208 t.Parallel()
2162 2209 f := newFixture(t)
2163 2210 writerKey := f.addWriter(t)
2164 2211 f.publish(f.repo)
@@ -2212,6 +2259,7 @@ func TestMergeCommitShapeAllowed(t *testing.T) {
2212 2259 // TestImpersonatedApprovalRejected: an approval file is only written by the person
2213 2260 // it names, otherwise "approval" means nothing.
2214 2261 func TestImpersonatedApprovalRejected(t *testing.T) {
2262 t.Parallel()
2215 2263 f := newFixture(t)
2216 2264 writerKey := f.addWriter(t)
2217 2265 f.publish(f.repo)
@@ -2244,6 +2292,7 @@ func TestImpersonatedApprovalRejected(t *testing.T) {
2244 2292 // TestApprovalIsBoundToTheBase: repointing a request at another branch must not
2245 2293 // carry its approvals along.
2246 2294 func TestApprovalIsBoundToTheBase(t *testing.T) {
2295 t.Parallel()
2247 2296 f := newFixture(t)
2248 2297 writerKey := f.addWriter(t)
2249 2298 f.publish(f.repo)
@@ -2292,6 +2341,7 @@ func TestApprovalIsBoundToTheBase(t *testing.T) {
2292 2341 // its named principal must also cover the push that creates the branch, when there
2293 2342 // is no previous commit to diff against.
2294 2343 func TestFirstPushCannotSeedForeignApprovals(t *testing.T) {
2344 t.Parallel()
2295 2345 f := newFixture(t)
2296 2346 writerKey := f.addWriter(t)
2297 2347 f.publish(f.repo)
@@ -2320,6 +2370,7 @@ func TestFirstPushCannotSeedForeignApprovals(t *testing.T) {
2320 2370 // *edited* rather than added, so a check that only inspects added paths misses it.
2321 2371 // This is the exploit: approve commit A, then rewrite the approval to name B.
2322 2372 func TestModifiedApprovalCannotBeForged(t *testing.T) {
2373 t.Parallel()
2323 2374 f := newFixture(t)
2324 2375 writerKey := f.addWriter(t)
2325 2376 f.publish(f.repo)
@@ -2376,6 +2427,7 @@ func TestModifiedApprovalCannotBeForged(t *testing.T) {
2376 2427 // TestApprovalDeletionIsOwnerOrAdmin: deleting a file forges nothing, but it
2377 2428 // erases someone else's statement, so a writer may not do it to another person.
2378 2429 func TestApprovalDeletionIsOwnerOrAdmin(t *testing.T) {
2430 t.Parallel()
2379 2431 f := newFixture(t)
2380 2432 writerKey := f.addWriter(t)
2381 2433 f.publish(f.repo)
@@ -2443,6 +2495,7 @@ func TestApprovalDeletionIsOwnerOrAdmin(t *testing.T) {
2443 2495 // changing a person's name must not change what they may do, nor who owns their
2444 2496 // past approvals.
2445 2497 func TestRenameKeepsAccessAndApprovalOwnership(t *testing.T) {
2498 t.Parallel()
2446 2499 f := newFixture(t)
2447 2500 writerKey := f.addWriter(t)
2448 2501 f.publish(f.repo)
@@ -2506,6 +2559,7 @@ func TestRenameKeepsAccessAndApprovalOwnership(t *testing.T) {
2506 2559 // TestRetiredNameCannotBeReissued: a departure must not hand someone a retired
2507 2560 // person's access and approvals.
2508 2561 func TestRetiredNameCannotBeReissued(t *testing.T) {
2562 t.Parallel()
2509 2563 f := newFixture(t)
2510 2564 writerKey := f.addWriter(t)
2511 2565 f.publish(f.repo)
@@ -2530,6 +2584,7 @@ func TestRetiredNameCannotBeReissued(t *testing.T) {
2530 2584 // introduced it, naming the file and the reason, instead of sitting in the tree
2531 2585 // and never running.
2532 2586 func TestInvalidWorkflowRejectedAtPush(t *testing.T) {
2587 t.Parallel()
2533 2588 f := newFixture(t)
2534 2589 f.publish(f.repo)
2535 2590
@@ -2596,6 +2651,7 @@ func workflowFile(body string) map[string]string {
2596 2651 // TestPushRunsWorkflow is the end-to-end criterion: a push queues a job, a runner
2597 2652 // leases it, executes the pinned steps and reports the log.
2598 2653 func TestPushRunsWorkflow(t *testing.T) {
2654 t.Parallel()
2599 2655 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 2 })
2600 2656 f.publish(f.repo)
2601 2657
@@ -2682,6 +2738,7 @@ jobs:
2682 2738 // matches nothing looks exactly like a quiet instance, so a runner that started
2683 2739 // happily and idled would be indistinguishable from a healthy one.
2684 2740 func TestARepoFilterOutsideTheGrantIsRefusedAtStartup(t *testing.T) {
2741 t.Parallel()
2685 2742 f := newFixture(t)
2686 2743 f.publish(f.repo)
2687 2744
@@ -2703,6 +2760,7 @@ func TestARepoFilterOutsideTheGrantIsRefusedAtStartup(t *testing.T) {
2703 2760 // on the metadata branch, so a plain clone carries it, and the queue's finished
2704 2761 // half can be rebuilt from git after the database is deleted.
2705 2762 func TestRunRecordIsPortable(t *testing.T) {
2763 t.Parallel()
2706 2764 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 2 })
2707 2765 f.publish(f.repo)
2708 2766
@@ -2830,6 +2888,7 @@ jobs:
2830 2888 // TestKilledRunnerIsRequeued is the other half of the contract: killing a runner
2831 2889 // mid-job returns the job to the queue and another runner finishes it.
2832 2890 func TestKilledRunnerIsRequeued(t *testing.T) {
2891 t.Parallel()
2833 2892 if testing.Short() {
2834 2893 t.Skip("waits for a lease to expire and a janitor tick; slow under -race")
2835 2894 }
@@ -2841,7 +2900,11 @@ jobs:
2841 2900 slow:
2842 2901 steps:
2843 2902 - name: hold the lease
2844 run: sleep 8
2903 # Long enough that the runner is certainly still working when it is killed
2904 # (the kill happens as soon as the lease is visible, so the margin is the whole
2905 # step), and no longer: this same step also runs on the retry, so every second
2906 # here is a second added to the test.
2907 run: sleep 3
2845 2908 `))
2846 2909 if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil {
2847 2910 t.Fatalf("push: %s", errs)
@@ -2893,6 +2956,7 @@ jobs:
2893 2956 // A run still waiting when a newer tip arrives is canceled rather than started — the
2894 2957 // pinning question only exists for a run that got as far as executing.
2895 2958 func TestQueuedRunUsesTheWorkflowItWasQueuedFrom(t *testing.T) {
2959 t.Parallel()
2896 2960 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 5 })
2897 2961 f.publish(f.repo)
2898 2962
@@ -3046,6 +3110,7 @@ func (f *fixture) openAndApprove(t *testing.T, head, author string) {
3046 3110 // TestProtectedBranchRequiresGreenChecks: approvals say a person read the change,
3047 3111 // checks say it works, and a protected branch wants both.
3048 3112 func TestProtectedBranchRequiresGreenChecks(t *testing.T) {
3113 t.Parallel()
3049 3114 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 2 })
3050 3115 writerKey := f.addWriter(t)
3051 3116 f.publish(f.repo)
@@ -3080,6 +3145,7 @@ func TestProtectedBranchRequiresGreenChecks(t *testing.T) {
3080 3145 // the account that reported it. A required check is a claim about a commit, and
3081 3146 // without this rule any writer could satisfy it by running CI on their own machine.
3082 3147 func TestOnlyAllowedRunnersGreenLightABranch(t *testing.T) {
3148 t.Parallel()
3083 3149 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 2 })
3084 3150 writerKey := f.addWriter(t)
3085 3151 wilbur := f.accountID("wilbur")
@@ -3157,6 +3223,7 @@ func TestOnlyAllowedRunnersGreenLightABranch(t *testing.T) {
3157 3223 // TestACLRunnersEditsTheList: three shapes, because replacing a list to add a sixth
3158 3224 // name is how a list gets shortened by mistake.
3159 3225 func TestACLRunnersEditsTheList(t *testing.T) {
3226 t.Parallel()
3160 3227 f := newFixture(t)
3161 3228 f.addWriter(t)
3162 3229 f.publish(f.repo)
@@ -3248,6 +3315,7 @@ func TestACLRunnersEditsTheList(t *testing.T) {
3248 3315
3249 3316 // TestFailingCheckBlocksTheMerge: a green approval does not outvote a red build.
3250 3317 func TestFailingCheckBlocksTheMerge(t *testing.T) {
3318 t.Parallel()
3251 3319 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 2 })
3252 3320 writerKey := f.addWriter(t)
3253 3321 f.publish(f.repo)
@@ -3285,6 +3353,7 @@ func TestFailingCheckBlocksTheMerge(t *testing.T) {
3285 3353 // TestMissingRunBlocksWithAHint: a workflow that only runs on main can never
3286 3354 // produce a result for a feature branch, and the message has to say so.
3287 3355 func TestMissingRunBlocksWithAHint(t *testing.T) {
3356 t.Parallel()
3288 3357 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 2 })
3289 3358 writerKey := f.addWriter(t)
3290 3359 f.publish(f.repo)
@@ -3338,6 +3407,7 @@ func repoID(t *testing.T, f *fixture) string {
3338 3407 }
3339 3408
3340 3409 func TestHealthzReportsUsers(t *testing.T) {
3410 t.Parallel()
3341 3411 f := newFixture(t)
3342 3412 code, body := f.get("/healthz")
3343 3413 if code != 200 {
@@ -3354,6 +3424,7 @@ var _ = acl.Read
3354 3424 // label. The end-to-end shape matters here: the whole point is that nothing runs, which
3355 3425 // has to be distinguishable from "a runner started and failed".
3356 3426 func TestRunsOnWaitsForARunnerThatCarriesTheLabel(t *testing.T) {
3427 t.Parallel()
3357 3428 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 5 })
3358 3429 f.publish(f.repo)
3359 3430
@@ -3410,6 +3481,7 @@ jobs:
3410 3481 // refuses it before it dials. A machine advertising a label no workflow can ask for is
3411 3482 // a typo with no symptom.
3412 3483 func TestABadRunnerLabelIsRefusedBeforeConnecting(t *testing.T) {
3484 t.Parallel()
3413 3485 f := newFixture(t)
3414 3486 cmd := exec.Command(binPath, "runner", "--remote", f.sshBase(), "--once",
3415 3487 "--dir", t.TempDir(), "--label", "Linux")