Commit d2fa3c3cbb

d2fa3c3cbb138c5df0e5ff1a4c971c6b1c6dc573

parent: 89601690f2

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-21 21:17 UTC

e2e: build each binary once per process

startInstance built gitbayd per test, so a full run linked a
byte-identical 35MB binary 208 times. Go's build cache covers the
compile but not the final link, which measured 0.8s each. The gitbay
and gitbay-runner helpers did the same.

One sync.Once per binary, under a directory TestMain owns: t.TempDir
is removed when the test that asked for it ends, and these outlive any
single test. Built lazily, so -run of one test does not link binaries
it has no use for.

Full suite locally: 637s to 401s.
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/ci_test.go −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()
e2e/cli_test.go −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
e2e/main_test.go +9 −1
@@ -35,5 +35,13 @@ func TestMain(m *testing.M) {
3535 }
3636 }
3737 }
38 os.Exit(m.Run())
38 dir, cleanup, err := makeBinDir()
39 if err != nil {
40 fmt.Fprintf(os.Stderr, "e2e: %v\n", err)
41 os.Exit(1)
42 }
43 binDir = dir
44 code := m.Run()
45 cleanup()
46 os.Exit(code)
3947}
e2e/ssh_test.go −11
@@ -24,17 +24,6 @@ type instance struct {
2424 sshDir string // per-user client keys live here
2525}
2626
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}
37
3827// freePorts reserves n distinct ports. A port is chosen by binding :0 and
3928// reading back what the kernel assigned, so every listener has to stay open
4029// until all of them are picked — closing one before picking the next lets