Make product checks additive

This commit is contained in:
2026-10-06 17:15:46 +00:00
parent 08093124af
commit 3e9b5c1b99
23 changed files with 368 additions and 169 deletions

View File

@@ -21,6 +21,8 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
repo := newPublishFixture(t)
wrapper := installGitWrapper(t, repo, "pass")
t.Setenv("PATH", wrapper.bin+string(os.PathListSeparator)+os.Getenv("PATH"))
suiteRuns := filepath.Join(t.TempDir(), "suite-runs")
t.Setenv("RELEASEKIT_FIXTURE_SUITE_RUNS", suiteRuns)
refsBefore := gitText(t, repo.bare, "show-ref", "--heads", "--tags")
commit := repo.commit
var validations int
@@ -28,8 +30,12 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(_ context.Context, candidate releasekit.Candidate) error {
AdditionalChecks: func(_ context.Context, candidate releasekit.Candidate) error {
validations++
runs, readErr := os.ReadFile(suiteRuns)
if readErr != nil || string(runs) != "ran\n" {
return fmt.Errorf("standard suite runs before additional checks: contents=%q error=%v", runs, readErr)
}
if candidate.RepositoryDir != repo.work || candidate.Commit != commit || candidate.Version != "v1.2.3" {
t.Fatalf("callback candidate = %#v", candidate)
}
@@ -42,6 +48,9 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
if validations != 1 {
t.Fatalf("validation calls = %d, want 1", validations)
}
if runs, err := os.ReadFile(suiteRuns); err != nil || string(runs) != "ran\n" {
t.Fatalf("standard suite runs = %q, error = %v; want one run", runs, err)
}
if queries := countLines(t, wrapper.queryLog); queries != 3 {
t.Fatalf("authoritative tag query count = %d, want 3 guards and no success read-back", queries)
}
@@ -84,6 +93,69 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
}
}
func TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation(t *testing.T) {
repo := newPublishFixture(t)
t.Setenv("RELEASEKIT_FAIL_FIXTURE_SUITE", "1")
var checks int
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
checks++
return nil
},
})
if err == nil || !strings.Contains(err.Error(), "fixture standard suite failure") {
t.Fatalf("Publish() error = %v, want fixture standard suite failure", err)
}
if checks != 0 || result.TagObject != "" || result.State != releasekit.PublicationNotAttempted {
t.Fatalf("failed standard suite result=%#v additional checks=%d", result, checks)
}
for _, command := range [][]string{
{"git", "-C", repo.work, "show-ref", "--verify", "--quiet", "refs/tags/v1.2.3"},
{"git", "--git-dir", repo.bare, "show-ref", "--verify", "--quiet", "refs/tags/v1.2.3"},
} {
if _, err := exec.Command(command[0], command[1:]...).CombinedOutput(); err == nil {
t.Fatalf("failed standard suite created release tag with %v", command)
}
}
}
func TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation(t *testing.T) {
repo := newPublishFixture(t)
suiteRuns := filepath.Join(t.TempDir(), "suite-runs")
t.Setenv("RELEASEKIT_FIXTURE_SUITE_RUNS", suiteRuns)
wantErr := errors.New("additional product check failed")
var checks int
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
checks++
return wantErr
},
})
if !errors.Is(err, wantErr) {
t.Fatalf("Publish() error = %v, want wrapped additional-check error", err)
}
if runs, readErr := os.ReadFile(suiteRuns); readErr != nil || string(runs) != "ran\n" {
t.Fatalf("standard suite runs = %q, error = %v; want one successful run", runs, readErr)
}
if checks != 1 || result.TagObject != "" || result.State != releasekit.PublicationNotAttempted {
t.Fatalf("additional-check failure result=%#v checks=%d", result, checks)
}
for _, command := range [][]string{
{"git", "-C", repo.work, "show-ref", "--verify", "--quiet", "refs/tags/v1.2.3"},
{"git", "--git-dir", repo.bare, "show-ref", "--verify", "--quiet", "refs/tags/v1.2.3"},
} {
if _, err := exec.Command(command[0], command[1:]...).CombinedOutput(); err == nil {
t.Fatalf("additional-check failure created release tag with %v", command)
}
}
}
func TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished(t *testing.T) {
repo := newPublishFixture(t)
wrapper := installGitWrapper(t, repo, "pass")
@@ -93,10 +165,10 @@ func TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished(t *testing.T) {
t.Fatal(err)
}
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error { return nil },
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error { return nil },
})
if err == nil {
t.Fatal("Publish() succeeded despite rejected push hook")
@@ -130,7 +202,7 @@ func TestPublishLostAcknowledgmentConfirmsExactRemoteObject(t *testing.T) {
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error {
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
go func() {
defer close(watcherDone)
for {
@@ -173,10 +245,10 @@ func TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown(t *testing.T
t.Setenv("PATH", wrapper.bin+string(os.PathListSeparator)+os.Getenv("PATH"))
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error { return nil },
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error { return nil },
})
if err == nil || result.State != releasekit.PublicationUnknown || result.Phase != releasekit.PublicationReconciliation {
t.Fatalf("result = %#v, error = %v", result, err)
@@ -216,10 +288,10 @@ func TestPublishConflictingRemoteTagIsNotReportedAsAbsent(t *testing.T) {
t.Fatal(err)
}
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error { return nil },
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error { return nil },
})
if err == nil || result.State != releasekit.PublicationConflict || result.Phase != releasekit.PublicationReconciliation {
t.Fatalf("result = %#v, error = %v", result, err)
@@ -239,10 +311,10 @@ func TestPublishStopsWhenLocalTagChangesBeforePush(t *testing.T) {
wrapper := installGitWrapperWithMutation(t, repo, conflictingObject)
t.Setenv("PATH", wrapper.bin+string(os.PathListSeparator)+os.Getenv("PATH"))
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error { return nil },
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error { return nil },
})
if err == nil || result.State != releasekit.PublicationNotPublished || result.Phase != releasekit.PublicationTagCreation {
t.Fatalf("result = %#v, error = %v", result, err)
@@ -270,10 +342,10 @@ func TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError(t *
t.Fatal(err)
}
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error { return nil },
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error { return nil },
})
if err == nil || result.State != releasekit.PublicationUnknown || result.Phase != releasekit.PublicationReconciliation {
t.Fatalf("result = %#v, error = %v", result, err)
@@ -303,10 +375,10 @@ func TestPublishUninterpretableReconciliationRemainsUnknown(t *testing.T) {
t.Fatal(err)
}
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error { return nil },
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error { return nil },
})
if err == nil || result.State != releasekit.PublicationUnknown || result.Phase != releasekit.PublicationReconciliation {
t.Fatalf("result = %#v, error = %v", result, err)
@@ -330,7 +402,7 @@ func TestPublishRemoteConflictStopsBeforeValidation(t *testing.T) {
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error {
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
validations++
return nil
},
@@ -349,7 +421,7 @@ func TestPublishRemoteChangeDuringValidationPreventsLocalTag(t *testing.T) {
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
Validate: func(context.Context, releasekit.Candidate) error {
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
gitRun(t, repo.bare, "update-ref", "refs/heads/main", repo.unrelatedCommit)
return nil
},
@@ -493,6 +565,33 @@ func newPublishFixture(t *testing.T) publishFixture {
gitRun(t, base, "init", "-b", "main", work)
gitRun(t, work, "config", "user.name", "ReleaseKit Test")
gitRun(t, work, "config", "user.email", "releasekit@example.invalid")
writeFile(t, work, "go.mod", "module example.com/releasekit-publish-fixture\n\ngo 1.25.0\n")
writeFile(t, work, "fixture_test.go", `package fixture
import (
"os"
"testing"
)
func TestFixture(t *testing.T) {
if path := os.Getenv("RELEASEKIT_FIXTURE_SUITE_RUNS"); path != "" {
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
if err != nil {
t.Fatal(err)
}
if _, err := file.WriteString("ran\n"); err != nil {
_ = file.Close()
t.Fatal(err)
}
if err := file.Close(); err != nil {
t.Fatal(err)
}
}
if os.Getenv("RELEASEKIT_FAIL_FIXTURE_SUITE") == "1" {
t.Fatal("fixture standard suite failure")
}
}
`)
writeFile(t, work, "docs/releases/v1.2.3.md", releaseNote)
writeFile(t, work, "source.txt", "candidate")
gitRun(t, work, "add", ".")