Enable race testing in validation

This commit is contained in:
2026-10-07 22:10:34 +00:00
parent 89981abbe5
commit 30fe4fe206
20 changed files with 510 additions and 61 deletions

View File

@@ -112,14 +112,25 @@ type CheckCommand struct {
Env map[string]string `json:"env,omitempty"`
}
// Settings contains the product, remote, and check commands read from a
// releasekit.json profile.
// Settings contains the product, remote, shared validation selections, and
// check commands read from a releasekit.json profile.
type Settings struct {
Product string `json:"product"`
Remote string `json:"remote,omitempty"`
Checks []CheckCommand `json:"checks,omitempty"`
Build *BuildSettings `json:"build,omitempty"`
Delivery *DeliverySettings `json:"delivery,omitempty"`
Product string `json:"product"`
Remote string `json:"remote,omitempty"`
Checks []CheckCommand `json:"checks,omitempty"`
Validation *ValidationSettings `json:"validation,omitempty"`
Build *BuildSettings `json:"build,omitempty"`
Delivery *DeliverySettings `json:"delivery,omitempty"`
}
// ValidationSettings selects shared candidate checks in a release profile.
type ValidationSettings struct {
Go GoValidationSettings `json:"go,omitempty"`
}
// GoValidationSettings selects checks performed by the Go toolchain.
type GoValidationSettings struct {
Race bool `json:"race,omitempty"`
}
// DeliverySettings selects the hosted Gitea release destination.

View File

