Review of 650a1d0: a member that only failed its tests got no preview, so the indexer held no manifest
of it, the assigner's composition only said it waits, and the check waited for ever. The preview is now
written whenever every build module's fetch merged; a member with no preview of a merge (a conflict, a
fetch that did not answer) makes the composition not run, said with the members it names, unawaited.
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
98 lines
4.3 KiB
Go
98 lines
4.3 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// fakeCompositions answers the assigner's composition of a check from memory, recording the keys asked.
|
|
type fakeCompositions struct {
|
|
v delivery.CompositionVerdict
|
|
err error
|
|
asked []string
|
|
// waits answers only that the composition waits, as the assigner does for a member the indexer holds no manifest of:
|
|
// Await returns only when its context ends.
|
|
waits bool
|
|
}
|
|
|
|
func (f *fakeCompositions) Await(ctx context.Context, key string) (delivery.CompositionVerdict, error) {
|
|
f.asked = append(f.asked, key)
|
|
if f.waits {
|
|
<-ctx.Done()
|
|
return delivery.CompositionVerdict{}, ctx.Err()
|
|
}
|
|
return f.v, f.err
|
|
}
|
|
|
|
// 1297/AC3 (its unit half): a check whose tests pass takes the assigner's composition of it, by the check's own key,
|
|
// into its verdict: a pass says it passed with the mesh-sdk and machines it composed, and is no longer "not run".
|
|
func TestAPassingCheckTakesTheAssignersComposition(t *testing.T) {
|
|
b := newBench(t)
|
|
f := &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionPass, SDK: "v0.1.14-x", Machines: []string{"g14"}, Took: 40}}
|
|
b.ex.Compositions = f
|
|
cs := caseOf(t, pullOf(repoA, 7, head1))
|
|
cs.FPSet = "fps-of-the-candidate"
|
|
v := b.judge(t, cs).Verdict
|
|
if v.State != delivery.VerdictPass || v.Composition == nil || v.Composition.State != delivery.CompositionPass || v.Composition.SDK != "v0.1.14-x" || v.Took.Composition != 40 {
|
|
t.Fatalf("verdict = %+v, composition %+v", v, v.Composition)
|
|
}
|
|
if len(f.asked) != 1 || f.asked[0] != cs.Key {
|
|
t.Errorf("asked %v, want the check's key %s", f.asked, cs.Key)
|
|
}
|
|
if !compositionMentions(v, "passed") {
|
|
t.Errorf("the line says %q", compositionWords(v.Composition, v.Took.Composition))
|
|
}
|
|
if v.Composition.FPSet == "" || v.Composition.FPSet != cs.FPSet {
|
|
t.Errorf("the composition names the fingerprint set %q, want the candidate's %q (ADR 0548 §5)", v.Composition.FPSet, cs.FPSet)
|
|
}
|
|
// ADR 0548 §5: a carry whose fingerprints did not change keeps the composition it passed and asks the assigner nothing.
|
|
carry := caseOf(t, pullOf(repoA, 7, head1))
|
|
carry.CarriedFrom, carry.Composition = map[string]string{repoA: trunkA2}, v.Composition
|
|
if kept := b.judge(t, carry).Verdict; kept.Composition == nil || kept.Composition.SDK != "v0.1.14-x" || len(f.asked) != 1 {
|
|
t.Errorf("the carry's composition is %+v and the assigner was asked %d times; want the kept one and no second ask", kept.Composition, len(f.asked))
|
|
}
|
|
}
|
|
|
|
func compositionMentions(v delivery.Verdict, word string) bool {
|
|
return strings.Contains(compositionWords(v.Composition, v.Took.Composition), word)
|
|
}
|
|
|
|
// 1297/AC2 at the checker: a failed composition fails the check, naming the machine it failed at and its words.
|
|
func TestAFailedCompositionFailsTheCheck(t *testing.T) {
|
|
b := newBench(t)
|
|
b.ex.Compositions = &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionFail, SDK: "x",
|
|
Machine: "g14", Words: "g14 runs web, which the change removes from its source"}}
|
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
|
if v.State != delivery.VerdictFail || v.Failed.Machine != "g14" || v.Composition == nil || v.Composition.State != delivery.CompositionFail {
|
|
t.Fatalf("verdict = %+v", v)
|
|
}
|
|
v.Group, v.Heads = "feat/x", "0123456789ab" // the checker names them when it writes the verdict
|
|
if _, err := delivery.EncodeVerdict(v); err != nil {
|
|
t.Errorf("the verdict cannot be written: %v", err)
|
|
}
|
|
}
|
|
|
|
// 1297/B3d: a composition that cannot be read leaves it not run; a try is not composed.
|
|
func TestACompositionNotHadIsNotRun(t *testing.T) {
|
|
b := newBench(t)
|
|
f := &fakeCompositions{err: errors.New("the check's bound passed")}
|
|
b.ex.Compositions = f
|
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
|
if v.State != delivery.VerdictPass || v.Composition == nil || v.Composition.State != delivery.CompositionNotRun {
|
|
t.Fatalf("verdict = %+v", v)
|
|
}
|
|
b2 := newBench(t)
|
|
f2 := &fakeCompositions{v: delivery.CompositionVerdict{State: delivery.CompositionFail, Words: "w", SDK: "x"}}
|
|
b2.ex.Compositions = f2
|
|
cs := caseOf(t, pullOf(repoA, 7, head1))
|
|
cs.Try = &TryOptions{}
|
|
b2.judge(t, cs)
|
|
if len(f2.asked) != 0 {
|
|
t.Errorf("a try asked for a composition: %v", f2.asked)
|
|
}
|
|
}
|