diff --git a/version_test.go b/version_test.go index 1f434e682..3d983a19d 100644 --- a/version_test.go +++ b/version_test.go @@ -6,21 +6,16 @@ package tailscaleroot import ( "fmt" "os" - "regexp" + "os/exec" + "runtime" "strings" "testing" + + "golang.org/x/mod/modfile" ) func TestDockerfileVersion(t *testing.T) { - goMod, err := os.ReadFile("go.mod") - 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] + goVersion := mustGetGoModVersion(t, false) dockerFile, err := os.ReadFile("Dockerfile") if err != nil { @@ -31,3 +26,60 @@ func TestDockerfileVersion(t *testing.T) { 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 +}