The validation run's answer is read from the step's end through steprun's ReadOutput, whole or from the build seat's output object, and the indexer invokes seat:node-build-go.output for it (A-254); the patch B5g6 left for the join, applied on the merged heads. Builds: design 47 G §712, §713; lead answers A-254, A-268 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
114 lines
5.8 KiB
Go
114 lines
5.8 KiB
Go
package index
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/composer"
|
|
)
|
|
|
|
type askRecorder struct{ asks []buildstep.StepAsk }
|
|
|
|
func (r *askRecorder) Put(_ context.Context, a buildstep.StepAsk) error {
|
|
r.asks = append(r.asks, a)
|
|
return nil
|
|
}
|
|
|
|
// Design 47 G §713, A-247: a pending revision names its ask on its newest value and resumes on that ask's end; an end of a
|
|
// replaced ask is dropped; a run that could not validate is no refusal and never passed; the start asks nothing again while
|
|
// the ask is out.
|
|
func TestAPendingRevisionWaitsForItsValidation(t *testing.T) {
|
|
ctx := context.Background()
|
|
b := newTakeInBed(t)
|
|
asks := &askRecorder{}
|
|
b.ix.ValidationAsks, b.ix.Inputs, b.ix.Revisions = asks, newMem(), newMemObjects(0)
|
|
// No entry of the revision claims the seat mesh-catalog: the validation could not be asked, so it is failed, said, and
|
|
// nothing is taken in.
|
|
b.arrive(t, 2, b.merge(21, sha('c'), "tree-avahi-3", ""))
|
|
entries := readPending(t, b)
|
|
if len(entries) != 1 || entries[0].Value.Validation == nil || !strings.Contains(entries[0].Value.Validation.Failed, "claims the seat mesh-catalog") || len(asks.asks) != 0 {
|
|
t.Fatalf("pending %+v, asks %d: want m.2 pending with failed and no ask", entries, len(asks.asks))
|
|
}
|
|
if last := b.said[len(b.said)-1]; last.Kind != takein.TakeInPending || last.Failed == "" || last.Refused != "" {
|
|
t.Errorf("said %+v, want take-in.pending naming failed, never a refusal", last)
|
|
}
|
|
if w := PendingWaits(entries); len(w) != 1 || w[0].Validation == nil || w[0].Validation.Try != 1 {
|
|
t.Errorf("indexer_pending %+v, want the validation whole", w)
|
|
}
|
|
// An ask out: the newest value names it; the start asks nothing new while it is out.
|
|
loaded, _, _, _ := b.ix.loadNewest(ctx, entries)
|
|
req, _, err := b.ix.validationRequest(ctx, TrunkBranch, loaded)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, _ := json.Marshal(req)
|
|
setValidation := func(v takein.Validation) {
|
|
e := readPending(t, b)[0]
|
|
p := e.Value
|
|
p.Validation = &v
|
|
data, _ := encodePending(p)
|
|
if err := b.pending.Update(ctx, e.Key, data, e.Revision); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
setValidation(takein.Validation{Ask: "mvl0frv18d1957", Input: runDigest(raw), Seq: 2, Try: 2})
|
|
if _, _, err := b.ix.TakeInAtStart(ctx); err != nil || len(asks.asks) != 0 || len(b.keys()) != 1 {
|
|
t.Fatalf("the start: %v, asks %d, pending %v: want it waiting for its ask", err, len(asks.asks), b.keys())
|
|
}
|
|
end := func(ask, outcome, said string, answer any) buildstep.StepEnded {
|
|
out, _ := json.Marshal(answer)
|
|
return buildstep.StepEnded{Ask: ask, ID: ask, Asker: ValidationAsker, Node: "ace", Outcome: outcome, Said: said, Output: string(out)}
|
|
}
|
|
// The answer is the manifest validator's verdict itself, the program's last line (A-254).
|
|
accepted := composer.Verdict{Cases: []composer.CaseVerdict{{Name: "change"}, {Name: "trunk"}}}
|
|
// The end of a replaced ask is dropped, written in the journal.
|
|
if err := b.ix.HearValidationEnded(ctx, end("mvl0frv18d1950", buildstep.OutcomePassed, "", accepted)); err != nil || len(b.keys()) != 1 || !strings.Contains(b.logs.String(), "is dropped") {
|
|
t.Fatalf("a replaced ask's end: %v, pending %v", err, b.keys())
|
|
}
|
|
// could not run: pending with failed, never passed.
|
|
if err := b.ix.HearValidationEnded(ctx, end("mvl0frv18d1957", buildstep.OutcomeCouldNotRun, "no build node", nil)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v := readPending(t, b)[0].Value.Validation; v == nil || v.Failed != "could not run: no build node" || v.Accepted != 0 {
|
|
t.Fatalf("after could not run: %+v", v)
|
|
}
|
|
// indexer_validate_again (§713): its hand act recorded first, then the next try asked; the answer says the ask's state.
|
|
var recorded []string
|
|
tools := ValidationTools(func(key string, value []byte) (TakeInAsk, error) {
|
|
a, err := DecodeTakeIn(value)
|
|
if err != nil || key != "pending.validate-again."+takein.PendingKey(2) || !a.ValidateAgain {
|
|
t.Fatalf("asked %s %+v %v", key, a, err)
|
|
}
|
|
r := b.ix.ValidateAgainByHand(ctx, a.Pending, a.By, a.Why)
|
|
a.Result = &r
|
|
return a, nil
|
|
}, b.ix.now, func(what, why string) error { recorded = append(recorded, what); return nil })
|
|
if len(tools) != 1 || tools[0].Name != ValidateAgainTool {
|
|
t.Fatalf("tools = %v", tools)
|
|
}
|
|
if _, err := tools[0].Run(map[string]any{"key": takein.PendingKey(2)}); err == nil || len(recorded) != 0 {
|
|
t.Fatalf("asked again with no why: %v, %v", err, recorded)
|
|
}
|
|
got, err := tools[0].Run(map[string]any{"key": takein.PendingKey(2), "why": "the build node is back"})
|
|
if v := readPending(t, b)[0].Value.Validation; err != nil || len(recorded) != 1 || v == nil || v.Try != 3 || v.Failed == "could not run: no build node" ||
|
|
!strings.Contains(got.(*TakenIn).Answer, "try 3") {
|
|
t.Fatalf("asked again: %v, recorded %v, validation %+v, answer %+v: want the try after 2 asked", err, recorded, v, got)
|
|
}
|
|
setValidation(takein.Validation{Ask: "mvl0frv18d1959", Input: runDigest(raw), Seq: 2, Try: 3})
|
|
if r := b.ix.ValidateAgainByHand(ctx, takein.PendingKey(2), "lead", "again"); r.Stored || !strings.Contains(r.Answer, "is out") || readPending(t, b)[0].Value.Validation.Try != 3 {
|
|
t.Fatalf("an ask out was asked again: %+v", r)
|
|
}
|
|
// Asked again (by hand or by a later merge) and accepted: the revision is taken in, and its keys go.
|
|
setValidation(takein.Validation{Ask: "mvl0frv18d1958", Input: runDigest(raw), Seq: 2, Try: 3})
|
|
if err := b.ix.HearValidationEnded(ctx, end("mvl0frv18d1958", buildstep.OutcomePassed, "", accepted)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(b.keys()) != 0 || b.commit(t, "avahi") != sha('c') {
|
|
t.Fatalf("accepted: pending %v %+v, avahi at %s: want it taken in\n%s", b.keys(), readPending(t, b)[0].Value, b.commit(t, "avahi"), b.logs.String())
|
|
}
|
|
}
|