Merge branch 'b5i/registry' into feat/registry-milestone

This commit is contained in:
2026-11-05 16:13:21 +01:00
115 changed files with 1195 additions and 920 deletions
+57 -43
View File
@@ -10,7 +10,8 @@ import (
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
trunk "git.novox.be/novox/mesh-delivery/sdk/trunk"
)
// **One ask, one whole job** (novox/hq ADR 0525 §3). A build module runs a pulled ask with its own step tools' bodies,
@@ -35,21 +36,21 @@ type AskSteps struct {
Compare StepFunc
// Toolchain is the toolchain the module builds with now; a build ask naming another is declined (Declining). Nil checks nothing
// (an image or an archive names none).
Toolchain func(ctx context.Context) (delivery.ToolchainUsed, error)
// Kept looks up a kept verdict by level and digest (the checker's kept bucket, delivery.KeptKey): whether one is
Toolchain func(ctx context.Context) (sdkstep.ToolchainUsed, error)
// Kept looks up a kept verdict by level and digest (the checker's kept bucket, the checker seat's kept key): whether one is
// kept and how many tests it counted. Nil takes none.
Kept func(ctx context.Context, level, digest string) (tests int, found bool, err error)
// Release removes a workspace once the ask's steps on it ended (the module's release tool's body); nil leaves it to
// the module's sweep.
Release func(workspace string) error
Logf func(format string, args ...any)
// Has says whether this node has a need a package's tests declare (delivery.AskNeeds, issue 890); a package needing
// Has says whether this node has a need a package's tests declare (sdkstep.AskNeeds, issue 890); a package needing
// what it lacks is not tested here but named in the level's Unmet, for the asker to place. Nil has nothing.
Has func(need string) bool
}
// RunAsk runs ask's job with steps and answers a delivery.BuildAnswer or a delivery.CheckAnswer.
func RunAsk(ctx context.Context, steps AskSteps, ask delivery.StepAsk) (any, error) {
// RunAsk runs ask's job with steps and answers a sdkstep.BuildAnswer or a CheckAnswer.
func RunAsk(ctx context.Context, steps AskSteps, ask sdkstep.StepAsk) (any, error) {
if ask.Build != nil {
job := *ask.Build
job.Fetch = withPackages(job.Fetch, ask.Packages)
@@ -57,7 +58,7 @@ func RunAsk(ctx context.Context, steps AskSteps, ask delivery.StepAsk) (any, err
}
if ask.Check != nil {
job := *ask.Check
job.Fetch = withPackages(job.Fetch, ask.Packages)
job.Fetch = *withPackages(&job.Fetch, ask.Packages)
return steps.check(ctx, job)
}
return nil, Refuse("the ask %s holds no job", ask.ID)
@@ -66,13 +67,28 @@ func RunAsk(ctx context.Context, steps AskSteps, ask delivery.StepAsk) (any, err
// withPackages is the fetch's arguments with the ask's package versions (novox/hq ADR 0573 §2), which the build module
// writes into the workspace it fetches; a copy, so the ask is left as it was. An ask with none, or a copy alone that
// fetches nothing, is left as it is.
func withPackages(fetch map[string]any, packages delivery.PackageVersions) map[string]any {
func withPackages(fetch *sdkstep.Fetch, packages sdkstep.PackageVersions) *sdkstep.Fetch {
if len(packages) == 0 || fetch == nil {
return fetch
}
out := maps.Clone(fetch)
out["packages"] = maps.Clone(packages)
return out
out := *fetch
out.Packages = maps.Clone(packages)
return &out
}
// FetchArgs is a fetch as the fetch tool takes it (sdkstep.FetchRequest's Args and the build module's own, beside): the
// ask's fetch through the one JSON form, without the layout version, which no tool takes.
func FetchArgs(f sdkstep.Fetch) (map[string]any, error) {
b, err := json.Marshal(f)
if err != nil {
return nil, err
}
args := map[string]any{}
if err := json.Unmarshal(b, &args); err != nil {
return nil, err
}
delete(args, "v")
return args, nil
}
func raw(v any) (json.RawMessage, error) {
@@ -91,11 +107,18 @@ type fetched struct {
Modules []string `json:"modules"`
}
func (s AskSteps) fetch(ctx context.Context, args map[string]any) (json.RawMessage, fetched, error) {
func (s AskSteps) fetch(ctx context.Context, fetch *sdkstep.Fetch) (json.RawMessage, fetched, error) {
if s.Fetch == nil {
return nil, fetched{}, Refuse("this build module fetches nothing")
}
if _, merges := args["base"]; merges {
if fetch == nil {
return nil, fetched{}, Refuse("the ask names no fetch")
}
args, err := FetchArgs(*fetch)
if err != nil {
return nil, fetched{}, err
}
if fetch.Base != "" {
Phase(ctx, "fetch and merge")
} else {
Phase(ctx, "fetch")
@@ -118,7 +141,7 @@ func (s AskSteps) fetch(ctx context.Context, args map[string]any) (json.RawMessa
// onToolchain is the toolchain this node builds with when the ask names one and this module reports its own, nil when
// either is absent. A node on another toolchain than the ask names answers a Declining: it is not on the agreed one and
// sits out, for a node that is to take the ask (novox/hq ADR 0584 clause 2).
func (s AskSteps) onToolchain(ctx context.Context, want *delivery.ToolchainUsed) (*delivery.ToolchainUsed, error) {
func (s AskSteps) onToolchain(ctx context.Context, want *sdkstep.ToolchainUsed) (*sdkstep.ToolchainUsed, error) {
if want == nil || s.Toolchain == nil {
return nil, nil
}
@@ -134,8 +157,8 @@ func (s AskSteps) onToolchain(ctx context.Context, want *delivery.ToolchainUsed)
return &tc, nil
}
func (s AskSteps) build(ctx context.Context, job delivery.BuildJob) (any, error) {
answer := delivery.BuildAnswer{}
func (s AskSteps) build(ctx context.Context, job sdkstep.BuildJob) (any, error) {
answer := sdkstep.BuildAnswer{}
tc, err := s.onToolchain(ctx, job.Toolchain)
if err != nil {
return nil, err
@@ -149,7 +172,7 @@ func (s AskSteps) build(ctx context.Context, job delivery.BuildJob) (any, error)
if err != nil {
return nil, err
}
if _, candidate := job.Fetch["base"]; candidate {
if job.Fetch.Base != "" {
// A candidate is built from its head merged onto the trunk; a head that does not merge leaves the tree at the
// trunk, and building that would store the trunk's build under the candidate's fingerprint.
if len(got.Conflicts) > 0 {
@@ -225,7 +248,7 @@ func (s AskSteps) build(ctx context.Context, job delivery.BuildJob) (any, error)
if err := json.Unmarshal(r, &built); err != nil || built.Digest == "" {
return nil, fmt.Errorf("the build of %s answered no digest: %s", a.Artifact, r)
}
answer.Artifacts = append(answer.Artifacts, delivery.AskBuilt{Artifact: a.Artifact, Digest: built.Digest, Reference: built.Reference})
answer.Artifacts = append(answer.Artifacts, sdkstep.AskBuilt{Artifact: a.Artifact, Digest: built.Digest, Reference: built.Reference})
}
if s.Imports != nil && f.Workspace != "" {
out, err := s.Imports(ctx, map[string]any{"workspace": f.Workspace, "module": job.Path})
@@ -242,7 +265,7 @@ func (s AskSteps) build(ctx context.Context, job delivery.BuildJob) (any, error)
if err := json.Unmarshal(r, &read); err != nil {
return nil, fmt.Errorf("the imports of %s answered what is not a list: %s", job.Module, r)
}
if err := delivery.CheckImports(read.Imports); err != nil {
if err := trunk.CheckImports(read.Imports); err != nil {
return nil, err
}
answer.Imports = read.Imports
@@ -250,16 +273,16 @@ func (s AskSteps) build(ctx context.Context, job delivery.BuildJob) (any, error)
return answer, nil
}
func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error) {
if _, err := s.onToolchain(ctx, job.Toolchain); err != nil {
func (s AskSteps) check(ctx context.Context, job sdkstep.CheckJob) (any, error) {
if _, err := s.onToolchain(ctx, checkToolchain(ctx)); err != nil {
return nil, err
}
r, f, err := s.fetch(ctx, job.Fetch)
r, f, err := s.fetch(ctx, &job.Fetch)
if err != nil {
return nil, err
}
defer s.release(f.Workspace)
answer := delivery.CheckAnswer{Fetch: r}
answer := CheckAnswer{Fetch: r}
if len(f.Conflicts) > 0 {
return answer, nil // the asker reads the conflict from the fetch's answer: a verdict, not an error
}
@@ -273,7 +296,7 @@ func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error)
// Every level runs, also after one that did not pass: one check names every failure, not only the first (the operator,
// 2026-11-02: "A failed check shows only its first fault").
for _, level := range job.Levels {
la := delivery.LevelAnswer{Level: level}
la := sdkstep.LevelAnswer{Level: level}
_, err := s.level(ctx, job, level, f, runner, &la)
answer.Levels = append(answer.Levels, la)
if err != nil {
@@ -284,13 +307,13 @@ func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error)
}
// 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) {
func (s AskSteps) level(ctx context.Context, job sdkstep.CheckJob, level string, f fetched, runner string, la *sdkstep.LevelAnswer) (bool, error) {
withRunner := func(args map[string]any) map[string]any {
if runner != "" {
args["runner"] = runner
}
if len(job.NodeNames) > 0 {
args[delivery.NodeNamesArg] = job.NodeNames // the names check's (ADR 0567 §12)
args[sdkstep.NodeNamesArg] = job.NodeNames // the names check's (ADR 0567 §12)
}
return args
}
@@ -390,7 +413,7 @@ func (s AskSteps) release(workspace string) {
// is digested for keeping (ADR 0535 §4); when the installed module answers it (it does not take the runner), the level is
// planned as any other and the runner is dropped. The level 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, bool) {
func (s AskSteps) plan(ctx context.Context, job sdkstep.CheckJob, level string, f fetched, runner string, withRunner func(map[string]any) map[string]any, la *sdkstep.LevelAnswer) ([]string, bool, bool) {
if job.Run != "" {
return nil, true, true
}
@@ -406,7 +429,7 @@ func (s AskSteps) plan(ctx context.Context, job delivery.CheckJob, level string,
if err != nil {
return nil, true, true
}
var d delivery.LevelDigest // packages kept as a sorted list, never keyed by a package path (issue 1930)
var d sdkstep.LevelDigest // packages kept as a sorted list, never keyed by a package path (issue 1930)
if err := json.Unmarshal(r, &d); err != nil {
return nil, true, true
}
@@ -437,16 +460,16 @@ func (s AskSteps) plan(ctx context.Context, job delivery.CheckJob, level string,
s.Logf("the kept verdict of %s at %s cannot be read, so it runs: %v", p, level, err)
}
if err == nil && found {
la.Kept = append(la.Kept, delivery.KeptTaken{Package: p, Digest: pd.Digest, Tests: tests})
la.Kept = append(la.Kept, sdkstep.KeptTaken{Package: p, Digest: pd.Digest, Tests: tests})
continue
}
}
if needs := d.NeedsOf(p); len(delivery.Lacks(needs, s.Has)) > 0 {
if needs := d.NeedsOf(p); len(sdkstep.Lacks(needs, s.Has)) > 0 {
// Its tests would only skip here (issue 890): it is left for a node that has what they need.
la.Unmet = append(la.Unmet, delivery.PackageNeeds{Package: p, Needs: needs})
la.Unmet = append(la.Unmet, sdkstep.PackageNeeds{Package: p, Needs: needs})
if s.Logf != nil {
s.Logf("the %s level of %s is left to a node with %s: this node lacks %s", level, p,
strings.Join(needs, ", "), strings.Join(delivery.Lacks(needs, s.Has), ", "))
strings.Join(needs, ", "), strings.Join(sdkstep.Lacks(needs, s.Has), ", "))
}
continue
}
@@ -456,18 +479,9 @@ func (s AskSteps) plan(ctx context.Context, job delivery.CheckJob, level string,
}
// 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
}
var refs []struct {
Repository string `json:"repository"`
Commit string `json:"commit"`
}
_ = json.Unmarshal(raw, &refs)
func besideOf(fetch sdkstep.Fetch) []string {
var out []string
for _, r := range refs {
for _, r := range fetch.Beside {
out = append(out, r.Repository+" "+r.Commit)
}
sort.Strings(out)
@@ -0,0 +1,52 @@
package buildstep
// Two layouts the part build-step (mesh-delivery-sdk buildstep, as the three node-build seats generate it) lacks, which a
// check ask still carries; both are kept here and nowhere else, so that deleting this file is the whole change once the
// part has them (B5i finding 1 and 2, issue 1966):
//
// - the check job's toolchain (novox/hq ADR 0584 clause 2): the part's CheckJob has no `toolchain`, so a node on
// another toolchain than the checker agreed would take the ask and record a pass it did not run. The Puller reads
// the field from the ask's own bytes and the run takes it from its context.
// - the check answer's fetch: the part types the answer's `fetch` as Fetch (the arguments), but the fetch tool's answer
// (workspace, merge, conflicts, changed, modules) is what the checker reads a conflict from.
import (
"context"
"encoding/json"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
type checkToolchainKey struct{}
// checkToolchainOf reads the toolchain a check ask names, from the ask's bytes; nil when it names none or is no check.
func checkToolchainOf(data []byte) *sdkstep.ToolchainUsed {
var a struct {
Check *struct {
Toolchain *sdkstep.ToolchainUsed `json:"toolchain"`
} `json:"check"`
}
if json.Unmarshal(data, &a) != nil || a.Check == nil {
return nil
}
return a.Check.Toolchain
}
// WithCheckToolchain is ctx carrying the toolchain the ask in data names for its check.
func WithCheckToolchain(ctx context.Context, data []byte) context.Context {
if tc := checkToolchainOf(data); tc != nil {
return context.WithValue(ctx, checkToolchainKey{}, tc)
}
return ctx
}
func checkToolchain(ctx context.Context) *sdkstep.ToolchainUsed {
tc, _ := ctx.Value(checkToolchainKey{}).(*sdkstep.ToolchainUsed)
return tc
}
// CheckAnswer is a check ask's answer with the fetch tool's own answer as it is.
type CheckAnswer struct {
Fetch json.RawMessage `json:"fetch"`
Levels []sdkstep.LevelAnswer `json:"levels,omitempty"`
}
+86 -50
View File
@@ -5,13 +5,14 @@ import (
"encoding/json"
"errors"
"fmt"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"slices"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
nodebuildgo "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
nodebuildnode "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
const runCommit = "0123456789abcdef0123456789abcdef01234567"
@@ -22,7 +23,7 @@ type recorder struct {
fetch map[string]any
tests map[string]bool // package or module directory -> passed
digests map[string]string
needs []delivery.PackageNeeds
needs []sdkstep.PackageNeeds
}
func (r *recorder) steps() AskSteps {
@@ -58,12 +59,12 @@ func (r *recorder) steps() AskSteps {
}
}
func ask(job any) delivery.StepAsk {
a := delivery.StepAsk{ID: sdkstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "t", At: time.Now()}
func ask(job any) sdkstep.StepAsk {
a := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskCheck, Language: sdkstep.AskGo, Asker: "t", At: time.Now()}
switch j := job.(type) {
case delivery.BuildJob:
case sdkstep.BuildJob:
a.Build = &j
case delivery.CheckJob:
case sdkstep.CheckJob:
a.Check = &j
}
return a
@@ -84,15 +85,15 @@ func TestRunAskBuilds(t *testing.T) {
return map[string]any{"imports": []string{"git.novox.be/novox/mesh-sdk/go/announce", "git.novox.be/novox/mesh-sdk/go/delivery"}}, nil
}
// Issue 1585: the ask's package versions reach the fetch, and the ask's own fetch arguments stay as they were.
a := 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", Seat: "go-package-registry", Version: "v0.1.15"}}})
a.Packages = delivery.PackageVersions{"git.novox.be/novox/mesh-sdk/go": runCommit}
a := ask(sdkstep.BuildJob{Module: "m", Path: "modules/m", Publish: true,
Fetch: &sdkstep.Fetch{Repository: "novox/x", Commit: runCommit},
Artifacts: []sdkstep.AskArtifact{{Artifact: "base", From: "registry/b@sha256:00"}, {Artifact: "bundle"}, {Artifact: "lib", Seat: "go-package-registry", Version: "v0.1.15"}}})
a.Packages = sdkstep.PackageVersions{"git.novox.be/novox/mesh-sdk/go": runCommit}
out, err := RunAsk(context.Background(), steps, a)
if err != nil {
t.Fatal(err)
}
if _, put := a.Build.Fetch["packages"]; put {
if len(a.Build.Fetch.Packages) != 0 {
t.Error("the ask's fetch arguments were changed")
}
got := strings.Join(r.calls, "\n")
@@ -107,7 +108,7 @@ imports {"module":"modules/m","workspace":"/w/1"}`
if len(released) != 1 || released[0] != "/w/1" {
t.Errorf("released %v, want the one workspace", released)
}
answer := out.(delivery.BuildAnswer)
answer := out.(sdkstep.BuildAnswer)
if len(answer.Artifacts) != 3 || answer.Artifacts[1].Reference != "artifact-store://bundle" {
t.Fatalf("answer %+v", answer)
}
@@ -120,18 +121,24 @@ imports {"module":"modules/m","workspace":"/w/1"}`
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
s.Toolchain = func(context.Context) (sdkstep.ToolchainUsed, error) {
return sdkstep.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"}}))
_, err := RunAsk(context.Background(), s, ask(sdkstep.BuildJob{Module: "m", Path: ".", Fetch: &sdkstep.Fetch{Repository: "novox/x", Commit: runCommit},
Artifacts: []sdkstep.AskArtifact{{Artifact: "bundle"}}, Toolchain: &sdkstep.ToolchainUsed{Version: "go1.26", Recipe: "3"}}))
var dec *Declining
if !errors.As(err, &dec) || len(dec.Lacks) != 1 || len(r.calls) != 0 {
t.Fatalf("err %v, calls %v", err, r.calls)
}
// A check ask names the toolchain too (ADR 0584), and a node on another declines it before it fetches.
_, err = RunAsk(context.Background(), s, ask(delivery.CheckJob{Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}, Levels: []string{delivery.LevelUnit},
Toolchain: &delivery.ToolchainUsed{Version: "go1.26", Recipe: "3"}}))
// A check ask names the toolchain too (ADR 0584), and a node on another declines it before it fetches. The part's
// CheckJob has no field for it, so the ask's own bytes carry it (askrun_gaps.go).
check := ask(sdkstep.CheckJob{Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}, Levels: []string{sdkstep.LevelUnit}})
raw, _ := json.Marshal(check)
var doc map[string]any
_ = json.Unmarshal(raw, &doc)
doc["check"].(map[string]any)["toolchain"] = map[string]any{"version": "go1.26", "recipe": "3"}
raw, _ = json.Marshal(doc)
_, err = RunAsk(WithCheckToolchain(context.Background(), raw), s, check)
if !errors.As(err, &dec) || len(r.calls) != 0 {
t.Fatalf("a check ask: err %v, calls %v", err, r.calls)
}
@@ -146,8 +153,8 @@ func TestRunAskChecksTheLevelsOfOneCheck(t *testing.T) {
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}}))
out, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit, sdkstep.LevelIntegration}, Kept: true,
Runner: "workspace", RunnerWhen: "modules/mesh-build-go/", Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit, Base: runCommit}}))
if err != nil {
t.Fatal(err)
}
@@ -162,20 +169,20 @@ test {"level":"integration","packages":["x/pkg/a"],"workspace":"/w"}`
if got != want {
t.Fatalf("calls:\n%s\nwant:\n%s", got, want)
}
a := out.(delivery.CheckAnswer)
a := out.(CheckAnswer)
if len(a.Levels) != 2 {
t.Fatalf("levels %+v, want both levels", 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) != 2 || u.Digest == nil {
if u.Level != sdkstep.LevelUnit || len(u.Kept) != 1 || u.Kept[0].Package != "x/pkg/k" || u.Kept[0].Tests != 7 || len(u.Tests) != 2 || u.Digest == nil {
t.Fatalf("the unit level %+v", u)
}
// ADR 0567 §12: a check's node names reach the digest and test tools, for the names check (a kept verdict misses when
// they change).
r.calls = nil
if _, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, NodeNames: []string{"anchor"},
Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})); err != nil || !strings.Contains(strings.Join(r.calls, "\n"), `digest {"level":"unit","node-names":["anchor"]`) ||
if _, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, NodeNames: []string{"anchor"},
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}})); err != nil || !strings.Contains(strings.Join(r.calls, "\n"), `digest {"level":"unit","node-names":["anchor"]`) ||
!strings.Contains(strings.Join(r.calls, "\n"), `test {"level":"unit","node-names":["anchor"]`) {
t.Fatalf("the node names are not handed to the digest and test tools: %v, %v", r.calls, err)
}
@@ -184,19 +191,19 @@ test {"level":"integration","packages":["x/pkg/a"],"workspace":"/w"}`
// tested on a node that has it.
for _, has := range []bool{false, true} {
r.calls, r.tests["x/pkg/b"] = nil, true
r.needs = []delivery.PackageNeeds{{Package: "x/pkg/a", Needs: []string{delivery.NeedNodeBuildNode}}}
r.needs = []sdkstep.PackageNeeds{{Package: "x/pkg/a", Needs: []string{sdkstep.NeedNodeBuildNode}}}
steps := r.steps()
if has {
steps.Has = func(n string) bool { return n == delivery.NeedNodeBuildNode }
steps.Has = func(n string) bool { return n == sdkstep.NeedNodeBuildNode }
}
out, err := RunAsk(context.Background(), steps, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Kept: true,
Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}}))
out, err := RunAsk(context.Background(), steps, ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Kept: true,
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
if err != nil {
t.Fatal(err)
}
u := out.(delivery.CheckAnswer).Levels[0]
u := out.(CheckAnswer).Levels[0]
tested := strings.Contains(strings.Join(r.calls, "\n"), `"x/pkg/a"`)
if tested != has || (len(u.Unmet) == 0) == !has || (!has && (u.Unmet[0].Package != "x/pkg/a" || delivery.UnmetNeeds([]delivery.LevelAnswer{u})[0] != delivery.NeedNodeBuildNode)) {
if tested != has || (len(u.Unmet) == 0) == !has || (!has && (u.Unmet[0].Package != "x/pkg/a" || sdkstep.UnmetNeeds([]sdkstep.LevelAnswer{u})[0] != sdkstep.NeedNodeBuildNode)) {
t.Fatalf("has %v: tested x/pkg/a %v, unmet %+v, calls %v", has, tested, u.Unmet, r.calls)
}
}
@@ -206,12 +213,12 @@ test {"level":"integration","packages":["x/pkg/a"],"workspace":"/w"}`
r.calls, r.digests = nil, digests
steps := r.steps()
steps.Has = func(string) bool { return true }
out, err := RunAsk(context.Background(), steps, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Kept: true, Packages: []string{"x/pkg/a"},
Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}}))
out, err := RunAsk(context.Background(), steps, ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Kept: true, Packages: []string{"x/pkg/a"},
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
if err != nil {
t.Fatal(err)
}
u := out.(delivery.CheckAnswer).Levels[0]
u := out.(CheckAnswer).Levels[0]
if last := r.calls[len(r.calls)-1]; !strings.HasSuffix(last, `"packages":["x/pkg/a"],"workspace":"/w"}`) || len(u.Kept) != 0 || len(u.Tests) != 1 {
t.Fatalf("digests %v: calls %v, kept %+v: want x/pkg/a alone tested, nothing taken kept", digests != nil, r.calls, u.Kept)
}
@@ -235,15 +242,15 @@ test {"level":"unit","runner":"workspace","workspace":"/w"}`},
_, _ = 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}}))
out, err := RunAsk(context.Background(), steps, ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Kept: true,
Runner: "workspace", RunnerWhen: "modules/mesh-build-go/", Fetch: sdkstep.Fetch{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]
l := out.(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)
}
@@ -252,12 +259,12 @@ test {"level":"unit","runner":"workspace","workspace":"/w"}`},
}
}
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}}}}))
out, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Kept: true,
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit, Beside: []sdkstep.FetchBeside{{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 {
if l := out.(CheckAnswer).Levels[0]; len(l.Kept) != 0 || l.Digest != nil {
t.Fatalf("a digest without the siblings kept %+v", l)
}
}
@@ -269,15 +276,15 @@ func TestRunAskChecksNodeModulesAndConflicts(t *testing.T) {
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 {
if _, err := RunAsk(context.Background(), s, ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Fetch: sdkstep.Fetch{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 {
out, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
if err != nil || len(r.calls) != 1 || len(out.(CheckAnswer).Levels) != 0 {
t.Fatalf("a conflict: %v %v", err, r.calls)
}
}
@@ -301,8 +308,8 @@ func TestRunAskRunsEveryPackageAndLevelAfterAFailure(t *testing.T) {
if _, ok := row.tests["x/pkg/c"]; ok {
r.digests["x/pkg/c"] = "d-c"
}
out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit, delivery.LevelIntegration},
Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}}))
out, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit, sdkstep.LevelIntegration},
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
if err != nil {
t.Fatal(err)
}
@@ -317,7 +324,7 @@ func TestRunAskRunsEveryPackageAndLevelAfterAFailure(t *testing.T) {
if want := append(slices.Clone(row.want), row.want...); fmt.Sprint(got) != fmt.Sprint(want) {
t.Errorf("%s: tested %v, want %v", row.name, got, want)
}
if a := out.(delivery.CheckAnswer); len(a.Levels) != 2 || len(a.Levels[0].Tests) != len(row.want) {
if a := out.(CheckAnswer); len(a.Levels) != 2 || len(a.Levels[0].Tests) != len(row.want) {
t.Errorf("%s: levels %+v, want both, each with every test result", row.name, a.Levels)
}
}
@@ -326,12 +333,41 @@ func TestRunAskRunsEveryPackageAndLevelAfterAFailure(t *testing.T) {
tests: map[string]bool{"a": true, "b": false}}
s := r.steps()
s.Digest = nil
out, err := RunAsk(context.Background(), s, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit, delivery.LevelIntegration},
Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}}))
out, err := RunAsk(context.Background(), s, ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit, sdkstep.LevelIntegration},
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
if err != nil {
t.Fatal(err)
}
if a := out.(delivery.CheckAnswer); len(r.calls) != 5 || len(a.Levels) != 2 || len(a.Levels[0].Tests) != 2 {
if a := out.(CheckAnswer); len(r.calls) != 5 || len(a.Levels) != 2 || len(a.Levels[0].Tests) != 2 {
t.Fatalf("calls %v, levels %+v: want b then a at each of the two levels", r.calls, a.Levels)
}
}
// A seat's own ask type (the generated one of any of the three build seats) is the neutral ask through the form's one
// conversion (sdkstep.AskOf), reads for its step description through the same conversion (askRead), and reaches the
// fetch tool as the fetch's arguments alone: no layout version, nothing of the ask's.
func TestASeatsAskTypeRunsAsTheNeutralAsk(t *testing.T) {
at := time.Now().UTC()
fetch := &nodebuildgo.Fetch{Commit: runCommit, Repository: "novox/x", Dir: "modules/m", Paths: []string{"src"}}
goAsk := nodebuildgo.StepAsk{V: sdkstep.StepAskVersion, ID: sdkstep.NewID(), Kind: sdkstep.AskDelivery, Language: sdkstep.AskGo, Asker: "mesh-delivery-builder", At: &at,
Build: &nodebuildgo.StepAskBuild{Module: "m", Path: "modules/m", Fetch: fetch, Artifacts: []nodebuildgo.AskArtifact{{Artifact: "bundle"}}},
Packages: map[string]string{"git.novox.be/novox/mesh-sdk/go": runCommit}}
a, err := sdkstep.AskOf(goAsk)
if err != nil {
t.Fatal(err)
}
if read := askRead(a); read.Kind != sdkstep.AskDelivery || read.Build == nil || read.Build.Module != "m" {
t.Errorf("the step description reads %+v", read)
}
r := &recorder{fetch: map[string]any{"workspace": "/w"}}
if _, err := RunAsk(context.Background(), r.steps(), a); err != nil {
t.Fatal(err)
}
want := `fetch {"commit":"` + runCommit + `","dir":"modules/m","packages":{"git.novox.be/novox/mesh-sdk/go":"` + runCommit + `"},"paths":["src"],"repository":"novox/x"}`
if len(r.calls) == 0 || r.calls[0] != want {
t.Errorf("the fetch tool was called with %v, want %s", r.calls, want)
}
if back, err := sdkstep.AskAs[nodebuildnode.StepAsk](a); err != nil || back.Build == nil || back.Build.Module != "m" {
t.Errorf("the ask as another seat's type: %+v, %v", back, err)
}
}
@@ -10,7 +10,7 @@ import (
"strings"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/buildcpu"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// **A build seat's capacity follows the CPU its node gives builds** (issue 1806's follow-up; issue 1890, 2026-10-26
@@ -30,7 +30,7 @@ const CoresPerStepSetting = "cores-per-step"
// check on mesh-build-go takes what it is given (three at once took 965 % of the workstation's 1200 % slice, about 4 cores each,
// and ran past the 30-minute step bound), so a Go step is given 6, half again what starved; a Node step (tsc, esbuild, a
// test runner: median 15 s) and an image build (buildah, its RUN lines mostly fetches) need 2.
var DefaultCoresPerStep = map[string]float64{delivery.AskGo: 6, delivery.AskNode: 2, delivery.AskImage: 2}
var DefaultCoresPerStep = map[string]float64{sdkstep.AskGo: 6, sdkstep.AskNode: 2, sdkstep.AskImage: 2}
// Quota is the build slice's CPU quota as this process's cgroup holds it, in percent of one processor (1200 is 12
// cores), and where it was read: the cgroup's cpu.max (buildcpu.ReadInForce, the tighter of its unit's and its slice's),
@@ -54,11 +54,11 @@ func Quota() (percent int, from string) {
// one processor, 0 when none is known) and the cores one of its steps needs. Without a quota it is the configured one;
// with one, min(configured, floor(quota cores / coresPerStep)), and one at least while the configured one is above
// zero. A coresPerStep of zero or less is taken as 1.
func EffectiveCapacity(configured, quotaPercent int, quotaFrom string, coresPerStep float64) delivery.Capacity {
func EffectiveCapacity(configured, quotaPercent int, quotaFrom string, coresPerStep float64) sdkstep.Capacity {
if coresPerStep <= 0 || math.IsNaN(coresPerStep) || math.IsInf(coresPerStep, 0) {
coresPerStep = 1
}
c := delivery.Capacity{Configured: configured, Effective: configured, QuotaFrom: quotaFrom, CoresPerStep: coresPerStep}
c := sdkstep.Capacity{Configured: configured, Effective: configured, QuotaFrom: quotaFrom, CoresPerStep: coresPerStep}
switch {
case configured <= 0:
c.Effective = 0
@@ -8,6 +8,7 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
@@ -25,14 +26,14 @@ func TestTheFailureContract(t *testing.T) {
// 1890/AC7: the SDK's start sweep keeps the left-running case, and the kit refuses a sweep that ends nothing or ends
// everything.
t.Run("the left-running case", func(t *testing.T) {
LeftRunning(t, func(ctx context.Context, scan sdkstep.Scan, node string, before time.Time, logf func(string, ...any)) {
sdkstep.EndStaleAtStart(ctx, scan, node, before, logf)
LeftRunning(t, func(ctx context.Context, scan buildrun.Scan, node string, before time.Time, logf func(string, ...any)) {
buildrun.EndStaleAtStart(ctx, scan, node, before, logf)
})
if p := JudgeLeftRunning(func(context.Context, sdkstep.Scan, string, time.Time, func(string, ...any)) {}); len(p) != 11 {
if p := JudgeLeftRunning(func(context.Context, buildrun.Scan, string, time.Time, func(string, ...any)) {}); len(p) != 11 {
t.Errorf("a sweep that ends nothing: %d problems, want 11 (three steps not ended and unsaid, one owner unnamed, two asks not deleted and unsaid):\n%s", len(p), strings.Join(p, "\n"))
}
wrongNode := func(ctx context.Context, scan sdkstep.Scan, _ string, before time.Time, logf func(string, ...any)) {
sdkstep.EndStaleAtStart(ctx, scan, "kitother", before, logf)
wrongNode := func(ctx context.Context, scan buildrun.Scan, _ string, before time.Time, logf func(string, ...any)) {
buildrun.EndStaleAtStart(ctx, scan, "kitother", before, logf)
}
if p := JudgeLeftRunning(wrongNode); len(p) == 0 {
t.Error("a sweep of another node's steps is not refused")
@@ -10,9 +10,9 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// **A step left running by a previous process** (novox/hq issue 1890, note 21): when a delivery replaced mesh-build-go
@@ -23,9 +23,9 @@ import (
// catalogue check (mesh-catalog checks/failurekit) refuses a build module whose tests do not call LeftRunning.
// Sweep is a build module's own start sweep, as its main runs it once at its start: it ends node's steps in scan that
// no process of the module runs any more (buildstep.EndStaleAtStart, here with the runtime already serving), before
// no process of the module runs any more (buildrun.EndStaleAtStart, here with the runtime already serving), before
// being the start of the process, saying each end with logf.
type Sweep func(ctx context.Context, scan buildstep.Scan, node string, before time.Time, logf func(string, ...any))
type Sweep func(ctx context.Context, scan buildrun.Scan, node string, before time.Time, logf func(string, ...any))
// LeftNode is the node the case's steps are of.
const LeftNode = "kitnode"
@@ -47,12 +47,12 @@ func leftCases() []leftCase {
{id: "mvpwlkzi7e6ca8", tool: "check ask", owner: gone, age: 5 * time.Minute, deleted: true}, // note 21's ghost
{id: "mvpwlkzi7e6ca9", tool: "delivery ask", owner: gone, age: -time.Second, deleted: true},
{id: "gonehand1", tool: "build_x_test", owner: gone, age: time.Minute, ended: true},
{id: "oldhand1", tool: "build_x_fetch", age: time.Hour, ended: true}, // no owner: an older build's
{id: "mvpwlkzi7e6caa", tool: "check ask", age: time.Hour, ended: true}, // no owner, past the bound
{id: "mvpwlkzi7e6cab", tool: "check ask", age: 5 * time.Minute}, // no owner, the stream's still
{id: "mvpwlkzi7e6cac", tool: "check ask", owner: buildstep.Owner, age: time.Minute}, // this process's
{id: "minehand1", tool: "build_x_test", owner: buildstep.Owner, age: -time.Second},
{id: "donehand1", tool: "build_x_test", owner: gone, age: time.Hour, state: delivery.StepPassed},
{id: "oldhand1", tool: "build_x_fetch", age: time.Hour, ended: true}, // no owner: an older build's
{id: "mvpwlkzi7e6caa", tool: "check ask", age: time.Hour, ended: true}, // no owner, past the bound
{id: "mvpwlkzi7e6cab", tool: "check ask", age: 5 * time.Minute}, // no owner, the stream's still
{id: "mvpwlkzi7e6cac", tool: "check ask", owner: buildrun.Owner, age: time.Minute}, // this process's
{id: "minehand1", tool: "build_x_test", owner: buildrun.Owner, age: -time.Second},
{id: "donehand1", tool: "build_x_test", owner: gone, age: time.Hour, state: sdkstep.StepPassed},
{id: "otherhand1", tool: "build_x_test", owner: gone, age: time.Hour, node: "kitother"},
}
}
@@ -76,10 +76,10 @@ func JudgeLeftRunning(sweep Sweep) []string {
node = LeftNode
}
if state == "" {
state = delivery.StepRunning
state = sdkstep.StepRunning
}
key, _ := delivery.StepKey(node, c.id)
raw, err := delivery.EncodeStep(delivery.Step{ID: c.id, Tool: c.tool, Node: node, State: state, Started: before.Add(-c.age).UTC(), Owner: c.owner})
key, _ := sdkstep.StepKey(node, c.id)
raw, err := sdkstep.EncodeStep(sdkstep.Step{ID: c.id, Tool: c.tool, Node: node, State: state, Started: before.Add(-c.age).UTC(), Owner: c.owner})
if err != nil {
return []string{"the kit's step " + c.id + ": " + err.Error()}
}
@@ -87,9 +87,9 @@ func JudgeLeftRunning(sweep Sweep) []string {
}
served := make(chan struct{})
close(served)
was := buildstep.StaleServed
buildstep.StaleServed = func() <-chan struct{} { return served }
defer func() { buildstep.StaleServed = was }()
was := buildrun.StaleServed
buildrun.StaleServed = func() <-chan struct{} { return served }
defer func() { buildrun.StaleServed = was }()
var mu sync.Mutex
var said []string
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
@@ -108,7 +108,7 @@ func JudgeLeftRunning(sweep Sweep) []string {
if node == "" {
node = LeftNode
}
key, _ := delivery.StepKey(node, c.id)
key, _ := sdkstep.StepKey(node, c.id)
raw := st.get(key)
if c.deleted {
if raw != nil {
@@ -124,17 +124,17 @@ func JudgeLeftRunning(sweep Sweep) []string {
problems = append(problems, "left running: the step "+key+" is gone after the sweep, want it kept or ended")
continue
}
s, err := delivery.DecodeStep(raw)
s, err := sdkstep.DecodeStep(raw)
if err != nil {
problems = append(problems, key+": "+err.Error())
continue
}
want := c.state
if want == "" {
want = delivery.StepRunning
want = sdkstep.StepRunning
}
if c.ended {
want = delivery.StepFailed
want = sdkstep.StepFailed
}
if s.State != want {
problems = append(problems, fmt.Sprintf("left running: the step %s (%s, owner %q, %s before the start) is %s after the sweep, want %s", key, c.tool, c.owner, c.age, s.State, want))
@@ -169,8 +169,8 @@ func (s *steps) get(key string) []byte {
return s.vals[key]
}
func (s *steps) scan() buildstep.Scan {
return buildstep.Scan{
func (s *steps) scan() buildrun.Scan {
return buildrun.Scan{
Keys: func() ([]string, error) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -11,7 +11,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// credentialField is the node runtime's rule for a state value (mesh-tools node-tools/internal/bus/state.go, novox/hq
@@ -59,11 +59,11 @@ func credentialField(value json.RawMessage) string {
// runtimeState is a steps state that refuses a value as the node runtime does, and keeps every value it took.
type runtimeState struct {
mu sync.Mutex
took map[string][]delivery.Step
took map[string][]sdkstep.Step
refused int
}
func newRuntimeState() *runtimeState { return &runtimeState{took: map[string][]delivery.Step{}} }
func newRuntimeState() *runtimeState { return &runtimeState{took: map[string][]sdkstep.Step{}} }
func (s *runtimeState) put(key string, value any) (uint64, error) {
raw, _ := value.(json.RawMessage)
@@ -73,7 +73,7 @@ func (s *runtimeState) put(key string, value any) (uint64, error) {
s.mu.Unlock()
return 0, fmt.Errorf("steps.%s carries a field %q, which names a credential", key, f)
}
st, err := delivery.DecodeStep(raw)
st, err := sdkstep.DecodeStep(raw)
if err != nil {
return 0, err
}
@@ -83,12 +83,12 @@ func (s *runtimeState) put(key string, value any) (uint64, error) {
return uint64(len(s.took[key])), nil
}
func (s *runtimeState) last(key string) (delivery.Step, bool) {
func (s *runtimeState) last(key string) (sdkstep.Step, bool) {
s.mu.Lock()
defer s.mu.Unlock()
t := s.took[key]
if len(t) == 0 {
return delivery.Step{}, false
return sdkstep.Step{}, false
}
return t[len(t)-1], true
}
@@ -109,8 +109,8 @@ func TestADigestOfAPackageNamedTokenIsStoredAsAList(t *testing.T) {
digests: map[string]string{"x/internal/token": "d-token", "x/pkg/a": "d-a"},
tests: map[string]bool{"x/internal/token": true, "x/pkg/a": true},
}
out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit},
Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}}))
out, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit},
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
if err != nil {
t.Fatal(err)
}
@@ -119,21 +119,21 @@ func TestADigestOfAPackageNamedTokenIsStoredAsAList(t *testing.T) {
t.Fatalf("the rule does not refuse the form before issue 1930 (%s): the test proves nothing", old)
}
st := newRuntimeState()
if err := finish(st.put, "shanks.a1", delivery.Step{ID: "a1", Tool: "check ask", Node: "shanks", State: delivery.StepRunning, Started: time.Now()}, out, nil, t.Logf); err != nil {
if err := finish(st.put, "shanks.a1", sdkstep.Step{ID: "a1", Tool: "check ask", Node: "shanks", State: sdkstep.StepRunning, Started: time.Now()}, out, nil, t.Logf); err != nil {
t.Fatal(err)
}
s, ok := st.last("shanks.a1")
if !ok || s.State != delivery.StepPassed || st.refused != 0 {
if !ok || s.State != sdkstep.StepPassed || st.refused != 0 {
t.Fatalf("the end %+v (written %v, refused %d), want it passed and taken at once", s, ok, st.refused)
}
var a delivery.CheckAnswer
var a CheckAnswer
if err := json.Unmarshal(s.Result, &a); err != nil {
t.Fatal(err)
}
if len(a.Levels) != 1 || a.Levels[0].Digest == nil {
t.Fatalf("levels %+v, want the unit level with its digest", a.Levels)
}
want := []delivery.PackageDigest{{Package: "x/internal/token", Digest: "d-token"}, {Package: "x/pkg/a", Digest: "d-a"}}
want := []sdkstep.PackageDigest{{Package: "x/internal/token", Digest: "d-token"}, {Package: "x/pkg/a", Digest: "d-a"}}
if got := a.Levels[0].Digest.Packages; fmt.Sprint(got) != fmt.Sprint(want) {
t.Errorf("packages %v, want %v", got, want)
}
@@ -148,9 +148,9 @@ func TestARefusedEndIsSaidAndEndsTheStepFailed(t *testing.T) {
fastEnds(t)
big := errors.New("the value carries a field \"x/internal/token\", which names a credential")
var refused int
var stored []delivery.Step
var stored []sdkstep.Step
put := func(key string, value any) (uint64, error) {
st, _ := delivery.DecodeStep(value.(json.RawMessage))
st, _ := sdkstep.DecodeStep(value.(json.RawMessage))
if len(st.Result) > 0 {
refused++
return 0, big
@@ -160,7 +160,7 @@ func TestARefusedEndIsSaidAndEndsTheStepFailed(t *testing.T) {
}
var logged []string
logf := func(format string, args ...any) { logged = append(logged, fmt.Sprintf(format, args...)) }
err := finish(put, "shanks.a2", delivery.Step{ID: "a2", Tool: "check ask", Node: "shanks", State: delivery.StepRunning, Started: time.Now()}, map[string]any{"levels": 1}, nil, logf)
err := finish(put, "shanks.a2", sdkstep.Step{ID: "a2", Tool: "check ask", Node: "shanks", State: sdkstep.StepRunning, Started: time.Now()}, map[string]any{"levels": 1}, nil, logf)
if err != nil {
t.Fatalf("finish answered %v, want the small end written", err)
}
@@ -168,7 +168,7 @@ func TestARefusedEndIsSaidAndEndsTheStepFailed(t *testing.T) {
t.Fatalf("refused %d, stored %d, want %d refusals and one small end", refused, len(stored), EndAttempts)
}
s := stored[0]
if s.State != delivery.StepFailed || len(s.Result) != 0 || !strings.Contains(s.Reason, "could not be written") ||
if s.State != sdkstep.StepFailed || len(s.Result) != 0 || !strings.Contains(s.Reason, "could not be written") ||
!strings.Contains(s.Reason, big.Error()) || !strings.Contains(s.Reason, "passed") {
t.Errorf("the small end %+v, want failed, no result, naming the write's error and that it passed", s)
}
@@ -176,7 +176,7 @@ func TestARefusedEndIsSaidAndEndsTheStepFailed(t *testing.T) {
t.Errorf("logged %q, want the refusal said", logged)
}
none := func(string, any) (uint64, error) { return 0, errors.New("the bus is gone") }
if err := finish(none, "shanks.a3", delivery.Step{ID: "a3", Tool: "check ask", Node: "shanks", Started: time.Now()}, nil, nil, logf); err == nil || !strings.Contains(err.Error(), "the bus is gone") {
if err := finish(none, "shanks.a3", sdkstep.Step{ID: "a3", Tool: "check ask", Node: "shanks", Started: time.Now()}, nil, nil, logf); err == nil || !strings.Contains(err.Error(), "the bus is gone") {
t.Errorf("finish with no write possible answered %v, want the error", err)
}
}
@@ -192,16 +192,16 @@ func TestAnAskWhoseEndCannotBeWrittenIsNotAcked(t *testing.T) {
}{{"the small end written", true, true}, {"no end written", false, false}} {
t.Run(tc.name, func(t *testing.T) {
src := NewFakeAsks()
a := testAsk(delivery.AskCheck)
a := testAsk(sdkstep.AskCheck)
_ = src.PublishAsk(a)
var mu sync.Mutex
var ends []delivery.Step
var ends []sdkstep.Step
put := func(key string, value any) (uint64, error) {
st, err := delivery.DecodeStep(value.(json.RawMessage))
st, err := sdkstep.DecodeStep(value.(json.RawMessage))
if err != nil {
return 0, err
}
if st.State != delivery.StepRunning && (len(st.Result) > 0 || !tc.smallOK) {
if st.State != sdkstep.StepRunning && (len(st.Result) > 0 || !tc.smallOK) {
return 0, errors.New("refused")
}
mu.Lock()
@@ -209,9 +209,9 @@ func TestAnAskWhoseEndCannotBeWrittenIsNotAcked(t *testing.T) {
mu.Unlock()
return 1, nil
}
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: put, Capacity: func() int { return 1 },
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Logf: t.Logf,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { return map[string]any{"ran": true}, nil }}
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { return map[string]any{"ran": true}, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
@@ -225,7 +225,7 @@ func TestAnAskWhoseEndCannotBeWrittenIsNotAcked(t *testing.T) {
defer mu.Unlock()
if tc.smallOK {
last := ends[len(ends)-1]
if last.State != delivery.StepFailed || !strings.Contains(last.Reason, "refused") {
if last.State != sdkstep.StepFailed || !strings.Contains(last.Reason, "refused") {
t.Errorf("the end %+v, want the small failed end", last)
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ package buildstep
// it was built with (its version is this package's requirement, so a new generator is a new build module and a new
// fingerprint of what it builds) and names each file out of step: one that differs, one missing, one under a generated
// root the generator did not write. The test step fails on any; a trunk build's ask answers them in its out-of-step
// (delivery.BuildAnswer.OutOfStep), so the builder's build end raises generated-types-out-of-step even where no test
// (sdkstep.BuildAnswer.OutOfStep), so the builder's build end raises generated-types-out-of-step even where no test
// step runs. Each build module compares its own roots: mesh-build-go `go`, mesh-build-node `ts`.
import (
@@ -11,7 +11,7 @@ import (
"testing"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/seatgen"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// contracts writes a contracts module of one seat, its generated files as the generator writes them, and answers its
@@ -113,16 +113,16 @@ func TestAContractsModulesGeneratedFilesAreComparedWithTheGenerator(t *testing.T
}
return map[string]any{"digest": "sha256:aa"}, nil
}}
job := delivery.BuildJob{Module: "c", Path: filepath.Base(dir), Fetch: map[string]any{"repository": "novox/c"},
Artifacts: []delivery.AskArtifact{{Artifact: "go"}}}
job := sdkstep.BuildJob{Module: "c", Path: filepath.Base(dir), Fetch: &sdkstep.Fetch{Repository: "novox/c", Commit: "0123456789abcdef0123456789abcdef01234567"},
Artifacts: []sdkstep.AskArtifact{{Artifact: "go"}}}
out, err := RunAsk(context.Background(), steps, ask(job))
if a, ok := out.(delivery.BuildAnswer); err != nil || !ok || !reflect.DeepEqual(a.OutOfStep, sorted(goFile, "go/extra.go")) || len(a.Artifacts) != 1 {
if a, ok := out.(sdkstep.BuildAnswer); err != nil || !ok || !reflect.DeepEqual(a.OutOfStep, sorted(goFile, "go/extra.go")) || len(a.Artifacts) != 1 {
t.Errorf("the build ask's out-of-step: %+v %v", out, err)
}
job.Artifacts = append(job.Artifacts, delivery.AskArtifact{Artifact: "go-other"})
job.Artifacts = append(job.Artifacts, sdkstep.AskArtifact{Artifact: "go-other"})
built = 0
out, err = RunAsk(context.Background(), steps, ask(job))
if a, ok := out.(delivery.BuildAnswer); err == nil || !ok || len(a.OutOfStep) != 2 {
if a, ok := out.(sdkstep.BuildAnswer); err == nil || !ok || len(a.OutOfStep) != 2 {
t.Errorf("a failed build answered no out-of-step: %+v %v", out, err)
}
}
+4
View File
@@ -3,6 +3,10 @@ module git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep
go 1.26.0
require (
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/clients/gopackageregistryclient v0.0.0-00010101000000-000000000000
+4 -6
View File
@@ -4,15 +4,13 @@ import (
"context"
"encoding/json"
"fmt"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// Guard is a build module's check that the toolchain installed on its node is the one its recipe builds with
// (novox/hq ADR 0469 §1; issue 1473). The same guard serves ADR 0470 §4's postgres major for the integration
// level, with its own Probe and Want.
type Guard struct {
// Language is the toolchain's key part: go or typescript (delivery.ToolchainKey).
// Language is the toolchain's key part: go or typescript (ToolchainKey).
Language string
// Want is the toolchain version the recipe builds with, and Recipe the recipe's own version. The indexer
// compares both texts as they are written here.
@@ -20,7 +18,7 @@ type Guard struct {
// Probe reads the installed version (go version, node --version).
Probe func(ctx context.Context) (installed string, err error)
Node string
// Put is the toolchains state's Put (stdio.State(delivery.ToolchainsState).Put).
// Put is the toolchains state's Put (stdio.State(the seat's toolchains state).Put).
Put Put
}
@@ -33,11 +31,11 @@ func (g Guard) Check(ctx context.Context) error {
if err != nil {
return &Refusal{Text: fmt.Sprintf("the installed %s version could not be read: %v", g.Language, err), Want: g.Want}
}
key, err := delivery.ToolchainKey(g.Node, g.Language)
key, err := ToolchainKey(g.Node, g.Language)
if err != nil {
return err
}
raw, err := delivery.EncodeToolchain(delivery.Toolchain{Language: g.Language, Version: installed, Recipe: g.Recipe, At: now().UTC()})
raw, err := EncodeToolchain(Toolchain{Language: g.Language, Version: installed, Recipe: g.Recipe, At: now().UTC()})
if err != nil {
return err
}
@@ -6,14 +6,12 @@ import (
"errors"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
type toolchainKeys map[string]delivery.Toolchain
type toolchainKeys map[string]Toolchain
func (k toolchainKeys) put(key string, value any) (uint64, error) {
tc, err := delivery.DecodeToolchain(value.(json.RawMessage))
tc, err := DecodeToolchain(value.(json.RawMessage))
if err != nil {
return 0, err
}
@@ -19,13 +19,13 @@ import (
"time"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/sdk/parts"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// NumberedPattern is the event type a build module consumes for the late publish, as its manifest's `consumes` names it.
const NumberedPattern = delivery.BuilderModule + "." + delivery.VersionNumberedEvent
const NumberedPattern = builds.BuilderModule + "." + builds.VersionNumberedEvent
// errNotHere is a held part's Open: its bytes did not stay on the node.
var errNotHere = errors.New("the built bytes did not stay on the node; the holder must hold the digest")
@@ -35,7 +35,7 @@ var errNotHere = errors.New("the built bytes did not stay on the node; the holde
// kind, another seat (an event naming none is the artifact seat's, as the builder wrote it before images), or a version
// numbered with no digest.
func LatePublish(ctx context.Context, call SeatCall, body []byte, seat string, kinds ...string) (bool, error) {
n, err := delivery.DecodeVersionNumbered(body)
n, err := builds.DecodeVersionNumbered(body)
if err != nil {
return false, err
}
@@ -11,7 +11,7 @@ import (
"time"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// CAT-NR1: a build's publish on a seat says asked, waiting on the seat (on every tick of NarrateEvery while the holder
@@ -52,9 +52,9 @@ func TestABuildNarratesItsPublishOnASeat(t *testing.T) {
var said []string
wrote := make(chan struct{}, 16)
step := delivery.Step{ID: "a1", Tool: "build", Node: "ace", State: delivery.StepRunning, Started: clock}
step := sdkstep.Step{ID: "a1", Tool: "build", Node: "ace", State: sdkstep.StepRunning, Started: clock}
ph := &phases{key: "ace.a1", step: step, put: func(key string, value any) (uint64, error) {
var s delivery.Step
var s sdkstep.Step
_ = json.Unmarshal(value.(json.RawMessage), &s)
phasesMu.Lock()
said = append(said, s.Phase)
+4 -4
View File
@@ -6,14 +6,14 @@ import (
"sync"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// **A running ask says its phases** (issue 1890, 2026-10-26 19:09: a mesh-catalog try's step on the workstation ran 30 minutes
// and its module's journal held no line naming its ask, so nobody could tell whether it fetched, linted or tested). The
// puller hands every ask's run a phase reporter in its context; RunAsk and a module's own step bodies call Phase as they
// move (fetch and merge, a level's digest, lint, a level's tests), and each phase is logged with the ask's id and its
// time since the start, and written to the ask's running step key (delivery.Step.Phase), so its asker and the delivery
// time since the start, and written to the ask's running step key (sdkstep.Step.Phase), so its asker and the delivery
// view read where it stands from the bus.
type phaseKey struct{}
@@ -30,7 +30,7 @@ type phases struct {
mu sync.Mutex
put Put
key string
step delivery.Step
step sdkstep.Step
ended bool
logf func(format string, args ...any)
prefix string
@@ -59,7 +59,7 @@ func (r *phases) enter(phase string) {
}
// end stops the writes (the step's end is written after it) and answers the step with its last phase, for the end.
func (r *phases) end() delivery.Step {
func (r *phases) end() sdkstep.Step {
r.mu.Lock()
defer r.mu.Unlock()
r.ended = true
+39 -42
View File
@@ -5,14 +5,14 @@ import (
"encoding/json"
"errors"
"fmt"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"sort"
"strings"
"sync"
"time"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// **The pull loop the build modules share** (novox/hq ADR 0525 §4-§6, ADR 0536 §4; issue 1887). A build module on a node
@@ -55,23 +55,23 @@ var (
// Puller pulls a build seat's asks and runs them.
type Puller struct {
Source AskSource
Language string // the seat's language: delivery.AskGo, AskNode or AskImage
Language string // the seat's language: sdkstep.AskGo, AskNode or AskImage
Node string // the node this holder runs on, the first part of its step keys
Put Put // writes the module's steps state
// Capacity is how many asks the node runs at once; read before every pull, so a change takes effect at the next
// one. 0 pulls nothing.
Capacity func() int
// Run runs one ask's job; a *Refusal ends its step refused. Its result is the step's result.
Run func(ctx context.Context, ask delivery.StepAsk) (any, error)
Run func(ctx context.Context, ask sdkstep.StepAsk) (any, error)
// PullWait and Heartbeat default to DefaultPullWait and DefaultHeartbeat.
PullWait time.Duration
Heartbeat time.Duration
Logf func(format string, args ...any)
// Load reads one key of the state the node's pause is kept in (nil and no error when it is absent). When set, Pull
// reads the node's pause key (delivery.PauseKey) before it pulls, and Pause and Resume write it with PausePut, so a
// reads the node's pause key (sdkstep.PauseKey) before it pulls, and Pause and Resume write it with PausePut, so a
// pause outlives a restart.
Load func(key string) ([]byte, error)
// PausePut writes the node's pause key: the module's pauses state (delivery.PausesState), which has no age, so a
// PausePut writes the node's pause key: the module's pauses state (sdkstep.PausesState), which has no age, so a
// pause never ends by itself while the steps state drops its old keys. Put when nil.
PausePut Put
// WatchConfig, when set, follows the module's config value `capacity` (CapacitySetting) as mesh-config resolves it
@@ -94,8 +94,8 @@ type Puller struct {
// taken, and the asks go to a node that can run them instead of failing here. The module calls Wake when its answer
// changes. Nil is always eligible.
Eligible func() string
// Has says whether this node has a need an ask names (delivery.AskNeeds, issue 890). An ask needing what it lacks is
// declined: its step ends refused as declined (delivery.StepDeclined), the message is dropped for its asker to put
// Has says whether this node has a need an ask names (sdkstep.AskNeeds, issue 890). An ask needing what it lacks is
// declined: its step ends refused as declined (sdkstep.StepDeclined), the message is dropped for its asker to put
// again, and the worker it came from is not looked at again until the next pull's wait, so a node lacking the need
// does not take the asker's next copy at once. Nil has nothing.
Has func(need string) bool
@@ -129,7 +129,7 @@ type Puller struct {
hoursFault string
hasHours bool
// judged is the capacity said last (log and capacity.<node>), so a change is said once.
judged delivery.Capacity
judged sdkstep.Capacity
hasJudged bool
capWritten bool
capFault string
@@ -166,11 +166,11 @@ func (p *Puller) Resume(by string) error { return p.setPaused(false, by) }
func (p *Puller) setPaused(paused bool, by string) error {
if p.Load != nil {
key, err := delivery.PauseKey(p.Node)
key, err := sdkstep.PauseKey(p.Node)
if err != nil {
return err
}
raw, err := delivery.EncodePause(delivery.Pause{Paused: paused, By: by, At: now()})
raw, err := sdkstep.EncodePause(sdkstep.Pause{Paused: paused, By: by, At: now()})
if err != nil {
return err
}
@@ -194,7 +194,7 @@ func (p *Puller) restore() error {
if p.Load == nil {
return nil
}
key, err := delivery.PauseKey(p.Node)
key, err := sdkstep.PauseKey(p.Node)
if err != nil {
return err
}
@@ -205,7 +205,7 @@ func (p *Puller) restore() error {
if raw == nil {
return nil
}
v, err := delivery.DecodePause(raw)
v, err := sdkstep.DecodePause(raw)
if err != nil {
return fmt.Errorf("buildstep: the pause of %s: %w", p.Node, err)
}
@@ -287,7 +287,7 @@ func (p *Puller) CapacityNow() int {
// else Capacity), bounded by the node's build slice when CoresPerStep is set (EffectiveCapacity), its quota read now. A
// change from what it said last is logged and written to `capacity.<node>` of the pauses state (PausePut), so the
// delivery view says it too.
func (p *Puller) CapacityDetail() delivery.Capacity {
func (p *Puller) CapacityDetail() sdkstep.Capacity {
p.mu.Lock()
watched, has := p.watched, p.hasWatched
p.mu.Unlock()
@@ -307,9 +307,9 @@ func (p *Puller) CapacityDetail() delivery.Capacity {
case p.Capacity != nil:
configured = p.Capacity()
}
var c delivery.Capacity
var c sdkstep.Capacity
if p.CoresPerStep == nil {
c = delivery.Capacity{Configured: configured, Effective: configured, QuotaFrom: "not judged: the module names no cores a step needs",
c = sdkstep.Capacity{Configured: configured, Effective: configured, QuotaFrom: "not judged: the module names no cores a step needs",
Why: fmt.Sprintf("its configured capacity %d", configured)}
} else {
quota := p.Quota
@@ -375,7 +375,7 @@ func (p *Puller) untilEdge() (time.Duration, bool) {
// sayCapacity logs c when it differs from what was said last, and writes it to the node's capacity key until a write
// lands (a failed write is tried again at the next judgement, its error said once).
func (p *Puller) sayCapacity(c delivery.Capacity) {
func (p *Puller) sayCapacity(c sdkstep.Capacity) {
p.mu.Lock()
same := p.hasJudged && p.judged.Effective == c.Effective && p.judged.Configured == c.Configured && p.judged.ByCPU == c.ByCPU &&
p.judged.CoresPerStep == c.CoresPerStep && p.judged.QuotaCores == c.QuotaCores && p.judged.OutsideHours == c.OutsideHours &&
@@ -392,12 +392,12 @@ func (p *Puller) sayCapacity(c delivery.Capacity) {
if !write {
return
}
key, err := delivery.CapacityKey(p.Node)
key, err := sdkstep.CapacityKey(p.Node)
if err != nil {
return
}
c.At = now()
raw, err := delivery.EncodeCapacity(c)
raw, err := sdkstep.EncodeCapacity(c)
if err == nil {
_, err = p.PausePut(key, json.RawMessage(raw))
}
@@ -493,7 +493,7 @@ func (p *Puller) next(ctx context.Context, wait time.Duration) (*Pulled, error)
p.mu.Lock()
declined := p.declined
p.mu.Unlock()
for _, kind := range delivery.AskKinds {
for _, kind := range sdkstep.AskKinds {
if declined[kind] {
continue
}
@@ -502,24 +502,24 @@ func (p *Puller) next(ctx context.Context, wait time.Duration) (*Pulled, error)
return withKind(msg, kind), err
}
}
msg, err := p.Source.Next(ctx, delivery.AskDelivery, wait)
msg, err := p.Source.Next(ctx, sdkstep.AskDelivery, wait)
p.mu.Lock()
p.declined = nil
p.mu.Unlock()
return withKind(msg, delivery.AskDelivery), err
return withKind(msg, sdkstep.AskDelivery), err
}
// decline ends the step of an ask this node lacks a need of, refused as declined with what it lacks, and drops the
// message: the asker puts the ask again under a new id (issue 890). A message whose decline cannot be written is handed
// back, as one whose start cannot be.
func (p *Puller) decline(msg *Pulled, ask delivery.StepAsk, lacks []string) {
step, key, err := begin(p.Put, p.Node, ask.Kind+" ask", ask.ID, sdkstep.AskWhat(askRead(ask)))
func (p *Puller) decline(msg *Pulled, ask sdkstep.StepAsk, lacks []string) {
step, key, err := begin(p.Put, p.Node, ask.Kind+" ask", ask.ID, buildrun.AskWhat(askRead(ask)))
if err == nil {
why := Refuse("the ask %s needs %s, and %s lacks %s: it is left to a node that has it", ask.ID,
strings.Join(ask.Needs, ", "), p.Node, strings.Join(lacks, ", "))
step.Ended = now().UTC()
step.State, step.Reason = delivery.StepRefused, why.Error()
step.Result, err = json.Marshal(delivery.StepDeclined{Declined: true, Needs: ask.Needs, Lacks: lacks})
step.State, step.Reason = sdkstep.StepRefused, why.Error()
step.Result, err = json.Marshal(sdkstep.StepDeclined{Declined: true, Needs: ask.Needs, Lacks: lacks})
if err == nil {
err = writeEnd(p.Put, key, step)
}
@@ -542,10 +542,10 @@ func (p *Puller) decline(msg *Pulled, ask delivery.StepAsk, lacks []string) {
// declineRun ends the step of an ask that began and then found this node should not run it (Declining), as decline does
// before it begins: refused as declined, the message dropped for its asker to put again, and the worker it came from left
// alone until the next pull's wait.
func (p *Puller) declineRun(msg *Pulled, ask delivery.StepAsk, key string, step delivery.Step, d *Declining) {
result, _ := json.Marshal(delivery.StepDeclined{Declined: true, Needs: ask.Needs, Lacks: d.Lacks})
func (p *Puller) declineRun(msg *Pulled, ask sdkstep.StepAsk, key string, step sdkstep.Step, d *Declining) {
result, _ := json.Marshal(sdkstep.StepDeclined{Declined: true, Needs: ask.Needs, Lacks: d.Lacks})
step.Ended = now().UTC()
step.State, step.Reason, step.Result = delivery.StepRefused, d.Text, result
step.State, step.Reason, step.Result = sdkstep.StepRefused, d.Text, result
if err := writeEnd(p.Put, key, step); err != nil {
p.logf("the ask %s cannot be declined on %s, so it is given back: %v", ask.ID, p.Node, err)
_ = msg.Nak()
@@ -571,7 +571,7 @@ func withKind(msg *Pulled, kind string) *Pulled {
// take starts one ask: it decodes it, refuses one that is not of this seat, and runs it as a step under its own id.
func (p *Puller) take(ctx context.Context, msg *Pulled) {
kind := msg.Kind
ask, err := delivery.DecodeStepAsk(msg.Data)
ask, err := sdkstep.DecodeStepAsk(msg.Data)
if err == nil && (ask.Language != p.Language || (kind != "" && ask.Kind != kind)) {
err = fmt.Errorf("the ask %s is a %s ask of %s, pulled from the %s %s worker", ask.ID, ask.Kind, ask.Language, p.Language, kind)
}
@@ -581,17 +581,17 @@ func (p *Puller) take(ctx context.Context, msg *Pulled) {
_ = msg.Term()
return
}
if lacks := delivery.Lacks(ask.Needs, p.Has); len(lacks) > 0 {
if lacks := sdkstep.Lacks(ask.Needs, p.Has); len(lacks) > 0 {
p.decline(msg, ask, lacks)
return
}
step, key, err := begin(p.Put, p.Node, ask.Kind+" ask", ask.ID, sdkstep.AskWhat(askRead(ask)))
step, key, err := begin(p.Put, p.Node, ask.Kind+" ask", ask.ID, buildrun.AskWhat(askRead(ask)))
if err != nil {
p.logf("the ask %s cannot start on %s: %v", ask.ID, p.Node, err)
_ = msg.Nak()
return
}
if msg.Delivered > delivery.AskRuns {
if msg.Delivered > sdkstep.AskRuns {
// Run AskRuns times and never ended: each run took its node down or died with it. It is run no more, and said
// (issue 1907): the asker's key fails with why.
why := fmt.Errorf("the ask was taken %d times and no run of it ended: each took its build node down or died with it, so it is run no more (poison)", msg.Delivered-1)
@@ -615,7 +615,7 @@ func (p *Puller) take(ctx context.Context, msg *Pulled) {
go p.run(ctx, msg, ask, key, step)
}
func (p *Puller) run(ctx context.Context, msg *Pulled, ask delivery.StepAsk, key string, step delivery.Step) {
func (p *Puller) run(ctx context.Context, msg *Pulled, ask sdkstep.StepAsk, key string, step sdkstep.Step) {
defer func() {
p.mu.Lock()
p.inHand--
@@ -644,7 +644,7 @@ func (p *Puller) run(ctx context.Context, msg *Pulled, ask delivery.StepAsk, key
}()
ph := &phases{put: p.Put, key: key, step: step, logf: p.Logf, prefix: fmt.Sprintf("the %s ask %s on %s", ask.Kind, ask.ID, p.Node)}
result, err := protect(ctx, func(ctx context.Context) (any, error) {
return p.Run(context.WithValue(ctx, phaseKey{}, ph), ask)
return p.Run(WithCheckToolchain(context.WithValue(ctx, phaseKey{}, ph), msg.Data), ask)
})
step = ph.end() // its end keeps the phase it ended in
close(done)
@@ -798,12 +798,9 @@ func (p *Puller) capacityFrom(c stdio.ProvisionChange) (int, string) {
return n, ""
}
// askRead is the part of an ask that mesh-delivery-sdk's AskWhat reads, taken from the ask's own layout (the part
// build-step's StepAsk is the same JSON).
func askRead(ask delivery.StepAsk) sdkstep.Ask {
var out sdkstep.Ask
if raw, err := json.Marshal(ask); err == nil {
_ = json.Unmarshal(raw, &out)
}
// askRead is the part of an ask that buildrun.AskWhat reads, taken from the ask's own layout by the one conversion the
// neutral form has (sdkstep.AskAs); an ask that does not convert reads as empty and is described by its kind alone.
func askRead(ask sdkstep.StepAsk) sdkstep.Ask {
out, _ := sdkstep.AskAs[sdkstep.Ask](ask)
return out
}
+9 -10
View File
@@ -6,13 +6,12 @@ import (
"sync"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// JetStreamAsks is a build seat's workers on the bus: the shared pull consumer of each kind on the seat's stream
// (delivery.AskStream, AskWorker), which the broker makes when the seat is declared; a holder only binds them.
// (sdkstep.AskStream, AskWorker), which the broker makes when the seat is declared; a holder only binds them.
type JetStreamAsks struct {
JS jetstream.JetStream
Language string
@@ -27,11 +26,11 @@ func (s *JetStreamAsks) consumer(ctx context.Context, kind string) (jetstream.Co
if c, ok := s.cons[kind]; ok {
return c, nil
}
stream, err := delivery.AskStream(s.Language)
stream, err := sdkstep.AskStream(s.Language)
if err != nil {
return nil, err
}
worker, err := delivery.AskWorker(s.Language, kind)
worker, err := sdkstep.AskWorker(s.Language, kind)
if err != nil {
return nil, err
}
@@ -81,18 +80,18 @@ func (s *JetStreamAsks) Next(ctx context.Context, kind string, wait time.Duratio
// modules' integration tests start one). On the mesh the broker declares them from the seat, as it declares every
// seat's stream (mesh-controller internal/broker SeatStreams); a holder never does.
func DeclareAskWorkers(ctx context.Context, js jetstream.JetStream, language string, ackWait time.Duration) error {
stream, err := delivery.AskStream(language)
stream, err := sdkstep.AskStream(language)
if err != nil {
return err
}
seat, _ := delivery.BuildSeat(language)
seat, _ := sdkstep.BuildSeat(language)
if _, err := js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{Name: stream, Subjects: []string{"mesh.seat." + seat + ".accept.>"},
Retention: jetstream.WorkQueuePolicy}); err != nil {
return fmt.Errorf("the stream %s: %w", stream, err)
}
for _, kind := range delivery.AskKinds {
worker, _ := delivery.AskWorker(language, kind)
subject, _ := delivery.AskSubject(language, kind)
for _, kind := range sdkstep.AskKinds {
worker, _ := sdkstep.AskWorker(language, kind)
subject, _ := sdkstep.AskSubject(language, kind)
if _, err := js.CreateOrUpdateConsumer(ctx, stream, jetstream.ConsumerConfig{Durable: worker, FilterSubject: subject,
AckPolicy: jetstream.AckExplicitPolicy, AckWait: ackWait, MaxDeliver: 5}); err != nil {
return fmt.Errorf("the worker %s: %w", worker, err)
+4 -4
View File
@@ -5,7 +5,7 @@ import (
"sync"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// FakeAsks is a test double of a seat's workers for the build modules' unit tests: Publish puts an ask on a kind's
@@ -38,8 +38,8 @@ func (f *FakeAsks) Publish(kind string, data []byte) {
}
// PublishAsk encodes a and puts it on its kind's worker.
func (f *FakeAsks) PublishAsk(a delivery.StepAsk) error {
raw, err := delivery.EncodeStepAsk(a)
func (f *FakeAsks) PublishAsk(a sdkstep.StepAsk) error {
raw, err := sdkstep.EncodeStepAsk(a)
if err != nil {
return err
}
@@ -93,7 +93,7 @@ func (f *FakeAsks) Next(ctx context.Context, kind string, wait time.Duration) (*
func (f *FakeAsks) pulled(data []byte) *Pulled {
id := string(data)
if a, err := delivery.DecodeStepAsk(data); err == nil {
if a, err := sdkstep.DecodeStepAsk(data); err == nil {
id = a.ID
}
fate := func(list *[]string) func() error {
@@ -12,7 +12,7 @@ import (
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/testbus"
)
@@ -27,19 +27,19 @@ func askBus(t *testing.T, ackWait time.Duration) jetstream.JetStream {
if err != nil {
t.Fatal(err)
}
if err := DeclareAskWorkers(context.Background(), js, delivery.AskGo, ackWait); err != nil {
if err := DeclareAskWorkers(context.Background(), js, sdkstep.AskGo, ackWait); err != nil {
t.Fatal(err)
}
return js
}
func publishAsk(t *testing.T, js jetstream.JetStream, a delivery.StepAsk) {
func publishAsk(t *testing.T, js jetstream.JetStream, a sdkstep.StepAsk) {
t.Helper()
raw, err := delivery.EncodeStepAsk(a)
raw, err := sdkstep.EncodeStepAsk(a)
if err != nil {
t.Fatal(err)
}
subject, _ := delivery.AskSubject(a.Language, a.Kind)
subject, _ := sdkstep.AskSubject(a.Language, a.Kind)
if _, err := js.Publish(context.Background(), subject, raw); err != nil {
t.Fatal(err)
}
@@ -49,7 +49,7 @@ func publishAsk(t *testing.T, js jetstream.JetStream, a delivery.StepAsk) {
// with capacity 2 and three asks two run, the third waits in the stream and starts when one ends.
func TestPullOrderAndCapacityOnTheBus(t *testing.T) {
js := askBus(t, 30*time.Second)
check, first, second := testAsk(delivery.AskCheck), testAsk(delivery.AskDelivery), testAsk(delivery.AskDelivery)
check, first, second := testAsk(sdkstep.AskCheck), testAsk(sdkstep.AskDelivery), testAsk(sdkstep.AskDelivery)
publishAsk(t, js, check)
publishAsk(t, js, first)
publishAsk(t, js, second)
@@ -57,9 +57,9 @@ func TestPullOrderAndCapacityOnTheBus(t *testing.T) {
var mu sync.Mutex
var order []string
st := newSteps()
p := &Puller{Source: &JetStreamAsks{JS: js, Language: delivery.AskGo}, Language: delivery.AskGo, Node: "shanks", Put: st.put,
p := &Puller{Source: &JetStreamAsks{JS: js, Language: sdkstep.AskGo}, Language: sdkstep.AskGo, Node: "shanks", Put: st.put,
Capacity: func() int { return 2 }, PullWait: 200 * time.Millisecond,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
mu.Lock()
order = append(order, a.ID)
mu.Unlock()
@@ -80,9 +80,9 @@ func TestPullOrderAndCapacityOnTheBus(t *testing.T) {
waitFor(t, "the check taken", func() bool { mu.Lock(); defer mu.Unlock(); return len(order) == 3 })
close(release)
waitFor(t, "every step ended", func() bool {
for _, a := range []delivery.StepAsk{check, first, second} {
key, _ := delivery.AskStepKey("shanks", a.ID)
if s, ok := st.get(key); !ok || s.State != delivery.StepPassed {
for _, a := range []sdkstep.StepAsk{check, first, second} {
key, _ := sdkstep.AskStepKey("shanks", a.ID)
if s, ok := st.get(key); !ok || s.State != sdkstep.StepPassed {
return false
}
}
@@ -102,13 +102,13 @@ func TestPullOrderAndCapacityOnTheBus(t *testing.T) {
// whose step key names the reference.
func TestPullRedeliversAKilledPullersAsk(t *testing.T) {
js := askBus(t, 2*time.Second)
a := testAsk(delivery.AskDelivery)
a := testAsk(sdkstep.AskDelivery)
publishAsk(t, js, a)
st := newSteps()
taken := make(chan struct{})
dying := &Puller{Source: &JetStreamAsks{JS: js, Language: delivery.AskGo}, Language: delivery.AskGo, Node: "ace", Put: st.put,
dying := &Puller{Source: &JetStreamAsks{JS: js, Language: sdkstep.AskGo}, Language: sdkstep.AskGo, Node: "ace", Put: st.put,
Capacity: func() int { return 1 }, PullWait: 200 * time.Millisecond, Heartbeat: time.Hour,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
close(taken)
<-ctx.Done()
return nil, ctx.Err()
@@ -117,18 +117,18 @@ func TestPullRedeliversAKilledPullersAsk(t *testing.T) {
go dying.Pull(kill)
<-taken
killed()
other := &Puller{Source: &JetStreamAsks{JS: js, Language: delivery.AskGo}, Language: delivery.AskGo, Node: "shanks", Put: st.put,
other := &Puller{Source: &JetStreamAsks{JS: js, Language: sdkstep.AskGo}, Language: sdkstep.AskGo, Node: "shanks", Put: st.put,
Capacity: func() int { return 1 }, PullWait: 200 * time.Millisecond,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
return delivery.BuildAnswer{Artifacts: []delivery.AskBuilt{{Artifact: "bundle", Digest: "sha256:00", Reference: "artifact-store://x"}}}, nil
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
return sdkstep.BuildAnswer{Artifacts: []sdkstep.AskBuilt{{Artifact: "bundle", Digest: "sha256:00", Reference: "artifact-store://x"}}}, nil
}}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go other.Pull(ctx)
key, _ := delivery.AskStepKey("shanks", a.ID)
key, _ := sdkstep.AskStepKey("shanks", a.ID)
deadline := time.Now().Add(10 * time.Second)
for {
if s, ok := st.get(key); ok && s.State == delivery.StepPassed {
if s, ok := st.get(key); ok && s.State == sdkstep.StepPassed {
if want := `artifact-store://x`; !contains(string(s.Result), want) {
t.Fatalf("the step's result %s names no %s", s.Result, want)
}
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"context"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// SeatAsks is a build seat's workers as a tools bundle reaches them: through its runtime's seat pull and settle
@@ -27,7 +27,7 @@ func (s SeatAsks) Next(ctx context.Context, kind string, wait time.Duration) (*P
if settle == nil {
settle = stdio.SeatSettle
}
worker, err := delivery.AskWorker(s.Language, kind)
worker, err := sdkstep.AskWorker(s.Language, kind)
if err != nil {
return nil, err
}
@@ -5,15 +5,15 @@ import (
"testing"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// 1887/A11: a bundle's source pulls the kind's worker by its seat's name and settles each ask by the runtime's id.
func TestSeatAsksPullsTheKindsWorkerAndSettlesByID(t *testing.T) {
var pulled []string
var settled []string
src := SeatAsks{Language: delivery.AskGo,
src := SeatAsks{Language: sdkstep.AskGo,
Pull: func(worker string, wait time.Duration) (*stdio.Pulled, error) {
pulled = append(pulled, worker)
if worker != "SEAT_NODE_BUILD_GO_CHECK_worker" {
@@ -22,10 +22,10 @@ func TestSeatAsksPullsTheKindsWorkerAndSettlesByID(t *testing.T) {
return &stdio.Pulled{ID: "p9", Worker: worker, Body: []byte(`{"v":1}`), Delivered: 2}, nil
},
Settle: func(id, fate string) error { settled = append(settled, id+" "+fate); return nil }}
if p, err := src.Next(context.Background(), delivery.AskDelivery, 0); err != nil || p != nil {
if p, err := src.Next(context.Background(), sdkstep.AskDelivery, 0); err != nil || p != nil {
t.Fatalf("delivery = %+v, %v", p, err)
}
p, err := src.Next(context.Background(), delivery.AskCheck, time.Second)
p, err := src.Next(context.Background(), sdkstep.AskCheck, time.Second)
if err != nil || p == nil || string(p.Data) != `{"v":1}` || p.Delivered != 2 {
t.Fatalf("check = %+v, %v", p, err)
}
+85 -85
View File
@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"os"
"path/filepath"
"strings"
@@ -14,21 +13,22 @@ import (
"testing"
"time"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// steps records a steps state's puts: the last value of each key.
type steps struct {
mu sync.Mutex
keys map[string]delivery.Step
keys map[string]sdkstep.Step
}
func newSteps() *steps { return &steps{keys: map[string]delivery.Step{}} }
func newSteps() *steps { return &steps{keys: map[string]sdkstep.Step{}} }
func (s *steps) put(key string, value any) (uint64, error) {
raw, _ := value.(json.RawMessage)
st, err := delivery.DecodeStep(raw)
st, err := sdkstep.DecodeStep(raw)
if err != nil {
return 0, err
}
@@ -38,28 +38,28 @@ func (s *steps) put(key string, value any) (uint64, error) {
return 1, nil
}
func (s *steps) get(key string) (delivery.Step, bool) {
func (s *steps) get(key string) (sdkstep.Step, bool) {
s.mu.Lock()
defer s.mu.Unlock()
st, ok := s.keys[key]
return st, ok
}
func testAsk(kind string) delivery.StepAsk {
func testAsk(kind string) sdkstep.StepAsk {
id := sdkstep.NewID()
a := delivery.StepAsk{ID: id, Kind: kind, Language: delivery.AskGo, Asker: "mesh-delivery-builder", At: time.Now()}
fetch := map[string]any{"repository": "novox/x", "commit": "0123456789abcdef0123456789abcdef01234567"}
if kind == delivery.AskDelivery {
a := sdkstep.StepAsk{ID: id, Kind: kind, Language: sdkstep.AskGo, Asker: "mesh-delivery-builder", At: time.Now()}
fetch := sdkstep.Fetch{Repository: "novox/x", Commit: "0123456789abcdef0123456789abcdef01234567"}
if kind == sdkstep.AskDelivery {
b := buildJobFixture
b.Fetch = fetch
b.Fetch = &fetch
a.Build = &b
} else {
a.Check = &delivery.CheckJob{Fetch: fetch, Levels: []string{delivery.LevelUnit}}
a.Check = &sdkstep.CheckJob{Fetch: fetch, Levels: []string{sdkstep.LevelUnit}}
}
return a
}
var buildJobFixture = delivery.BuildJob{Module: "m", Path: ".", Artifacts: []delivery.AskArtifact{{Artifact: "bundle"}}}
var buildJobFixture = sdkstep.BuildJob{Module: "m", Path: ".", Artifacts: []sdkstep.AskArtifact{{Artifact: "bundle"}}}
func waitFor(t *testing.T, what string, cond func() bool) {
t.Helper()
@@ -75,16 +75,16 @@ func waitFor(t *testing.T, what string, cond func() bool) {
// 1887/A2a: given a check ask published before a delivery ask and one slot, the delivery ask runs first.
func TestPullTakesDeliveryBeforeCheck(t *testing.T) {
src, st := NewFakeAsks(), newSteps()
check, recheck, deliver := testAsk(delivery.AskCheck), testAsk(delivery.AskRecheck), testAsk(delivery.AskDelivery)
for _, a := range []delivery.StepAsk{recheck, check, deliver} {
check, recheck, deliver := testAsk(sdkstep.AskCheck), testAsk(sdkstep.AskRecheck), testAsk(sdkstep.AskDelivery)
for _, a := range []sdkstep.StepAsk{recheck, check, deliver} {
if err := src.PublishAsk(a); err != nil {
t.Fatal(err)
}
}
var mu sync.Mutex
var order []string
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
mu.Lock()
order = append(order, a.Kind)
mu.Unlock()
@@ -96,12 +96,12 @@ func TestPullTakesDeliveryBeforeCheck(t *testing.T) {
waitFor(t, "three asks acked", func() bool { a, _, _ := src.Fates(); return len(a) == 3 })
mu.Lock()
defer mu.Unlock()
if want := []string{delivery.AskDelivery, delivery.AskCheck, delivery.AskRecheck}; len(order) != 3 || order[0] != want[0] || order[1] != want[1] || order[2] != want[2] {
if want := []string{sdkstep.AskDelivery, sdkstep.AskCheck, sdkstep.AskRecheck}; len(order) != 3 || order[0] != want[0] || order[1] != want[1] || order[2] != want[2] {
t.Fatalf("order = %v, want %v", order, want)
}
key, _ := delivery.AskStepKey("shanks", deliver.ID)
key, _ := sdkstep.AskStepKey("shanks", deliver.ID)
s, ok := st.get(key)
if !ok || s.State != delivery.StepPassed || string(s.Result) != `{"ran":"`+deliver.ID+`"}` {
if !ok || s.State != sdkstep.StepPassed || string(s.Result) != `{"ran":"`+deliver.ID+`"}` {
t.Fatalf("the step %s = %+v (%v)", key, s, ok)
}
}
@@ -112,9 +112,9 @@ func TestPullTakesDeliveryBeforeCheck(t *testing.T) {
// its ask id, and its end keeps the last phase.
func TestPullHoldsAtMostItsCapacity(t *testing.T) {
src, st := NewFakeAsks(), newSteps()
asks := []delivery.StepAsk{}
asks := []sdkstep.StepAsk{}
for i := 0; i < 3; i++ {
a := testAsk(delivery.AskDelivery)
a := testAsk(sdkstep.AskDelivery)
asks = append(asks, a)
_ = src.PublishAsk(a)
}
@@ -124,7 +124,7 @@ func TestPullHoldsAtMostItsCapacity(t *testing.T) {
var said []string
capacities := map[string][]byte{}
quota := 1200
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 3 },
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 3 },
CoresPerStep: func() float64 { return 6 },
Quota: func() (int, string) { mu.Lock(); defer mu.Unlock(); return quota, "a test's slice" },
PausePut: func(key string, v any) (uint64, error) {
@@ -134,7 +134,7 @@ func TestPullHoldsAtMostItsCapacity(t *testing.T) {
return 1, nil
},
Logf: func(f string, a ...any) { mu.Lock(); said = append(said, fmt.Sprintf(f, a...)); mu.Unlock() },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
mu.Lock()
started++
mu.Unlock()
@@ -147,8 +147,8 @@ func TestPullHoldsAtMostItsCapacity(t *testing.T) {
go p.Pull(ctx)
waitFor(t, "two in hand", func() bool { return p.InHand() == 2 })
time.Sleep(30 * time.Millisecond) // three pull waits: the third must still wait
if p.InHand() != 2 || src.Waiting(delivery.AskDelivery) != 1 {
t.Fatalf("in hand %d, waiting %d; want 2 and 1", p.InHand(), src.Waiting(delivery.AskDelivery))
if p.InHand() != 2 || src.Waiting(sdkstep.AskDelivery) != 1 {
t.Fatalf("in hand %d, waiting %d; want 2 and 1", p.InHand(), src.Waiting(sdkstep.AskDelivery))
}
c := p.CapacityDetail()
if c.Effective != 2 || c.Configured != 3 || c.ByCPU != 2 || c.QuotaCores != 12 || !strings.Contains(c.Why, "bounded by the CPU") {
@@ -163,23 +163,23 @@ func TestPullHoldsAtMostItsCapacity(t *testing.T) {
}
}
mu.Unlock()
if got, err := delivery.DecodeCapacity(raw); err != nil || got.Effective != 2 || capLines != 1 {
if got, err := sdkstep.DecodeCapacity(raw); err != nil || got.Effective != 2 || capLines != 1 {
t.Fatalf("capacity.shanks = %s (%v), said %d times; want effective 2, said once", raw, err, capLines)
}
waitFor(t, "a phase on the step key", func() bool {
key, _ := delivery.AskStepKey("shanks", p.Running()[0].ID)
key, _ := sdkstep.AskStepKey("shanks", p.Running()[0].ID)
s, _ := st.get(key)
return s.State == delivery.StepRunning && s.Phase == "unit: tests of 3 packages" && !s.PhaseAt.IsZero() && s.Owner == sdkstep.Owner // 1890/AC5
return s.State == sdkstep.StepRunning && s.Phase == "unit: tests of 3 packages" && !s.PhaseAt.IsZero() && s.Owner == buildrun.Owner // 1890/AC5
})
release <- struct{}{}
waitFor(t, "the third taken", func() bool { return src.Waiting(delivery.AskDelivery) == 0 })
waitFor(t, "the third taken", func() bool { return src.Waiting(sdkstep.AskDelivery) == 0 })
// Taken from the source comes a moment before its run starts: wait for the start, not only the take.
waitFor(t, "the third started", func() bool { mu.Lock(); defer mu.Unlock(); return started == 3 })
close(release)
waitFor(t, "all acked", func() bool { a, _, _ := src.Fates(); return len(a) == 3 })
for _, a := range asks {
key, _ := delivery.AskStepKey("shanks", a.ID)
if s, _ := st.get(key); s.State != delivery.StepPassed || s.Phase != "unit: tests of 3 packages" || s.Owner != sdkstep.Owner {
key, _ := sdkstep.AskStepKey("shanks", a.ID)
if s, _ := st.get(key); s.State != sdkstep.StepPassed || s.Phase != "unit: tests of 3 packages" || s.Owner != buildrun.Owner {
t.Errorf("the end of %s = %+v, want passed in its last phase", a.ID, s)
}
}
@@ -200,18 +200,18 @@ func TestPullHoldsAtMostItsCapacity(t *testing.T) {
// dropped for the asker to put again), and runs on a node that has it.
func TestPullDropsWhatIsNoAsk(t *testing.T) {
src, st := NewFakeAsks(), newSteps()
src.Publish(delivery.AskDelivery, []byte(`{"v":1}`))
other := testAsk(delivery.AskDelivery)
other.Language = delivery.AskNode
src.Publish(sdkstep.AskDelivery, []byte(`{"v":1}`))
other := testAsk(sdkstep.AskDelivery)
other.Language = sdkstep.AskNode
_ = src.PublishAsk(other)
placed := testAsk(delivery.AskCheck)
placed.Needs = []string{delivery.NeedNodeBuildNode}
placed := testAsk(sdkstep.AskCheck)
placed.Needs = []string{sdkstep.NeedNodeBuildNode}
if err := src.PublishAsk(placed); err != nil {
t.Fatal(err)
}
ran := false
p := &Puller{Source: src, Language: delivery.AskGo, Node: "ace", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { ran = true; return nil, nil }}
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "ace", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { ran = true; return nil, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
@@ -219,31 +219,31 @@ func TestPullDropsWhatIsNoAsk(t *testing.T) {
if ran {
t.Fatal("a wrong or a declined ask ran")
}
key, _ := delivery.AskStepKey("ace", placed.ID)
key, _ := sdkstep.AskStepKey("ace", placed.ID)
s, _ := st.get(key)
if d, ok := delivery.DeclinedOf(s); !ok || len(d.Lacks) != 1 || d.Lacks[0] != delivery.NeedNodeBuildNode || !strings.Contains(s.Reason, "lacks node-build-node") {
if d, ok := sdkstep.DeclinedOf(s); !ok || len(d.Lacks) != 1 || d.Lacks[0] != sdkstep.NeedNodeBuildNode || !strings.Contains(s.Reason, "lacks node-build-node") {
t.Fatalf("the declined step = %+v", s)
}
has, hasSteps := NewFakeAsks(), newSteps()
_ = has.PublishAsk(placed)
q := &Puller{Source: has, Language: delivery.AskGo, Node: "g14", Put: hasSteps.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Has: func(n string) bool { return n == delivery.NeedNodeBuildNode },
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { return nil, nil }}
q := &Puller{Source: has, Language: sdkstep.AskGo, Node: "g14", Put: hasSteps.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Has: func(n string) bool { return n == sdkstep.NeedNodeBuildNode },
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { return nil, nil }}
go q.Pull(ctx)
waitFor(t, "the placed ask acked where its need is met", func() bool { a, _, _ := has.Fates(); return len(a) == 1 })
// A run that finds its node is not on the agreed toolchain declines the ask after it began (ADR 0584 clause 2); a node on it builds.
sit, sitSteps := NewFakeAsks(), newSteps()
toolchain := testAsk(delivery.AskDelivery)
toolchain := testAsk(sdkstep.AskDelivery)
_ = sit.PublishAsk(toolchain)
s2 := &Puller{Source: sit, Language: delivery.AskGo, Node: "ace", Put: sitSteps.put, Capacity: func() int { return 1 }, PullWait: 10 * time.Millisecond,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
s2 := &Puller{Source: sit, Language: sdkstep.AskGo, Node: "ace", Put: sitSteps.put, Capacity: func() int { return 1 }, PullWait: 10 * time.Millisecond,
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
return nil, &Declining{Text: "the ask is for 26.13.0 and this node builds with 26.12.1", Lacks: []string{"the agreed toolchain 26.13.0 (recipe 3)"}}
}}
go s2.Pull(ctx)
waitFor(t, "the ask termed on the node that sits out", func() bool { _, tm, _ := sit.Fates(); return len(tm) == 1 })
sk, _ := delivery.AskStepKey("ace", toolchain.ID)
sk, _ := sdkstep.AskStepKey("ace", toolchain.ID)
ss, _ := sitSteps.get(sk)
if d, ok := delivery.DeclinedOf(ss); !ok || len(d.Lacks) != 1 || !strings.Contains(ss.Reason, "26.12.1") {
if d, ok := sdkstep.DeclinedOf(ss); !ok || len(d.Lacks) != 1 || !strings.Contains(ss.Reason, "26.12.1") {
t.Fatalf("the sitting-out step = %+v", ss)
}
}
@@ -278,17 +278,17 @@ func (k *kv) load(key string) ([]byte, error) {
func TestPullAtCapacityZeroTakesNothing(t *testing.T) {
t.Run("settings file", func(t *testing.T) {
src, st := NewFakeAsks(), &kv{}
_ = src.PublishAsk(testAsk(delivery.AskDelivery))
_ = src.PublishAsk(testAsk(sdkstep.AskDelivery))
var mu sync.Mutex
capacity := 0
p := &Puller{Source: src, Language: delivery.AskGo, Node: "novox", Put: st.put, PullWait: 10 * time.Millisecond,
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "novox", Put: st.put, PullWait: 10 * time.Millisecond,
Capacity: func() int { mu.Lock(); defer mu.Unlock(); return capacity },
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { return nil, nil }}
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { return nil, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
time.Sleep(50 * time.Millisecond)
if src.Waiting(delivery.AskDelivery) != 1 {
if src.Waiting(sdkstep.AskDelivery) != 1 {
t.Fatal("a node at capacity 0 took an ask")
}
mu.Lock()
@@ -299,18 +299,18 @@ func TestPullAtCapacityZeroTakesNothing(t *testing.T) {
})
t.Run("config watch wakes", func(t *testing.T) {
src, st := NewFakeAsks(), newSteps()
_ = src.PublishAsk(testAsk(delivery.AskDelivery))
_ = src.PublishAsk(testAsk(sdkstep.AskDelivery))
w := &configWatch{value: "0"}
p := &Puller{Source: src, Language: delivery.AskGo, Node: "g14", Put: st.put, PullWait: 10 * time.Millisecond,
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "g14", Put: st.put, PullWait: 10 * time.Millisecond,
Capacity: func() int { return 0 }, // the settings file, not refilled yet
WatchConfig: w.watch,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { return nil, nil }}
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { return nil, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
waitFor(t, "the watch of the capacity", func() bool { w.mu.Lock(); defer w.mu.Unlock(); return w.handle != nil })
time.Sleep(30 * time.Millisecond)
if src.Waiting(delivery.AskDelivery) != 1 {
if src.Waiting(sdkstep.AskDelivery) != 1 {
t.Fatal("a node at capacity 0 took an ask")
}
if w.asked != sdkstep.CapacitySetting {
@@ -323,7 +323,7 @@ func TestPullAtCapacityZeroTakesNothing(t *testing.T) {
// 2026-10-28: a replaced builder held the old "shanks=2,..." in its settings file, its config follow timed out, and
// the puller parked at 0 with nothing to wake it when the node-engine refilled the file.
src, st := NewFakeAsks(), newSteps()
_ = src.PublishAsk(testAsk(delivery.AskDelivery))
_ = src.PublishAsk(testAsk(sdkstep.AskDelivery))
file := filepath.Join(t.TempDir(), "settings.json")
if err := os.WriteFile(file, []byte(`{"capacity":"shanks=2,ace=2"}`), 0o600); err != nil {
t.Fatal(err)
@@ -347,14 +347,14 @@ func TestPullAtCapacityZeroTakesNothing(t *testing.T) {
parked.Add(1)
}
}
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: st.put, PullWait: 10 * time.Millisecond,
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, PullWait: 10 * time.Millisecond,
SettingsFile: file, WatchConfig: watch, Logf: logf, watching: func() { armed <- struct{}{} },
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { return nil, nil }}
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { return nil, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
waitFor(t, "the parked puller to say why", func() bool { why, fault := p.CapacityNote(); return fault && why != "" })
if src.Waiting(delivery.AskDelivery) != 1 {
if src.Waiting(sdkstep.AskDelivery) != 1 {
t.Fatal("a node whose capacity cannot be read took an ask")
}
<-armed // the file watch is in place
@@ -377,7 +377,7 @@ func TestPullAtCapacityZeroTakesNothing(t *testing.T) {
}
<-armed // armed again
waitFor(t, "the puller parked with no settings file", func() bool { return parks() > before })
_ = src.PublishAsk(testAsk(delivery.AskDelivery))
_ = src.PublishAsk(testAsk(sdkstep.AskDelivery))
if err := os.WriteFile(file, []byte(`{"capacity":2}`), 0o600); err != nil {
t.Fatal(err)
}
@@ -397,9 +397,9 @@ func TestPullAtCapacityZeroTakesNothing(t *testing.T) {
}, true},
} {
t.Run(c.name, func(t *testing.T) {
p := &Puller{Source: NewFakeAsks(), Language: delivery.AskGo, Node: "g14", Put: newSteps().put,
p := &Puller{Source: NewFakeAsks(), Language: sdkstep.AskGo, Node: "g14", Put: newSteps().put,
Capacity: func() int { return 1 }, WatchConfig: c.watch,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { return nil, nil }}
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { return nil, nil }}
p.watchCapacity()
if p.room() != c.room {
t.Fatalf("room = %v, want %v", p.room(), c.room)
@@ -412,30 +412,30 @@ func TestPullAtCapacityZeroTakesNothing(t *testing.T) {
// stays paused until resumed.
func TestPullPauseOutlivesARestart(t *testing.T) {
src, st, steps := NewFakeAsks(), &kv{}, &kv{}
run := func(ctx context.Context, a delivery.StepAsk) (any, error) { return nil, nil }
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: steps.put, PausePut: st.put, Load: st.load,
run := func(ctx context.Context, a sdkstep.StepAsk) (any, error) { return nil, nil }
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: steps.put, PausePut: st.put, Load: st.load,
PullWait: 10 * time.Millisecond, Capacity: func() int { return 2 }, Run: run}
if err := p.Pause("the operator"); err != nil {
t.Fatal(err)
}
key, _ := delivery.PauseKey("shanks")
key, _ := sdkstep.PauseKey("shanks")
raw, _ := st.load(key)
if v, err := delivery.DecodePause(raw); err != nil || !v.Paused || v.By != "the operator" {
if v, err := sdkstep.DecodePause(raw); err != nil || !v.Paused || v.By != "the operator" {
t.Fatalf("the pause key holds %s (%v)", raw, err)
}
if raw, _ := steps.load(key); raw != nil {
t.Fatalf("the pause was written in the steps state, whose keys age out: %s", raw)
}
// The restart: a new puller on the same state.
again := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: steps.put, PausePut: st.put, Load: st.load,
again := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: steps.put, PausePut: st.put, Load: st.load,
PullWait: 10 * time.Millisecond, Capacity: func() int { return 2 }, Run: run}
_ = src.PublishAsk(testAsk(delivery.AskDelivery))
_ = src.PublishAsk(testAsk(sdkstep.AskDelivery))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go again.Pull(ctx)
time.Sleep(50 * time.Millisecond)
if !again.Paused() || src.Waiting(delivery.AskDelivery) != 1 {
t.Fatalf("restarted: paused %v, waiting %d; want paused and 1", again.Paused(), src.Waiting(delivery.AskDelivery))
if !again.Paused() || src.Waiting(sdkstep.AskDelivery) != 1 {
t.Fatalf("restarted: paused %v, waiting %d; want paused and 1", again.Paused(), src.Waiting(sdkstep.AskDelivery))
}
if err := again.Resume("the operator"); err != nil {
t.Fatal(err)
@@ -445,7 +445,7 @@ func TestPullPauseOutlivesARestart(t *testing.T) {
t.Run("a pause that cannot be read is said and pulled as not paused", func(t *testing.T) {
src := NewFakeAsks()
var said atomic.Value
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: steps.put, PausePut: st.put,
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: steps.put, PausePut: st.put,
Load: func(string) ([]byte, error) { return nil, errors.New("no runtime yet") },
PullWait: 10 * time.Millisecond, Capacity: func() int { return 2 }, Run: run,
Logf: func(f string, a ...any) {
@@ -453,7 +453,7 @@ func TestPullPauseOutlivesARestart(t *testing.T) {
said.Store(m)
}
}}
_ = src.PublishAsk(testAsk(delivery.AskDelivery))
_ = src.PublishAsk(testAsk(sdkstep.AskDelivery))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
@@ -468,12 +468,12 @@ func TestPullPauseOutlivesARestart(t *testing.T) {
// acknowledges its ask, so the stream gives it to another node.
func TestPullHeartbeatAndStop(t *testing.T) {
src, st := NewFakeAsks(), newSteps()
a := testAsk(delivery.AskDelivery)
a := testAsk(sdkstep.AskDelivery)
_ = src.PublishAsk(a)
running := make(chan struct{})
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Heartbeat: 5 * time.Millisecond,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
close(running)
<-ctx.Done()
return nil, ctx.Err()
@@ -490,8 +490,8 @@ func TestPullHeartbeatAndStop(t *testing.T) {
if len(acked)+len(termed)+len(naked) != 0 {
t.Fatalf("a stopped holder gave its ask a fate: acked %v termed %v naked %v", acked, termed, naked)
}
key, _ := delivery.AskStepKey("shanks", a.ID)
if s, ok := st.get(key); !ok || s.State != delivery.StepRunning {
key, _ := sdkstep.AskStepKey("shanks", a.ID)
if s, ok := st.get(key); !ok || s.State != sdkstep.StepRunning {
t.Fatalf("the step %s = %+v, want left running", key, s)
}
}
@@ -500,19 +500,19 @@ func TestPullHeartbeatAndStop(t *testing.T) {
// asker's key fails, and it is ended on the stream, not offered to another node.
func TestAPoisonedAskIsSaidAndRunNoMore(t *testing.T) {
src, st := NewFakeAsks(), newSteps()
src.Delivered = delivery.AskRuns + 1
a := testAsk(delivery.AskDelivery)
src.Delivered = sdkstep.AskRuns + 1
a := testAsk(sdkstep.AskDelivery)
_ = src.PublishAsk(a)
ran := false
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { ran = true; return nil, nil }}
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { ran = true; return nil, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
waitFor(t, "the ask termed", func() bool { _, tm, _ := src.Fates(); return len(tm) == 1 })
key, _ := delivery.AskStepKey("shanks", a.ID)
key, _ := sdkstep.AskStepKey("shanks", a.ID)
s, ok := st.get(key)
if ran || !ok || s.State != delivery.StepFailed || !strings.Contains(s.Reason, "taken 3 times") || !strings.Contains(s.Reason, "poison") {
if ran || !ok || s.State != sdkstep.StepFailed || !strings.Contains(s.Reason, "taken 3 times") || !strings.Contains(s.Reason, "poison") {
t.Fatalf("ran %v, step %+v", ran, s)
}
if acked, _, naked := src.Fates(); len(acked)+len(naked) != 0 {
+1 -1
View File
@@ -17,7 +17,7 @@ func (r *Refusal) Error() string { return r.Text }
// Refuse is a Refusal with its text formatted.
func Refuse(format string, args ...any) *Refusal { return &Refusal{Text: fmt.Sprintf(format, args...)} }
// Declining is a step a node does not run because another node should: it ends refused as declined (delivery.StepDeclined),
// Declining is a step a node does not run because another node should: it ends refused as declined (sdkstep.StepDeclined),
// the ask's message is dropped, and the asker puts the ask again under a new id (issue 890). A build ask naming a
// toolchain other than this node's is one (novox/hq ADR 0584 clause 2): a node that is not in the toolchain majority
// sits out and never fails a build.
@@ -5,7 +5,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// 1887/A9a, ADR 0525 §10: a build seat's verbs are <seat>.current, .pause and .resume; current names the ask running,
@@ -13,8 +13,8 @@ import (
func TestSeatToolsCurrentPauseResume(t *testing.T) {
src, st := NewFakeAsks(), newSteps()
release := make(chan struct{})
p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { <-release; return nil, nil }}
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { <-release; return nil, nil }}
tools := map[string]func(map[string]any) (any, error){}
for _, tool := range SeatTools("node-build-go", "Go", p) {
tools[tool.Name] = tool.Run
@@ -24,7 +24,7 @@ func TestSeatToolsCurrentPauseResume(t *testing.T) {
t.Fatalf("no %s", name)
}
}
a := testAsk(delivery.AskDelivery)
a := testAsk(sdkstep.AskDelivery)
_ = src.PublishAsk(a)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -40,13 +40,13 @@ func TestSeatToolsCurrentPauseResume(t *testing.T) {
t.Fatalf("pause = %v, %v", out, err)
}
close(release)
_ = src.PublishAsk(testAsk(delivery.AskDelivery))
_ = src.PublishAsk(testAsk(sdkstep.AskDelivery))
time.Sleep(50 * time.Millisecond)
if src.Waiting(delivery.AskDelivery) != 1 {
if src.Waiting(sdkstep.AskDelivery) != 1 {
t.Fatal("a paused node took an ask")
}
if _, err := tools["node-build-go.resume"](nil); err != nil {
t.Fatal(err)
}
waitFor(t, "the ask taken after the resume", func() bool { return src.Waiting(delivery.AskDelivery) == 0 })
waitFor(t, "the ask taken after the resume", func() bool { return src.Waiting(sdkstep.AskDelivery) == 0 })
}
+17 -17
View File
@@ -12,11 +12,11 @@ import (
"log"
"time"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// Put writes one key of a state: stdio.State(delivery.StepsState).Put's shape (go/state.go).
// Put writes one key of a state: the steps state.Put's shape (go/state.go).
type Put func(key string, value any) (uint64, error)
// EndAttempts and EndRetry bound how a step's end is written: a write that fails is tried again, since the end
@@ -43,20 +43,20 @@ func Start(ctx context.Context, put Put, node, tool string, run func(ctx context
}
// begin writes the step running under id, owned by this process (Owner).
func begin(put Put, node, tool, id, what string) (delivery.Step, string, error) {
step := delivery.Step{ID: id, Tool: tool, Node: node, State: delivery.StepRunning, Started: now().UTC(), What: what, Owner: sdkstep.Owner}
key, err := delivery.StepKey(node, step.ID)
func begin(put Put, node, tool, id, what string) (sdkstep.Step, string, error) {
step := sdkstep.Step{ID: id, Tool: tool, Node: node, State: sdkstep.StepRunning, Started: now().UTC(), What: what, Owner: buildrun.Owner}
key, err := sdkstep.StepKey(node, step.ID)
if err != nil {
return delivery.Step{}, "", err
return sdkstep.Step{}, "", err
}
if err := write(put, key, step); err != nil {
return delivery.Step{}, "", fmt.Errorf("buildstep: writing the running step %s: %w", key, err)
return sdkstep.Step{}, "", fmt.Errorf("buildstep: writing the running step %s: %w", key, err)
}
return step, key, nil
}
// end runs run and writes the step's end.
func end(ctx context.Context, put Put, key string, step delivery.Step, run func(ctx context.Context) (any, error)) {
func end(ctx context.Context, put Put, key string, step sdkstep.Step, run func(ctx context.Context) (any, error)) {
result, err := protect(ctx, run)
_ = finish(put, key, step, result, err, log.Printf) // finish says a refused end itself
}
@@ -66,23 +66,23 @@ func end(ctx context.Context, put Put, key string, step delivery.Step, run func(
// refusal is logged and a small failed end without the result is written instead, naming the write's error, so the
// step's reader learns it is over and why. finish answers an error only when no end could be written at all, and then
// the step's ask is not acknowledged as done.
func finish(put Put, key string, step delivery.Step, result any, err error, logf func(format string, args ...any)) error {
func finish(put Put, key string, step sdkstep.Step, result any, err error, logf func(format string, args ...any)) error {
step.Ended = now().UTC()
switch ref := (*Refusal)(nil); {
case err == nil:
step.State = delivery.StepPassed
step.State = sdkstep.StepPassed
if result != nil {
raw, merr := json.Marshal(result)
if merr != nil {
step.State, step.Reason = delivery.StepFailed, "the result is not JSON: "+merr.Error()
step.State, step.Reason = sdkstep.StepFailed, "the result is not JSON: "+merr.Error()
} else {
step.Result = raw
}
}
case errors.As(err, &ref):
step.State, step.Reason = delivery.StepRefused, err.Error()
step.State, step.Reason = sdkstep.StepRefused, err.Error()
default:
step.State, step.Reason = delivery.StepFailed, err.Error()
step.State, step.Reason = sdkstep.StepFailed, err.Error()
// A failed step may answer what it found on the way (a build's out-of-step files): kept on its end.
if result != nil {
if raw, merr := json.Marshal(result); merr == nil {
@@ -102,7 +102,7 @@ func finish(put Put, key string, step delivery.Step, result any, err error, logf
if step.Reason != "" {
why = fmt.Sprintf("the step ended %s (%s), and its end could not be written: %v", ended, step.Reason, werr)
}
step.State, step.Reason, step.Result = delivery.StepFailed, why, nil
step.State, step.Reason, step.Result = sdkstep.StepFailed, why, nil
if ferr := writeEnd(put, key, step); ferr != nil {
if logf != nil {
logf("the failed end of the step %s could not be written either: %v", key, ferr)
@@ -113,7 +113,7 @@ func finish(put Put, key string, step delivery.Step, result any, err error, logf
}
// writeEnd writes the step's end, trying EndAttempts times.
func writeEnd(put Put, key string, step delivery.Step) error {
func writeEnd(put Put, key string, step sdkstep.Step) error {
for attempt := 1; ; attempt++ {
werr := write(put, key, step)
if werr == nil || attempt >= EndAttempts {
@@ -133,8 +133,8 @@ func protect(ctx context.Context, run func(ctx context.Context) (any, error)) (r
return run(ctx)
}
func write(put Put, key string, step delivery.Step) error {
raw, err := delivery.EncodeStep(step)
func write(put Put, key string, step sdkstep.Step) error {
raw, err := sdkstep.EncodeStep(step)
if err != nil {
return err
}
+15 -15
View File
@@ -4,25 +4,25 @@ import (
"context"
"encoding/json"
"errors"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// keys is a steps state in memory: every value put, in order, per key, and a signal for each end.
type keys struct {
mu sync.Mutex
puts map[string][]delivery.Step
puts map[string][]sdkstep.Step
ended chan string
}
func newKeys() *keys { return &keys{puts: map[string][]delivery.Step{}, ended: make(chan string, 16)} }
func newKeys() *keys { return &keys{puts: map[string][]sdkstep.Step{}, ended: make(chan string, 16)} }
func (k *keys) put(key string, value any) (uint64, error) {
step, err := delivery.DecodeStep(value.(json.RawMessage))
step, err := sdkstep.DecodeStep(value.(json.RawMessage))
if err != nil {
return 0, err
}
@@ -30,13 +30,13 @@ func (k *keys) put(key string, value any) (uint64, error) {
k.puts[key] = append(k.puts[key], step)
n := uint64(len(k.puts[key]))
k.mu.Unlock()
if step.State != delivery.StepRunning {
if step.State != sdkstep.StepRunning {
k.ended <- key
}
return n, nil
}
func (k *keys) last(key string) delivery.Step {
func (k *keys) last(key string) sdkstep.Step {
k.mu.Lock()
defer k.mu.Unlock()
s := k.puts[key]
@@ -72,8 +72,8 @@ func TestStartAnswersAtOnceWithTheStepRunning(t *testing.T) {
}
s := k.last("ace." + id)
// 1890/AC5: the running key names this process as its owner.
if s.State != delivery.StepRunning || s.Tool != "fetch" || s.Node != "ace" || s.ID != id || s.Started.IsZero() || s.Owner != sdkstep.Owner || sdkstep.Owner == "" {
t.Errorf("step = %+v, want owner %q", s, sdkstep.Owner)
if s.State != sdkstep.StepRunning || s.Tool != "fetch" || s.Node != "ace" || s.ID != id || s.Started.IsZero() || s.Owner != buildrun.Owner || buildrun.Owner == "" {
t.Errorf("step = %+v, want owner %q", s, buildrun.Owner)
}
}
@@ -86,11 +86,11 @@ func TestStepEndsPassedFailedOrRefusedOnItsKey(t *testing.T) {
reason string
result string
}{
{"result", func(context.Context) (any, error) { return map[string]string{"sha": "abc"}, nil }, delivery.StepPassed, "", `{"sha":"abc"}`},
{"error", func(context.Context) (any, error) { return nil, errors.New("exit 2") }, delivery.StepFailed, "exit 2", ""},
{"result", func(context.Context) (any, error) { return map[string]string{"sha": "abc"}, nil }, sdkstep.StepPassed, "", `{"sha":"abc"}`},
{"error", func(context.Context) (any, error) { return nil, errors.New("exit 2") }, sdkstep.StepFailed, "exit 2", ""},
{"refusal", func(context.Context) (any, error) {
return nil, &Refusal{Text: "go 1.27.1 installed, the recipe builds with go 1.27.0", Want: "1.27.0", Have: "1.27.1"}
}, delivery.StepRefused, "go 1.27.1 installed, the recipe builds with go 1.27.0", ""},
}, sdkstep.StepRefused, "go 1.27.1 installed, the recipe builds with go 1.27.0", ""},
} {
t.Run(c.name, func(t *testing.T) {
k := newKeys()
@@ -102,7 +102,7 @@ func TestStepEndsPassedFailedOrRefusedOnItsKey(t *testing.T) {
t.Fatalf("ended key %q", got)
}
s := k.last("ace." + id)
if s.State != c.state || s.Reason != c.reason || string(s.Result) != c.result || s.Ended.IsZero() || s.Owner != sdkstep.Owner {
if s.State != c.state || s.Reason != c.reason || string(s.Result) != c.result || s.Ended.IsZero() || s.Owner != buildrun.Owner {
t.Errorf("step = %+v", s)
}
})
@@ -115,7 +115,7 @@ func TestAPanicEndsTheStepFailed(t *testing.T) {
id, _ := Start(context.Background(), k.put, "ace", "build", func(context.Context) (any, error) { panic("boom") })
k.waitEnd(t)
s := k.last("ace." + id)
if s.State != delivery.StepFailed || s.Reason != "the step panicked: boom" {
if s.State != sdkstep.StepFailed || s.Reason != "the step panicked: boom" {
t.Errorf("step = %+v", s)
}
}
@@ -134,7 +134,7 @@ func TestTwoIDsInOneMillisecondDiffer(t *testing.T) {
t.Fatalf("id %q handed out twice", id)
}
seen[id] = true
if _, err := delivery.StepKey("ace", id); err != nil {
if _, err := sdkstep.StepKey("ace", id); err != nil {
t.Errorf("id %q: %v", id, err)
}
}
+3 -3
View File
@@ -1,14 +1,14 @@
package buildstep
import (
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// StepEndTool is the module's step-end tool, named name (build_go_step_end): it ends one step left running, with a
// reason, refusing one this process started. p may be nil.
func StepEndTool(name string, node string, scan sdkstep.Scan, p *Puller) stdio.Tool {
func StepEndTool(name string, node string, scan buildrun.Scan, p *Puller) stdio.Tool {
return stdio.Tool{Name: name,
Description: "End one step of this machine that is stuck in state running (for instance left by a restart of the module) " +
"as failed, with the reason given; its asker then sees it over, so ending an ask still in its stream fails its asker. " +
@@ -31,6 +31,6 @@ func StepEndTool(name string, node string, scan sdkstep.Scan, p *Puller) stdio.T
return false
}
}
return sdkstep.EndOne(scan, node, id, why, live)
return buildrun.EndOne(scan, node, id, why, live)
}}
}
@@ -0,0 +1,82 @@
package buildstep
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"time"
)
// ToolchainVersion is the layout version of a toolchains value.
const ToolchainVersion = 1
// Toolchain is the value of a build module's state toolchains under the key <node>.<language>: the version of the
// language's toolchain installed on the node, the build module's recipe version, and when the module saw them. It is
// the layout every node-build seat gives that state (the seats' generated Toolchain); the three build modules share
// this form because their runner does (design 26 *Where a seat's code goes*, a module's own package). A test holds it
// to each seat's decoder.
type Toolchain struct {
V int `json:"v,omitempty"`
Language string `json:"language"`
Version string `json:"version"`
Recipe string `json:"recipe"`
At time.Time `json:"at,omitzero"`
}
func checkToolchainLanguage(language string) error {
switch language {
case "go", "typescript":
return nil
}
return fmt.Errorf("buildstep: the toolchain language %q is not go or typescript", language)
}
// ToolchainKey is the key of a node's toolchain of one language: <node>.<language>. It refuses an empty node, a node
// with a dot and a language other than go or typescript.
func ToolchainKey(node, language string) (string, error) {
if node == "" || strings.ContainsAny(node, ". ") {
return "", fmt.Errorf("buildstep: %q is not a node name", node)
}
if err := checkToolchainLanguage(language); err != nil {
return "", err
}
return node + "." + language, nil
}
func (t Toolchain) check() error {
if err := checkToolchainLanguage(t.Language); err != nil {
return err
}
if t.Version == "" || t.Recipe == "" {
return fmt.Errorf("buildstep: a %s toolchain needs its version and its recipe", t.Language)
}
return nil
}
// EncodeToolchain is t as JSON; it refuses a language other than go or typescript and an empty version or recipe.
func EncodeToolchain(t Toolchain) ([]byte, error) {
if err := t.check(); err != nil {
return nil, err
}
t.V = ToolchainVersion
return json.Marshal(t)
}
// DecodeToolchain reads a toolchains value, refusing an unknown field, another layout version and what
// EncodeToolchain refuses.
func DecodeToolchain(data []byte) (Toolchain, error) {
var t Toolchain
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&t); err != nil {
return Toolchain{}, fmt.Errorf("buildstep: a toolchain does not read: %w", err)
}
if t.V != ToolchainVersion {
return Toolchain{}, fmt.Errorf("buildstep: a toolchain of version %d, this reads %d", t.V, ToolchainVersion)
}
if err := t.check(); err != nil {
return Toolchain{}, err
}
return t, nil
}
@@ -0,0 +1,48 @@
package buildstep
import (
"strings"
"testing"
"time"
nodebuildgo "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
nodebuildimage "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
nodebuildnode "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
)
// The toolchains value and key are the layout all three node-build seats generate: each seat's strict decoder reads
// what the shared runner writes, and each seat's key function agrees with ToolchainKey.
func TestTheToolchainsValueAndKeyAreTheSeatsLayout(t *testing.T) {
raw, err := EncodeToolchain(Toolchain{Language: "go", Version: "go1.27.1", Recipe: "3", At: time.Date(2026, 11, 5, 10, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
if _, err := nodebuildgo.DecodeToolchainsByNodeLanguageValue(raw); err != nil {
t.Errorf("node-build-go does not read the value: %v", err)
}
if _, err := nodebuildimage.DecodeToolchainsByNodeLanguageValue(raw); err != nil {
t.Errorf("node-build-image does not read the value: %v", err)
}
if _, err := nodebuildnode.DecodeToolchainsByNodeLanguageValue(raw); err != nil {
t.Errorf("node-build-node does not read the value: %v", err)
}
back, err := DecodeToolchain(raw)
if err != nil || back.Version != "go1.27.1" || back.At.IsZero() {
t.Errorf("the value reads back as %+v, %v", back, err)
}
key, err := ToolchainKey("ace", "typescript")
if err != nil || key != nodebuildnode.ToolchainsByNodeLanguageKey("ace", "typescript") || key != nodebuildgo.ToolchainsByNodeLanguageKey("ace", "typescript") {
t.Errorf("key %q, %v", key, err)
}
for _, bad := range [][2]string{{"", "go"}, {"a.b", "go"}, {"ace", "rust"}} {
if _, err := ToolchainKey(bad[0], bad[1]); err == nil {
t.Errorf("the key of %v was made", bad)
}
}
if _, err := EncodeToolchain(Toolchain{Language: "go", Version: "go1.27"}); err == nil {
t.Error("a toolchain without its recipe was written")
}
if _, err := DecodeToolchain([]byte(`{"v":1,"language":"go","version":"x","recipe":"1","extra":1}`)); err == nil || !strings.Contains(err.Error(), "extra") {
t.Errorf("a field the layout lacks was read: %v", err)
}
}
@@ -16,7 +16,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// Issue 1589: the build step packs an archive artifact (novox/hq ADR 0478 §1), as the build agent packs it
@@ -146,7 +146,7 @@ func pinnedModule(t *testing.T, path, sha, extra string) (*Module, *keptStates)
func TestAnArchiveOfADirectoryHasTheBuildAgentsDigest(t *testing.T) {
m, states, calls := archiveModule(t, "")
res, st := runBuild(t, m, states, "files")
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
sameAsBuildAgent(t, res, buildAgentDirectoryStream, buildAgentDirectoryDigest)
@@ -177,7 +177,7 @@ func TestAnArchiveFromOutsideTheModuleOrMissingFailsTheStep(t *testing.T) {
"nothing": "nothing",
} {
m, states, _ := archiveModule(t, `{"module":"demo","version":"1","build":{"artifacts":[{"name":"files","kind":"archive","from":"`+from+`"}]}}`)
if _, st := runBuild(t, m, states, "files"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, want) {
if _, st := runBuild(t, m, states, "files"); st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, want) {
t.Errorf("from %q: %s %q", from, st.State, st.Reason)
}
}
@@ -193,7 +193,7 @@ func pinned(url, sha, extra string) string {
func TestAPinnedArchiveIsUnpackedWithinAndHasTheBuildAgentsDigest(t *testing.T) {
m, states := pinnedModule(t, "/release.tar.gz", releaseSHA256, `,"within":"release/app"`)
res, st := runBuild(t, m, states, "files")
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("%s %q", st.State, st.Reason)
}
sameAsBuildAgent(t, res, buildAgentWithinStream, buildAgentWithinDigest)
@@ -202,7 +202,7 @@ func TestAPinnedArchiveIsUnpackedWithinAndHasTheBuildAgentsDigest(t *testing.T)
func TestAPinnedFileKeptWholeHasTheBuildAgentsDigest(t *testing.T) {
m, states := pinnedModule(t, "/release.tar.gz", releaseSHA256, `,"file":"tool.bin"`)
res, st := runBuild(t, m, states, "files")
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("%s %q", st.State, st.Reason)
}
sameAsBuildAgent(t, res, buildAgentFileStream, buildAgentFileDigest)
@@ -213,7 +213,7 @@ func TestAPinnedFileOfAnotherDigestIsRefused(t *testing.T) {
wrong := strings.Repeat("ab", 32)
m, states := pinnedModule(t, "/release.tar.gz", wrong, `,"within":"release/app"`)
_, st := runBuild(t, m, states, "files")
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "sha256:"+wrong) || !strings.Contains(st.Reason, "sha256:"+releaseSHA256) {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "sha256:"+wrong) || !strings.Contains(st.Reason, "sha256:"+releaseSHA256) {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
if entries, _ := os.ReadDir(filepath.Join(m.Work, "ws1", "artifacts")); len(entries) != 0 {
@@ -223,7 +223,7 @@ func TestAPinnedFileOfAnotherDigestIsRefused(t *testing.T) {
func TestAPinnedFileTheServerRefusesFailsTheStep(t *testing.T) {
m, states := pinnedModule(t, "/gone", releaseSHA256, `,"file":"x"`)
if _, st := runBuild(t, m, states, "files"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, "404") {
if _, st := runBuild(t, m, states, "files"); st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "404") {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
}
@@ -231,7 +231,7 @@ func TestAPinnedFileTheServerRefusesFailsTheStep(t *testing.T) {
// Entries outside `within` are refused, as the build agent refuses them.
func TestAPinnedArchiveWithEntriesOutsideWithinIsRefused(t *testing.T) {
m, states := pinnedModule(t, "/release.tar.gz", releaseSHA256, `,"within":"release/other"`)
if _, st := runBuild(t, m, states, "files"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, "release/other") {
if _, st := runBuild(t, m, states, "files"); st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "release/other") {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
}
@@ -239,7 +239,7 @@ func TestAPinnedArchiveWithEntriesOutsideWithinIsRefused(t *testing.T) {
// Other kinds are still refused by name.
func TestAnImageArtifactIsStillRefusedByName(t *testing.T) {
m, states, _ := archiveModule(t, `{"module":"demo","version":"1","build":{"artifacts":[{"name":"files","kind":"image","from":"files"}]}}`)
if _, st := runBuild(t, m, states, "files"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, "image") {
if _, st := runBuild(t, m, states, "files"); st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "image") {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
}
@@ -259,7 +259,7 @@ func TestAnArchiveBehindASymlinkedParentIsRefused(t *testing.T) {
t.Fatal(err)
}
_, st := runBuild(t, m, states, "files")
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "outside") || !strings.Contains(st.Reason, outside) {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "outside") || !strings.Contains(st.Reason, outside) {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
if entries, _ := os.ReadDir(filepath.Join(m.Work, "ws1", "artifacts")); len(entries) != 0 {
@@ -288,7 +288,7 @@ func TestAnArchiveOfALinkedModuleDirectoryIsRefused(t *testing.T) {
}
}
st := states.waitEnd(t)
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "outside") || !strings.Contains(st.Reason, outside) {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "outside") || !strings.Contains(st.Reason, outside) {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
if entries, _ := os.ReadDir(filepath.Join(m.Work, "ws1", "artifacts")); len(entries) != 0 {
+22 -20
View File
@@ -8,9 +8,11 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
checkerseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// **The module pulls its seat's asks** (novox/hq ADR 0525 §2-§6, ADR 0536 §4; issue 1887 A3go). It holds the seat
@@ -24,7 +26,10 @@ const Seat = "node-build-go"
// KeptState is the checker's kept verdicts, which a check ask takes for the packages whose digest has one (issue
// 1887's design limit 1).
const KeptState = delivery.CheckerModule + ".kept"
const KeptState = checkerModule + "." + checkerseat.StateKept
// checkerModule is the module that holds the seat mesh-delivery-checker, whose kept state a check ask reads.
const checkerModule = "mesh-delivery-checker"
// askSteps are the module's step bodies for an ask, each holding its workspace while it runs, as a tool's does.
func (m *Module) askSteps(steps []StepTool) buildstep.AskSteps {
@@ -40,12 +45,12 @@ func (m *Module) askSteps(steps []StepTool) buildstep.AskSteps {
Fetch: byName["build_go_fetch"], Build: byName["build_go_build"], Digest: byName["build_go_digest"], Test: byName["build_go_test"],
Imports: byName[ImportsTool],
Compare: buildstep.CompareStep(buildstep.GeneratedRootGo, func(ws string) string { return filepath.Join(m.Work, ws, "tree") }),
Toolchain: func(ctx context.Context) (delivery.ToolchainUsed, error) {
Toolchain: func(ctx context.Context) (sdkstep.ToolchainUsed, error) {
installed, err := m.Probe(ctx)
if err != nil {
return delivery.ToolchainUsed{}, err
return sdkstep.ToolchainUsed{}, err
}
return delivery.ToolchainUsed{Version: installed, Recipe: m.Recipe.Version()}, nil
return sdkstep.ToolchainUsed{Version: installed, Recipe: m.Recipe.Version()}, nil
},
Kept: m.KeptLookup,
Has: m.Has,
@@ -65,25 +70,22 @@ func (m *Module) keptValue(key string) (json.RawMessage, error) {
// KeptLookup is whether the checker kept a verdict for the level and digest, and how many tests it counted.
func (m *Module) KeptLookup(ctx context.Context, level, digest string) (int, bool, error) {
key, err := delivery.KeptKey(level, digest)
if err != nil {
return 0, false, err
}
key := checkerseat.KeptByLevelDigestKey(level, digest)
raw, err := m.keptValue(key)
if err != nil || raw == nil {
return 0, false, err
}
v, err := delivery.DecodeKept(raw)
v, err := checkerseat.DecodeKeptByLevelDigestValue(raw)
if err != nil {
return 0, false, fmt.Errorf("%s holds what is not a kept verdict: %w", key, err)
}
return v.Tests, true, nil
return int(v.Tests), true, nil
}
// Puller is the module's pull of its seat's asks, on source (the runtime's seat pull when nil).
func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep.Puller {
if source == nil {
source = buildstep.SeatAsks{Language: delivery.AskGo}
source = buildstep.SeatAsks{Language: sdkstep.AskGo}
}
capacity := m.Capacity
// The capacity's own change wakes the puller (issue 1887): the runtime's watch of the config value as mesh-config
@@ -101,7 +103,7 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
capacity = buildstep.NodeCapacity(m.Settings, m.Node, m.Say)
settings = m.Settings
watch = buildstep.WatchConfigServed
cores = buildstep.NodeCoresPerStep(m.Settings, buildstep.DefaultCoresPerStep[delivery.AskGo], m.Say)
cores = buildstep.NodeCoresPerStep(m.Settings, buildstep.DefaultCoresPerStep[sdkstep.AskGo], m.Say)
}
ask := m.askSteps(steps)
var load func(string) ([]byte, error)
@@ -111,9 +113,9 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
return raw, err
}
}
return &buildstep.Puller{Source: source, Language: delivery.AskGo, Node: m.Node, Put: m.Steps, PausePut: m.Pauses, Load: load,
return &buildstep.Puller{Source: source, Language: sdkstep.AskGo, Node: m.Node, Put: m.Steps, PausePut: m.Pauses, Load: load,
Capacity: capacity, SettingsFile: settings, WatchConfig: watch, CoresPerStep: cores, BuildHours: hours, Logf: m.Say, Has: m.Has,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
if err := m.Check(ctx); err != nil {
return nil, err
}
@@ -136,13 +138,13 @@ func (m *Module) SeatTools(p *buildstep.Puller) []stdio.Tool {
const StepEndTool = "build_go_step_end"
// endLeftRunning is the module's start sweep (issue 1890): it ends the steps of its node that a previous process left
// running (sdkstep.EndStaleAtStart; every step names its owner). main runs it once at the start, and the module's test
// running (buildrun.EndStaleAtStart; every step names its owner). main runs it once at the start, and the module's test
// runs it through the conformance kit's left-running case.
func (m *Module) endLeftRunning(ctx context.Context, scan sdkstep.Scan, booted time.Time, logf func(string, ...any)) {
sdkstep.EndStaleAtStart(ctx, scan, m.Node, booted, logf)
func (m *Module) endLeftRunning(ctx context.Context, scan buildrun.Scan, booted time.Time, logf func(string, ...any)) {
buildrun.EndStaleAtStart(ctx, scan, m.Node, booted, logf)
}
// stepScan is the module's steps state as buildstep reads it.
func (m *Module) stepScan() sdkstep.Scan {
return sdkstep.ScanOf(stdio.State(delivery.StepsState))
func (m *Module) stepScan() buildrun.Scan {
return buildrun.ScanOf(stdio.State(seat.StateSteps))
}
@@ -12,7 +12,7 @@ import (
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/testbus"
)
@@ -42,17 +42,17 @@ func TestTheModuleTakesADeliveryAskBeforeAnEarlierCheck(t *testing.T) {
}
ctx, stop := context.WithCancel(context.Background())
defer stop()
if err := buildstep.DeclareAskWorkers(ctx, js, delivery.AskGo, 30*time.Second); err != nil {
if err := buildstep.DeclareAskWorkers(ctx, js, sdkstep.AskGo, 30*time.Second); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = js.DeleteStream(context.Background(), "SEAT_NODE_BUILD_GO") })
check := deliveryAsk()
check.Kind = delivery.AskCheck
check.Build.Fetch["commit"] = checkCommit
check.Kind = sdkstep.AskCheck
check.Build.Fetch.Commit = checkCommit
first := deliveryAsk()
for _, a := range []delivery.StepAsk{check, first} {
raw, _ := delivery.EncodeStepAsk(a)
subject, _ := delivery.AskSubject(a.Language, a.Kind)
for _, a := range []sdkstep.StepAsk{check, first} {
raw, _ := sdkstep.EncodeStepAsk(a)
subject, _ := sdkstep.AskSubject(a.Language, a.Kind)
if _, err := js.Publish(ctx, subject, raw); err != nil {
t.Fatal(err)
}
@@ -74,19 +74,19 @@ func TestTheModuleTakesADeliveryAskBeforeAnEarlierCheck(t *testing.T) {
}
}
}
p := m.Puller(steps, &buildstep.JetStreamAsks{JS: js, Language: delivery.AskGo})
p := m.Puller(steps, &buildstep.JetStreamAsks{JS: js, Language: sdkstep.AskGo})
p.PullWait = 200 * time.Millisecond
go p.Pull(ctx)
var ran map[string][]delivery.Step
var ran map[string][]sdkstep.Step
for deadline := time.Now().Add(10 * time.Second); ; time.Sleep(10 * time.Millisecond) {
ran = map[string][]delivery.Step{}
ran = map[string][]sdkstep.Step{}
ended := 0
states.mu.Lock()
for _, a := range []delivery.StepAsk{first, check} {
key, _ := delivery.AskStepKey("ace", a.ID)
v := append([]delivery.Step(nil), states.steps[key]...)
for _, a := range []sdkstep.StepAsk{first, check} {
key, _ := sdkstep.AskStepKey("ace", a.ID)
v := append([]sdkstep.Step(nil), states.steps[key]...)
ran[a.ID] = v
if len(v) > 0 && v[len(v)-1].State != delivery.StepRunning {
if len(v) > 0 && v[len(v)-1].State != sdkstep.StepRunning {
ended++
}
}
@@ -13,7 +13,6 @@ import (
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
const askCommit = "0123456789abcdef0123456789abcdef01234567"
@@ -34,11 +33,11 @@ func fakeSteps(calls *[]string) []StepTool {
}
}
func deliveryAsk() delivery.StepAsk {
return delivery.StepAsk{ID: sdkstep.NewID(), Kind: delivery.AskDelivery, Language: delivery.AskGo, Asker: "mesh-delivery-builder",
At: time.Now(), Build: &delivery.BuildJob{Module: "mesh-delivery-view", Path: "modules/mesh-delivery-view", Publish: true,
Fetch: map[string]any{"repository": "novox/mesh-delivery", "commit": askCommit},
Artifacts: []delivery.AskArtifact{{Artifact: "program"}}}}
func deliveryAsk() sdkstep.StepAsk {
return sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskDelivery, Language: sdkstep.AskGo, Asker: "mesh-delivery-builder",
At: time.Now(), Build: &sdkstep.BuildJob{Module: "mesh-delivery-view", Path: "modules/mesh-delivery-view", Publish: true,
Fetch: &sdkstep.Fetch{Repository: "novox/mesh-delivery", Commit: askCommit},
Artifacts: []sdkstep.AskArtifact{{Artifact: "program"}}}}
}
// 1887/A3go: an ask pulled runs build_go_build after build_go_fetch, and the step key <node>.<ask id> names the
@@ -58,13 +57,13 @@ func TestAPulledAskRunsTheBuildAndItsKeyNamesTheReference(t *testing.T) {
ctx, stop := context.WithCancel(context.Background())
defer stop()
go p.Pull(ctx)
key, _ := delivery.AskStepKey("ace", a.ID)
var step delivery.Step
key, _ := sdkstep.AskStepKey("ace", a.ID)
var step sdkstep.Step
for deadline := time.Now().Add(30 * time.Second); ; time.Sleep(5 * time.Millisecond) {
states.mu.Lock()
v := states.steps[key]
states.mu.Unlock()
if len(v) > 0 && v[len(v)-1].State != delivery.StepRunning {
if len(v) > 0 && v[len(v)-1].State != sdkstep.StepRunning {
step = v[len(v)-1]
break
}
@@ -72,7 +71,7 @@ func TestAPulledAskRunsTheBuildAndItsKeyNamesTheReference(t *testing.T) {
t.Fatalf("the ask's step %s never ended: %v", key, v)
}
}
if step.State != delivery.StepPassed || !strings.Contains(string(step.Result), "artifact-store://mesh-build-go/tools@sha256:aa") {
if step.State != sdkstep.StepPassed || !strings.Contains(string(step.Result), "artifact-store://mesh-build-go/tools@sha256:aa") {
t.Fatalf("the step = %+v", step)
}
if strings.Join(calls, ",") != "build_go_fetch,build_go_build" {
@@ -98,8 +97,8 @@ func TestAtCapacityZeroNothingIsPulledAndTheToolsAnswer(t *testing.T) {
defer stop()
go p.Pull(ctx)
time.Sleep(50 * time.Millisecond)
if src.Waiting(delivery.AskDelivery) != 1 || len(calls) != 0 {
t.Fatalf("a node at capacity 0 pulled: waiting %d, calls %v", src.Waiting(delivery.AskDelivery), calls)
if src.Waiting(sdkstep.AskDelivery) != 1 || len(calls) != 0 {
t.Fatalf("a node at capacity 0 pulled: waiting %d, calls %v", src.Waiting(sdkstep.AskDelivery), calls)
}
for _, tool := range m.Tools(steps) {
if tool.Name == "build_go_fetch" {
@@ -134,10 +133,10 @@ func TestManifestClaimsTheGoBuildSeatAndReadsItsCapacity(t *testing.T) {
}
_ = json.Unmarshal(raw, &doc)
// Issue 1806's follow-up: the cores a step needs is a setting of its own, in the settings file, with the SDK's default.
if cores := string(doc.Config[buildstep.CoresPerStepSetting].Default); cores != fmt.Sprint(buildstep.DefaultCoresPerStep[delivery.AskGo]) {
t.Errorf("the cores-per-step default = %s, want %g", cores, buildstep.DefaultCoresPerStep[delivery.AskGo])
if cores := string(doc.Config[buildstep.CoresPerStepSetting].Default); cores != fmt.Sprint(buildstep.DefaultCoresPerStep[sdkstep.AskGo]) {
t.Errorf("the cores-per-step default = %s, want %g", cores, buildstep.DefaultCoresPerStep[sdkstep.AskGo])
}
if got := buildstep.NodeCoresPerStep(moduleDir+"/files/settings.json", 0, nil)(); got != buildstep.DefaultCoresPerStep[delivery.AskGo] {
if got := buildstep.NodeCoresPerStep(moduleDir+"/files/settings.json", 0, nil)(); got != buildstep.DefaultCoresPerStep[sdkstep.AskGo] {
t.Errorf("the settings template holds cores-per-step %g", got)
}
// And the build hours (the operator, 2026-10-26): a setting of their own, empty by default (any hour), in the template.
@@ -198,13 +197,13 @@ func TestACheckAsksDigestIsTheToolsAnswerInTheCall(t *testing.T) {
}
}
ask := m.askSteps(steps)
a := delivery.StepAsk{ID: sdkstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "mesh-delivery-checker",
At: time.Now(), Check: &delivery.CheckJob{Fetch: map[string]any{"repository": "novox/x", "commit": askCommit}, Levels: []string{delivery.LevelUnit}}}
a := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskCheck, Language: sdkstep.AskGo, Asker: "mesh-delivery-checker",
At: time.Now(), Check: &sdkstep.CheckJob{Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: askCommit}, Levels: []string{sdkstep.LevelUnit}}}
out, err := buildstep.RunAsk(context.Background(), ask, a)
if err != nil {
t.Fatal(err)
}
if l := out.(delivery.CheckAnswer).Levels; len(l) != 1 || l[0].Digest.Map()["x/a"] != "d1" {
if l := out.(buildstep.CheckAnswer).Levels; len(l) != 1 || l[0].Digest.Map()["x/a"] != "d1" {
t.Fatalf("levels = %+v, want the digest's packages", l)
}
}
@@ -8,7 +8,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// writeSiblings records the siblings the fetch placed, as the fetch does.
@@ -93,7 +93,7 @@ func TestTheFetchRecordsTheSiblingsItPlaced(t *testing.T) {
st := states.waitEnd(t)
var res FetchResult
json.Unmarshal(st.Result, &res)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
raw, err := os.ReadFile(filepath.Join(m.Work, res.Workspace, besideRecord))
@@ -14,8 +14,8 @@ import (
"strings"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The Go recipe of the build agent (toolchain.go, the entry of language go): static, trimmed of its own paths and of
@@ -177,7 +177,7 @@ func (m *Module) packInto(ctx context.Context, workspace, module, name, artifact
return BuildResult{}, fmt.Errorf("packing %s: %w", artifact, err)
}
if err := m.publishArchive(ctx, name, artifact, kind, digest, archive); err != nil {
return BuildResult{}, fmt.Errorf("%s%s: %v", delivery.NotStored, artifact, err)
return BuildResult{}, fmt.Errorf("%s%s: %v", builds.NotStored, artifact, err)
}
return BuildResult{Artifact: artifact, Digest: digest, Size: len(archive), Reference: catalogue.SeatReference(ArtifactSeat, name, artifact, digest)}, nil
}
@@ -14,7 +14,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -56,7 +56,7 @@ func buildModule(t *testing.T, manifest, content string) (*Module, *keptStates,
return m, states, calls
}
func runBuild(t *testing.T, m *Module, states *keptStates, artifact string) (BuildResult, delivery.Step) {
func runBuild(t *testing.T, m *Module, states *keptStates, artifact string) (BuildResult, sdkstep.Step) {
t.Helper()
for _, tool := range m.Tools(m.StepTools()) {
if tool.Name == "build_go_build" {
@@ -67,7 +67,7 @@ func runBuild(t *testing.T, m *Module, states *keptStates, artifact string) (Bui
}
st := states.waitEnd(t)
var res BuildResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatal(err)
}
@@ -106,7 +106,7 @@ func goEntry(t *testing.T) (compile []string, linker []string, stamp string) {
func TestTheBuildCommandCarriesTodaysFlagsAndEnvironment(t *testing.T) {
m, states, calls := buildModule(t, manifestWith(goBundle), "binary")
res, st := runBuild(t, m, states, "tools")
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
compile, linker, stamp := goEntry(t)
@@ -177,12 +177,12 @@ func TestAnArtifactOfAnotherLanguageFailsTheStepNamingIt(t *testing.T) {
} {
m, states, calls := buildModule(t, manifestWith(artifact), "x")
_, st := runBuild(t, m, states, "tools")
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "tools") || !strings.Contains(st.Reason, want) || len(*calls) != 0 {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "tools") || !strings.Contains(st.Reason, want) || len(*calls) != 0 {
t.Errorf("%s: the step ended %s: %q after %d commands", artifact, st.State, st.Reason, len(*calls))
}
}
m, states, _ := buildModule(t, manifestWith(goBundle), "x")
if _, st := runBuild(t, m, states, "nope"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, `"nope"`) || !strings.Contains(st.Reason, "tools") {
if _, st := runBuild(t, m, states, "nope"); st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, `"nope"`) || !strings.Contains(st.Reason, "tools") {
t.Errorf("an unknown artifact: %s %q", st.State, st.Reason)
}
}
@@ -209,7 +209,7 @@ func TestAModuleDirectoryOutsideTheWorkspaceIsRefused(t *testing.T) {
tool.Run(map[string]any{"workspace": "ws1", "module": "../../..", "artifact": "tools"})
}
}
if st := states.waitEnd(t); st.State != delivery.StepFailed || len(*calls) != 0 {
if st := states.waitEnd(t); st.State != sdkstep.StepFailed || len(*calls) != 0 {
t.Errorf("the step ended %s after %d commands", st.State, len(*calls))
}
}
@@ -230,7 +230,7 @@ func TestABuildOutputDirectoryLinkedOutsideTheModuleIsRefused(t *testing.T) {
t.Fatal(err)
}
_, st := runBuild(t, m, states, "tools")
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "outside") || !strings.Contains(st.Reason, outside) || len(*calls) != 0 {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "outside") || !strings.Contains(st.Reason, outside) || len(*calls) != 0 {
t.Errorf("the step ended %s: %q after %d commands", st.State, st.Reason, len(*calls))
}
if body, err := os.ReadFile(kept); err != nil || string(body) != "mine" {
@@ -248,7 +248,7 @@ func TestAManifestFallingBackToAPredefinedKeyBuilds(t *testing.T) {
}
m, states, _ := buildModule(t, string(raw), "binary")
res, st := runBuild(t, m, states, "tools")
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if res.Artifact != "tools" || res.Binary != "claude-code" || !strings.HasPrefix(res.Digest, "sha256:") {
@@ -9,7 +9,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -88,14 +88,14 @@ func newCandidateRig(t *testing.T) *candidateRig {
return cr
}
func (cr *candidateRig) test(t *testing.T, args map[string]any) (TestResult, delivery.Step) {
func (cr *candidateRig) test(t *testing.T, args map[string]any) (TestResult, sdkstep.Step) {
t.Helper()
if _, err := stepTool(t, cr.m, "build_go_test")(args); err != nil {
t.Fatal(err)
}
st := cr.states.waitEnd(t)
var res TestResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatalf("result %s: %v", st.Result, err)
}
@@ -119,7 +119,7 @@ func TestTheDigestAndTestToolsAnnounceTheRunner(t *testing.T) { // 1898 K1, ADR
func TestTheUnitLevelUnderTheWorkspaceRunnerRunsTheCandidateShutOff(t *testing.T) { // 1898 K1, K2 (AC4's unit part)
cr := newCandidateRig(t)
res, st := cr.test(t, map[string]any{"workspace": "ws1", "level": LevelUnit, "runner": RunnerWorkspace, "packages": []any{"./..."}})
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if res.Tests != 42 || res.Why != "mark" || res.JudgedBy != "candidate "+candidateSHA {
@@ -146,7 +146,7 @@ func TestTheUnitLevelUnderTheWorkspaceRunnerRunsTheCandidateShutOff(t *testing.T
func TestTheIntegrationLevelUnderTheWorkspaceRunnerRunsTheCandidate(t *testing.T) { // 1898 K2
cr := newCandidateRig(t)
res, st := cr.test(t, map[string]any{"workspace": "ws1", "level": LevelIntegration, "runner": RunnerWorkspace})
if st.State != delivery.StepPassed || res.JudgedBy != "candidate "+candidateSHA {
if st.State != sdkstep.StepPassed || res.JudgedBy != "candidate "+candidateSHA {
t.Fatalf("the step ended %s (%s): %+v", st.State, st.Reason, res)
}
if len(cr.specs) != 1 || cr.specs[0].Step != stepIntegration || cr.specs[0].Bound != IntegrationRun || cr.specs[0].Setup != IntegrationSetup {
@@ -177,7 +177,7 @@ func TestACandidateThatDoesNotCompileFailsTheStepWithTheCompilersFirstErrorAndRu
cr := newCandidateRig(t)
cr.compile = forgekey.Ran{Status: 1, Stderr: "# meshbuildgo/cmd/mesh-build-go\ncmd/mesh-build-go/digest.go:12:2: undefined: x\ncmd/mesh-build-go/digest.go:13:2: undefined: y\n"}
_, st := cr.test(t, map[string]any{"workspace": "ws1", "level": LevelUnit, "runner": RunnerWorkspace})
if st.State == delivery.StepPassed || !strings.Contains(st.Reason, "does not compile: cmd/mesh-build-go/digest.go:12:2: undefined: x") || strings.Contains(st.Reason, "undefined: y") {
if st.State == sdkstep.StepPassed || !strings.Contains(st.Reason, "does not compile: cmd/mesh-build-go/digest.go:12:2: undefined: x") || strings.Contains(st.Reason, "undefined: y") {
t.Fatalf("the step ended %s: %q, want it failed naming the first compile error", st.State, st.Reason)
}
if len(cr.specs) != 0 {
@@ -202,7 +202,7 @@ func TestTheInstalledProgramSaysItJudged(t *testing.T) { // 1898 K1, ADR 0535 §
args["runner"] = runner
}
res, st := runUnitWith(t, m, states, args)
if st.State != delivery.StepPassed || res.JudgedBy != RunnerInstalled {
if st.State != sdkstep.StepPassed || res.JudgedBy != RunnerInstalled {
t.Errorf("runner %v: the step ended %s, judged by %q, want installed", runner, st.State, res.JudgedBy)
}
}
@@ -246,14 +246,14 @@ func TestTheShutOffProcessRunsTheStepItIsAsked(t *testing.T) { // 1898 K2: the c
}
}
func runUnitWith(t *testing.T, m *Module, states *keptStates, args map[string]any) (TestResult, delivery.Step) {
func runUnitWith(t *testing.T, m *Module, states *keptStates, args map[string]any) (TestResult, sdkstep.Step) {
t.Helper()
if _, err := stepTool(t, m, "build_go_test")(args); err != nil {
t.Fatal(err)
}
st := states.waitEnd(t)
var res TestResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatalf("result %s: %v", st.Result, err)
}
@@ -7,9 +7,9 @@ package main
// engine cannot judge its own next code. The module fetches the commit
// (its fetch step: the forge key and the network, none of the change's code), builds the composer package in the Go
// module the ask names, in a run step shut off with nothing fetched, and keeps the program under its key
// (delivery.ComposerKey), so each commit is built once on a node. It fills the request's manifests from the delivery
// (sdkstep.ComposerKey), so each commit is built once on a node. It fills the request's manifests from the delivery
// indexer's manifests state, by digest, and runs the program shut off: no network, none of the account, the request a
// file of a workspace of its own. The answer is the composer's verdict whole (delivery.ComposeAnswer); the module
// file of a workspace of its own. The answer is the composer's verdict whole (sdkstep.ComposeAnswer); the module
// judges nothing of it.
import (
@@ -26,17 +26,20 @@ import (
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-delivery/sdk/composer"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/views"
)
// ManifestsTool is the delivery indexer's read tool of its manifests by digest, which a compose ask's request names (the
// module invokes it, module.json invokes). A manifest by its digest never changes and a compose ask is rare, so the
// module asks for the one key it needs when it needs it, and keeps no picture of the indexer's whole state (ADR 0615).
const ManifestsTool = delivery.IndexerModule + ".indexer_read_manifests"
// indexerModule is the module that holds the seat mesh-catalog, whose manifests state a compose ask reads.
const indexerModule = "mesh-delivery-indexer"
const ManifestsTool = indexerModule + ".indexer_read_manifests"
// composerLocks hold one mutex per composer key, so a composer two asks need at once is built once.
var composerLocks sync.Map
@@ -47,15 +50,14 @@ func (m *Module) composerPath(key string) string {
}
// compose runs a compose ask: the composer of its commit, built once, over its request with the manifests filled.
func (m *Module) compose(ctx context.Context, fetch buildstep.StepFunc, a delivery.StepAsk) (any, error) {
func (m *Module) compose(ctx context.Context, fetch buildstep.StepFunc, a sdkstep.StepAsk) (any, error) {
job := *a.Compose
repository, _ := job.Fetch["repository"].(string)
commit, _ := job.Fetch["commit"].(string)
repository, commit := job.Fetch.Repository, job.Fetch.Commit
installed, err := m.Probe(ctx)
if err != nil {
return nil, err
}
key := delivery.ComposerKey(repository, commit, job.Dir, job.Package, delivery.ToolchainUsed{Version: installed, Recipe: m.Recipe.Version()})
key := sdkstep.ComposerKey(repository, commit, job.Dir, job.Package, sdkstep.ToolchainUsed{Version: installed, Recipe: m.Recipe.Version()})
exe, built, err := m.composerOf(ctx, fetch, job, key)
if err != nil {
return nil, err
@@ -71,7 +73,7 @@ func (m *Module) compose(ctx context.Context, fetch buildstep.StepFunc, a delive
if err != nil {
return nil, err
}
return delivery.ComposeAnswer{Built: built, Key: key, Verdict: verdict}, nil
return sdkstep.ComposeAnswer{Built: built, Key: key, Verdict: verdict}, nil
}
// askManifest asks the indexer's read tool for one manifest by its key: nil when it keeps none. A call the indexer does not
@@ -81,7 +83,7 @@ func askManifest(key string) (json.RawMessage, error) {
}
func askManifestVia(ask func(string, any) (json.RawMessage, error), findings views.FindingWriter, now func() time.Time, key string) (json.RawMessage, error) {
st := views.Status{Owner: delivery.IndexerModule, Tool: ManifestsTool, State: "manifests", Asked: now().UTC()}
st := views.Status{Owner: indexerModule, Tool: ManifestsTool, State: "manifests", Asked: now().UTC()}
raw, err := ask(ManifestsTool, map[string]any{"key": key})
if err != nil {
st.Error = err.Error()
@@ -107,7 +109,7 @@ func (m *Module) manifestOf(digest string) ([]byte, error) {
if m.ReadManifest == nil {
return nil, errors.New("this module reads no manifests state")
}
key, err := delivery.ManifestsKey(digest)
key, err := catalogclient.ManifestsKey(digest)
if err != nil {
return nil, err
}
@@ -118,15 +120,15 @@ func (m *Module) manifestOf(digest string) ([]byte, error) {
if raw == nil {
return nil, fmt.Errorf("the indexer keeps no manifest of digest %s", digest)
}
man, err := delivery.DecodeManifest(raw)
man, err := catalogclient.DecodeManifest(raw)
if err != nil {
return nil, err
}
return []byte(man.JSON), nil
return []byte(man.Manifest), nil
}
// composerOf is the composer kept under key, or, when none is, the one built now from the fetched commit (built true).
func (m *Module) composerOf(ctx context.Context, fetch buildstep.StepFunc, job delivery.ComposeJob, key string) (string, bool, error) {
func (m *Module) composerOf(ctx context.Context, fetch buildstep.StepFunc, job sdkstep.ComposeJob, key string) (string, bool, error) {
exe := m.composerPath(key)
mu, _ := composerLocks.LoadOrStore(exe, &sync.Mutex{})
mu.(*sync.Mutex).Lock()
@@ -137,7 +139,11 @@ func (m *Module) composerOf(ctx context.Context, fetch buildstep.StepFunc, job d
if fetch == nil {
return "", false, buildstep.Refuse("this build module fetches nothing")
}
out, err := fetch(ctx, job.Fetch)
args, err := buildstep.FetchArgs(job.Fetch)
if err != nil {
return "", false, err
}
out, err := fetch(ctx, args)
if err != nil {
return "", false, err
}
@@ -156,7 +162,7 @@ func (m *Module) composerOf(ctx context.Context, fetch buildstep.StepFunc, job d
defer done()
dir := filepath.Join(m.Work, f.Workspace, "tree", job.Dir)
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err != nil {
return "", false, fmt.Errorf("%s at %s holds no go.mod in %s, so no composer can be built there", job.Fetch["repository"], job.Fetch["commit"], job.Dir)
return "", false, fmt.Errorf("%s at %s holds no go.mod in %s, so no composer can be built there", job.Fetch.Repository, job.Fetch.Commit, job.Dir)
}
into := filepath.Join(m.Work, f.Workspace, "composer", "mesh-compose")
if err := os.MkdirAll(filepath.Dir(into), 0o700); err != nil {
@@ -171,7 +177,7 @@ func (m *Module) composerOf(ctx context.Context, fetch buildstep.StepFunc, job d
if r.Status != 0 || r.Err != "" {
// The words Go says when the module holds no such package (a mesh-sdk from before the composer) are kept whole,
// so the asker tells that apart from a fault.
return "", false, fmt.Errorf("the composer %s does not build at %s: %s", job.Package, job.Fetch["commit"], compileError(r.Err+"\n"+r.Stderr+"\n"+r.Stdout))
return "", false, fmt.Errorf("the composer %s does not build at %s: %s", job.Package, job.Fetch.Commit, compileError(r.Err+"\n"+r.Stderr+"\n"+r.Stdout))
}
if err := copyExecutable(into, exe); err != nil {
return "", false, fmt.Errorf("the composer could not be kept: %w", err)
@@ -17,9 +17,9 @@ import (
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-delivery/sdk/composer"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/testbus"
)
@@ -111,7 +111,7 @@ func TestAComposeAskIsBuiltOnceAndAnsweredOnTheBus(t *testing.T) {
}
ctx, stop := context.WithCancel(context.Background())
defer stop()
if err := buildstep.DeclareAskWorkers(ctx, js, delivery.AskGo, 2*time.Minute); err != nil {
if err := buildstep.DeclareAskWorkers(ctx, js, sdkstep.AskGo, 2*time.Minute); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = js.DeleteStream(context.Background(), "SEAT_NODE_BUILD_GO") })
@@ -158,39 +158,39 @@ func TestAComposeAskIsBuiltOnceAndAnsweredOnTheBus(t *testing.T) {
}
}
}
p := m.Puller(steps, &buildstep.JetStreamAsks{JS: js, Language: delivery.AskGo})
p := m.Puller(steps, &buildstep.JetStreamAsks{JS: js, Language: sdkstep.AskGo})
p.PullWait = 200 * time.Millisecond
go p.Pull(ctx)
ask := func() delivery.Step {
ask := func() sdkstep.Step {
a := composeAskOf(t, digests[0])
a.Compose.Fetch["repository"], a.Compose.Dir, a.Compose.Package = "novox/mesh-host", delivery.RootPath, "github.com/novox/mesh-host/cmd/mesh-compose"
raw, err := delivery.EncodeStepAsk(a)
a.Compose.Fetch.Repository, a.Compose.Dir, a.Compose.Package = "novox/mesh-host", catalogclient.RootPath, "github.com/novox/mesh-host/cmd/mesh-compose"
raw, err := sdkstep.EncodeStepAsk(a)
if err != nil {
t.Fatal(err)
}
subject, _ := delivery.AskSubject(delivery.AskGo, delivery.AskCheck)
subject, _ := sdkstep.AskSubject(sdkstep.AskGo, sdkstep.AskCheck)
if _, err := js.Publish(ctx, subject, raw); err != nil {
t.Fatal(err)
}
key, _ := delivery.AskStepKey("ace", a.ID)
key, _ := sdkstep.AskStepKey("ace", a.ID)
for deadline := time.Now().Add(90 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
states.mu.Lock()
v := states.steps[key]
states.mu.Unlock()
if len(v) > 0 && v[len(v)-1].State != delivery.StepRunning {
if len(v) > 0 && v[len(v)-1].State != sdkstep.StepRunning {
return v[len(v)-1]
}
}
t.Fatalf("the compose ask %s never ended", a.ID)
return delivery.Step{}
return sdkstep.Step{}
}
for n, wantBuilt := range []bool{true, false} {
step := ask()
if step.State != delivery.StepPassed {
if step.State != sdkstep.StepPassed {
t.Fatalf("ask %d: %s: %s", n+1, step.State, step.Reason)
}
var ans delivery.ComposeAnswer
var ans sdkstep.ComposeAnswer
var v composer.Verdict
if err := json.Unmarshal(step.Result, &ans); err != nil {
t.Fatal(err)
@@ -11,9 +11,10 @@ import (
"testing"
"time"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
meshcat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-delivery/sdk/composer"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/views"
)
@@ -29,9 +30,9 @@ func indexerHolds(t *testing.T, bodies ...string) (func(string) (json.RawMessage
t.Helper()
kept, digests := map[string]json.RawMessage{}, []string{}
for _, b := range bodies {
d := delivery.ManifestDigest([]byte(b))
key, _ := delivery.ManifestsKey(d)
raw, err := delivery.EncodeManifest(delivery.Manifest{Module: "demo", JSON: b})
d := catalogclient.ManifestDigest([]byte(b))
key, _ := catalogclient.ManifestsKey(d)
raw, err := catalogclient.EncodeManifest(meshcat.ManifestsByDigestValue{Module: "demo", Manifest: b})
if err != nil {
t.Fatal(err)
}
@@ -40,12 +41,12 @@ func indexerHolds(t *testing.T, bodies ...string) (func(string) (json.RawMessage
return func(key string) (json.RawMessage, error) { return kept[key], nil }, digests
}
func composeAskOf(t *testing.T, digest string) delivery.StepAsk {
func composeAskOf(t *testing.T, digest string) sdkstep.StepAsk {
t.Helper()
req, _ := json.Marshal(composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change",
Manifests: map[string]composer.Manifest{"demo": {Digest: digest}}}}})
return delivery.StepAsk{ID: sdkstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "mesh-delivery-assigner",
At: time.Now(), Compose: &delivery.ComposeJob{Fetch: map[string]any{"repository": "novox/mesh-sdk", "commit": composeCommit},
return sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskCheck, Language: sdkstep.AskGo, Asker: "mesh-delivery-assigner",
At: time.Now(), Compose: &sdkstep.ComposeJob{Fetch: sdkstep.Fetch{Repository: "novox/mesh-sdk", Commit: composeCommit},
Dir: "go", Package: composer.Package, Request: req}}
}
@@ -66,7 +67,7 @@ func TestAKeptComposerIsRunWithTheManifestsFilled(t *testing.T) {
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
m.ReadManifest = read
a := composeAskOf(t, digests[0])
key := delivery.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
key := sdkstep.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, sdkstep.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
exe := m.composerPath(key)
if err := os.MkdirAll(filepath.Dir(exe), 0o700); err != nil {
t.Fatal(err)
@@ -84,7 +85,7 @@ func TestAKeptComposerIsRunWithTheManifestsFilled(t *testing.T) {
if err != nil {
t.Fatal(err)
}
ans := out.(delivery.ComposeAnswer)
ans := out.(sdkstep.ComposeAnswer)
var v composer.Verdict
if err := json.Unmarshal(ans.Verdict, &v); err != nil {
t.Fatal(err)
@@ -103,7 +104,7 @@ func TestAComposeAskFailsLoudly(t *testing.T) {
m := composeModule(t)
m.ReadManifest, _ = indexerHolds(t)
a := composeAskOf(t, "feedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed")
key := delivery.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
key := sdkstep.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, sdkstep.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
os.MkdirAll(filepath.Dir(m.composerPath(key)), 0o700)
os.WriteFile(m.composerPath(key), []byte("#!/bin/sh\nexit 0\n"), 0o700)
if _, err := m.compose(context.Background(), nil, a); err == nil || !strings.Contains(err.Error(), "feedfeed") {
@@ -127,7 +128,7 @@ func TestAComposeAskFailsLoudly(t *testing.T) {
// 1622, ADR 0615: the module asks the delivery indexer's read tool for the manifests a compose ask names by digest, and
// reads none of the indexer's state.
func TestTheModuleReadsTheIndexersManifests(t *testing.T) {
if m := readManifest(t); !slices.Contains(m.Invokes, ManifestsTool) || slices.Contains(m.Reads, delivery.IndexerModule+".manifests") {
if m := readManifest(t); !slices.Contains(m.Invokes, ManifestsTool) || slices.Contains(m.Reads, indexerModule+".manifests") {
t.Errorf("invokes = %v, reads = %v: it asks %s", m.Invokes, m.Reads, ManifestsTool)
}
}
@@ -161,7 +162,7 @@ func TestAComposeAskBuildsTheNodeEnginesComposerAtItsRoot(t *testing.T) {
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
m.ReadManifest = read
a := composeAskOf(t, digests[0])
a.Compose.Fetch["repository"], a.Compose.Dir, a.Compose.Package = "novox/mesh-host", delivery.RootPath, nodeEngineComposer
a.Compose.Fetch.Repository, a.Compose.Dir, a.Compose.Package = "novox/mesh-host", catalogclient.RootPath, nodeEngineComposer
tree := ""
var built []string
real := m.Run
@@ -191,8 +192,8 @@ func TestAComposeAskBuildsTheNodeEnginesComposerAtItsRoot(t *testing.T) {
if err != nil {
t.Fatal(err)
}
ans := out.(delivery.ComposeAnswer)
key := delivery.ComposerKey("novox/mesh-host", composeCommit, delivery.RootPath, nodeEngineComposer, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
ans := out.(sdkstep.ComposeAnswer)
key := sdkstep.ComposerKey("novox/mesh-host", composeCommit, catalogclient.RootPath, nodeEngineComposer, sdkstep.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
var v composer.Verdict
if err := json.Unmarshal(ans.Verdict, &v); err != nil {
t.Fatal(err)
@@ -22,7 +22,8 @@ import (
"strconv"
"strings"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
trunk "git.novox.be/novox/mesh-delivery/sdk/trunk"
)
// DigestVersion is the form of the digest's text: changed whenever what the digest hashes changes in kind.
@@ -56,7 +57,7 @@ const namesVariable = "MESH_NODE_NAMES"
const ReadsOutsideMarker = "reads-outside"
// NeedsMarker is the name of a file in a package's directory that names, one per line, what its integration tests need
// of the node they run on (issue 890; mesh-sdk delivery.AskNeeds): `node-build-node` for tests that read the Node build
// of the node they run on (issue 890; mesh-delivery-sdk buildstep.AskNeeds): `node-build-node` for tests that read the Node build
// module's installed toolchain. The digest reports it (DigestResult.Needs); a check ask leaves such a package untested on
// a node that lacks it, and the checker asks it again of a node that has it. A marker beside the tests, as
// ReadsOutsideMarker is, and not a manifest field: it is a property of the package's tests, read only where they are
@@ -149,7 +150,7 @@ type DigestResult struct {
neverKept map[string]string
hashedFiles map[string]string
// Needs are the packages whose tests at this level need something of the node, as their NeedsMarker names it.
Needs []delivery.PackageNeeds `json:"needs,omitempty"`
Needs []sdkstep.PackageNeeds `json:"needs,omitempty"`
// JudgedBy is the program that ran the digest (ADR 0535 §5): "installed", or "candidate <merge commit>" under the
// runner workspace.
JudgedBy string `json:"judged_by,omitempty"`
@@ -168,8 +169,8 @@ func (m *Module) digestTool() StepTool {
"workspace": map[string]any{"type": "string", "description": "the workspace id build_go_fetch answered"},
"level": map[string]any{"type": "string", "enum": []string{LevelUnit, LevelIntegration}, "description": "the level"},
"runner": runnerProperty,
delivery.NodeNamesArg: map[string]any{"type": "array", "items": map[string]any{"type": "string"},
"description": "optional, the names of the mesh's nodes: a package whose tests read " + delivery.NodeNamesEnv + " has them in its digest"},
sdkstep.NodeNamesArg: map[string]any{"type": "array", "items": map[string]any{"type": "string"},
"description": "optional, the names of the mesh's nodes: a package whose tests read " + sdkstep.NodeNamesEnv + " has them in its digest"},
},
"required": []string{"workspace", "level"},
},
@@ -184,7 +185,7 @@ func (m *Module) digest(ctx context.Context, args map[string]any) (any, error) {
if err != nil {
return nil, err
}
names, err := nodeNamesArg(args[delivery.NodeNamesArg])
names, err := nodeNamesArg(args[sdkstep.NodeNamesArg])
if err != nil {
return nil, err
}
@@ -344,7 +345,7 @@ func digestModule(res *DigestResult, pkgs map[string]*goPackage, tree string, he
return fmt.Errorf("%s: its %s: %w", path, NeedsMarker, err)
}
if len(n) > 0 {
res.Needs = append(res.Needs, delivery.PackageNeeds{Package: path, Needs: n})
res.Needs = append(res.Needs, sdkstep.PackageNeeds{Package: path, Needs: n})
}
}
declared, why, err := declaredReads(tree, root, res.hashed())
@@ -401,7 +402,7 @@ func packageDigest(root, testMain *goPackage, pkgs map[string]*goPackage, tree s
// A module outside the tree counts by its version and checksum; a package of the mesh's own forge by the content of
// its files, as a local replacement does, so a new version of a mesh package whose reached files are the same keeps
// every verdict kept for them (ADR 0593 §5, ADR 0444 §3).
if !mod.Main && mod.Version != "" && !strings.HasPrefix(mod.Path, delivery.ForgeModulePrefix) {
if !mod.Main && mod.Version != "" && !strings.HasPrefix(mod.Path, trunk.ForgeModulePrefix) {
lines = append(lines, "module "+mod.Path+"@"+mod.Version+" "+mod.Sum)
continue
}
@@ -10,7 +10,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -112,7 +112,7 @@ func TestFetchAsksGitForTheSshRemoteAndDownloadsEachModule(t *testing.T) {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
lines := strings.Join(f.lines(), "\n")
@@ -222,7 +222,7 @@ func TestAVerifyThatFailsEndsTheStepNamingTheDirectoryAndTheLine(t *testing.T) {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepFailed {
if st.State != sdkstep.StepFailed {
t.Fatalf("the step ended %s, want failed", st.State)
}
if !strings.Contains(st.Reason, "tools") || !strings.Contains(st.Reason, "example.org/x v1.0.0: dir has been modified") || strings.Contains(st.Reason, "second line") {
@@ -251,7 +251,7 @@ func TestTheCallAnswersAStepIdBeforeTheFetchEnds(t *testing.T) {
states.mu.Lock()
running := states.steps["ace."+id]
states.mu.Unlock()
if len(running) != 1 || running[0].State != delivery.StepRunning {
if len(running) != 1 || running[0].State != sdkstep.StepRunning {
t.Errorf("the key holds %+v, want one running step", running)
}
close(f.gate)
@@ -267,7 +267,7 @@ func TestTheCallAnswersAStepIdBeforeTheFetchEnds(t *testing.T) {
last := states.steps["ace."+id][len(states.steps["ace."+id])-1]
states.mu.Unlock()
var res FetchResult
if last.State != delivery.StepPassed || json.Unmarshal(last.Result, &res) != nil || res.Workspace == "" {
if last.State != sdkstep.StepPassed || json.Unmarshal(last.Result, &res) != nil || res.Workspace == "" {
t.Errorf("the key ends %+v", last)
}
}
@@ -278,7 +278,7 @@ func TestARepositoryThatIsNotOwnerSlashNameIsRefusedBeforeAnythingRuns(t *testin
m, states := fetchModule(t, f)
fetchTool(t, m)(map[string]any{"repository": repo, "commit": commitSHA})
st := states.waitEnd(t)
if st.State != delivery.StepFailed || len(f.lines()) != 0 {
if st.State != sdkstep.StepFailed || len(f.lines()) != 0 {
t.Errorf("%q: the step ended %s after %d commands", repo, st.State, len(f.lines()))
}
}
@@ -22,8 +22,8 @@ import (
"golang.org/x/mod/module"
modzip "golang.org/x/mod/zip"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// GoPackageSeat is the registry seat a Go package is published on.
@@ -82,7 +82,7 @@ func (m *Module) buildGoPackage(ctx context.Context, workspace, dir string, art
return res, nil
}
if err := m.publishOn(ctx, seat, path, "package", version, digest, transferName(path, version, digest), body); err != nil {
return BuildResult{}, fmt.Errorf("%s%s: %v", delivery.NotStored, art.Name, err)
return BuildResult{}, fmt.Errorf("%s%s: %v", builds.NotStored, art.Name, err)
}
res.Reference = path + "@" + version
return res, nil
@@ -14,8 +14,8 @@ import (
"slices"
"strings"
trunk "git.novox.be/novox/mesh-delivery/sdk/trunk"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -94,14 +94,14 @@ func ModuleImports(ctx context.Context, run forgekey.Runner, env []string, dir s
out = append(out, p)
}
slices.Sort(out)
if err := delivery.CheckImports(out); err != nil {
if err := trunk.CheckImports(out); err != nil {
return ImportsResult{}, err
}
return ImportsResult{Imports: out}, nil
}
func meshPackage(pkg string) bool {
_, _, ok := delivery.PackageRepository(pkg)
_, _, ok := trunk.PackageRepository(pkg)
return ok
}
@@ -12,7 +12,7 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/buildcpu"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/health"
)
@@ -73,7 +73,7 @@ func TestTheFetchStepsGoCommandsSayGotoolchainLocalAndFetchNoToolchain(t *testin
if _, err := fetchTool(t, m)(fetchArgs); err != nil {
t.Fatal(err)
}
if st := states.waitEnd(t); st.State != delivery.StepPassed {
if st := states.waitEnd(t); st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
goCalls := 0
@@ -11,7 +11,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -126,7 +126,7 @@ func integModule(t *testing.T, f *integFake) (*Module, *keptStates) {
return m, states
}
func runLevelTool(t *testing.T, m *Module, states *keptStates, level string) (TestResult, delivery.Step) {
func runLevelTool(t *testing.T, m *Module, states *keptStates, level string) (TestResult, sdkstep.Step) {
t.Helper()
for _, tool := range m.Tools(m.StepTools()) {
if tool.Name != "build_go_test" {
@@ -138,7 +138,7 @@ func runLevelTool(t *testing.T, m *Module, states *keptStates, level string) (Te
}
st := states.waitEnd(t)
var res TestResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatal(err)
}
@@ -152,7 +152,7 @@ func TestTheStoreListensOnASocketUnderTheRunAndNowhereElse(t *testing.T) {
f := newIntegFake(t, "slow")
m, states := integModule(t, f)
res, st := runLevelTool(t, m, states, "integration")
if st.State != delivery.StepPassed || !res.Passed || res.Level != "integration" {
if st.State != sdkstep.StepPassed || !res.Passed || res.Level != "integration" {
t.Fatalf("the step ended %s (%s): %+v", st.State, st.Reason, res)
}
var start, initdb string
@@ -219,14 +219,14 @@ func TestAnotherPostgresMajorRefusesTheIntegrationLevelOnly(t *testing.T) {
f.postgres = "postgres (PostgreSQL) 17.2"
m, states := integModule(t, f)
_, st := runLevelTool(t, m, states, "integration")
if st.State != delivery.StepRefused || !strings.Contains(st.Reason, "17") || !strings.Contains(st.Reason, "18") {
if st.State != sdkstep.StepRefused || !strings.Contains(st.Reason, "17") || !strings.Contains(st.Reason, "18") {
t.Fatalf("the integration step ended %s: %q", st.State, st.Reason)
}
if strings.Contains(f.lines(), "initdb") || strings.Contains(f.lines(), "-tags") {
t.Errorf("the refused level ran something:\n%s", f.lines())
}
res, st := runLevelTool(t, m, states, "unit")
if st.State != delivery.StepPassed || !res.Passed || res.Level != "unit" {
if st.State != sdkstep.StepPassed || !res.Passed || res.Level != "unit" {
t.Errorf("the unit level: %s %+v", st.State, res)
}
}
@@ -308,7 +308,7 @@ func TestAStoreThatDoesNotStartEndsTheStepFailedAndLeavesNothing(t *testing.T) {
return r
}
_, st := runLevelTool(t, m, states, "integration")
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "pg_ctl: could not start server") {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "pg_ctl: could not start server") {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
if !strings.Contains(f.lines(), " stop") {
@@ -11,7 +11,7 @@ import (
"testing"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -118,7 +118,7 @@ func TestTheUnitLevelRunsEveryGoTestShutOff(t *testing.T) {
m, states, ws, calls := workspaceFor(t, "failing")
var seen []shutOff
m.Run = throughBubblewrap(t, m.Run, &seen)
if _, st := runUnit(t, m, states, ws); st.State != delivery.StepPassed {
if _, st := runUnit(t, m, states, ws); st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
ran := 0
@@ -174,7 +174,7 @@ func TestABuildRunsShutOffAndOnlyATrunkBuildWritesTheCaches(t *testing.T) {
if _, err := run(args); err != nil {
t.Fatal(err)
}
if st := states.waitEnd(t); st.State != delivery.StepPassed {
if st := states.waitEnd(t); st.State != sdkstep.StepPassed {
t.Fatalf("publish %v: the step ended %s: %s", publish, st.State, st.Reason)
}
if len(seen) != 1 || !strings.HasPrefix(seen[0].command, "go ") || !strings.Contains(seen[0].command, " build ") {
@@ -230,7 +230,7 @@ func TestTheIntegrationLevelRunsInOneShutOffProcess(t *testing.T) {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
var got TestResult
@@ -6,6 +6,7 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
@@ -27,7 +28,7 @@ func TestTheTestStepKeepsTheFailureContract(t *testing.T) {
// case: a step a previous process left running is ended at once and said, whatever its age, and a step of this process
// is kept.
func TestTheStartSweepEndsWhatAPreviousProcessLeft(t *testing.T) {
conformance.LeftRunning(t, func(ctx context.Context, scan sdkstep.Scan, node string, before time.Time, logf func(string, ...any)) {
conformance.LeftRunning(t, func(ctx context.Context, scan buildrun.Scan, node string, before time.Time, logf func(string, ...any)) {
(&Module{Node: node}).endLeftRunning(ctx, scan, before, logf)
})
}
@@ -17,7 +17,7 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// levelArg is the first argument that makes this binary a step's shut-off process: the integration level's, and, when
@@ -148,7 +148,7 @@ func (m *Module) nodeToolchains() []string {
const nodeToolchainMark = ".installed"
// Has says whether this node has a need a check ask or a package's tests name (issue 890, ADR 0552; mesh-sdk
// delivery.AskNeeds), read from the node itself at every ask: node-build-node when the Node build module's toolchains
// sdkstep.AskNeeds), read from the node itself at every ask: node-build-node when the Node build module's toolchains
// directory, given to the integration level as nodeToolchains gives it, holds a toolchain installed whole (its mark and
// its package-lock.json readable by this account), and node, npm and bwrap are installed: what mesh-build-node's
// integration tests (realMachine) look for before they run. Any other need it lacks.
@@ -157,7 +157,7 @@ func (m *Module) Has(need string) bool {
return m.HasNeed(need)
}
switch need {
case delivery.NeedNodeBuildNode:
case sdkstep.NeedNodeBuildNode:
dirs := m.nodeToolchains()
if len(dirs) == 0 {
return false
@@ -13,8 +13,9 @@ import (
"strings"
"time"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The directories the module's manifest places outside its home (ADR 0466 §1): the workspaces, and the Go module
@@ -37,13 +38,13 @@ func main() {
m := &Module{
Node: strings.TrimSpace(os.Getenv("MESH_NODE")),
Recipe: Current(),
Toolchains: stdio.State(delivery.ToolchainsState).Put,
Toolchains: stdio.State(seat.StateToolchains).Put,
Emit: func(t string, b any) error { return stdio.Emit(t, b) },
Steps: stdio.State(delivery.StepsState).Put,
ReadSteps: readOf(stdio.State(delivery.StepsState)),
Pauses: stdio.State(delivery.PausesState).Put,
ReadPauses: readOf(stdio.State(delivery.PausesState)),
ReadToolchainsState: readOf(stdio.State(delivery.ToolchainsState)),
Steps: stdio.State(seat.StateSteps).Put,
ReadSteps: readOf(stdio.State(seat.StateSteps)),
Pauses: stdio.State(sdkstep.PausesState).Put,
ReadPauses: readOf(stdio.State(sdkstep.PausesState)),
ReadToolchainsState: readOf(stdio.State(seat.StateToolchains)),
Ctx: ctx,
Say: logf,
Work: WorkDir,
@@ -52,6 +52,10 @@ func resource(t *testing.T, m catalogue.Manifest, id string) map[string]any {
func TestManifestDeclaresTheAccountTheCapabilityAndThePackages(t *testing.T) {
m := readManifest(t)
// The key keeper is ssh-client's published package, taken by its series (ADR 0634 §1).
// The builder's step, answer and key forms are read through its published package, taken by its series (ADR 0634 §1).
if m.Packages["builds"] != "0.1" {
t.Errorf("packages %v: want builds of series 0.1", m.Packages)
}
if m.Packages["sshkeys"] != "0.1" {
t.Errorf("packages %v: want sshkeys of series 0.1", m.Packages)
}
@@ -3,7 +3,6 @@ package main
import (
"encoding/json"
"fmt"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"os"
"os/exec"
"path/filepath"
@@ -12,7 +11,8 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
func fetchOnce(t *testing.T, m *Module, states *keptStates, args map[string]any) FetchResult {
@@ -21,7 +21,7 @@ func fetchOnce(t *testing.T, m *Module, states *keptStates, args map[string]any)
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
var res FetchResult
@@ -9,8 +9,8 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/health"
)
@@ -129,7 +129,7 @@ func (m *Module) Start(ctx context.Context) {
m.Say("the toolchain: %v", err)
}
m.CleanWorkspaces(time.Now())
sdkstep.EndStaleRound(m.stepScan(), m.Node, m.Say) // asks past the step bound, on the module's own round (issue 1890)
buildrun.EndStaleRound(m.stepScan(), m.Node, m.Say) // asks past the step bound, on the module's own round (issue 1890)
select {
case <-ctx.Done():
return
@@ -183,8 +183,8 @@ func (m *Module) Long(tool string, run func(ctx context.Context, args map[string
if err != nil {
return nil, err
}
key, _ := delivery.StepKey(m.Node, id)
return map[string]any{"step": id, "key": key, "state": delivery.StepRunning}, nil
key, _ := sdkstep.StepKey(m.Node, id)
return map[string]any{"step": id, "key": key, "state": sdkstep.StepRunning}, nil
}
}
@@ -9,24 +9,25 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
buildstep "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/health"
)
// keptStates stands in for the toolchains and the steps states.
type keptStates struct {
mu sync.Mutex
toolchains map[string]delivery.Toolchain
steps map[string][]delivery.Step
toolchains map[string]buildstep.Toolchain
steps map[string][]sdkstep.Step
ended chan string
}
func newStates() *keptStates {
return &keptStates{toolchains: map[string]delivery.Toolchain{}, steps: map[string][]delivery.Step{}, ended: make(chan string, 16)}
return &keptStates{toolchains: map[string]buildstep.Toolchain{}, steps: map[string][]sdkstep.Step{}, ended: make(chan string, 16)}
}
func (s *keptStates) putToolchain(key string, value any) (uint64, error) {
tc, err := delivery.DecodeToolchain(value.(json.RawMessage))
tc, err := buildstep.DecodeToolchain(value.(json.RawMessage))
if err != nil {
return 0, err
}
@@ -37,27 +38,27 @@ func (s *keptStates) putToolchain(key string, value any) (uint64, error) {
}
func (s *keptStates) putStep(key string, value any) (uint64, error) {
st, err := delivery.DecodeStep(value.(json.RawMessage))
st, err := sdkstep.DecodeStep(value.(json.RawMessage))
if err != nil {
return 0, err
}
s.mu.Lock()
s.steps[key] = append(s.steps[key], st)
s.mu.Unlock()
if st.State != delivery.StepRunning {
if st.State != sdkstep.StepRunning {
s.ended <- key
}
return 1, nil
}
func (s *keptStates) toolchain(key string) (delivery.Toolchain, bool) {
func (s *keptStates) toolchain(key string) (buildstep.Toolchain, bool) {
s.mu.Lock()
defer s.mu.Unlock()
tc, ok := s.toolchains[key]
return tc, ok
}
func (s *keptStates) waitEnd(t *testing.T) delivery.Step {
func (s *keptStates) waitEnd(t *testing.T) sdkstep.Step {
t.Helper()
select {
case key := <-s.ended:
@@ -67,7 +68,7 @@ func (s *keptStates) waitEnd(t *testing.T) delivery.Step {
return v[len(v)-1]
case <-time.After(30 * time.Second):
t.Fatal("the step never ended on its key")
return delivery.Step{}
return sdkstep.Step{}
}
}
@@ -85,7 +86,7 @@ func TestStartWritesTheToolchainAndHealthIsHealthy(t *testing.T) {
ctx, stop := context.WithCancel(context.Background())
defer stop()
go m.Start(ctx)
var tc delivery.Toolchain
var tc buildstep.Toolchain
for deadline := time.Now().Add(30 * time.Second); time.Now().Before(deadline); time.Sleep(5 * time.Millisecond) {
if got, ok := states.toolchain("ace.go"); ok {
tc = got
@@ -127,7 +128,7 @@ func TestAGoThatCannotBeRunRefusesEveryStepAndHealthIsUnhealthy(t *testing.T) {
if out.(map[string]any)["step"] == "" {
t.Fatalf("%s answered no step id: %v", tool.Name, out)
}
if st := states.waitEnd(t); st.State != delivery.StepRefused {
if st := states.waitEnd(t); st.State != sdkstep.StepRefused {
t.Errorf("%s: the step ended %s, want refused", tool.Name, st.State)
} else {
reason = st.Reason
@@ -8,7 +8,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// ADR 0499 §4 (issue 1758): a fetch asked with the module's directory and its named paths leaves the workspace holding
@@ -28,7 +28,7 @@ func TestAFetchWithNamedPathsNarrowsTheWorkspace(t *testing.T) {
})
m, states := f.module(t)
res, st := fetched(t, m, states, map[string]any{"repository": "novox/narrow", "commit": head, "dir": "lib", "paths": []any{"cmd", "go.mod"}})
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the fetch ended %s: %s", st.State, st.Reason)
}
tree := filepath.Join(m.Work, res.Workspace, "tree")
@@ -46,7 +46,7 @@ func TestAFetchWithNamedPathsNarrowsTheWorkspace(t *testing.T) {
t.Errorf("the fetch answered the modules %v, want lib alone", res.Modules)
}
_, st = fetched(t, m, states, map[string]any{"repository": "novox/narrow", "commit": head, "paths": []any{"cmd"}})
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "no module directory") {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "no module directory") {
t.Errorf("paths without a directory: %s %q", st.State, st.Reason)
}
}
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// Issue 1309: a test that runs a step in bubblewrap is proof only where it runs. Where a build module judges (it hands
@@ -32,11 +32,11 @@ func TestAMissingToolSkipsExceptBubblewrapOnABuildNode(t *testing.T) {
// The levels a build module judges hand the tests the build node mark, with the siblings they read.
func TestEveryLevelHandsTheTestsTheBuildNodeMark(t *testing.T) {
m := &Module{Work: t.TempDir()}
if env := m.checkEnv("ws1", nil); !slices.Contains(env, BuildNodeEnv+"=1") || slices.ContainsFunc(env, func(e string) bool { return strings.HasPrefix(e, delivery.NodeNamesEnv) }) {
if env := m.checkEnv("ws1", nil); !slices.Contains(env, BuildNodeEnv+"=1") || slices.ContainsFunc(env, func(e string) bool { return strings.HasPrefix(e, sdkstep.NodeNamesEnv) }) {
t.Errorf("env %v lacks %s=1, or names nodes when none were asked", env, BuildNodeEnv)
}
// And the mesh's node names, when the ask carried them (ADR 0567 §12).
if env := m.checkEnv("ws1", []string{"a", "b"}); !slices.Contains(env, delivery.NodeNamesEnv+"=a,b") {
if env := m.checkEnv("ws1", []string{"a", "b"}); !slices.Contains(env, sdkstep.NodeNamesEnv+"=a,b") {
t.Errorf("env %v lacks the node names", env)
}
}
@@ -9,7 +9,7 @@ import (
"strconv"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -36,7 +36,7 @@ func integrationShutOffWith(t *testing.T, toolchains string) shutOff {
if _, err := run(map[string]any{"workspace": ws, "level": LevelIntegration}); err != nil {
t.Fatal(err)
}
if st := states.waitEnd(t); st.State != delivery.StepPassed {
if st := states.waitEnd(t); st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if len(seen) != 1 {
@@ -81,12 +81,12 @@ func TestANodeWithoutNodeToolchainsRunsTheLevelWithNothingMoreBound(t *testing.T
}
t.Setenv("PATH", bin)
m := &Module{NodeToolchains: missing}
if m.Has(delivery.NeedNodeBuildNode) {
if m.Has(sdkstep.NeedNodeBuildNode) {
t.Error("a node with no Node toolchain has node-build-node")
}
installed := t.TempDir()
m.NodeToolchains = installed
if m.Has(delivery.NeedNodeBuildNode) {
if m.Has(sdkstep.NeedNodeBuildNode) {
t.Error("a node with an empty toolchains directory has node-build-node")
}
half := filepath.Join(installed, "0.1.0")
@@ -96,19 +96,19 @@ func TestANodeWithoutNodeToolchainsRunsTheLevelWithNothingMoreBound(t *testing.T
if err := os.WriteFile(filepath.Join(half, "package-lock.json"), []byte("{}"), 0o644); err != nil {
t.Fatal(err)
}
if m.Has(delivery.NeedNodeBuildNode) {
if m.Has(sdkstep.NeedNodeBuildNode) {
t.Error("a toolchain without its installed mark counts")
}
if err := os.WriteFile(filepath.Join(half, nodeToolchainMark), nil, 0o644); err != nil {
t.Fatal(err)
}
if !m.Has(delivery.NeedNodeBuildNode) || m.Has("gpu") {
t.Errorf("with a toolchain installed: node-build-node %v, gpu %v; want true, false", m.Has(delivery.NeedNodeBuildNode), m.Has("gpu"))
if !m.Has(sdkstep.NeedNodeBuildNode) || m.Has("gpu") {
t.Errorf("with a toolchain installed: node-build-node %v, gpu %v; want true, false", m.Has(sdkstep.NeedNodeBuildNode), m.Has("gpu"))
}
if err := os.Remove(filepath.Join(bin, "bwrap")); err != nil {
t.Fatal(err)
}
if m.Has(delivery.NeedNodeBuildNode) {
if m.Has(sdkstep.NeedNodeBuildNode) {
t.Error("a node with no bubblewrap installed has node-build-node")
}
}
@@ -120,7 +120,7 @@ func TestTheUnitLevelIsNotGivenTheNodeToolchains(t *testing.T) {
m.NodeToolchains = dir
var seen []shutOff
m.Run = throughBubblewrap(t, m.Run, &seen)
if _, st := runUnit(t, m, states, ws); st.State != delivery.StepPassed {
if _, st := runUnit(t, m, states, ws); st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
for _, s := range seen {
@@ -9,7 +9,6 @@ import (
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// 1925/AC1: packagesArg takes the list as the bus's JSON shape ([]any) and as the typed list an ask hands in process
@@ -64,8 +63,8 @@ func TestACheckAskReachesTheTestStepPastItsPackages(t *testing.T) {
}
}
}
a := delivery.StepAsk{ID: sdkstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "mesh-delivery-checker",
At: time.Now(), Check: &delivery.CheckJob{Fetch: map[string]any{"repository": "novox/x", "commit": askCommit}, Levels: []string{delivery.LevelUnit}}}
a := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskCheck, Language: sdkstep.AskGo, Asker: "mesh-delivery-checker",
At: time.Now(), Check: &sdkstep.CheckJob{Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: askCommit}, Levels: []string{sdkstep.LevelUnit}}}
out, err := buildstep.RunAsk(context.Background(), m.askSteps(steps), a)
got := ""
if err != nil {
@@ -13,7 +13,7 @@ import (
"path/filepath"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// 1148/AC2 as issue 1585 S15 has it (novox/hq ADR 0573 §4): given a forge with a mesh-sdk-like pull request whose branch
@@ -46,7 +46,7 @@ func TestRealFetchAndBuildOfAMemberAskedAPullHeadItsGoModDoesNotName(t *testing.
res, st := fetched(t, m, states, map[string]any{"repository": "novox/member", "commit": memberHead,
"packages": map[string]any{"git.novox.be/novox/dep/go": head}})
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the fetch ended %s: %s", st.State, st.Reason)
}
if len(res.Packages) != 1 || res.Packages[0].Built != head || res.Packages[0].Named != "v0.0.0-20261021120000-"+fork[:12] {
@@ -10,7 +10,7 @@ import (
"sync"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -71,14 +71,14 @@ func newPinHarness(t *testing.T, gomod string, vendor bool, pulls string) (*pinH
return h, states
}
func (h *pinHarness) fetch(states *keptStates) (FetchResult, delivery.Step) {
func (h *pinHarness) fetch(states *keptStates) (FetchResult, sdkstep.Step) {
h.t.Helper()
if _, err := fetchTool(h.t, h.m)(fetchArgs); err != nil {
h.t.Fatal(err)
}
st := states.waitEnd(h.t)
var res FetchResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
json.Unmarshal(st.Result, &res)
}
return res, st
@@ -101,7 +101,7 @@ const pullList = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\trefs/pull/6/head\n"
func TestAPinOnAPullHeadIsPlacedBesideAndHandedToGoByAModfile(t *testing.T) {
h, states := newPinHarness(t, pinnedGoMod, false, pullList)
res, st := h.fetch(states)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
placed := filepath.Join(h.m.Work, res.Workspace, "pinned", "mesh-sdk@"+commitSHA, "beside", "mesh-sdk")
@@ -144,7 +144,7 @@ func TestAPinGoCanDownloadIsLeftAsToday(t *testing.T) {
h, states := newPinHarness(t, pinnedGoMod, false, pullList)
h.onTrunk = true
res, st := h.fetch(states)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if h.ran("ls-remote") || h.ran("pinned") {
@@ -165,7 +165,7 @@ func TestAPinGoCanDownloadIsLeftAsToday(t *testing.T) {
func TestAPinOnNoRefFailsNamingTheModuleAndTheCommit(t *testing.T) {
h, states := newPinHarness(t, pinnedGoMod, false, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\trefs/pull/6/head\n")
_, st := h.fetch(states)
if st.State != delivery.StepFailed {
if st.State != sdkstep.StepFailed {
t.Fatalf("the step ended %s, want failed", st.State)
}
if !strings.Contains(st.Reason, "no pull request head") || !strings.Contains(st.Reason, "unknown revision") ||
@@ -181,7 +181,7 @@ func TestAPinOnNoRefFailsNamingTheModuleAndTheCommit(t *testing.T) {
func TestAVendoringDirectoryResolvesNothing(t *testing.T) {
h, states := newPinHarness(t, pinnedGoMod, true, pullList)
_, st := h.fetch(states)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if h.ran("go -C") || h.ran("ls-remote") || h.ran("pinned") {
@@ -195,7 +195,7 @@ func TestTwoDirectoriesPinningOneHeadShareOnePlacement(t *testing.T) {
h, states := newPinHarness(t, pinnedGoMod, false, pullList)
h.f.gomods = "go.mod\nsub/go.mod\n"
res, st := h.fetch(states)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
checkouts := 0
@@ -240,7 +240,7 @@ func TestTwoCommitsOfOneRepositoryGetTwoPlacements(t *testing.T) {
return base(ctx, env, name, args...)
}
_, st := h.fetch(states)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
for _, c := range []string{commitSHA, other} {
@@ -15,7 +15,7 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
func git(t *testing.T, dir string, args ...string) string {
@@ -86,7 +86,7 @@ func (f *forge) module(t *testing.T) (*Module, *keptStates) {
}
// waitLong is waitEnd for a step that runs the real go, whose first build in a cold cache takes longer than 3 s.
func waitLong(t *testing.T, s *keptStates) delivery.Step {
func waitLong(t *testing.T, s *keptStates) sdkstep.Step {
t.Helper()
select {
case key := <-s.ended:
@@ -96,18 +96,18 @@ func waitLong(t *testing.T, s *keptStates) delivery.Step {
return v[len(v)-1]
case <-time.After(2 * time.Minute):
t.Fatal("the step never ended on its key")
return delivery.Step{}
return sdkstep.Step{}
}
}
func fetched(t *testing.T, m *Module, states *keptStates, args map[string]any) (FetchResult, delivery.Step) {
func fetched(t *testing.T, m *Module, states *keptStates, args map[string]any) (FetchResult, sdkstep.Step) {
t.Helper()
if _, err := fetchTool(t, m)(args); err != nil {
t.Fatal(err)
}
st := states.waitEnd(t)
var res FetchResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatal(err)
}
@@ -136,7 +136,7 @@ func TestRealFetchMergesConflictsAndPlainlyFetches(t *testing.T) {
m, states := f.module(t)
res, st := fetched(t, m, states, map[string]any{"repository": "o/r", "commit": clean, "ref": "refs/pull/1/head", "base": trunk})
if st.State != delivery.StepPassed || res.Merge == "" || res.Base != trunk || res.Commit != clean || len(res.Conflicts) != 0 {
if st.State != sdkstep.StepPassed || res.Merge == "" || res.Base != trunk || res.Commit != clean || len(res.Conflicts) != 0 {
t.Fatalf("clean: %s %s", st.State, st.Result)
}
if strings.Join(res.Changed, ",") != "modules/m/m.txt" {
@@ -154,7 +154,7 @@ func TestRealFetchMergesConflictsAndPlainlyFetches(t *testing.T) {
}
res, st = fetched(t, m, states, map[string]any{"repository": "o/r", "commit": clash, "ref": "refs/pull/2/head", "base": trunk})
if st.State != delivery.StepPassed || res.Merge != "" || strings.Join(res.Conflicts, ",") != "a.txt" {
if st.State != sdkstep.StepPassed || res.Merge != "" || strings.Join(res.Conflicts, ",") != "a.txt" {
t.Fatalf("conflict: %s %s", st.State, st.Result)
}
if got := git(t, filepath.Join(m.Work, res.Workspace, "tree"), "rev-parse", "HEAD"); got != trunk {
@@ -162,7 +162,7 @@ func TestRealFetchMergesConflictsAndPlainlyFetches(t *testing.T) {
}
res, st = fetched(t, m, states, map[string]any{"repository": "o/r", "commit": clean, "ref": "refs/pull/1/head"})
if st.State != delivery.StepPassed || res.Merge != "" || res.Base != "" || res.Trees != nil || len(res.Modules) != 1 {
if st.State != sdkstep.StepPassed || res.Merge != "" || res.Base != "" || res.Trees != nil || len(res.Modules) != 1 {
t.Errorf("no base: %s %s", st.State, st.Result)
}
}
@@ -192,13 +192,13 @@ func TestRealTestsReadTheSiblingBesideTheTree(t *testing.T) {
var res TestResult
st := waitLong(t, states)
json.Unmarshal(st.Result, &res)
if st.State != delivery.StepPassed || res.Passed || len(res.Failed) != 1 || !strings.Contains(res.Failed[0].Output, "MESH_CHECK_BESIDE is not set") {
if st.State != sdkstep.StepPassed || res.Passed || len(res.Failed) != 1 || !strings.Contains(res.Failed[0].Output, "MESH_CHECK_BESIDE is not set") {
t.Fatalf("without the sibling: %s %s", st.State, st.Result)
}
with, st2 := fetched(t, m, states, map[string]any{"repository": "o/r", "commit": head,
"beside": []any{map[string]any{"repository": "o/sibling", "commit": sibling}}})
if st2.State != delivery.StepPassed || len(with.Beside) != 1 {
if st2.State != sdkstep.StepPassed || len(with.Beside) != 1 {
t.Fatalf("with the sibling: %s %s", st2.State, st2.Result)
}
m.testToolRun(t, with.Workspace)
@@ -223,7 +223,7 @@ func TestRealTestsReadTheSiblingBesideTheTree(t *testing.T) {
git(t, work, "push", "-q", "-f", filepath.Join(f.root, "forge", "o/linked.git"), "HEAD:refs/heads/main")
_, st3 := fetched(t, m, states, map[string]any{"repository": "o/r", "commit": head,
"beside": []any{map[string]any{"repository": "o/linked", "commit": bad}}})
if st3.State != delivery.StepFailed || !strings.Contains(st3.Reason, "etc-link") {
if st3.State != sdkstep.StepFailed || !strings.Contains(st3.Reason, "etc-link") {
t.Errorf("a sibling with a symlink: %s %s", st3.State, st3.Reason)
}
}
@@ -10,7 +10,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -69,7 +69,7 @@ func TestFetchWithABaseMergesAndAnswersThePreview(t *testing.T) {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
var res FetchResult
@@ -102,7 +102,7 @@ func TestFetchWithAConflictEndsPassedAndNamesTheFiles(t *testing.T) {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("a conflict ended the step %s: %s", st.State, st.Reason)
}
var res FetchResult
@@ -137,7 +137,7 @@ func TestABaseThatIsNotACommitIsRefusedBeforeAnythingRuns(t *testing.T) {
f := &fakeStep{}
m, states := fetchModule(t, f)
fetchTool(t, m)(map[string]any{"repository": "novox/mesh-sdk", "commit": commitSHA, "base": "main"})
if st := states.waitEnd(t); st.State != delivery.StepFailed || len(f.lines()) != 0 {
if st := states.waitEnd(t); st.State != sdkstep.StepFailed || len(f.lines()) != 0 {
t.Errorf("the step ended %s after %d commands", st.State, len(f.lines()))
}
}
@@ -156,7 +156,7 @@ func TestSiblingsArePlacedBesideAndTheUnitLevelIsGivenTheirDirectory(t *testing.
st := states.waitEnd(t)
var res FetchResult
json.Unmarshal(st.Result, &res)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if len(res.Beside) != 2 || res.Beside[0].Dir != "mesh-catalog" || res.Beside[1].Dir != "mesh-host" {
@@ -233,7 +233,7 @@ func TestSiblingsThatAreNotRepositoriesOrCommitsAreRefused(t *testing.T) {
f := &fakeStep{}
m, states := fetchModule(t, f)
fetchTool(t, m)(map[string]any{"repository": "novox/mesh-sdk", "commit": commitSHA, "beside": beside})
if st := states.waitEnd(t); st.State != delivery.StepFailed || len(f.lines()) != 0 {
if st := states.waitEnd(t); st.State != sdkstep.StepFailed || len(f.lines()) != 0 {
t.Errorf("%s: the step ended %s after %d commands", name, st.State, len(f.lines()))
}
}
@@ -285,7 +285,7 @@ func TestTheIntegrationLevelIsGivenTheBesideDirectoryOnlyWhenSiblingsWerePlaced(
t.Fatal(err)
}
}
if _, st := runLevelTool(t, m, states, "integration"); st.State != delivery.StepPassed {
if _, st := runLevelTool(t, m, states, "integration"); st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
tested := 0
@@ -15,7 +15,8 @@ import (
"testing"
registry "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/delivery"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// pushed are the blobs the fake artifact store took, by digest, for the tests that read an archive back.
@@ -105,7 +106,7 @@ func TestADoneBuildAnswersTheReferenceOfTheArchiveItPushed(t *testing.T) {
m, states, _ := buildModule(t, manifestWith(goBundle), "the program")
store := withStore(t, m)
res, st := runBuild(t, m, states, "tools")
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if want := "oci-artifact-registry/demo/tools@" + res.Digest; res.Reference != want {
@@ -117,7 +118,7 @@ func TestADoneBuildAnswersTheReferenceOfTheArchiveItPushed(t *testing.T) {
a, states2, _ := archiveModule(t, "")
withStore(t, a)
arch, st := runBuild(t, a, states2, "files")
if st.State != delivery.StepPassed || arch.Reference != "oci-artifact-registry/demo/files@"+arch.Digest {
if st.State != sdkstep.StepPassed || arch.Reference != "oci-artifact-registry/demo/files@"+arch.Digest {
t.Errorf("the archive artifact: %s %q, reference %q", st.State, st.Reason, arch.Reference)
}
}
@@ -128,13 +129,13 @@ func TestARefusedPushFailsTheStepNotStored(t *testing.T) {
m, states, _ := buildModule(t, manifestWith(goBundle), "the program")
store := withStore(t, m)
store.fail = errors.New("refused: the forge did not take it: disk full")
if _, st := runBuild(t, m, states, "tools"); st.State != delivery.StepFailed || !strings.HasPrefix(st.Reason, delivery.NotStored+"tools: ") || !strings.Contains(st.Reason, "disk full") {
if _, st := runBuild(t, m, states, "tools"); st.State != sdkstep.StepFailed || !strings.HasPrefix(st.Reason, builds.NotStored+"tools: ") || !strings.Contains(st.Reason, "disk full") {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
m.Call = func(string, any) (json.RawMessage, error) {
return nil, errors.New("nats: no responders available for request")
}
if _, st := runBuild(t, m, states, "tools"); st.State != delivery.StepFailed || !strings.HasPrefix(st.Reason, delivery.NotStored+"tools: ") ||
if _, st := runBuild(t, m, states, "tools"); st.State != sdkstep.StepFailed || !strings.HasPrefix(st.Reason, builds.NotStored+"tools: ") ||
!strings.Contains(st.Reason, "oci-artifact-registry") {
t.Errorf("no holder: the step ended %s: %q", st.State, st.Reason)
}
@@ -178,7 +179,7 @@ func TestAGoPackageIsBuiltAsAModuleZipAndPublishedUnderItsModulePath(t *testing.
t.Fatal(err)
}
}
run := func(args map[string]any) (BuildResult, delivery.Step) {
run := func(args map[string]any) (BuildResult, sdkstep.Step) {
t.Helper()
for _, tool := range m.Tools(m.StepTools()) {
if tool.Name == "build_go_build" {
@@ -189,7 +190,7 @@ func TestAGoPackageIsBuiltAsAModuleZipAndPublishedUnderItsModulePath(t *testing.
}
st := states.waitEnd(t)
var res BuildResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
_ = json.Unmarshal(st.Result, &res)
}
return res, st
@@ -197,12 +198,12 @@ func TestAGoPackageIsBuiltAsAModuleZipAndPublishedUnderItsModulePath(t *testing.
store := withStore(t, m)
args := map[string]any{"workspace": "ws1", "module": "modules/demo", "artifact": "go"}
cand, st := run(args)
if st.State != delivery.StepPassed || !strings.HasPrefix(cand.Digest, "sha256:") || cand.Reference != "" || len(store.asked) != 0 {
if st.State != sdkstep.StepPassed || !strings.HasPrefix(cand.Digest, "sha256:") || cand.Reference != "" || len(store.asked) != 0 {
t.Fatalf("a candidate's Go package: %s %q, %+v, asked %v", st.State, st.Reason, cand, store.asked)
}
args["version"], args["seat"] = "v0.1.15", GoPackageSeat
res, st := run(args)
if st.State != delivery.StepPassed || res.Reference != "git.example/demo/go@v0.1.15" {
if st.State != sdkstep.StepPassed || res.Reference != "git.example/demo/go@v0.1.15" {
t.Fatalf("a trunk Go package: %s %q, %+v", st.State, st.Reason, res)
}
if len(store.published) != 1 {
@@ -226,7 +227,7 @@ func TestAGoPackageIsBuiltAsAModuleZipAndPublishedUnderItsModulePath(t *testing.
if err := os.Remove(filepath.Join(src, "go.mod")); err != nil {
t.Fatal(err)
}
if _, st := run(args); st.State != delivery.StepFailed || !strings.Contains(st.Reason, "go.mod") {
if _, st := run(args); st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "go.mod") {
t.Errorf("no go.mod: %s %q", st.State, st.Reason)
}
}
@@ -268,7 +269,7 @@ func TestEachGoPackageOfAModuleInAFolderIsPublishedUnderItsOwnModulePath(t *test
}
st := states.waitEnd(t)
var res BuildResult
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("%s: %s %q", c.artifact, st.State, st.Reason)
}
_ = json.Unmarshal(st.Result, &res)
@@ -298,7 +299,7 @@ func TestTheNumberedEventPublishesAHeldDigestUnderItsVersion(t *testing.T) {
digest := "sha256:" + strings.Repeat("ab", 32)
other := "sha256:" + strings.Repeat("cd", 32)
numbered := func(kind, seat, digest string) []byte {
raw, err := delivery.EncodeVersionNumbered(delivery.VersionNumbered{Name: "mod/tools", Kind: kind, Version: "0.1.4",
raw, err := builds.EncodeVersionNumbered(builds.VersionNumbered{Name: "mod/tools", Kind: kind, Version: "0.1.4",
Module: "mod", Fingerprint: "fp", Commit: "c0", Digest: digest, Seat: seat})
if err != nil {
t.Fatal(err)
+15 -14
View File
@@ -14,8 +14,9 @@ import (
"sync"
"time"
buildstep "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The read tools, named as the module is: build_go_ and the verb.
@@ -98,26 +99,26 @@ func (m *Module) stepKey(id string) (string, error) {
}
id = rest
}
return delivery.StepKey(m.Node, id)
return sdkstep.StepKey(m.Node, id)
}
// ReadStep is the stored value of this node's step id (state, reason, result).
func (m *Module) ReadStep(id string) (delivery.Step, error) {
func (m *Module) ReadStep(id string) (sdkstep.Step, error) {
key, err := m.stepKey(id)
if err != nil {
return delivery.Step{}, err
return sdkstep.Step{}, err
}
if m.ReadSteps.Get == nil {
return delivery.Step{}, fmt.Errorf("the steps state cannot be read here")
return sdkstep.Step{}, fmt.Errorf("the steps state cannot be read here")
}
raw, err := m.ReadSteps.Get(key)
if err != nil {
return delivery.Step{}, err
return sdkstep.Step{}, err
}
if len(raw) == 0 {
return delivery.Step{}, fmt.Errorf("no step %s in the steps state", key)
return sdkstep.Step{}, fmt.Errorf("no step %s in the steps state", key)
}
return delivery.DecodeStep(raw)
return sdkstep.DecodeStep(raw)
}
// StepLine is a step without its result: the list answers these.
@@ -150,7 +151,7 @@ func (m *Module) RecentSteps(limit int) (lines []StepLine, cut bool, err error)
own := keys[:0:0]
for _, k := range keys {
if strings.HasPrefix(k, m.Node+".") {
if node, _, err := delivery.ParseStepKey(k); err == nil && node == m.Node {
if node, _, err := sdkstep.ParseStepKey(k); err == nil && node == m.Node {
own = append(own, k)
}
}
@@ -164,12 +165,12 @@ func (m *Module) RecentSteps(limit int) (lines []StepLine, cut bool, err error)
if err != nil {
return nil, cut, err
}
var steps []delivery.Step
var steps []sdkstep.Step
for _, raw := range raws {
if len(raw) == 0 {
continue
}
s, err := delivery.DecodeStep(raw)
s, err := sdkstep.DecodeStep(raw)
if err != nil {
continue // a value this module cannot read is not one of its own steps
}
@@ -191,7 +192,7 @@ func (m *Module) RecentSteps(limit int) (lines []StepLine, cut bool, err error)
}
// ReadToolchains is every key of the toolchains state (<node>.<language>) with its stored value.
func (m *Module) ReadToolchains() (map[string]delivery.Toolchain, error) {
func (m *Module) ReadToolchains() (map[string]buildstep.Toolchain, error) {
if m.ReadToolchainsState.Keys == nil || m.ReadToolchainsState.Get == nil {
return nil, fmt.Errorf("the toolchains state cannot be read here")
}
@@ -209,12 +210,12 @@ func (m *Module) ReadToolchains() (map[string]delivery.Toolchain, error) {
if err != nil {
return nil, err
}
out := map[string]delivery.Toolchain{}
out := map[string]buildstep.Toolchain{}
for i, raw := range raws {
if len(raw) == 0 {
continue
}
t, err := delivery.DecodeToolchain(raw)
t, err := buildstep.DecodeToolchain(raw)
if err != nil {
return nil, fmt.Errorf("%s: %w", keys[i], err)
}
@@ -10,7 +10,8 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
buildstep "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// fakeState is a state as stored: key to raw value.
@@ -29,13 +30,13 @@ func (f fakeState) read() StateRead {
}
}
func putStep(t *testing.T, f fakeState, s delivery.Step) {
func putStep(t *testing.T, f fakeState, s sdkstep.Step) {
t.Helper()
b, err := delivery.EncodeStep(s)
b, err := sdkstep.EncodeStep(s)
if err != nil {
t.Fatal(err)
}
key, _ := delivery.StepKey(s.Node, s.ID)
key, _ := sdkstep.StepKey(s.Node, s.ID)
f[key] = b
}
@@ -60,22 +61,22 @@ func tool(t *testing.T, m *Module, name string) func(map[string]any) (any, error
func TestStepToolAnswersTheStoredStep(t *testing.T) {
steps := fakeState{}
t0 := time.Date(2026, 10, 21, 9, 0, 0, 0, time.UTC)
putStep(t, steps, delivery.Step{ID: "abc1", Tool: "build_go_fetch", Node: "ace", State: delivery.StepPassed, Started: t0, Ended: t0.Add(time.Second),
putStep(t, steps, sdkstep.Step{ID: "abc1", Tool: "build_go_fetch", Node: "ace", State: sdkstep.StepPassed, Started: t0, Ended: t0.Add(time.Second),
Result: json.RawMessage(`{"workspace":"w42"}`)})
putStep(t, steps, delivery.Step{ID: "abc2", Tool: "build_go_test", Node: "ace", State: delivery.StepRefused, Started: t0, Reason: "go cannot be run"})
putStep(t, steps, sdkstep.Step{ID: "abc2", Tool: "build_go_test", Node: "ace", State: sdkstep.StepRefused, Started: t0, Reason: "go cannot be run"})
m := readModule(steps, fakeState{})
for _, id := range []string{"abc1", "ace.abc1"} {
out, err := tool(t, m, StepReadTool)(map[string]any{"id": id})
if err != nil {
t.Fatal(err)
}
st := out.(delivery.Step)
if st.State != delivery.StepPassed || string(st.Result) != `{"workspace":"w42"}` || st.Tool != "build_go_fetch" {
st := out.(sdkstep.Step)
if st.State != sdkstep.StepPassed || string(st.Result) != `{"workspace":"w42"}` || st.Tool != "build_go_fetch" {
t.Errorf("id %s: step = %+v", id, st)
}
}
out, _ := tool(t, m, StepReadTool)(map[string]any{"id": "abc2"})
if st := out.(delivery.Step); st.State != delivery.StepRefused || st.Reason != "go cannot be run" {
if st := out.(sdkstep.Step); st.State != sdkstep.StepRefused || st.Reason != "go cannot be run" {
t.Errorf("refused step = %+v", st)
}
}
@@ -94,10 +95,10 @@ func TestStepToolRefusesAnUnknownOrForeignOrBadID(t *testing.T) {
func TestStepsToolListsRecentStepsOfThisNode(t *testing.T) {
steps := fakeState{}
t0 := time.Date(2026, 10, 21, 9, 0, 0, 0, time.UTC)
putStep(t, steps, delivery.Step{ID: "a", Tool: "build_go_fetch", Node: "ace", State: delivery.StepPassed, Started: t0, Ended: t0, Result: json.RawMessage(`1`)})
putStep(t, steps, delivery.Step{ID: "b", Tool: "build_go_test", Node: "ace", State: delivery.StepRunning, Started: t0.Add(time.Minute)})
putStep(t, steps, delivery.Step{ID: "c", Tool: "build_go_build", Node: "ace", State: delivery.StepFailed, Started: t0.Add(2 * time.Minute), Reason: "boom", What: "check ask, novox/x@abc"})
putStep(t, steps, delivery.Step{ID: "d", Tool: "build_go_build", Node: "g14", State: delivery.StepPassed, Started: t0.Add(time.Hour)})
putStep(t, steps, sdkstep.Step{ID: "a", Tool: "build_go_fetch", Node: "ace", State: sdkstep.StepPassed, Started: t0, Ended: t0, Result: json.RawMessage(`1`)})
putStep(t, steps, sdkstep.Step{ID: "b", Tool: "build_go_test", Node: "ace", State: sdkstep.StepRunning, Started: t0.Add(time.Minute)})
putStep(t, steps, sdkstep.Step{ID: "c", Tool: "build_go_build", Node: "ace", State: sdkstep.StepFailed, Started: t0.Add(2 * time.Minute), Reason: "boom", What: "check ask, novox/x@abc"})
putStep(t, steps, sdkstep.Step{ID: "d", Tool: "build_go_build", Node: "g14", State: sdkstep.StepPassed, Started: t0.Add(time.Hour)})
m := readModule(steps, fakeState{})
out, err := tool(t, m, StepsReadTool)(map[string]any{})
if err != nil {
@@ -111,7 +112,7 @@ func TestStepsToolListsRecentStepsOfThisNode(t *testing.T) {
if !slices.Equal(ids, []string{"c", "b", "a"}) {
t.Errorf("ids = %v, want c b a", ids)
}
if lines[0].Reason != "boom" || lines[0].What != "check ask, novox/x@abc" || lines[0].State != delivery.StepFailed {
if lines[0].Reason != "boom" || lines[0].What != "check ask, novox/x@abc" || lines[0].State != sdkstep.StepFailed {
t.Errorf("first = %+v", lines[0])
}
out, _ = tool(t, m, StepsReadTool)(map[string]any{"limit": float64(1)})
@@ -123,7 +124,7 @@ func TestStepsToolListsRecentStepsOfThisNode(t *testing.T) {
// 1562 (1483/AC5): build_go_toolchains answers each toolchains key as stored.
func TestToolchainsToolAnswersTheStoredKeys(t *testing.T) {
tcs := fakeState{}
b, err := delivery.EncodeToolchain(delivery.Toolchain{Language: "go", Version: "go1.27.1", Recipe: "1", At: time.Date(2026, 10, 21, 9, 0, 0, 0, time.UTC)})
b, err := buildstep.EncodeToolchain(buildstep.Toolchain{Language: "go", Version: "go1.27.1", Recipe: "1", At: time.Date(2026, 10, 21, 9, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
@@ -133,7 +134,7 @@ func TestToolchainsToolAnswersTheStoredKeys(t *testing.T) {
if err != nil {
t.Fatal(err)
}
got := out.(map[string]any)["toolchains"].(map[string]delivery.Toolchain)
got := out.(map[string]any)["toolchains"].(map[string]buildstep.Toolchain)
if got["ace.go"].Version != "go1.27.1" || got["ace.go"].Recipe != "1" {
t.Errorf("toolchains = %+v", got)
}
@@ -166,7 +167,7 @@ func TestRecentStepsReadsOnlyTheNodesKeysInParallelAndBounded(t *testing.T) {
stored := map[string][]byte{}
for _, n := range []string{"ace", "g14", "novox", "shanks"} {
for i := 0; i < row.perNode; i++ {
b, _ := delivery.EncodeStep(delivery.Step{ID: fmt.Sprintf("s%04d", i), Tool: "t", Node: n, State: delivery.StepPassed, Started: t0.Add(time.Duration(i) * time.Second)})
b, _ := sdkstep.EncodeStep(sdkstep.Step{ID: fmt.Sprintf("s%04d", i), Tool: "t", Node: n, State: sdkstep.StepPassed, Started: t0.Add(time.Duration(i) * time.Second)})
stored[n+".s"+fmt.Sprintf("%04d", i)] = b
}
}
@@ -9,7 +9,7 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/health"
)
@@ -44,7 +44,7 @@ func TestUnderTheFloorEveryStepIsRefusedAndTheModuleIsUnhealthy(t *testing.T) {
if _, err := tool.Run(map[string]any{}); err != nil {
t.Fatal(err)
}
if st := states.waitEnd(t); st.State != delivery.StepRefused || !strings.HasPrefix(st.Reason, underTheFloor) {
if st := states.waitEnd(t); st.State != sdkstep.StepRefused || !strings.HasPrefix(st.Reason, underTheFloor) {
t.Errorf("the long step ended %s: %q, want refused: %q", st.State, st.Reason, underTheFloor)
}
case "short_probe":
@@ -95,7 +95,7 @@ func TestAFailedFetchLeavesNoWorkspace(t *testing.T) {
if _, err := fetchTool(t, m)(fetchArgs); err != nil {
t.Fatal(err)
}
if st := states.waitEnd(t); st.State != delivery.StepFailed {
if st := states.waitEnd(t); st.State != sdkstep.StepFailed {
t.Fatalf("the step ended %s, want failed", st.State)
}
if entries, _ := os.ReadDir(m.Work); len(entries) != 0 {
@@ -10,7 +10,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// threeModules gives the workspace three go.mod directories, a, b and c, whose modules are fixture/a, fixture/b and
@@ -31,7 +31,7 @@ func threeModules(t *testing.T, f *integFake) (*Module, *keptStates) {
return m, states
}
func runIntegrationOf(t *testing.T, m *Module, states *keptStates, packages ...any) (TestResult, delivery.Step) {
func runIntegrationOf(t *testing.T, m *Module, states *keptStates, packages ...any) (TestResult, sdkstep.Step) {
t.Helper()
for _, tool := range m.Tools(m.StepTools()) {
if tool.Name != "build_go_test" {
@@ -47,7 +47,7 @@ func runIntegrationOf(t *testing.T, m *Module, states *keptStates, packages ...a
}
st := states.waitEnd(t)
var res TestResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatal(err)
}
@@ -60,7 +60,7 @@ func runIntegrationOf(t *testing.T, m *Module, states *keptStates, packages ...a
func TestOnlyTheSelectedDirectoryCompilesAndRuns(t *testing.T) {
f := newIntegFake(t, "slow")
m, states := threeModules(t, f)
if _, st := runIntegrationOf(t, m, states, "fixture/b/x"); st.State != delivery.StepPassed {
if _, st := runIntegrationOf(t, m, states, "fixture/b/x"); st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
tree := filepath.Join(m.Work, "ws1", "tree")
@@ -131,7 +131,7 @@ func TestSelectedDirectoriesRunAtOnceAndAnswerInDirectoryOrder(t *testing.T) {
m, states := threeModules(t, f)
m.Parallel = 3
res, st := runIntegrationOf(t, m, states, "fixture/c/x", "fixture/a/x")
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if most != 2 {
@@ -24,8 +24,8 @@ import (
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
registry "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// realStore starts the artifact store's own program (the distribution registry, modules/distribution) inside this
@@ -96,7 +96,7 @@ func uploadBlob(base, name, digest string, body []byte) error {
// resolve reads a done artifact's reference from the store as a node-engine does (catalogue.Routed): the seat's form
// oci-artifact-registry/<module>/<artifact>@<digest> is <store>/v2/<module>/<artifact>/blobs/<digest> (ADR 0605 §9).
func resolve(t *testing.T, addr string, a delivery.BuiltArtifact) []byte {
func resolve(t *testing.T, addr string, a builds.BuiltArtifact) []byte {
t.Helper()
if _, _, ok := catalogue.InRegistrySeat(a.Reference); !ok {
t.Fatalf("%s's reference %q is not in the form a registry seat answers", a.Name, a.Reference)
@@ -136,16 +136,16 @@ func TestADoneBuildsArtifactsResolveInARealArtifactStore(t *testing.T) {
m := &Module{Home: home, Work: work, Cache: t.TempDir()}
m.Run = StepRunner(home)
addr := realStore(t, m)
done := delivery.BuilderBuild{V: 1, State: delivery.BuildDone, Manifest: strings.Repeat("0", 64)}
done := builds.BuilderBuild{V: 1, State: builds.BuildDone, Manifest: strings.Repeat("0", 64)}
for _, a := range []struct{ name, kind string }{{"tools", "bundle"}, {"files", "archive"}} {
out, err := m.build(context.Background(), map[string]any{"workspace": "ws1", "module": ".", "artifact": a.name})
if err != nil {
t.Fatalf("%s: %v", a.name, err)
}
r := out.(BuildResult)
done.Artifacts = append(done.Artifacts, delivery.BuiltArtifact{Name: a.name, Kind: a.kind, Digest: r.Digest, Reference: r.Reference})
done.Artifacts = append(done.Artifacts, builds.BuiltArtifact{Name: a.name, Kind: a.kind, Digest: r.Digest, Reference: r.Reference})
}
if _, err := delivery.EncodeBuilderBuild(done); err != nil {
if _, err := builds.EncodeBuilderBuild(done); err != nil {
t.Errorf("the done key the builder would write is refused: %v", err)
}
if ok, missing := done.Referenced(); !ok {
@@ -17,7 +17,6 @@ import (
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -41,8 +40,8 @@ func (m *Module) testTool() StepTool {
"packages": map[string]any{"type": "array", "items": map[string]any{"type": "string"},
"description": "package patterns of each module, ./... when none"},
"run": map[string]any{"type": "string", "description": "optional go test -run pattern: only the tests it matches run (a try's tests)"},
delivery.NodeNamesArg: map[string]any{"type": "array", "items": map[string]any{"type": "string"},
"description": "optional, the names of the mesh's nodes: the tests get them comma-separated in " + delivery.NodeNamesEnv + " (a check that keeps node names out of the repository reads them there)"},
sdkstep.NodeNamesArg: map[string]any{"type": "array", "items": map[string]any{"type": "string"},
"description": "optional, the names of the mesh's nodes: the tests get them comma-separated in " + sdkstep.NodeNamesEnv + " (a check that keeps node names out of the repository reads them there)"},
"runner": runnerProperty,
},
"required": []string{"workspace", "level"},
@@ -82,7 +81,7 @@ func (m *Module) test(ctx context.Context, args map[string]any) (any, error) {
if err != nil {
return nil, err
}
names, err := nodeNamesArg(args[delivery.NodeNamesArg])
names, err := nodeNamesArg(args[sdkstep.NodeNamesArg])
if err != nil {
return nil, err
}
@@ -173,11 +172,11 @@ func (m *Module) besideEnv(workspace string) []string {
const BuildNodeEnv = "MESH_BUILD_NODE"
// checkEnv is what every level hands the tests it runs: the build node mark, the siblings (besideEnv) and, when the
// ask carried them, the mesh's node names (delivery.NodeNamesEnv, novox/hq ADR 0567 §12).
// ask carried them, the mesh's node names (sdkstep.NodeNamesEnv, novox/hq ADR 0567 §12).
func (m *Module) checkEnv(workspace string, names []string) []string {
env := append([]string{BuildNodeEnv + "=1"}, m.besideEnv(workspace)...)
if len(names) > 0 {
env = append(env, delivery.NodeNamesEnv+"="+strings.Join(names, ","))
env = append(env, sdkstep.NodeNamesEnv+"="+strings.Join(names, ","))
}
return env
}
@@ -194,13 +193,13 @@ func nodeNamesArg(raw any) ([]string, error) {
for _, e := range v {
n, ok := e.(string)
if !ok || n == "" || strings.ContainsAny(n, ", \n") {
return nil, fmt.Errorf("%s is a list of node names, not %v", delivery.NodeNamesArg, e)
return nil, fmt.Errorf("%s is a list of node names, not %v", sdkstep.NodeNamesArg, e)
}
out = append(out, n)
}
return out, nil
}
return nil, fmt.Errorf("%s is a list of node names", delivery.NodeNamesArg)
return nil, fmt.Errorf("%s is a list of node names", sdkstep.NodeNamesArg)
}
// packagesArg reads the packages argument: a list of package patterns, as []any when it came over the bus as JSON and
@@ -9,7 +9,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -53,7 +53,7 @@ func workspaceFor(t *testing.T, captured string) (*Module, *keptStates, string,
return m, states, "ws1", calls
}
func runUnit(t *testing.T, m *Module, states *keptStates, ws string) (TestResult, delivery.Step) {
func runUnit(t *testing.T, m *Module, states *keptStates, ws string) (TestResult, sdkstep.Step) {
t.Helper()
var run func(map[string]any) (any, error)
for _, tool := range m.Tools(m.StepTools()) {
@@ -66,7 +66,7 @@ func runUnit(t *testing.T, m *Module, states *keptStates, ws string) (TestResult
}
st := states.waitEnd(t)
var res TestResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatalf("result %s: %v", st.Result, err)
}
@@ -79,7 +79,7 @@ func runUnit(t *testing.T, m *Module, states *keptStates, ws string) (TestResult
func TestUnitLevelNamesTheFailingTestAndFailsTheLevel(t *testing.T) {
m, states, ws, _ := workspaceFor(t, "failing")
res, st := runUnit(t, m, states, ws)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
if res.Passed || res.Level != "unit" {
@@ -109,7 +109,7 @@ func TestUnitLevelNamesTheFailingTestAndFailsTheLevel(t *testing.T) {
func TestUnitLevelNamesEveryFailingTestOfEveryPackage(t *testing.T) {
m, states, ws, calls := workspaceFor(t, "twofailing")
res, st := runUnit(t, m, states, ws)
if st.State != delivery.StepPassed || res.Passed {
if st.State != sdkstep.StepPassed || res.Passed {
t.Fatalf("step %s (%s), result %+v: want the step done and the level failed", st.State, st.Reason, res)
}
var got []string
@@ -173,7 +173,7 @@ func TestUnitLevelOfAFixtureWithOnlyAnIntegrationTestHasNoTests(t *testing.T) {
func TestUnitLevelOfAFixtureWithNoTestsSaysSo(t *testing.T) {
m, states, ws, _ := workspaceFor(t, "empty")
res, st := runUnit(t, m, states, ws)
if st.State != delivery.StepPassed || !res.Passed || !res.NoTests || res.Why != "the repository has no tests at the unit level" || len(res.Failed) != 0 {
if st.State != sdkstep.StepPassed || !res.Passed || !res.NoTests || res.Why != "the repository has no tests at the unit level" || len(res.Failed) != 0 {
t.Errorf("result = %+v (%s)", res, st.State)
}
}
@@ -206,7 +206,7 @@ func TestUnitLevelOfAContractsModuleOutOfStepFailsNamingEachPath(t *testing.T) {
t.Errorf("%s is not under the Go root", p)
}
}
if st.State != delivery.StepPassed || res.Passed || !stray || !strings.Contains(res.Why, "go/stray.go") {
if st.State != sdkstep.StepPassed || res.Passed || !stray || !strings.Contains(res.Why, "go/stray.go") {
t.Errorf("result = %+v (%s %s)", res, st.State, st.Reason)
}
}
@@ -240,7 +240,7 @@ func TestUnitLevelOfAContractsModuleInAFolderOutOfStepFailsNamingEachPath(t *tes
t.Errorf("%s is not under the folder's Go root", p)
}
}
if st.State != delivery.StepPassed || res.Passed || !stray || !strings.Contains(res.Why, "domain/go/stray.go") {
if st.State != sdkstep.StepPassed || res.Passed || !stray || !strings.Contains(res.Why, "domain/go/stray.go") {
t.Errorf("result = %+v (%s %s)", res, st.State, st.Reason)
}
}
@@ -291,7 +291,7 @@ func TestAGoTestThatCouldNotRunEndsTheStepFailed(t *testing.T) {
return forgekey.Ran{Stdout: "go.mod\n"}
}, &[]shutOff{})
_, st := runUnit(t, m, states, ws)
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "go: cannot find main module") {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, "go: cannot find main module") {
t.Errorf("the step ended %s: %q", st.State, st.Reason)
}
}
@@ -312,7 +312,7 @@ func TestATestOfAnUnknownWorkspaceOrAnotherLevelIsRefused(t *testing.T) {
{"workspace": "ws1", "level": "unit", "packages": []any{"../x"}},
} {
run(args)
if st := states.waitEnd(t); st.State != delivery.StepFailed {
if st := states.waitEnd(t); st.State != sdkstep.StepFailed {
t.Errorf("%v: the step ended %s", args, st.State)
}
}
@@ -2,7 +2,7 @@ package main
// The asked versions of the mesh's packages (novox/hq ADR 0573 §2, §4; issue 1585): every module builds against the latest
// published version of each mesh package it uses, and a delivery group's check against the group's own heads, whatever
// version its go.mod names. The builder and the checker name each version in the ask (delivery.StepAsk.Packages, a full
// version its go.mod names. The builder and the checker name each version in the ask (sdkstep.StepAsk.Packages, a full
// commit by package); before Go downloads anything, the fetch writes them into the temporary -modfile of each directory
// whose go.mod requires the package (pins.go), so the fetch's go commands, the build and the tests all take them:
//
@@ -8,7 +8,8 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -101,13 +102,13 @@ func TestAnAskedVersionIsWrittenIntoTheModfileWhateverGoModNames(t *testing.T) {
}
st := states.waitEnd(t)
if c.failsWith != "" {
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, c.failsWith) || strings.Contains(st.Reason, "no pull request head") ||
!strings.Contains(st.Reason, "may be retaken") || delivery.FailedUntilChanged(st.Reason) {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, c.failsWith) || strings.Contains(st.Reason, "no pull request head") ||
!strings.Contains(st.Reason, "may be retaken") || builds.FailedUntilChanged(st.Reason) {
t.Errorf("the download failure ended %s: %q", st.State, st.Reason)
}
return
}
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
var res FetchResult
@@ -9,7 +9,7 @@ import (
"path/filepath"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// releaseXzSHA256 is the sha256 of testdata/upstream-release.tar.xz: the tar stream of upstream-release.tar.gz,
@@ -45,7 +45,7 @@ func pinnedModuleServing(t *testing.T, fixture, sha, extra string) (*Module, *ke
func TestAPinnedXzArchiveIsUnpackedAsTheBuildAgentDoes(t *testing.T) {
m, states := pinnedModuleServing(t, "upstream-release.tar.xz", releaseXzSHA256, `,"within":"release/app"`)
res, st := runBuild(t, m, states, "files")
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("the step ended %s: %q", st.State, st.Reason)
}
sameAsBuildAgent(t, res, buildAgentWithinStream, buildAgentWithinDigest)
+5
View File
@@ -3,6 +3,11 @@ module meshbuildgo
go 1.26.0
require (
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
+1
View File
@@ -2,6 +2,7 @@
"module": "mesh-build-go",
"version": "1",
"packages": {
"builds": "0.1",
"sshkeys": "0.1"
},
"moved": true,
@@ -5,9 +5,10 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// **The module pulls its seat's asks** (novox/hq ADR 0525 §2-§6; issue 1887 A3image), as mesh-build-go does: the seat
@@ -36,7 +37,7 @@ func (m *Module) askSteps(steps []StepTool) buildstep.AskSteps {
// Puller is the module's pull of its seat's asks, on source (the runtime's seat pull when nil).
func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep.Puller {
if source == nil {
source = buildstep.SeatAsks{Language: delivery.AskImage}
source = buildstep.SeatAsks{Language: sdkstep.AskImage}
}
capacity := m.Capacity
// The capacity's own change wakes the puller (issue 1887): the runtime's watch of the config value as mesh-config
@@ -54,16 +55,16 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
capacity = buildstep.NodeCapacity(m.Settings, m.Node, m.Say)
settings = m.Settings
watch = buildstep.WatchConfigServed
cores = buildstep.NodeCoresPerStep(m.Settings, buildstep.DefaultCoresPerStep[delivery.AskImage], m.Say)
cores = buildstep.NodeCoresPerStep(m.Settings, buildstep.DefaultCoresPerStep[sdkstep.AskImage], m.Say)
}
ask := m.askSteps(steps)
var load func(string) ([]byte, error)
if m.ReadPauses.Get != nil {
load = func(key string) ([]byte, error) { return m.ReadPauses.Get(key) }
}
return &buildstep.Puller{Source: source, Language: delivery.AskImage, Node: m.Node, Put: m.Steps, PausePut: m.Pauses, Load: load,
return &buildstep.Puller{Source: source, Language: sdkstep.AskImage, Node: m.Node, Put: m.Steps, PausePut: m.Pauses, Load: load,
Capacity: capacity, SettingsFile: settings, WatchConfig: watch, CoresPerStep: cores, BuildHours: hours, Eligible: m.Eligible, Logf: m.Say,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
if err := m.Check(ctx); err != nil {
return nil, err
}
@@ -83,13 +84,13 @@ func (m *Module) SeatTools(p *buildstep.Puller) []stdio.Tool {
const StepEndTool = "build_image_step_end"
// endLeftRunning is the module's start sweep (issue 1890): it ends the steps of its node that a previous process left
// running (sdkstep.EndStaleAtStart; every step names its owner). main runs it once at the start, and the module's test
// running (buildrun.EndStaleAtStart; every step names its owner). main runs it once at the start, and the module's test
// runs it through the conformance kit's left-running case.
func (m *Module) endLeftRunning(ctx context.Context, scan sdkstep.Scan, booted time.Time, logf func(string, ...any)) {
sdkstep.EndStaleAtStart(ctx, scan, m.Node, booted, logf)
func (m *Module) endLeftRunning(ctx context.Context, scan buildrun.Scan, booted time.Time, logf func(string, ...any)) {
buildrun.EndStaleAtStart(ctx, scan, m.Node, booted, logf)
}
// stepScan is the module's steps state as buildstep reads it.
func (m *Module) stepScan() sdkstep.Scan {
return sdkstep.ScanOf(stdio.State(delivery.StepsState))
func (m *Module) stepScan() buildrun.Scan {
return buildrun.ScanOf(stdio.State(seat.StateSteps))
}
@@ -8,7 +8,6 @@ import (
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
const askCommit = "0123456789abcdef0123456789abcdef01234567"
@@ -32,23 +31,23 @@ func TestAPulledImageAskCopiesThenBuilds(t *testing.T) {
answer("build_image_copy", map[string]any{"digest": "sha256:dd", "reference": "registry/m/base@sha256:dd"}),
}
src := buildstep.NewFakeAsks()
a := delivery.StepAsk{ID: sdkstep.NewID(), Kind: delivery.AskDelivery, Language: delivery.AskImage, Asker: "mesh-delivery-builder",
At: time.Now(), Build: &delivery.BuildJob{Module: "m", Path: "modules/m", Publish: true,
Fetch: map[string]any{"repository": "novox/mesh-catalog", "commit": askCommit},
Artifacts: []delivery.AskArtifact{{Artifact: "base", From: "docker.io/library/alpine@sha256:00"}, {Artifact: "server"}}}}
a := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskDelivery, Language: sdkstep.AskImage, Asker: "mesh-delivery-builder",
At: time.Now(), Build: &sdkstep.BuildJob{Module: "m", Path: "modules/m", Publish: true,
Fetch: &sdkstep.Fetch{Repository: "novox/mesh-catalog", Commit: askCommit},
Artifacts: []sdkstep.AskArtifact{{Artifact: "base", From: "docker.io/library/alpine@sha256:00"}, {Artifact: "server"}}}}
_ = src.PublishAsk(a)
p := m.Puller(steps, src)
p.PullWait = 10 * time.Millisecond
ctx, stop := context.WithCancel(context.Background())
defer stop()
go p.Pull(ctx)
key, _ := delivery.AskStepKey("ace", a.ID)
var step delivery.Step
key, _ := sdkstep.AskStepKey("ace", a.ID)
var step sdkstep.Step
for deadline := time.Now().Add(3 * time.Second); ; time.Sleep(5 * time.Millisecond) {
states.mu.Lock()
v := states.steps[key]
states.mu.Unlock()
if len(v) > 0 && v[len(v)-1].State != delivery.StepRunning {
if len(v) > 0 && v[len(v)-1].State != sdkstep.StepRunning {
step = v[len(v)-1]
break
}
@@ -56,7 +55,7 @@ func TestAPulledImageAskCopiesThenBuilds(t *testing.T) {
t.Fatalf("the ask's step never ended: calls %v", calls)
}
}
if step.State != delivery.StepPassed || !strings.Contains(string(step.Result), "registry/m/server@sha256:cc") ||
if step.State != sdkstep.StepPassed || !strings.Contains(string(step.Result), "registry/m/server@sha256:cc") ||
!strings.Contains(string(step.Result), "registry/m/base@sha256:dd") {
t.Fatalf("the step = %+v", step)
}
@@ -2,14 +2,14 @@ package main
import (
"encoding/json"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"os"
"path/filepath"
"strings"
"testing"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
// The fixture module (testdata/repos/image): a Dockerfile that adds a file to an upstream base the manifest pins in
@@ -38,7 +38,7 @@ func place(t *testing.T, m *Module, edit func(string) string) {
func same(s string) string { return s }
func runBuild(t *testing.T, m *Module, states *keptStates, extra map[string]any) (BuildResult, delivery.Step) {
func runBuild(t *testing.T, m *Module, states *keptStates, extra map[string]any) (BuildResult, sdkstep.Step) {
t.Helper()
args := map[string]any{"workspace": "ws1", "module": "modules/demo", "artifact": "server"}
for k, v := range extra {
@@ -49,7 +49,7 @@ func runBuild(t *testing.T, m *Module, states *keptStates, extra map[string]any)
}
st := states.waitEnd(t)
var res BuildResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatal(err)
}
@@ -73,7 +73,7 @@ func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) {
t.Setenv("MESH_BUILD_CPU_SHARE", "50") // the composer's share (ADR 0553): buildah is given its threads
m, f, states := setup(t, same)
res, st := runBuild(t, m, states, nil)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("step = %+v", st)
}
if res.Digest != imageDigest || res.Artifact != "server" || res.Pushed || res.Reference != "" {
@@ -144,7 +144,7 @@ func TestBuildContextIsTheModulesDirectoryForADockerfileInASubdirectory(t *testi
}
}
_, st := runBuild(t, m, states, nil)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("step = %+v", st)
}
bud := f.called("bud")
@@ -183,7 +183,7 @@ func TestBuildFailsWithBuildahsError(t *testing.T) {
m, f, states := setup(t, same)
f.failBud = c.ran
_, st := runBuild(t, m, states, nil)
if st.State != delivery.StepFailed {
if st.State != sdkstep.StepFailed {
t.Errorf("%s: step = %+v, want failed", name, st)
continue
}
@@ -223,7 +223,7 @@ func TestBuildRefusesAnImageWithARepositoryContextOrAModuleBase(t *testing.T) {
for name, c := range cases {
m, f, states := setup(t, c.edit)
_, st := runBuild(t, m, states, nil)
if st.State != delivery.StepRefused {
if st.State != sdkstep.StepRefused {
t.Errorf("%s: step = %+v, want refused", name, st)
continue
}
@@ -266,7 +266,7 @@ func TestBuildFailsForWhatIsNotAnImageOrNotThere(t *testing.T) {
} {
m, f, states := setup(t, tc.edit)
_, st := runBuild(t, m, states, tc.args)
if st.State != delivery.StepFailed {
if st.State != sdkstep.StepFailed {
t.Errorf("%s: step = %+v, want failed", name, st)
}
if len(f.called("bud")) != 0 {
@@ -317,7 +317,7 @@ func TestAManifestFallingBackToAPredefinedKeyBuilds(t *testing.T) {
return string(out)
})
res, st := runBuild(t, m, states, nil)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("step = %+v", st)
}
if res.Digest != imageDigest || res.Artifact != "server" {
@@ -16,7 +16,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"github.com/nats-io/nats-server/v2/server"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -100,23 +100,23 @@ func call(t *testing.T, nc *nats.Conn, tool string, args map[string]any) map[str
}
// ended waits for the step's key in the bucket to leave running and answers the step as the bucket holds it.
func ended(t *testing.T, kv jetstream.KeyValue, key string) delivery.Step {
func ended(t *testing.T, kv jetstream.KeyValue, key string) sdkstep.Step {
t.Helper()
deadline := time.Now().Add(30 * time.Second)
for time.Now().Before(deadline) {
if e, err := kv.Get(context.Background(), key); err == nil {
st, err := delivery.DecodeStep(e.Value())
st, err := sdkstep.DecodeStep(e.Value())
if err != nil {
t.Fatal(err)
}
if st.State != delivery.StepRunning {
if st.State != sdkstep.StepRunning {
return st
}
}
time.Sleep(20 * time.Millisecond)
}
t.Fatalf("the key %s never left running", key)
return delivery.Step{}
return sdkstep.Step{}
}
func TestTheStepsAnswerThroughARealBusWithTheCapturedShapes(t *testing.T) {
@@ -128,7 +128,7 @@ func TestTheStepsAnswerThroughARealBusWithTheCapturedShapes(t *testing.T) {
// The fetch: a running key at once, then the workspace on the key.
started := call(t, nc, FetchTool, map[string]any{"repository": "novox/mesh-catalog", "commit": commitSHA})
if started["state"] != delivery.StepRunning || started["key"] == "" {
if started["state"] != sdkstep.StepRunning || started["key"] == "" {
t.Fatalf("the fetch answered %v, want a running step and its key", started)
}
st := ended(t, kv, started["key"].(string))
@@ -142,7 +142,7 @@ func TestTheStepsAnswerThroughARealBusWithTheCapturedShapes(t *testing.T) {
}
started := call(t, nc, BuildTool, args)
st := ended(t, kv, started["key"].(string))
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("%s: step = %+v", name, st)
}
var res BuildResult
@@ -17,7 +17,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
const capturedDir = "testdata/captured"
@@ -25,7 +25,7 @@ const capturedDir = "testdata/captured"
var hexDigest = regexp.MustCompile(`sha256:[0-9a-f]{64}`)
// normalized is a stored step with the words that differ between runs put to fixed ones.
func normalized(t *testing.T, st delivery.Step, workspace, commit, registry string) []byte {
func normalized(t *testing.T, st sdkstep.Step, workspace, commit, registry string) []byte {
t.Helper()
raw, err := json.Marshal(st)
if err != nil {
@@ -75,7 +75,7 @@ func compareCaptured(t *testing.T, name string, got []byte) {
}
}
func workspaceOf(t *testing.T, st delivery.Step) string {
func workspaceOf(t *testing.T, st sdkstep.Step) string {
var r struct {
Workspace string `json:"workspace"`
}
@@ -23,9 +23,10 @@ import (
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// CopyTool is the copy step's tool.
@@ -90,7 +91,7 @@ func (m *Module) copy(ctx context.Context, args map[string]any) (any, error) {
return nil, fmt.Errorf("not pinned: %s: %v; an upstream artifact is copied by the digest its manifest pins", name, err)
}
if m.Outcomes == nil || m.Outcomes.Waits == nil {
return nil, fmt.Errorf("%s%s: this process hears no outcome of the image seat", delivery.NotStored, name)
return nil, fmt.Errorf("%s%s: this process hears no outcome of the image seat", builds.NotStored, name)
}
key := MirrorWaitKey(digest)
now := time.Now
@@ -104,14 +105,14 @@ func (m *Module) copy(ctx context.Context, args map[string]any) (any, error) {
heard, stop := m.Outcomes.Expect(digest)
defer stop()
if _, err := m.Outcomes.Waits.Put(key, asked); err != nil {
return nil, fmt.Errorf("%s%s: keeping the copy's wait in %s: %v", delivery.NotStored, name, MirrorWaitsState, err)
return nil, fmt.Errorf("%s%s: keeping the copy's wait in %s: %v", builds.NotStored, name, MirrorWaitsState, err)
}
defer m.Outcomes.drop(key, asked.Token)
said := buildstep.NarrateSeat(ctx, buildstep.ImageSeat, "mirror of "+from)
raw, err := m.call()("seat:"+buildstep.ImageSeat+"."+reg.VerbMirror, reg.MirrorInput{Name: module + "/" + name, Source: from, Digest: digest})
if err != nil {
said.Refused(err)
return nil, fmt.Errorf("%s%s: %s's mirror: %v", delivery.NotStored, name, buildstep.ImageSeat, err)
return nil, fmt.Errorf("%s%s: %s's mirror: %v", builds.NotStored, name, buildstep.ImageSeat, err)
}
var answer reg.MirrorAnswer
_ = json.Unmarshal(raw, &answer)
@@ -139,10 +140,10 @@ func (m *Module) copy(ctx context.Context, args map[string]any) (any, error) {
case <-bound:
err := fmt.Errorf("%s said no outcome of the copy of %s in %s", buildstep.ImageSeat, from, wait)
said.Refused(err)
return nil, fmt.Errorf("%s%s: %w", delivery.NotStored, name, err)
return nil, fmt.Errorf("%s%s: %w", builds.NotStored, name, err)
case <-ctx.Done():
said.Refused(ctx.Err())
return nil, fmt.Errorf("%s%s: stopped while waiting for the copy of %s: %w", delivery.NotStored, name, from, ctx.Err())
return nil, fmt.Errorf("%s%s: stopped while waiting for the copy of %s: %w", builds.NotStored, name, from, ctx.Err())
}
}
}
@@ -150,7 +151,7 @@ func (m *Module) copy(ctx context.Context, args map[string]any) (any, error) {
// copyEnd is a copy's end on the outcome kept for it.
func copyEnd(name string, o MirrorOutcome, asked string) (any, error) {
if o.Event == reg.EventRefused {
return nil, fmt.Errorf("%s%s: %s refused the copy: %s", delivery.NotStored, name, buildstep.ImageSeat, o.Reason)
return nil, fmt.Errorf("%s%s: %s refused the copy: %s", builds.NotStored, name, buildstep.ImageSeat, o.Reason)
}
ref := o.Reference
if ref == "" {
@@ -377,7 +378,7 @@ func (m *Module) Plain(tool string, run func(ctx context.Context, args map[strin
if err != nil {
return nil, err
}
key, _ := delivery.StepKey(m.Node, id)
return map[string]any{"step": id, "key": key, "state": delivery.StepRunning}, nil
key, _ := sdkstep.StepKey(m.Node, id)
return map[string]any{"step": id, "key": key, "state": sdkstep.StepRunning}, nil
}
}
@@ -7,17 +7,17 @@ import (
"time"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
func runCopy(t *testing.T, m *Module, states *keptStates, args map[string]any) (CopyResult, delivery.Step) {
func runCopy(t *testing.T, m *Module, states *keptStates, args map[string]any) (CopyResult, sdkstep.Step) {
t.Helper()
if _, err := tool(t, m, CopyTool)(args); err != nil {
t.Fatal(err)
}
st := states.waitEnd(t)
var res CopyResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatal(err)
}
@@ -67,7 +67,7 @@ func TestTheCopyIsTheImageSeatsMirrorAndEndsOnItsOutcome(t *testing.T) {
f.atAsk = func() { asked, _ = m.Waiting() }
res, st := runCopy(t, m, states, map[string]any{"module": "nats", "artifact": "server", "from": c.from})
if c.reason != "" {
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, c.reason) {
if st.State != sdkstep.StepFailed || !strings.Contains(st.Reason, c.reason) {
t.Fatalf("step = %+v, want failed with %q", st, c.reason)
}
if c.reason == "not pinned: server" && len(f.mirrors) != 0 {
@@ -75,7 +75,7 @@ func TestTheCopyIsTheImageSeatsMirrorAndEndsOnItsOutcome(t *testing.T) {
}
return
}
if st.State != delivery.StepPassed || res.Digest != d || !res.Pushed ||
if st.State != sdkstep.StepPassed || res.Digest != d || !res.Pushed ||
res.Reference != reg.Seat+"/upstream/nats/server@"+d {
t.Fatalf("step = %+v result = %+v", st, res)
}
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// 1703b: the fetch step asks git, through the SDK's FetchCommit, for the ssh remote of the forge as the account, makes
@@ -20,11 +20,11 @@ func TestFetchAsksGitForTheSshRemoteIntoAWorkspace(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if a := ans.(map[string]any); a["state"] != delivery.StepRunning || a["step"] == "" {
if a := ans.(map[string]any); a["state"] != sdkstep.StepRunning || a["step"] == "" {
t.Errorf("the call answered %v, want a running step", a)
}
st := states.waitEnd(t)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("step = %+v", st)
}
var res FetchResult
@@ -60,7 +60,7 @@ func TestFetchRefusesWhatIsNotARepositoryOrACommit(t *testing.T) {
if _, err := tool(t, m, FetchTool)(args); err != nil {
t.Fatal(err)
}
if st := states.waitEnd(t); st.State != delivery.StepFailed {
if st := states.waitEnd(t); st.State != sdkstep.StepFailed {
t.Errorf("%s: step = %+v, want failed", name, st)
}
}
@@ -16,7 +16,7 @@ import (
stdio "git.novox.be/novox/mesh-sdk/go"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -25,29 +25,29 @@ const commitSHA = "0123456789abcdef0123456789abcdef01234567"
// keptStates is the steps state, kept in memory: every write of a key, and the keys that ended.
type keptStates struct {
mu sync.Mutex
steps map[string][]delivery.Step
steps map[string][]sdkstep.Step
ended chan string
}
func newStates() *keptStates {
return &keptStates{steps: map[string][]delivery.Step{}, ended: make(chan string, 16)}
return &keptStates{steps: map[string][]sdkstep.Step{}, ended: make(chan string, 16)}
}
func (s *keptStates) putStep(key string, value any) (uint64, error) {
st, err := delivery.DecodeStep(value.(json.RawMessage))
st, err := sdkstep.DecodeStep(value.(json.RawMessage))
if err != nil {
return 0, err
}
s.mu.Lock()
s.steps[key] = append(s.steps[key], st)
s.mu.Unlock()
if st.State != delivery.StepRunning {
if st.State != sdkstep.StepRunning {
s.ended <- key
}
return 1, nil
}
func (s *keptStates) waitEnd(t *testing.T) delivery.Step {
func (s *keptStates) waitEnd(t *testing.T) sdkstep.Step {
t.Helper()
select {
case key := <-s.ended:
@@ -57,7 +57,7 @@ func (s *keptStates) waitEnd(t *testing.T) delivery.Step {
return v[len(v)-1]
case <-time.After(5 * time.Second):
t.Fatal("the step never ended on its key")
return delivery.Step{}
return sdkstep.Step{}
}
}
@@ -19,7 +19,7 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
func needTools(t *testing.T, what string, tools ...string) {
@@ -91,7 +91,7 @@ func TestFetchOnARealRepository(t *testing.T) {
t.Fatal(err)
}
st := waitLong(t, states)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("step = %+v", st)
}
var res FetchResult
@@ -106,7 +106,7 @@ func TestFetchOnARealRepository(t *testing.T) {
}
}
func waitLong(t *testing.T, s *keptStates) delivery.Step {
func waitLong(t *testing.T, s *keptStates) sdkstep.Step {
t.Helper()
select {
case key := <-s.ended:
@@ -116,7 +116,7 @@ func waitLong(t *testing.T, s *keptStates) delivery.Step {
return v[len(v)-1]
case <-time.After(5 * time.Minute):
t.Fatal("the step never ended on its key")
return delivery.Step{}
return sdkstep.Step{}
}
}
@@ -133,14 +133,14 @@ func imageModule(t *testing.T, m *Module, dockerfile string, files map[string]st
}
}
func buildReal(t *testing.T, m *Module, states *keptStates) (BuildResult, delivery.Step) {
func buildReal(t *testing.T, m *Module, states *keptStates) (BuildResult, sdkstep.Step) {
t.Helper()
if _, err := tool(t, m, BuildTool)(map[string]any{"workspace": "ws1", "module": "modules/img", "artifact": "server"}); err != nil {
t.Fatal(err)
}
st := waitLong(t, states)
var res BuildResult
if st.State == delivery.StepPassed {
if st.State == sdkstep.StepPassed {
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatal(err)
}
@@ -154,7 +154,7 @@ func TestARealBuildAnswersTheImagesDigest(t *testing.T) {
m, states := realModule(t)
imageModule(t, m, "FROM scratch\nCOPY hello.txt /hello.txt\n", map[string]string{"hello.txt": "hello\n"})
res, st := buildReal(t, m, states)
if st.State != delivery.StepPassed || !digestOK.MatchString(res.Digest) {
if st.State != sdkstep.StepPassed || !digestOK.MatchString(res.Digest) {
t.Fatalf("step = %+v result = %+v", st, res)
}
}
@@ -191,17 +191,17 @@ func TestARealBuildIsRootlessWithNoSocketAndNoWayOut(t *testing.T) {
"RUN awk 'NR==1 { exit ($2 == 0) }' /proc/self/uid_map\n"+
"RUN ! ls /var/run/docker.sock /run/docker.sock /run/podman/podman.sock /run/containerd/containerd.sock 2>/dev/null\n"+
"RUN ! grep -E 'docker.sock|podman.sock|containerd.sock' /proc/self/mountinfo\n", nil)
if _, st := buildReal(t, m, states); st.State != delivery.StepPassed {
if _, st := buildReal(t, m, states); st.State != sdkstep.StepPassed {
t.Errorf("the isolation checks inside the build failed: %+v", st)
}
// A step that writes outside its workspace fails: the context cannot be left, and the host's kernel files are not writable.
imageModule(t, m, "FROM "+base+"\nCOPY ../outside /outside\n", nil)
if _, st := buildReal(t, m, states); st.State != delivery.StepFailed {
if _, st := buildReal(t, m, states); st.State != sdkstep.StepFailed {
t.Errorf("a COPY out of the context passed: %+v", st)
}
imageModule(t, m, "FROM "+base+"\nRUN echo b > /proc/sysrq-trigger\n", nil)
if _, st := buildReal(t, m, states); st.State != delivery.StepFailed {
if _, st := buildReal(t, m, states); st.State != sdkstep.StepFailed {
t.Errorf("a write to the host's /proc/sysrq-trigger passed: %+v", st)
} else if strings.Contains(st.Reason, "setup network") {
t.Errorf("the RUN never started, so the write was not what failed (issue 1703): %s", st.Reason)
@@ -220,7 +220,7 @@ func TestARealBuildFindsNoCredentialsOfTheAccount(t *testing.T) {
"RUN ! find / -xdev -name mesh_ed25519 2>/dev/null | grep .\n"+
"RUN ! test -e /root/.ssh\n"+
"RUN ! env | grep -E 'REGISTRY_AUTH|MESH_|SSH_AUTH'\n", nil)
if _, st := buildReal(t, m, states); st.State != delivery.StepPassed {
if _, st := buildReal(t, m, states); st.State != sdkstep.StepPassed {
t.Errorf("a step found a credential path of the account: %+v", st)
}
}
@@ -6,14 +6,14 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
)
// 1890/AC7: the module's own start sweep (endLeftRunning, as main runs it) keeps the conformance kit's left-running
// case: a step a previous process left running is ended at once and said, whatever its age, and a step of this process
// is kept.
func TestTheStartSweepEndsWhatAPreviousProcessLeft(t *testing.T) {
conformance.LeftRunning(t, func(ctx context.Context, scan sdkstep.Scan, node string, before time.Time, logf func(string, ...any)) {
conformance.LeftRunning(t, func(ctx context.Context, scan buildrun.Scan, node string, before time.Time, logf func(string, ...any)) {
(&Module{Node: node}).endLeftRunning(ctx, scan, before, logf)
})
}
@@ -9,14 +9,15 @@ package main
import (
"context"
"fmt"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
"os"
"os/user"
"strings"
"time"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The directories the module's manifest places outside its home (ADR 0466 §1): the workspaces, and buildah's image
@@ -32,10 +33,10 @@ func main() {
ctx := context.Background()
m := &Module{
Node: strings.TrimSpace(os.Getenv("MESH_NODE")),
Steps: stdio.State(delivery.StepsState).Put,
ReadSteps: readOf(stdio.State(delivery.StepsState)),
Pauses: stdio.State(delivery.PausesState).Put,
ReadPauses: readOf(stdio.State(delivery.PausesState)),
Steps: stdio.State(seat.StateSteps).Put,
ReadSteps: readOf(stdio.State(seat.StateSteps)),
Pauses: stdio.State(sdkstep.PausesState).Put,
ReadPauses: readOf(stdio.State(sdkstep.PausesState)),
Ctx: ctx,
Say: logf,
Work: WorkDir,
@@ -54,6 +54,10 @@ func resource(t *testing.T, m catalogue.Manifest, id string) map[string]any {
func TestManifestDeclaresTheAccountTheCapabilityAndThePackages(t *testing.T) {
m := readManifest(t)
// The key keeper is ssh-client's published package, taken by its series (ADR 0634 §1).
// The builder's step, answer and key forms are read through its published package, taken by its series (ADR 0634 §1).
if m.Packages["builds"] != "0.1" {
t.Errorf("packages %v: want builds of series 0.1", m.Packages)
}
if m.Packages["sshkeys"] != "0.1" {
t.Errorf("packages %v: want sshkeys of series 0.1", m.Packages)
}
@@ -8,8 +8,8 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/health"
)
@@ -164,7 +164,7 @@ func (m *Module) Start(ctx context.Context) {
m.Say("this machine cannot build images: %v", err)
}
m.CleanWorkspaces(m.clock())
sdkstep.EndStaleRound(m.stepScan(), m.Node, m.Say) // asks past the step bound, on the module's own round (issue 1890)
buildrun.EndStaleRound(m.stepScan(), m.Node, m.Say) // asks past the step bound, on the module's own round (issue 1890)
select {
case <-ctx.Done():
return
@@ -230,8 +230,8 @@ func (m *Module) Long(tool string, run func(ctx context.Context, args map[string
if err != nil {
return nil, err
}
key, _ := delivery.StepKey(m.Node, id)
return map[string]any{"step": id, "key": key, "state": delivery.StepRunning}, nil
key, _ := sdkstep.StepKey(m.Node, id)
return map[string]any{"step": id, "key": key, "state": sdkstep.StepRunning}, nil
}
}
@@ -8,7 +8,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/health"
)
@@ -167,7 +167,7 @@ func TestAStepOnAMachineThatCannotBuildEndsRefused(t *testing.T) {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepRefused || !strings.Contains(st.Reason, "/nonexistent/subgid") {
if st.State != sdkstep.StepRefused || !strings.Contains(st.Reason, "/nonexistent/subgid") {
t.Errorf("step = %+v, want refused naming the file", st)
}
for _, l := range f.lines() {
@@ -19,9 +19,9 @@ import (
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
)
// KindImage is the kind an image is published as on its seat.
@@ -116,16 +116,16 @@ func (m *Module) publish(ctx context.Context, workspace, module, local string, r
argv := append(m.storeFlags(), "push", "--digestfile", digestFile, local, "oci:"+dir)
if r := m.Run(ctx, m.env(), "buildah", argv...); r.Status != 0 || r.Err != "" {
m.sayFailure("buildah push of "+res.Artifact+" to its layout", r)
return nil, fmt.Errorf("%s%s: writing its layout: %s", delivery.NotStored, res.Artifact, cause(r))
return nil, fmt.Errorf("%s%s: writing its layout: %s", builds.NotStored, res.Artifact, cause(r))
}
img, err := readLayout(dir)
if err != nil {
return nil, fmt.Errorf("%s%s: %w", delivery.NotStored, res.Artifact, err)
return nil, fmt.Errorf("%s%s: %w", builds.NotStored, res.Artifact, err)
}
// What buildah wrote is checked against the layout: a digest never goes out unchecked.
raw, err := os.ReadFile(digestFile)
if pushed := strings.TrimSpace(string(raw)); err != nil || pushed != img.Digest {
return nil, fmt.Errorf("%s%s: buildah wrote the digest %q (%v), but its layout's manifest is %s", delivery.NotStored, res.Artifact, pushed, err, img.Digest)
return nil, fmt.Errorf("%s%s: buildah wrote the digest %q (%v), but its layout's manifest is %s", builds.NotStored, res.Artifact, pushed, err, img.Digest)
}
name := module + "/" + res.Artifact
q := KindImage
@@ -136,7 +136,7 @@ func (m *Module) publish(ctx context.Context, workspace, module, local string, r
Transfer: buildstep.TransferName(name, q, img.Digest)}
sent, err := buildstep.PublishFiles(ctx, m.call(), buildstep.ImageSeat, in, img.Files)
if err != nil {
return nil, fmt.Errorf("%s%s: %v", delivery.NotStored, res.Artifact, err)
return nil, fmt.Errorf("%s%s: %v", builds.NotStored, res.Artifact, err)
}
m.say("published %s on %s: %d of its %d parts sent, the rest held", name, buildstep.ImageSeat, len(sent.Parts), len(img.Files))
res.Digest, res.Pushed = img.Digest, true
@@ -9,14 +9,15 @@ import (
"testing"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/delivery"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// 1703d/AC (ADR 0487 §5): a candidate's build leaves the image seat unchanged: nothing is written, nothing is asked.
func TestACandidatesBuildLeavesTheRegistryUnchanged(t *testing.T) {
m, f, states := setup(t, same)
res, st := runBuild(t, m, states, nil) // no publish
if st.State != delivery.StepPassed || res.Pushed {
if st.State != sdkstep.StepPassed || res.Pushed {
t.Fatalf("step = %+v result = %+v", st, res)
}
if len(f.called("push")) != 0 || len(f.published) != 0 {
@@ -57,12 +58,12 @@ func TestATrunkBuildPublishesThroughTheImageSeatOnlyWhatTheHolderLacks(t *testin
}
res, st := runBuild(t, m, states, args)
if c.reason != "" {
if (st.State != delivery.StepFailed && st.State != delivery.StepRefused) || !strings.Contains(st.Reason, c.reason) {
if (st.State != sdkstep.StepFailed && st.State != sdkstep.StepRefused) || !strings.Contains(st.Reason, c.reason) {
t.Fatalf("step = %+v, want failed with %q", st, c.reason)
}
return
}
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("step = %+v", st)
}
if !res.Pushed || res.Digest != f.pushed || res.Reference != reg.Seat+"/fixture-image/server@"+f.pushed {
@@ -118,7 +119,7 @@ func TestATrunkBuildPublishesThroughTheImageSeatOnlyWhatTheHolderLacks(t *testin
})
}
m2, f2, states2 := setup(t, same)
if _, st := runBuild(t, m2, states2, map[string]any{"publish": true, "tag": "a/b:c"}); st.State != delivery.StepFailed || len(f2.called("bud")) != 0 {
if _, st := runBuild(t, m2, states2, map[string]any{"publish": true, "tag": "a/b:c"}); st.State != sdkstep.StepFailed || len(f2.called("bud")) != 0 {
t.Errorf("a tag that is not one: step = %+v", st)
}
}
@@ -128,7 +129,7 @@ func TestATrunkBuildPublishesThroughTheImageSeatOnlyWhatTheHolderLacks(t *testin
func TestTheNumberedEventPublishesAHeldImageUnderItsVersion(t *testing.T) {
digest := "sha256:" + strings.Repeat("ab", 32)
numbered := func(kind, seat, digest string) []byte {
raw, err := delivery.EncodeVersionNumbered(delivery.VersionNumbered{Name: "fixture-image/server", Kind: kind, Version: "1.4.0",
raw, err := builds.EncodeVersionNumbered(builds.VersionNumbered{Name: "fixture-image/server", Kind: kind, Version: "1.4.0",
Module: "fixture-image", Fingerprint: "fp", Commit: "c0", Digest: digest, Seat: seat})
if err != nil {
t.Fatal(err)
@@ -14,8 +14,8 @@ import (
"sync"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The read tools, named as the module is: build_image_ and the verb.
@@ -97,26 +97,26 @@ func (m *Module) stepKey(id string) (string, error) {
}
id = rest
}
return delivery.StepKey(m.Node, id)
return sdkstep.StepKey(m.Node, id)
}
// ReadStep is the stored value of this node's step id (state, reason, result).
func (m *Module) ReadStep(id string) (delivery.Step, error) {
func (m *Module) ReadStep(id string) (sdkstep.Step, error) {
key, err := m.stepKey(id)
if err != nil {
return delivery.Step{}, err
return sdkstep.Step{}, err
}
if m.ReadSteps.Get == nil {
return delivery.Step{}, fmt.Errorf("the steps state cannot be read here")
return sdkstep.Step{}, fmt.Errorf("the steps state cannot be read here")
}
raw, err := m.ReadSteps.Get(key)
if err != nil {
return delivery.Step{}, err
return sdkstep.Step{}, err
}
if len(raw) == 0 {
return delivery.Step{}, fmt.Errorf("no step %s in the steps state", key)
return sdkstep.Step{}, fmt.Errorf("no step %s in the steps state", key)
}
return delivery.DecodeStep(raw)
return sdkstep.DecodeStep(raw)
}
// StepLine is a step without its result: the list answers these.
@@ -149,7 +149,7 @@ func (m *Module) RecentSteps(limit int) (lines []StepLine, cut bool, err error)
own := keys[:0:0]
for _, k := range keys {
if strings.HasPrefix(k, m.Node+".") {
if node, _, err := delivery.ParseStepKey(k); err == nil && node == m.Node {
if node, _, err := sdkstep.ParseStepKey(k); err == nil && node == m.Node {
own = append(own, k)
}
}
@@ -163,12 +163,12 @@ func (m *Module) RecentSteps(limit int) (lines []StepLine, cut bool, err error)
if err != nil {
return nil, cut, err
}
var steps []delivery.Step
var steps []sdkstep.Step
for _, raw := range raws {
if len(raw) == 0 {
continue
}
s, err := delivery.DecodeStep(raw)
s, err := sdkstep.DecodeStep(raw)
if err != nil {
continue // a value this module cannot read is not one of its own steps
}
@@ -8,14 +8,14 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// The read tools answer what the steps state holds: a finished build's result is there, read by the step's id.
func TestTheReadToolsAnswerAStepsResult(t *testing.T) {
m, _, states := setup(t, same)
res, st := runBuild(t, m, states, nil)
if st.State != delivery.StepPassed {
if st.State != sdkstep.StepPassed {
t.Fatalf("step = %+v", st)
}
m.ReadSteps = StateRead{
@@ -36,7 +36,7 @@ func TestTheReadToolsAnswerAStepsResult(t *testing.T) {
},
}
got, err := m.ReadStep(st.ID)
if err != nil || got.State != delivery.StepPassed {
if err != nil || got.State != sdkstep.StepPassed {
t.Fatalf("ReadStep = %+v, %v", got, err)
}
var back BuildResult
@@ -67,7 +67,7 @@ func TestRecentStepsReadsOnlyTheNodesKeysInParallelAndBounded(t *testing.T) {
stored := map[string][]byte{}
for _, n := range []string{"ace", "g14", "novox", "shanks"} {
for i := 0; i < row.perNode; i++ {
b, _ := delivery.EncodeStep(delivery.Step{ID: fmt.Sprintf("s%04d", i), Tool: "t", Node: n, State: delivery.StepPassed, Started: t0.Add(time.Duration(i) * time.Second)})
b, _ := sdkstep.EncodeStep(sdkstep.Step{ID: fmt.Sprintf("s%04d", i), Tool: "t", Node: n, State: sdkstep.StepPassed, Started: t0.Add(time.Duration(i) * time.Second)})
stored[n+".s"+fmt.Sprintf("%04d", i)] = b
}
}
@@ -9,7 +9,7 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/health"
)
@@ -43,7 +43,7 @@ func TestUnderTheFloorAFetchAndABuildAreRefusedAndTheModuleIsUnhealthy(t *testin
if _, err := tool(t, m, name)(args); err != nil {
t.Fatalf("%s: %v", name, err)
}
if st := states.waitEnd(t); st.State != delivery.StepRefused || !strings.HasPrefix(st.Reason, underTheFloor) {
if st := states.waitEnd(t); st.State != sdkstep.StepRefused || !strings.HasPrefix(st.Reason, underTheFloor) {
t.Errorf("%s ended %s: %q, want refused: %q", name, st.State, st.Reason, underTheFloor)
}
}
@@ -87,7 +87,7 @@ func TestAFailedFetchLeavesNoWorkspace(t *testing.T) {
if _, err := tool(t, m, FetchTool)(map[string]any{"repository": "novox/mesh-catalog", "commit": commitSHA}); err != nil {
t.Fatal(err)
}
if st := states.waitEnd(t); st.State != delivery.StepFailed {
if st := states.waitEnd(t); st.State != sdkstep.StepFailed {
t.Fatalf("the step ended %s (%s), want failed", st.State, st.Reason)
}
if entries, _ := os.ReadDir(m.Work); len(entries) != 0 {
+2
View File
@@ -3,6 +3,8 @@ module meshbuildimage
go 1.26.0
require (
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0
+1
View File
@@ -2,6 +2,7 @@
"module": "mesh-build-image",
"version": "1",
"packages": {
"builds": "0.1",
"sshkeys": "0.1"
},
"moved": true,
@@ -6,9 +6,10 @@ import (
"time"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// **The module pulls its seat's asks** (novox/hq ADR 0525 §2-§6, ADR 0536 §4; issue 1887 A3node), as mesh-build-go
@@ -31,12 +32,12 @@ func (m *Module) askSteps(steps []StepTool) buildstep.AskSteps {
return buildstep.AskSteps{
Fetch: byName["build_node_fetch"], Build: byName["build_node_build"], Test: byName["build_node_test"],
Compare: buildstep.CompareStep(buildstep.GeneratedRootTS, func(ws string) string { return filepath.Join(m.Work, ws, "tree") }),
Toolchain: func(ctx context.Context) (delivery.ToolchainUsed, error) {
Toolchain: func(ctx context.Context) (sdkstep.ToolchainUsed, error) {
installed, err := m.Probe(ctx)
if err != nil {
return delivery.ToolchainUsed{}, err
return sdkstep.ToolchainUsed{}, err
}
return delivery.ToolchainUsed{Version: installed, Recipe: m.recipe().Version()}, nil
return sdkstep.ToolchainUsed{Version: installed, Recipe: m.recipe().Version()}, nil
},
Release: func(ws string) error { return m.spaces().Release(ws) },
Logf: m.Say,
@@ -46,7 +47,7 @@ func (m *Module) askSteps(steps []StepTool) buildstep.AskSteps {
// Puller is the module's pull of its seat's asks, on source (the runtime's seat pull when nil).
func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep.Puller {
if source == nil {
source = buildstep.SeatAsks{Language: delivery.AskNode}
source = buildstep.SeatAsks{Language: sdkstep.AskNode}
}
capacity := m.Capacity
// The capacity's own change wakes the puller (issue 1887): the runtime's watch of the config value as mesh-config
@@ -64,16 +65,16 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
capacity = buildstep.NodeCapacity(m.Settings, m.Node, m.Say)
settings = m.Settings
watch = buildstep.WatchConfigServed
cores = buildstep.NodeCoresPerStep(m.Settings, buildstep.DefaultCoresPerStep[delivery.AskNode], m.Say)
cores = buildstep.NodeCoresPerStep(m.Settings, buildstep.DefaultCoresPerStep[sdkstep.AskNode], m.Say)
}
ask := m.askSteps(steps)
var load func(string) ([]byte, error)
if m.ReadPauses.Get != nil {
load = func(key string) ([]byte, error) { return m.ReadPauses.Get(key) }
}
return &buildstep.Puller{Source: source, Language: delivery.AskNode, Node: m.Node, Put: m.Steps, PausePut: m.Pauses, Load: load,
return &buildstep.Puller{Source: source, Language: sdkstep.AskNode, Node: m.Node, Put: m.Steps, PausePut: m.Pauses, Load: load,
Capacity: capacity, SettingsFile: settings, WatchConfig: watch, CoresPerStep: cores, BuildHours: hours, Logf: m.Say,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
if err := m.Prepare(ctx); err != nil {
return nil, err
}
@@ -93,13 +94,13 @@ func (m *Module) SeatTools(p *buildstep.Puller) []stdio.Tool {
const StepEndTool = "build_node_step_end"
// endLeftRunning is the module's start sweep (issue 1890): it ends the steps of its node that a previous process left
// running (sdkstep.EndStaleAtStart; every step names its owner). main runs it once at the start, and the module's test
// running (buildrun.EndStaleAtStart; every step names its owner). main runs it once at the start, and the module's test
// runs it through the conformance kit's left-running case.
func (m *Module) endLeftRunning(ctx context.Context, scan sdkstep.Scan, booted time.Time, logf func(string, ...any)) {
sdkstep.EndStaleAtStart(ctx, scan, m.Node, booted, logf)
func (m *Module) endLeftRunning(ctx context.Context, scan buildrun.Scan, booted time.Time, logf func(string, ...any)) {
buildrun.EndStaleAtStart(ctx, scan, m.Node, booted, logf)
}
// stepScan is the module's steps state as buildstep reads it.
func (m *Module) stepScan() sdkstep.Scan {
return sdkstep.ScanOf(stdio.State(delivery.StepsState))
func (m *Module) stepScan() buildrun.Scan {
return buildrun.ScanOf(stdio.State(seat.StateSteps))
}
@@ -12,7 +12,6 @@ import (
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
const askCommit = "0123456789abcdef0123456789abcdef01234567"
@@ -31,14 +30,14 @@ func fakeSteps(calls *[]string) []StepTool {
}
}
func nodeAsk(kind string) delivery.StepAsk {
a := delivery.StepAsk{ID: sdkstep.NewID(), Kind: kind, Language: delivery.AskNode, Asker: "mesh-delivery-checker", At: time.Now()}
fetch := map[string]any{"repository": "novox/mesh-catalog", "commit": askCommit}
if kind == delivery.AskDelivery {
func nodeAsk(kind string) sdkstep.StepAsk {
a := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: kind, Language: sdkstep.AskNode, Asker: "mesh-delivery-checker", At: time.Now()}
fetch := sdkstep.Fetch{Repository: "novox/mesh-catalog", Commit: askCommit}
if kind == sdkstep.AskDelivery {
a.Asker = "mesh-delivery-builder"
a.Build = &delivery.BuildJob{Module: "gitea", Path: "modules/gitea", Fetch: fetch, Artifacts: []delivery.AskArtifact{{Artifact: "code"}}}
a.Build = &sdkstep.BuildJob{Module: "gitea", Path: "modules/gitea", Fetch: &fetch, Artifacts: []sdkstep.AskArtifact{{Artifact: "code"}}}
} else {
a.Check = &delivery.CheckJob{Fetch: fetch, Levels: []string{delivery.LevelUnit}}
a.Check = &sdkstep.CheckJob{Fetch: fetch, Levels: []string{sdkstep.LevelUnit}}
}
return a
}
@@ -51,7 +50,7 @@ func TestAPulledNodeAskRunsItsJob(t *testing.T) {
m.Capacity = func() int { return 1 }
var calls []string
src := buildstep.NewFakeAsks()
build, check := nodeAsk(delivery.AskDelivery), nodeAsk(delivery.AskCheck)
build, check := nodeAsk(sdkstep.AskDelivery), nodeAsk(sdkstep.AskCheck)
_ = src.PublishAsk(check)
_ = src.PublishAsk(build)
p := m.Puller(fakeSteps(&calls), src)
@@ -70,7 +69,7 @@ func TestAPulledNodeAskRunsItsJob(t *testing.T) {
if got := strings.Join(calls, ","); got != "build_node_fetch,build_node_build,build_node_fetch,build_node_test" {
t.Errorf("calls = %s, want the delivery ask's job first, then the check's", got)
}
key, _ := delivery.AskStepKey("ace", build.ID)
key, _ := sdkstep.AskStepKey("ace", build.ID)
states.mu.Lock()
v := states.steps[key]
states.mu.Unlock()
@@ -92,10 +91,10 @@ func TestManifestTakesTheBuildCapacity(t *testing.T) {
}
_ = json.Unmarshal(raw, &doc)
// Issue 1806's follow-up: the cores a step needs is a setting of its own, in the settings file, with the SDK's default.
if cores := string(doc.Config[buildstep.CoresPerStepSetting].Default); cores != fmt.Sprint(buildstep.DefaultCoresPerStep[delivery.AskNode]) {
t.Errorf("the cores-per-step default = %s, want %g", cores, buildstep.DefaultCoresPerStep[delivery.AskNode])
if cores := string(doc.Config[buildstep.CoresPerStepSetting].Default); cores != fmt.Sprint(buildstep.DefaultCoresPerStep[sdkstep.AskNode]) {
t.Errorf("the cores-per-step default = %s, want %g", cores, buildstep.DefaultCoresPerStep[sdkstep.AskNode])
}
if got := buildstep.NodeCoresPerStep(moduleDir+"/files/settings.json", 0, nil)(); got != buildstep.DefaultCoresPerStep[delivery.AskNode] {
if got := buildstep.NodeCoresPerStep(moduleDir+"/files/settings.json", 0, nil)(); got != buildstep.DefaultCoresPerStep[sdkstep.AskNode] {
t.Errorf("the settings template holds cores-per-step %g", got)
}
// And the build hours (the operator, 2026-10-26): a setting of their own, empty by default (any hour), in the template.
@@ -18,9 +18,9 @@ import (
"strings"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// Out is where a bundle's compiled files go in the module's directory (builder toolchain.go:118).
@@ -145,7 +145,7 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) {
// Published from this process once the build ended, a candidate's as a trunk build's (ADR 0498 §1 §2; CAT-BN3): the
// step answers the seat's reference, or fails not stored. The packed bytes are never written to the node (L9).
if err := m.publishOn(ctx, buildstep.ArtifactSeat, a.module+"/"+a.Name, "bundle", "", digest, archive); err != nil {
return nil, fmt.Errorf("%s%s: %v", delivery.NotStored, a.Name, err)
return nil, fmt.Errorf("%s%s: %v", builds.NotStored, a.Name, err)
}
files := []string{}
_ = filepath.WalkDir(bundleDir, func(p string, d os.DirEntry, err error) error {

Some files were not shown because too many files have changed in this diff Show More