Clear a step's success only when it was started and failed, and say updated when it passes again (novox/hq issue 467)
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

A bundle that could not be fetched refused nothing; clearing for it would rerun
every step on a node once the artifact store came back from an outage.
This commit is contained in:
2026-10-11 11:12:15 +02:00
parent c957f1ec5e
commit 0ec96a048e
3 changed files with 69 additions and 4 deletions
+58 -1
View File
@@ -3,6 +3,8 @@ package apply
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
@@ -25,6 +27,7 @@ type aStepBed struct {
dir, body, digest string
starts int // how many times the step's unit was started
refuse bool // whether the step fails when started
storeGone bool // whether the bundle's store answers
}
func newStepBed(t *testing.T) *aStepBed {
@@ -33,7 +36,18 @@ func newStepBed(t *testing.T) *aStepBed {
unitDir, daemonRoot = units, bundles
t.Cleanup(func() { unitDir, daemonRoot = wasUnits, wasBundles })
body, digest := anArchive(t, map[string]string{"check": "#!/bin/sh\n"})
return &aStepBed{t: t, dir: t.TempDir(), body: serving(t, body), digest: digest}
b := &aStepBed{t: t, dir: t.TempDir(), digest: digest}
// The bundle's store, which can be made not to answer.
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
if b.storeGone {
http.NotFound(w, nil)
return
}
_, _ = w.Write(body)
}))
t.Cleanup(server.Close)
b.body = server.URL + "/check.tar.gz"
return b
}
func (b *aStepBed) run(ctx context.Context, name string, args ...string) (string, error) {
@@ -195,3 +209,46 @@ func TestAContainerStepThatFailedRunsAgain(t *testing.T) {
t.Errorf("apply 5: a step that passed ran again: err %v, runs %d (want 4)", err, runs)
}
}
func TestAStepWhoseBundleCouldNotBeFetchedIsNotRunAgainForIt(t *testing.T) {
// A fetch that fails is a failure of the step, and gates its module, but the step was never started:
// it did not refuse anything, and its last success stands. Cleared for it, every step on a machine
// would run again once the artifact store came back from an outage.
b := newStepBed(t)
_, known, err := b.apply(store.State{}, "good")
if err != nil || b.starts != 1 {
t.Fatalf("apply 1: err %v, starts %d", err, b.starts)
}
b.storeGone = true
report, known, err := b.apply(known, "good")
if err == nil || !skippedIn(report, "mod.after") || b.starts != 1 {
t.Fatalf("store gone: err %v, skipped %v, starts %d", err, skippedIn(report, "mod.after"), b.starts)
}
b.storeGone = false
report, _, err = b.apply(known, "good")
if err != nil || b.starts != 1 {
t.Errorf("store back: err %v, a step that passed and was never started since ran again (started %d times, want 1)", err, b.starts)
}
for _, o := range report.Outcomes {
if o.ID == "mod.check" && o.Action != "unchanged" {
t.Errorf("store back: the step reports %s: %s", o.Action, o.Detail)
}
}
}
func TestAStepPassingAfterAFailureSaysItWasUpdated(t *testing.T) {
b := newStepBed(t)
_, known, _ := b.apply(store.State{}, "good")
b.refuse = true
_, known, _ = b.apply(known, "bad")
b.refuse = false
report, _, err := b.apply(known, "bad")
if err != nil {
t.Fatal(err)
}
for _, o := range report.Outcomes {
if o.ID == "mod.check" && o.Action != "updated" {
t.Errorf("a step with a record that passed after failing says %s, want updated", o.Action)
}
}
}