e2e: build binaries once, run the tests in parallel !454

merged merged by cmc on 2026-09-21 21:48 UTC · krz/gitbay:e2e-speed into main

142 files changed, +311 −79

Layout: unified · split

Makefile +2 −3
@@ -26,9 +26,8 @@ RUNNER_BIN := dist/gitbay-runner-linux-amd64
2626help:
2727 @sed -n 's/^# //p' $(MAKEFILE_LIST)
2828
29# The e2e suite runs 400-700s against go's 600s per-package default, so a
30# loaded machine turns a passing tree into a goroutine dump that reads as
31# an unrelated failure. A real hang still fails, just later.
29# -timeout 30m is a ceiling for a real hang, not a working figure: the
30# suite runs in a couple of minutes since the e2e tests went parallel.
3231test:
3332 go test ./... -count=1 -timeout 30m
3433
e2e/aboutbackfill_test.go +1
@@ -11,6 +11,7 @@ import (
1111// The about text parked by migration 0058 becomes a file in the owner's
1212// .gitbay repository. Running it twice writes nothing the second time.
1313func TestMigrateProfileAbout(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 inst.admin(t, "admin", "user", "create", "alice",
e2e/accounts_test.go +3
@@ -49,6 +49,7 @@ func browserPost(t *testing.T, c *http.Client, u string, form url.Values) (int,
4949}
5050
5151func TestWebAccounts(t *testing.T) {
52 t.Parallel()
5253 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
5354
5455 aliceKey := inst.newKey(t, "alice")
@@ -246,6 +247,7 @@ func TestWebAccounts(t *testing.T) {
246247// TestViewOnlyHasNoLoginOnTheWire is the M8 negative: in view_only mode the
247248// login route does not exist and web login over ssh is refused.
248249func TestViewOnlyHasNoLoginOnTheWire(t *testing.T) {
250 t.Parallel()
249251 inst := startInstance(t) // default: view_only
250252 aliceKey := inst.newKey(t, "alice")
251253 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
@@ -263,6 +265,7 @@ func TestViewOnlyHasNoLoginOnTheWire(t *testing.T) {
263265// and its hostname: the login page prints a command to paste into a terminal,
264266// so it must name the host even when the operator has set a display title.
265267func TestTitleIsNotAHostname(t *testing.T) {
268 t.Parallel()
266269 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\ntitle = \"GitBay\"\n")
267270
268271 status, body := inst.get(t, "/login")
e2e/accountweb_test.go +1
@@ -13,6 +13,7 @@ import (
1313// browser session. Public keys are the only credential-shaped input the web
1414// accepts; secrets and token minting stay on SSH.
1515func TestAccountSettingsWeb(t *testing.T) {
16 t.Parallel()
1617 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1718 aliceKey := inst.newKey(t, "alice")
1819 inst.admin(t, "admin", "user", "create", "alice",
e2e/acme_test.go +1
@@ -18,6 +18,7 @@ import (
1818// name, which a test cannot have; what matters here is that the plumbing is
1919// correct and failure to issue does not kill the daemon.
2020func TestACMEServe(t *testing.T) {
21 t.Parallel()
2122 inst := startInstanceWith(t, "") // helper for binary + keys; killed below
2223 inst.proc.Process.Kill()
2324 inst.proc.Wait()
e2e/activity_test.go +1
@@ -11,6 +11,7 @@ import (
1111)
1212
1313func TestActivityGraph(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 bobKey := inst.newKey(t, "bob")
e2e/adminusers_test.go +7
@@ -17,6 +17,7 @@ type adminUserRow struct {
1717}
1818
1919func TestAdminUserListAndShow(t *testing.T) {
20 t.Parallel()
2021 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
2122 adminKey := inst.newKey(t, "root")
2223 aliceKey := inst.newKey(t, "alice")
@@ -171,6 +172,7 @@ func TestAdminUserListAndShow(t *testing.T) {
171172}
172173
173174func TestAdminPromoteDemote(t *testing.T) {
175 t.Parallel()
174176 inst := startInstance(t)
175177 rootKey := inst.newKey(t, "root")
176178 aliceKey := inst.newKey(t, "alice")
@@ -226,6 +228,7 @@ func TestAdminPromoteDemote(t *testing.T) {
226228}
227229
228230func TestAdminRepoModeration(t *testing.T) {
231 t.Parallel()
229232 inst := startInstance(t)
230233 rootKey := inst.newKey(t, "root")
231234 aliceKey := inst.newKey(t, "alice")
@@ -337,6 +340,7 @@ func TestAdminRepoModeration(t *testing.T) {
337340// commands work in an admin's SSH session and audit rows say which path
338341// ran them.
339342func TestAdminHostAndSSHAreOneSurface(t *testing.T) {
343 t.Parallel()
340344 inst := startInstance(t)
341345 rootKey := inst.newKey(t, "root")
342346 aliceKey := inst.newKey(t, "alice")
@@ -402,6 +406,7 @@ func TestAdminHostAndSSHAreOneSurface(t *testing.T) {
402406}
403407
404408func TestAuditFilters(t *testing.T) {
409 t.Parallel()
405410 inst := startInstance(t)
406411 rootKey := inst.newKey(t, "root")
407412 aliceKey := inst.newKey(t, "alice")
@@ -454,6 +459,7 @@ func TestAuditFilters(t *testing.T) {
454459}
455460
456461func TestAdminQueuesDashboard(t *testing.T) {
462 t.Parallel()
457463 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n[webhooks]\nallow_local = true\n")
458464 rootKey := inst.newKey(t, "root")
459465 aliceKey := inst.newKey(t, "alice")
@@ -568,6 +574,7 @@ func TestAdminQueuesDashboard(t *testing.T) {
568574}
569575
570576func TestAdminConfigShow(t *testing.T) {
577 t.Parallel()
571578 inst := startInstanceWith(t, "[mail]\nsmtp_host = \"127.0.0.1:1\"\nfrom = \"forge@example.test\"\nsmtp_pass = \"hunter2\"\n")
572579 out := inst.admin(t, "admin", "config", "show")
573580 for _, want := range []string{"[server]", "site_url", "ssh_auth_rate = 10", "pull_interval_minutes = 15", "[mail]", `smtp_pass = "<redacted>"`} {
e2e/adminusersweb_test.go +1
@@ -10,6 +10,7 @@ import (
1010// web can reach now that nothing is held back from it (#234). Demote
1111// and disable carry the typed-name check.
1212func TestAdminUsersWeb(t *testing.T) {
13 t.Parallel()
1314 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1415 rootKey := inst.newKey(t, "root")
1516 aliceKey := inst.newKey(t, "alice")
e2e/api_test.go +3
@@ -40,6 +40,7 @@ func (i *instance) apiCall(t *testing.T, token string, argv []string, stdin stri
4040}
4141
4242func TestJSONAPI(t *testing.T) {
43 t.Parallel()
4344 inst := startInstanceWith(t, "[api]\nenabled = true\n")
4445 aliceKey := inst.newKey(t, "alice")
4546 inst.admin(t, "admin", "user", "create", "alice",
@@ -202,6 +203,7 @@ func TestJSONAPI(t *testing.T) {
202203// metered separately from reads, and one caller cannot spend another's
203204// budget.
204205func TestAPIRateLimit(t *testing.T) {
206 t.Parallel()
205207 // 6/minute sustained, so the read burst is 6 and the write burst 0.6 —
206208 // the first write is allowed and the second is not.
207209 inst := startInstanceWith(t, "[api]\nenabled = true\n[limits]\napi_rate = 6\n")
@@ -309,6 +311,7 @@ func (i *instance) apiGet(t *testing.T, token string, argv []string, ifNoneMatch
309311// with an ETag, 304 on revalidation, writes refused, and one caller's ETag
310312// never matching another's.
311313func TestAPIReadGET(t *testing.T) {
314 t.Parallel()
312315 inst := startInstanceWith(t, "[api]\nenabled = true\n")
313316 aliceKey := inst.newKey(t, "alice")
314317 bobKey := inst.newKey(t, "bob")
e2e/apiread_test.go +1
@@ -14,6 +14,7 @@ import (
1414// before: no command returned file contents, and the web's raw route
1515// authenticates by session cookie, not bearer token.
1616func TestRepoTreeAndCat(t *testing.T) {
17 t.Parallel()
1718 inst := startInstance(t)
1819 aliceKey := inst.newKey(t, "alice")
1920 bobKey := inst.newKey(t, "bob")
e2e/approvals_test.go +2
@@ -10,6 +10,7 @@ import (
1010)
1111
1212func TestMergeRequirements(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
@@ -147,6 +148,7 @@ func TestMergeRequirements(t *testing.T) {
147148// merges. When it is on, it gates independently of require_approvals —
148149// the coupling that left owners unenforced under default settings (#99).
149150func TestCodeownersToggle(t *testing.T) {
151 t.Parallel()
150152 inst := startInstance(t)
151153 aliceKey := inst.newKey(t, "alice")
152154 carolKey := inst.newKey(t, "carol")
e2e/archive_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestArchiveAndTopics(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/audit_test.go +1
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestAuditAndHardening(t *testing.T) {
12 t.Parallel()
1213 inst := startInstanceWith(t, "[limits]\nssh_auth_rate = 3\nmax_pack_bytes = 2000\n")
1314 adminKey := inst.newKey(t, "root")
1415 aliceKey := inst.newKey(t, "alice")
e2e/autolink_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestWebAutolinks(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/backup_test.go +1
@@ -12,6 +12,7 @@ import (
1212)
1313
1414func TestAdminBackup(t *testing.T) {
15 t.Parallel()
1516 inst := startInstance(t)
1617 aliceKey := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice",
e2e/badge_test.go +1
@@ -9,6 +9,7 @@ import (
99// says "unknown" before any build exists, and 404s for a private repo so
1010// it cannot be used to probe for one.
1111func TestBuildBadge(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314 aliceKey := inst.newKey(t, "alice")
1415 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/bin_test.go added +67
@@ -0,0 +1,67 @@
1package e2e
2
3import (
4 "os"
5 "os/exec"
6 "path/filepath"
7 "sync"
8 "testing"
9)
10
11// The suite starts an instance per test, and each one needs gitbayd. Built
12// per test, that is 200-odd links of a byte-identical 35MB binary: go's
13// build cache covers the compile but not the final link, so it cost about
14// 0.8s every time, a quarter of the whole run.
15//
16// Built once per process instead, under a directory TestMain owns. The
17// binaries outlive any single test, so t.TempDir is the wrong home for
18// them — it is removed when the test that asked for it ends.
19var (
20 gitbaydBin = &builtBin{name: "gitbayd", pkg: "gitbay.org/gitbay/cmd/gitbayd"}
21 gitbayBin = &builtBin{name: "gitbay", pkg: "gitbay.org/gitbay/cmd/gitbay"}
22 runnerBin = &builtBin{name: "gitbay-runner", pkg: "gitbay.org/gitbay/cmd/gitbay-runner"}
23)
24
25// binDir is where the built binaries live, set by TestMain.
26var binDir string
27
28type builtBin struct {
29 name string
30 pkg string
31
32 once sync.Once
33 path string
34 out []byte
35 err error
36}
37
38// get builds the binary on first use and returns the same path thereafter.
39// Lazily, so `-run TestOneThing` does not link the two binaries it has no
40// use for.
41func (b *builtBin) get(t *testing.T) string {
42 t.Helper()
43 b.once.Do(func() {
44 b.path = filepath.Join(binDir, b.name)
45 cmd := exec.Command("go", "build", "-o", b.path, b.pkg)
46 cmd.Dir = ".."
47 b.out, b.err = cmd.CombinedOutput()
48 })
49 if b.err != nil {
50 t.Fatalf("build %s: %v\n%s", b.name, b.err, b.out)
51 }
52 return b.path
53}
54
55func buildGitbayd(t *testing.T) string { return gitbaydBin.get(t) }
56func buildGitbayCLI(t *testing.T) string { return gitbayBin.get(t) }
57func buildRunner(t *testing.T) string { return runnerBin.get(t) }
58
59// makeBinDir is called by TestMain. Returned rather than deferred because
60// TestMain ends in os.Exit, which runs no deferred functions.
61func makeBinDir() (dir string, cleanup func(), err error) {
62 dir, err = os.MkdirTemp("", "e2e-bin")
63 if err != nil {
64 return "", func() {}, err
65 }
66 return dir, func() { os.RemoveAll(dir) }, nil
67}
e2e/blame_test.go +1
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestBlameView(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314 aliceKey := inst.newKey(t, "alice")
1415 inst.admin(t, "admin", "user", "create", "alice",
e2e/blobrender_test.go +1
@@ -10,6 +10,7 @@ import (
1010// Markdown and org files render on the blob page, with the source one
1111// click away; everything else is unchanged.
1212func TestBlobRendersMarkup(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/bookmarks_test.go +2
@@ -12,6 +12,7 @@ import (
1212// are something you do to someone else's repository, and the count is a
1313// signal of what people found worth returning to (#146).
1414func TestBookmarks(t *testing.T) {
15 t.Parallel()
1516 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1617 aliceKey := inst.newKey(t, "alice")
1718 bobKey := inst.newKey(t, "bob")
@@ -89,6 +90,7 @@ func TestBookmarks(t *testing.T) {
8990// A repository bookmarked while public and since made private drops out
9091// of the listing rather than leaking its existence.
9192func TestBookmarkOfRepoGonePrivate(t *testing.T) {
93 t.Parallel()
9294 inst := startInstance(t)
9395 aliceKey := inst.newKey(t, "alice")
9496 bobKey := inst.newKey(t, "bob")
e2e/build_cancel_test.go +2
@@ -14,6 +14,7 @@ import (
1414// Cancelling a duplicate of a commit that already passed puts that
1515// result back on the commit.
1616func TestBuildCancel(t *testing.T) {
17 t.Parallel()
1718 inst := startInstance(t)
1819 inst.runner = buildRunner(t)
1920 aliceKey := inst.newKey(t, "alice")
@@ -91,6 +92,7 @@ func TestBuildCancel(t *testing.T) {
9192// server closes the log session, the runner kills the step, and its late
9293// report lands on a row that already says cancelled.
9394func TestBuildCancelRunning(t *testing.T) {
95 t.Parallel()
9496 inst := startInstance(t)
9597 inst.runner = buildRunner(t)
9698 aliceKey := inst.newKey(t, "alice")
e2e/buildcancelweb_test.go +1
@@ -14,6 +14,7 @@ import (
1414// real, so a stale or repeated post against a build that is no longer
1515// cancellable shows the refusal rather than a broken page.
1616func TestBuildCancelWeb(t *testing.T) {
17 t.Parallel()
1718 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1819 aliceKey := inst.newKey(t, "alice")
1920 inst.admin(t, "admin", "user", "create", "alice",
e2e/buildorphan_test.go +1
@@ -13,6 +13,7 @@ import (
1313// looking like a real failure; it is cancelled when a runner claims it, and
1414// the runner gets the real build behind it instead, in the same poll.
1515func TestBuildOrphanedByForcePushCancelledAtClaim(t *testing.T) {
16 t.Parallel()
1617 inst := startInstance(t)
1718 inst.runner = buildRunner(t)
1819 aliceKey := inst.newKey(t, "alice")
e2e/ci_dedupe_test.go +1
@@ -10,6 +10,7 @@ import (
1010// A fast-forward lands the exact commit that was already built on its
1111// branch; that commit is not built again. A commit whose build failed is.
1212func TestFastForwardMergeSkipsBuiltCommit(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 inst.runner = buildRunner(t)
1516 aliceKey := inst.newKey(t, "alice")
e2e/ci_merge_test.go +1
@@ -12,6 +12,7 @@ import (
1212// an MR into a repository with a CI config queued nothing, while pushing
1313// the identical commit ran the whole config.
1414func TestMergeQueuesBuildsAndRecordsPush(t *testing.T) {
15 t.Parallel()
1516 inst := startInstance(t)
1617 aliceKey := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub",
e2e/ci_nojobs_test.go +1
@@ -11,6 +11,7 @@ import (
1111// the setting off. Nothing was ever going to report, so the gate has
1212// nothing to wait for.
1313func TestRequireChecksWithoutCIConfigMerges(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub",
e2e/ci_skipped_test.go +1
@@ -13,6 +13,7 @@ import (
1313// filtered job now records a "skipped" status instead, which the gate reads
1414// as green (#172).
1515func TestSkippedStatusSatisfiesRequireChecks(t *testing.T) {
16 t.Parallel()
1617 inst := startInstance(t)
1718 aliceKey := inst.newKey(t, "alice")
1819 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub",
e2e/ci_test.go +3 −11
@@ -11,17 +11,6 @@ import (
1111 "time"
1212)
1313
14func buildRunner(t *testing.T) string {
15 t.Helper()
16 bin := filepath.Join(t.TempDir(), "gitbay-runner")
17 cmd := exec.Command("go", "build", "-o", bin, "gitbay.org/gitbay/cmd/gitbay-runner")
18 cmd.Dir = ".."
19 if out, err := cmd.CombinedOutput(); err != nil {
20 t.Fatalf("build gitbay-runner: %v\n%s", err, out)
21 }
22 return bin
23}
24
2514// runnerOnce processes at most one pending build with the given key.
2615func (i *instance) runnerOnce(t *testing.T, key string, extra ...string) string {
2716 t.Helper()
@@ -50,6 +39,7 @@ func (i *instance) runnerOnce(t *testing.T, key string, extra ...string) string
5039// A failed build mails the repo owner with the log tail; green builds
5140// stay silent.
5241func TestBuildFailureMail(t *testing.T) {
42 t.Parallel()
5343 smtp := startFakeSMTP(t)
5444 inst := startInstanceWith(t, fmt.Sprintf(
5545 "[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
@@ -91,6 +81,7 @@ func TestBuildFailureMail(t *testing.T) {
9181}
9282
9383func TestCI(t *testing.T) {
84 t.Parallel()
9485 inst := startInstance(t)
9586 inst.runner = buildRunner(t)
9687 aliceKey := inst.newKey(t, "alice")
@@ -318,6 +309,7 @@ func (i *instance) runnerJobs(t *testing.T, key, repo string, jobs int) string {
318309// transaction that selects and updates, so several workers claiming
319310// together is safe; the runner simply never used more than one (#115).
320311func TestRunnerConcurrentJobs(t *testing.T) {
312 t.Parallel()
321313 inst := startInstance(t)
322314 inst.runner = buildRunner(t)
323315 aliceKey := inst.newKey(t, "alice")
e2e/cipaths_test.go +2
@@ -11,6 +11,7 @@ import (
1111// A doc-only push queues nothing; a push touching the named path queues
1212// the job, same as before path filters existed.
1313func TestCIPaths(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
@@ -68,6 +69,7 @@ func TestCIPaths(t *testing.T) {
6869// that first push is always a new branch. Without a merge-base fallback,
6970// a docs-only branch queues the full suite anyway (#171).
7071func TestCIPathsNewBranch(t *testing.T) {
72 t.Parallel()
7173 inst := startInstance(t)
7274 aliceKey := inst.newKey(t, "alice")
7375 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/cli_test.go +1 −11
@@ -10,17 +10,6 @@ import (
1010 "testing"
1111)
1212
13func buildGitbayCLI(t *testing.T) string {
14 t.Helper()
15 bin := filepath.Join(t.TempDir(), "gitbay")
16 cmd := exec.Command("go", "build", "-o", bin, "gitbay.org/gitbay/cmd/gitbay")
17 cmd.Dir = ".."
18 if out, err := cmd.CombinedOutput(); err != nil {
19 t.Fatalf("build gitbay: %v\n%s", err, out)
20 }
21 return bin
22}
23
2413// cli runs the forge binary with an isolated config home.
2514type cli struct {
2615 bin string
@@ -66,6 +55,7 @@ func (c *cli) must(t *testing.T, dir, stdin string, args ...string) string {
6655}
6756
6857func TestCLI(t *testing.T) {
58 t.Parallel()
6959 inst := startInstance(t)
7060 aliceKey := inst.newKey(t, "alice")
7161 inst.admin(t, "admin", "user", "create", "alice",
e2e/commentmigrate_test.go +1
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestMigrateCommitRefComments(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314 aliceKey := inst.newKey(t, "alice")
1415 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/commitrefs_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestCommitMessageIssueActions(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 inst.admin(t, "admin", "user", "create", "alice",
e2e/compare_test.go +1
@@ -10,6 +10,7 @@ import (
1010// The compare view shows what a branch adds on top of another from their
1111// merge base, and repo diff is the same range over ssh (#118).
1212func TestCompareView(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/dashboard_test.go +4
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestDashboard(t *testing.T) {
12 t.Parallel()
1213 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1314 aliceKey := inst.newKey(t, "alice")
1415 bobKey := inst.newKey(t, "bob")
@@ -136,6 +137,7 @@ func TestDashboard(t *testing.T) {
136137// dashboard — review queue, assigned/open work, pins, and activity — in
137138// one read. Builds remain for clients that already consume them.
138139func TestDashboardCommand(t *testing.T) {
140 t.Parallel()
139141 inst := startInstance(t)
140142 aliceKey := inst.newKey(t, "alice")
141143 bobKey := inst.newKey(t, "bob")
@@ -286,6 +288,7 @@ func TestDashboardCommand(t *testing.T) {
286288// TestDashboardQueues covers the parts of the dashboard that answer "what
287289// needs me": the review queue, assigned issues, and the activity feed.
288290func TestDashboardQueues(t *testing.T) {
291 t.Parallel()
289292 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
290293 aliceKey := inst.newKey(t, "alice")
291294 bobKey := inst.newKey(t, "bob")
@@ -361,6 +364,7 @@ func TestDashboardQueues(t *testing.T) {
361364// folded line's state is the worse of the two, which the feed no longer
362365// draws; TestFeedLinesFoldsBuildRun covers that rule.
363366func TestDashboardFeedFoldsBuildRun(t *testing.T) {
367 t.Parallel()
364368 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
365369 inst.runner = buildRunner(t)
366370 aliceKey := inst.newKey(t, "alice")
e2e/dashboardbuild_test.go +1
@@ -9,6 +9,7 @@ import (
99// visible without reading the journal. It is admin-only: the exact build a
1010// host runs narrows down which known issues apply to it.
1111func TestDashboardReportsTheServerBuild(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314
1415 adminKey := inst.newKey(t, "root")
e2e/defaultbranch_test.go +1
@@ -11,6 +11,7 @@ import (
1111// A first push of a branch other than main moves the unborn HEAD to it,
1212// and repo settings default-branch moves it later (#189).
1313func TestDefaultBranch(t *testing.T) {
14 t.Parallel()
1415 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1516 aliceKey := inst.newKey(t, "alice")
1617 bobKey := inst.newKey(t, "bob")
e2e/deploykey_test.go +1
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestDeployKeys(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314 aliceKey := inst.newKey(t, "alice")
1415 bobKey := inst.newKey(t, "bob")
e2e/deps_test.go +1
@@ -20,6 +20,7 @@ type depsStatus struct {
2020// can turn it on: the check tells a public registry what the repository
2121// depends on.
2222func TestDepsEnableDisable(t *testing.T) {
23 t.Parallel()
2324 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
2425 aliceKey := inst.newKey(t, "alice")
2526 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub",
e2e/description_test.go +1
@@ -6,6 +6,7 @@ import (
66)
77
88func TestRepoDescriptions(t *testing.T) {
9 t.Parallel()
910 inst := startInstance(t)
1011 aliceKey := inst.newKey(t, "alice")
1112 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/design_test.go +6
@@ -12,6 +12,7 @@ import (
1212)
1313
1414func TestReadmeRelativeLinks(t *testing.T) {
15 t.Parallel()
1516 inst := startInstance(t)
1617 aliceKey := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
@@ -177,6 +178,7 @@ func TestReadmeRelativeLinks(t *testing.T) {
177178
178179// TestLandingRoutes checks the landing page's copy and the two routes.
179180func TestLandingRoutes(t *testing.T) {
181 t.Parallel()
180182 smtp := startFakeSMTP(t)
181183 inst := startInstanceWith(t, fmt.Sprintf(
182184 "[web]\nmode = \"accounts\"\n[registration]\nmode = \"open\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n",
@@ -204,6 +206,7 @@ func TestLandingRoutes(t *testing.T) {
204206}
205207
206208func TestWebInteractions(t *testing.T) {
209 t.Parallel()
207210 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
208211 aliceKey := inst.newKey(t, "alice")
209212 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
@@ -271,6 +274,7 @@ func TestWebInteractions(t *testing.T) {
271274}
272275
273276func TestCommitParentLinks(t *testing.T) {
277 t.Parallel()
274278 inst := startInstance(t)
275279 aliceKey := inst.newKey(t, "alice")
276280 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
@@ -305,6 +309,7 @@ func TestCommitParentLinks(t *testing.T) {
305309// verified here displays the account's name rather than whatever git
306310// config carried, and that an unknown address keeps its own name.
307311func TestAuthorNamesResolve(t *testing.T) {
312 t.Parallel()
308313 inst := startInstance(t)
309314 aliceKey := inst.newKey(t, "alice")
310315 inst.admin(t, "admin", "user", "create", "alice",
@@ -365,6 +370,7 @@ func TestAuthorNamesResolve(t *testing.T) {
365370// TestTreeSearchCodeAndClone: the overview links "Search code", not
366371// "Find file", and shows two labelled clone blocks after the file table.
367372func TestTreeSearchCodeAndClone(t *testing.T) {
373 t.Parallel()
368374 inst := startInstance(t)
369375 key := inst.newKey(t, "alice")
370376 inst.admin(t, "admin", "user", "create", "alice", "--key", key+".pub")
e2e/diffcomment_test.go +1
@@ -10,6 +10,7 @@ import (
1010)
1111
1212func TestDiffComments(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
e2e/diffweb_test.go +1
@@ -11,6 +11,7 @@ import (
1111// line-number gutters, syntax highlighting, and binary files declared
1212// rather than dumped.
1313func TestDiffRendering(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/disabled_test.go +1
@@ -10,6 +10,7 @@ import (
1010// listener checked the flag and disabling deleted sessions but not tokens
1111// (#95).
1212func TestDisabledAccountAPI(t *testing.T) {
13 t.Parallel()
1314 inst := startInstanceWith(t, "[api]\nenabled = true\n")
1415 aliceKey := inst.newKey(t, "alice")
1516 inst.admin(t, "admin", "user", "create", "alice",
e2e/edit_test.go +1
@@ -10,6 +10,7 @@ import (
1010)
1111
1212func TestIssueMREditing(t *testing.T) {
13 t.Parallel()
1314 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
e2e/effectiveaccess_test.go +1
@@ -9,6 +9,7 @@ import (
99// outsider gets one answer about an org: it exists and has members;
1010// its teams are not theirs to see (#200).
1111func TestEffectiveAccessAndOutsiders(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314 keys := map[string]string{}
1415 for _, u := range []string{"alice", "bob", "carol", "dave", "eve"} {
e2e/emaillogin_test.go +6
@@ -11,6 +11,7 @@ import (
1111// A person with no SSH key can still get into the web UI: they ask for a
1212// link by username or verified address and it arrives by mail (#155).
1313func TestEmailLogin(t *testing.T) {
14 t.Parallel()
1415 smtp := startFakeSMTP(t)
1516 inst := startInstanceWith(t, fmt.Sprintf(
1617 "[web]\nmode = \"accounts\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n",
@@ -71,6 +72,7 @@ func TestEmailLogin(t *testing.T) {
7172// A verified secondary address stands in for an unverified primary:
7273// resolution by username must not stop at the primary (#158).
7374func TestEmailLoginResolvesVerifiedSecondary(t *testing.T) {
75 t.Parallel()
7476 smtp := startFakeSMTP(t)
7577 inst := startInstanceWith(t, fmt.Sprintf(
7678 "[web]\nmode = \"accounts\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n",
@@ -108,6 +110,7 @@ func TestEmailLoginResolvesVerifiedSecondary(t *testing.T) {
108110// The response must not say whether an account exists. A different status,
109111// body, or destination answers "is this person here?" to anyone who asks.
110112func TestEmailLoginDoesNotEnumerate(t *testing.T) {
113 t.Parallel()
111114 smtp := startFakeSMTP(t)
112115 inst := startInstanceWith(t, fmt.Sprintf(
113116 "[web]\nmode = \"accounts\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n",
@@ -175,6 +178,7 @@ func TestEmailLoginDoesNotEnumerate(t *testing.T) {
175178// An anonymous endpoint that sends mail needs a durable per-account bound,
176179// the same one email verification has (#136).
177180func TestEmailLoginThrottled(t *testing.T) {
181 t.Parallel()
178182 smtp := startFakeSMTP(t)
179183 inst := startInstanceWith(t, fmt.Sprintf(
180184 "[web]\nmode = \"accounts\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n",
@@ -220,6 +224,7 @@ func TestEmailLoginThrottled(t *testing.T) {
220224// itself a link. It must not get a session out of it: read access to the
221225// private repos it is a member of is what suspension takes away.
222226func TestEmailLoginRefusesDisabledAccount(t *testing.T) {
227 t.Parallel()
223228 smtp := startFakeSMTP(t)
224229 inst := startInstanceWith(t, fmt.Sprintf(
225230 "[web]\nmode = \"accounts\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n",
@@ -256,6 +261,7 @@ func TestEmailLoginRefusesDisabledAccount(t *testing.T) {
256261// login tokens, and login() refuses a disabled account after consuming one,
257262// so neither the window nor a token that somehow survives it opens a session.
258263func TestEmailLoginRefusesLinkMintedBeforeSuspension(t *testing.T) {
264 t.Parallel()
259265 smtp := startFakeSMTP(t)
260266 inst := startInstanceWith(t, fmt.Sprintf(
261267 "[web]\nmode = \"accounts\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n",
e2e/emailremove_test.go +1
@@ -11,6 +11,7 @@ import (
1111// store holds: the primary stays until another is primary, and the last
1212// verified address stays (#181).
1313func TestEmailRemoveAndPrimary(t *testing.T) {
14 t.Parallel()
1415 smtp := startFakeSMTP(t)
1516 inst := startInstanceWith(t, fmt.Sprintf(
1617 "[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
e2e/emailthrottle_test.go +1
@@ -10,6 +10,7 @@ import (
1010// rate-limited; this path was not, so an authenticated account could
1111// enqueue mail without bound (#136).
1212func TestEmailAddThrottled(t *testing.T) {
13 t.Parallel()
1314 smtp := startFakeSMTP(t)
1415 inst := startInstanceWith(t, fmt.Sprintf(
1516 "[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
e2e/facts_test.go +2
@@ -12,6 +12,7 @@ import (
1212// TestRepoFacts covers the repository summary: counts, license, languages,
1313// and contributors resolved to accounts where the email is verified.
1414func TestRepoFacts(t *testing.T) {
15 t.Parallel()
1516 inst := startInstance(t)
1617 aliceKey := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice",
@@ -102,6 +103,7 @@ WITH REGARD TO THIS SOFTWARE.
102103// account has verified here. Addresses with neither stay distinct, even
103104// when they carry the same name.
104105func TestContributorIdentity(t *testing.T) {
106 t.Parallel()
105107 smtp := startFakeSMTP(t)
106108 inst := startInstanceWith(t, fmt.Sprintf(
107109 "[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
e2e/feeds_test.go +1
@@ -11,6 +11,7 @@ import (
1111// Atom feeds for releases, commits and an owner's public activity, read
1212// with no session; private repositories stay out of them (#192).
1313func TestAtomFeeds(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/filenav_test.go +1
@@ -10,6 +10,7 @@ import (
1010// A file page lists its directory beside the file, marks the file, and
1111// links up (desktop layout spec).
1212func TestFileNavigator(t *testing.T) {
13 t.Parallel()
1314 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1415 key := inst.newKey(t, "alice")
1516 inst.admin(t, "admin", "user", "create", "alice", "--key", key+".pub")
e2e/forkweb_test.go +1
@@ -12,6 +12,7 @@ import (
1212// file in the fork, open the merge request against the parent, without a
1313// terminal at any step (#174).
1414func TestForkWeb(t *testing.T) {
15 t.Parallel()
1516 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1617 aliceKey := inst.newKey(t, "alice")
1718 bobKey := inst.newKey(t, "bob")
e2e/formatpickerweb_test.go +1
@@ -12,6 +12,7 @@ import (
1212// request create forms (#160). Neither form offered --format before; a
1313// browser session could only ever write markdown.
1414func TestIssueMRWebFormat(t *testing.T) {
15 t.Parallel()
1516 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1617 aliceKey := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice",
e2e/gates_test.go +1
@@ -11,6 +11,7 @@ import (
1111// unmet gate at once, and mr review says when a verdict is advisory
1212// (#199).
1313func TestMergeGatesVisible(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 bobKey := inst.newKey(t, "bob")
e2e/ghimport_test.go +3
@@ -72,6 +72,7 @@ func fakeGitHub(t *testing.T) *httptest.Server {
7272}
7373
7474func TestGitHubIssueImport(t *testing.T) {
75 t.Parallel()
7576 // allow_local lets --api-base reach the loopback fake; a default
7677 // instance refuses it (see the SSRF check at the end).
7778 inst := startInstanceWith(t, "[webhooks]\nallow_local = true\n")
@@ -143,6 +144,7 @@ func TestGitHubIssueImport(t *testing.T) {
143144}
144145
145146func TestGitHubImportSSRFGuard(t *testing.T) {
147 t.Parallel()
146148 inst := startInstance(t) // allow_local off: default posture
147149 aliceKey := inst.newKey(t, "alice")
148150 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
@@ -219,6 +221,7 @@ func fakeForgejo(t *testing.T) *httptest.Server {
219221}
220222
221223func TestForgejoIssueImport(t *testing.T) {
224 t.Parallel()
222225 inst := startInstanceWith(t, "[webhooks]\nallow_local = true\n")
223226 aliceKey := inst.newKey(t, "alice")
224227 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/git_test.go +1
@@ -55,6 +55,7 @@ func mustGit(t *testing.T, dir string, env []string, args ...string) string {
5555}
5656
5757func TestGitOverSSH(t *testing.T) {
58 t.Parallel()
5859 inst := startInstance(t)
5960
6061 aliceKey := inst.newKey(t, "alice")
e2e/goimport_test.go +1
@@ -6,6 +6,7 @@ import (
66)
77
88func TestGoImportVanity(t *testing.T) {
9 t.Parallel()
910 inst := startInstanceWith(t, "[go_import]\n\"127.0.0.1/tool\" = \"alice/tool\"\n")
1011 aliceKey := inst.newKey(t, "alice")
1112 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/http_test.go +2
@@ -64,6 +64,7 @@ func (i *instance) httpURL(repo string) string {
6464}
6565
6666func TestHTTPTransport(t *testing.T) {
67 t.Parallel()
6768 inst := startInstance(t)
6869 aliceKey := setupPublicRepo(t, inst, "alice/pub")
6970
@@ -127,6 +128,7 @@ func TestHTTPTransport(t *testing.T) {
127128}
128129
129130func TestGitDaemon(t *testing.T) {
131 t.Parallel()
130132 inst := startInstance(t)
131133 aliceKey := setupPublicRepo(t, inst, "alice/pub")
132134 gitURL := func(repo string) string {
e2e/import_test.go +1
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestRepoImport(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314 aliceKey := inst.newKey(t, "alice")
1415 inst.admin(t, "admin", "user", "create", "alice",
e2e/inbox_test.go +1
@@ -41,6 +41,7 @@ func notices(t *testing.T, inst *instance, key string, args ...string) []struct
4141}
4242
4343func TestNotificationInbox(t *testing.T) {
44 t.Parallel()
4445 inst := startInstance(t)
4546
4647 aliceKey := inst.newKey(t, "alice")
e2e/isolation_podman_test.go +5
@@ -46,6 +46,7 @@ func havePodman(t *testing.T) bool {
4646// the runner refuses to start rather than running a build on the host.
4747// This one needs no podman, so it runs everywhere.
4848func TestRunnerRefusesToStartWithoutPodman(t *testing.T) {
49 t.Parallel()
4950 bin := buildRunner(t)
5051 cmd := exec.Command(bin, "-once", "-remote", "git@127.0.0.1",
5152 "-isolation", "podman", "-image", "localhost/whatever:1", "-workdir", t.TempDir())
@@ -66,6 +67,7 @@ func TestRunnerRefusesToStartWithoutPodman(t *testing.T) {
6667
6768// An unknown mode is refused rather than guessed at.
6869func TestRunnerRefusesUnknownIsolation(t *testing.T) {
70 t.Parallel()
6971 bin := buildRunner(t)
7072 cmd := exec.Command(bin, "-once", "-remote", "git@127.0.0.1",
7173 "-isolation", "chroot", "-workdir", t.TempDir())
@@ -81,6 +83,7 @@ func TestRunnerRefusesUnknownIsolation(t *testing.T) {
8183// With podman, a step runs in a container: it cannot read the runner's
8284// SSH key, and it does not see the runner's home.
8385func TestPodmanStepCannotReachTheRunnersKey(t *testing.T) {
86 t.Parallel()
8487 if !havePodman(t) {
8588 t.Skip("no podman")
8689 }
@@ -131,6 +134,7 @@ func TestPodmanStepCannotReachTheRunnersKey(t *testing.T) {
131134// An image this runner does not have fails the build and says an
132135// operator must provision it, rather than pulling it.
133136func TestPodmanMissingImageFailsTheBuild(t *testing.T) {
137 t.Parallel()
134138 if !havePodman(t) {
135139 t.Skip("no podman")
136140 }
@@ -192,6 +196,7 @@ func runnerPodmanOnce(t *testing.T, inst *instance, key string) {
192196// guessing one: with --pull=never an image the host does not have fails
193197// every job that names none.
194198func TestRunnerRefusesPodmanWithoutAnImage(t *testing.T) {
199 t.Parallel()
195200 bin := buildRunner(t)
196201 cmd := exec.Command(bin, "-once", "-remote", "git@127.0.0.1",
197202 "-isolation", "podman", "-workdir", t.TempDir())
e2e/isolation_test.go +1
@@ -19,6 +19,7 @@ import (
1919// of surfaces is the thing under test, so adding a route without adding
2020// it here is the omission that shows up.
2121func TestPrivateRepoIsInvisible(t *testing.T) {
22 t.Parallel()
2223 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
2324 ownerKey := inst.newKey(t, "owner")
2425 strangerKey := inst.newKey(t, "stranger")
e2e/issue_test.go +1
@@ -24,6 +24,7 @@ func golden(t *testing.T, raw string) string {
2424}
2525
2626func TestIssueLifecycleOverBareSSH(t *testing.T) {
27 t.Parallel()
2728 inst := startInstance(t)
2829
2930 aliceKey := inst.newKey(t, "alice")
e2e/issuesearch_test.go +1
@@ -29,6 +29,7 @@ func issueNumbers(t *testing.T, inst *instance, key string, args ...string) []in
2929}
3030
3131func TestIssueSearch(t *testing.T) {
32 t.Parallel()
3233 inst := startInstance(t)
3334 aliceKey := inst.newKey(t, "alice")
3435 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/issueweb_test.go +1
@@ -10,6 +10,7 @@ import (
1010// the browser. Each action runs the issue command the CLI runs, so the
1111// CLI is the check that they took effect.
1212func TestIssueWebTriage(t *testing.T) {
13 t.Parallel()
1314 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
e2e/label_test.go +1
@@ -8,6 +8,7 @@ import (
88// Labels get colours: set from the CLI, listed with their use, painted
99// on the web, and removed from every issue at once.
1010func TestLabelColors(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/labelweb_test.go +1
@@ -9,6 +9,7 @@ import (
99// The label set itself is managed from the browser: create, recolour and
1010// remove, each through the label command the CLI runs (#163).
1111func TestLabelsWeb(t *testing.T) {
12 t.Parallel()
1213 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1314 aliceKey := inst.newKey(t, "alice")
1415 bobKey := inst.newKey(t, "bob")
e2e/lfs_test.go +1
@@ -34,6 +34,7 @@ func waitForPort(t *testing.T, port int) {
3434}
3535
3636func TestLFS(t *testing.T) {
37 t.Parallel()
3738 if _, err := exec.LookPath("git-lfs"); err != nil {
3839 t.Skip("git-lfs client not installed")
3940 }
e2e/listfilters_test.go +1
@@ -10,6 +10,7 @@ import (
1010// issue list and mr list narrow by label, assignee, author and
1111// milestone, and the web lists take the same names as query parameters.
1212func TestListFilters(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
e2e/mailpref_test.go +1
@@ -12,6 +12,7 @@ import (
1212// mail; on brings it back. The account page carries the same switch
1313// (#194).
1414func TestMailPreference(t *testing.T) {
15 t.Parallel()
1516 smtp := startFakeSMTP(t)
1617 inst := startInstanceWith(t, fmt.Sprintf(
1718 "[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n[web]\nmode = \"accounts\"\n", smtp.addr))
e2e/main_test.go +8 −28
@@ -1,39 +1,19 @@
11package e2e
22
33import (
4 "flag"
54 "fmt"
65 "os"
76 "testing"
8 "time"
97)
108
11// This suite drives real git, ssh, sshd and gpg, and takes six to fifteen
12// minutes depending on the machine — past `go test`'s ten-minute default.
13// Exceeding it panics with the name of whichever test happened to be
14// running, which is never the one at fault and reads like a hang (#143).
15//
16// make test passes -timeout 30m and .gitbay/ci.yml passes -timeout 20m.
17// A bare `go test ./...` gets the default, so say so up front rather than
18// eleven minutes later.
19const minTimeout = 15 * time.Minute
20
219func TestMain(m *testing.M) {
22 flag.Parse()
23 // Only for a whole-package run: `-run TestOneThing -timeout 2m` is a
24 // reasonable thing to type and none of this applies to it.
25 if run := flag.Lookup("test.run"); run == nil || run.Value.String() == "" {
26 if f := flag.Lookup("test.timeout"); f != nil {
27 d, err := time.ParseDuration(f.Value.String())
28 if err == nil && d > 0 && d < minTimeout {
29 fmt.Fprintf(os.Stderr,
30 "e2e: -timeout is %s; this suite needs about %s.\n"+
31 "Run `make test` (which passes -timeout 30m), or pass -timeout yourself.\n"+
32 "Without this check the run panics part way through and blames whichever\n"+
33 "test was executing at the deadline.\n", d, minTimeout)
34 os.Exit(1)
35 }
36 }
10 dir, cleanup, err := makeBinDir()
11 if err != nil {
12 fmt.Fprintf(os.Stderr, "e2e: %v\n", err)
13 os.Exit(1)
3714 }
38 os.Exit(m.Run())
15 binDir = dir
16 code := m.Run()
17 cleanup()
18 os.Exit(code)
3919}
e2e/maint_test.go +1
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestAdminGCAndStats(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314 aliceKey := inst.newKey(t, "alice")
1415 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/mentions_test.go +1
@@ -10,6 +10,7 @@ import (
1010// mute holds, and someone who cannot read the repository is not reached
1111// (#202).
1212func TestMentionsNotify(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 keys := map[string]string{}
1516 for _, u := range []string{"alice", "bob", "carol", "eve"} {
e2e/merge_strategies_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestSquashAndRebaseMerges(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/mergerecord_test.go +1
@@ -12,6 +12,7 @@ import (
1212// or a merge moved the ref and then failed to record itself. Either way
1313// refusing left the merge request open for good (#108).
1414func TestMergeRecordedWhenTargetContainsHead(t *testing.T) {
15 t.Parallel()
1516 inst := startInstance(t)
1617 aliceKey := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/migrate_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestAccountMigration(t *testing.T) {
11 t.Parallel()
1112 src := startInstance(t)
1213 dst := startInstance(t)
1314 key := src.newKey(t, "alice") // one identity, both instances
e2e/migratemr_test.go +1
@@ -17,6 +17,7 @@ import (
1717// diff resolves through it and an empty head_sha is invisible — the first
1818// version of this test made that mistake and passed without the fix.
1919func TestMigratedMRHasADiff(t *testing.T) {
20 t.Parallel()
2021 src := startInstance(t)
2122 dst := startInstance(t)
2223 key := src.newKey(t, "alice")
e2e/milestone_test.go +1
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestMilestonesAndTemplates(t *testing.T) {
12 t.Parallel()
1213 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1314 aliceKey := inst.newKey(t, "alice")
1415 bobKey := inst.newKey(t, "bob")
e2e/mirror_test.go +1
@@ -186,6 +186,7 @@ func TestMirrors(t *testing.T) {
186186}
187187
188188func TestMirrorSSRFGuard(t *testing.T) {
189 t.Parallel()
189190 inst := startInstance(t) // default posture: allow_local off
190191 aliceKey := inst.newKey(t, "alice")
191192 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/mr_test.go +1
@@ -46,6 +46,7 @@ func (i *instance) mrShow(t *testing.T, key, repo, n string) mrShow {
4646}
4747
4848func TestMergeRequests(t *testing.T) {
49 t.Parallel()
4950 inst := startInstance(t)
5051
5152 aliceKey := inst.newKey(t, "alice")
e2e/mrbuilds_test.go +1
@@ -16,6 +16,7 @@ import (
1616// merge request unbuildable and, under require-checks, unmergeable (#98).
1717// A head from another repository runs without the target's secrets.
1818func TestForkMRHeadIsBuilt(t *testing.T) {
19 t.Parallel()
1920 inst := startInstance(t)
2021 inst.runner = buildRunner(t)
2122 aliceKey := inst.newKey(t, "alice")
e2e/mrdraft_test.go +1
@@ -11,6 +11,7 @@ import (
1111// TestMRDraft covers the first stage of #111: a merge request opened to
1212// show work rather than to ask for a merge.
1313func TestMRDraft(t *testing.T) {
14 t.Parallel()
1415 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1516 aliceKey := inst.newKey(t, "alice")
1617 bobKey := inst.newKey(t, "bob")
e2e/mrforkweb_test.go +1
@@ -12,6 +12,7 @@ import (
1212// the source picker offers the branches of a fork they can push to, and
1313// the merge request opens against the parent (#168).
1414func TestMRFromForkWeb(t *testing.T) {
15 t.Parallel()
1516 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1617 aliceKey := inst.newKey(t, "alice")
1718 bobKey := inst.newKey(t, "bob")
e2e/mrrebase_test.go +2
@@ -15,6 +15,7 @@ import (
1515// server is never asked to vouch for a commit it did not receive already
1616// signed (#175).
1717func TestCLIMRRebase(t *testing.T) {
18 t.Parallel()
1819 inst := startInstance(t)
1920 aliceKey := inst.newKey(t, "alice")
2021 inst.admin(t, "admin", "user", "create", "alice",
@@ -84,6 +85,7 @@ func TestCLIMRRebase(t *testing.T) {
8485
8586// The guards: a dirty tree, and a source in a fork this clone cannot push.
8687func TestCLIMRRebaseRefusals(t *testing.T) {
88 t.Parallel()
8789 inst := startInstance(t)
8890 aliceKey := inst.newKey(t, "alice")
8991 bobKey := inst.newKey(t, "bob")
e2e/mrretarget_test.go +1
@@ -12,6 +12,7 @@ import (
1212// SSH and from the browser. Retargeting changes which diff a review was
1313// of, so the existing approvals have to go stale with it.
1414func TestMRRetarget(t *testing.T) {
15 t.Parallel()
1516 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1617 aliceKey := inst.newKey(t, "alice")
1718 bobKey := inst.newKey(t, "bob")
e2e/mrweb_test.go +7
@@ -37,6 +37,7 @@ func (i *instance) login(t *testing.T, key string) *http.Client {
3737// browser. Every action runs the same control command the CLI runs, so the
3838// test also proves the merge gates apply to web merges.
3939func TestMRWebReviewLoop(t *testing.T) {
40 t.Parallel()
4041 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
4142 aliceKey := inst.newKey(t, "alice")
4243 bobKey := inst.newKey(t, "bob")
@@ -182,6 +183,7 @@ func TestMRWebReviewLoop(t *testing.T) {
182183// TestMRWebCreate opens a merge request from the browser and checks the
183184// form survives a refusal with the draft intact.
184185func TestMRWebCreate(t *testing.T) {
186 t.Parallel()
185187 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
186188 aliceKey := inst.newKey(t, "alice")
187189 inst.admin(t, "admin", "user", "create", "alice",
@@ -241,6 +243,7 @@ func TestMRWebCreate(t *testing.T) {
241243// TestMRListRows checks that the merge request list shows each row's
242244// combined check state and comment count (#230).
243245func TestMRListRows(t *testing.T) {
246 t.Parallel()
244247 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
245248 aliceKey := inst.newKey(t, "alice")
246249 inst.admin(t, "admin", "user", "create", "alice",
@@ -292,6 +295,7 @@ func TestMRListRows(t *testing.T) {
292295// proves the page dispatched mr diff-comment rather than writing its own
293296// rows.
294297func TestMRWebDiffThreads(t *testing.T) {
298 t.Parallel()
295299 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
296300 aliceKey := inst.newKey(t, "alice")
297301 inst.admin(t, "admin", "user", "create", "alice",
@@ -406,6 +410,7 @@ func (i *instance) mrThreads(t *testing.T, key, repo, n string) []mrThread {
406410// TestMRDiffEmptyExplained: a merge request whose head was fast-forwarded
407411// into the target outside the request shows why its diff is empty.
408412func TestMRDiffEmptyExplained(t *testing.T) {
413 t.Parallel()
409414 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
410415 key := inst.newKey(t, "alice")
411416 inst.admin(t, "admin", "user", "create", "alice", "--key", key+".pub", "--email", "alice@example.test", "--verified")
@@ -442,6 +447,7 @@ func TestMRDiffEmptyExplained(t *testing.T) {
442447// web form, and checks both pages say so; clearing it over ssh removes
443448// both lines again (#223).
444449func TestMRSupersedes(t *testing.T) {
450 t.Parallel()
445451 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
446452 aliceKey := inst.newKey(t, "alice")
447453 inst.admin(t, "admin", "user", "create", "alice",
@@ -507,6 +513,7 @@ func TestMRSupersedes(t *testing.T) {
507513// list by it (#231). The CLI is the check that the page dispatched
508514// mr label rather than writing its own rows.
509515func TestMRWebLabels(t *testing.T) {
516 t.Parallel()
510517 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
511518 aliceKey := inst.newKey(t, "alice")
512519 bobKey := inst.newKey(t, "bob")
e2e/notify_test.go +1
@@ -39,6 +39,7 @@ func (f *fakeSMTP) waitFor(t *testing.T, recipient, substr string) string {
3939}
4040
4141func TestActivityNotifications(t *testing.T) {
42 t.Parallel()
4243 smtp := startFakeSMTP(t)
4344 inst := startInstanceWith(t, fmt.Sprintf(
4445 "[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
e2e/org_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestOrganizations(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/orgbody_test.go +1
@@ -10,6 +10,7 @@ import (
1010// without a format is markdown, which is what everything written before the
1111// format existed carries.
1212func TestOrgBodies(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 inst.admin(t, "admin", "user", "create", "alice",
e2e/orglabels_test.go +1
@@ -11,6 +11,7 @@ import (
1111// commit in one repository closes an issue in another; the org pages
1212// answer members and outsiders as their access allows.
1313func TestOrgLabelsMilestonesAndCrossRepoCloses(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 carolKey := inst.newKey(t, "carol")
e2e/orgremove_test.go +1
@@ -11,6 +11,7 @@ import (
1111// teams (#196). Before the fix, team_members rows survived removal, so a
1212// former member kept pushing.
1313func TestOrgMemberRemovalEndsTeamAccess(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 bobKey := inst.newKey(t, "bob")
e2e/orgweb_test.go +2
@@ -12,6 +12,7 @@ import (
1212// membership and teams, admin-gated, dispatched through the same commands
1313// the CLI uses.
1414func TestOrgManagementWeb(t *testing.T) {
15 t.Parallel()
1516 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1617 aliceKey := inst.newKey(t, "alice")
1718 bobKey := inst.newKey(t, "bob")
@@ -157,6 +158,7 @@ func orgMembers(t *testing.T, inst *instance, key string) []string {
157158// rename from the org's. Delete stays on the CLI, where a typed
158159// confirmation is the norm (#167).
159160func TestOrgLifecycleWeb(t *testing.T) {
161 t.Parallel()
160162 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
161163 aliceKey := inst.newKey(t, "alice")
162164 bobKey := inst.newKey(t, "bob")
e2e/pagination_test.go +1
@@ -28,6 +28,7 @@ func decodePage[T any](t *testing.T, out string) ([]T, string) {
2828// Cursor pagination on the list commands: opaque cursors, stable pages,
2929// and the bare-array shape untouched when the flags are absent.
3030func TestCursorPagination(t *testing.T) {
31 t.Parallel()
3132 inst := startInstance(t)
3233 aliceKey := inst.newKey(t, "alice")
3334 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/pendingreview_test.go +1
@@ -12,6 +12,7 @@ import (
1212// review and submits it as a unit, instead of every comment landing in
1313// the author's inbox the moment it is typed.
1414func TestPendingReviewBatch(t *testing.T) {
15 t.Parallel()
1516 inst := startInstance(t)
1617 authorKey := inst.newKey(t, "author")
1718 reviewerKey := inst.newKey(t, "reviewer")
e2e/previewweb_test.go +1
@@ -13,6 +13,7 @@ import (
1313// create, their edit and comment boxes, release create and edit, the
1414// profile about text, and the file editor on a path the forge renders.
1515func TestMarkupPreviewWeb(t *testing.T) {
16 t.Parallel()
1617 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1718 aliceKey := inst.newKey(t, "alice")
1819 inst.admin(t, "admin", "user", "create", "alice",
e2e/profile_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestOwnerProfiles(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/profileabout_test.go +4
@@ -8,6 +8,7 @@ import (
88// The about text is a file in <owner>/.gitbay, read on every surface with
99// the reader's own access.
1010func TestProfileAboutFromRepo(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 inst.admin(t, "admin", "user", "create", "alice",
@@ -61,6 +62,7 @@ func TestProfileAboutFromRepo(t *testing.T) {
6162// The extension picks the format, .md wins the resolution order, and a
6263// private .gitbay keeps the about to the people who can read it.
6364func TestProfileAboutFormatAndPrivacy(t *testing.T) {
65 t.Parallel()
6466 inst := startInstance(t)
6567 aliceKey := inst.newKey(t, "alice")
6668 inst.admin(t, "admin", "user", "create", "alice",
@@ -104,6 +106,7 @@ func TestProfileAboutFormatAndPrivacy(t *testing.T) {
104106// A dot-repo is infrastructure: it stays out of explore and off the
105107// profile's repository list, and stays in the owner's own inventory.
106108func TestDotReposHiddenFromListings(t *testing.T) {
109 t.Parallel()
107110 inst := startInstance(t)
108111 aliceKey := inst.newKey(t, "alice")
109112 inst.admin(t, "admin", "user", "create", "alice",
@@ -140,6 +143,7 @@ func TestDotReposHiddenFromListings(t *testing.T) {
140143
141144// The about is not settable through profile set any more: it is a file.
142145func TestProfileSetHasNoAbout(t *testing.T) {
146 t.Parallel()
143147 inst := startInstance(t)
144148 aliceKey := inst.newKey(t, "alice")
145149 inst.admin(t, "admin", "user", "create", "alice",
e2e/profileweb_test.go +2
@@ -11,6 +11,7 @@ import (
1111// emptying a field actually clears it rather than being skipped. The about
1212// text is not on this form — it is a file, covered below.
1313func TestProfileSettingsWeb(t *testing.T) {
14 t.Parallel()
1415 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1516 aliceKey := inst.newKey(t, "alice")
1617 inst.admin(t, "admin", "user", "create", "alice",
@@ -98,6 +99,7 @@ func TestProfileSettingsWeb(t *testing.T) {
9899// The settings page does not edit the about text; it creates the
99100// repository that holds it and points at the file editor.
100101func TestProfileAboutRepoFromWeb(t *testing.T) {
102 t.Parallel()
101103 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
102104 aliceKey := inst.newKey(t, "alice")
103105 inst.admin(t, "admin", "user", "create", "alice",
e2e/rangediff_test.go +1
@@ -12,6 +12,7 @@ import (
1212// nothing said what had changed between the two heads. A plain diff of
1313// the heads cannot answer that — it shows the whole branch again.
1414func TestMRRangeDiff(t *testing.T) {
15 t.Parallel()
1516 inst := startInstance(t)
1617 key := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice", "--key", key+".pub")
e2e/readonly_test.go +1
@@ -26,6 +26,7 @@ import (
2626// Every ReadOnly command needs an entry in readArgs; a new one without
2727// arguments here fails the test rather than going untested.
2828func TestReadOnlyCommandsWriteNothing(t *testing.T) {
29 t.Parallel()
2930 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n[webhooks]\nallow_local = true\n")
3031 aliceKey := inst.newKey(t, "alice")
3132 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub",
e2e/reap_test.go +4
@@ -88,6 +88,7 @@ func TestStaleBuildReapedWithoutRunner(t *testing.T) {
8888}
8989
9090func TestAdminRunners(t *testing.T) {
91 t.Parallel()
9192 inst := startInstance(t)
9293 rootKey := inst.newKey(t, "root")
9394 aliceKey := inst.newKey(t, "alice")
@@ -152,6 +153,7 @@ func TestAdminRunners(t *testing.T) {
152153
153154// /healthz is unauthenticated, cache-free, and says which build serves.
154155func TestHealthz(t *testing.T) {
156 t.Parallel()
155157 inst := startInstance(t)
156158 status, body := inst.get(t, "/healthz")
157159 if status != 200 || !strings.Contains(body, `"ok":true`) || !strings.Contains(body, `"commit":"`) {
@@ -166,6 +168,7 @@ func TestHealthz(t *testing.T) {
166168// gc --lfs removes objects no pointer names, keeps referenced ones, and
167169// leaves anything young enough to be an upload ahead of its push.
168170func TestGCLFSOrphans(t *testing.T) {
171 t.Parallel()
169172 inst := startInstance(t)
170173 aliceKey := inst.newKey(t, "alice")
171174 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
@@ -216,6 +219,7 @@ func TestGCLFSOrphans(t *testing.T) {
216219// backup --verify reads an archive back and says whether a restore would
217220// have what the database expects.
218221func TestBackupVerify(t *testing.T) {
222 t.Parallel()
219223 inst := startInstance(t)
220224 aliceKey := inst.newKey(t, "alice")
221225 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/registration_test.go +2
@@ -111,6 +111,7 @@ func extractCode(t *testing.T, mail string) string {
111111}
112112
113113func TestOpenRegistration(t *testing.T) {
114 t.Parallel()
114115 smtp := startFakeSMTP(t)
115116 inst := startInstanceWith(t, fmt.Sprintf(
116117 "[registration]\nmode = \"open\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
@@ -195,6 +196,7 @@ func TestOpenRegistration(t *testing.T) {
195196}
196197
197198func TestInviteRegistration(t *testing.T) {
199 t.Parallel()
198200 smtp := startFakeSMTP(t)
199201 inst := startInstanceWith(t, fmt.Sprintf(
200202 "[registration]\nmode = \"invite\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
e2e/release_test.go +1
@@ -10,6 +10,7 @@ import (
1010)
1111
1212func TestReleases(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/releaseweb_test.go +1
@@ -11,6 +11,7 @@ import (
1111// TestReleaseAndBuildWeb creates and edits a release and triggers a build
1212// from the browser, each through the command the CLI runs.
1313func TestReleaseAndBuildWeb(t *testing.T) {
14 t.Parallel()
1415 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1516 inst.runner = buildRunner(t)
1617 aliceKey := inst.newKey(t, "alice")
e2e/reporename_test.go +1
@@ -10,6 +10,7 @@ import (
1010// repo rename moves the directory and the row together; what hangs off
1111// the repository by id (issues here) follows it (#190).
1212func TestRepoRename(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
e2e/reposhowstate_test.go +1
@@ -9,6 +9,7 @@ import (
99// the caller can read it, what the repository was forked from — so a
1010// client draws the real state without a second read per screen (#178).
1111func TestRepoShowCarriesViewerState(t *testing.T) {
12 t.Parallel()
1213 inst := startInstance(t)
1314 aliceKey := inst.newKey(t, "alice")
1415 bobKey := inst.newKey(t, "bob")
e2e/requiremr_test.go +1
@@ -12,6 +12,7 @@ import (
1212// not exist yet can still be created, and unprotected branches are
1313// unaffected (#197).
1414func TestRequireMR(t *testing.T) {
15 t.Parallel()
1516 inst := startInstance(t)
1617 aliceKey := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice",
e2e/reviewcarry_test.go +1
@@ -10,6 +10,7 @@ import (
1010// A rebase that leaves the merge request's diff unchanged keeps its
1111// fresh approvals; a push that changes the diff stales them (#198).
1212func TestApprovalsSurviveSameDiffRebase(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
e2e/reviewloop_test.go +1
@@ -20,6 +20,7 @@ import (
2020// exercised the loop end to end at all — every feature had its own test
2121// and none of them met.
2222func TestTwoAccountReviewLoop(t *testing.T) {
23 t.Parallel()
2324 inst := startInstance(t)
2425 authorKey := inst.newKey(t, "author")
2526 reviewerKey := inst.newKey(t, "reviewer")
e2e/reviewrequest_test.go +1
@@ -13,6 +13,7 @@ import (
1313// and --remove takes them out outright. A separate, private repository
1414// checks that requesting someone who cannot read it is refused.
1515func TestMRReviewRequest(t *testing.T) {
16 t.Parallel()
1617 inst := startInstance(t)
1718 aliceKey := inst.newKey(t, "alice")
1819 bobKey := inst.newKey(t, "bob")
e2e/reviewstanding_test.go +1
@@ -20,6 +20,7 @@ import (
2020// Reviewing stays open to everyone: an outside opinion on a public change
2121// is worth having. It just does not decide the gate.
2222func TestReviewsCountOnlyFromWriters(t *testing.T) {
23 t.Parallel()
2324 inst := startInstance(t)
2425 ownerKey := inst.newKey(t, "owner")
2526 writerKey := inst.newKey(t, "writer")
e2e/robustness_test.go +2
@@ -15,6 +15,7 @@ import (
1515// tree, cat, grep, blame and the web blob page. Nothing in the suite
1616// covered a non-ASCII path before (#129).
1717func TestNonASCIIPaths(t *testing.T) {
18 t.Parallel()
1819 inst := startInstance(t)
1920 aliceKey := inst.newKey(t, "alice")
2021 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
@@ -62,6 +63,7 @@ func TestNonASCIIPaths(t *testing.T) {
6263// land: the receive hooks, the post-receive work and the store take
6364// them concurrently without losing one (#129).
6465func TestConcurrentPushes(t *testing.T) {
66 t.Parallel()
6567 inst := startInstance(t)
6668 aliceKey := inst.newKey(t, "alice")
6769 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/runner_scope_test.go +2
@@ -13,6 +13,7 @@ import (
1313// that should only build one project ends up executing every repository's
1414// steps — including those of a repository it has nothing to do with.
1515func TestRunnerNextScopedToRepos(t *testing.T) {
16 t.Parallel()
1617 inst := startInstance(t)
1718 aliceKey := inst.newKey(t, "alice")
1819 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
@@ -65,6 +66,7 @@ func TestRunnerNextScopedToRepos(t *testing.T) {
6566// same account's full key nor the runner key reaches the wrong side, so a
6667// key stolen from a build step cannot administer the instance (#92).
6768func TestRunnerScopedKey(t *testing.T) {
69 t.Parallel()
6870 inst := startInstance(t)
6971 aliceKey := inst.newKey(t, "alice")
7072 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/runnerattach_test.go +1
@@ -14,6 +14,7 @@ import (
1414// wrote. The runner builds their push and leaves a fork's merge request
1515// head alone until started with -untrusted.
1616func TestAttachedRunnerBuildsOwnRepo(t *testing.T) {
17 t.Parallel()
1718 inst := startInstance(t)
1819 inst.runner = buildRunner(t)
1920 aliceKey := inst.newKey(t, "alice")
e2e/runnerstop_test.go +3
@@ -79,6 +79,7 @@ func (i *instance) buildStatus(t *testing.T, key string) string {
7979}
8080
8181func TestRunnerDrainsOnSIGTERM(t *testing.T) {
82 t.Parallel()
8283 inst, key := stopFixture(t)
8384 cmd := exec.Command(inst.runner, inst.runnerArgs(t, key)...)
8485 cmd.Env = append(os.Environ(), "XDG_CONFIG_HOME="+t.TempDir(), "GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null")
@@ -137,6 +138,7 @@ func dropInValue(conf, key string) string {
137138// The unit must let the drain happen: the stop signal reaches the runner
138139// alone, and the stop timeout outlasts a build plus the report retries.
139140func TestRunnerDropInLetsTheDrainHappen(t *testing.T) {
141 t.Parallel()
140142 conf := runnerDropIn(t)
141143 if mode := dropInValue(conf, "KillMode"); mode != "mixed" {
142144 t.Fatalf("KillMode=%q, want mixed: control-group signals the step and the log session with the runner", mode)
@@ -173,6 +175,7 @@ func haveUserSystemd(t *testing.T) bool {
173175// KillMode the build in flight is reported a success; under systemd's
174176// default it is not, which is the failure that shipped once.
175177func TestRunnerDrainUnderSystemd(t *testing.T) {
178 t.Parallel()
176179 if !haveUserSystemd(t) {
177180 t.Skip("no systemd user manager")
178181 }
e2e/runnerweb_test.go +1
@@ -10,6 +10,7 @@ import (
1010// The settings page attaches and detaches runners through the same
1111// commands the CLI uses, and lists what is attached.
1212func TestRunnerSettingsWeb(t *testing.T) {
13 t.Parallel()
1314 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1415 aliceKey := inst.newKey(t, "alice")
1516 inst.admin(t, "admin", "user", "create", "alice",
e2e/search_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestSearch(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/searchweb_test.go +1
@@ -9,6 +9,7 @@ import (
99// TestGlobalSearchAndNotificationsWeb covers the two web surfaces #118
1010// still lacked: /search across the instance, and the notification inbox.
1111func TestGlobalSearchAndNotificationsWeb(t *testing.T) {
12 t.Parallel()
1213 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1314 aliceKey := inst.newKey(t, "alice")
1415 bobKey := inst.newKey(t, "bob")
e2e/security_test.go +1
@@ -7,6 +7,7 @@ import (
77)
88
99func TestSecurityHeaders(t *testing.T) {
10 t.Parallel()
1011 inst := startInstance(t)
1112 aliceKey := inst.newKey(t, "alice")
1213 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/settingsweb_test.go +1
@@ -9,6 +9,7 @@ import (
99// TestRepoSettingsWeb drives the settings page: each control runs the
1010// command the CLI runs, so repo show and settings show are the check.
1111func TestRepoSettingsWeb(t *testing.T) {
12 t.Parallel()
1213 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1314 aliceKey := inst.newKey(t, "alice")
1415 bobKey := inst.newKey(t, "bob")
e2e/shutdown_test.go +2
@@ -15,6 +15,7 @@ import (
1515// kill: no listener closed, no request or push allowed to finish. The
1616// daemon now stops its listeners, drains, and exits 0 (#105).
1717func TestServeStopsOnSIGTERM(t *testing.T) {
18 t.Parallel()
1819 inst := startInstance(t)
1920 if resp, err := http.Get(inst.base() + "/healthz"); err != nil || resp.StatusCode != 200 {
2021 t.Fatalf("healthz before shutdown: %v", err)
@@ -41,6 +42,7 @@ func TestServeStopsOnSIGTERM(t *testing.T) {
4142// idle connection is closed at once rather than holding the drain for its
4243// full 30 s; only a session mid-command is waited for (#141).
4344func TestShutdownClosesIdleConnections(t *testing.T) {
45 t.Parallel()
4446 inst := startInstance(t)
4547 aliceKey := inst.newKey(t, "alice")
4648 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/sig_test.go +1
@@ -153,6 +153,7 @@ func (i *instance) repoLog(t *testing.T, key, repo string) map[string]logEntry {
153153}
154154
155155func TestSignatureVerification(t *testing.T) {
156 t.Parallel()
156157 inst := startInstance(t)
157158
158159 aliceKey := inst.newKey(t, "alice")
e2e/signupnotice_test.go +2
@@ -11,6 +11,7 @@ import (
1111// becomes active. An open-mode signup counts at verification, not when
1212// the row is created, so an unverified attempt is silent (#234).
1313func TestSignupNotifiesAdmins(t *testing.T) {
14 t.Parallel()
1415 smtp := startFakeSMTP(t)
1516 inst := startInstanceWith(t, fmt.Sprintf(
1617 "[registration]\nmode = \"open\"\nnotify_admin = true\n"+
@@ -45,6 +46,7 @@ func TestSignupNotifiesAdmins(t *testing.T) {
4546// With notify_admin off, the default, the same signup mails no one but
4647// the person registering.
4748func TestSignupNoticeOffByDefault(t *testing.T) {
49 t.Parallel()
4850 smtp := startFakeSMTP(t)
4951 inst := startInstanceWith(t, fmt.Sprintf(
5052 "[registration]\nmode = \"open\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
e2e/snippet_test.go +1
@@ -10,6 +10,7 @@ import (
1010// Snippets over SSH: create from stdin, read back, list by visibility,
1111// edit files and metadata, and the not-found rule for private ones.
1212func TestSnippets(t *testing.T) {
13 t.Parallel()
1314 inst := startInstanceWith(t, "[limits]\nmax_snippets_per_user = 3\n")
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
e2e/snippetweb_test.go +1
@@ -24,6 +24,7 @@ func snippetIDFrom(t *testing.T, out string) string {
2424// Snippet pages: the owner's list, one snippet with highlighted files, the
2525// raw route, the owner-page link, and 404 for what the viewer may not see.
2626func TestSnippetsWeb(t *testing.T) {
27 t.Parallel()
2728 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
2829 aliceKey := inst.newKey(t, "alice")
2930 bobKey := inst.newKey(t, "bob")
e2e/ssh_test.go +34 −26
@@ -4,11 +4,13 @@ package e2e
44import (
55 "encoding/json"
66 "fmt"
7 "math/rand/v2"
78 "net"
89 "os"
910 "os/exec"
1011 "path/filepath"
1112 "strings"
13 "sync/atomic"
1214 "testing"
1315)
1416
@@ -24,40 +26,44 @@ type instance struct {
2426 sshDir string // per-user client keys live here
2527}
2628
27func buildGitbayd(t *testing.T) string {
28 t.Helper()
29 bin := filepath.Join(t.TempDir(), "gitbayd")
30 cmd := exec.Command("go", "build", "-o", bin, "gitbay.org/gitbay/cmd/gitbayd")
31 cmd.Dir = ".."
32 if out, err := cmd.CombinedOutput(); err != nil {
33 t.Fatalf("build gitbayd: %v\n%s", err, out)
34 }
35 return bin
36}
29// nextPort hands out candidate ports. Seeded randomly so two test processes
30// on one machine — `go test ./...` runs packages concurrently — start in
31// different places.
32var nextPort = func() *atomic.Int32 {
33 var n atomic.Int32
34 n.Store(int32(20000 + rand.IntN(20000)))
35 return &n
36}()
3737
38// freePorts reserves n distinct ports. A port is chosen by binding :0 and
39// reading back what the kernel assigned, so every listener has to stay open
40// until all of them are picked — closing one before picking the next lets
41// the kernel hand out the same port again, and the instance that asked for
42// three then fails to bind its second listener.
38// freePorts reserves n distinct ports, counting up rather than asking the
39// kernel for :0.
4340//
44// Still a narrowing rather than a guarantee: another process can take a port
45// between the close here and the bind in gitbayd. Distinctness within one
46// instance is the part that is ours.
41// :0 cannot be made safe once tests run in parallel. A port is picked by
42// binding, reading the number back and closing, and between that close and
43// the bind inside gitbayd the kernel is free to hand the same port to
44// another instance picking at that moment. The loser does not fail
45// cleanly: waitForPort only asks whether something is listening, so a test
46// whose port was taken talks to a different test's server and reports
47// whatever that one says.
48//
49// A counter cannot collide within a process, whatever the interleaving.
50// Each candidate is still bind-tested, which skips ports other programs
51// hold. An outside process taking one in the close-to-bind window remains
52// possible, as it was before; that race is not ours to close.
4753func freePorts(t *testing.T, n int) []int {
4854 t.Helper()
49 lns := make([]net.Listener, 0, n)
5055 ports := make([]int, 0, n)
51 for i := 0; i < n; i++ {
52 ln, err := net.Listen("tcp", "127.0.0.1:0")
56 for len(ports) < n {
57 p := int(nextPort.Add(1))
58 if p > 60000 {
59 t.Fatal("ran out of ports")
60 }
61 ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", p))
5362 if err != nil {
54 t.Fatal(err)
63 continue // somebody else has it
5564 }
56 lns = append(lns, ln)
57 ports = append(ports, ln.Addr().(*net.TCPAddr).Port)
58 }
59 for _, ln := range lns {
6065 ln.Close()
66 ports = append(ports, p)
6167 }
6268 return ports
6369}
@@ -184,6 +190,7 @@ func (i *instance) ssh(t *testing.T, key string, stdin string, args ...string) (
184190}
185191
186192func TestControlPlaneOverBareSSH(t *testing.T) {
193 t.Parallel()
187194 inst := startInstance(t)
188195
189196 aliceKey := inst.newKey(t, "alice")
@@ -278,6 +285,7 @@ func TestControlPlaneOverBareSSH(t *testing.T) {
278285// a row and then fails to bind its second listener, which surfaces as an
279286// unrelated test timing out on "gitbayd did not start listening".
280287func TestFreePortsAreDistinct(t *testing.T) {
288 t.Parallel()
281289 for round := 0; round < 50; round++ {
282290 seen := map[int]bool{}
283291 for _, p := range freePorts(t, 8) {
e2e/stack_test.go +1
@@ -12,6 +12,7 @@ import (
1212// it onto the merged target with its reviews intact; a squash under a
1313// stack is refused.
1414func TestStackedMergeRequests(t *testing.T) {
15 t.Parallel()
1516 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1617 aliceKey := inst.newKey(t, "alice")
1718 bobKey := inst.newKey(t, "bob")
e2e/status_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestCommitStatuses(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/system_test.go +1
@@ -15,6 +15,7 @@ import (
1515// TestSystemSSHMode runs the M1/M2 scenarios against a real host sshd using
1616// AuthorizedKeysCommand + forced command instead of the embedded listener.
1717func TestSystemSSHMode(t *testing.T) {
18 t.Parallel()
1819 sshdBin := "/usr/sbin/sshd"
1920 if _, err := os.Stat(sshdBin); err != nil {
2021 t.Skipf("no host sshd at %s", sshdBin)
e2e/tagprotect_test.go +1
@@ -10,6 +10,7 @@ import (
1010// Protected-tag globs refuse moving and deleting matching tags; a tag a
1111// release is anchored to refuses both on its own (#201).
1212func TestTagProtection(t *testing.T) {
13 t.Parallel()
1314 inst := startInstance(t)
1415 aliceKey := inst.newKey(t, "alice")
1516 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/teams_test.go +1
@@ -6,6 +6,7 @@ import (
66)
77
88func TestOrgTeams(t *testing.T) {
9 t.Parallel()
910 inst := startInstance(t)
1011 adminKey := inst.newKey(t, "alice")
1112 bobKey := inst.newKey(t, "bob")
e2e/theme_test.go +1
@@ -9,6 +9,7 @@ import (
99// web theme set fixes the colour scheme the layout stamps on <html>; the
1010// account page shows the same setting and changes it (#232).
1111func TestWebTheme(t *testing.T) {
12 t.Parallel()
1213 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1314 key := inst.newKey(t, "alice")
1415 inst.admin(t, "admin", "user", "create", "alice", "--key", key+".pub")
e2e/userdelete_test.go +1
@@ -6,6 +6,7 @@ import (
66)
77
88func TestAdminUserDelete(t *testing.T) {
9 t.Parallel()
910 inst := startInstance(t)
1011 aliceKey := inst.newKey(t, "alice")
1112 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
e2e/watchpref_test.go +1
@@ -11,6 +11,7 @@ import (
1111// read access is not enough, and off returns it to the default. The
1212// account page carries the same switch (#194).
1313func TestWatchPreference(t *testing.T) {
14 t.Parallel()
1415 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1516 aliceKey := inst.newKey(t, "alice")
1617 bobKey := inst.newKey(t, "bob")
e2e/web_test.go +1
@@ -27,6 +27,7 @@ func (i *instance) get(t *testing.T, path string) (int, string) {
2727}
2828
2929func TestWebUI(t *testing.T) {
30 t.Parallel()
3031 inst := startInstance(t)
3132
3233 aliceKey := inst.newKey(t, "alice")
e2e/webhook_test.go +2
@@ -78,6 +78,7 @@ func (h *hookReceiver) waitN(t *testing.T, n int) []capturedHook {
7878}
7979
8080func TestWebhooks(t *testing.T) {
81 t.Parallel()
8182 inst := startInstanceWith(t, "[webhooks]\nallow_local = true\n")
8283 // Restart the daemon with a fast retry base for the failure tests.
8384 inst.proc.Process.Kill()
@@ -260,6 +261,7 @@ func TestWebhooks(t *testing.T) {
260261// asserts each reaches a subscriber. Half the forge's mutations recorded
261262// nothing, so a webhook could be subscribed to them and never fire.
262263func TestWebhookEventCoverage(t *testing.T) {
264 t.Parallel()
263265 inst := startInstance(t)
264266 aliceKey := inst.newKey(t, "alice")
265267 bobKey := inst.newKey(t, "bob")
e2e/webonly_test.go +1
@@ -11,6 +11,7 @@ import (
1111// ref, an archive, and the public listing. Each existed as a web route
1212// whose handler went around the registry, so no other surface had them.
1313func TestWebOnlyReadsAreCommands(t *testing.T) {
14 t.Parallel()
1415 inst := startInstance(t)
1516 aliceKey := inst.newKey(t, "alice")
1617 bobKey := inst.newKey(t, "bob")
e2e/websessions_test.go +1
@@ -10,6 +10,7 @@ import (
1010// A browser session can be listed and ended from SSH, one at a time or
1111// all at once, and only its owner sees it.
1212func TestWebSessionsListRevoke(t *testing.T) {
13 t.Parallel()
1314 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
1415 aliceKey := inst.newKey(t, "alice")
1516 bobKey := inst.newKey(t, "bob")
e2e/websignup_test.go +2
@@ -9,6 +9,7 @@ import (
99)
1010
1111func TestWebSignup(t *testing.T) {
12 t.Parallel()
1213 smtp := startFakeSMTP(t)
1314 inst := startInstanceWith(t, fmt.Sprintf(
1415 "[web]\nmode = \"accounts\"\n[registration]\nmode = \"invite\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n",
@@ -73,6 +74,7 @@ func TestWebSignup(t *testing.T) {
7374}
7475
7576func TestWebSignupClosedInstance(t *testing.T) {
77 t.Parallel()
7678 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
7779 // Closed registration: no signup route at all, and no landing hint.
7880 if status, _ := inst.get(t, "/register"); status != 404 {
e2e/webwrites_test.go +1
@@ -12,6 +12,7 @@ import (
1212// store directly and skip the per-account quota; issue comments skipped
1313// the archived check (#93).
1414func TestWebWritesGoThroughRegistry(t *testing.T) {
15 t.Parallel()
1516 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n[limits]\nmax_repos_per_user = 1\n")
1617 aliceKey := inst.newKey(t, "alice")
1718 inst.admin(t, "admin", "user", "create", "alice",
e2e/wiki_test.go +1
@@ -8,6 +8,7 @@ import (
88)
99
1010func TestWikis(t *testing.T) {
11 t.Parallel()
1112 inst := startInstance(t)
1213 aliceKey := inst.newKey(t, "alice")
1314 bobKey := inst.newKey(t, "bob")
e2e/writerate_test.go +2
@@ -11,6 +11,7 @@ import (
1111// budget is the same one whichever surface spends it. Reads are not
1212// charged (#148).
1313func TestWriteRateLimit(t *testing.T) {
14 t.Parallel()
1415 inst := startInstanceWith(t, "[limits]\nwrite_rate = 4\n")
1516 aliceKey := inst.newKey(t, "alice")
1617 bobKey := inst.newKey(t, "bob")
@@ -48,6 +49,7 @@ func TestWriteRateLimit(t *testing.T) {
4849// The runner protocol is exempt: a build streams its log in many small
4950// writes, and throttling those would throttle CI itself.
5051func TestWriteRateLimitSparesTheRunner(t *testing.T) {
52 t.Parallel()
5153 inst := startInstanceWith(t, "[limits]\nwrite_rate = 2\n")
5254 inst.runner = buildRunner(t)
5355 aliceKey := inst.newKey(t, "alice")