Test the controller as one that judges no manifest, and reset the new-module waits each test leaves (ADR 0578)
Issue 1585: registration publishes the predefined list and judges nothing against it; a definition naming an installation is the indexer's judge's to refuse; a word the controller does not read is set aside and the module registered (0578 §6 changes 0501 §7); a refused verdict holds the module and names it. The process-wide list of new modules waiting is emptied at each take-in test's start and end, so no test reads another's. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -49,8 +49,10 @@ func TestABuildHeardIsRecordedAndRegistered(t *testing.T) {
|
|||||||
"env": map[string]any{"KC_HOSTNAME": "https://login.mesh-one.be"}}}})
|
"env": map[string]any{"KC_HOSTNAME": "https://login.mesh-one.be"}}}})
|
||||||
_, _, err = takeIn(ctx, open.inventory, link.BuildResult{
|
_, _, err = takeIn(ctx, open.inventory, link.BuildResult{
|
||||||
ID: "b-2", Repository: "/r", On: "anchor", Commit: "0123456789", Manifest: named})
|
ID: "b-2", Repository: "/r", On: "anchor", Commit: "0123456789", Manifest: named})
|
||||||
if err == nil || !strings.Contains(err.Error(), "does not register it") {
|
// Not registered from a source the catalogue does not build it from. That it names an installation is no longer the
|
||||||
t.Fatalf("a definition naming an installation was taken in: %v", err)
|
// controller's to judge (ADR 0578 §3): the indexer's judge refuses it (mesh-delivery-indexer judge's installation rows).
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "recorded and not registered") {
|
||||||
|
t.Fatalf("a definition from a source the catalogue does not build it from was taken in: %v", err)
|
||||||
}
|
}
|
||||||
if shelf, _ := open.inventory.Catalogue(ctx); shelf["idp"].Module != "" {
|
if shelf, _ := open.inventory.Catalogue(ctx); shelf["idp"].Module != "" {
|
||||||
t.Fatal("the refused module was registered anyway")
|
t.Fatal("the refused module was registered anyway")
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import (
|
|||||||
type builderOnTheBus struct {
|
type builderOnTheBus struct {
|
||||||
js *broker.JetStream
|
js *broker.JetStream
|
||||||
builds, modules, manifests jetstream.KeyValue
|
builds, modules, manifests jetstream.KeyValue
|
||||||
|
// verdicts is the indexer's state of verdicts on manifests (ADR 0578 §1).
|
||||||
|
verdicts jetstream.KeyValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func aBuilderOnTheBus(t *testing.T) builderOnTheBus {
|
func aBuilderOnTheBus(t *testing.T) builderOnTheBus {
|
||||||
@@ -40,6 +42,7 @@ func aBuilderOnTheBus(t *testing.T) builderOnTheBus {
|
|||||||
{&b.builds, sdkdelivery.BuilderBuildsBucket, sdkdelivery.BuilderBuildsHistory},
|
{&b.builds, sdkdelivery.BuilderBuildsBucket, sdkdelivery.BuilderBuildsHistory},
|
||||||
{&b.modules, sdkdelivery.ModulesBucket, sdkdelivery.ModulesHistory},
|
{&b.modules, sdkdelivery.ModulesBucket, sdkdelivery.ModulesHistory},
|
||||||
{&b.manifests, sdkdelivery.ManifestsBucket, 1},
|
{&b.manifests, sdkdelivery.ManifestsBucket, 1},
|
||||||
|
{&b.verdicts, sdkdelivery.VerdictsBucket, sdkdelivery.VerdictsHistory},
|
||||||
} {
|
} {
|
||||||
kv, err := api.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: c.bucket, History: c.history})
|
kv, err := api.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: c.bucket, History: c.history})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
// one module's directory and touches nothing else the mesh holds: nothing asks its first build, it opens no plan, and
|
// one module's directory and touches nothing else the mesh holds: nothing asks its first build, it opens no plan, and
|
||||||
// the module is registered from the builder's first done key of it, assigned nowhere, with no build request written.
|
// the module is registered from the builder's first done key of it, assigned nowhere, with no build request written.
|
||||||
func TestAMergeAddingAModuleEndsRegisteredFromItsKeyAndAsksNoBuild(t *testing.T) {
|
func TestAMergeAddingAModuleEndsRegisteredFromItsKeyAndAsksNoBuild(t *testing.T) {
|
||||||
|
withNoNewModuleWaits(t)
|
||||||
open := aMesh(t)
|
open := aMesh(t)
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
inv := open.inventory
|
inv := open.inventory
|
||||||
@@ -66,9 +67,11 @@ func TestAMergeAddingAModuleEndsRegisteredFromItsKeyAndAsksNoBuild(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADR 0501 §7: a new module whose manifest uses a word this controller does not read is named waiting at once, never
|
// ADR 0578 §6 (which changes ADR 0501 §7): a new module whose manifest uses a word this controller does not read is
|
||||||
// failed, and registered from the same key, with no second build, when a controller that reads it looks.
|
// registered at once, the word set aside, never failed and with no second build; one whose manifest the indexer's judge
|
||||||
func TestANewModuleWithAnUnreadWordWaitsForTheControllerThatReadsIt(t *testing.T) {
|
// refused is held, named in the indexer's words, and never registered (ADR 0578 §3).
|
||||||
|
func TestANewModuleWithAnUnreadWordIsRegisteredAndARefusedOneIsHeld(t *testing.T) {
|
||||||
|
withNoNewModuleWaits(t)
|
||||||
open := aMesh(t)
|
open := aMesh(t)
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
inv := open.inventory
|
inv := open.inventory
|
||||||
@@ -94,12 +97,38 @@ func TestANewModuleWithAnUnreadWordWaitsForTheControllerThatReadsIt(t *testing.T
|
|||||||
}
|
}
|
||||||
b.built(t, "watchman", "1", fp, digest, sdkdelivery.BuildDone)
|
b.built(t, "watchman", "1", fp, digest, sdkdelivery.BuildDone)
|
||||||
takeNewModules(ctx, open, "watchman")
|
takeNewModules(ctx, open, "watchman")
|
||||||
if _, known := inventoryHolds(t, inv, "watchman"); known {
|
if _, known := inventoryHolds(t, inv, "watchman"); !known {
|
||||||
t.Fatal("0501 §7: a manifest with an unread word was registered")
|
t.Fatalf("0578 §6: a manifest with a word set aside is not registered: %+v", newModuleObservations(time.Now()))
|
||||||
}
|
}
|
||||||
obs := newModuleObservations(time.Now())
|
// The indexer refused another: held, named, not registered.
|
||||||
if len(obs) != 1 || obs[0].Kind != kindNewModuleWaits || !strings.Contains(obs[0].Summary, `"not-a-word-yet"`) {
|
refusedFP := fpOf('9')
|
||||||
t.Fatalf("0501 §7: the module waiting for a reader of its word is named by %+v", obs)
|
refusedRaw := `{"module":"refusedmod","version":"1","build":{"artifacts":[{"name":"a","kind":"archive","from":"files"}]},` +
|
||||||
|
`"resources":[{"id":"a","type":"archive","path":"/opt/refusedmod","artifact":"a"}]}`
|
||||||
|
refusedDigest := sdkdelivery.ManifestDigest([]byte(refusedRaw))
|
||||||
|
value, _ = sdkdelivery.EncodeManifest(sdkdelivery.Manifest{Module: "refusedmod", JSON: refusedRaw})
|
||||||
|
if _, err := b.manifests.Put(ctx, refusedDigest, value); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
verdict, _ := sdkdelivery.EncodeManifestVerdict(sdkdelivery.ManifestVerdict{Module: "refusedmod", State: sdkdelivery.VerdictRefused,
|
||||||
|
Problems: []string{"it breaks a rule"}, Judge: "exe:1", At: time.Now()})
|
||||||
|
if _, err := b.verdicts.Put(ctx, refusedDigest, verdict); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
trunk, _ = sdkdelivery.EncodeModule(sdkdelivery.Module{FP: "src1:" + refusedFP[5:], FP2: refusedFP, Manifest: refusedDigest,
|
||||||
|
Repository: "novox/mesh-catalog", Commit: "5555555500000000", Path: "modules/refusedmod"})
|
||||||
|
if _, err := b.modules.Put(ctx, "refusedmod", trunk); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b.built(t, "refusedmod", "1", refusedFP, refusedDigest, sdkdelivery.BuildDone)
|
||||||
|
takeNewModules(ctx, open, "refusedmod")
|
||||||
|
if _, known := inventoryHolds(t, inv, "refusedmod"); known {
|
||||||
|
t.Fatal("0578 §3: a manifest the indexer refused was registered")
|
||||||
|
}
|
||||||
|
// Said at once in the controller's journal; a condition once it waits past the bound (the watchman raises the
|
||||||
|
// indexer's manifest-refused at once).
|
||||||
|
obs := newModuleObservations(time.Now().Add(newModuleWaitBound + time.Minute))
|
||||||
|
if len(obs) != 1 || obs[0].Kind != kindNewModuleWaits || !strings.Contains(obs[0].Summary, "refused the manifest of refusedmod: it breaks a rule") {
|
||||||
|
t.Fatalf("0578 §3: the refused module is not held and named: %+v", obs)
|
||||||
}
|
}
|
||||||
builds, _ := inv.Builds(ctx, "watchman", 5)
|
builds, _ := inv.Builds(ctx, "watchman", 5)
|
||||||
for _, b := range builds {
|
for _, b := range builds {
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ import (
|
|||||||
// ADR 0501 §6, §7 (issue 1851, A5b): the condition of a new module reads its key's take-in: one waiting for a controller
|
// ADR 0501 §6, §7 (issue 1851, A5b): the condition of a new module reads its key's take-in: one waiting for a controller
|
||||||
// that reads its manifest's word is named at once, one waiting for its key only past the bound, each reason said once.
|
// that reads its manifest's word is named at once, one waiting for its key only past the bound, each reason said once.
|
||||||
func TestANewModuleWaitingIsNamedByWhatItWaitsFor(t *testing.T) {
|
func TestANewModuleWaitingIsNamedByWhatItWaitsFor(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
withNoNewModuleWaits(t)
|
||||||
newModulesWaiting.Lock()
|
|
||||||
newModulesWaiting.by = map[string]newModuleWait{}
|
|
||||||
newModulesWaiting.Unlock()
|
|
||||||
})
|
|
||||||
word := `waits: watchman's manifest uses the word "key-ttl", which this controller does not read: it is registered when a controller that reads it runs`
|
word := `waits: watchman's manifest uses the word "key-ttl", which this controller does not read: it is registered when a controller that reads it runs`
|
||||||
sayNewModuleWait(newModuleWait{Module: "watchman", Commit: "c1", Why: word, Word: wordIn(word)})
|
sayNewModuleWait(newModuleWait{Module: "watchman", Commit: "c1", Why: word, Word: wordIn(word)})
|
||||||
sayNewModuleWait(newModuleWait{Module: "view", Commit: "c1", Why: "waits: the build of view at aaaa is building on ace"})
|
sayNewModuleWait(newModuleWait{Module: "view", Commit: "c1", Why: "waits: the build of view at aaaa is building on ace"})
|
||||||
@@ -31,3 +27,15 @@ func TestANewModuleWaitingIsNamedByWhatItWaitsFor(t *testing.T) {
|
|||||||
t.Fatalf("registered modules are still named: %+v", obs)
|
t.Fatalf("registered modules are still named: %+v", obs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// withNoNewModuleWaits empties the controller's list of new modules waiting, for a test, at its start and at its end: the
|
||||||
|
// list is the process's, so a test that leaves a module in it is read by the next.
|
||||||
|
func withNoNewModuleWaits(t *testing.T) {
|
||||||
|
reset := func() {
|
||||||
|
newModulesWaiting.Lock()
|
||||||
|
newModulesWaiting.by = map[string]newModuleWait{}
|
||||||
|
newModulesWaiting.Unlock()
|
||||||
|
}
|
||||||
|
reset()
|
||||||
|
t.Cleanup(reset)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import (
|
|||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt(t *testing.T) {
|
// Registration publishes the newest trunk's list of predefined keys. It judges no manifest against it (ADR 0578 §3): the
|
||||||
|
// indexer's judge does, with the list it keeps, and a fallback to a key not on it, or of another type, is refused there
|
||||||
|
// (mesh-delivery-indexer judge's tests).
|
||||||
|
func TestRegistrationPublishesTheNewestTrunkList(t *testing.T) {
|
||||||
withoutAPublishedList(t)
|
withoutAPublishedList(t)
|
||||||
open := aMesh(t)
|
open := aMesh(t)
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
@@ -28,22 +31,6 @@ func TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt(t
|
|||||||
t.Fatalf("the controller judges against %v, not the list published", got)
|
t.Fatalf("the controller judges against %v, not the list published", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
// One naming a key not on the list, or of another type, is refused and never enters the catalogue.
|
|
||||||
for i, c := range []struct{ manifest, says string }{
|
|
||||||
{notifierFallingBackTo("font-scale", "number"), "not on the list"},
|
|
||||||
{notifierFallingBackTo("font-size", "string"), "which is a number"},
|
|
||||||
} {
|
|
||||||
id := []string{"b-2", "b-3"}[i]
|
|
||||||
if _, _, err := takeIn(ctx, open.inventory, catalogueBuild(id, c.manifest, theFirstKeys, 10)); err == nil ||
|
|
||||||
!strings.Contains(err.Error(), c.says) {
|
|
||||||
t.Errorf("wanted the registration refused saying %q, got %v", c.says, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
src, err := open.inventory.SourceOf(ctx, "notifier")
|
|
||||||
if err != nil || src.BuiltFrom != strings.Repeat("1", 12) {
|
|
||||||
t.Fatalf("a refused build replaced the registered one: %+v %v", src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A newer build's list, which drops agent-managed-settings, replaces the published one.
|
// A newer build's list, which drops agent-managed-settings, replaces the published one.
|
||||||
smaller := `{"font-size": {"type": "number", "description": "points", "minimum": 6, "maximum": 72}}`
|
smaller := `{"font-size": {"type": "number", "description": "points", "minimum": 6, "maximum": 72}}`
|
||||||
if _, _, err := takeIn(ctx, open.inventory, catalogueBuild("b-4",
|
if _, _, err := takeIn(ctx, open.inventory, catalogueBuild("b-4",
|
||||||
@@ -60,7 +47,7 @@ func TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt(t
|
|||||||
t.Fatalf("an older build's list heard later replaced the newer one: %+v", kept)
|
t.Fatalf("an older build's list heard later replaced the newer one: %+v", kept)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A build of another repository publishes nothing, and its manifest is judged against the published list.
|
// A build of another repository publishes nothing.
|
||||||
other := catalogueBuild("b-6", notifierFallingBackTo("font-size", "number"), theFirstKeys, 99)
|
other := catalogueBuild("b-6", notifierFallingBackTo("font-size", "number"), theFirstKeys, 99)
|
||||||
other.Repository, other.Source.Repository = "http://forge.internal:20000/novox/notifier.git", "novox/notifier"
|
other.Repository, other.Source.Repository = "http://forge.internal:20000/novox/notifier.git", "novox/notifier"
|
||||||
other.Manifest = json.RawMessage(strings.Replace(notifierFallingBackTo("font-size", "number"), `"notifier"`,
|
other.Manifest = json.RawMessage(strings.Replace(notifierFallingBackTo("font-size", "number"), `"notifier"`,
|
||||||
@@ -71,9 +58,4 @@ func TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt(t
|
|||||||
if kept, _, _ = open.inventory.PredefinedKept(ctx); kept.Build != "b-4" {
|
if kept, _, _ = open.inventory.PredefinedKept(ctx); kept.Build != "b-4" {
|
||||||
t.Fatalf("a build of another repository published its list: %+v", kept)
|
t.Fatalf("a build of another repository published its list: %+v", kept)
|
||||||
}
|
}
|
||||||
other.ID, other.Manifest = "b-7", json.RawMessage(strings.Replace(notifierFallingBackTo("agent-managed-settings",
|
|
||||||
"number"), `"notifier"`, `"chimes"`, 1))
|
|
||||||
if _, _, err := takeIn(ctx, open.inventory, other); err == nil || !strings.Contains(err.Error(), "not on the list") {
|
|
||||||
t.Fatalf("a key only another repository's list holds was registered: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user