package validation import ( "context" "encoding/json" "errors" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" "time" ) func TestRunUsesOneSuiteWithGoValidationSettings(t *testing.T) { for _, test := range []struct { name string race bool fullVet bool }{ {name: "default"}, {name: "race", race: true}, {name: "full vet", fullVet: true}, {name: "race and full vet", race: true, fullVet: true}, } { t.Run(test.name, func(t *testing.T) { logPath := installGoShim(t, "", false) err := Run(context.Background(), Request{ RepositoryDir: t.TempDir(), Settings: Settings{Race: test.race, FullVet: test.fullVet}, }) if err != nil { t.Fatalf("Run() error = %v", err) } commands := readGoShimLog(t, logPath) var tests, vets []string var testPosition, vetPosition int for i, command := range commands { if strings.HasPrefix(command, "test ") { tests = append(tests, command) testPosition = i } if strings.HasPrefix(command, "vet ") { vets = append(vets, command) vetPosition = i } } if len(tests) != 1 { t.Fatalf("Go test invocations = %q, want exactly one", tests) } command := " " + tests[0] + " " for _, argument := range []string{"test", "-mod=readonly", "-count=1", "./..."} { if !strings.Contains(command, " "+argument+" ") { t.Errorf("Go test command %q does not contain %q", tests[0], argument) } } if got := strings.Contains(command, " -race "); got != test.race { t.Errorf("Go test command %q has race=%t, want %t", tests[0], got, test.race) } if got := strings.Contains(command, " -vet=off "); got != test.fullVet { t.Errorf("Go test command %q disables test-time vet=%t, want %t", tests[0], got, test.fullVet) } wantVets := 0 if test.fullVet { wantVets = 1 } if len(vets) != wantVets { t.Fatalf("Go vet invocations = %q, want %d", vets, wantVets) } if test.fullVet { vetCommand := " " + vets[0] + " " for _, argument := range []string{"vet", "-mod=readonly", "./..."} { if !strings.Contains(vetCommand, " "+argument+" ") { t.Errorf("Go vet command %q does not contain %q", vets[0], argument) } } if vetPosition <= testPosition { t.Errorf("Go vet command %q did not follow the test suite %q", vets[0], tests[0]) } } }) } } func TestRunReportsRacePrerequisiteFailureWithoutFallback(t *testing.T) { logPath := installGoShim(t, "race", false) err := Run(context.Background(), Request{ RepositoryDir: t.TempDir(), Settings: Settings{Race: true}, }) var failure *Failure if !errors.As(err, &failure) || failure.Check != GoTest { t.Fatalf("Run() error = %v, want a go-test validation failure", err) } var exitError *exec.ExitError if !errors.As(err, &exitError) { t.Fatalf("Run() error = %v, want the command failure preserved", err) } commands := readGoShimLog(t, logPath) var tests []string for _, command := range commands { if strings.HasPrefix(command, "test ") { tests = append(tests, command) } } if len(tests) != 1 || !strings.Contains(" "+tests[0]+" ", " -race ") { t.Fatalf("Go test invocations = %q, want one race-enabled invocation and no fallback", tests) } } func TestRunStopsAfterGoTestFailure(t *testing.T) { logPath := installGoShim(t, "test", false) err := Run(context.Background(), Request{ RepositoryDir: t.TempDir(), Settings: Settings{FullVet: true}, }) var failure *Failure if !errors.As(err, &failure) || failure.Check != GoTest { t.Fatalf("Run() error = %v, want a go-test validation failure", err) } for _, command := range readGoShimLog(t, logPath) { if strings.HasPrefix(command, "vet ") { t.Fatalf("Go vet ran after Go test failed: %q", command) } } } func TestRunAttributesFullVetFailure(t *testing.T) { logPath := installGoShim(t, "vet", false) err := Run(context.Background(), Request{ RepositoryDir: t.TempDir(), Settings: Settings{FullVet: true}, }) var failure *Failure if !errors.As(err, &failure) || failure.Check != GoVet { t.Fatalf("Run() error = %v, want a go-vet validation failure", err) } var exitError *exec.ExitError if !errors.As(err, &exitError) { t.Fatalf("Run() error = %v, want the command failure preserved", err) } commands := readGoShimLog(t, logPath) var validationCommands []string for _, command := range commands { if strings.HasPrefix(command, "test ") || strings.HasPrefix(command, "vet ") { validationCommands = append(validationCommands, command) } } if len(validationCommands) != 2 || !strings.HasPrefix(validationCommands[0], "test ") || !strings.HasPrefix(validationCommands[1], "vet ") { t.Fatalf("validation command sequence = %q, want test followed by vet", validationCommands) } } func TestRunPreservesFullVetCancellation(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("the controlled Go command fixture uses a POSIX shell") } installGoShim(t, "", true) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() err := Run(ctx, Request{ RepositoryDir: t.TempDir(), Settings: Settings{FullVet: true}, }) var failure *Failure if !errors.As(err, &failure) || failure.Check != GoVet { t.Fatalf("Run() error = %v, want a go-vet validation failure", err) } if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("Run() error = %v, want deadline cause preserved", err) } } func TestRunExecutesRaceEnabledSuiteOnHost(t *testing.T) { if !raceSupported(runtime.GOOS, runtime.GOARCH) { t.Skipf("Go race detector is not supported on %s/%s", runtime.GOOS, runtime.GOARCH) } cgo, err := exec.Command("go", "env", "CGO_ENABLED").Output() if err != nil || strings.TrimSpace(string(cgo)) != "1" { t.Skip("Go race detector requires CGO_ENABLED=1") } cc, err := exec.Command("go", "env", "CC").Output() if err != nil || len(strings.Fields(string(cc))) == 0 { t.Skip("Go race detector requires a C compiler") } if _, err := exec.LookPath(strings.Fields(string(cc))[0]); err != nil { t.Skip("Go race detector C compiler is unavailable") } module := t.TempDir() if err := os.WriteFile(filepath.Join(module, "go.mod"), []byte("module example.com/racefixture\n\ngo 1.25.0\n"), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(module, "race_test.go"), []byte(`package racefixture import "testing" func TestRaceFixture(t *testing.T) {} `), 0o600); err != nil { t.Fatal(err) } if err := Run(context.Background(), Request{RepositoryDir: module, Settings: Settings{Race: true}}); err != nil { t.Fatalf("race-enabled Go suite failed: %v", err) } } func installGoShim(t *testing.T, failure string, delayVet bool) string { t.Helper() if runtime.GOOS == "windows" { t.Skip("the controlled Go command fixture uses a POSIX shell") } directory := t.TempDir() logPath := filepath.Join(directory, "go-commands") envLogPath := filepath.Join(directory, "go-environment") settings := map[string]string{ "GOHOSTOS": runtime.GOOS, "GOHOSTARCH": runtime.GOARCH, "CC": "", "CXX": "", "CGO_CFLAGS": "", "CGO_CPPFLAGS": "", "CGO_CXXFLAGS": "", "CGO_ENABLED": "1", "CGO_LDFLAGS": "", "GOCACHE": "", "GOCACHEPROG": "", "GOMODCACHE": "", "GOPATH": "", "GOROOT": "", "GOTMPDIR": "", "PKG_CONFIG": "", } encodedSettings, err := json.Marshal(settings) if err != nil { t.Fatal(err) } vetDelay := "" if delayVet { vetDelay = ` if [ "${1-}" = "vet" ]; then exec sleep 5 fi ` } failBlock := fmt.Sprintf(` if [ "${1-}" = "test" ]; then for argument in "$@"; do if [ "%s" = "race" ] && [ "$argument" = "-race" ]; then echo "race detector prerequisite unavailable" >&2 exit 2 fi done if [ "%s" = "test" ]; then echo "Go test failed" >&2 exit 2 fi fi if [ "${1-}" = "vet" ] && [ "%s" = "vet" ]; then echo "Go vet failed" >&2 exit 2 fi if [ "${1-}" = "build" ]; then printf '%%s/%%s|%%s\n' "${GOOS-}" "${GOARCH-}" "${CGO_ENABLED-}" >> "$RELEASEKIT_GO_ENV_LOG" if [ "%s" = "build" ] || [ "%s" = "${GOOS-}/${GOARCH-}" ]; then echo "Go build failed" >&2 exit 2 fi fi %s `, failure, failure, failure, failure, failure, vetDelay) script := fmt.Sprintf(`#!/bin/sh set -eu printf '%%s\n' "$*" >> "$RELEASEKIT_GO_LOG" if [ "${1-}" = "env" ]; then printf '%%s\n' '%s' exit 0 fi %s exit 0 `, encodedSettings, failBlock) shimPath := filepath.Join(directory, "go") if err := os.WriteFile(shimPath, []byte(script), 0o700); err != nil { t.Fatal(err) } path := directory if current := os.Getenv("PATH"); current != "" { path += string(os.PathListSeparator) + current } t.Setenv("PATH", path) t.Setenv("RELEASEKIT_GO_LOG", logPath) t.Setenv("RELEASEKIT_GO_ENV_LOG", envLogPath) return logPath } func readGoShimLog(t *testing.T, path string) []string { t.Helper() contents, err := os.ReadFile(path) if err != nil { t.Fatal(err) } return strings.Split(strings.TrimSpace(string(contents)), "\n") } func raceSupported(goos, goarch string) bool { switch goos { case "darwin": return goarch == "amd64" || goarch == "arm64" case "freebsd", "linux": return goarch == "amd64" || goarch == "arm64" || goarch == "ppc64le" case "windows": return goarch == "amd64" default: return false } }