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) } }