The builder finds a build module as the indexer's seed does: the build seat whose acting rule `build` has an `on` matching the artifact's language or kind:image, and that seat's holder, read from the seat list's definitions and the claims of the catalog half's manifests. Task.Module, every lane key, the unpublished modules, the toolchain sources, the ends watched, the sayer and builder_stuck (through `<seat>.steps`) derive from it; the floor's sources are the holders of the seats git and mesh-catalog. buildrun.GoBuildModule, NodeBuildModule, LanguageOf, buildstep.BuildModuleOf use, build.ImageBuildModule and StuckBuildModules are gone from the builder (the checker keeps its own names in two small files until its part reads them from the list). Builds: design 47 G §7, §428, §704, §204, A-82 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
121 lines
5.5 KiB
Go
121 lines
5.5 KiB
Go
package build
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
|
"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/seatdef"
|
|
)
|
|
|
|
// manifestOf is a manifest holding these artifacts: the plan reads only their name, kind and language.
|
|
func manifestOf(artifacts ...catalogue.Artifact) catalogue.Manifest {
|
|
return catalogue.Manifest{Module: "m", Build: &catalogue.Build{Artifacts: artifacts}}
|
|
}
|
|
|
|
func TestPlanRoutesEachKindToItsBuildModule(t *testing.T) { // 1607/AC2
|
|
m := manifestOf(
|
|
catalogue.Artifact{Name: "tool", Kind: "bundle", Language: "go"},
|
|
catalogue.Artifact{Name: "web", Kind: "bundle", Language: "typescript"},
|
|
catalogue.Artifact{Name: "files", Kind: "archive"})
|
|
p, err := PlanOf(m, testSeats())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []Task{
|
|
{Artifact: "files", Kind: "archive", Module: testGo, Language: "go"},
|
|
{Artifact: "tool", Kind: "bundle", Module: testGo, Language: "go"},
|
|
{Artifact: "web", Kind: "bundle", Module: testNode, Language: "typescript"},
|
|
}
|
|
if len(p.Tasks) != len(want) {
|
|
t.Fatalf("tasks = %+v, want %+v", p.Tasks, want)
|
|
}
|
|
for i := range want {
|
|
if p.Tasks[i] != want[i] {
|
|
t.Errorf("task %d = %+v, want %+v", i, p.Tasks[i], want[i])
|
|
}
|
|
}
|
|
if modules := p.Modules(); len(modules) != 2 || modules[0] != testGo || modules[1] != testNode {
|
|
t.Errorf("modules = %v", modules)
|
|
}
|
|
}
|
|
|
|
const pinned = "nats:2.11.17-alpine@sha256:3333333333333333333333333333333333333333333333333333333333333333"
|
|
|
|
func TestPlanSendsAPinnedUpstreamToTheImageBuildModulesCopyAndRefusesAnUnpinnedOne(t *testing.T) { // B3, ADR 0498 clause 4
|
|
p, err := PlanOf(manifestOf(catalogue.Artifact{Name: "ok", Kind: "archive"}, catalogue.Artifact{Name: "up", Kind: "upstream", From: pinned}), testSeats())
|
|
if err != nil || len(p.Tasks) != 2 {
|
|
t.Fatalf("plan %+v, error %v", p, err)
|
|
}
|
|
if up := p.Tasks[1]; up.Artifact != "up" || up.Kind != "upstream" || up.Module != testImage || up.From != pinned || up.Language != "" {
|
|
t.Errorf("task = %+v, want the upstream sent to %s with its pinned reference and no toolchain", up, testImage)
|
|
}
|
|
if p.NeedsToolchain(testImage) {
|
|
t.Error("a copy needs no toolchain")
|
|
}
|
|
_, err = PlanOf(manifestOf(catalogue.Artifact{Name: "up", Kind: "upstream", From: "nats:2.11.17-alpine"}), testSeats())
|
|
if err == nil || !strings.HasPrefix(err.Error(), builds.NotPinned+"up") || !builds.FailedUntilChanged(err.Error()) {
|
|
t.Errorf("an upstream with no digest: error = %v, want it to open %q naming the artifact", err, builds.NotPinned)
|
|
}
|
|
}
|
|
|
|
func TestPlanRefusesAPackageOfAnotherLanguageByName(t *testing.T) { // ADR 0498 clause 5
|
|
_, err := PlanOf(manifestOf(catalogue.Artifact{Name: "ok", Kind: "archive"}, catalogue.Artifact{Name: "pkg", Kind: "package", Language: "python"}), testSeats())
|
|
var r *Refused
|
|
if err == nil || !asRefused(err, &r) || !strings.HasPrefix(err.Error(), builds.RefusedKind) || !strings.Contains(err.Error(), "python") || !strings.Contains(err.Error(), "pkg") {
|
|
t.Errorf("error = %v, want it to open %q naming the artifact and its language", err, builds.RefusedKind)
|
|
}
|
|
}
|
|
|
|
func TestPlanRefusesABundleOfAnotherLanguageAndAModuleWithNoArtifact(t *testing.T) {
|
|
if _, err := PlanOf(manifestOf(catalogue.Artifact{Name: "x", Kind: "bundle", Language: "python"}), testSeats()); err == nil || !strings.HasPrefix(err.Error(), builds.RefusedKind) {
|
|
t.Errorf("a python bundle: error = %v, want a refusal", err)
|
|
}
|
|
p, err := PlanOf(manifestOf(), testSeats())
|
|
if err != nil || len(p.Tasks) != 0 {
|
|
t.Errorf("no artifact: plan %+v error %v, want an empty plan", p, err)
|
|
}
|
|
}
|
|
|
|
func asRefused(err error, target **Refused) bool { return errors.As(err, target) }
|
|
|
|
func TestPlanSendsAnImageToMeshBuildImageWithNoToolchain(t *testing.T) { // 1705a, ADR 0487 clause 5
|
|
p, err := PlanOf(manifestOf(catalogue.Artifact{Name: "img", Kind: "image"}, catalogue.Artifact{Name: "files", Kind: "archive"}), testSeats())
|
|
if err != nil || len(p.Tasks) != 2 {
|
|
t.Fatalf("plan %+v, error %v", p, err)
|
|
}
|
|
if p.Tasks[1].Artifact != "img" || p.Tasks[1].Module != testImage {
|
|
t.Errorf("task = %+v, want the image on %s", p.Tasks[1], testImage)
|
|
}
|
|
if p.NeedsToolchain(testImage) {
|
|
t.Error("an image needs no toolchain")
|
|
}
|
|
}
|
|
|
|
// A plan's task names the holder of the build seat the seat list and the catalog half give, and a plan made before they name one
|
|
// is no refusal and is not kept (design 47 G A-82).
|
|
func TestAPlanNamesTheHoldersOfTheBuildSeats(t *testing.T) {
|
|
m := manifestOf(catalogue.Artifact{Name: "tool", Kind: "bundle", Language: "go"}, catalogue.Artifact{Name: "img", Kind: "image"})
|
|
defs := map[string]seatdef.Definition{
|
|
"node-build-go": {Acts: []seatdef.ActingRule{{Step: seatdef.StepBuild, On: "go"}}},
|
|
"node-build-image": {Acts: []seatdef.ActingRule{{Step: seatdef.StepBuild, On: seatdef.OnKindImage}}},
|
|
}
|
|
holders := map[string]string{}
|
|
seats := buildrun.NewBuildSeats(func() map[string]seatdef.Definition { return defs }, func(s string) (string, bool) { h, ok := holders[s]; return h, ok })
|
|
_, err := PlanOf(m, seats)
|
|
var none *NoBuildModule
|
|
if !errors.As(err, &none) {
|
|
t.Fatalf("with no holder: %v, want NoBuildModule", err)
|
|
}
|
|
holders["node-build-go"], holders["node-build-image"] = "acme-go", "acme-img"
|
|
p, err := PlanOf(m, seats)
|
|
if err != nil || len(p.Tasks) != 2 || p.Tasks[0].Module != "acme-img" || p.Tasks[1].Module != "acme-go" || p.Tasks[1].Language != "go" || p.Tasks[0].Language != "" {
|
|
t.Fatalf("plan = %+v, %v", p, err)
|
|
}
|
|
}
|