.github/workflows: test that ./go/tool version matches go mod version

Tests that go mod version matches ./tool/go version.
Mismatched versions result in incosistent Go versions being used i.e.
in CI jobs as the version in go.mod is used to determine what Go version
Github actions pull in.

Updates #16283

Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
Irbe Krumina 2025-06-16 16:01:46 +01:00 committed by Brad Fitzpatrick
parent 077d52b22f
commit d7770d2b81

View File

@ -6,21 +6,16 @@ package tailscaleroot
import ( import (
"fmt" "fmt"
"os" "os"
"regexp" "os/exec"
"runtime"
"strings" "strings"
"testing" "testing"
"golang.org/x/mod/modfile"
) )
func TestDockerfileVersion(t *testing.T) { func TestDockerfileVersion(t *testing.T) {
goMod, err := os.ReadFile("go.mod") goVersion := mustGetGoModVersion(t, false)
if err != nil {
t.Fatal(err)
}
m := regexp.MustCompile(`(?m)^go (\d\.\d+)\r?($|\.)`).FindStringSubmatch(string(goMod))
if m == nil {
t.Fatalf("didn't find go version in go.mod")
}
goVersion := m[1]
dockerFile, err := os.ReadFile("Dockerfile") dockerFile, err := os.ReadFile("Dockerfile")
if err != nil { if err != nil {
@ -31,3 +26,60 @@ func TestDockerfileVersion(t *testing.T) {
t.Errorf("didn't find %q in Dockerfile", wantSub) t.Errorf("didn't find %q in Dockerfile", wantSub)
} }
} }
// TestGoVersion tests that the Go version specified in go.mod matches ./tool/go version.
func TestGoVersion(t *testing.T) {
// We could special-case ./tool/go path for Windows, but really there is no
// need to run it there.
if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows")
}
goModVersion := mustGetGoModVersion(t, true)
goToolCmd := exec.Command("./tool/go", "version")
goToolOutput, err := goToolCmd.Output()
if err != nil {
t.Fatalf("Failed to get ./tool/go version: %v", err)
}
// Version info will approximately look like 'go version go1.24.4 linux/amd64'.
parts := strings.Fields(string(goToolOutput))
if len(parts) < 4 {
t.Fatalf("Unexpected ./tool/go version output format: %s", goToolOutput)
}
goToolVersion := strings.TrimPrefix(parts[2], "go")
if goModVersion != goToolVersion {
t.Errorf("Go version in go.mod (%q) does not match the version of ./tool/go (%q).\nEnsure that the go.mod refers to the same Go version as ./go.toolchain.rev.",
goModVersion, goToolVersion)
}
}
func mustGetGoModVersion(t *testing.T, includePatchVersion bool) string {
t.Helper()
goModBytes, err := os.ReadFile("go.mod")
if err != nil {
t.Fatal(err)
}
modFile, err := modfile.Parse("go.mod", goModBytes, nil)
if err != nil {
t.Fatal(err)
}
if modFile.Go == nil {
t.Fatal("no Go version found in go.mod")
}
version := modFile.Go.Version
parts := strings.Split(version, ".")
if !includePatchVersion {
if len(parts) >= 2 {
version = parts[0] + "." + parts[1]
}
}
return version
}