Files
ReleaseKit/internal/validation/race_test.go

189 lines
5.2 KiB
Go

package validation
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestRunUsesOneSuiteWithSelectedRaceSetting(t *testing.T) {
for _, test := range []struct {
name string
race bool
}{
{name: "default"},
{name: "race", race: 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},
})
if err != nil {
t.Fatalf("Run() error = %v", 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 {
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)
}
})
}
}
func TestRunReportsRacePrerequisiteFailureWithoutFallback(t *testing.T) {
logPath := installGoShim(t, true)
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 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, failRace 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")
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)
}
failBlock := ""
if failRace {
failBlock = `
for argument in "$@"; do
if [ "$argument" = "-race" ]; then
echo "race detector prerequisite unavailable" >&2
exit 2
fi
done
`
}
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)
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
}
}