// Package gates holds the tests of the zone's BUILD gates — the checks that run before any code // does. They live in their own package because their subject is the Makefile and go.mod rather than // any package's behaviour, and a gate nobody tests is a gate that quietly stops gating: this file // exists because the toolchain floor was raised in three documents and one echo, while the check // itself went on accepting the version it was raised against (re-check of the P5 dofix, FP5-9). package gates import ( "os" "os/exec" "path/filepath" "regexp" "strings" "testing" ) // zoneRoot is where the Makefile and go.mod live, two levels up from this package. const zoneRoot = "../.." // The gate is exercised with versions THIS HOST does not have. That is the whole point of asserting // on the comparison rather than on a run: a gate tested only against the toolchain that happens to be // installed proves nothing about the one it is meant to refuse. func TestTheToolchainGateComparesVersionsRatherThanMatchingThem(t *testing.T) { min := minVersion(t) if min != "1.26.6" { // Not a failure — the floor is allowed to move — but the table below is written against it. t.Logf("GO_MIN_VERSION is %s; the cases below assume the 1.26.x floor", min) } for _, c := range []struct { version string accept bool }{ {"go1.26.6", true}, // the floor itself {"go1.26.7", true}, // a later patch {"go1.26.10", true}, // double digits: the regex this replaced ordered 1.26.10 BELOW 1.26.9 {"go1.27.0", true}, // a later minor {"go2.0.0", true}, {"go1.26.5", false}, // the release the floor was raised against — the case that used to pass {"go1.26.4", false}, {"go1.25.9", false}, {"go1.9.9", false}, // one-digit minor must not sort above 1.26 {"go1.26.6rc1", false}, // a prerelease does not carry the fixes its number promises {"devel go1.27-abcdef", false}, } { err := makeTarget(t, "version-check", "GO_VERSION="+c.version) if c.accept && err != nil { t.Errorf("the gate refused %s, which is at or above the floor %s: %v", c.version, min, err) } if !c.accept && err == nil { t.Errorf("the gate ACCEPTED %s, below the floor %s", c.version, min) } } } // The other half of the same floor: the Makefile is a convenience, go.mod is what every build reads — // including one that never calls make. The two must name the same version, or a build that skips the // battery gets a toolchain the battery would have refused. func TestGoModPinsTheSameToolchainTheBatteryDemands(t *testing.T) { src, err := os.ReadFile(filepath.Join(zoneRoot, "go.mod")) if err != nil { t.Fatal(err) } m := regexp.MustCompile(`(?m)^toolchain go(\S+)$`).FindSubmatch(src) if m == nil { t.Fatal("go.mod carries no toolchain directive: the floor then lives only in the Makefile, and a build that does not call make ignores it") } if got, want := string(m[1]), minVersion(t); got != want { t.Errorf("go.mod pins the toolchain at %s and the battery demands %s", got, want) } } func minVersion(t *testing.T) string { t.Helper() src, err := os.ReadFile(filepath.Join(zoneRoot, "Makefile")) if err != nil { t.Fatal(err) } m := regexp.MustCompile(`(?m)^GO_MIN_VERSION := (\S+)$`).FindSubmatch(src) if m == nil { t.Fatal("the Makefile no longer declares GO_MIN_VERSION") } return string(m[1]) } func makeTarget(t *testing.T, args ...string) error { t.Helper() if _, err := exec.LookPath("make"); err != nil { t.Skip("make is not on this host: the build gates cannot be exercised") } cmd := exec.CommandContext(t.Context(), "make", args...) cmd.Dir = zoneRoot out, err := cmd.CombinedOutput() if err != nil && !strings.Contains(string(out), "required") { t.Fatalf("make %v failed for a reason other than the gate: %s", args, out) } return err }