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 9b53cce8..f85f0add 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 @@ -827,12 +827,12 @@ func TestACheckThatCannotBeJudgedFailsNamingWhy(t *testing.T) { // 1289/AC4, 192 env map[string]string words string tests bool // the digest and test steps ran - dropped int32 // the run asks dropped: each put twice, the seat list's and the verdict's + dropped int32 // the run asks dropped: the seat list's, put twice, and no verdict asked after it program bool // the checking program judged it }{ {name: "a head that does not merge", set: func(f *fakeBuildModules) { f.conflict.Store(true) }, words: "files in conflict: a.go, sub/b.go", program: true}, {name: "a run ask no build node begins", set: func(f *fakeBuildModules) { f.drop.Store(true) }, env: map[string]string{"MESH_CHECKER_BEGIN_BOUND": "500ms"}, - words: "no build node began the job run of the asks", tests: true, dropped: 4}, + words: "no build node began the job run of the asks", tests: true, dropped: 2}, {name: "a program that does not build", set: func(f *fakeBuildModules) { f.broken.Store("go build: undefined: rules.Nothing") }, words: "by the checking program: its run ask", tests: true}, } { diff --git a/modules/mesh-delivery-checker/internal/check/examiner_test.go b/modules/mesh-delivery-checker/internal/check/examiner_test.go index efa9362d..a755ef7a 100644 --- a/modules/mesh-delivery-checker/internal/check/examiner_test.go +++ b/modules/mesh-delivery-checker/internal/check/examiner_test.go @@ -380,16 +380,16 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679 run func(a delivery.StepAsk, s delivery.Step) (delivery.Step, bool) at string // the commit the program is built at; empty when it fails fails string - runs int // the run asks put when it fails: each put twice when never begun + runs int // the run asks put when it fails: each put twice when never begun, and none after the seat list's }{ {name: "at the group's mesh-delivery head", at: head1}, {name: "at mesh-delivery's trunk", repo: "novox/mesh-catalog", at: trunkD}, {name: "never begun", run: func(delivery.StepAsk, delivery.Step) (delivery.Step, bool) { return delivery.Step{}, true }, - fails: "no build node began the job run of the asks", runs: 4}, + fails: "no build node began the job run of the asks", runs: 2}, {name: "does not build", run: func(_ delivery.StepAsk, s delivery.Step) (delivery.Step, bool) { s.State, s.Reason = delivery.StepFailed, "the program does not build: undefined: rules.Nothing" return s, true - }, fails: "by the checking program: its run ask", runs: 2}, + }, fails: "by the checking program: its run ask", runs: 1}, {name: "a rule the change alters", at: head1, run: func(a delivery.StepAsk, s delivery.Step) (delivery.Step, bool) { if a.Run.Args[0] != "-verdict" { return s, false @@ -411,7 +411,7 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679 if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails) { t.Errorf("%s: verdict %+v, want a fail saying %q", row.name, v, row.fails) } - if row.at == "" && (len(b.runs) != row.runs || v.JudgedBy != nil) { + if row.at == "" && (len(b.runs) != row.runs || v.JudgedBy != nil || slices.ContainsFunc(b.runs, func(a delivery.StepAsk) bool { return a.Run.Args[0] == "-verdict" })) { t.Errorf("%s: %d run asks, judged by %+v: want %d and none judging", row.name, len(b.runs), v.JudgedBy, row.runs) } } else if v.State != delivery.VerdictPass { @@ -428,10 +428,18 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679 t.Fatalf("%s: run asks %+v, want the seat list and then the verdict", row.name, b.runs) } in := b.runs[1].Run.Input // the facts kept as their exact bytes under their digest - if body, _, found, _ := b.inputs.Get(context.Background(), in.Digest); !found || in.Verify(body) != nil || in.State != InputsState || + body, _, found, _ := b.inputs.Get(context.Background(), in.Digest) + if !found || in.Verify(body) != nil || in.State != InputsState || !strings.Contains(string(body), `"group":"feat/x"`) { t.Errorf("%s: the verdict's input %+v is not kept whole: %s", row.name, in, body) } + // Review round 1 of milestone 47/B0: a level is carried as the rules read it, without its digest, its packages or + // its kept packages' names, so a group's facts stay within the input's bound. + for _, unread := range []string{`"digest":{`, `"packages":[`, `"unmet"`, `"package":"m/`} { + if strings.Contains(string(body), unread) { + t.Errorf("%s: the facts carry %s, which no rule reads: %s", row.name, unread, body) + } + } } } diff --git a/modules/mesh-delivery-checker/internal/check/judge.go b/modules/mesh-delivery-checker/internal/check/judge.go index 036f98e8..246345c4 100644 --- a/modules/mesh-delivery-checker/internal/check/judge.go +++ b/modules/mesh-delivery-checker/internal/check/judge.go @@ -307,12 +307,18 @@ func (j *Examiner) Judge(ctx context.Context, c *Case) (Outcome, error) { st.judgeAll(ctx) st.takeTryCandidate(ctx) // The heads' seat list, from the checking program, before the validator and the composer are asked (ADR 0636 §4). - st.seatList(ctx) - st.judgeManifests(ctx) - st.refuseCycle(ctx) - st.compose(ctx) - // The verdict is the checking program's of the facts (ADR 0644 §1, ADR 0656 §5). - answer, by := st.judged(ctx) + // A list it cannot make ends the check there, failed naming the checking program, worded from the facts so far. + var answer rules.Answer + var by *delivery.JudgedBy + if st.seatList(ctx) { + st.judgeManifests(ctx) + st.refuseCycle(ctx) + st.compose(ctx) + // The verdict is the checking program's of the facts (ADR 0644 §1, ADR 0656 §5). + answer, by = st.judged(ctx) + } else { + answer = rules.Answer{V: rules.AnswerVersion, Verdict: rules.Judge(st.factsOf())} + } for _, n := range answer.Notes { st.c.note(n) } @@ -691,7 +697,7 @@ func (st *state) level(ctx context.Context, t *target, la delivery.LevelAnswer) t.judgedBy(la.Judged) // the candidate judged the level: nothing is kept (ADR 0535 §4) } _, elapsed, results := st.levels.Level(t.pull.Repository, t.pull.Number, t.module, t.node, la) - st.facts = append(st.facts, rules.Step{Repository: t.pull.Repository, Number: t.pull.Number, Module: t.module, Node: t.node, Level: &la}) + st.facts = append(st.facts, rules.Step{Repository: t.pull.Repository, Number: t.pull.Number, Module: t.module, Node: t.node, Level: rules.LevelFact(la)}) var digests map[string]string if t.module == buildrun.GoBuildModule && la.Digest != nil { // The packages come as a list, never keyed by a package path (issue 1930); an answer stored before reads too. diff --git a/modules/mesh-delivery-checker/internal/check/programs.go b/modules/mesh-delivery-checker/internal/check/programs.go index 1b5b9cb4..5572a0fc 100644 --- a/modules/mesh-delivery-checker/internal/check/programs.go +++ b/modules/mesh-delivery-checker/internal/check/programs.go @@ -165,10 +165,12 @@ func (st *state) waitBegun(ctx context.Context, a delivery.StepAsk) (delivery.St } // seatList asks the checking program for the heads' seat list, which the validator's and the composer's requests carry -// (ADR 0636 §4); the list and what it left out are the check's notes. A list it cannot make fails the check, naming why. -func (st *state) seatList(ctx context.Context) { +// (ADR 0636 §4); the list and what it left out are the check's notes. A list it cannot make fails the check, naming why, +// and answers false: the check ends there, with no verdict asked of a program that could not answer its list (review +// round 1 of milestone 47/B0: a build node with no job run would hold the check two begin bounds more). +func (st *state) seatList(ctx context.Context) bool { if st.narrowTry() { - return + return true } body, err := json.Marshal(st.factsHead()) if err == nil { @@ -184,11 +186,12 @@ func (st *state) seatList(ctx context.Context) { for _, l := range a.Left { st.c.note("the heads' seat list leaves out " + l.Path + ": " + l.Why) } - return + return true } } } st.fail(goModule, "the heads' seat list could not be made by the checking program: "+err.Error()) + return false } // factsHead are the facts that name the check, without what the asking gathered. diff --git a/modules/mesh-delivery-checker/rules/facts.go b/modules/mesh-delivery-checker/rules/facts.go index 874dfdc9..573c29de 100644 --- a/modules/mesh-delivery-checker/rules/facts.go +++ b/modules/mesh-delivery-checker/rules/facts.go @@ -64,6 +64,36 @@ type Step struct { Manifests *Manifests `json:"manifests,omitempty"` } +// LevelFact is a level answer as the facts carry it: only what the rules read (Tally.Level), so a group's facts stay +// within the run input's bound (delivery.RunInputOf). The digest, the unmet packages and the level's judged program +// are left out, the kept packages are one count, and each test result keeps only the fields Tested names, without its +// packages, bounds and judged program (review round 1 of milestone 47/B0). An answer this cannot read is carried as given, +// for the rules to refuse it. +func LevelFact(la delivery.LevelAnswer) *delivery.LevelAnswer { + out := &delivery.LevelAnswer{Level: la.Level} + kept := 0 + for _, k := range la.Kept { + kept += k.Tests + } + if len(la.Kept) > 0 { + out.Kept = []delivery.KeptTaken{{Tests: kept}} + } + for _, raw := range la.Tests { + var res Tested + if err := json.Unmarshal(raw, &res); err != nil { + out.Tests = append(out.Tests, raw) + continue + } + res.Packages, res.JudgedBy, res.Bound, res.SetupBound, res.OverWhich = nil, "", 0, 0, "" + small, err := json.Marshal(res) + if err != nil { + small = raw + } + out.Tests = append(out.Tests, small) + } + return out +} + // DecodeFacts reads facts, refusing a version this program does not read, naming it. func DecodeFacts(data []byte) (Facts, error) { var f Facts