Files
mesh-controller/internal/inventory/dependencies_test.go
T

249 lines
10 KiB
Go

package inventory
import (
"strings"
"testing"
"github.com/novox/mesh-controller/internal/catalogue"
)
// The catalogue's one dependency relation (novox/hq ADR 0162): four kinds of edge from one call.
func TestDependenciesAreOneRelationWithTheirKinds(t *testing.T) {
entry := func(name, repository string) Entry {
return Entry{Manifest: catalogue.Manifest{Module: name}, Source: Source{Repository: repository}}
}
builder := entry("builder", "http://forge/novox/mesh-catalog.git")
builder.Manifest.Claims = []catalogue.Claim{{Name: "node-build-agent", Scope: catalogue.ScopeNode}}
plugin := entry("shop-plugin", "http://forge/novox/mesh-catalog.git")
plugin.Manifest.Build = &catalogue.Build{On: []catalogue.BuildsOn{{Arg: "BASE", Module: "shop"}}}
entries := []Entry{
entry("mesh-tools", "http://forge/novox/mesh-tools.git"),
entry("shop", "http://forge/novox/mesh-catalog.git"),
plugin,
builder,
entry("mesh-controller", "http://forge/novox/mesh-controller.git"),
entry("route-proxy", "http://forge/novox/mesh-catalog.git"),
{Manifest: catalogue.Manifest{Module: "hand-made"}},
}
against := map[string][]string{
"shop": {catalogue.ArtifactStoreScheme + "mesh-tools/runtime@sha256:a"},
"builder": {catalogue.ArtifactStoreScheme + "mesh-tools/runtime@sha256:a"},
}
read := map[string][]ReadRepository{
"route-proxy": {{Repository: "http://forge/novox/mesh-controller.git", Ref: "main"}},
}
got := dependenciesOf(entries, against, read)
has := func(from, to, kind string) bool {
for _, e := range got {
if e == (Edge{From: from, To: to, Kind: kind}) {
return true
}
}
return false
}
for _, want := range []Edge{
{From: "shop", To: "mesh-tools", Kind: EdgeStandsOn},
{From: "builder", To: "mesh-tools", Kind: EdgeStandsOn},
{From: "shop-plugin", To: "shop", Kind: EdgeDeclared},
{From: "shop", To: "builder", Kind: EdgeBuiltBy},
{From: "mesh-controller", To: "builder", Kind: EdgeBuiltBy},
{From: "mesh-tools", To: "builder", Kind: EdgeBuiltBy},
} {
if !has(want.From, want.To, want.Kind) {
t.Errorf("missing %+v in %+v", want, got)
}
}
// Sharing a repository is no dependency (novox/hq ADR 0267 rule 4): what a build read draws no edge.
if has("route-proxy", "mesh-controller", EdgePackages) {
t.Error("a packages edge was drawn from what a build read")
}
if has("builder", "builder", EdgeBuiltBy) {
t.Error("the builder is not built by itself")
}
for _, e := range got {
if e.From == "hand-made" {
t.Errorf("a module with no source depends on nothing: %+v", e)
}
}
}
// novox/hq 04-ISSUES/211: a bundle stands on the toolchain it is compiled in, so a merge moving both
// builds the toolchain first — read from the manifest, before any build recorded it.
func TestABundleStandsOnTheToolchainItIsCompiledIn(t *testing.T) {
entries := []Entry{
{Manifest: catalogue.Manifest{Module: "mesh-tools"}, Source: Source{Repository: "novox/mesh-tools"}},
{Manifest: catalogue.Manifest{Module: "mesh-tools-go"}, Source: Source{Repository: "novox/mesh-tools-go"}},
{Manifest: catalogue.Manifest{Module: "node-tools", Build: &catalogue.Build{Artifacts: []catalogue.Artifact{
{Name: "runtime", Kind: catalogue.ArtifactBundle, Language: "go", System: "arch", From: "cmd/node-tools"}}}},
Source: Source{Repository: "novox/mesh-tools"}},
{Manifest: catalogue.Manifest{Module: "nftables", Build: &catalogue.Build{Artifacts: []catalogue.Artifact{
{Name: "tools", Kind: catalogue.ArtifactBundle, Language: "typescript", Entrypoints: []string{"tools/index.js"}}}}},
Source: Source{Repository: "novox/mesh-catalog"}},
{Manifest: catalogue.Manifest{Module: "photos", Build: &catalogue.Build{Artifacts: []catalogue.Artifact{
{Name: "server", Kind: catalogue.ArtifactImage, From: "Dockerfile"}}}},
Source: Source{Repository: "novox/photos"}},
}
edges := dependenciesOf(entries, nil, nil)
has := func(from, to string) bool {
for _, e := range edges {
if e.From == from && e.To == to && e.Kind == EdgeStandsOn {
return true
}
}
return false
}
if !has("nftables", "mesh-tools") {
t.Errorf("a TypeScript bundle does not stand on the TypeScript toolchain: %v", edges)
}
if !has("node-tools", "mesh-tools-go") {
t.Errorf("a Go bundle does not stand on the Go toolchain: %v", edges)
}
for _, e := range edges {
if e.From == "photos" && e.Kind == EdgeStandsOn {
t.Errorf("an image stands on a toolchain it is not compiled in: %v", e)
}
}
}
// novox/hq 04-ISSUES/212: a toolchain standing on the SDK's package is planned after the SDK, so a
// release of the SDK rebuilds the toolchain, and every bundle compiled in it after that.
func TestAToolchainStandingOnTheSDKFollowsIt(t *testing.T) {
entries := []Entry{
{Manifest: catalogue.Manifest{Module: "mesh-sdk"}},
{Manifest: catalogue.Manifest{Module: "mesh-tools", Build: &catalogue.Build{
On: []catalogue.BuildsOn{{Arg: "MESH_SDK", Module: "mesh-sdk", Artifact: "lib"}}}}},
}
edges := dependenciesOf(entries, nil, nil)
found := false
for _, e := range edges {
if e.From == "mesh-tools" && e.To == "mesh-sdk" {
found = true
}
}
if !found {
t.Errorf("no edge from the toolchain to the SDK: %v", edges)
}
}
// novox/hq issue 278: nothing depends on the build agent but by being built by it. A plan is widened
// only along the other kinds (reachableFrom), so this is what keeps a change to the build agent — its
// manifest or its program — from rebuilding what it builds: the relation holds no edge to it that could.
func TestNothingStandsOnTheBuildAgentOnlyIsBuiltByIt(t *testing.T) {
const catalogueRepo = "http://forge/novox/mesh-catalog.git"
agent := Entry{Manifest: catalogue.Manifest{Module: "build-agent",
Claims: []catalogue.Claim{{Name: "node-build-agent", Scope: catalogue.ScopeNode}}},
Source: Source{Repository: catalogueRepo, Path: "modules/build-agent"}}
bundle := Entry{Manifest: catalogue.Manifest{Module: "redis", Build: &catalogue.Build{
Artifacts: []catalogue.Artifact{{Name: "code", Kind: catalogue.ArtifactBundle, Language: "typescript"}}}},
Source: Source{Repository: catalogueRepo, Path: "modules/redis"}}
entries := []Entry{
agent, bundle,
{Manifest: catalogue.Manifest{Module: "postgres"}, Source: Source{Repository: catalogueRepo, Path: "modules/postgres"}},
{Manifest: catalogue.Manifest{Module: "mesh-tools"}, Source: Source{Repository: "http://forge/novox/mesh-tools.git"}},
{Manifest: catalogue.Manifest{Module: TheControlPlane}, Source: Source{Repository: "http://forge/novox/mesh-controller.git"}},
}
// A module packaging another repository depends on what is built from it, never on the agent.
read := map[string][]ReadRepository{"postgres": {{Repository: "http://forge/novox/mesh-tools.git"}}}
builtBy := 0
for _, e := range dependenciesOf(entries, nil, read) {
if e.To != "build-agent" {
continue
}
if e.Kind != EdgeBuiltBy {
t.Errorf("%s depends on the build agent as %s: a change to the agent would rebuild it", e.From, e.Kind)
}
builtBy++
}
if builtBy != 4 {
t.Errorf("everything source-built is built by the agent: %d edges", builtBy)
}
}
// A copy of an upstream image is no module's artifact (novox/hq ADR 0257), even when a module happens to
// carry the name its repository starts with.
func TestACopyOfAnUpstreamImageIsNoEdge(t *testing.T) {
entries := []Entry{
{Manifest: catalogue.Manifest{Module: "route-proxy"}},
{Manifest: catalogue.Manifest{Module: "upstream"}},
}
against := map[string][]string{
"route-proxy": {catalogue.ArtifactStoreScheme + "upstream/docker.io/library/golang@sha256:a"},
}
for _, e := range dependenciesOf(entries, against, nil) {
if e.From == "route-proxy" && e.To == "upstream" {
t.Fatalf("a copy of an upstream image was read as a module's artifact: %+v", e)
}
}
}
// **A built module keeps its build edges** (novox/hq issue 614). A manifest the mesh registers from a
// build carries no `build` (catalogue.Resolve drops it), so mesh-tools' `build.on` naming mesh-sdk's
// `lib` package and a provider's TypeScript bundle drew no edge once built, and a new SDK rebuilt
// neither. The package base is read from what the build was handed (`@novox/mesh-sdk@0.1.13`, the
// package's name whichever version the mesh holds now), the toolchain from the bundles it keeps.
func TestABuiltModuleKeepsItsBuildEdges(t *testing.T) {
built := func(name string, bundles ...catalogue.Bundle) Entry {
return Entry{Manifest: catalogue.Manifest{Module: name, Bundles: bundles},
Source: Source{Repository: "http://forge/novox/" + name + ".git"}}
}
entries := []Entry{
built("mesh-sdk"),
built("mesh-tools"),
built("mesh-tools-go"),
built("minio", catalogue.Bundle{Name: "code", Language: "typescript"}),
built("umami", catalogue.Bundle{Name: "code", Language: "TypeScript"}),
built("dnsmasq", catalogue.Bundle{Name: "tools", Language: "go"}),
built("plain"),
}
against := map[string][]string{
"mesh-tools": {"@novox/mesh-sdk@0.1.13",
catalogue.ArtifactStoreScheme + "upstream/docker.io/library/node@sha256:" + strings.Repeat("0", 64)},
// A package nobody in the mesh publishes is no edge.
"plain": {"left-pad@1.3.0"},
}
held := map[string]string{
"mesh-sdk/lib": "@novox/mesh-sdk@0.1.14",
"mesh-tools/runtime": catalogue.ArtifactStoreScheme + "mesh-tools/runtime@sha256:" + strings.Repeat("1", 64),
}
got := dependenciesWith(entries, against, held)
has := func(from, to string) bool {
for _, e := range got {
if e.From == from && e.To == to && e.Kind == EdgeStandsOn {
return true
}
}
return false
}
for _, want := range [][2]string{
{"mesh-tools", "mesh-sdk"},
{"minio", "mesh-tools"},
{"umami", "mesh-tools"},
{"dnsmasq", "mesh-tools-go"},
} {
if !has(want[0], want[1]) {
t.Errorf("no %s stands-on %s: %+v", want[0], want[1], got)
}
}
for _, e := range got {
if e.From == "plain" && e.Kind != EdgeBuiltBy {
t.Errorf("an unknown package drew an edge: %+v", e)
}
}
}
func TestAPackageNameIsReadOnlyFromAPackageReference(t *testing.T) {
for ref, want := range map[string]string{
"@novox/mesh-sdk@0.1.14": "@novox/mesh-sdk",
"left-pad@1.3.0": "left-pad",
"@novox/mesh-sdk": "",
"novox.internal:5100/mesh-tools/build@sha256:" + strings.Repeat("0", 64): "",
catalogue.ArtifactStoreScheme + "mesh-tools/build@sha256:0": "",
"http://store/v2/x/blobs/sha256:0": "",
"": "",
} {
if got := packageName(ref); got != want {
t.Errorf("packageName(%q) = %q, want %q", ref, got, want)
}
}
}