Run validation in the selected module
This commit is contained in:
@@ -432,6 +432,21 @@ func TestBuildCancellationReturnsCompletedArtifacts(t *testing.T) {
|
|||||||
|
|
||||||
func TestCheckBuildsAfterValidationAndKeepsOutputOutsideWorktree(t *testing.T) {
|
func TestCheckBuildsAfterValidationAndKeepsOutputOutsideWorktree(t *testing.T) {
|
||||||
repository := newPublishFixture(t)
|
repository := newPublishFixture(t)
|
||||||
|
realGo, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
goShimDir := t.TempDir()
|
||||||
|
goLog := filepath.Join(goShimDir, "go-commands")
|
||||||
|
goShim := filepath.Join(goShimDir, "go")
|
||||||
|
shimSource := "#!/bin/sh\nset -eu\nprintf '%s\\n' \"$*\" >> \"$RELEASEKIT_REAL_GO_LOG\"\nexec \"$RELEASEKIT_REAL_GO\" \"$@\"\n"
|
||||||
|
if err := os.WriteFile(goShim, []byte(shimSource), 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Setenv("RELEASEKIT_REAL_GO", realGo)
|
||||||
|
t.Setenv("RELEASEKIT_REAL_GO_LOG", goLog)
|
||||||
|
t.Setenv("GOENV", "off")
|
||||||
|
t.Setenv("PATH", goShimDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||||
writeFile(t, repository.work, "guide.md", "# Guide\n[section](#guide)\n")
|
writeFile(t, repository.work, "guide.md", "# Guide\n[section](#guide)\n")
|
||||||
gitRun(t, repository.work, "add", "guide.md")
|
gitRun(t, repository.work, "add", "guide.md")
|
||||||
gitRun(t, repository.work, "commit", "-m", "add Markdown guide")
|
gitRun(t, repository.work, "commit", "-m", "add Markdown guide")
|
||||||
@@ -465,6 +480,26 @@ func main() { if len(os.Args) == 2 && os.Args[1] == "version" { fmt.Printf("fixt
|
|||||||
if validations != 1 || candidate.Commit != gitText(t, repository.work, "rev-parse", "HEAD") {
|
if validations != 1 || candidate.Commit != gitText(t, repository.work, "rev-parse", "HEAD") {
|
||||||
t.Fatalf("candidate = %#v, validation calls = %d", candidate, validations)
|
t.Fatalf("candidate = %#v, validation calls = %d", candidate, validations)
|
||||||
}
|
}
|
||||||
|
goCommands, err := os.ReadFile(goLog)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var discoveries, suites, builds, versions int
|
||||||
|
for _, command := range strings.Split(strings.TrimSpace(string(goCommands)), "\n") {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(command, "env "):
|
||||||
|
discoveries++
|
||||||
|
case strings.HasPrefix(command, "test "):
|
||||||
|
suites++
|
||||||
|
case strings.HasPrefix(command, "build "):
|
||||||
|
builds++
|
||||||
|
case command == "version":
|
||||||
|
versions++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if discoveries != 2 || suites != 1 || builds != 1 || versions != 1 {
|
||||||
|
t.Fatalf("Go commands = %q; discovery=%d suite=%d build=%d version=%d, want 2, 1, 1, 1", goCommands, discoveries, suites, builds, versions)
|
||||||
|
}
|
||||||
if got := gitText(t, repository.work, "status", "--porcelain"); got != "" {
|
if got := gitText(t, repository.work, "status", "--porcelain"); got != "" {
|
||||||
t.Fatalf("candidate build dirtied worktree: %s", got)
|
t.Fatalf("candidate build dirtied worktree: %s", got)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ version must be stable `vMAJOR.MINOR.PATCH`. The committed
|
|||||||
product name and exact version as complete title elements. No local tag with
|
product name and exact version as complete title elements. No local tag with
|
||||||
that version may already exist.
|
that version may already exist.
|
||||||
|
|
||||||
ReleaseKit runs one `go test -mod=readonly -count=1 ./...` suite with the
|
ReleaseKit runs one `go test -mod=readonly -count=1 ./...` suite from the
|
||||||
available compatible local Go toolchain. Set `Validation.Go.Race` to run that
|
selected module directory with the available compatible local Go toolchain.
|
||||||
|
Set `Validation.Go.Race` to run that
|
||||||
suite with `-race`; set `Validation.Go.FullVet` to run
|
suite with `-race`; set `Validation.Go.FullVet` to run
|
||||||
`go vet -mod=readonly ./...` after successful tests, with test-time vet disabled
|
`go vet -mod=readonly ./...` after successful tests, with test-time vet disabled
|
||||||
for the suite. Set `Validation.Go.CompileTargets` to compile packages for each
|
for the suite. Set `Validation.Go.CompileTargets` to compile packages for each
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
# Go command environments
|
# Go command environments
|
||||||
|
|
||||||
`internal/gotool` resolves effective Go settings once for a validation or build
|
`internal/gotool` resolves effective Go settings when an operation first needs
|
||||||
operation and applies the shared environment to its Go commands. It uses
|
a Go command and applies the shared environment to its commands. Candidate
|
||||||
|
validation and its build callback share an operation-local owner while the
|
||||||
|
inherited environment and persistent Go settings file remain unchanged.
|
||||||
|
Standalone and delivery builds create their own owners. The package uses
|
||||||
`internal/process` for cancellation, the working directory, output, and wrapped
|
`internal/process` for cancellation, the working directory, output, and wrapped
|
||||||
errors.
|
errors.
|
||||||
|
|
||||||
@@ -35,9 +38,10 @@ uses the same ownership transfer when formatting a failure.
|
|||||||
|
|
||||||
## Configuration discovery
|
## Configuration discovery
|
||||||
|
|
||||||
The owner asks the installed `go` command for `GOHOSTOS` and `GOHOSTARCH`, then
|
The owner asks the installed `go` command for `GOHOSTOS`, `GOHOSTARCH`, and the
|
||||||
queries effective operational settings with that host selected. Both queries
|
`GOENV` settings-file path, then queries effective operational settings with
|
||||||
use `GOTOOLCHAIN=local` and controlled `GOFLAGS`. Discovery sets
|
that host selected. Both queries use `GOTOOLCHAIN=local` and controlled
|
||||||
|
`GOFLAGS`. Discovery sets
|
||||||
`GOEXPERIMENT=none` and clears architecture feature variables so an invalid or
|
`GOEXPERIMENT=none` and clears architecture feature variables so an invalid or
|
||||||
nonbaseline tuning value cannot prevent settings from being read. The
|
nonbaseline tuning value cannot prevent settings from being read. The
|
||||||
experiment override is discovery-only.
|
experiment override is discovery-only.
|
||||||
@@ -48,6 +52,11 @@ and native cgo/C-tool configuration into its settings. This captures values
|
|||||||
supplied by either the process environment or the persistent Go environment
|
supplied by either the process environment or the persistent Go environment
|
||||||
file.
|
file.
|
||||||
|
|
||||||
|
The operation-local owner snapshots the inherited environment and selected
|
||||||
|
settings file. It reuses the resolved tool across validation and its following
|
||||||
|
build callback while those inputs match, and rediscovers after a consumer
|
||||||
|
callback changes either input. It does not cache settings across operations.
|
||||||
|
|
||||||
Configuration discovery parses JSON from stdout. Stderr is captured separately
|
Configuration discovery parses JSON from stdout. Stderr is captured separately
|
||||||
and included in command-failure diagnostics, so a successful query can emit
|
and included in command-failure diagnostics, so a successful query can emit
|
||||||
warnings while supplying valid configuration.
|
warnings while supplying valid configuration.
|
||||||
@@ -69,9 +78,10 @@ baseline values. Effective `GOCACHE`, `GOCACHEPROG`, `GOMODCACHE`, `GOPATH`,
|
|||||||
`GOROOT`, and `GOTMPDIR` values are retained. Other process variables remain
|
`GOROOT`, and `GOTMPDIR` values are retained. Other process variables remain
|
||||||
available to child commands.
|
available to child commands.
|
||||||
|
|
||||||
Candidate validation uses native mode for the offline `go test` suite and
|
Candidate validation runs the offline `go test` suite and selected full
|
||||||
selected full `go vet` pass. Package-compilation checks use static mode for each
|
`go vet` pass from the selected module directory using native mode.
|
||||||
selected target and direct Go's output to the operating system null device, so
|
Package-compilation checks use static mode for each selected target and direct
|
||||||
they leave no binary artifact. Artifact builds also use static mode for
|
Go's output to the operating system null device, so they leave no binary
|
||||||
selected-target compilation, then build the native version-check executable;
|
artifact. Artifact builds also compile selected targets in static mode, then
|
||||||
that executable runs as a normal child process.
|
build the native version-check executable, which runs as a normal child
|
||||||
|
process.
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ reports these implemented packages:
|
|||||||
| `internal/delivery` | Tagged-source and release-note checks, managed builds in disposable storage outside the checkout, structural artifact declaration, manifest assembly, one local digest-and-size preflight before hosted requests, and delivery coordination. |
|
| `internal/delivery` | Tagged-source and release-note checks, managed builds in disposable storage outside the checkout, structural artifact declaration, manifest assembly, one local digest-and-size preflight before hosted requests, and delivery coordination. |
|
||||||
| `internal/git` | Git command boundary, committed-tree reads, lazy local-only batched blob reads, push-destination resolution, exact ref inspection, tagged checkout preparation, and tag operations, including captured-endpoint verification during recovery. |
|
| `internal/git` | Git command boundary, committed-tree reads, lazy local-only batched blob reads, push-destination resolution, exact ref inspection, tagged checkout preparation, and tag operations, including captured-endpoint verification during recovery. |
|
||||||
| `internal/gitea` | Gitea API transport, hosted release pages, asset inspection, manifests, and uploads. |
|
| `internal/gitea` | Gitea API transport, hosted release pages, asset inspection, manifests, and uploads. |
|
||||||
| `internal/gotool` | Effective Go configuration discovery, shared native-validation and static-build environments, and network-capable dependency preparation; see [Go command environments](gotool.md). |
|
| `internal/gotool` | Operation-local Go configuration discovery and reuse, shared native-validation and static-build environments, and network-capable dependency preparation; see [Go command environments](gotool.md). |
|
||||||
| `internal/tempdir` | Disposable system temporary storage outside source worktrees. |
|
| `internal/tempdir` | Disposable system temporary storage outside source worktrees. |
|
||||||
| `internal/process` | Cancellable child-process execution, including owned stdin/stdout pipes for serialized exchanges, with per-command environment overlays and removals. |
|
| `internal/process` | Cancellable child-process execution, including owned stdin/stdout pipes for serialized exchanges, with per-command environment overlays and removals. |
|
||||||
| `internal/preparation` | Coordination for expected-commit tag checkout and Go module dependency preparation. |
|
| `internal/preparation` | Coordination for expected-commit tag checkout and Go module dependency preparation. |
|
||||||
@@ -61,10 +61,11 @@ also recorded in [ADR 0001](../adr/0001-public-facade.md) and the
|
|||||||
|
|
||||||
Candidate checking captures and validates one clean committed source revision
|
Candidate checking captures and validates one clean committed source revision
|
||||||
in `internal/candidate`. Its validation bridge calls `internal/validation` for
|
in `internal/candidate`. Its validation bridge calls `internal/validation` for
|
||||||
the default offline Go suite, then runs product-specific checks once. The
|
the selected module's default offline Go suite, then runs product-specific
|
||||||
candidate package owns post-validation builds and local-state rechecks;
|
checks once. Validation and its post-validation build callback share one local
|
||||||
publication owns endpoint guards, tag creation, push, and recovery around that
|
Go settings owner; the candidate package owns those builds and local-state
|
||||||
same candidate validation path. `internal/validation.CandidateTree` reads
|
rechecks; publication owns endpoint guards, tag creation, push, and recovery
|
||||||
|
around that same candidate validation path. `internal/validation.CandidateTree` reads
|
||||||
committed blobs lazily and resolves local destinations from committed entries;
|
committed blobs lazily and resolves local destinations from committed entries;
|
||||||
it does not consult neighboring files or traverse submodule checkouts.
|
it does not consult neighboring files or traverse submodule checkouts.
|
||||||
See the [shared validation guide](validation.md) for checker, Go command, and
|
See the [shared validation guide](validation.md) for checker, Go command, and
|
||||||
@@ -81,7 +82,7 @@ not a separate release matrix:
|
|||||||
| Candidate capture, shared validation, public diagnostics, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `internal/validation`, `cmd/releasekit/internal/cli` | `TestRunUsesOneSuiteWithSelectedRaceSetting`, `TestRunReportsRacePrerequisiteFailureWithoutFallback`, `TestRunExecutesRaceEnabledSuiteOnHost`, `TestRunCompilesTargetsAfterTestAndVetInSelectionOrder`, `TestRunAttributesTargetCompileFailureAndStops`, `TestRunCompilesSourceOnlyAndExecutablePackagesWithoutArtifacts`, `TestCandidateRequestMapsGoValidation`, `TestCandidateRequestPreservesMarkdownObjectSelection`, `TestLoadSettingsReadsGoValidation`, `TestLoadSettingsPreservesMarkdownObjectSelection`, `TestCheckUsesSelectedRaceSuiteBeforeAdditionalChecks`, `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestCheckReturnsPublicValidationError`, `TestMarkdownValidationRunsThroughCheckAndPublish`, `TestCheckStopsBeforeProductAndBuildCallbacksWhenMarkdownBoundaryFails`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate`, `TestPublishCommandPublishesConfiguredCandidate`, `TestCandidateCommandsPresentValidationDiagnosticsOnce`, `TestDeliverPageOnlyCreatesAndReusesRelease` |
|
| Candidate capture, shared validation, public diagnostics, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `internal/validation`, `cmd/releasekit/internal/cli` | `TestRunUsesOneSuiteWithSelectedRaceSetting`, `TestRunReportsRacePrerequisiteFailureWithoutFallback`, `TestRunExecutesRaceEnabledSuiteOnHost`, `TestRunCompilesTargetsAfterTestAndVetInSelectionOrder`, `TestRunAttributesTargetCompileFailureAndStops`, `TestRunCompilesSourceOnlyAndExecutablePackagesWithoutArtifacts`, `TestCandidateRequestMapsGoValidation`, `TestCandidateRequestPreservesMarkdownObjectSelection`, `TestLoadSettingsReadsGoValidation`, `TestLoadSettingsPreservesMarkdownObjectSelection`, `TestCheckUsesSelectedRaceSuiteBeforeAdditionalChecks`, `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestCheckReturnsPublicValidationError`, `TestMarkdownValidationRunsThroughCheckAndPublish`, `TestCheckStopsBeforeProductAndBuildCallbacksWhenMarkdownBoundaryFails`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate`, `TestPublishCommandPublishesConfiguredCandidate`, `TestCandidateCommandsPresentValidationDiagnosticsOnce`, `TestDeliverPageOnlyCreatesAndReusesRelease` |
|
||||||
| Committed-file validation: formatting, workspace files, local replacements, and file size | `internal/validation`, `internal/candidate`, `releasekit.Check`, `releasekit.Publish`, CLI profile conversion | `TestCheckGoFormatUsesCapturedRegularGoBlobs`, `TestCheckWorkspaceFilesReportsCommittedBlobNames`, `TestCheckLocalReplacementsParsesCommittedModuleDeclarations`, `TestCheckLocalReplacementsFollowsCommittedModuleSymlinks`, `TestCheckFileSizeUsesStrictLimitsAndDefault`, `TestCheckFileSizeAppliesLiteralPathExclusions`, `TestCheckFileSizeUsesCommittedBlobSizes`, `TestCheckFileSizeUsesOnlyCandidateFilesIndividually` |
|
| Committed-file validation: formatting, workspace files, local replacements, and file size | `internal/validation`, `internal/candidate`, `releasekit.Check`, `releasekit.Publish`, CLI profile conversion | `TestCheckGoFormatUsesCapturedRegularGoBlobs`, `TestCheckWorkspaceFilesReportsCommittedBlobNames`, `TestCheckLocalReplacementsParsesCommittedModuleDeclarations`, `TestCheckLocalReplacementsFollowsCommittedModuleSymlinks`, `TestCheckFileSizeUsesStrictLimitsAndDefault`, `TestCheckFileSizeAppliesLiteralPathExclusions`, `TestCheckFileSizeUsesCommittedBlobSizes`, `TestCheckFileSizeUsesOnlyCandidateFilesIndividually` |
|
||||||
| Internal Markdown parsing, committed destinations, fragments, boundary observations, and public selection | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `internal/validation`, `cmd/releasekit/internal/cli` | `TestParseMarkdownLinksAndSourceLocations`, `TestParseMarkdownDecodesEntitiesInDestinations`, `TestParseMarkdownHeadingAnchors`, `TestMarkdownCacheKeepsParsedDocumentsPerCandidatePath`, `TestCheckMarkdownLinksResolvesCommittedDestinationsAndFragments`, `TestCheckMarkdownLinksReportsMissingPathsAnchorsAndCycles`, `TestCheckMarkdownLinksUsesResolvedMarkdownIdentity`, `TestCheckMarkdownLinksReportsBoundaryAndSubmoduleObservations`, `TestCheckMarkdownLinksPreservesResolverCauseAndCancellation`, `TestCheckMarkdownLinksPreservesAnchorReadCause`, `TestRunSelectsMarkdownWithCombinedGoChecksAndReportsOutOfScopeLinks`, `TestMarkdownValidationRunsThroughCheckAndPublish`, `TestLoadSettingsPreservesMarkdownObjectSelection`, `TestValidationSpecPreservesMarkdownObjectSelection` |
|
| Internal Markdown parsing, committed destinations, fragments, boundary observations, and public selection | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `internal/validation`, `cmd/releasekit/internal/cli` | `TestParseMarkdownLinksAndSourceLocations`, `TestParseMarkdownDecodesEntitiesInDestinations`, `TestParseMarkdownHeadingAnchors`, `TestMarkdownCacheKeepsParsedDocumentsPerCandidatePath`, `TestCheckMarkdownLinksResolvesCommittedDestinationsAndFragments`, `TestCheckMarkdownLinksReportsMissingPathsAnchorsAndCycles`, `TestCheckMarkdownLinksUsesResolvedMarkdownIdentity`, `TestCheckMarkdownLinksReportsBoundaryAndSubmoduleObservations`, `TestCheckMarkdownLinksPreservesResolverCauseAndCancellation`, `TestCheckMarkdownLinksPreservesAnchorReadCause`, `TestRunSelectsMarkdownWithCombinedGoChecksAndReportsOutOfScopeLinks`, `TestMarkdownValidationRunsThroughCheckAndPublish`, `TestLoadSettingsPreservesMarkdownObjectSelection`, `TestValidationSpecPreservesMarkdownObjectSelection` |
|
||||||
| Shared Go settings for validation and builds | `internal/gotool`, `internal/process`, `internal/candidate`, `internal/build` | `TestDefaultValidationDoesNotInheritGOFLAGSRunFilter`, `TestNativeAndStaticCommandsUseOwnedBaseline`, `TestPersistentSettingsPreserveCachesAndNativeCTools`, `TestBuildIgnoresAmbientAndPersistentArchitectureTuning` |
|
| Shared Go settings for validation and builds | `internal/gotool`, `internal/process`, `internal/candidate`, `internal/build` | `TestDefaultValidationDoesNotInheritGOFLAGSRunFilter`, `TestNativeAndStaticCommandsUseOwnedBaseline`, `TestPersistentSettingsPreserveCachesAndNativeCTools`, `TestOwnerReusesSettingsUntilInheritedEnvironmentChanges`, `TestOwnerRefreshesWhenPersistentSettingsChange`, `TestCheckSharesGoDiscoveryWithAfterValidateCallback`, `TestCheckBuildsAfterValidationAndKeepsOutputOutsideWorktree`, `TestRunValidatesOnlyTheSelectedModule`, `TestBuildIgnoresAmbientAndPersistentArchitectureTuning` |
|
||||||
| Batched local Git blob reads | `internal/git`, `internal/process` | `TestBlobReaderReadsLooseAndPackedObjects`, `TestBlobReaderMissingObjectKeepsBatchSessionUsable`, `TestCandidateObjectReadsStayLocalInPartialClones` |
|
| Batched local Git blob reads | `internal/git`, `internal/process` | `TestBlobReaderReadsLooseAndPackedObjects`, `TestBlobReaderMissingObjectKeepsBatchSessionUsable`, `TestCandidateObjectReadsStayLocalInPartialClones` |
|
||||||
| Piped child-process exchanges and cancellation cleanup | `internal/process` | `TestStartPipedExchangesBeforeInputEOFAndPreservesSettings`, `TestStartPipedCancellationAfterReadinessReapsChild`, `TestStartPipedPreservesNonzeroExit` |
|
| Piped child-process exchanges and cancellation cleanup | `internal/process` | `TestStartPipedExchangesBeforeInputEOFAndPreservesSettings`, `TestStartPipedCancellationAfterReadinessReapsChild`, `TestStartPipedPreservesNonzeroExit` |
|
||||||
| Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishRejectsUnexpectedPushURLBeforeValidation`, `TestPublishComparesExpectedURLWithResolvedInsteadOfDestination`, `TestPublishExpectedPushURLUsesPushURLWhenFetchAndPushDiffer`, `TestCheckIgnoresExpectedPushURL`, `TestResolveUsesPushURLAndQueriesWithoutMutation` |
|
| Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishRejectsUnexpectedPushURLBeforeValidation`, `TestPublishComparesExpectedURLWithResolvedInsteadOfDestination`, `TestPublishExpectedPushURLUsesPushURLWhenFetchAndPushDiffer`, `TestCheckIgnoresExpectedPushURL`, `TestResolveUsesPushURLAndQueriesWithoutMutation` |
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ post-validation build callback and local-state recheck. `internal/publish`
|
|||||||
adds its endpoint guards, tag creation, push, and recovery around that same
|
adds its endpoint guards, tag creation, push, and recovery around that same
|
||||||
candidate path.
|
candidate path.
|
||||||
|
|
||||||
The executor runs one offline Go suite, optionally with the race detector when
|
The executor runs one offline Go suite from the captured module directory,
|
||||||
selected. When full vet is selected, it disables test-time vet for that suite
|
optionally with the race detector when selected. When full vet is selected, it
|
||||||
and runs native `go vet -mod=readonly ./...` after the suite succeeds. It then
|
disables test-time vet for that suite and runs native
|
||||||
runs `go build -mod=readonly -o <os.DevNull> ./...` in static mode for each
|
`go vet -mod=readonly ./...` from the same directory after the suite succeeds.
|
||||||
selected package-compilation target, preserving target order and stopping on
|
It then runs `go build -mod=readonly -o <os.DevNull> ./...` there in static mode
|
||||||
the first failure. This compiles source without producing an artifact or
|
for each selected package-compilation target, preserving target order and
|
||||||
executing a target binary. Test, vet, and package-compilation failures retain
|
stopping on the first failure. This compiles source without producing an
|
||||||
their `go-test`, `go-vet`, and `package-compile` identities, selected target
|
artifact or executing a target binary. Test, vet, and package-compilation
|
||||||
where applicable, and underlying causes. Its
|
failures retain their `go-test`, `go-vet`, and `package-compile` identities,
|
||||||
|
selected target where applicable, and underlying causes. Its
|
||||||
request carries the captured repository directory and commit, output writer,
|
request carries the captured repository directory and commit, output writer,
|
||||||
and internal settings. When selected, the Go-format check loads the candidate
|
and internal settings. When selected, the Go-format check loads the candidate
|
||||||
tree once, reads regular Go blobs from the captured commit, and compares them
|
tree once, reads regular Go blobs from the captured commit, and compares them
|
||||||
|
|||||||
@@ -371,6 +371,18 @@ suite and reused discovery in an unchanged validation/build operation; changing
|
|||||||
environment inputs through an additional callback triggers fresh settings.
|
environment inputs through an additional callback triggers fresh settings.
|
||||||
Run Go-tool, validation, and candidate coordination tests.
|
Run Go-tool, validation, and candidate coordination tests.
|
||||||
|
|
||||||
|
**Completed:** Go validation commands now run from the captured module
|
||||||
|
directory. Candidate checks and publication share a lazy operation-local Go
|
||||||
|
settings owner with their later build callbacks; standalone and managed delivery
|
||||||
|
builds acquire separate owners. The owner reuses discovery while the inherited
|
||||||
|
environment and selected `GOENV` file stay unchanged, and refreshes after
|
||||||
|
consumer callbacks when either changes. Nested-module fixtures now include
|
||||||
|
packages for selected-module validation. Verified one suite and shared discovery
|
||||||
|
through candidate build coordination, environment and persistent-file refresh,
|
||||||
|
nested-suite failure and sibling isolation, selected vet/compile command
|
||||||
|
directories and compile baseline, `go test ./...`, and
|
||||||
|
`go run ./internal/apicheck`.
|
||||||
|
|
||||||
## Stage 7: Build the selected module and bind output evidence
|
## Stage 7: Build the selected module and bind output evidence
|
||||||
|
|
||||||
**Outcome:** Standalone and composite builds use the correct declaration source,
|
**Outcome:** Standalone and composite builds use the correct declaration source,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type Request struct {
|
|||||||
Spec Spec
|
Spec Spec
|
||||||
OutputDir string
|
OutputDir string
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
|
ToolOwner *gotool.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
type Artifact struct {
|
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)
|
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 {
|
if err != nil {
|
||||||
return result, fmt.Errorf("prepare Go toolchain: %w", err)
|
return result, fmt.Errorf("prepare Go toolchain: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
|
||||||
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/tempdir"
|
"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 {
|
if spec == nil || len(spec.Targets) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
selected := *spec
|
selected := *spec
|
||||||
selected.Targets = append([]Target(nil), spec.Targets...)
|
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-")
|
temporaryDir, err := tempdir.Outside(captured.RepositoryDir, "releasekit-candidate-build-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create temporary candidate build output: %w", err)
|
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,
|
Spec: selected,
|
||||||
OutputDir: temporaryDir,
|
OutputDir: temporaryDir,
|
||||||
Output: output,
|
Output: output,
|
||||||
|
ToolOwner: owner,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("build candidate: %w", err)
|
return fmt.Errorf("build candidate: %w", err)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
|
"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/process"
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity"
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
|
||||||
@@ -37,8 +38,9 @@ type Request struct {
|
|||||||
Version string
|
Version string
|
||||||
Validation validation.Settings
|
Validation validation.Settings
|
||||||
AdditionalChecks func(context.Context, Candidate) error
|
AdditionalChecks func(context.Context, Candidate) error
|
||||||
AfterValidate func(context.Context, Candidate) error
|
AfterValidate func(context.Context, Candidate, *gotool.Owner) error
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
|
ToolOwner *gotool.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
func Check(ctx context.Context, request Request) (Candidate, error) {
|
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 {
|
if err != nil {
|
||||||
return captured, err
|
return captured, err
|
||||||
}
|
}
|
||||||
|
request.ToolOwner = gotool.NewOwner(captured.ModuleDir)
|
||||||
if err := Validate(ctx, request, captured); err != nil {
|
if err := Validate(ctx, request, captured); err != nil {
|
||||||
return captured, err
|
return captured, err
|
||||||
}
|
}
|
||||||
if request.AfterValidate != nil {
|
if request.AfterValidate != nil {
|
||||||
if err := request.AfterValidate(ctx, captured); err != nil {
|
callbackErr := request.AfterValidate(ctx, captured, request.ToolOwner)
|
||||||
return captured, fmt.Errorf("after candidate validation: %w", err)
|
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 {
|
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
|
// Validate runs the selected offline Go suite, followed by AdditionalChecks
|
||||||
// when supplied, for the captured candidate.
|
// when supplied, for the captured candidate.
|
||||||
func Validate(ctx context.Context, request Request, captured Candidate) error {
|
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{
|
if err := validation.Run(ctx, validation.Request{
|
||||||
RepositoryDir: captured.RepositoryDir,
|
RepositoryDir: captured.RepositoryDir,
|
||||||
ModuleDir: captured.ModuleDir,
|
ModuleDir: captured.ModuleDir,
|
||||||
Commit: captured.Commit,
|
Commit: captured.Commit,
|
||||||
Output: request.Output,
|
Output: request.Output,
|
||||||
Settings: request.Validation,
|
Settings: request.Validation,
|
||||||
|
ToolOwner: owner,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
|
return fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
|
||||||
}
|
}
|
||||||
if request.AdditionalChecks != nil {
|
if request.AdditionalChecks != nil {
|
||||||
if err := request.AdditionalChecks(ctx, captured); err != nil {
|
callbackErr := request.AdditionalChecks(ctx, captured)
|
||||||
return fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
|
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
|
return nil
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package candidate
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -159,7 +163,7 @@ func TestCheckStopsBeforeProductAndBuildCallbacksWhenGoFormatFails(t *testing.T)
|
|||||||
productCalls++
|
productCalls++
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
AfterValidate: func(context.Context, Candidate) error {
|
AfterValidate: func(context.Context, Candidate, *gotool.Owner) error {
|
||||||
buildCalls++
|
buildCalls++
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -191,7 +195,7 @@ func TestCheckStopsBeforeProductAndBuildCallbacksWhenMarkdownBoundaryFails(t *te
|
|||||||
productCalls++
|
productCalls++
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
AfterValidate: func(context.Context, Candidate) error {
|
AfterValidate: func(context.Context, Candidate, *gotool.Owner) error {
|
||||||
buildCalls++
|
buildCalls++
|
||||||
return nil
|
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) {
|
func TestDefaultValidationDoesNotInheritGOFLAGSRunFilter(t *testing.T) {
|
||||||
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
|
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")
|
writeTestFile(t, repository, "failure_test.go", "package candidate\nimport \"testing\"\nfunc TestExpectedFailure(t *testing.T) { t.Fatal(\"expected fixture failure\") }\n")
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ type Target struct {
|
|||||||
// Tool holds the host target and effective operational settings resolved from
|
// Tool holds the host target and effective operational settings resolved from
|
||||||
// the process environment and Go's persistent environment file.
|
// the process environment and Go's persistent environment file.
|
||||||
type Tool struct {
|
type Tool struct {
|
||||||
host Target
|
host Target
|
||||||
settings map[string]string
|
settings map[string]string
|
||||||
|
settingsFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
var operationalSettings = []string{
|
var operationalSettings = []string{
|
||||||
@@ -53,10 +54,11 @@ var architectureFeatures = []string{
|
|||||||
// configuration from being read.
|
// configuration from being read.
|
||||||
func New(ctx context.Context, directory string) (*Tool, error) {
|
func New(ctx context.Context, directory string) (*Tool, error) {
|
||||||
var host struct {
|
var host struct {
|
||||||
OS string `json:"GOHOSTOS"`
|
OS string `json:"GOHOSTOS"`
|
||||||
Arch string `json:"GOHOSTARCH"`
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("identify Go host: %w", err)
|
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 == "" {
|
if host.OS == "" || host.Arch == "" {
|
||||||
return nil, fmt.Errorf("Go returned an incomplete host target: %s", strings.TrimSpace(string(output)))
|
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 := discoveryEnvironment()
|
||||||
environment["GOOS"] = tool.host.OS
|
environment["GOOS"] = tool.host.OS
|
||||||
|
|||||||
@@ -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 {
|
func readSettings(t *testing.T, tool *Tool, mode Mode, target Target, names ...string) map[string]string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
args := append([]string{"env", "-json"}, names...)
|
args := append([]string{"env", "-json"}, names...)
|
||||||
|
|||||||
136
internal/gotool/owner.go
Normal file
136
internal/gotool/owner.go
Normal 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
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
|
||||||
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ type Request struct {
|
|||||||
ExpectedPushURL string
|
ExpectedPushURL string
|
||||||
Validation validation.Settings
|
Validation validation.Settings
|
||||||
AdditionalChecks func(context.Context, candidate.Candidate) error
|
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
|
Output io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +121,18 @@ func Publish(ctx context.Context, request Request) (Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.Phase = Validation
|
result.Phase = Validation
|
||||||
|
checks.ToolOwner = gotool.NewOwner(captured.ModuleDir)
|
||||||
if err := candidate.Validate(ctx, checks, captured); err != nil {
|
if err := candidate.Validate(ctx, checks, captured); err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
if request.AfterValidate != nil {
|
if request.AfterValidate != nil {
|
||||||
if err := request.AfterValidate(ctx, captured); err != nil {
|
callbackErr := request.AfterValidate(ctx, captured, checks.ToolOwner)
|
||||||
return result, fmt.Errorf("after candidate validation: %w", err)
|
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 {
|
if err := candidate.Recheck(ctx, captured, ""); err != nil {
|
||||||
|
|||||||
@@ -19,25 +19,38 @@ func TestRunUsesOneSuiteWithGoValidationSettings(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
race bool
|
race bool
|
||||||
fullVet bool
|
fullVet bool
|
||||||
|
compile []Target
|
||||||
}{
|
}{
|
||||||
{name: "default"},
|
{name: "default"},
|
||||||
{name: "race", race: true},
|
{name: "race", race: true},
|
||||||
{name: "full vet", fullVet: true},
|
{name: "full vet", fullVet: true},
|
||||||
{name: "race and full vet", race: true, 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) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
logPath := installGoShim(t, "", false)
|
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{
|
err := Run(context.Background(), Request{
|
||||||
RepositoryDir: t.TempDir(),
|
RepositoryDir: repositoryDir,
|
||||||
Settings: Settings{Race: test.race, FullVet: test.fullVet},
|
ModuleDir: moduleDir,
|
||||||
|
Settings: Settings{Race: test.race, FullVet: test.fullVet, CompileTargets: test.compile},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Run() error = %v", err)
|
t.Fatalf("Run() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commands := readGoShimLog(t, logPath)
|
commands := readGoShimLog(t, logPath)
|
||||||
var tests, vets []string
|
for _, directory := range readGoShimDirectories(t, os.Getenv("RELEASEKIT_GO_DIR_LOG")) {
|
||||||
var testPosition, vetPosition int
|
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 {
|
for i, command := range commands {
|
||||||
if strings.HasPrefix(command, "test ") {
|
if strings.HasPrefix(command, "test ") {
|
||||||
tests = append(tests, command)
|
tests = append(tests, command)
|
||||||
@@ -47,6 +60,10 @@ func TestRunUsesOneSuiteWithGoValidationSettings(t *testing.T) {
|
|||||||
vets = append(vets, command)
|
vets = append(vets, command)
|
||||||
vetPosition = i
|
vetPosition = i
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(command, "build ") {
|
||||||
|
builds = append(builds, command)
|
||||||
|
buildPosition = i
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(tests) != 1 {
|
if len(tests) != 1 {
|
||||||
t.Fatalf("Go test invocations = %q, want exactly one", tests)
|
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])
|
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) {
|
func TestRunStopsAfterGoTestFailure(t *testing.T) {
|
||||||
logPath := installGoShim(t, "test", false)
|
logPath := installGoShim(t, "test", false)
|
||||||
err := Run(context.Background(), Request{
|
err := Run(context.Background(), Request{
|
||||||
@@ -214,6 +295,7 @@ func installGoShim(t *testing.T, failure string, delayVet bool) string {
|
|||||||
directory := t.TempDir()
|
directory := t.TempDir()
|
||||||
logPath := filepath.Join(directory, "go-commands")
|
logPath := filepath.Join(directory, "go-commands")
|
||||||
envLogPath := filepath.Join(directory, "go-environment")
|
envLogPath := filepath.Join(directory, "go-environment")
|
||||||
|
directoryLogPath := filepath.Join(directory, "go-directories")
|
||||||
settings := map[string]string{
|
settings := map[string]string{
|
||||||
"GOHOSTOS": runtime.GOOS,
|
"GOHOSTOS": runtime.GOOS,
|
||||||
"GOHOSTARCH": runtime.GOARCH,
|
"GOHOSTARCH": runtime.GOARCH,
|
||||||
@@ -263,6 +345,7 @@ fi
|
|||||||
script := fmt.Sprintf(`#!/bin/sh
|
script := fmt.Sprintf(`#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
printf '%%s\n' "$*" >> "$RELEASEKIT_GO_LOG"
|
printf '%%s\n' "$*" >> "$RELEASEKIT_GO_LOG"
|
||||||
|
pwd -P >> "$RELEASEKIT_GO_DIR_LOG"
|
||||||
if [ "${1-}" = "env" ]; then
|
if [ "${1-}" = "env" ]; then
|
||||||
printf '%%s\n' '%s'
|
printf '%%s\n' '%s'
|
||||||
exit 0
|
exit 0
|
||||||
@@ -281,6 +364,7 @@ exit 0
|
|||||||
t.Setenv("PATH", path)
|
t.Setenv("PATH", path)
|
||||||
t.Setenv("RELEASEKIT_GO_LOG", logPath)
|
t.Setenv("RELEASEKIT_GO_LOG", logPath)
|
||||||
t.Setenv("RELEASEKIT_GO_ENV_LOG", envLogPath)
|
t.Setenv("RELEASEKIT_GO_ENV_LOG", envLogPath)
|
||||||
|
t.Setenv("RELEASEKIT_GO_DIR_LOG", directoryLogPath)
|
||||||
return logPath
|
return logPath
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,6 +377,15 @@ func readGoShimLog(t *testing.T, path string) []string {
|
|||||||
return strings.Split(strings.TrimSpace(string(contents)), "\n")
|
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 {
|
func raceSupported(goos, goarch string) bool {
|
||||||
switch goos {
|
switch goos {
|
||||||
case "darwin":
|
case "darwin":
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ type Request struct {
|
|||||||
Commit string
|
Commit string
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
Settings Settings
|
Settings Settings
|
||||||
|
ToolOwner *gotool.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadCandidateTree loads the committed-file inventory when a selected check
|
// 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 {
|
if err != nil {
|
||||||
return &Failure{Check: GoTest, Cause: err}
|
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, "-vet=off")
|
||||||
}
|
}
|
||||||
args = append(args, "./...")
|
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 {
|
if err != nil {
|
||||||
return &Failure{Check: GoTest, Cause: err}
|
return &Failure{Check: GoTest, Cause: err}
|
||||||
}
|
}
|
||||||
if request.Settings.FullVet {
|
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 {
|
if err != nil {
|
||||||
return &Failure{Check: GoVet, Cause: err}
|
return &Failure{Check: GoVet, Cause: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, target := range request.Settings.CompileTargets {
|
for _, target := range request.Settings.CompileTargets {
|
||||||
selected := gotool.Target{OS: target.OS, Arch: target.Arch}
|
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, "./...")
|
"build", "-mod=readonly", "-o", os.DevNull, "./...")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Failure{
|
return &Failure{
|
||||||
|
|||||||
@@ -805,6 +805,7 @@ func installGitWrapper(t *testing.T, repo publishFixture, mode string) gitWrappe
|
|||||||
func addNestedReleaseModule(t *testing.T, repository, moduleDir, modulePath, version string) {
|
func addNestedReleaseModule(t *testing.T, repository, moduleDir, modulePath, version string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
writeFile(t, repository, moduleDir+"/go.mod", "module "+modulePath+"\n\ngo 1.25.0\n")
|
writeFile(t, repository, moduleDir+"/go.mod", "module "+modulePath+"\n\ngo 1.25.0\n")
|
||||||
|
writeFile(t, repository, moduleDir+"/fixture_test.go", "package fixture\nimport \"testing\"\nfunc TestFixture(t *testing.T) {}\n")
|
||||||
writeFile(t, repository, moduleDir+"/docs/releases/"+version+".md", "# Sample App "+version+"\n\nNested release.\n")
|
writeFile(t, repository, moduleDir+"/docs/releases/"+version+".md", "# Sample App "+version+"\n\nNested release.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user