Fill the shared module cache with the release by the probe before the real-go integration tests, and skip loudly when it cannot (issue 1560, ADR 0476)

This commit is contained in:
2026-10-21 00:56:03 +00:00
parent c5f8f81a07
commit 4605fcf46d
2 changed files with 57 additions and 6 deletions
@@ -7,11 +7,14 @@ import (
"bytes"
"compress/gzip"
"context"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
@@ -21,7 +24,52 @@ func TestMain(m *testing.M) {
if len(os.Args) > 1 && os.Args[1] == busArg {
os.Exit(runBus(os.Args[2:]))
}
os.Exit(m.Run())
code := m.Run()
releaseCleanup()
os.Exit(code)
}
// The module cache the real-go tests share, filled once by the probe as the module's start does (ADR 0476 §3): the
// steps carry GOTOOLCHAIN=<RecipeGo> and fetch no toolchain, so a test that runs the real go needs the release in its
// cache first.
var (
releaseOnce sync.Once
releaseCache string
releaseErr error
)
// needRelease answers the cache with the recipe's release in it, filled by ProbeGo, and skips loudly when the probe
// cannot have it (no network to Go's download service where the check runs); the build node has it (issues 1486, 1560).
func needRelease(t *testing.T, home string) string {
t.Helper()
releaseOnce.Do(func() {
releaseCache, releaseErr = os.MkdirTemp("", "release-cache")
if releaseErr != nil {
return
}
var v string
if v, releaseErr = ProbeGo(home, releaseCache)(context.Background()); releaseErr == nil && v != RecipeGo {
releaseErr = fmt.Errorf("the probe read %s", v)
}
})
if releaseErr != nil {
t.Skipf("NOT RUN: ADR 0476: the probe could not put %s into the module cache where this check runs (%v); proven live on a build node", RecipeGo, releaseErr)
}
return releaseCache
}
// releaseCleanup removes the shared cache; Go writes its module cache read-only.
func releaseCleanup() {
if releaseCache == "" {
return
}
filepath.WalkDir(releaseCache, func(p string, d fs.DirEntry, err error) error {
if err == nil && d.IsDir() {
os.Chmod(p, 0o755)
}
return nil
})
os.RemoveAll(releaseCache)
}
// needTools skips, loudly, a test whose tools are not installed where the check runs (the mesh's Go toolchain image
@@ -68,7 +116,7 @@ func TestUnitLevelRunsTheRealGoOnTheFixtureRepositories(t *testing.T) {
for name, w := range want {
tree := repository(t, name)
home := t.TempDir()
m := &Module{Home: home, Work: t.TempDir(), Cache: t.TempDir()}
m := &Module{Home: home, Work: t.TempDir(), Cache: needRelease(t, home)}
m.Run = StepRunner(home)
res, err := m.runLevel(context.Background(), tree, levelOpts{level: LevelUnit, bounds: Bounds{Run: UnitBound}})
if err != nil {
@@ -111,7 +159,7 @@ func TestIntegrationLevelRunsAgainstARealStoreAndBusAndLeavesNothing(t *testing.
t.Fatal(err)
}
home := t.TempDir()
m := &Module{Recipe: Current(), Home: home, Work: work, Cache: t.TempDir()}
m := &Module{Recipe: Current(), Home: home, Work: work, Cache: needRelease(t, home)}
m.Run = StepRunner(home)
res, err := m.integration(context.Background(), "ws1", filepath.Join(work, "ws1", "tree"), nil)
if err != nil {
@@ -149,7 +197,7 @@ func TestBuildTwiceGivesOneDigestAndOneExecutable(t *testing.T) {
t.Fatal(err)
}
home := t.TempDir()
m := &Module{Home: home, Work: work, Cache: t.TempDir()}
m := &Module{Home: home, Work: work, Cache: needRelease(t, home)}
m.Run = StepRunner(home)
args := map[string]any{"workspace": "ws1", "module": ".", "artifact": "tools"}
first, err := m.build(context.Background(), args)
@@ -25,8 +25,11 @@ func TestTheProbePutsTheReleaseIntoTheCacheAndAStepReadsItOffline(t *testing.T)
})
})
v, err := ProbeGo(home, cache)(context.Background())
if err != nil || v != RecipeGo {
t.Fatalf("probe = %q, %v; want %s", v, err, RecipeGo)
if err != nil {
t.Skipf("NOT RUN: ADR 0476: the probe could not put %s into the module cache where this check runs (%v); proven live on a build node", RecipeGo, err)
}
if v != RecipeGo {
t.Fatalf("probe = %q; want %s", v, RecipeGo)
}
dirs, _ := filepath.Glob(filepath.Join(cache, "gomod", "golang.org", "toolchain@v0.0.1-"+RecipeGo+".*"))
if len(dirs) != 1 {