delivery.ManifestAccepted.EventID now names, for an event with no digest, the module and the event's revision (J3, ruling A5(d)), so the indexer drops its own left id. The revision is the modules state's revision of the value that left (write hands the revision it read), so two leaves of one module are two events; asked again by hand for a module no longer held it is 0, and the again suffix keeps it unique. Row in TestAMergeThatRemovesAModuleDeletesItsKey (failed first: Revision 0, id naming the last digest). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
168 lines
6.1 KiB
Go
168 lines
6.1 KiB
Go
package index
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
func twoModules(t *testing.T) (*Indexer, *memStore) {
|
|
t.Helper()
|
|
ix, mods := modulesIndexer(t)
|
|
if err := ix.Apply(context.Background(), mergedMsg(catalogueRepo, map[string]string{"modules/a": "t", "modules/b": "t"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, k := range []string{"a", "b"} {
|
|
if _, ok := mods.vals[k]; !ok {
|
|
t.Fatalf("setup: modules.%s not written", k)
|
|
}
|
|
}
|
|
return ix, mods
|
|
}
|
|
|
|
func TestAMergeThatRemovesAModuleDeletesItsKey(t *testing.T) { // 1900/AC1
|
|
ix, mods := twoModules(t)
|
|
// DLV-I9 (design 25 § 12.2): the module leaving the trunk is said as an empty manifest-accepted, under an id naming it.
|
|
var said []delivery.ManifestAccepted
|
|
var ids []string
|
|
ix.SayAccepted = func(_ context.Context, a delivery.ManifestAccepted, id string) error {
|
|
said, ids = append(said, a), append(ids, id)
|
|
return nil
|
|
}
|
|
defer func() {
|
|
var left []string
|
|
for i, a := range said {
|
|
if a.Digest == "" {
|
|
left = append(left, a.Module)
|
|
// Join item 3: the id is the event's own (delivery.ManifestAccepted.EventID), naming the module and the
|
|
// modules state's revision of the value that left, so two leaves of one module are two events.
|
|
if a.BusState == nil || len(a.BusState) != 0 || a.Revision == 0 || ids[i] != a.EventID() || !strings.Contains(ids[i], "-left-b-") {
|
|
t.Errorf("the empty manifest-accepted = %+v, id %s", a, ids[i])
|
|
}
|
|
}
|
|
}
|
|
if strings.Join(left, ",") != "b" {
|
|
t.Errorf("said empty for %v, want b alone", left)
|
|
}
|
|
}()
|
|
if err := ix.Apply(context.Background(), mergedMsg(catalogueRepo, map[string]string{"modules/a": "t2"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := mods.vals["b"]; ok {
|
|
t.Error("modules.b is still held after a merge whose trunk has no module b")
|
|
}
|
|
if _, ok := mods.vals["a"]; !ok {
|
|
t.Error("modules.a, which the trunk still holds, was deleted")
|
|
}
|
|
}
|
|
|
|
func TestAMergeLeavesTheModulesOfAnotherRepository(t *testing.T) { // 1900/AC1 (scope)
|
|
ix, mods := twoModules(t)
|
|
if err := ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/c": "t"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := mods.vals["a"]; !ok {
|
|
t.Error("a merge of another repository deleted modules.a")
|
|
}
|
|
if _, ok := mods.vals["b"]; !ok {
|
|
t.Error("a merge of another repository deleted modules.b")
|
|
}
|
|
}
|
|
|
|
func TestAMergeWithoutManifestsDeletesNothing(t *testing.T) { // 1900/AC1: older writers send none, which does not say the trunk is empty
|
|
ix, mods := twoModules(t)
|
|
f := mergedMsg(catalogueRepo, map[string]string{"modules/a": "t2"})
|
|
f.Manifests = nil
|
|
if err := ix.Apply(context.Background(), f); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := mods.vals["b"]; !ok {
|
|
t.Error("a merge message with no manifests deleted modules.b")
|
|
}
|
|
}
|
|
|
|
func TestAPathWithoutAModuleJSONIsNeverAModule(t *testing.T) { // 1900/AC2: testdata such as fixture-build
|
|
ix, mods := twoModules(t)
|
|
f := mergedMsg(catalogueRepo, map[string]string{"modules/a": "t2"})
|
|
f.Trees["testdata/fixture-build"] = "t" // a directory in the tree whose module.json the message does not carry
|
|
if err := ix.Apply(context.Background(), f); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := mods.vals["fixture-build"]; ok {
|
|
t.Error("modules.fixture-build written for a path without a module.json")
|
|
}
|
|
}
|
|
|
|
func TestStartingDeletesTheKeysTheTrunkDoesNotHold(t *testing.T) { // 1900/AC3
|
|
ix, mods := twoModules(t)
|
|
ctx := context.Background()
|
|
if err := ix.ApplySource(ctx, 3, sourceOf(catalogueRepo, merge)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var asked string
|
|
ix.Trunk = func(_ context.Context, repository, sha string) ([]string, error) {
|
|
asked = repository + "@" + sha
|
|
return []string{"modules/a"}, nil
|
|
}
|
|
ix.Contains = func(context.Context, string, string, string) (bool, error) { return true, nil }
|
|
if err := ix.Reconcile(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if asked != catalogueRepo+"@"+merge {
|
|
t.Errorf("the trunk was asked as %q, want the source's head of the repository", asked)
|
|
}
|
|
if _, ok := mods.vals["b"]; ok {
|
|
t.Error("modules.b is still held though the trunk holds no module at modules/b")
|
|
}
|
|
if _, ok := mods.vals["a"]; !ok {
|
|
t.Error("modules.a was deleted")
|
|
}
|
|
}
|
|
|
|
func TestStartingDeletesNothingWhenTheTrunkCannotBeRead(t *testing.T) { // 1900/AC3: failure is loud, never a mass delete
|
|
ix, mods := twoModules(t)
|
|
ctx := context.Background()
|
|
if err := ix.ApplySource(ctx, 3, sourceOf(catalogueRepo, merge)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for name, trunk := range map[string]func(context.Context, string, string) ([]string, error){
|
|
"an error": func(context.Context, string, string) ([]string, error) { return nil, errors.New("forge down") },
|
|
"no module at all": func(context.Context, string, string) ([]string, error) { return nil, nil },
|
|
} {
|
|
ix.Trunk = trunk
|
|
_ = ix.Reconcile(ctx)
|
|
if len(mods.vals) != 2 {
|
|
t.Errorf("%s: %d keys held, want 2 untouched", name, len(mods.vals))
|
|
}
|
|
}
|
|
_ = delivery.RootPath
|
|
}
|
|
|
|
func TestAKeyWrittenAheadOfTheSourceHeadStaysAndOneWithoutAPathIsSkipped(t *testing.T) { // 1900/AC3 (review): the source lags a merge already applied
|
|
ix, mods := twoModules(t) // written at the merge commit C
|
|
ctx := context.Background()
|
|
const older = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
|
if err := ix.ApplySource(ctx, 3, sourceOf(catalogueRepo, older)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mods.put("nopath", mustEncode(t, delivery.Module{Repository: catalogueRepo, Commit: older, None2: "x", Manifest: strings.Repeat("a", 64)}))
|
|
ix.Trunk = func(context.Context, string, string) ([]string, error) { return []string{"modules/a"}, nil }
|
|
ix.Contains = func(_ context.Context, _, commit, in string) (bool, error) { return commit == in, nil } // C is not in B
|
|
if err := ix.Reconcile(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, k := range []string{"a", "b", "nopath"} {
|
|
if _, ok := mods.vals[k]; !ok {
|
|
t.Errorf("modules.%s was deleted", k)
|
|
}
|
|
}
|
|
ix.Contains = func(context.Context, string, string, string) (bool, error) { return false, errors.New("forge down") }
|
|
_ = ix.Reconcile(ctx)
|
|
if _, ok := mods.vals["b"]; !ok {
|
|
t.Error("modules.b deleted though whether its commit is in the head cannot be read")
|
|
}
|
|
}
|