The checker's line says whether each candidate module's build is built, failed or waiting, read from the builder's keys (issue 1684)
This commit is contained in:
@@ -220,7 +220,7 @@ func startBed(t *testing.T) *bed {
|
||||
for name, history := range map[string]int{delivery.CheckerVerdictsBucket: delivery.CheckerVerdictsHistory, delivery.CheckerKeptBucket: delivery.CheckerKeptHistory,
|
||||
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} {
|
||||
delivery.PresenceBucket: delivery.PresenceHistory, delivery.HaveBucket: delivery.HaveHistory, delivery.BuilderBuildsBucket: delivery.BuilderBuildsHistory} {
|
||||
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: name, History: uint8(history)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -526,3 +526,28 @@ func TestNoCheckStartsWhileADeliveryBuildsAndOneStartsWhenItEnds(t *testing.T) {
|
||||
t.Errorf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTheLineSaysTheStateOfTheBuildsOfTheCandidatesModules: the builder wrote builds.avahi.<fp> done for the fingerprint the
|
||||
// candidate carries for avahi; the status's line says avahi is built (ADR 0484 clause 7).
|
||||
func TestTheLineSaysTheStateOfTheBuildsOfTheCandidatesModules(t *testing.T) { // N7/AC1
|
||||
b := startBed(t)
|
||||
fp := "src2:" + strings.Repeat("a", 64)
|
||||
buildKey, _ := delivery.BuilderBuildKey("avahi", fp)
|
||||
done, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, By: "n1.mesh-delivery-builder", Manifest: strings.Repeat("0a", 32),
|
||||
Artifacts: []delivery.BuiltArtifact{{Name: "avahi", Kind: "bundle", Digest: "sha256:" + strings.Repeat("a", 64)}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.buckets[delivery.BuilderBuildsBucket].Put(b.ctx, buildKey, done); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := pull(repo, 7, head1)
|
||||
p.Modules = []string{"avahi"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"avahi": {FP2: fp, Manifest: strings.Repeat("0a", 32)}}
|
||||
key := b.candidate(t, "feat/x", p)
|
||||
b.start(t, nil)
|
||||
b.verdict(t, key, ended)
|
||||
if o := b.owed(t, 7, head1, "success"); !strings.Contains(o.Text, "builds: avahi built") {
|
||||
t.Errorf("owed = %+v, want the builds in the line", o)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,13 @@ type judgeDeps struct {
|
||||
previews check.KV
|
||||
repos *repoModules
|
||||
announce check.Announcer
|
||||
builds check.BuildStates
|
||||
logf func(format string, args ...any)
|
||||
}
|
||||
|
||||
func newJudge(d judgeDeps) check.Judge {
|
||||
return &check.Examiner{Steps: d.runner, Toolchains: d.tools, Presence: d.presence, Health: d.have, Places: d.places,
|
||||
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Announce: d.announce, Trunks: d.trunks, Logf: d.logf}
|
||||
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Builds: d.builds, Announce: d.announce, Trunks: d.trunks, Logf: d.logf}
|
||||
}
|
||||
|
||||
// manifestKV reads a manifest by its digest.
|
||||
@@ -110,3 +111,19 @@ func (r *repoModules) BuildModules(repository string) []string {
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// buildReader reads the builder's build of a module at a fingerprint (mesh-delivery-builder_builds).
|
||||
type buildReader struct{ kv kvStore }
|
||||
|
||||
func (r buildReader) Build(ctx context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error) {
|
||||
key, err := delivery.BuilderBuildKey(module, fp2)
|
||||
if err != nil {
|
||||
return delivery.BuilderBuild{}, false, err
|
||||
}
|
||||
value, _, found, err := r.kv.Get(ctx, key)
|
||||
if err != nil || !found {
|
||||
return delivery.BuilderBuild{}, false, err
|
||||
}
|
||||
b, err := delivery.DecodeBuilderBuild(value)
|
||||
return b, err == nil, err
|
||||
}
|
||||
|
||||
@@ -198,6 +198,17 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
Link: link,
|
||||
Logf: logger.Printf,
|
||||
}
|
||||
// The builder's builds say how the candidates' fingerprints stand (issue 1684); where its bucket is not there yet the line
|
||||
// says nothing of the builds, until the checker starts again.
|
||||
var builds check.BuildStates
|
||||
switch kv, err := js.KeyValue(ctx, delivery.BuilderBuildsBucket); {
|
||||
case errors.Is(err, jetstream.ErrBucketNotFound):
|
||||
logger.Printf("the bucket %s does not exist yet: the line says nothing of the builds until the checker starts again", delivery.BuilderBuildsBucket)
|
||||
case err != nil:
|
||||
return fmt.Errorf("the bucket %s: %w", delivery.BuilderBuildsBucket, err)
|
||||
default:
|
||||
builds = buildReader{kvStore{kv}}
|
||||
}
|
||||
holds := newHoldWatch(ctx, js, ch.Reconsider, logger)
|
||||
ch.Hold = holds.Held
|
||||
announce := func(ctx context.Context, event, key string) {
|
||||
@@ -210,7 +221,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
ch.Judge = newJudge(judgeDeps{
|
||||
runner: runner, tools: tools, presence: presence, have: have, places: places, trunks: trunks,
|
||||
kept: kvStore{buckets[delivery.CheckerKeptBucket]}, previews: kvStore{buckets[delivery.PreviewsBucket]},
|
||||
repos: repos, logf: logger.Printf,
|
||||
repos: repos, builds: builds, logf: logger.Printf,
|
||||
announce: announce,
|
||||
})
|
||||
ch.Announce = announce
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The states of a build the status line says (ADR 0484 clause 7).
|
||||
const (
|
||||
BuildBuilt = "built"
|
||||
BuildFailed = "failed"
|
||||
BuildWaiting = "waiting"
|
||||
)
|
||||
|
||||
// BuildStates reads the builder's build of a module at a fingerprint of the new form (builds.<module>.<fingerprint>): the
|
||||
// value and whether the key exists.
|
||||
type BuildStates interface {
|
||||
Build(ctx context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error)
|
||||
}
|
||||
|
||||
// NoteOf is the state of a build as the line says it: done is built, failed is failed, and one queued, building or not
|
||||
// begun is waiting.
|
||||
func NoteOf(b delivery.BuilderBuild, found bool) string {
|
||||
switch {
|
||||
case !found:
|
||||
return BuildWaiting
|
||||
case b.State == delivery.BuildDone:
|
||||
return BuildBuilt
|
||||
case b.State == delivery.BuildFailed:
|
||||
return BuildFailed
|
||||
}
|
||||
return BuildWaiting
|
||||
}
|
||||
|
||||
// buildNotes are the state of each build the case's modules carry a fingerprint of, by module, sorted. A module two pull
|
||||
// requests give is said once.
|
||||
func (j *Examiner) buildNotes(ctx context.Context, c *Case) []BuildNote {
|
||||
if j.Builds == nil {
|
||||
return nil
|
||||
}
|
||||
fps := map[string]string{}
|
||||
for _, p := range c.Pulls {
|
||||
for module, cm := range p.Fps {
|
||||
if cm.FP2 != "" {
|
||||
fps[module] = cm.FP2
|
||||
}
|
||||
}
|
||||
}
|
||||
modules := make([]string, 0, len(fps))
|
||||
for m := range fps {
|
||||
modules = append(modules, m)
|
||||
}
|
||||
sort.Strings(modules)
|
||||
var notes []BuildNote
|
||||
for _, m := range modules {
|
||||
b, found, err := j.Builds.Build(ctx, m, fps[m])
|
||||
if err != nil {
|
||||
j.logf("the build of %s at %s cannot be read: %v", m, fps[m], err)
|
||||
found = false
|
||||
}
|
||||
notes = append(notes, BuildNote{Module: m, State: NoteOf(b, found)})
|
||||
}
|
||||
return notes
|
||||
}
|
||||
@@ -522,3 +522,33 @@ func TestTheIndexerIsToldOfAPreviewOnceWhenItIsCreated(t *testing.T) { // 1445/A
|
||||
t.Errorf("told %v, want once of %s", told, key)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeBuilds map[string]delivery.BuilderBuild
|
||||
|
||||
func (f fakeBuilds) Build(_ context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error) {
|
||||
b, ok := f[module+" "+fp2]
|
||||
return b, ok, nil
|
||||
}
|
||||
|
||||
func TestTheOutcomeSaysIfEachCandidatesModuleIsBuiltFailedOrWaiting(t *testing.T) { // N7/AC1 (the checker reads the builder's keys)
|
||||
b := newBench(t)
|
||||
fpA := "src2:" + strings.Repeat("a", 64)
|
||||
fpB := "src2:" + strings.Repeat("b", 64)
|
||||
fpC := "src2:" + strings.Repeat("c", 64)
|
||||
b.ex.Builds = fakeBuilds{
|
||||
"avahi " + fpA: {State: delivery.BuildDone},
|
||||
"mesh-tools " + fpB: {State: delivery.BuildFailed, Error: "boom"},
|
||||
// netcheck has no key yet
|
||||
}
|
||||
p := pullOf(repoA, 7, head1)
|
||||
p.Modules = []string{"avahi", "mesh-tools", "netcheck", "nofp"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"avahi": {FP2: fpA}, "mesh-tools": {FP2: fpB}, "netcheck": {FP2: fpC}, "nofp": {None2: "toolchain not yet reported"}}
|
||||
out := b.judge(t, caseOf(t, p))
|
||||
var got []string
|
||||
for _, n := range out.Builds {
|
||||
got = append(got, n.Module+" "+n.State)
|
||||
}
|
||||
if strings.Join(got, ", ") != "avahi built, mesh-tools failed, netcheck waiting" {
|
||||
t.Errorf("builds = %v, want one note for each module that has a fingerprint, sorted", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ type Examiner struct {
|
||||
// Previews is the previews bucket; nil writes none.
|
||||
Previews KV
|
||||
Modules BuildModules
|
||||
// Builds says how the build of a fingerprint the candidate carries stands (the builder's builds bucket); nil says nothing
|
||||
// of the builds.
|
||||
Builds BuildStates
|
||||
// Announce says a preview was written under the key (EventPreviewWritten); nil says nothing.
|
||||
Announce Announcer
|
||||
// Trunks are the trunk heads of every repository, for the siblings a repository's tests read.
|
||||
@@ -115,18 +118,25 @@ type state struct {
|
||||
func (j *Examiner) Judge(ctx context.Context, c *Case) (Outcome, error) {
|
||||
st := &state{j: j, c: c, levels: map[string]*acc{delivery.LevelUnit: {}, delivery.LevelIntegration: {}}, nodes: map[string]bool{}}
|
||||
if v := st.fetchAll(ctx); v != nil {
|
||||
return st.outcome(*v), nil
|
||||
return st.finish(ctx, *v), nil
|
||||
}
|
||||
for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} {
|
||||
for _, priority := range []bool{true, false} {
|
||||
for _, t := range st.targets {
|
||||
if v := st.runTarget(ctx, t, level, priority); v != nil {
|
||||
return st.outcome(*v), nil
|
||||
return st.finish(ctx, *v), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return st.outcome(delivery.Verdict{State: delivery.VerdictPass}), nil
|
||||
return st.finish(ctx, delivery.Verdict{State: delivery.VerdictPass}), nil
|
||||
}
|
||||
|
||||
// finish is the outcome of the verdict with the state of the builds the candidate's modules carry.
|
||||
func (st *state) finish(ctx context.Context, v delivery.Verdict) Outcome {
|
||||
out := st.outcome(v)
|
||||
out.Builds = st.j.buildNotes(ctx, st.c)
|
||||
return out
|
||||
}
|
||||
|
||||
// outcome fills the verdict's levels, times, machines and composition from what ran.
|
||||
|
||||
Reference in New Issue
Block a user