Files
ReleaseKit/internal/validation/compile_test.go

205 lines
6.9 KiB
Go

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
}