Run the levels of one check in one ask after one fetch, keeping nothing under the candidate's runner or a digest without the siblings, as ADR 0525 §3 says a check ask is (issue 1887 A5a)
This commit is contained in:
+69
-33
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -162,19 +163,35 @@ func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error)
|
||||
if len(f.Conflicts) > 0 {
|
||||
return answer, nil // the asker reads the conflict from the fetch's answer: a verdict, not an error
|
||||
}
|
||||
if s.Test == nil {
|
||||
return nil, Refuse("this build module tests nothing")
|
||||
}
|
||||
runner := ""
|
||||
if job.Runner != "" && (job.RunnerWhen == "" || changesUnder(f.Changed, job.RunnerWhen)) {
|
||||
runner = job.Runner
|
||||
}
|
||||
for _, level := range job.Levels {
|
||||
la := delivery.LevelAnswer{Level: level}
|
||||
passed, err := s.level(ctx, job, level, f, runner, &la)
|
||||
answer.Levels = append(answer.Levels, la)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !passed {
|
||||
return answer, nil // the first level that did not pass ends the check (ADR 0484 clause 13)
|
||||
}
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
// level runs one level of a check ask and says whether it passed.
|
||||
func (s AskSteps) level(ctx context.Context, job delivery.CheckJob, level string, f fetched, runner string, la *delivery.LevelAnswer) (bool, error) {
|
||||
withRunner := func(args map[string]any) map[string]any {
|
||||
if runner != "" {
|
||||
args["runner"] = runner
|
||||
}
|
||||
return args
|
||||
}
|
||||
if s.Test == nil {
|
||||
return nil, Refuse("this build module tests nothing")
|
||||
}
|
||||
test := func(args map[string]any) (bool, error) {
|
||||
out, err := s.Test(ctx, args)
|
||||
if err != nil {
|
||||
@@ -184,7 +201,7 @@ func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
answer.Tests = append(answer.Tests, r)
|
||||
la.Tests = append(la.Tests, r)
|
||||
var t struct {
|
||||
Passed bool `json:"passed"`
|
||||
}
|
||||
@@ -198,23 +215,20 @@ func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error)
|
||||
if changesTestIn(f.Changed, dir) != priority {
|
||||
continue
|
||||
}
|
||||
passed, err := test(withRunner(map[string]any{"workspace": f.Workspace, "module": dir, "level": job.Level}))
|
||||
passed, err := test(withRunner(map[string]any{"workspace": f.Workspace, "module": dir, "level": level}))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the %s level of %s: %w", job.Level, dir, err)
|
||||
return false, fmt.Errorf("the %s level of %s: %w", level, dir, err)
|
||||
}
|
||||
if !passed {
|
||||
return answer, nil
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
todo, all, err := s.plan(ctx, job, f, withRunner, &answer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return true, nil
|
||||
}
|
||||
todo, all := s.plan(ctx, job, level, f, runner, withRunner, la)
|
||||
for _, priority := range []bool{true, false} {
|
||||
args := withRunner(map[string]any{"workspace": f.Workspace, "level": job.Level})
|
||||
args := withRunner(map[string]any{"workspace": f.Workspace, "level": level})
|
||||
if job.Run != "" {
|
||||
args["run"] = job.Run
|
||||
}
|
||||
@@ -236,13 +250,13 @@ func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error)
|
||||
}
|
||||
passed, err := test(args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the %s level: %w", job.Level, err)
|
||||
return false, fmt.Errorf("the %s level: %w", level, err)
|
||||
}
|
||||
if !passed {
|
||||
return answer, nil
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return answer, nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// release removes an ask's workspace, saying a failure: the module's sweep removes it later.
|
||||
@@ -255,30 +269,36 @@ func (s AskSteps) release(workspace string) {
|
||||
}
|
||||
}
|
||||
|
||||
// plan digests the level and takes the kept verdicts: the packages left to test, or all when the level runs whole (a
|
||||
// try of some tests, or a digest that named nothing).
|
||||
func (s AskSteps) plan(ctx context.Context, job delivery.CheckJob, f fetched, withRunner func(map[string]any) map[string]any, answer *delivery.CheckAnswer) ([]string, bool, error) {
|
||||
if job.Run != "" {
|
||||
return nil, true, nil // a partial run proves no package: nothing is taken or kept
|
||||
// plan digests the level and takes the kept verdicts: the packages left to test, or all when the level runs whole. It
|
||||
// runs whole, digesting nothing, for a try of some tests (a partial run proves no package) and under the candidate's own
|
||||
// runner (ADR 0535 §4: the candidate decides which verdicts would be taken, so every package runs); it runs whole and
|
||||
// takes nothing when the digest names no package or does not cover the siblings the fetch placed (ADR 0444, L5).
|
||||
func (s AskSteps) plan(ctx context.Context, job delivery.CheckJob, level string, f fetched, runner string, withRunner func(map[string]any) map[string]any, la *delivery.LevelAnswer) ([]string, bool) {
|
||||
if job.Run != "" || runner != "" {
|
||||
return nil, true
|
||||
}
|
||||
out, err := s.Digest(ctx, withRunner(map[string]any{"workspace": f.Workspace, "level": job.Level}))
|
||||
out, err := s.Digest(ctx, withRunner(map[string]any{"workspace": f.Workspace, "level": level}))
|
||||
if err != nil {
|
||||
if s.Logf != nil {
|
||||
s.Logf("no digest of the %s level, so it runs whole: %v", job.Level, err)
|
||||
s.Logf("no digest of the %s level, so it runs whole: %v", level, err)
|
||||
}
|
||||
return nil, true, nil
|
||||
return nil, true
|
||||
}
|
||||
r, err := raw(out)
|
||||
if err != nil {
|
||||
return nil, true, nil
|
||||
return nil, true
|
||||
}
|
||||
answer.Digest = r
|
||||
var d struct {
|
||||
Packages map[string]string `json:"packages"`
|
||||
Beside []string `json:"beside"`
|
||||
}
|
||||
if err := json.Unmarshal(r, &d); err != nil || len(d.Packages) == 0 {
|
||||
return nil, true, nil
|
||||
return nil, true
|
||||
}
|
||||
if !slices.Equal(d.Beside, besideOf(job.Fetch)) {
|
||||
return nil, true // a digest that does not cover the siblings keys no verdict: the level runs whole, keeping nothing
|
||||
}
|
||||
la.Digest = r
|
||||
pkgs := make([]string, 0, len(d.Packages))
|
||||
for p := range d.Packages {
|
||||
pkgs = append(pkgs, p)
|
||||
@@ -287,21 +307,37 @@ func (s AskSteps) plan(ctx context.Context, job delivery.CheckJob, f fetched, wi
|
||||
var todo []string
|
||||
for _, p := range pkgs {
|
||||
if job.Kept && s.Kept != nil {
|
||||
tests, found, err := s.Kept(ctx, job.Level, d.Packages[p])
|
||||
tests, found, err := s.Kept(ctx, level, d.Packages[p])
|
||||
if err != nil && s.Logf != nil {
|
||||
s.Logf("the kept verdict of %s at %s cannot be read, so it runs: %v", p, job.Level, err)
|
||||
s.Logf("the kept verdict of %s at %s cannot be read, so it runs: %v", p, level, err)
|
||||
}
|
||||
if err == nil && found {
|
||||
answer.Kept = append(answer.Kept, delivery.KeptTaken{Package: p, Digest: d.Packages[p], Tests: tests})
|
||||
la.Kept = append(la.Kept, delivery.KeptTaken{Package: p, Digest: d.Packages[p], Tests: tests})
|
||||
continue
|
||||
}
|
||||
}
|
||||
todo = append(todo, p)
|
||||
}
|
||||
if len(todo) == 0 {
|
||||
return nil, false, nil
|
||||
return todo, false
|
||||
}
|
||||
|
||||
// besideOf are the siblings a fetch's arguments place, as a digest echoes them: `<owner/repo> <commit>`, sorted.
|
||||
func besideOf(fetch map[string]any) []string {
|
||||
raw, err := json.Marshal(fetch["beside"])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return todo, false, nil
|
||||
var refs []struct {
|
||||
Repository string `json:"repository"`
|
||||
Commit string `json:"commit"`
|
||||
}
|
||||
_ = json.Unmarshal(raw, &refs)
|
||||
var out []string
|
||||
for _, r := range refs {
|
||||
out = append(out, r.Repository+" "+r.Commit)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// changesUnder says whether a path the merge changes is under prefix.
|
||||
|
||||
Reference in New Issue
Block a user