package validation import ( "context" "errors" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" ) func TestRunCompilesTargetsAfterTestAndVetInSelectionOrder(t *testing.T) { logPath := installGoShim(t, "", false) targets := []Target{{OS: "linux", Arch: "amd64"}, {OS: "linux", Arch: "arm64"}, {OS: "darwin", Arch: "arm64"}} err := Run(context.Background(), Request{ RepositoryDir: t.TempDir(), Settings: Settings{FullVet: true, CompileTargets: targets}, }) if err != nil { t.Fatalf("Run() error = %v", err) } var commands []string for _, command := range readGoShimLog(t, logPath) { if strings.HasPrefix(command, "test ") || strings.HasPrefix(command, "vet ") || strings.HasPrefix(command, "build ") { commands = append(commands, command) } } if len(commands) != len(targets)+2 || !strings.HasPrefix(commands[0], "test ") || !strings.HasPrefix(commands[1], "vet ") { t.Fatalf("validation commands = %q, want test, vet, then one build per target", commands) } for index, target := range targets { command := commands[index+2] if !strings.HasPrefix(command, "build ") { t.Fatalf("command %d = %q, want package build", index+2, command) } for _, argument := range []string{"build", "-mod=readonly", "-o " + os.DevNull, "./..."} { if !strings.Contains(" "+command+" ", " "+argument+" ") { t.Errorf("build command %q does not contain %q", command, argument) } } if got, want := strings.Fields(command)[0], "build"; got != want { t.Errorf("command = %q, want %q", got, want) } if target.OS == "" || target.Arch == "" { t.Fatalf("test target is incomplete: %#v", target) } } environments, err := os.ReadFile(os.Getenv("RELEASEKIT_GO_ENV_LOG")) if err != nil { t.Fatal(err) } got := strings.Split(strings.TrimSpace(string(environments)), "\n") if len(got) != len(targets) { t.Fatalf("static build environments = %q, want %d entries", got, len(targets)) } for index, target := range targets { if want := fmt.Sprintf("%s/%s|0", target.OS, target.Arch); got[index] != want { t.Errorf("static build environment %d = %q, want %q", index, got[index], want) } } } func TestRunAttributesTargetCompileFailureAndStops(t *testing.T) { logPath := installGoShim(t, "darwin/arm64", false) targets := []Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}, {OS: "windows", Arch: "amd64"}} err := Run(context.Background(), Request{ RepositoryDir: t.TempDir(), Settings: Settings{FullVet: true, CompileTargets: targets}, }) var failure *Failure if !errors.As(err, &failure) || failure.Check != PackageCompile { t.Fatalf("Run() error = %v, want a package-compile failure", err) } if len(failure.Findings) != 1 || failure.Findings[0].Target != "darwin/arm64" { t.Fatalf("package compilation findings = %#v, want target darwin/arm64", failure.Findings) } var exitError *exec.ExitError if !errors.As(err, &exitError) { t.Fatalf("Run() error = %v, want the Go command failure preserved", err) } var commands []string for _, command := range readGoShimLog(t, logPath) { if strings.HasPrefix(command, "test ") || strings.HasPrefix(command, "vet ") || strings.HasPrefix(command, "build ") { commands = append(commands, command) } } if len(commands) != 4 || !strings.HasPrefix(commands[0], "test ") || !strings.HasPrefix(commands[1], "vet ") || !strings.HasPrefix(commands[2], "build ") || !strings.HasPrefix(commands[3], "build ") { t.Fatalf("validation commands after target failure = %q, want test, vet, two builds", commands) } } func TestRunRejectsIncompleteCompileTargets(t *testing.T) { installGoShim(t, "", false) for _, target := range []Target{{OS: "linux"}, {Arch: "amd64"}, {OS: " ", Arch: "amd64"}, {OS: "linux/other", Arch: "amd64"}} { err := Run(context.Background(), Request{ RepositoryDir: t.TempDir(), Settings: Settings{CompileTargets: []Target{target}}, }) var failure *Failure if !errors.As(err, &failure) || failure.Check != PackageCompile { t.Errorf("Run(%#v) error = %v, want a package-compile validation failure", target, err) continue } if len(failure.Findings) != 1 || failure.Findings[0].Target != target.OS+"/"+target.Arch { t.Errorf("Run(%#v) findings = %#v, want the incomplete target", target, failure.Findings) } } } func TestRunCompilesSourceOnlyAndExecutablePackagesWithoutArtifacts(t *testing.T) { fixtures := []struct { name string files map[string]string }{ { name: "source library", files: map[string]string{ "library.go": "package fixture\n\nconst Value = 1\n", }, }, { name: "single main", files: map[string]string{ "library.go": "package fixture\n\nconst Value = 1\n", "cmd/app/main.go": "package main\n\nfunc main() {}\n", }, }, { name: "multiple mains with repeated basenames", files: map[string]string{ "library.go": "package fixture\n\nconst Value = 1\n", "cmd/one/tool/main.go": "package main\n\nfunc main() {}\n", "cmd/two/tool/main.go": "package main\n\nfunc main() {}\n", }, }, } for _, fixture := range fixtures { t.Run(fixture.name, func(t *testing.T) { module := writeCompileFixture(t, fixture.files) before := readCompileFixture(t, module, fixture.files) var targets []Target if fixture.name == "source library" { targets = []Target{{OS: "linux", Arch: "amd64"}, {OS: "linux", Arch: "arm64"}, {OS: "darwin", Arch: "arm64"}} } else { targets = []Target{{OS: "linux", Arch: "amd64"}} } if err := Run(context.Background(), Request{ RepositoryDir: module, Settings: Settings{CompileTargets: targets}, }); err != nil { t.Fatalf("Run() error = %v", err) } after := readCompileFixture(t, module, fixture.files) for path, contents := range before { if string(after[path]) != string(contents) { t.Errorf("candidate file %q changed during compilation", path) } } }) } } func writeCompileFixture(t *testing.T, files map[string]string) string { t.Helper() module := t.TempDir() if err := os.WriteFile(filepath.Join(module, "go.mod"), []byte("module example.com/compilefixture\n\ngo 1.25.0\n"), 0o600); err != nil { t.Fatal(err) } for path, contents := range files { filename := filepath.Join(module, filepath.FromSlash(path)) if err := os.MkdirAll(filepath.Dir(filename), 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filename, []byte(contents), 0o600); err != nil { t.Fatal(err) } } return module } func readCompileFixture(t *testing.T, module string, files map[string]string) map[string][]byte { t.Helper() contents := make(map[string][]byte, len(files)+1) for _, path := range append([]string{"go.mod"}, fixturePaths(files)...) { data, err := os.ReadFile(filepath.Join(module, filepath.FromSlash(path))) if err != nil { t.Fatal(err) } contents[path] = data } return contents } func fixturePaths(files map[string]string) []string { paths := make([]string, 0, len(files)) for path := range files { paths = append(paths, path) } return paths }