Inspect committed workspace files
This commit is contained in:
17
check.go
17
check.go
@@ -52,6 +52,17 @@ type ValidationSpec struct {
|
|||||||
// GoFormat checks the formatting of committed regular Go source files. It
|
// GoFormat checks the formatting of committed regular Go source files. It
|
||||||
// does not modify repository contents and is disabled when false.
|
// does not modify repository contents and is disabled when false.
|
||||||
GoFormat bool
|
GoFormat bool
|
||||||
|
// ModuleDistribution selects checks for files that affect module-source
|
||||||
|
// distribution. Its zero value disables those checks.
|
||||||
|
ModuleDistribution ModuleDistributionValidationSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModuleDistributionValidationSpec selects independent module-source
|
||||||
|
// distribution checks.
|
||||||
|
type ModuleDistributionValidationSpec struct {
|
||||||
|
// WorkspaceFiles rejects committed go.work and go.work.sum blobs, including
|
||||||
|
// nested paths and symlink entries. It does not select Go's test workspace.
|
||||||
|
WorkspaceFiles bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoValidationSpec selects checks performed by the Go toolchain.
|
// GoValidationSpec selects checks performed by the Go toolchain.
|
||||||
@@ -79,6 +90,9 @@ type GoValidationSpec struct {
|
|||||||
// compilation produces no artifacts and does not execute target binaries.
|
// compilation produces no artifacts and does not execute target binaries.
|
||||||
// Validation.GoFormat checks regular committed Go source after those selected
|
// Validation.GoFormat checks regular committed Go source after those selected
|
||||||
// Go checks and before AdditionalChecks. Its zero value disables formatting.
|
// Go checks and before AdditionalChecks. Its zero value disables formatting.
|
||||||
|
// Validation.ModuleDistribution.WorkspaceFiles rejects committed workspace
|
||||||
|
// files after the other selected shared checks. Its zero value disables the
|
||||||
|
// distribution check and does not change Go's workspace environment.
|
||||||
// The zero values of all selections preserve the ordinary suite. If
|
// The zero values of all selections preserve the ordinary suite. If
|
||||||
// Build selects targets, Check builds them after validation in temporary
|
// Build selects targets, Check builds them after validation in temporary
|
||||||
// storage outside the worktree, then rechecks
|
// storage outside the worktree, then rechecks
|
||||||
@@ -106,6 +120,9 @@ func candidateRequest(request ReleaseRequest) candidate.Request {
|
|||||||
FullVet: request.Validation.Go.FullVet,
|
FullVet: request.Validation.Go.FullVet,
|
||||||
CompileTargets: make([]validation.Target, len(request.Validation.Go.CompileTargets)),
|
CompileTargets: make([]validation.Target, len(request.Validation.Go.CompileTargets)),
|
||||||
GoFormat: request.Validation.GoFormat,
|
GoFormat: request.Validation.GoFormat,
|
||||||
|
ModuleDistribution: validation.ModuleDistributionSettings{
|
||||||
|
WorkspaceFiles: request.Validation.ModuleDistribution.WorkspaceFiles,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
AdditionalChecks: candidateChecks(request.AdditionalChecks),
|
AdditionalChecks: candidateChecks(request.AdditionalChecks),
|
||||||
AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output),
|
AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output),
|
||||||
|
|||||||
@@ -129,7 +129,15 @@ type ValidationSettings struct {
|
|||||||
Go GoValidationSettings `json:"go,omitempty"`
|
Go GoValidationSettings `json:"go,omitempty"`
|
||||||
// GoFormat checks the formatting of committed regular Go source files.
|
// GoFormat checks the formatting of committed regular Go source files.
|
||||||
// Omitted or false disables the check.
|
// Omitted or false disables the check.
|
||||||
GoFormat bool `json:"go_format,omitempty"`
|
GoFormat bool `json:"go_format,omitempty"`
|
||||||
|
ModuleDistribution ModuleDistributionSettings `json:"module_distribution,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModuleDistributionSettings selects committed module-source distribution checks.
|
||||||
|
type ModuleDistributionSettings struct {
|
||||||
|
// WorkspaceFiles rejects committed go.work and go.work.sum blobs.
|
||||||
|
// Omitted or false disables the check.
|
||||||
|
WorkspaceFiles bool `json:"workspace_files,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoValidationSettings selects checks performed by the Go toolchain.
|
// GoValidationSettings selects checks performed by the Go toolchain.
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func TestLoadSettingsDefaultsRemoteAndConfigPath(t *testing.T) {
|
|||||||
func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
||||||
repoDir := t.TempDir()
|
repoDir := t.TempDir()
|
||||||
profilePath := filepath.Join(repoDir, "releasekit.json")
|
profilePath := filepath.Join(repoDir, "releasekit.json")
|
||||||
if err := os.WriteFile(profilePath, []byte(`{"product":"Example","expected_push_url":"ssh://git.example/owner/repo.git","validation":{"go_format":true,"go":{"race":true,"full_vet":true,"compile_targets":["linux/amd64","linux/arm64","darwin/arm64"]}}}`), 0o600); err != nil {
|
if err := os.WriteFile(profilePath, []byte(`{"product":"Example","expected_push_url":"ssh://git.example/owner/repo.git","validation":{"go_format":true,"module_distribution":{"workspace_files":true},"go":{"race":true,"full_vet":true,"compile_targets":["linux/amd64","linux/arm64","darwin/arm64"]}}}`), 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,8 +152,8 @@ func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if settings.Validation == nil || !settings.Validation.GoFormat || !settings.Validation.Go.Race || !settings.Validation.Go.FullVet {
|
if settings.Validation == nil || !settings.Validation.GoFormat || !settings.Validation.ModuleDistribution.WorkspaceFiles || !settings.Validation.Go.Race || !settings.Validation.Go.FullVet {
|
||||||
t.Fatalf("validation settings = %#v, want Go formatting, race, and full vet enabled", settings.Validation)
|
t.Fatalf("validation settings = %#v, want Go formatting, workspace inspection, race, and full vet enabled", settings.Validation)
|
||||||
}
|
}
|
||||||
if got, want := strings.Join(settings.Validation.Go.CompileTargets, ","), "linux/amd64,linux/arm64,darwin/arm64"; got != want {
|
if got, want := strings.Join(settings.Validation.Go.CompileTargets, ","), "linux/amd64,linux/arm64,darwin/arm64"; got != want {
|
||||||
t.Fatalf("compile targets = %q, want %q", got, want)
|
t.Fatalf("compile targets = %q, want %q", got, want)
|
||||||
|
|||||||
@@ -162,6 +162,9 @@ func validationSpec(settings *cli.ValidationSettings) releasekit.ValidationSpec
|
|||||||
}
|
}
|
||||||
result := releasekit.ValidationSpec{
|
result := releasekit.ValidationSpec{
|
||||||
GoFormat: settings.GoFormat,
|
GoFormat: settings.GoFormat,
|
||||||
|
ModuleDistribution: releasekit.ModuleDistributionValidationSpec{
|
||||||
|
WorkspaceFiles: settings.ModuleDistribution.WorkspaceFiles,
|
||||||
|
},
|
||||||
Go: releasekit.GoValidationSpec{
|
Go: releasekit.GoValidationSpec{
|
||||||
Race: settings.Go.Race,
|
Race: settings.Go.Race,
|
||||||
FullVet: settings.Go.FullVet,
|
FullVet: settings.Go.FullVet,
|
||||||
|
|||||||
@@ -117,8 +117,9 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
|||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
ExpectedPushURL: "publication-only value",
|
ExpectedPushURL: "publication-only value",
|
||||||
Validation: &cli.ValidationSettings{
|
Validation: &cli.ValidationSettings{
|
||||||
GoFormat: true,
|
GoFormat: true,
|
||||||
Go: cli.GoValidationSettings{Race: true, FullVet: true, CompileTargets: []string{"linux/amd64", "darwin/arm64"}},
|
Go: cli.GoValidationSettings{Race: true, FullVet: true, CompileTargets: []string{"linux/amd64", "darwin/arm64"}},
|
||||||
|
ModuleDistribution: cli.ModuleDistributionSettings{WorkspaceFiles: true},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
if !request.Validation.Go.Race || !request.Validation.Go.FullVet {
|
if !request.Validation.Go.Race || !request.Validation.Go.FullVet {
|
||||||
@@ -127,6 +128,9 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
|||||||
if !request.Validation.GoFormat {
|
if !request.Validation.GoFormat {
|
||||||
t.Fatalf("candidate request GoFormat = false, want true")
|
t.Fatalf("candidate request GoFormat = false, want true")
|
||||||
}
|
}
|
||||||
|
if !request.Validation.ModuleDistribution.WorkspaceFiles {
|
||||||
|
t.Fatalf("candidate request workspace files = false, want true")
|
||||||
|
}
|
||||||
if request.ExpectedPushURL != "" {
|
if request.ExpectedPushURL != "" {
|
||||||
t.Fatalf("candidate request carried publication-only expected push URL %q", request.ExpectedPushURL)
|
t.Fatalf("candidate request carried publication-only expected push URL %q", request.ExpectedPushURL)
|
||||||
}
|
}
|
||||||
|
|||||||
17
docs/cli.md
17
docs/cli.md
@@ -39,8 +39,11 @@ show the selected target. When `validation.go_format` is true, `check` examines
|
|||||||
regular committed Go files with the Go formatter after tests, vet, and package
|
regular committed Go files with the Go formatter after tests, vet, and package
|
||||||
compilation. It includes vendor and nested-module source, ignores the worktree
|
compilation. It includes vendor and nested-module source, ignores the worktree
|
||||||
and symlink blobs, and reports unformatted paths as `go-format` without
|
and symlink blobs, and reports unformatted paths as `go-format` without
|
||||||
modifying files. Any artifact build selected by the separate `build`
|
modifying files. When `validation.module_distribution.workspace_files` is true,
|
||||||
setting runs after shared validation and configured checks.
|
`check` reports committed `go.work` and `go.work.sum` blobs, including nested
|
||||||
|
and symlink entries, as `workspace-files`. This inspection is independent of
|
||||||
|
`GOWORK` and ignores uncommitted files. Any artifact build selected by the
|
||||||
|
separate `build` setting runs after shared validation and configured checks.
|
||||||
|
|
||||||
`check` writes errors and diagnostics to standard error. It returns status 0
|
`check` writes errors and diagnostics to standard error. It returns status 0
|
||||||
after local validation, 1 after a candidate validation failure, and 2 for usage,
|
after local validation, 1 after a candidate validation failure, and 2 for usage,
|
||||||
@@ -60,8 +63,9 @@ settings as `check`. It checks the authoritative remote and validates the
|
|||||||
candidate: the single standard offline Go test suite runs first, with `-race`
|
candidate: the single standard offline Go test suite runs first, with `-race`
|
||||||
when `validation.go.race` is true. When `validation.go.full_vet` is true,
|
when `validation.go.race` is true. When `validation.go.full_vet` is true,
|
||||||
test-time vet is disabled and `go vet -mod=readonly ./...` runs after successful
|
test-time vet is disabled and `go vet -mod=readonly ./...` runs after successful
|
||||||
tests. Selected package compilation and committed Go formatting follow, then
|
tests. Selected package compilation and committed Go formatting follow. The
|
||||||
configured checks run in profile order. It then creates an annotated local tag
|
selected workspace-file check then runs, followed by configured checks in
|
||||||
|
profile order. It then creates an annotated local tag
|
||||||
and pushes only its exact tag object. It prints the confirmed version,
|
and pushes only its exact tag object. It prints the confirmed version,
|
||||||
candidate commit, and tag object
|
candidate commit, and tag object
|
||||||
on success. Validation
|
on success. Validation
|
||||||
@@ -77,8 +81,9 @@ after tests and full vet and before configured checks. Each failure is reported
|
|||||||
as `package-compile` with its selected target; this does not create build
|
as `package-compile` with its selected target; this does not create build
|
||||||
artifacts or execute target binaries. When `validation.go_format` is true,
|
artifacts or execute target binaries. When `validation.go_format` is true,
|
||||||
committed Go formatting runs after package compilation and before configured
|
committed Go formatting runs after package compilation and before configured
|
||||||
checks. A failure is reported as `go-format` with affected repository paths;
|
checks. The selected workspace-file check then reports committed go.work and
|
||||||
parser and source-inspection causes are retained.
|
go.work.sum paths as `workspace-files`. Formatting failures are reported as
|
||||||
|
`go-format` with affected paths and preserve parser or source-inspection causes.
|
||||||
|
|
||||||
The optional profile field `expected_push_url` applies only to `publish`. A
|
The optional profile field `expected_push_url` applies only to `publish`. A
|
||||||
nonempty value must exactly match the effective push URL resolved by Git;
|
nonempty value must exactly match the effective push URL resolved by Git;
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ symlink blobs and the worktree, and reports every selected path whose bytes
|
|||||||
change under formatting. It never writes formatted bytes back. Omitted or false
|
change under formatting. It never writes formatted bytes back. Omitted or false
|
||||||
disables the check. It runs after tests, selected full vet, and selected package
|
disables the check. It runs after tests, selected full vet, and selected package
|
||||||
compilation, before configured product checks.
|
compilation, before configured product checks.
|
||||||
|
|
||||||
|
When `validation.module_distribution.workspace_files` is true, it reports
|
||||||
|
committed blob entries whose basename is `go.work` or `go.work.sum`, including
|
||||||
|
nested paths and symlinks. It checks candidate distribution contents regardless
|
||||||
|
of `GOWORK`; it does not select or change the workspace used by Go commands,
|
||||||
|
and it does not alter existing vendor behavior. Omitted or false disables it.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -56,7 +62,10 @@ For example:
|
|||||||
"full_vet": true,
|
"full_vet": true,
|
||||||
"compile_targets": ["linux/amd64", "linux/arm64"]
|
"compile_targets": ["linux/amd64", "linux/arm64"]
|
||||||
},
|
},
|
||||||
"go_format": true
|
"go_format": true,
|
||||||
|
"module_distribution": {
|
||||||
|
"workspace_files": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -84,10 +93,14 @@ run native `go vet -mod=readonly ./...` after successful tests. They then run
|
|||||||
`validation.go.compile_targets` entry, in profile order. Compile failures use
|
`validation.go.compile_targets` entry, in profile order. Compile failures use
|
||||||
the `package-compile` identity and include the selected target. When selected,
|
the `package-compile` identity and include the selected target. When selected,
|
||||||
`validation.go_format` checks regular committed Go source with go/format after
|
`validation.go_format` checks regular committed Go source with go/format after
|
||||||
package compilation; unformatted paths use the `go-format` identity. Configured
|
package compilation; unformatted paths use the `go-format` identity. When
|
||||||
`checks` commands run afterward, once each in order, as additional product
|
`validation.module_distribution.workspace_files` is true, shared validation
|
||||||
checks. Artifact builds selected by the separate `build` setting run after
|
also reports committed go.work and go.work.sum blobs after Go formatting, using
|
||||||
shared validation and product checks.
|
the workspace-files identity. Uncommitted workspace files are not inspected.
|
||||||
|
Configured `checks` commands run afterward, once each in order, as additional
|
||||||
|
product checks. Artifact builds selected by the separate `build` setting run
|
||||||
|
after shared validation and product checks.
|
||||||
|
|
||||||
Do not repeat the standard suite or vet in `checks`; doing so repeats work. The
|
Do not repeat the standard suite or vet in `checks`; doing so repeats work. The
|
||||||
`build` command uses the selected build settings and writes binaries to `dist` unless
|
`build` command uses the selected build settings and writes binaries to `dist` unless
|
||||||
`--output` selects another path.
|
`--output` selects another path.
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ formatter built into ReleaseKit. It includes vendor and nested-module source,
|
|||||||
ignores symlink blobs and worktree changes, and reports unformatted paths as
|
ignores symlink blobs and worktree changes, and reports unformatted paths as
|
||||||
`go-format` without rewriting files. Its zero value disables the check; it runs
|
`go-format` without rewriting files. Its zero value disables the check; it runs
|
||||||
after tests, selected vet, and package compilation, before `AdditionalChecks`.
|
after tests, selected vet, and package compilation, before `AdditionalChecks`.
|
||||||
|
|
||||||
|
Set `Validation.ModuleDistribution.WorkspaceFiles` to reject committed
|
||||||
|
`go.work` and `go.work.sum` blobs, including nested and symlink entries. It
|
||||||
|
examines the captured candidate even when `GOWORK=off`, ignores uncommitted
|
||||||
|
files, and does not change Go test workspace selection or vendor behavior. The
|
||||||
|
zero value disables the check; failures use `workspace-files`.
|
||||||
ReleaseKit calls `AdditionalChecks` once after shared validation when supplied.
|
ReleaseKit calls `AdditionalChecks` once after shared validation when supplied.
|
||||||
It does not download a different toolchain automatically. If race prerequisites
|
It does not download a different toolchain automatically. If race prerequisites
|
||||||
are unavailable, the `go-test` check fails rather than being skipped; a selected
|
are unavailable, the `go-test` check fails rather than being skipped; a selected
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ worktree changes and symlink blobs, and reports unformatted paths as
|
|||||||
tests, selected vet, and package compilation, and a failure stops publication
|
tests, selected vet, and package compilation, and a failure stops publication
|
||||||
before product checks or tag creation.
|
before product checks or tag creation.
|
||||||
|
|
||||||
|
Set `Validation.ModuleDistribution.WorkspaceFiles` to reject committed
|
||||||
|
`go.work` and `go.work.sum` blobs, including nested paths and symlink entries.
|
||||||
|
The rule reads the captured candidate regardless of `GOWORK`; uncommitted files
|
||||||
|
are ignored, and Go workspace selection and vendor behavior are unchanged. Its
|
||||||
|
zero value disables the check. Findings use `workspace-files` and stop
|
||||||
|
publication before product checks or tag creation.
|
||||||
|
|
||||||
If `Build` selects targets, ReleaseKit verifies them after the standard suite
|
If `Build` selects targets, ReleaseKit verifies them after the standard suite
|
||||||
and additional checks, before creating the tag. The operation then rechecks the
|
and additional checks, before creating the tag. The operation then rechecks the
|
||||||
candidate and remote guards, creates an annotated tag for the captured commit,
|
candidate and remote guards, creates an annotated tag for the captured commit,
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ symlink blobs, reports all unformatted paths, and stops with a contextual cause
|
|||||||
on parse or inspection errors. It runs before product checks and build callbacks.
|
on parse or inspection errors. It runs before product checks and build callbacks.
|
||||||
`Request.ReadCandidateTree` lets selected file checks request candidate
|
`Request.ReadCandidateTree` lets selected file checks request candidate
|
||||||
contents; the Go-only path does not enumerate the tree.
|
contents; the Go-only path does not enumerate the tree.
|
||||||
|
|
||||||
|
The workspace-file check uses that same candidate inventory and reports blob
|
||||||
|
entries named go.work or go.work.sum, including nested paths and symlink blobs.
|
||||||
|
It does not read workspace contents or depend on the workspace selected for Go
|
||||||
|
commands.
|
||||||
`CandidateTree` gets metadata and bytes from Git objects, reads blob bytes
|
`CandidateTree` gets metadata and bytes from Git objects, reads blob bytes
|
||||||
lazily, and resolves destinations without consulting the worktree or
|
lazily, and resolves destinations without consulting the worktree or
|
||||||
neighboring files.
|
neighboring files.
|
||||||
|
|||||||
@@ -337,6 +337,12 @@ behavior, API/profile parity, and failure before later product/build work.
|
|||||||
|
|
||||||
## Stage 6: Select committed workspace-file inspection
|
## Stage 6: Select committed workspace-file inspection
|
||||||
|
|
||||||
|
**Completion:** Complete. `Validation.ModuleDistribution.WorkspaceFiles` and
|
||||||
|
`validation.module_distribution.workspace_files` report committed blob entries
|
||||||
|
named `go.work` or `go.work.sum`, including nested paths and symlinks. The rule
|
||||||
|
inspects the captured candidate independently of `GOWORK` and leaves the Go
|
||||||
|
workspace environment and vendor behavior unchanged.
|
||||||
|
|
||||||
**Outcome:** One distribution rule works independently of the Go environment.
|
**Outcome:** One distribution rule works independently of the Go environment.
|
||||||
|
|
||||||
1. Add `ModuleDistributionValidationSpec.WorkspaceFiles` and the matching JSON
|
1. Add `ModuleDistributionValidationSpec.WorkspaceFiles` and the matching JSON
|
||||||
|
|||||||
@@ -108,6 +108,52 @@ func TestCheckStopsBeforeProductAndBuildCallbacksWhenGoFormatFails(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckReportsCommittedWorkspaceFilesWhenGoWorkspaceIsDisabled(t *testing.T) {
|
||||||
|
t.Setenv("GOWORK", "off")
|
||||||
|
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
|
||||||
|
writeTestFile(t, repository, "go.work", "go 1.25.0\n")
|
||||||
|
writeTestFile(t, repository, "nested/go.work.sum", "workspace-sum\n")
|
||||||
|
if err := os.MkdirAll(filepath.Join(repository, "linked"), 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink("../go.work", filepath.Join(repository, "linked", "go.work")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gitRun(t, repository, "add", "go.work", "nested/go.work.sum", "linked/go.work")
|
||||||
|
gitRun(t, repository, "commit", "-m", "add workspace files")
|
||||||
|
_, err := Check(context.Background(), Request{
|
||||||
|
RepositoryDir: repository,
|
||||||
|
Product: "Example Product",
|
||||||
|
Version: testVersion,
|
||||||
|
Validation: validation.Settings{ModuleDistribution: validation.ModuleDistributionSettings{
|
||||||
|
WorkspaceFiles: true,
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
var failure *validation.Failure
|
||||||
|
if !errors.As(err, &failure) || failure.Check != validation.WorkspaceFiles {
|
||||||
|
t.Fatalf("Check error = %v, want workspace-files failure", err)
|
||||||
|
}
|
||||||
|
want := map[string]bool{
|
||||||
|
"go.work": false,
|
||||||
|
"linked/go.work": false,
|
||||||
|
"nested/go.work.sum": false,
|
||||||
|
}
|
||||||
|
if len(failure.Findings) != len(want) {
|
||||||
|
t.Fatalf("workspace findings = %#v, want %d paths", failure.Findings, len(want))
|
||||||
|
}
|
||||||
|
for _, finding := range failure.Findings {
|
||||||
|
if _, ok := want[finding.Path]; !ok {
|
||||||
|
t.Errorf("unexpected workspace path %q", finding.Path)
|
||||||
|
}
|
||||||
|
want[finding.Path] = true
|
||||||
|
}
|
||||||
|
for path, found := range want {
|
||||||
|
if !found {
|
||||||
|
t.Errorf("workspace path %q was not reported", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckRunsDefaultOfflineGoValidation(t *testing.T) {
|
func TestCheckRunsDefaultOfflineGoValidation(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")
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const (
|
|||||||
PackageCompile Check = "package-compile"
|
PackageCompile Check = "package-compile"
|
||||||
// GoFormat identifies committed Go source formatting.
|
// GoFormat identifies committed Go source formatting.
|
||||||
GoFormat Check = "go-format"
|
GoFormat Check = "go-format"
|
||||||
|
// WorkspaceFiles identifies committed workspace files in module source.
|
||||||
|
WorkspaceFiles Check = "workspace-files"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Finding describes a validation observation at an optional source location.
|
// Finding describes a validation observation at an optional source location.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"go/format"
|
"go/format"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
|
||||||
@@ -15,10 +16,16 @@ import (
|
|||||||
|
|
||||||
// Settings selects shared validation behavior.
|
// Settings selects shared validation behavior.
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Race bool
|
Race bool
|
||||||
FullVet bool
|
FullVet bool
|
||||||
CompileTargets []Target
|
CompileTargets []Target
|
||||||
GoFormat bool
|
GoFormat bool
|
||||||
|
ModuleDistribution ModuleDistributionSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModuleDistributionSettings selects candidate distribution checks.
|
||||||
|
type ModuleDistributionSettings struct {
|
||||||
|
WorkspaceFiles bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Target selects one package-compilation target.
|
// Target selects one package-compilation target.
|
||||||
@@ -91,14 +98,45 @@ func Run(ctx context.Context, request Request) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if request.Settings.GoFormat {
|
if request.Settings.GoFormat || request.Settings.ModuleDistribution.WorkspaceFiles {
|
||||||
tree, err := request.ReadCandidateTree(ctx)
|
tree, err := request.ReadCandidateTree(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Failure{Check: GoFormat, Cause: err}
|
check := WorkspaceFiles
|
||||||
|
if request.Settings.GoFormat {
|
||||||
|
check = GoFormat
|
||||||
|
}
|
||||||
|
return &Failure{Check: check, Cause: err}
|
||||||
}
|
}
|
||||||
if err := checkGoFormat(ctx, tree); err != nil {
|
if request.Settings.GoFormat {
|
||||||
return err
|
if err := checkGoFormat(ctx, tree); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if request.Settings.ModuleDistribution.WorkspaceFiles {
|
||||||
|
if err := checkWorkspaceFiles(tree); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkWorkspaceFiles(tree *CandidateTree) error {
|
||||||
|
var findings []Finding
|
||||||
|
for _, entry := range tree.Entries() {
|
||||||
|
if entry.Type != "blob" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch path.Base(entry.Path) {
|
||||||
|
case "go.work", "go.work.sum":
|
||||||
|
findings = append(findings, Finding{
|
||||||
|
Path: entry.Path,
|
||||||
|
Message: "workspace file is committed in the candidate",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(findings) != 0 {
|
||||||
|
return &Failure{Check: WorkspaceFiles, Findings: findings}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
100
internal/validation/workspace_test.go
Normal file
100
internal/validation/workspace_test.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckWorkspaceFilesReportsCommittedBlobNames(t *testing.T) {
|
||||||
|
tree := &CandidateTree{entries: []git.TreeEntry{
|
||||||
|
{Path: "go.work", Type: "blob", Mode: "100644"},
|
||||||
|
{Path: "nested/go.work.sum", Type: "blob", Mode: "100644"},
|
||||||
|
{Path: "vendor/nested/link/go.work", Type: "blob", Mode: "120000"},
|
||||||
|
{Path: "nested/not-go.work.backup", Type: "blob", Mode: "100644"},
|
||||||
|
{Path: "submodule/go.work", Type: "commit", Mode: "160000"},
|
||||||
|
}}
|
||||||
|
err := checkWorkspaceFiles(tree)
|
||||||
|
failure, ok := err.(*Failure)
|
||||||
|
if !ok || failure.Check != WorkspaceFiles {
|
||||||
|
t.Fatalf("checkWorkspaceFiles error = %v, want workspace-files failure", err)
|
||||||
|
}
|
||||||
|
want := map[string]bool{
|
||||||
|
"go.work": false,
|
||||||
|
"nested/go.work.sum": false,
|
||||||
|
"vendor/nested/link/go.work": false,
|
||||||
|
}
|
||||||
|
if len(failure.Findings) != len(want) {
|
||||||
|
t.Fatalf("workspace findings = %#v, want %d paths", failure.Findings, len(want))
|
||||||
|
}
|
||||||
|
for _, finding := range failure.Findings {
|
||||||
|
if _, ok := want[finding.Path]; !ok {
|
||||||
|
t.Errorf("unexpected workspace path %q", finding.Path)
|
||||||
|
}
|
||||||
|
want[finding.Path] = true
|
||||||
|
}
|
||||||
|
for path, found := range want {
|
||||||
|
if !found {
|
||||||
|
t.Errorf("workspace path %q was not reported", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckWorkspaceFilesAllowsOtherNames(t *testing.T) {
|
||||||
|
tree := &CandidateTree{entries: []git.TreeEntry{
|
||||||
|
{Path: "go.mod", Type: "blob", Mode: "100644"},
|
||||||
|
{Path: "examples/go.work.example", Type: "blob", Mode: "100644"},
|
||||||
|
}}
|
||||||
|
if err := checkWorkspaceFiles(tree); err != nil {
|
||||||
|
t.Fatalf("checkWorkspaceFiles error = %v, want no findings", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunWorkspaceFilesUsesCommittedTreeWithGoWorkspaceDisabled(t *testing.T) {
|
||||||
|
t.Setenv("GOWORK", "off")
|
||||||
|
repository := newTreeRepository(t)
|
||||||
|
writeTreeFile(t, repository, "go.work", []byte("go 1.25.0\n"))
|
||||||
|
writeTreeFile(t, repository, "nested/go.work.sum", []byte("committed workspace metadata\n"))
|
||||||
|
gitTree(t, repository, "add", ".")
|
||||||
|
gitTree(t, repository, "commit", "-m", "capture workspace files")
|
||||||
|
commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD"))
|
||||||
|
writeTreeFile(t, repository, "untracked/go.work.sum", []byte("uncommitted workspace metadata\n"))
|
||||||
|
|
||||||
|
logPath := installGoShim(t, "", false)
|
||||||
|
if err := Run(context.Background(), Request{RepositoryDir: repository, Commit: commit}); err != nil {
|
||||||
|
t.Fatalf("default validation = %v, want workspace check disabled", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logPath = installGoShim(t, "", false)
|
||||||
|
err := Run(context.Background(), Request{
|
||||||
|
RepositoryDir: repository,
|
||||||
|
Commit: commit,
|
||||||
|
Settings: Settings{ModuleDistribution: ModuleDistributionSettings{
|
||||||
|
WorkspaceFiles: true,
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
failure, ok := err.(*Failure)
|
||||||
|
if !ok || failure.Check != WorkspaceFiles {
|
||||||
|
t.Fatalf("selected workspace error = %v, want workspace-files failure", err)
|
||||||
|
}
|
||||||
|
want := map[string]bool{"go.work": false, "nested/go.work.sum": false}
|
||||||
|
if len(failure.Findings) != len(want) {
|
||||||
|
t.Fatalf("workspace findings = %#v, want committed paths only", failure.Findings)
|
||||||
|
}
|
||||||
|
for _, finding := range failure.Findings {
|
||||||
|
if _, ok := want[finding.Path]; !ok {
|
||||||
|
t.Errorf("unexpected workspace path %q", finding.Path)
|
||||||
|
}
|
||||||
|
want[finding.Path] = true
|
||||||
|
}
|
||||||
|
for path, found := range want {
|
||||||
|
if !found {
|
||||||
|
t.Errorf("committed workspace path %q was not reported", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := strings.Join(readGoShimLog(t, logPath), "\n"); !strings.Contains(got, "test -mod=readonly -count=1 ./...") {
|
||||||
|
t.Fatalf("Go commands = %q, want ordinary test suite with GOWORK=off", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ValidationCheck identifies a shared validation check that failed.
|
// ValidationCheck identifies a shared validation check that failed.
|
||||||
// Current checks include "go-test", "go-vet", "package-compile", and
|
// Current checks include "go-test", "go-vet", "package-compile", "go-format",
|
||||||
// "go-format".
|
// and "workspace-files".
|
||||||
type ValidationCheck string
|
type ValidationCheck string
|
||||||
|
|
||||||
// ValidationFinding describes a failed check observation at an optional
|
// ValidationFinding describes a failed check observation at an optional
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
|||||||
Validation: ValidationSpec{Go: GoValidationSpec{
|
Validation: ValidationSpec{Go: GoValidationSpec{
|
||||||
Race: true, FullVet: true,
|
Race: true, FullVet: true,
|
||||||
CompileTargets: []Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}},
|
CompileTargets: []Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}},
|
||||||
}, GoFormat: true},
|
}, GoFormat: true, ModuleDistribution: ModuleDistributionValidationSpec{WorkspaceFiles: true}},
|
||||||
})
|
})
|
||||||
if !request.Validation.Race || !request.Validation.FullVet || !request.Validation.GoFormat {
|
if !request.Validation.Race || !request.Validation.FullVet || !request.Validation.GoFormat || !request.Validation.ModuleDistribution.WorkspaceFiles {
|
||||||
t.Fatalf("candidate request validation = %#v, want race, full vet, and formatting enabled", request.Validation)
|
t.Fatalf("candidate request validation = %#v, want race, full vet, formatting, and workspace inspection enabled", request.Validation)
|
||||||
}
|
}
|
||||||
want := []validation.Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}}
|
want := []validation.Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}}
|
||||||
if len(request.Validation.CompileTargets) != len(want) {
|
if len(request.Validation.CompileTargets) != len(want) {
|
||||||
@@ -61,3 +61,20 @@ func TestValidationErrorPreservesGoFormatIdentityAndFindings(t *testing.T) {
|
|||||||
t.Fatalf("converted error = %v, want parser cause preserved", got)
|
t.Fatalf("converted error = %v, want parser cause preserved", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidationErrorPreservesWorkspaceFilesIdentity(t *testing.T) {
|
||||||
|
got := convertValidationError(&validation.Failure{
|
||||||
|
Check: validation.WorkspaceFiles,
|
||||||
|
Findings: []validation.Finding{{
|
||||||
|
Path: "nested/go.work.sum",
|
||||||
|
Message: "workspace file is committed in the candidate",
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
var validationError *ValidationError
|
||||||
|
if !errors.As(got, &validationError) || validationError.Check != "workspace-files" {
|
||||||
|
t.Fatalf("converted error = %#v, want workspace-files validation identity", got)
|
||||||
|
}
|
||||||
|
if len(validationError.Findings) != 1 || validationError.Findings[0].Path != "nested/go.work.sum" {
|
||||||
|
t.Fatalf("converted findings = %#v, want nested workspace path", validationError.Findings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user