Run validation in the selected module

This commit is contained in:
2026-10-10 02:43:23 +00:00
parent 02896b6e12
commit 8350cbf9c9
17 changed files with 577 additions and 55 deletions

View File

@@ -34,6 +34,7 @@ type Request struct {
Spec Spec
OutputDir string
Output io.Writer
ToolOwner *gotool.Owner
}
type Artifact struct {
@@ -118,7 +119,11 @@ func Build(ctx context.Context, request Request) (Result, error) {
return result, fmt.Errorf("create build output directory %q: %w", result.OutputDir, err)
}
goTool, err := gotool.New(ctx, repositoryDir)
owner := request.ToolOwner
if owner == nil {
owner = gotool.NewOwner(repositoryDir)
}
goTool, err := owner.Acquire(ctx)
if err != nil {
return result, fmt.Errorf("prepare Go toolchain: %w", err)
}

View File

@@ -7,16 +7,17 @@ import (
"os"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/tempdir"
)
func CandidateCallback(spec *Spec, output io.Writer) func(context.Context, candidate.Candidate) error {
func CandidateCallback(spec *Spec, output io.Writer) func(context.Context, candidate.Candidate, *gotool.Owner) error {
if spec == nil || len(spec.Targets) == 0 {
return nil
}
selected := *spec
selected.Targets = append([]Target(nil), spec.Targets...)
return func(ctx context.Context, captured candidate.Candidate) error {
return func(ctx context.Context, captured candidate.Candidate, owner *gotool.Owner) error {
temporaryDir, err := tempdir.Outside(captured.RepositoryDir, "releasekit-candidate-build-")
if err != nil {
return fmt.Errorf("create temporary candidate build output: %w", err)
@@ -28,6 +29,7 @@ func CandidateCallback(spec *Spec, output io.Writer) func(context.Context, candi
Spec: selected,
OutputDir: temporaryDir,
Output: output,
ToolOwner: owner,
})
if err != nil {
return fmt.Errorf("build candidate: %w", err)

View File

@@ -12,6 +12,7 @@ import (
"strings"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/process"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
@@ -37,8 +38,9 @@ type Request struct {
Version string
Validation validation.Settings
AdditionalChecks func(context.Context, Candidate) error
AfterValidate func(context.Context, Candidate) error
AfterValidate func(context.Context, Candidate, *gotool.Owner) error
Output io.Writer
ToolOwner *gotool.Owner
}
func Check(ctx context.Context, request Request) (Candidate, error) {
@@ -46,12 +48,18 @@ func Check(ctx context.Context, request Request) (Candidate, error) {
if err != nil {
return captured, err
}
request.ToolOwner = gotool.NewOwner(captured.ModuleDir)
if err := Validate(ctx, request, captured); err != nil {
return captured, err
}
if request.AfterValidate != nil {
if err := request.AfterValidate(ctx, captured); err != nil {
return captured, fmt.Errorf("after candidate validation: %w", err)
callbackErr := request.AfterValidate(ctx, captured, request.ToolOwner)
refreshErr := request.ToolOwner.Refresh(ctx)
if callbackErr != nil {
return captured, fmt.Errorf("after candidate validation: %w", callbackErr)
}
if refreshErr != nil {
return captured, fmt.Errorf("refresh Go settings after candidate callback: %w", refreshErr)
}
}
if err := Recheck(ctx, captured, ""); err != nil {
@@ -120,18 +128,28 @@ func Capture(ctx context.Context, request Request) (Candidate, error) {
// Validate runs the selected offline Go suite, followed by AdditionalChecks
// when supplied, for the captured candidate.
func Validate(ctx context.Context, request Request, captured Candidate) error {
owner := request.ToolOwner
if owner == nil {
owner = gotool.NewOwner(captured.ModuleDir)
}
if err := validation.Run(ctx, validation.Request{
RepositoryDir: captured.RepositoryDir,
ModuleDir: captured.ModuleDir,
Commit: captured.Commit,
Output: request.Output,
Settings: request.Validation,
ToolOwner: owner,
}); err != nil {
return fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
}
if request.AdditionalChecks != nil {
if err := request.AdditionalChecks(ctx, captured); err != nil {
return fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
callbackErr := request.AdditionalChecks(ctx, captured)
refreshErr := owner.Refresh(ctx)
if callbackErr != nil {
return fmt.Errorf("validate candidate %s: %w", captured.Commit, callbackErr)
}
if refreshErr != nil {
return fmt.Errorf("refresh Go settings after additional checks: %w", refreshErr)
}
}
return nil

View File

@@ -3,13 +3,17 @@ package candidate
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
)
@@ -159,7 +163,7 @@ func TestCheckStopsBeforeProductAndBuildCallbacksWhenGoFormatFails(t *testing.T)
productCalls++
return nil
},
AfterValidate: func(context.Context, Candidate) error {
AfterValidate: func(context.Context, Candidate, *gotool.Owner) error {
buildCalls++
return nil
},
@@ -191,7 +195,7 @@ func TestCheckStopsBeforeProductAndBuildCallbacksWhenMarkdownBoundaryFails(t *te
productCalls++
return nil
},
AfterValidate: func(context.Context, Candidate) error {
AfterValidate: func(context.Context, Candidate, *gotool.Owner) error {
buildCalls++
return nil
},
@@ -265,6 +269,125 @@ func TestCheckRunsDefaultOfflineGoValidation(t *testing.T) {
}
}
func TestCheckRefreshesGoSettingsAfterAdditionalChecks(t *testing.T) {
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
t.Setenv("GOENV", "off")
t.Setenv("GOMODCACHE", filepath.Join(t.TempDir(), "initial-module-cache"))
selectedCache := filepath.Join(t.TempDir(), "callback-module-cache")
var observed string
_, err := Check(context.Background(), Request{
RepositoryDir: repository,
Product: "Example Product",
Version: testVersion,
AdditionalChecks: func(context.Context, Candidate) error {
t.Setenv("GOMODCACHE", selectedCache)
return nil
},
AfterValidate: func(ctx context.Context, captured Candidate, owner *gotool.Owner) error {
tool, err := owner.Acquire(ctx)
if err != nil {
return err
}
output, err := tool.Output(ctx, captured.ModuleDir, gotool.Native, gotool.Target{}, nil, "env", "GOMODCACHE")
if err != nil {
return err
}
observed = strings.TrimSpace(string(output))
return nil
},
})
if err != nil {
t.Fatal(err)
}
if observed != selectedCache {
t.Fatalf("Go settings after additional checks = %q, want %q", observed, selectedCache)
}
}
func TestCheckSharesGoDiscoveryWithAfterValidateCallback(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("the controlled Go command fixture uses a POSIX shell")
}
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
logPath := installCandidateGoShim(t)
t.Setenv("GOENV", "off")
_, err := Check(context.Background(), Request{
RepositoryDir: repository,
Product: "Example Product",
Version: testVersion,
AfterValidate: func(ctx context.Context, captured Candidate, owner *gotool.Owner) error {
tool, err := owner.Acquire(ctx)
if err != nil {
return err
}
_, err = tool.Output(ctx, captured.ModuleDir, gotool.Native, gotool.Target{}, nil, "version")
return err
},
})
if err != nil {
t.Fatal(err)
}
contents, err := os.ReadFile(logPath)
if err != nil {
t.Fatal(err)
}
commands := strings.Split(strings.TrimSpace(string(contents)), "\n")
var discoveries, suites, callbacks int
for _, command := range commands {
switch {
case strings.HasPrefix(command, "env "):
discoveries++
case strings.HasPrefix(command, "test "):
suites++
case command == "version":
callbacks++
}
}
if discoveries != 2 || suites != 1 || callbacks != 1 {
t.Fatalf("Go invocations = %q; discovery=%d suite=%d callback=%d, want 2, 1, 1", commands, discoveries, suites, callbacks)
}
}
func installCandidateGoShim(t *testing.T) string {
t.Helper()
directory := t.TempDir()
logPath := filepath.Join(directory, "go-commands")
settings := map[string]string{
"GOHOSTOS": runtime.GOOS, "GOHOSTARCH": runtime.GOARCH, "GOENV": "off",
"CC": "", "CXX": "", "CGO_CFLAGS": "", "CGO_CPPFLAGS": "", "CGO_CXXFLAGS": "",
"CGO_ENABLED": "1", "CGO_LDFLAGS": "", "GOCACHE": "", "GOCACHEPROG": "",
"GOMODCACHE": "", "GOPATH": "", "GOROOT": "", "GOTMPDIR": "", "PKG_CONFIG": "",
}
encoded, err := json.Marshal(settings)
if err != nil {
t.Fatal(err)
}
script := fmt.Sprintf(`#!/bin/sh
set -eu
printf '%%s\n' "$*" >> "$RELEASEKIT_GO_LOG"
if [ "${1-}" = "env" ]; then
printf '%%s\n' '%s'
exit 0
fi
if [ "${1-}" = "version" ]; then
echo 'go version fixture'
exit 0
fi
exit 0
`, encoded)
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 TestDefaultValidationDoesNotInheritGOFLAGSRunFilter(t *testing.T) {
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
writeTestFile(t, repository, "failure_test.go", "package candidate\nimport \"testing\"\nfunc TestExpectedFailure(t *testing.T) { t.Fatal(\"expected fixture failure\") }\n")

View File

@@ -34,8 +34,9 @@ type Target struct {
// Tool holds the host target and effective operational settings resolved from
// the process environment and Go's persistent environment file.
type Tool struct {
host Target
settings map[string]string
host Target
settings map[string]string
settingsFile string
}
var operationalSettings = []string{
@@ -53,10 +54,11 @@ var architectureFeatures = []string{
// configuration from being read.
func New(ctx context.Context, directory string) (*Tool, error) {
var host struct {
OS string `json:"GOHOSTOS"`
Arch string `json:"GOHOSTARCH"`
OS string `json:"GOHOSTOS"`
Arch string `json:"GOHOSTARCH"`
SettingsFile string `json:"GOENV"`
}
output, err := discover(ctx, directory, discoveryEnvironment(), "go", "env", "-json", "GOHOSTOS", "GOHOSTARCH")
output, err := discover(ctx, directory, discoveryEnvironment(), "go", "env", "-json", "GOHOSTOS", "GOHOSTARCH", "GOENV")
if err != nil {
return nil, fmt.Errorf("identify Go host: %w", err)
}
@@ -66,7 +68,7 @@ func New(ctx context.Context, directory string) (*Tool, error) {
if host.OS == "" || host.Arch == "" {
return nil, fmt.Errorf("Go returned an incomplete host target: %s", strings.TrimSpace(string(output)))
}
tool := &Tool{host: Target{OS: host.OS, Arch: host.Arch}}
tool := &Tool{host: Target{OS: host.OS, Arch: host.Arch}, settingsFile: host.SettingsFile}
environment := discoveryEnvironment()
environment["GOOS"] = tool.host.OS

View File

@@ -301,6 +301,72 @@ func TestPersistentSettingsPreserveCachesAndNativeCTools(t *testing.T) {
}
}
func TestOwnerReusesSettingsUntilInheritedEnvironmentChanges(t *testing.T) {
clearGoSettings(t)
t.Setenv("GOENV", "off")
owner := NewOwner(t.TempDir())
first, err := owner.Acquire(context.Background())
if err != nil {
t.Fatal(err)
}
second, err := owner.Acquire(context.Background())
if err != nil {
t.Fatal(err)
}
if first != second {
t.Fatal("unchanged Go inputs caused settings discovery to be repeated")
}
cache := filepath.Join(t.TempDir(), "changed-cache")
t.Setenv("GOCACHE", cache)
if err := owner.Refresh(context.Background()); err != nil {
t.Fatal(err)
}
third, err := owner.Acquire(context.Background())
if err != nil {
t.Fatal(err)
}
if third == first || third.settings["GOCACHE"] != cache {
t.Fatalf("changed inherited environment kept stale settings: tool=%p GOCACHE=%q", third, third.settings["GOCACHE"])
}
}
func TestOwnerRefreshesWhenPersistentSettingsChange(t *testing.T) {
clearGoSettings(t)
firstCache := filepath.Join(t.TempDir(), "first-cache")
secondCache := filepath.Join(t.TempDir(), "second-cache")
settingsFile := filepath.Join(t.TempDir(), "go-env")
writeSettings := func(cache string) {
t.Helper()
if err := os.WriteFile(settingsFile, []byte("GOCACHE="+cache+"\n"), 0o600); err != nil {
t.Fatal(err)
}
}
writeSettings(firstCache)
t.Setenv("GOENV", settingsFile)
owner := NewOwner(t.TempDir())
first, err := owner.Acquire(context.Background())
if err != nil {
t.Fatal(err)
}
if first.settings["GOCACHE"] != firstCache {
t.Fatalf("initial GOCACHE = %q, want %q", first.settings["GOCACHE"], firstCache)
}
writeSettings(secondCache)
if err := owner.Refresh(context.Background()); err != nil {
t.Fatal(err)
}
second, err := owner.Acquire(context.Background())
if err != nil {
t.Fatal(err)
}
if second == first || second.settings["GOCACHE"] != secondCache {
t.Fatalf("changed persistent settings were not rediscovered: tool=%p GOCACHE=%q", second, second.settings["GOCACHE"])
}
}
func readSettings(t *testing.T, tool *Tool, mode Mode, target Target, names ...string) map[string]string {
t.Helper()
args := append([]string{"env", "-json"}, names...)

136
internal/gotool/owner.go Normal file
View File

@@ -0,0 +1,136 @@
package gotool
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
// Owner lazily discovers and reuses one Go tool while its inherited
// environment and persistent settings file remain unchanged.
type Owner struct {
directory string
tool *Tool
env map[string]string
file string
contents []byte
fileFound bool
}
// NewOwner creates an operation-local Go tool owner without running Go.
func NewOwner(directory string) *Owner {
return &Owner{directory: directory}
}
// Acquire returns the operation's Go tool, rediscovering settings when their
// process or persistent inputs have changed.
func (owner *Owner) Acquire(ctx context.Context) (*Tool, error) {
if owner.tool != nil {
changed, err := owner.inputsChanged()
if err == nil && !changed {
return owner.tool, nil
}
}
if err := owner.discover(ctx); err != nil {
return nil, err
}
return owner.tool, nil
}
// Refresh checks the owner's inputs after a consumer callback and rediscovers
// settings when the callback changed the environment or persistent file.
func (owner *Owner) Refresh(ctx context.Context) error {
if owner.tool == nil {
return nil
}
changed, err := owner.inputsChanged()
if err == nil && !changed {
return nil
}
return owner.discover(ctx)
}
func (owner *Owner) discover(ctx context.Context) error {
environment := inheritedEnvironment()
tool, err := New(ctx, owner.directory)
if err != nil {
return err
}
settingsFile, err := resolveSettingsFile(owner.directory, tool.settingsFile)
if err != nil {
return err
}
contents, found, err := readSettingsFile(settingsFile)
if err != nil {
return fmt.Errorf("read Go settings file %q: %w", settingsFile, err)
}
owner.tool = tool
owner.env = environment
owner.file = settingsFile
owner.contents = contents
owner.fileFound = found
return nil
}
func (owner *Owner) inputsChanged() (bool, error) {
if !sameEnvironment(owner.env, inheritedEnvironment()) {
return true, nil
}
contents, found, err := readSettingsFile(owner.file)
if err != nil {
return true, err
}
return found != owner.fileFound || !bytes.Equal(contents, owner.contents), nil
}
func inheritedEnvironment() map[string]string {
values := make(map[string]string)
for _, entry := range os.Environ() {
key, value, ok := strings.Cut(entry, "=")
if ok {
values[key] = value
}
}
return values
}
func sameEnvironment(left, right map[string]string) bool {
if len(left) != len(right) {
return false
}
for key, value := range left {
other, ok := right[key]
if !ok || other != value {
return false
}
}
return true
}
func resolveSettingsFile(directory, filename string) (string, error) {
if filename == "" || filename == "off" {
return "", nil
}
if !filepath.IsAbs(filename) {
filename = filepath.Join(directory, filename)
}
return filepath.Abs(filename)
}
func readSettingsFile(filename string) ([]byte, bool, error) {
if filename == "" {
return nil, false, nil
}
contents, err := os.ReadFile(filename)
if errors.Is(err, os.ErrNotExist) {
return nil, false, nil
}
if err != nil {
return nil, false, err
}
return contents, true, nil
}

View File

@@ -10,6 +10,7 @@ import (
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
)
@@ -49,7 +50,7 @@ type Request struct {
ExpectedPushURL string
Validation validation.Settings
AdditionalChecks func(context.Context, candidate.Candidate) error
AfterValidate func(context.Context, candidate.Candidate) error
AfterValidate func(context.Context, candidate.Candidate, *gotool.Owner) error
Output io.Writer
}
@@ -120,12 +121,18 @@ func Publish(ctx context.Context, request Request) (Result, error) {
}
result.Phase = Validation
checks.ToolOwner = gotool.NewOwner(captured.ModuleDir)
if err := candidate.Validate(ctx, checks, captured); err != nil {
return result, err
}
if request.AfterValidate != nil {
if err := request.AfterValidate(ctx, captured); err != nil {
return result, fmt.Errorf("after candidate validation: %w", err)
callbackErr := request.AfterValidate(ctx, captured, checks.ToolOwner)
refreshErr := checks.ToolOwner.Refresh(ctx)
if callbackErr != nil {
return result, fmt.Errorf("after candidate validation: %w", callbackErr)
}
if refreshErr != nil {
return result, fmt.Errorf("refresh Go settings after candidate callback: %w", refreshErr)
}
}
if err := candidate.Recheck(ctx, captured, ""); err != nil {

View File

@@ -19,25 +19,38 @@ func TestRunUsesOneSuiteWithGoValidationSettings(t *testing.T) {
name string
race bool
fullVet bool
compile []Target
}{
{name: "default"},
{name: "race", race: true},
{name: "full vet", fullVet: true},
{name: "race and full vet", race: true, fullVet: true},
{name: "target compilation", compile: []Target{{OS: "linux", Arch: "amd64"}}},
} {
t.Run(test.name, func(t *testing.T) {
logPath := installGoShim(t, "", false)
repositoryDir := t.TempDir()
moduleDir := filepath.Join(repositoryDir, "nested module")
if err := os.MkdirAll(moduleDir, 0o700); err != nil {
t.Fatal(err)
}
err := Run(context.Background(), Request{
RepositoryDir: t.TempDir(),
Settings: Settings{Race: test.race, FullVet: test.fullVet},
RepositoryDir: repositoryDir,
ModuleDir: moduleDir,
Settings: Settings{Race: test.race, FullVet: test.fullVet, CompileTargets: test.compile},
})
if err != nil {
t.Fatalf("Run() error = %v", err)
}
commands := readGoShimLog(t, logPath)
var tests, vets []string
var testPosition, vetPosition int
for _, directory := range readGoShimDirectories(t, os.Getenv("RELEASEKIT_GO_DIR_LOG")) {
if directory != moduleDir {
t.Errorf("Go command directory = %q, want selected module %q", directory, moduleDir)
}
}
var tests, vets, builds []string
var testPosition, vetPosition, buildPosition int
for i, command := range commands {
if strings.HasPrefix(command, "test ") {
tests = append(tests, command)
@@ -47,6 +60,10 @@ func TestRunUsesOneSuiteWithGoValidationSettings(t *testing.T) {
vets = append(vets, command)
vetPosition = i
}
if strings.HasPrefix(command, "build ") {
builds = append(builds, command)
buildPosition = i
}
}
if len(tests) != 1 {
t.Fatalf("Go test invocations = %q, want exactly one", tests)
@@ -81,6 +98,27 @@ func TestRunUsesOneSuiteWithGoValidationSettings(t *testing.T) {
t.Errorf("Go vet command %q did not follow the test suite %q", vets[0], tests[0])
}
}
wantBuilds := 0
if len(test.compile) != 0 {
wantBuilds = len(test.compile)
}
if len(builds) != wantBuilds {
t.Fatalf("Go build invocations = %q, want %d", builds, wantBuilds)
}
if wantBuilds != 0 {
buildCommand := " " + builds[0] + " "
for _, argument := range []string{"build", "-mod=readonly", "-o", os.DevNull, "./..."} {
if !strings.Contains(buildCommand, " "+argument+" ") {
t.Errorf("Go compile command %q does not contain %q", builds[0], argument)
}
}
if buildPosition <= testPosition {
t.Errorf("Go compile command %q did not follow the test suite %q", builds[0], tests[0])
}
if environments := readGoShimLog(t, os.Getenv("RELEASEKIT_GO_ENV_LOG")); len(environments) != 1 || environments[0] != "linux/amd64|0" {
t.Errorf("Go compile baseline = %q, want linux/amd64|0", environments)
}
}
})
}
}
@@ -111,6 +149,49 @@ func TestRunReportsRacePrerequisiteFailureWithoutFallback(t *testing.T) {
}
}
func TestRunValidatesOnlyTheSelectedModule(t *testing.T) {
t.Setenv("GOENV", "off")
repositoryDir := t.TempDir()
nestedDir := filepath.Join(repositoryDir, "nested")
siblingDir := filepath.Join(repositoryDir, "sibling")
for _, directory := range []string{nestedDir, siblingDir} {
if err := os.MkdirAll(directory, 0o700); err != nil {
t.Fatal(err)
}
}
writeModule := func(directory, name, testSource string) {
t.Helper()
files := map[string]string{
"go.mod": "module " + name + "\n\ngo 1.25.0\n",
"module_test.go": testSource,
}
for filename, contents := range files {
if err := os.WriteFile(filepath.Join(directory, filename), []byte(contents), 0o600); err != nil {
t.Fatal(err)
}
}
}
passingTest := "package fixture\nimport \"testing\"\nfunc TestSelected(t *testing.T) {}\n"
failingTest := "package fixture\nimport \"testing\"\nfunc TestSibling(t *testing.T) { t.Fatal(\"sibling suite ran\") }\n"
writeModule(repositoryDir, "example.com/root", passingTest)
writeModule(nestedDir, "example.com/nested", passingTest)
writeModule(siblingDir, "example.com/sibling", failingTest)
if err := Run(context.Background(), Request{RepositoryDir: repositoryDir}); err != nil {
t.Fatalf("root module validation: %v", err)
}
if err := Run(context.Background(), Request{RepositoryDir: repositoryDir, ModuleDir: nestedDir}); err != nil {
t.Fatalf("nested module validation ran a sibling suite: %v", err)
}
writeModule(nestedDir, "example.com/nested", failingTest)
err := Run(context.Background(), Request{RepositoryDir: repositoryDir, ModuleDir: nestedDir})
var failure *Failure
if !errors.As(err, &failure) || failure.Check != GoTest {
t.Fatalf("failing nested module result = %v, want Go test failure", err)
}
}
func TestRunStopsAfterGoTestFailure(t *testing.T) {
logPath := installGoShim(t, "test", false)
err := Run(context.Background(), Request{
@@ -214,6 +295,7 @@ func installGoShim(t *testing.T, failure string, delayVet bool) string {
directory := t.TempDir()
logPath := filepath.Join(directory, "go-commands")
envLogPath := filepath.Join(directory, "go-environment")
directoryLogPath := filepath.Join(directory, "go-directories")
settings := map[string]string{
"GOHOSTOS": runtime.GOOS,
"GOHOSTARCH": runtime.GOARCH,
@@ -263,6 +345,7 @@ fi
script := fmt.Sprintf(`#!/bin/sh
set -eu
printf '%%s\n' "$*" >> "$RELEASEKIT_GO_LOG"
pwd -P >> "$RELEASEKIT_GO_DIR_LOG"
if [ "${1-}" = "env" ]; then
printf '%%s\n' '%s'
exit 0
@@ -281,6 +364,7 @@ exit 0
t.Setenv("PATH", path)
t.Setenv("RELEASEKIT_GO_LOG", logPath)
t.Setenv("RELEASEKIT_GO_ENV_LOG", envLogPath)
t.Setenv("RELEASEKIT_GO_DIR_LOG", directoryLogPath)
return logPath
}
@@ -293,6 +377,15 @@ func readGoShimLog(t *testing.T, path string) []string {
return strings.Split(strings.TrimSpace(string(contents)), "\n")
}
func readGoShimDirectories(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":

View File

@@ -57,6 +57,7 @@ type Request struct {
Commit string
Output io.Writer
Settings Settings
ToolOwner *gotool.Owner
}
// ReadCandidateTree loads the committed-file inventory when a selected check
@@ -87,7 +88,15 @@ func Run(ctx context.Context, request Request) (result error) {
}
}
}
tool, err := gotool.New(ctx, request.RepositoryDir)
goDirectory := request.ModuleDir
if goDirectory == "" {
goDirectory = request.RepositoryDir
}
owner := request.ToolOwner
if owner == nil {
owner = gotool.NewOwner(goDirectory)
}
tool, err := owner.Acquire(ctx)
if err != nil {
return &Failure{Check: GoTest, Cause: err}
}
@@ -99,19 +108,19 @@ func Run(ctx context.Context, request Request) (result error) {
args = append(args, "-vet=off")
}
args = append(args, "./...")
err = tool.Run(ctx, request.RepositoryDir, gotool.Native, gotool.Target{}, request.Output, args...)
err = tool.Run(ctx, goDirectory, gotool.Native, gotool.Target{}, request.Output, args...)
if err != nil {
return &Failure{Check: GoTest, Cause: err}
}
if request.Settings.FullVet {
err = tool.Run(ctx, request.RepositoryDir, gotool.Native, gotool.Target{}, request.Output, "vet", "-mod=readonly", "./...")
err = tool.Run(ctx, goDirectory, gotool.Native, gotool.Target{}, request.Output, "vet", "-mod=readonly", "./...")
if err != nil {
return &Failure{Check: GoVet, Cause: err}
}
}
for _, target := range request.Settings.CompileTargets {
selected := gotool.Target{OS: target.OS, Arch: target.Arch}
err = tool.Run(ctx, request.RepositoryDir, gotool.Static, selected, request.Output,
err = tool.Run(ctx, goDirectory, gotool.Static, selected, request.Output,
"build", "-mod=readonly", "-o", os.DevNull, "./...")
if err != nil {
return &Failure{