@@ -141,6 +141,22 @@ func TestLoadSettingsDefaultsRemoteAndConfigPath(t *testing.T) {
}
}
func TestLoadSettingsReadsRaceValidation(t *testing.T) {
repoDir := t.TempDir()
profilePath := filepath.Join(repoDir, "releasekit.json")
if err := os.WriteFile(profilePath, []byte(`{"product":"Example","validation":{"go":{"race":true}}}`), 0o600); err != nil {
t.Fatal(err)
}
settings, err := LoadSettings(repoDir, "")
if err != nil {
t.Fatal(err)
}
if settings.Validation == nil || !settings.Validation.Go.Race {
t.Fatalf("validation settings = %#v, want Go race enabled", settings.Validation)
}
}
func TestExampleProfileLoads(t *testing.T) {
repoDir, err := filepath.Abs(filepath.Join("..", "..", "..", ".."))
if err != nil {

View File

@@ -139,6 +139,7 @@ func candidateRequest(invocation cli.Invocation, settings cli.Settings, output i
Product: settings.Product,
Version: invocation.Version,
Remote: settings.Remote,
Validation: validationSpec(settings.Validation),
Build: configuredBuildSpec(settings.Build),
Output: output,
}
@@ -154,6 +155,15 @@ func candidateRequest(invocation cli.Invocation, settings cli.Settings, output i
return request
}
func validationSpec(settings *cli.ValidationSettings) releasekit.ValidationSpec {
if settings == nil {
return releasekit.ValidationSpec{}
}
return releasekit.ValidationSpec{
Go: releasekit.GoValidationSpec{Race: settings.Go.Race},
}
}
func candidateFailureText(err error) string {
return err.Error()
}

View File

@@ -49,6 +49,7 @@ func TestUnknownCommand(t *testing.T) {
}
func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
raceSelected := racePrerequisitesAvailable()
repository := newCLIRepository(t)
profilePath := filepath.Join(t.TempDir(), "profile with spaces.json")
orderPath := filepath.Join(t.TempDir(), "check-order")
@@ -73,6 +74,9 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
Remote: "origin",
Checks: []cli.CheckCommand{check("first"), check("second")},
}
if raceSelected {
settings.Validation = &cli.ValidationSettings{Go: cli.GoValidationSettings{Race: true}}
}
contents, err := json.Marshal(settings)
if err != nil {
t.Fatal(err)
@@ -82,6 +86,11 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
}
var stdout, stderr bytes.Buffer
raceMarker := ""
if raceSelected {
raceMarker = filepath.Join(t.TempDir(), "race-enabled")
t.Setenv("RELEASEKIT_RACE_MARKER", raceMarker)
}
args := []string{"check", "--repo", repository, "--config", profilePath, "v1.2.3"}
if status := run(context.Background(), args, &stdout, &stderr); status != 0 {
t.Fatalf("run() status = %d; stdout=%q stderr=%q", status, stdout.String(), stderr.String())
@@ -96,6 +105,23 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
if want := fmt.Sprintf("local candidate v1.2.3 at %s is valid\n", commit); stdout.String() != want {
t.Fatalf("stdout = %q, want %q", stdout.String(), want)
}
if raceSelected {
if _, err := os.Stat(raceMarker); err != nil {
t.Fatalf("race-enabled profile did not run: %v", err)
}
}
}
func TestCandidateRequestMapsRaceValidation(t *testing.T) {
request := candidateRequest(cli.Invocation{Repository: "repository", Version: "v1.2.3"}, cli.Settings{
Product: "Example Product",
Validation: &cli.ValidationSettings{
Go: cli.GoValidationSettings{Race: true},
},
}, nil)
if !request.Validation.Go.Race {
t.Fatalf("candidate request validation = %#v, want Go race enabled", request.Validation)
}
}
func TestCandidateCommandsPresentValidationDiagnosticsOnce(t *testing.T) {
@@ -165,6 +191,7 @@ func TestCandidateFailurePresentationRetainsCallbackContext(t *testing.T) {
}
func TestPublishCommandPublishesConfiguredCandidate(t *testing.T) {
raceSelected := racePrerequisitesAvailable()
repository := newCLIRepository(t)
writeRepositoryFile(t, repository, "docs/releases/v1.2.3.md", "# Example Product v1.2.3\n\n## Compatibility\n\nNo action required.\n")
writeRepositoryFile(t, repository, "scripts/check-release-note.sh", `#!/bin/sh
@@ -189,6 +216,9 @@ printf '%s %s\n' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG"
Env: map[string]string{"RELEASEKIT_COMPATIBILITY_LOG": checkLog},
}},
}
if raceSelected {
settings.Validation = &cli.ValidationSettings{Go: cli.GoValidationSettings{Race: true}}
}
contents, err := json.Marshal(settings)
if err != nil {
t.Fatal(err)
@@ -204,6 +234,11 @@ printf '%s %s\n' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG"
cliGitRun(t, repository, "push", "--set-upstream", "origin", "main")
var stdout, stderr bytes.Buffer
raceMarker := ""
if raceSelected {
raceMarker = filepath.Join(t.TempDir(), "race-enabled")
t.Setenv("RELEASEKIT_RACE_MARKER", raceMarker)
}
args := []string{"publish", "--repo", repository, "v1.2.3"}
if status := run(context.Background(), args, &stdout, &stderr); status != 0 {
t.Fatalf("run() status = %d; stdout=%q stderr=%q", status, stdout.String(), stderr.String())
@@ -226,6 +261,11 @@ printf '%s %s\n' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG"
if got, want := strings.Count(string(checkResult), "v1.2.3 "+commit), 1; got != want {
t.Fatalf("product compatibility check ran %d times, want %d", got, want)
}
if raceSelected {
if _, err := os.Stat(raceMarker); err != nil {
t.Fatalf("race-enabled profile did not run: %v", err)
}
}
}
func TestBuildCommandWritesValidatedReceiptForConfiguredArtifacts(t *testing.T) {
@@ -343,12 +383,52 @@ func newCLIRepository(t *testing.T) string {
}
write("go.mod", "module example.com/cli-candidate\n\ngo 1.25.0\n")
write("candidate_test.go", "package candidate\nimport \"testing\"\nfunc TestFixture(t *testing.T) {}\n")
write("race_candidate_test.go", `//go:build race
package candidate
import (
"os"
"testing"
)
func TestRaceBuildSelection(t *testing.T) {
marker := os.Getenv("RELEASEKIT_RACE_MARKER")
if marker == "" {
return
}
if err := os.WriteFile(marker, []byte("race enabled"), 0o600); err != nil {
t.Fatal(err)
}
}
`)
write("docs/releases/v1.2.3.md", "# Example Product v1.2.3\n\nRelease note.\n")
cliGitRun(t, repository, "add", "--all")
cliGitRun(t, repository, "commit", "-m", "candidate")
return repository
}
func racePrerequisitesAvailable() bool {
supported := runtime.GOOS == "darwin" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64") ||
(runtime.GOOS == "freebsd" || runtime.GOOS == "linux") && (runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" || runtime.GOARCH == "ppc64le") ||
runtime.GOOS == "windows" && runtime.GOARCH == "amd64"
if !supported {
return false
}
cgo, err := exec.Command("go", "env", "CGO_ENABLED").Output()
if err != nil || strings.TrimSpace(string(cgo)) != "1" {
return false
}
cc, err := exec.Command("go", "env", "CC").Output()
if err != nil || len(strings.Fields(string(cc))) == 0 {
return false
}
if _, err := exec.LookPath(strings.Fields(string(cc))[0]); err != nil {
return false
}
return true
}
func TestDeliverPageOnlyCreatesAndReusesRelease(t *testing.T) {
repository := newCLIRepository(t)
state := &deliveryServerState{}