diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go index 9eeb0c8f..05fe877e 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go @@ -15,6 +15,7 @@ import ( "sync" "sync/atomic" "testing" + "testing/fstest" "time" "github.com/nats-io/nats-server/v2/server" @@ -28,6 +29,7 @@ import ( "git.novox.be/novox/mesh-sdk/go/process" "mesh-delivery-checker/internal/check" + "mesh-delivery-checker/rules" ) // busConf is the shape of the composed accounts file (mesh-controller internal/broker): one account with JetStream, the @@ -91,6 +93,33 @@ type fakeBuildModules struct { beside map[string][]string // names are the nodes' names the last check ask carried, for the names check (ADR 0567 ยง12). names []string + // inputs is the checker's state of run inputs; runs counts the run asks answered, and broken, when set, is why the + // checking program does not build (issue 1921). + inputs jetstream.KeyValue + runs atomic.Int32 + broken atomic.Value +} + +// run answers a run ask as mesh-build-go does: the checking program's work (rules.Program) over the input the ask names, +// read from the checker's state inputs and checked against its digest, with no heads of its own laid. +func (f *fakeBuildModules) run(ctx context.Context, a delivery.StepAsk) (any, error) { + f.runs.Add(1) + if why, _ := f.broken.Load().(string); why != "" { + return nil, errors.New(why) + } + answer := delivery.RunAnswer{Built: true, Key: "program-0123456789abcdef"} + out, err := rules.Program(a.Run.Args, func() ([]byte, error) { + e, err := f.inputs.Get(ctx, a.Run.Input.Digest) + if err != nil { + return nil, err + } + return e.Value(), a.Run.Input.Verify(e.Value()) + }, fstest.MapFS{}) + if err != nil { + answer.Exit, answer.Said = 2, err.Error() + } + answer.Output = out + return answer, nil } func (f *fakeBuildModules) count(tool string) int { @@ -199,6 +228,9 @@ func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.Key f.names = a.Check.NodeNames f.mu.Unlock() } + if a.Run != nil { + return f.run(ctx, a) + } return gobuild.RunAsk(ctx, steps, a) }} go func() { _ = p.Pull(f.ctx) }() @@ -398,7 +430,7 @@ func startBed(t *testing.T) *bed { delivery.CheckerOwedBucket: delivery.CheckerOwedHistory, delivery.PreviewsBucket: delivery.PreviewsHistory, delivery.CandidatesBucket: delivery.CandidatesHistory, delivery.ModulesBucket: delivery.ModulesHistory, delivery.ManifestsBucket: delivery.ManifestsHistory, delivery.SourceBucket: delivery.SourceHistory, delivery.PresenceBucket: delivery.PresenceHistory, delivery.HaveBucket: delivery.HaveHistory, check.TriesBucket: 1, - delivery.TurnsBucket: delivery.TurnsHistory, delivery.BuilderBuildsBucket: 1} { + delivery.TurnsBucket: delivery.TurnsHistory, delivery.BuilderBuildsBucket: 1, check.InputsBucket: 1} { kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: name, History: uint8(history)}) if err != nil { t.Fatal(err) @@ -420,7 +452,7 @@ func startBed(t *testing.T) *bed { b.setHealth(t, "n1", module) } b.serveForge(t, ctl) - b.fake = &fakeBuildModules{js: js, ctx: ctx, calls: map[string]int{}} + b.fake = &fakeBuildModules{js: js, ctx: ctx, calls: map[string]int{}, inputs: b.buckets[check.InputsBucket]} b.fake.serve(t, "mesh-build-go", b.buckets[delivery.CheckerKeptBucket]) b.fake.serve(t, "mesh-build-node", b.buckets[delivery.CheckerKeptBucket]) b.report(t, "mesh-build-go", "n1", "go", "go1.27.1", "1") @@ -647,6 +679,15 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300 if got := asked(); len(got) == 0 || got[0].Candidate != key { t.Errorf("n1's engine was asked %+v, want the check's candidate %s", got, key) } + // Issue 1921 (B0): the verdict is the checking program's, built at the group's mesh-delivery head over its input kept in + // the state inputs, two run asks a check (the seat list, the verdict); the verdict names the program's build. + if by := v.JudgedBy; by == nil || by.Program.Commit != head1 || by.Program.Package != check.CheckingProgramPackage || + by.Key != "program-0123456789abcdef" || len(by.Heads) != 1 || by.Heads[0].Commit != head1 { + t.Errorf("judged by %+v, want the checking program at the head %s", v.JudgedBy, head1) + } + if n := b.fake.runs.Load(); n != 4 { + t.Errorf("%d run asks, want two for each of the two checks", n) + } b.fake.mu.Lock() names := b.fake.names b.fake.mu.Unlock() @@ -978,7 +1019,7 @@ func TestTheCheckerSaysOwedReadableOnTheBus(t *testing.T) { // R2-1 } if said.Module != "mesh-delivery-checker" || said.Node != "n1" || !said.Keeps("owed") || !said.Readable("owed") || !said.Readable("previews") || !said.Readable("verdicts") || !said.Readable("kept") || !said.Readable("tries") || said.Readable("hearing") || - !said.Keeps("asks") || said.Readable("asks") || len(said.States) != 7 { - t.Fatalf("the checker said %+v, want its seven states, asks and hearing its own, with kept, owed, previews, tries and verdicts readable, tries by the delivery view (1929/AC4; 1887 A5b; build_usage)", said) + !said.Keeps("asks") || said.Readable("asks") || !said.Readable("inputs") || len(said.States) != 8 { + t.Fatalf("the checker said %+v, want its eight states, asks and hearing its own, with inputs, kept, owed, previews, tries and verdicts readable, tries by the delivery view, inputs by the Go build module (1929/AC4; 1887 A5b; build_usage; 1921)", said) } }