A module whose only artifact is a Go package (mesh-wireguard's go-overlay) never had a done build: PlanOf refused every package of language go. The Go build module already packs and publishes a Go package; the builder now asks it. Builds: design 47 G §370; design 51 *Publishing* Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
123 lines
5.7 KiB
Go
123 lines
5.7 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"},
|
|
catalogue.Artifact{Name: "go-overlay", Kind: "package", Language: "go"}) // design 47 G §370: a Go package is built
|
|
p, err := PlanOf(m, testSeats())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []Task{
|
|
{Artifact: "files", Kind: "archive", Module: testGo, Language: "go"},
|
|
{Artifact: "go-overlay", Kind: "package", 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)
|
|
}
|
|
}
|