mesh/delivery covered after its failure: a later walk that contains it was delivered: plan-1792794944815912745 (novox/mesh-sdk at e1f393bb into main, don…
mesh/delivery-group group fix/1930-digest-shape merged: every member is merged
mesh/check pass 403a5d72ab93 on 1ff6d339780c | unit 4545 run, 2097 kept, 13 skipped | integration 4595 run, 2188 kept, 21 skipped | over bound: unit, …
A check ask's answer kept the digest's packages as a map keyed by Go import path;
the node runtime refuses a state value with a field ending in a credential word,
so the end of any check of mesh-controller (internal/token) was never written and
the ask was acked anyway. The level's digest is now delivery.LevelDigest, packages
a sorted list of {package, digest}, reading the old map form too. finish logs an
end refused every time, writes a small failed end naming the write's error, and
the puller gives the ask back instead of acking it when no end could be written.
206 lines
9.3 KiB
Go
206 lines
9.3 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
const runCommit = "0123456789abcdef0123456789abcdef01234567"
|
|
|
|
// recorder is a build module's step tools that record each call.
|
|
type recorder struct {
|
|
calls []string
|
|
fetch map[string]any
|
|
tests map[string]bool // package or module directory -> passed
|
|
digests map[string]string
|
|
}
|
|
|
|
func (r *recorder) steps() AskSteps {
|
|
call := func(name string, answer func(args map[string]any) any) StepFunc {
|
|
return func(ctx context.Context, args map[string]any) (any, error) {
|
|
b, _ := json.Marshal(args)
|
|
r.calls = append(r.calls, name+" "+string(b))
|
|
return answer(args), nil
|
|
}
|
|
}
|
|
return AskSteps{
|
|
Fetch: call("fetch", func(map[string]any) any { return r.fetch }),
|
|
Build: call("build", func(args map[string]any) any {
|
|
return map[string]any{"digest": "sha256:" + args["artifact"].(string), "reference": "artifact-store://" + args["artifact"].(string)}
|
|
}),
|
|
Copy: call("copy", func(args map[string]any) any { return map[string]any{"digest": "sha256:copied"} }),
|
|
Digest: call("digest", func(map[string]any) any { return map[string]any{"level": "unit", "packages": r.digests} }),
|
|
Test: call("test", func(args map[string]any) any {
|
|
passed := true
|
|
if pkgs, ok := args["packages"].([]string); ok {
|
|
for _, p := range pkgs {
|
|
passed = passed && r.tests[p]
|
|
}
|
|
}
|
|
if dir, ok := args["module"].(string); ok {
|
|
passed = r.tests[dir]
|
|
}
|
|
return map[string]any{"passed": passed, "tests": 1}
|
|
}),
|
|
Kept: func(ctx context.Context, level, digest string) (int, bool, error) { return 7, digest == "d-kept", nil },
|
|
}
|
|
}
|
|
|
|
func ask(job any) delivery.StepAsk {
|
|
a := delivery.StepAsk{ID: NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "t", At: time.Now()}
|
|
switch j := job.(type) {
|
|
case delivery.BuildJob:
|
|
a.Build = &j
|
|
case delivery.CheckJob:
|
|
a.Check = &j
|
|
}
|
|
return a
|
|
}
|
|
|
|
// 1887/A3: a build ask fetches once, builds each artifact from the workspace with publish, copies a pinned one without a
|
|
// fetch, and answers each artifact's digest and reference.
|
|
func TestRunAskBuilds(t *testing.T) {
|
|
r := &recorder{fetch: map[string]any{"workspace": "/w/1"}}
|
|
steps := r.steps()
|
|
var released []string
|
|
steps.Release = func(ws string) error { released = append(released, ws); return nil }
|
|
out, err := RunAsk(context.Background(), steps, ask(delivery.BuildJob{Module: "m", Path: "modules/m", Publish: true,
|
|
Fetch: map[string]any{"repository": "novox/x", "commit": runCommit},
|
|
Artifacts: []delivery.AskArtifact{{Artifact: "base", From: "registry/b@sha256:00"}, {Artifact: "bundle"}, {Artifact: "lib"}}}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := strings.Join(r.calls, "\n")
|
|
want := `copy {"artifact":"base","from":"registry/b@sha256:00","module":"m"}
|
|
fetch {"commit":"` + runCommit + `","repository":"novox/x"}
|
|
build {"artifact":"bundle","module":"modules/m","publish":true,"workspace":"/w/1"}
|
|
build {"artifact":"lib","module":"modules/m","publish":true,"workspace":"/w/1"}`
|
|
if got != want {
|
|
t.Fatalf("calls:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
if len(released) != 1 || released[0] != "/w/1" {
|
|
t.Errorf("released %v, want the one workspace", released)
|
|
}
|
|
a := out.(delivery.BuildAnswer)
|
|
if len(a.Artifacts) != 3 || a.Artifacts[1].Reference != "artifact-store://bundle" {
|
|
t.Fatalf("answer %+v", a)
|
|
}
|
|
}
|
|
|
|
// A build ask for another toolchain is refused, so its step ends refused and the asker reads why.
|
|
func TestRunAskRefusesAnotherToolchain(t *testing.T) {
|
|
r := &recorder{fetch: map[string]any{"workspace": "/w"}}
|
|
s := r.steps()
|
|
s.Toolchain = func(context.Context) (delivery.ToolchainUsed, error) {
|
|
return delivery.ToolchainUsed{Version: "go1.27", Recipe: "3"}, nil
|
|
}
|
|
_, err := RunAsk(context.Background(), s, ask(delivery.BuildJob{Module: "m", Path: ".", Fetch: map[string]any{"repository": "novox/x", "commit": runCommit},
|
|
Artifacts: []delivery.AskArtifact{{Artifact: "bundle"}}, Toolchain: &delivery.ToolchainUsed{Version: "go1.26", Recipe: "3"}}))
|
|
var ref *Refusal
|
|
if !errors.As(err, &ref) || len(r.calls) != 0 {
|
|
t.Fatalf("err %v, calls %v", err, r.calls)
|
|
}
|
|
}
|
|
|
|
// 1887/A3, A5a, issue 1887 design limit 1: a check ask runs the levels of one check after one fetch: it digests the unit
|
|
// level, takes the kept verdicts, tests the packages whose tests changed first, and stops at the first failure, running
|
|
// no later level.
|
|
func TestRunAskChecksTheLevelsOfOneCheck(t *testing.T) {
|
|
r := &recorder{
|
|
fetch: map[string]any{"workspace": "/w", "changed": []string{"pkg/b/b_test.go"}},
|
|
digests: map[string]string{"x/pkg/a": "d-a", "x/pkg/b": "d-b", "x/pkg/k": "d-kept"},
|
|
tests: map[string]bool{"x/pkg/a": true, "x/pkg/b": false},
|
|
}
|
|
out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit, delivery.LevelIntegration}, Kept: true,
|
|
Runner: "workspace", RunnerWhen: "modules/mesh-build-go/", Fetch: map[string]any{"repository": "novox/x", "commit": runCommit, "base": runCommit}}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := strings.Join(r.calls, "\n")
|
|
want := `fetch {"base":"` + runCommit + `","commit":"` + runCommit + `","repository":"novox/x"}
|
|
digest {"level":"unit","workspace":"/w"}
|
|
test {"level":"unit","packages":["x/pkg/b"],"workspace":"/w"}`
|
|
if got != want {
|
|
t.Fatalf("calls:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
a := out.(delivery.CheckAnswer)
|
|
if len(a.Levels) != 1 {
|
|
t.Fatalf("levels %+v, want the unit level alone", a.Levels)
|
|
}
|
|
u := a.Levels[0]
|
|
if u.Level != delivery.LevelUnit || len(u.Kept) != 1 || u.Kept[0].Package != "x/pkg/k" || u.Kept[0].Tests != 7 || len(u.Tests) != 1 || u.Digest == nil {
|
|
t.Fatalf("the unit level %+v", u)
|
|
}
|
|
}
|
|
|
|
// ADR 0535 §2, §4: a merge that changes the build module's own code is judged by the candidate's runner: its digest says
|
|
// the candidate ran, and every package runs with nothing kept; an installed module that ignores the runner is planned as
|
|
// any other, the runner dropped. A digest that does not cover the fetch's siblings runs the level whole too.
|
|
func TestRunAskRunsWholeUnderTheRunnerOrWithoutTheSiblings(t *testing.T) {
|
|
for _, tc := range []struct{ judged, want string }{
|
|
{"candidate 999", `digest {"level":"unit","runner":"workspace","workspace":"/w"}
|
|
test {"level":"unit","runner":"workspace","workspace":"/w"}`},
|
|
{"installed", `digest {"level":"unit","runner":"workspace","workspace":"/w"}`},
|
|
} {
|
|
r := &recorder{fetch: map[string]any{"workspace": "/w", "changed": []string{"modules/mesh-build-go/x.go"}},
|
|
digests: map[string]string{"x/pkg/a": "d-kept"}, tests: map[string]bool{}}
|
|
steps := r.steps()
|
|
digest := steps.Digest
|
|
steps.Digest = func(ctx context.Context, args map[string]any) (any, error) {
|
|
_, _ = digest(ctx, args)
|
|
return map[string]any{"level": "unit", "packages": r.digests, "judged_by": tc.judged}, nil
|
|
}
|
|
out, err := RunAsk(context.Background(), steps, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Kept: true,
|
|
Runner: "workspace", RunnerWhen: "modules/mesh-build-go/", Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := strings.Join(r.calls[1:], "\n"); got != tc.want {
|
|
t.Errorf("judged by %s: calls\n%s\nwant\n%s", tc.judged, got, tc.want)
|
|
}
|
|
l := out.(delivery.CheckAnswer).Levels[0]
|
|
if tc.judged == "installed" && (len(l.Kept) != 1 || l.Judged != "") {
|
|
t.Errorf("an installed module's level %+v, want the kept verdict taken", l)
|
|
}
|
|
if tc.judged != "installed" && (len(l.Kept) != 0 || l.Digest != nil || l.Judged != tc.judged) {
|
|
t.Errorf("the candidate's level %+v, want nothing kept and the program named", l)
|
|
}
|
|
}
|
|
r := &recorder{fetch: map[string]any{"workspace": "/w"}, digests: map[string]string{"x/pkg/a": "d-kept"}, tests: map[string]bool{}}
|
|
out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Kept: true,
|
|
Fetch: map[string]any{"repository": "novox/x", "commit": runCommit, "beside": []any{map[string]any{"repository": "novox/mesh-sdk", "commit": runCommit}}}}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if l := out.(delivery.CheckAnswer).Levels[0]; len(l.Kept) != 0 || l.Digest != nil {
|
|
t.Fatalf("a digest without the siblings kept %+v", l)
|
|
}
|
|
}
|
|
|
|
// A Node check tests each module directory, the one whose tests changed first; a conflict ends the ask with the fetch
|
|
// alone, a verdict for the asker.
|
|
func TestRunAskChecksNodeModulesAndConflicts(t *testing.T) {
|
|
r := &recorder{fetch: map[string]any{"workspace": "/w", "modules": []string{"a", "b"}, "changed": []string{"b/test/x.test.ts"}},
|
|
tests: map[string]bool{"a": true, "b": true}}
|
|
s := r.steps()
|
|
s.Digest = nil
|
|
if _, err := RunAsk(context.Background(), s, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fmt.Sprint(r.calls[1:]) != `[test {"level":"unit","module":"b","workspace":"/w"} test {"level":"unit","module":"a","workspace":"/w"}]` {
|
|
t.Fatalf("calls %v", r.calls)
|
|
}
|
|
r = &recorder{fetch: map[string]any{"workspace": "/w", "conflicts": []string{"go.mod"}}}
|
|
out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}}))
|
|
if err != nil || len(r.calls) != 1 || len(out.(delivery.CheckAnswer).Levels) != 0 {
|
|
t.Fatalf("a conflict: %v %v", err, r.calls)
|
|
}
|
|
}
|