A copied base was named only in what a build stood on, and nowhere when the build failed, so the store's sweep could never let one go (hq issue 321). One repository per upstream image stops each module asking the public registry for the same image again, and letting an index go now takes its own platform manifests, which otherwise kept every byte. A person can record the copies no record names through the new mirrors verb (hq ADR 0257). The forge test fix is the same commit as on feat/plain-notifications: main fails without it.
174 lines
7.4 KiB
Go
174 lines
7.4 KiB
Go
package inventory
|
|
|
|
import (
|
|
"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{
|
|
{"shop", "mesh-tools", EdgeStandsOn},
|
|
{"builder", "mesh-tools", EdgeStandsOn},
|
|
{"shop-plugin", "shop", EdgeDeclared},
|
|
{"route-proxy", "mesh-controller", EdgePackages},
|
|
{"shop", "builder", EdgeBuiltBy},
|
|
{"mesh-controller", "builder", EdgeBuiltBy},
|
|
{"mesh-tools", "builder", EdgeBuiltBy},
|
|
} {
|
|
if !has(want.From, want.To, want.Kind) {
|
|
t.Errorf("missing %+v in %+v", want, got)
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
}
|