While the mesh-delivery seat has a holder on record, a merge that moves no core module opens its walk and asks nothing until mesh-delivery or a person says go; nothing of it is registered before its turn, so no other send carries it. The controller keeps the planner, the gate, sending and the walk, and gains the verbs the owner asks with: delivery-plan, -order, -check (a group composed as one future state), deliver, delivery-stop, delivery-walks; every walk kept is said as plan-moved.
338 lines
15 KiB
Go
338 lines
15 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0110: a seat is a module assignment from a closed set.
|
|
|
|
// The set is closed, and changing it is a decision.
|
|
//
|
|
// **The count is asserted, and every entry names the record that made it a seat**, so the next
|
|
// person changing the set finds the argument rather than a number to edit — the pattern the host's
|
|
// vocabulary test follows. If this fails because a seat was added, the fix is a record in novox/hq
|
|
// and a row in to-be 26, not a new number here.
|
|
func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
|
|
// A seat a later record extends names both, "novox/hq ADR 0166, ADR 0207": the one that defined
|
|
// it and the one that seeded it.
|
|
record := regexp.MustCompile(`^novox/hq ADR \d{4}(, ADR \d{4})*$`)
|
|
seen := map[string]bool{}
|
|
delivered := map[string]string{}
|
|
for _, s := range Seats() {
|
|
if seen[s.Name] {
|
|
t.Errorf("%s is in the set twice", s.Name)
|
|
}
|
|
seen[s.Name] = true
|
|
if !record.MatchString(s.Decision) {
|
|
t.Errorf("%s names %q as its decision; every seat names the record that made it one",
|
|
s.Name, s.Decision)
|
|
}
|
|
switch s.Scope {
|
|
case ScopeNode, ScopeSite, ScopeMesh:
|
|
default:
|
|
t.Errorf("%s is held per %q, which is not a scope", s.Name, s.Scope)
|
|
}
|
|
if s.Delivers != "" {
|
|
// Two seats answering for one provision would put the question "which one?" back,
|
|
// which is the question a seat exists to answer.
|
|
if other, twice := delivered[s.Delivers]; twice {
|
|
t.Errorf("%s and %s both deliver %q", other, s.Name, s.Delivers)
|
|
}
|
|
delivered[s.Delivers] = s.Name
|
|
}
|
|
}
|
|
// Thirty-seven with mesh-delivery (novox/hq ADR 0239); thirty-six since node-resolver-config retired
|
|
// into node-uplink (novox/hq ADR 0223); thirty-seven
|
|
// since the retired node-dns-resolver went (novox/hq ADR 0220); thirty-eight with
|
|
// node-backup (novox/hq ADR 0214); thirty-seven with node-message-bus (novox/hq ADR 0215);
|
|
// thirty-six with mesh-dns-resolver (novox/hq ADR 0194) and node-hosts-file (ADR 0199, now
|
|
// node-hostname); thirty-four
|
|
// with node-hotkeys (ADR 0212); thirty-three with node-power (ADR 0211); thirty-two since the
|
|
// graphical session's eleven (ADR 0208); twenty-one with node-package-manager and
|
|
// node-container-runtime (ADR 0207); nineteen with node-environment and node-login-shell (ADR 0203,
|
|
// ADR 0204); seventeen with node-build-agent (ADR 0190). One fewer once the retired
|
|
// mesh-build-machine row goes, when no registered manifest claims it.
|
|
if len(Seats()) != 37 {
|
|
t.Errorf("the mesh defines %d seats rather than 37; the set is closed, so a change here is "+
|
|
"a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames())
|
|
}
|
|
}
|
|
|
|
// novox/hq ADR 0117: a machine's uplink is a seat, held per machine, and delivers nothing.
|
|
//
|
|
// **Nothing, because nothing may be required of it.** A holder only keeps its network manager from
|
|
// contradicting the mesh; a requirement resolving to it would make the manager the mesh's answer
|
|
// for something, and the manager's link is the one thing the mesh must never be able to break.
|
|
func TestTheUplinkIsANodeSeatThatDeliversNothing(t *testing.T) {
|
|
seat, known := SeatNamed("node-uplink")
|
|
if !known {
|
|
t.Fatalf("the uplink is not a seat; the seats are: %s", seatNames())
|
|
}
|
|
if seat.Scope != ScopeNode || seat.Delivers != "" || seat.Decision != "novox/hq ADR 0117" {
|
|
t.Fatalf("the uplink is %+v, not a node seat delivering nothing by ADR 0117", seat)
|
|
}
|
|
// And a manager's module can hold it without providing anything.
|
|
raw := []byte(`{"module":"networkmanager","version":"1","claims":[{"name":"node-uplink","scope":"node"}]}`)
|
|
if _, err := ParseManifest(raw); err != nil {
|
|
t.Fatalf("a network manager's module could not hold the uplink: %v", err)
|
|
}
|
|
}
|
|
|
|
func claimed(claims string) []byte {
|
|
return []byte(`{"module":"thing","version":"1","provides":[{"name":"npm-package-registry","scope":"mesh"}],"claims":` + claims + `}`)
|
|
}
|
|
|
|
// **The refusal moved, it did not go** (novox/hq ADR 0118, superseding 0110). A module may now
|
|
// declare its own seats, so whether a claimed seat exists is a fact about the *catalogue* and not
|
|
// about the manifest in front of the parser: a claim on a seat another registered module declares
|
|
// is perfectly good, and the parser cannot tell the two cases apart. So the parser accepts it and
|
|
// registration refuses it — the same guarantee, at the same moment work would otherwise start,
|
|
// from a set nobody maintains by hand.
|
|
func TestAClaimOnASeatNobodyDeclaresIsRefusedAtRegistration(t *testing.T) {
|
|
m, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
|
|
if err != nil {
|
|
t.Fatalf("the parser judged a claim it cannot judge alone: %v", err)
|
|
}
|
|
|
|
problems := CatalogueProblems(Shelf{m.Module: m})
|
|
if len(problems) == 0 {
|
|
t.Fatal("a module invented a seat by claiming it, and registration allowed it")
|
|
}
|
|
joined := strings.Join(problems, "; ")
|
|
if !strings.Contains(joined, "the-anything") || !strings.Contains(joined, "no module declares") {
|
|
t.Fatalf("the refusal does not say the seat is nobody's: %v", problems)
|
|
}
|
|
}
|
|
|
|
// And the same claim is fine once something declares that seat, which is the case the parser
|
|
// could not have distinguished.
|
|
func TestAClaimOnASeatAnotherModuleDeclaresIsAccepted(t *testing.T) {
|
|
claimant, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
declarer := Manifest{Module: "someone", DefinesSeats: []SeatDeclaration{
|
|
{Name: "the-anything", Scope: ScopeNode, Accepts: []string{"work"}},
|
|
}}
|
|
if problems := CatalogueProblems(Shelf{claimant.Module: claimant, "someone": declarer}); len(problems) != 0 {
|
|
t.Fatalf("a claim on a declared seat was refused: %v", problems)
|
|
}
|
|
}
|
|
|
|
// A module may define its own seat and claim it — the mesh enforces exclusivity without knowing
|
|
// what it means (novox/hq ADR 0121). But it may not define one in the mesh's own namespace.
|
|
func TestAModuleDefinesAndClaimsItsOwnSeat(t *testing.T) {
|
|
ok := []byte(`{"module":"showcase","version":"1","seats":[{"name":"the-showcase","scope":"node"}],` +
|
|
`"claims":[{"name":"the-showcase","scope":"node"}]}`)
|
|
if _, err := ParseManifest(ok); err != nil {
|
|
t.Fatalf("a module could not define and claim its own seat: %v", err)
|
|
}
|
|
// Claiming a name nobody defines is still refused — but **at registration, not here**: with
|
|
// seats declared by modules, a claim on a seat *another* module declares is good, and the
|
|
// parser cannot tell that from an invented name. See
|
|
// TestAClaimOnASeatNobodyDeclaresIsRefusedAtRegistration.
|
|
claimant, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
|
|
if err != nil {
|
|
t.Fatalf("the parser judged a claim it cannot judge alone: %v", err)
|
|
}
|
|
if len(CatalogueProblems(Shelf{claimant.Module: claimant})) == 0 {
|
|
t.Fatal("a module claimed a seat nobody defines")
|
|
}
|
|
// A module may not carve its seat out of the mesh's own namespace.
|
|
bad := []byte(`{"module":"x","version":"1","seats":[{"name":"node-mine","scope":"node"}],` +
|
|
`"claims":[{"name":"node-mine","scope":"node"}]}`)
|
|
if _, err := ParseManifest(bad); err == nil || !strings.Contains(err.Error(), "own namespace") {
|
|
t.Fatalf("a module defined a seat in the mesh's namespace and was not refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// **At registration, not in the parser** (novox/hq ADR 0122): a seat's scope is a property of the
|
|
// set, the set is the store's, and the parser also runs on a build machine that has no store.
|
|
func TestASeatClaimedAtAnotherScopeIsRefused(t *testing.T) {
|
|
m, err := ParseManifest(claimed(`[{"name":"npm-package-registry","scope":"node"}]`))
|
|
if err != nil {
|
|
t.Fatalf("the parser judged a scope it reads from data it may not have: %v", err)
|
|
}
|
|
got := strings.Join(CatalogueProblems(Shelf{m.Module: m}), "; ")
|
|
if !strings.Contains(got, "mesh seat") {
|
|
t.Fatalf("the refusal does not say which scope the seat is: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestADeliveringSeatIsOnlyHeldByAModuleThatProvides(t *testing.T) {
|
|
// Holding it makes the module the mesh's answer for the provision. A module that cannot answer
|
|
// would be the answer anyway, and every consumer would be sent to it.
|
|
// And refused at registration, where the seat set is the store's: what a seat delivers is
|
|
// data, so a compiled copy of it may not be what refuses a build (novox/hq ADR 0122).
|
|
raw := []byte(`{"module":"thing","version":"1","claims":[{"name":"git","scope":"mesh"}]}`)
|
|
m, err := ParseManifest(raw)
|
|
if err != nil {
|
|
t.Fatalf("the parser judged what a seat delivers: %v", err)
|
|
}
|
|
got := strings.Join(CatalogueProblems(Shelf{m.Module: m}), "; ")
|
|
if !strings.Contains(got, `does not provide "git"`) {
|
|
t.Fatalf("the refusal does not say what is missing: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAClaimThatIsMalformedIsRefusedOnceForThat(t *testing.T) {
|
|
// Not a second time for being unknown: one mistake, one line.
|
|
_, err := ParseManifest(claimed(`[{"name":"Not A Name","scope":"node"}]`))
|
|
if err == nil {
|
|
t.Fatal("a malformed claim was accepted")
|
|
}
|
|
if strings.Contains(err.Error(), "not a seat") {
|
|
t.Fatalf("a malformed claim was also called unknown: %v", err)
|
|
}
|
|
}
|
|
|
|
// Every module in use claims a seat in the set, so closing it refuses nothing that runs.
|
|
//
|
|
// Read from the catalogue beside this checkout and from this repository's own manifest, the two
|
|
// places a manifest lives (ADR 0069). The private-network module's manifest is composed in code,
|
|
// and its claim is checked where it is composed.
|
|
func TestEveryManifestInUseClaimsASeatTheMeshDefines(t *testing.T) {
|
|
paths, _ := filepath.Glob("../../../mesh-catalog/modules/*/module.json")
|
|
if len(paths) == 0 {
|
|
t.Skip("the catalogue is not beside this checkout")
|
|
}
|
|
paths = append(paths, "../../module.json")
|
|
var checked int
|
|
for _, path := range paths {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Leniently, so a manifest refused for something unrelated is not reported as a seat
|
|
// problem, and the seat check below is the only thing this test holds a module to.
|
|
var m Manifest
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
t.Fatalf("%s: %v", path, err)
|
|
}
|
|
for _, problem := range claimProblems(m) {
|
|
t.Errorf("%s: %s", path, problem)
|
|
}
|
|
checked += len(m.Claims)
|
|
}
|
|
if checked == 0 {
|
|
t.Fatal("no claims were checked, so this proved nothing")
|
|
}
|
|
}
|
|
|
|
// The holder of a seat answers among several providers.
|
|
|
|
func registryShelf() map[string]Manifest {
|
|
return shelf(
|
|
Manifest{Module: "gitea", Version: "1", Provides: FromAnywhere("npm-package-registry"),
|
|
Claims: []Claim{{Name: "npm-package-registry", Scope: ScopeMesh}}},
|
|
Manifest{Module: "verdaccio", Version: "1", Provides: FromAnywhere("npm-package-registry")},
|
|
Manifest{Module: "builder", Version: "1", Requires: []string{"npm-package-registry"}},
|
|
)
|
|
}
|
|
|
|
func twoRegistries() map[string][]Provider {
|
|
return map[string][]Provider{"npm-package-registry": {
|
|
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
|
|
{Node: "archive", At: "archive.internal", Module: "verdaccio"},
|
|
}}
|
|
}
|
|
|
|
func giteaHoldsTheSeat() []Held {
|
|
return []Held{{Claim: "npm-package-registry", Scope: ScopeMesh, Node: "anchor", Module: "gitea"}}
|
|
}
|
|
|
|
func TestTheSeatsHolderAnswersWhenSeveralProvide(t *testing.T) {
|
|
// The whole point: a second registry beside the holder harms nothing, and nobody pins.
|
|
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
|
|
World{Offered: twoRegistries(), Held: giteaHoldsTheSeat()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got.Needs) != 1 || got.Needs[0].From != "anchor" {
|
|
t.Fatalf("the seat's holder did not answer: %v", got.Needs)
|
|
}
|
|
}
|
|
|
|
func TestAPinStillWinsOverTheSeat(t *testing.T) {
|
|
// A consumer coupled to one provider's contents has said so, and the seat does not overrule it.
|
|
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
|
|
World{Offered: twoRegistries(), Held: giteaHoldsTheSeat(),
|
|
Pinned: map[string]Chosen{"npm-package-registry": {Node: "archive", Module: "verdaccio"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got.Needs) != 1 || got.Needs[0].From != "archive" {
|
|
t.Fatalf("the pin was overruled by the seat: %v", got.Needs)
|
|
}
|
|
}
|
|
|
|
func TestWithTheSeatUnheldSeveralProvidersAreStillRefused(t *testing.T) {
|
|
// No seat held is no choice made, and ADR 0009's rule stands: never guessed.
|
|
_, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
|
|
World{Offered: twoRegistries()})
|
|
if err == nil {
|
|
t.Fatal("one of two registries was picked with nobody holding the seat")
|
|
}
|
|
if !strings.Contains(err.Error(), "pin") {
|
|
t.Fatalf("the refusal does not say how to choose: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTheHolderIsTheModuleNotTheMachine(t *testing.T) {
|
|
// Two modules on one machine could provide the same thing; only the one holding the seat
|
|
// answers. A holder matched by node alone would send consumers to whichever came first.
|
|
providers := []Provider{
|
|
{Node: "anchor", At: "anchor.internal", Module: "verdaccio"},
|
|
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
|
|
}
|
|
holder, held := HolderAmong("npm-package-registry", providers, giteaHoldsTheSeat())
|
|
if !held || holder.Module != "gitea" {
|
|
t.Fatalf("the holder was not told apart from a neighbour: %+v", holder)
|
|
}
|
|
}
|
|
|
|
// The working set is loaded from the store, and an empty load never erases it (novox/hq ADR 0122).
|
|
func TestUseSeatsReplacesTheSetButNeverEmptiesIt(t *testing.T) {
|
|
before := Seats()
|
|
defer UseSeats(DefaultSeats()) // restore for other tests, whatever this leaves it as
|
|
|
|
// An empty load (store not seeded, or unreadable) leaves the compiled defaults in force.
|
|
UseSeats(nil)
|
|
if len(Seats()) != len(before) {
|
|
t.Fatalf("an empty load changed the set from %d to %d seats", len(before), len(Seats()))
|
|
}
|
|
// A non-empty load replaces it — this is how a rename in the store reaches the lookups.
|
|
UseSeats([]Seat{{Name: "node-firewall", Scope: ScopeNode, Decision: "novox/hq ADR 0122"}})
|
|
if _, known := SeatNamed("node-firewall"); !known {
|
|
t.Fatal("the loaded set did not replace the working set")
|
|
}
|
|
if len(Seats()) != 1 {
|
|
t.Fatalf("the working set is %d seats, not the one that was loaded", len(Seats()))
|
|
}
|
|
}
|
|
|
|
// A former name resolves to the seat it was renamed from (novox/hq ADR 0122), so a manifest's claim
|
|
// and a held record naming the old name break nothing after a rename.
|
|
func TestAFormerNameResolvesAfterARename(t *testing.T) {
|
|
defer func() { UseSeats(DefaultSeats()); UseAliases(nil) }()
|
|
UseSeats([]Seat{{Name: "git", Scope: ScopeMesh, Delivers: "git", Decision: "novox/hq ADR 0121"}})
|
|
UseAliases(map[string]string{"git": "git"})
|
|
|
|
// The old name resolves to the renamed seat.
|
|
if s, ok := SeatNamed("git"); !ok || s.Name != "git" {
|
|
t.Fatalf("the former name did not resolve to the renamed seat: %+v ok=%v", s, ok)
|
|
}
|
|
// And a holder recorded under the old name is still found for the provision the seat delivers.
|
|
providers := []Provider{{Node: "anchor", At: "anchor.internal", Module: "gitea"}}
|
|
held := []Held{{Claim: "git", Scope: ScopeMesh, Node: "anchor", Module: "gitea"}}
|
|
holder, found := HolderAmong("git", providers, held)
|
|
if !found || holder.Module != "gitea" {
|
|
t.Fatalf("the holder recorded under the former name was not matched: %+v found=%v", holder, found)
|
|
}
|
|
}
|