Expose Markdown validation selection
This commit is contained in:
@@ -432,6 +432,9 @@ func TestBuildCancellationReturnsCompletedArtifacts(t *testing.T) {
|
||||
|
||||
func TestCheckBuildsAfterValidationAndKeepsOutputOutsideWorktree(t *testing.T) {
|
||||
repository := newPublishFixture(t)
|
||||
writeFile(t, repository.work, "guide.md", "# Guide\n[section](#guide)\n")
|
||||
gitRun(t, repository.work, "add", "guide.md")
|
||||
gitRun(t, repository.work, "commit", "-m", "add Markdown guide")
|
||||
prepareCandidateBuild(t, repository.work, `package main
|
||||
import (
|
||||
"fmt"
|
||||
@@ -445,6 +448,7 @@ func main() { if len(os.Args) == 2 && os.Args[1] == "version" { fmt.Printf("fixt
|
||||
RepositoryDir: repository.work,
|
||||
Product: "Sample App",
|
||||
Version: "v1.2.3",
|
||||
Validation: releasekit.ValidationSpec{Markdown: &releasekit.MarkdownValidationSpec{}},
|
||||
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
|
||||
validations++
|
||||
return nil
|
||||
|
||||
28
check.go
28
check.go
@@ -52,6 +52,10 @@ type ValidationSpec struct {
|
||||
// GoFormat checks the formatting of committed regular Go source files. It
|
||||
// does not modify repository contents and is disabled when false.
|
||||
GoFormat bool
|
||||
// Markdown selects committed Markdown destination and fragment checking.
|
||||
// Nil disables the check; an empty object enables it with the default
|
||||
// behavior, reporting outside-checkout destinations as out of scope.
|
||||
Markdown *MarkdownValidationSpec
|
||||
// ModuleDistribution selects checks for files that affect module-source
|
||||
// distribution. Its zero value disables those checks.
|
||||
ModuleDistribution ModuleDistributionValidationSpec
|
||||
@@ -60,6 +64,13 @@ type ValidationSpec struct {
|
||||
FileSize *FileSizeValidationSpec
|
||||
}
|
||||
|
||||
// MarkdownValidationSpec selects behavior for committed Markdown links.
|
||||
type MarkdownValidationSpec struct {
|
||||
// EnforceCheckoutBoundary fails when a local destination resolves outside
|
||||
// the candidate checkout. Submodule contents remain out of scope.
|
||||
EnforceCheckoutBoundary bool
|
||||
}
|
||||
|
||||
// FileSizeValidationSpec selects a maximum size for each committed blob.
|
||||
type FileSizeValidationSpec struct {
|
||||
// LimitBytes is the maximum decimal byte count. Nil selects 1,000,000;
|
||||
@@ -107,14 +118,19 @@ type GoValidationSpec struct {
|
||||
// Validation.GoFormat checks regular committed Go source after those selected
|
||||
// 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.
|
||||
// files after formatting. Its zero value disables that distribution check and
|
||||
// does not change Go's workspace environment.
|
||||
// Validation.ModuleDistribution.LocalReplacements parses committed go.mod
|
||||
// declarations, including nested modules, and rejects filesystem replacements
|
||||
// after workspace-file inspection. Its zero value disables that check.
|
||||
// Validation.FileSize checks each selected committed blob after module
|
||||
// distribution checks. Its nil value disables size checks; an empty object
|
||||
// enables the default limit.
|
||||
// Validation.Markdown checks committed local Markdown destinations and
|
||||
// supported fragments after file-size inspection and before AdditionalChecks.
|
||||
// Nil disables the check; an empty object enables it. Outside-checkout
|
||||
// destinations are reported as out of scope unless the selected specification
|
||||
// enforces the checkout boundary. Submodule contents remain out of scope.
|
||||
// The zero values of all selections preserve the ordinary suite. If
|
||||
// Build selects targets, Check builds them after validation in temporary
|
||||
// storage outside the worktree, then rechecks
|
||||
@@ -143,6 +159,7 @@ func candidateRequest(request ReleaseRequest) candidate.Request {
|
||||
FullVet: request.Validation.Go.FullVet,
|
||||
CompileTargets: make([]validation.Target, len(request.Validation.Go.CompileTargets)),
|
||||
GoFormat: request.Validation.GoFormat,
|
||||
Markdown: internalMarkdownSettings(request.Validation.Markdown),
|
||||
ModuleDistribution: validation.ModuleDistributionSettings{
|
||||
WorkspaceFiles: request.Validation.ModuleDistribution.WorkspaceFiles,
|
||||
LocalReplacements: request.Validation.ModuleDistribution.LocalReplacements,
|
||||
@@ -159,6 +176,13 @@ func candidateRequest(request ReleaseRequest) candidate.Request {
|
||||
return result
|
||||
}
|
||||
|
||||
func internalMarkdownSettings(spec *MarkdownValidationSpec) *validation.MarkdownSettings {
|
||||
if spec == nil {
|
||||
return nil
|
||||
}
|
||||
return &validation.MarkdownSettings{EnforceCheckoutBoundary: spec.EnforceCheckoutBoundary}
|
||||
}
|
||||
|
||||
func internalFileSizeSettings(spec *FileSizeValidationSpec) *validation.FileSizeSettings {
|
||||
if spec == nil {
|
||||
return nil
|
||||
|
||||
@@ -130,12 +130,20 @@ type ValidationSettings struct {
|
||||
// GoFormat checks the formatting of committed regular Go source files.
|
||||
// Omitted or false disables the check.
|
||||
GoFormat bool `json:"go_format,omitempty"`
|
||||
Markdown *MarkdownSettings `json:"markdown,omitempty"`
|
||||
ModuleDistribution ModuleDistributionSettings `json:"module_distribution,omitempty"`
|
||||
// FileSize checks the size of each committed blob. Null or omission disables
|
||||
// the check; an empty object enables the default limit.
|
||||
FileSize *FileSizeSettings `json:"file_size,omitempty"`
|
||||
}
|
||||
|
||||
// MarkdownSettings selects committed Markdown destination and fragment checks.
|
||||
type MarkdownSettings struct {
|
||||
// EnforceCheckoutBoundary fails local destinations that resolve outside
|
||||
// the candidate checkout. Submodule contents remain out of scope.
|
||||
EnforceCheckoutBoundary bool `json:"enforce_checkout_boundary,omitempty"`
|
||||
}
|
||||
|
||||
// FileSizeSettings selects the maximum committed-file size and literal exclusions.
|
||||
type FileSizeSettings struct {
|
||||
LimitBytes *int64 `json:"limit_bytes,omitempty"`
|
||||
|
||||
@@ -144,7 +144,7 @@ func TestLoadSettingsDefaultsRemoteAndConfigPath(t *testing.T) {
|
||||
func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
||||
repoDir := t.TempDir()
|
||||
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,"module_distribution":{"workspace_files":true,"local_replacements":true},"file_size":{"limit_bytes":0,"exclusions":["assets"]},"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,"markdown":{"enforce_checkout_boundary":true},"module_distribution":{"workspace_files":true,"local_replacements":true},"file_size":{"limit_bytes":0,"exclusions":["assets"]},"go":{"race":true,"full_vet":true,"compile_targets":["linux/amd64","linux/arm64","darwin/arm64"]}}}`), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if settings.Validation == nil || !settings.Validation.GoFormat || !settings.Validation.ModuleDistribution.WorkspaceFiles || !settings.Validation.ModuleDistribution.LocalReplacements || !settings.Validation.Go.Race || !settings.Validation.Go.FullVet {
|
||||
if settings.Validation == nil || !settings.Validation.GoFormat || settings.Validation.Markdown == nil || !settings.Validation.Markdown.EnforceCheckoutBoundary || !settings.Validation.ModuleDistribution.WorkspaceFiles || !settings.Validation.ModuleDistribution.LocalReplacements || !settings.Validation.Go.Race || !settings.Validation.Go.FullVet {
|
||||
t.Fatalf("validation settings = %#v, want Go formatting, workspace inspection, race, and full vet enabled", settings.Validation)
|
||||
}
|
||||
if settings.Validation.FileSize == nil || settings.Validation.FileSize.LimitBytes == nil || *settings.Validation.FileSize.LimitBytes != 0 || len(settings.Validation.FileSize.Exclusions) != 1 || settings.Validation.FileSize.Exclusions[0] != "assets" {
|
||||
@@ -166,6 +166,35 @@ func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettingsPreservesMarkdownObjectSelection(t *testing.T) {
|
||||
repoDir := t.TempDir()
|
||||
profilePath := filepath.Join(repoDir, "releasekit.json")
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
profile string
|
||||
want bool
|
||||
}{
|
||||
{name: "omitted", profile: `{"product":"Example","validation":{}}`},
|
||||
{name: "null", profile: `{"product":"Example","validation":{"markdown":null}}`},
|
||||
{name: "empty object", profile: `{"product":"Example","validation":{"markdown":{}}}`, want: true},
|
||||
{name: "boundary enabled", profile: `{"product":"Example","validation":{"markdown":{"enforce_checkout_boundary":true}}}`, want: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if err := os.WriteFile(profilePath, []byte(test.profile), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
settings, err := LoadSettings(repoDir, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := settings.Validation != nil && settings.Validation.Markdown != nil
|
||||
if got != test.want {
|
||||
t.Fatalf("Markdown selected = %t, want %t", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettingsPreservesEmptyAndNullFileSize(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
|
||||
@@ -172,6 +172,11 @@ func validationSpec(settings *cli.ValidationSettings) releasekit.ValidationSpec
|
||||
CompileTargets: make([]releasekit.Target, len(settings.Go.CompileTargets)),
|
||||
},
|
||||
}
|
||||
if settings.Markdown != nil {
|
||||
result.Markdown = &releasekit.MarkdownValidationSpec{
|
||||
EnforceCheckoutBoundary: settings.Markdown.EnforceCheckoutBoundary,
|
||||
}
|
||||
}
|
||||
if settings.FileSize != nil {
|
||||
var limitBytes *int64
|
||||
if settings.FileSize.LimitBytes != nil {
|
||||
|
||||
@@ -119,6 +119,7 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
||||
ExpectedPushURL: "publication-only value",
|
||||
Validation: &cli.ValidationSettings{
|
||||
GoFormat: true,
|
||||
Markdown: &cli.MarkdownSettings{EnforceCheckoutBoundary: true},
|
||||
Go: cli.GoValidationSettings{Race: true, FullVet: true, CompileTargets: []string{"linux/amd64", "darwin/arm64"}},
|
||||
ModuleDistribution: cli.ModuleDistributionSettings{WorkspaceFiles: true, LocalReplacements: true},
|
||||
FileSize: &cli.FileSizeSettings{LimitBytes: &limit, Exclusions: []string{"cache"}},
|
||||
@@ -130,6 +131,9 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
||||
if !request.Validation.GoFormat {
|
||||
t.Fatalf("candidate request GoFormat = false, want true")
|
||||
}
|
||||
if request.Validation.Markdown == nil || !request.Validation.Markdown.EnforceCheckoutBoundary {
|
||||
t.Fatalf("candidate request Markdown = %#v, want boundary enforcement enabled", request.Validation.Markdown)
|
||||
}
|
||||
if !request.Validation.ModuleDistribution.WorkspaceFiles || !request.Validation.ModuleDistribution.LocalReplacements {
|
||||
t.Fatalf("candidate request module distribution = %#v, want both checks enabled", request.Validation.ModuleDistribution)
|
||||
}
|
||||
@@ -150,6 +154,58 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidationSpecPreservesMarkdownObjectSelection(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
settings *cli.MarkdownSettings
|
||||
want bool
|
||||
}{
|
||||
{name: "omitted"},
|
||||
{name: "empty object", settings: &cli.MarkdownSettings{}, want: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := validationSpec(&cli.ValidationSettings{Markdown: test.settings}).Markdown != nil
|
||||
if got != test.want {
|
||||
t.Fatalf("Markdown selected = %t, want %t", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidateCommandsRunMarkdownProfile(t *testing.T) {
|
||||
repository := newCLIRepository(t)
|
||||
profile, err := json.Marshal(cli.Settings{
|
||||
Product: "Example Product",
|
||||
Validation: &cli.ValidationSettings{
|
||||
Markdown: &cli.MarkdownSettings{},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeRepositoryFile(t, repository, "releasekit.json", string(profile))
|
||||
writeRepositoryFile(t, repository, "guide.md", "[missing](missing.md#top)\n")
|
||||
cliGitRun(t, repository, "add", "releasekit.json", "guide.md")
|
||||
cliGitRun(t, repository, "commit", "-m", "configure Markdown candidate validation")
|
||||
remote := filepath.Join(t.TempDir(), "origin.git")
|
||||
cliGitRun(t, repository, "init", "--bare", remote)
|
||||
cliGitRun(t, repository, "remote", "add", "origin", remote)
|
||||
cliGitRun(t, repository, "push", "origin", "HEAD:refs/heads/main")
|
||||
|
||||
for _, command := range []string{"check", "publish"} {
|
||||
t.Run(command, func(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
args := []string{command, "--repo", repository, "v1.2.3"}
|
||||
if status := run(context.Background(), args, &stdout, &stderr); status != 1 {
|
||||
t.Fatalf("run() status = %d; stdout=%q stderr=%q, want validation failure", status, stdout.String(), stderr.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "markdown-links") || !strings.Contains(stderr.String(), "guide.md:1:") || !strings.Contains(stderr.String(), "missing.md#top") {
|
||||
t.Fatalf("stderr = %q, want Markdown identity, source location, and target", stderr.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidateCommandsPresentValidationDiagnosticsOnce(t *testing.T) {
|
||||
repository := newCLIRepository(t)
|
||||
profile, err := json.Marshal(cli.Settings{Product: "Example Product"})
|
||||
|
||||
18
docs/cli.md
18
docs/cli.md
@@ -44,6 +44,14 @@ modifying files. When `validation.module_distribution.workspace_files` is true,
|
||||
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.
|
||||
When `validation.markdown` is a non-null object, `check` checks committed local
|
||||
Markdown destinations and supported heading fragments after file-size
|
||||
inspection. An empty object enables the check; omission or `null` disables it.
|
||||
Outside-checkout links are written as out-of-scope observations to standard
|
||||
error by default. Setting `validation.markdown.enforce_checkout_boundary` to
|
||||
true makes those links fail; submodule-content observations remain
|
||||
nonfailures. Failures use the `markdown-links` identity with the source location
|
||||
and target.
|
||||
|
||||
`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,
|
||||
@@ -64,8 +72,9 @@ 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,
|
||||
test-time vet is disabled and `go vet -mod=readonly ./...` runs after successful
|
||||
tests. Selected package compilation and committed Go formatting follow. The
|
||||
selected workspace-file check then runs, followed by configured checks in
|
||||
profile order. It then creates an annotated local tag
|
||||
selected file checks then run in order: workspace files, local replacements,
|
||||
file size, and Markdown. Configured checks run afterward in profile order. It
|
||||
then creates an annotated local tag
|
||||
and pushes only its exact tag object. It prints the confirmed version,
|
||||
candidate commit, and tag object
|
||||
on success. Validation
|
||||
@@ -97,6 +106,11 @@ disables the check; an empty object enables the 1,000,000 decimal-byte default.
|
||||
Oversized files use the `file-size` identity and report their path and measured
|
||||
size against the limit. See the [configuration reference](config.md) for
|
||||
literal exclusions and the current-candidate scope.
|
||||
When `validation.markdown` is a non-null object, `publish` checks committed
|
||||
Markdown destinations after file-size inspection and before configured checks.
|
||||
It reports outside-checkout links to standard error unless boundary enforcement
|
||||
is selected; submodule contents remain out of scope. Markdown failures stop
|
||||
publication before product checks or tag creation.
|
||||
|
||||
The optional profile field `expected_push_url` applies only to `publish`. A
|
||||
nonempty value must exactly match the effective push URL resolved by Git;
|
||||
|
||||
@@ -46,6 +46,26 @@ 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
|
||||
compilation, before configured product checks.
|
||||
|
||||
When `validation.markdown` is an object, ReleaseKit checks local destinations
|
||||
and supported heading fragments in committed regular Markdown files. Omission
|
||||
or `null` disables the check; `{}` enables it. Schemed and protocol-relative
|
||||
links are skipped, URI query components do not affect path checks, and other
|
||||
existing files or directories receive existence checks only. Missing local
|
||||
destinations, symlink cycles, and missing Markdown headings fail with the
|
||||
`markdown-links` identity. Outside-checkout destinations are reported as out
|
||||
of scope by default. Set
|
||||
`validation.markdown.enforce_checkout_boundary` to `true` to fail those links;
|
||||
submodule contents remain out of scope under either setting.
|
||||
|
||||
The parser recognizes inline links, images, reference links, and CommonMark
|
||||
autolinks; link-like text in code is ignored, and raw HTML destinations are
|
||||
outside this check. Relative paths resolve from the source document in the
|
||||
captured commit. Query components are ignored for file existence, and percent
|
||||
escapes in local paths and fragments are decoded once. Fragments are compared
|
||||
in lowercase with anchors derived from visible ATX or Setext heading text.
|
||||
Anchors keep Unicode letters and numbers, underscores, and hyphens; spaces
|
||||
become hyphens, and repeated headings receive numeric suffixes.
|
||||
|
||||
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
|
||||
@@ -71,6 +91,7 @@ For example:
|
||||
"compile_targets": ["linux/amd64", "linux/arm64"]
|
||||
},
|
||||
"go_format": true,
|
||||
"markdown": {},
|
||||
"module_distribution": {
|
||||
"workspace_files": true,
|
||||
"local_replacements": true
|
||||
@@ -130,6 +151,10 @@ preserve their causes.
|
||||
When `validation.file_size` is selected, shared validation checks committed
|
||||
blob sizes after module-distribution checks and reports the first oversized
|
||||
path with the `file-size` identity and measured and limit byte counts.
|
||||
When `validation.markdown` is selected, Markdown checking follows file-size
|
||||
inspection and runs before configured product checks. Out-of-scope observations
|
||||
are written to the command's standard error and do not fail validation unless
|
||||
the checkout boundary is enforced. Submodule observations remain nonfailures.
|
||||
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.
|
||||
|
||||
@@ -62,6 +62,20 @@ directories; excluded directories include descendants. Symlink blobs are
|
||||
measured by link-text bytes, and gitlinks are omitted. The check applies to the
|
||||
captured candidate and reports one oversized file with its measurement as
|
||||
`file-size`; it does not measure historical or aggregate size.
|
||||
Set `Validation.Markdown` to check committed local Markdown destinations and
|
||||
supported heading fragments. Nil disables the check; an empty object enables
|
||||
it with outside-checkout destinations reported as out of scope. Set
|
||||
`EnforceCheckoutBoundary` to fail outside-checkout links. Submodule contents
|
||||
remain out of scope under either setting. The checker runs after file-size
|
||||
inspection and before `AdditionalChecks`; its nonfailure observations use the
|
||||
request's output writer. Markdown failures use the `markdown-links` identity
|
||||
and include the source location and destination.
|
||||
|
||||
Use these selections for ReleaseKit-owned validation rather than repeating the
|
||||
standard suite, selected vet/format/distribution checks, or Markdown navigation
|
||||
checks in `AdditionalChecks` or profile `checks`. Keep release-note rules and
|
||||
consumer-specific assertions—such as tests of maintained examples—in those
|
||||
product-owned checks.
|
||||
ReleaseKit calls `AdditionalChecks` once after shared validation when supplied.
|
||||
It does not download a different toolchain automatically. If race prerequisites
|
||||
are unavailable, the `go-test` check fails rather than being skipped; a selected
|
||||
|
||||
@@ -78,6 +78,19 @@ blob size is its link text; gitlinks are omitted. This check measures only the
|
||||
captured candidate and reports one oversized file with its measurement as
|
||||
`file-size`; it stops publication before product checks or tag creation.
|
||||
|
||||
Set `Validation.Markdown` to check committed local Markdown destinations and
|
||||
supported heading fragments during publication. Nil disables the check; an
|
||||
empty object enables it with outside-checkout links reported as out of scope.
|
||||
Set `EnforceCheckoutBoundary` to fail those links. Submodule contents remain
|
||||
out of scope under either setting. Observations use the request's output writer;
|
||||
failures use `markdown-links` and stop publication before product checks or tag
|
||||
creation.
|
||||
|
||||
ReleaseKit-owned selections replace duplicate generic suite, vet, formatting,
|
||||
distribution, and Markdown checks in configured product checks. Keep
|
||||
release-note rules and consumer-specific assertions, such as maintained-example
|
||||
tests, in `AdditionalChecks` or profile `checks`.
|
||||
|
||||
If `Build` selects targets, ReleaseKit verifies them after the standard suite
|
||||
and additional checks, before creating the tag. The operation then rechecks the
|
||||
candidate and remote guards, creates an annotated tag for the captured commit,
|
||||
|
||||
@@ -46,7 +46,7 @@ reports these implemented packages:
|
||||
| `internal/tempdir` | Disposable system temporary storage outside source worktrees. |
|
||||
| `internal/process` | Cancellable child-process execution with per-command environment overlays and removals. |
|
||||
| `internal/preparation` | Coordination for expected-commit tag checkout and Go module dependency preparation. |
|
||||
| `internal/validation` | Shared Go validation with optional race, full-vet, and static package-compilation target selections, formatting, workspace-file and filesystem-replacement checks, per-blob size limits, internal Markdown parsing, destination and fragment checking, and per-document anchor caching; `internal/candidate` retains capture, callback sequencing, and local-state rechecks. |
|
||||
| `internal/validation` | Shared Go validation with optional race, full-vet, and static package-compilation target selections, formatting, workspace-file and filesystem-replacement checks, per-blob size limits, and selected Markdown destination and fragment checking; `internal/candidate` retains capture, callback sequencing, and local-state rechecks. |
|
||||
| `internal/publish` | Publication guards, one validation run, annotated tag creation, push, and recovery state. |
|
||||
| `internal/runtimeinfo` | Private version lookup used by the runtime facade. |
|
||||
| `internal/versioncontract` | Shared stable-version rules and linker-symbol identity. |
|
||||
@@ -78,8 +78,8 @@ not a separate release matrix:
|
||||
| Capability | Implementation owner | Existing evidence |
|
||||
| --- | --- | --- |
|
||||
| Runtime version lookup and injected application version | `buildinfo`, `internal/runtimeinfo`, `internal/versioncontract`, `internal/build` | `TestVersionInConsumerExecutable`, `TestVersion` |
|
||||
| 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`, `TestLoadSettingsReadsGoValidation`, `TestCheckUsesSelectedRaceSuiteBeforeAdditionalChecks`, `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestCheckReturnsPublicValidationError`, `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation`, `TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate`, `TestPublishCommandPublishesConfiguredCandidate`, `TestCandidateCommandsPresentValidationDiagnosticsOnce`, `TestDeliverPageOnlyCreatesAndReusesRelease` |
|
||||
| Internal Markdown parsing, committed destinations, fragments, boundary observations, and causes | `internal/validation` | `TestParseMarkdownLinksAndSourceLocations`, `TestParseMarkdownDecodesEntitiesInDestinations`, `TestParseMarkdownHeadingAnchors`, `TestMarkdownCacheKeepsParsedDocumentsPerCandidatePath`, `TestCheckMarkdownLinksResolvesCommittedDestinationsAndFragments`, `TestCheckMarkdownLinksReportsMissingPathsAnchorsAndCycles`, `TestCheckMarkdownLinksUsesResolvedMarkdownIdentity`, `TestCheckMarkdownLinksReportsBoundaryAndSubmoduleObservations`, `TestCheckMarkdownLinksPreservesResolverCauseAndCancellation`, `TestCheckMarkdownLinksPreservesAnchorReadCause` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishRejectsUnexpectedPushURLBeforeValidation`, `TestPublishComparesExpectedURLWithResolvedInsteadOfDestination`, `TestPublishExpectedPushURLUsesPushURLWhenFetchAndPushDiffer`, `TestCheckIgnoresExpectedPushURL`, `TestResolveUsesPushURLAndQueriesWithoutMutation` |
|
||||
| Publication partial and uncertain recovery | `internal/publish`, `internal/git` | `TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished`, `TestPublishLostAcknowledgmentConfirmsExactRemoteObject`, `TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown`, `TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError` |
|
||||
|
||||
@@ -41,6 +41,10 @@ The optional file-size check uses each blob's captured Git size, including
|
||||
symlink link-text bytes, and skips entries without a blob size such as gitlinks.
|
||||
It applies normalized literal file/directory exclusions with path-boundary
|
||||
matching, then returns the first oversized path with `FileSizeMeasurement`.
|
||||
The optional Markdown check follows file-size inspection and uses the same
|
||||
candidate tree. It parses regular Markdown blobs, resolves local destinations,
|
||||
and checks supported anchors; outside-checkout and submodule observations are
|
||||
written to the shared output writer. A nil writer keeps library use quiet.
|
||||
`CandidateTree` gets metadata and bytes from Git objects, reads blob bytes
|
||||
lazily, and resolves destinations without consulting the worktree or
|
||||
neighboring files.
|
||||
@@ -61,8 +65,8 @@ paths, symlink cycles, and missing anchors produce `markdown-links` findings at
|
||||
the source use site. Outside-checkout links produce out-of-scope observations by
|
||||
default and findings when boundary enforcement is selected. Submodule-content
|
||||
observations remain out of scope under either setting. Observations go to the
|
||||
provided writer; a nil writer keeps library use quiet. Public selection remains
|
||||
in the [implementation plan](../roadmap/implementation.md).
|
||||
provided writer; a nil writer keeps library use quiet. Public selection is
|
||||
defined in the [configuration reference](../config.md) and GoDoc.
|
||||
|
||||
Committed-tree metadata and blob reads disable lazy fetching and Git transports
|
||||
for those child commands. Missing local objects produce inspection errors;
|
||||
@@ -136,6 +140,7 @@ symlink expansion detects cycles, and traversal honors cancellation. Absolute
|
||||
destinations and symlink targets within the repository directory map back to
|
||||
candidate-relative paths; existence still comes from committed entries.
|
||||
|
||||
The [implementation plan](../roadmap/implementation.md) records the remaining
|
||||
public Markdown selection and integration work. The internal checker uses the
|
||||
committed-file and destination-resolution primitives described here.
|
||||
The [implementation plan](../roadmap/implementation.md) records the completed
|
||||
Markdown integration. The public selection is owned by the facade and profile;
|
||||
the internal checker uses the committed-file and destination-resolution
|
||||
primitives described here.
|
||||
|
||||
@@ -446,8 +446,8 @@ Undefined references and HTML prose remain governed by parser interpretation.
|
||||
|
||||
**Completion:** Complete. The internal checker resolves committed destinations,
|
||||
checks supported fragments, preserves inspection errors, and reports distinct
|
||||
outside-checkout and submodule observations. Public selection and shared executor
|
||||
wiring remain in Stage 11.
|
||||
outside-checkout and submodule observations. Stage 11 exposes this checker
|
||||
through public and profile selections and the shared executor.
|
||||
|
||||
**Outcome:** Committed Markdown navigation is checked with boundary observations.
|
||||
|
||||
@@ -477,6 +477,14 @@ missing-object, and traversal-cancellation cases.
|
||||
|
||||
## Stage 11: Expose Markdown selection and complete validation integration
|
||||
|
||||
**Completion:** Complete. `ValidationSpec.Markdown` and
|
||||
`validation.markdown` select the shared committed-link checker through both
|
||||
public operations and CLI profile commands. Omitted/null settings disable the
|
||||
check; an empty object enables it, and the boundary option maps explicitly.
|
||||
Warnings use the shared output writer, while Markdown failures stop product
|
||||
checks and builds. GoDoc, profile, consumer, CLI, and internal documentation
|
||||
now describe the operational behavior.
|
||||
|
||||
**Outcome:** All agreed validation selections work through both public operations.
|
||||
|
||||
1. Add `MarkdownValidationSpec`, `ValidationSpec.Markdown`, and their JSON fields.
|
||||
|
||||
@@ -45,12 +45,16 @@ func TestValidStableVersion(t *testing.T) {
|
||||
|
||||
func TestCheckCapturesCandidateAndCallsAdditionalChecksOnce(t *testing.T) {
|
||||
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
|
||||
writeTestFile(t, repository, "guide.md", "# Guide\n[section](#guide)\n")
|
||||
gitRun(t, repository, "add", "guide.md")
|
||||
gitRun(t, repository, "commit", "-m", "add Markdown guide")
|
||||
calls := 0
|
||||
var seen Candidate
|
||||
candidate, err := Check(context.Background(), Request{
|
||||
RepositoryDir: repository,
|
||||
Product: "Example Product",
|
||||
Version: testVersion,
|
||||
Validation: validation.Settings{Markdown: &validation.MarkdownSettings{}},
|
||||
AdditionalChecks: func(_ context.Context, captured Candidate) error {
|
||||
calls++
|
||||
seen = captured
|
||||
@@ -108,6 +112,38 @@ func TestCheckStopsBeforeProductAndBuildCallbacksWhenGoFormatFails(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckStopsBeforeProductAndBuildCallbacksWhenMarkdownBoundaryFails(t *testing.T) {
|
||||
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
|
||||
writeTestFile(t, repository, "guide.md", "[outside](../../outside.md)\n")
|
||||
gitRun(t, repository, "add", "guide.md")
|
||||
gitRun(t, repository, "commit", "-m", "add outside Markdown destination")
|
||||
productCalls := 0
|
||||
buildCalls := 0
|
||||
_, err := Check(context.Background(), Request{
|
||||
RepositoryDir: repository,
|
||||
Product: "Example Product",
|
||||
Version: testVersion,
|
||||
Validation: validation.Settings{
|
||||
Markdown: &validation.MarkdownSettings{EnforceCheckoutBoundary: true},
|
||||
},
|
||||
AdditionalChecks: func(context.Context, Candidate) error {
|
||||
productCalls++
|
||||
return nil
|
||||
},
|
||||
AfterValidate: func(context.Context, Candidate) error {
|
||||
buildCalls++
|
||||
return nil
|
||||
},
|
||||
})
|
||||
var failure *validation.Failure
|
||||
if !errors.As(err, &failure) || failure.Check != validation.MarkdownLinks {
|
||||
t.Fatalf("Check() error = %v, want markdown-links failure", err)
|
||||
}
|
||||
if productCalls != 0 || buildCalls != 0 {
|
||||
t.Fatalf("product callback calls = %d, build callback calls = %d; want neither after Markdown failure", productCalls, buildCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckReportsCommittedWorkspaceFilesWhenGoWorkspaceIsDisabled(t *testing.T) {
|
||||
t.Setenv("GOWORK", "off")
|
||||
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
|
||||
|
||||
@@ -192,6 +192,49 @@ func TestCheckMarkdownLinksPreservesAnchorReadCause(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSelectsMarkdownWithCombinedGoChecksAndReportsOutOfScopeLinks(t *testing.T) {
|
||||
repository, _ := committedMarkdownTree(t, map[string][]byte{
|
||||
"guide.md": []byte("[outside](../../outside.md)\n"),
|
||||
})
|
||||
commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD"))
|
||||
goLog := installGoShim(t, "", false)
|
||||
var output bytes.Buffer
|
||||
err := Run(context.Background(), Request{
|
||||
RepositoryDir: repository,
|
||||
Commit: commit,
|
||||
Output: &output,
|
||||
Settings: Settings{
|
||||
Race: true,
|
||||
FullVet: true,
|
||||
CompileTargets: []Target{{OS: "linux", Arch: "amd64"}},
|
||||
Markdown: &MarkdownSettings{},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v, want out-of-scope observation to succeed", err)
|
||||
}
|
||||
if got := output.String(); !strings.Contains(got, "outside the checkout") || !strings.Contains(got, "out of scope") {
|
||||
t.Fatalf("Run() output = %q, want outside-checkout observation", got)
|
||||
}
|
||||
commands := readGoShimLog(t, goLog)
|
||||
var testCommands []string
|
||||
var validationCommands []string
|
||||
for _, command := range commands {
|
||||
if strings.HasPrefix(command, "test ") {
|
||||
testCommands = append(testCommands, command)
|
||||
}
|
||||
if strings.HasPrefix(command, "test ") || strings.HasPrefix(command, "vet ") || strings.HasPrefix(command, "build ") {
|
||||
validationCommands = append(validationCommands, command)
|
||||
}
|
||||
}
|
||||
if len(testCommands) != 1 || !strings.Contains(testCommands[0], "-race") || !strings.Contains(testCommands[0], "-vet=off") {
|
||||
t.Fatalf("Go test commands = %#v, want one race-enabled suite with test-time vet disabled", testCommands)
|
||||
}
|
||||
if len(validationCommands) != 3 || !strings.HasPrefix(validationCommands[0], "test ") || !strings.HasPrefix(validationCommands[1], "vet ") || !strings.HasPrefix(validationCommands[2], "build ") {
|
||||
t.Fatalf("Go validation commands = %#v, want test, full vet, and target compilation before Markdown", validationCommands)
|
||||
}
|
||||
}
|
||||
|
||||
func committedMarkdownTree(t *testing.T, files map[string][]byte) (string, *CandidateTree) {
|
||||
t.Helper()
|
||||
repository := newTreeRepository(t)
|
||||
|
||||
@@ -20,10 +20,16 @@ type Settings struct {
|
||||
FullVet bool
|
||||
CompileTargets []Target
|
||||
GoFormat bool
|
||||
Markdown *MarkdownSettings
|
||||
ModuleDistribution ModuleDistributionSettings
|
||||
FileSize *FileSizeSettings
|
||||
}
|
||||
|
||||
// MarkdownSettings selects committed Markdown destination and fragment checks.
|
||||
type MarkdownSettings struct {
|
||||
EnforceCheckoutBoundary bool
|
||||
}
|
||||
|
||||
// FileSizeSettings selects a per-blob limit and literal path exclusions.
|
||||
type FileSizeSettings struct {
|
||||
LimitBytes *int64
|
||||
@@ -112,10 +118,13 @@ func Run(ctx context.Context, request Request) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if request.Settings.GoFormat || request.Settings.ModuleDistribution.WorkspaceFiles || request.Settings.ModuleDistribution.LocalReplacements || request.Settings.FileSize != nil {
|
||||
if request.Settings.GoFormat || request.Settings.ModuleDistribution.WorkspaceFiles || request.Settings.ModuleDistribution.LocalReplacements || request.Settings.FileSize != nil || request.Settings.Markdown != nil {
|
||||
tree, err := request.ReadCandidateTree(ctx)
|
||||
if err != nil {
|
||||
check := FileSize
|
||||
check := MarkdownLinks
|
||||
if request.Settings.FileSize != nil {
|
||||
check = FileSize
|
||||
}
|
||||
if request.Settings.ModuleDistribution.LocalReplacements {
|
||||
check = LocalReplacements
|
||||
}
|
||||
@@ -147,6 +156,11 @@ func Run(ctx context.Context, request Request) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if request.Settings.Markdown != nil {
|
||||
if err := checkMarkdownLinks(ctx, tree, request.Settings.Markdown.EnforceCheckoutBoundary, request.Output); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
11
publish.go
11
publish.go
@@ -54,9 +54,14 @@ type PublicationResult struct {
|
||||
// Validation.Go.FullVet runs go vet afterward instead of test-time vet, and
|
||||
// Validation.Go.CompileTargets compiles all packages for each selected target
|
||||
// with cgo disabled after tests and vet. Compilation produces no artifacts and
|
||||
// does not execute target binaries. ExpectedPushURL, when nonempty, is compared
|
||||
// exactly with the resolved effective push URL before validation; a mismatch
|
||||
// returns ErrUnexpectedPushURL. It then calls AdditionalChecks when supplied.
|
||||
// does not execute target binaries. The selected formatting, distribution,
|
||||
// file-size, and Markdown checks then run before AdditionalChecks. Markdown is
|
||||
// disabled when nil and enabled by a non-nil specification; outside-checkout
|
||||
// destinations are reported as out of scope unless boundary enforcement is
|
||||
// selected. Submodule contents remain out of scope. ExpectedPushURL, when
|
||||
// nonempty, is compared exactly with the resolved effective push URL before
|
||||
// validation; a mismatch returns ErrUnexpectedPushURL. It then calls
|
||||
// AdditionalChecks when supplied.
|
||||
// It optionally builds selected targets in temporary storage outside the
|
||||
// worktree and pushes the exact annotated tag object to the selected remote.
|
||||
// It returns a result with errors: preflight failures are not_attempted, failed
|
||||
|
||||
@@ -302,6 +302,62 @@ func TestCheckReturnsPublicValidationError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownValidationRunsThroughCheckAndPublish(t *testing.T) {
|
||||
addBrokenMarkdownDestination := func(t *testing.T, repo publishFixture) {
|
||||
t.Helper()
|
||||
writeFile(t, repo.work, "guide.md", "[missing](missing.md#top)\n")
|
||||
gitRun(t, repo.work, "add", "guide.md")
|
||||
gitRun(t, repo.work, "commit", "-m", "add broken Markdown destination")
|
||||
}
|
||||
assertMarkdownFailure := func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
var failure *releasekit.ValidationError
|
||||
if !errors.As(err, &failure) || failure.Check != "markdown-links" {
|
||||
t.Fatalf("operation error = %v, want public markdown-links failure", err)
|
||||
}
|
||||
if len(failure.Findings) != 1 || failure.Findings[0].Path != "guide.md" || failure.Findings[0].Target != "missing.md#top" || failure.Findings[0].Line != 1 {
|
||||
t.Fatalf("Markdown findings = %#v, want source location and target", failure.Findings)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("check", func(t *testing.T) {
|
||||
repo := newPublishFixture(t)
|
||||
addBrokenMarkdownDestination(t, repo)
|
||||
_, err := releasekit.Check(context.Background(), releasekit.ReleaseRequest{
|
||||
RepositoryDir: repo.work,
|
||||
Product: "Sample App",
|
||||
Version: "v1.2.3",
|
||||
Validation: releasekit.ValidationSpec{Markdown: &releasekit.MarkdownValidationSpec{}},
|
||||
})
|
||||
assertMarkdownFailure(t, err)
|
||||
})
|
||||
|
||||
t.Run("publish", func(t *testing.T) {
|
||||
repo := newPublishFixture(t)
|
||||
addBrokenMarkdownDestination(t, repo)
|
||||
gitRun(t, repo.work, "config", "--unset", "remote.origin.mirror")
|
||||
gitRun(t, repo.work, "push", "origin", "HEAD:refs/heads/main")
|
||||
callbackCalls := 0
|
||||
_, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
|
||||
RepositoryDir: repo.work,
|
||||
Product: "Sample App",
|
||||
Version: "v1.2.3",
|
||||
Validation: releasekit.ValidationSpec{Markdown: &releasekit.MarkdownValidationSpec{}},
|
||||
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
|
||||
callbackCalls++
|
||||
return nil
|
||||
},
|
||||
})
|
||||
assertMarkdownFailure(t, err)
|
||||
if callbackCalls != 0 {
|
||||
t.Fatalf("additional checks called %d times after Markdown failure", callbackCalls)
|
||||
}
|
||||
if tag := gitText(t, repo.work, "tag", "--list", "v1.2.3"); tag != "" {
|
||||
t.Fatalf("failed Markdown validation created tag %q", tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation(t *testing.T) {
|
||||
repo := newPublishFixture(t)
|
||||
suiteRuns := filepath.Join(t.TempDir(), "suite-runs")
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// ValidationCheck identifies a shared validation check that failed.
|
||||
// Current checks include "go-test", "go-vet", "package-compile", "go-format",
|
||||
// "workspace-files", "local-replacements", and "file-size".
|
||||
// "workspace-files", "local-replacements", "file-size", and "markdown-links".
|
||||
type ValidationCheck string
|
||||
|
||||
// ValidationFinding describes a failed check observation at an optional
|
||||
|
||||
@@ -13,11 +13,14 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
||||
Validation: ValidationSpec{Go: GoValidationSpec{
|
||||
Race: true, FullVet: true,
|
||||
CompileTargets: []Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}},
|
||||
}, GoFormat: true, ModuleDistribution: ModuleDistributionValidationSpec{WorkspaceFiles: true, LocalReplacements: true}, FileSize: &FileSizeValidationSpec{LimitBytes: &limit, Exclusions: []string{"assets"}}},
|
||||
}, GoFormat: true, Markdown: &MarkdownValidationSpec{EnforceCheckoutBoundary: true}, ModuleDistribution: ModuleDistributionValidationSpec{WorkspaceFiles: true, LocalReplacements: true}, FileSize: &FileSizeValidationSpec{LimitBytes: &limit, Exclusions: []string{"assets"}}},
|
||||
})
|
||||
if !request.Validation.Race || !request.Validation.FullVet || !request.Validation.GoFormat || !request.Validation.ModuleDistribution.WorkspaceFiles || !request.Validation.ModuleDistribution.LocalReplacements {
|
||||
t.Fatalf("candidate request validation = %#v, want race, full vet, formatting, and distribution checks enabled", request.Validation)
|
||||
}
|
||||
if request.Validation.Markdown == nil || !request.Validation.Markdown.EnforceCheckoutBoundary {
|
||||
t.Fatalf("candidate request Markdown = %#v, want boundary enforcement enabled", request.Validation.Markdown)
|
||||
}
|
||||
if request.Validation.FileSize == nil || request.Validation.FileSize.LimitBytes == nil || *request.Validation.FileSize.LimitBytes != limit || len(request.Validation.FileSize.Exclusions) != 1 || request.Validation.FileSize.Exclusions[0] != "assets" {
|
||||
t.Fatalf("candidate request file size = %#v, want configured limit and exclusions", request.Validation.FileSize)
|
||||
}
|
||||
@@ -32,6 +35,24 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidateRequestPreservesMarkdownObjectSelection(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
spec *MarkdownValidationSpec
|
||||
want bool
|
||||
}{
|
||||
{name: "omitted"},
|
||||
{name: "empty object", spec: &MarkdownValidationSpec{}, want: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
request := candidateRequest(ReleaseRequest{Validation: ValidationSpec{Markdown: test.spec}})
|
||||
if got := request.Validation.Markdown != nil; got != test.want {
|
||||
t.Fatalf("Markdown selected = %t, want %t", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidateRequestPreservesFileSizeObjectSelection(t *testing.T) {
|
||||
disabled := candidateRequest(ReleaseRequest{})
|
||||
enabled := candidateRequest(ReleaseRequest{Validation: ValidationSpec{FileSize: &FileSizeValidationSpec{}}})
|
||||
|
||||
Reference in New Issue
Block a user