mesh/merge-gate head 2b1eeb7cd53e merged into trunk f29d193ddbc1 — pass: builds mesh-controller → novox; no bus step; every machine composes with the chang…
mesh/repo-check head 2b1eeb7cd53e merged into trunk f29d193ddbc1 — pass: its merge-check.sh passed
mesh/delivery delivered
161 lines
6.4 KiB
Go
161 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// A world reader that fails the first `failing` reads, as the mesh does while the forge's seat holder changes in
|
|
// the plan's tier (novox/hq issue 1277), then answers with the forge held.
|
|
func aWorldFailing(failing int) (func(context.Context) (catalogue.World, error), *int) {
|
|
reads := 0
|
|
return func(context.Context) (catalogue.World, error) {
|
|
reads++
|
|
if reads <= failing {
|
|
return catalogue.World{}, errors.New("the mesh cannot be read: nothing serves")
|
|
}
|
|
return forgeHolding(float64(20000)), nil
|
|
}, &reads
|
|
}
|
|
|
|
const aRepoOnTheForge = "http://anchor.internal:20000/novox/mesh-delivery.git"
|
|
|
|
// 1277/AC1: a base that could not be read is not remembered as known.
|
|
func TestAnUnreadForgeAddressIsNotKeptAsKnown(t *testing.T) {
|
|
read, _ := aWorldFailing(1)
|
|
clock := time.Now()
|
|
forms := sourceFormsRead(context.Background(), func() time.Time { return clock }, read)
|
|
if _, _, ok := forms.onTheForge(aRepoOnTheForge, ""); ok {
|
|
t.Fatal("1277/AC1: the source was on the forge with the address unread")
|
|
}
|
|
clock = clock.Add(failedKept) // the failure is kept only for a moment, not as a known empty base
|
|
owner, name, ok := forms.onTheForge(aRepoOnTheForge, "")
|
|
if !ok || owner != "novox" || name != "mesh-delivery" {
|
|
t.Fatalf("1277/AC1: the second ask answered %q %q %v, not the owner and name: the empty base was kept", owner, name, ok)
|
|
}
|
|
}
|
|
|
|
type aMeshRegisteredFromTheURL struct {
|
|
anEmptyMesh
|
|
from string
|
|
}
|
|
|
|
func (m *aMeshRegisteredFromTheURL) SourceOf(context.Context, string) (inventory.Source, error) {
|
|
return inventory.Source{Repository: m.from}, nil
|
|
}
|
|
|
|
// The wait on a clock of its own, in place of the registration's.
|
|
func withFakeAddressWait(t *testing.T) *[]time.Duration {
|
|
w, paused := aFakeWait(60 * time.Second)
|
|
w.first, w.most = 5*time.Second, 5*time.Second
|
|
was := forgeAddressWait
|
|
forgeAddressWait = w
|
|
t.Cleanup(func() { forgeAddressWait = was })
|
|
return paused
|
|
}
|
|
|
|
func registerFrom(t *testing.T, forms *sourceForms) (int64, error) {
|
|
mesh := &aMeshRegisteredFromTheURL{anEmptyMesh{asked: true,
|
|
request: inventory.BuildRequest{ID: "build-1", Repository: aRepoOnTheForge, For: "build"}}, aRepoOnTheForge}
|
|
return mayRegisterIn(t.Context(), mesh, forms, "mesh-delivery-view", inventory.Source{Repository: aRepoOnTheForge},
|
|
"build-1", "", "main")
|
|
}
|
|
|
|
// 1277/AC2: an address unread twice, then read, is waited out and the forge is asked.
|
|
func TestAForgeAddressUnreadForAMomentIsWaitedOut(t *testing.T) {
|
|
paused := withFakeAddressWait(t)
|
|
read, reads := aWorldFailing(2)
|
|
id, err := registerFrom(t, sourceFormsRead(context.Background(), time.Now, read))
|
|
if err != nil || id == 0 {
|
|
t.Fatalf("1277/AC2: the registration was refused (id %d): %v", id, err)
|
|
}
|
|
if *reads != 3 || len(*paused) != 2 {
|
|
t.Fatalf("1277/AC2: %d reads and %d pauses, want 3 and 2", *reads, len(*paused))
|
|
}
|
|
}
|
|
|
|
// 1277/AC3: an address unread for the whole bound is said so, with the read's error.
|
|
func TestAForgeAddressUnreadForTheWholeBoundIsSaidSo(t *testing.T) {
|
|
paused := withFakeAddressWait(t)
|
|
read, _ := aWorldFailing(1 << 30)
|
|
_, err := registerFrom(t, sourceFormsRead(context.Background(), time.Now, read))
|
|
if !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("1277/AC3: not refused: %v", err)
|
|
}
|
|
for _, want := range []string{"the forge's address could not be read", "nothing serves"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("1277/AC3: the refusal lacks %q: %v", want, err)
|
|
}
|
|
}
|
|
if strings.Contains(err.Error(), "not on the mesh's forge") {
|
|
t.Errorf("1277/AC3: the refusal says the source is not on the forge: %v", err)
|
|
}
|
|
var total time.Duration
|
|
for _, d := range *paused {
|
|
total += d
|
|
}
|
|
if total > 60*time.Second || total < 50*time.Second {
|
|
t.Errorf("1277/AC3: waited %s, want about the 60 s bound", total)
|
|
}
|
|
}
|
|
|
|
// 1277/AC4: a known address and a URL on another host is still not on the forge.
|
|
func TestAKnownAddressAndAnotherHostIsStillNotOnTheForge(t *testing.T) {
|
|
withFakeAddressWait(t)
|
|
read, _ := aWorldFailing(0)
|
|
const elsewhere = "http://elsewhere.example/x/y.git"
|
|
mesh := &aMeshRegisteredFromTheURL{anEmptyMesh{asked: true, request: inventory.BuildRequest{ID: "build-1", For: "build"}}, elsewhere}
|
|
_, err := mayRegisterIn(t.Context(), mesh, sourceFormsRead(context.Background(), time.Now, read), "mesh-delivery-view",
|
|
inventory.Source{Repository: elsewhere}, "build-1", "", "main")
|
|
if !errors.Is(err, errNotItsSource) || !strings.Contains(err.Error(), "is not on the mesh's forge") {
|
|
t.Fatalf("1277/AC4: the refusal changed: %v", err)
|
|
}
|
|
}
|
|
|
|
// The registration holds the controller's one receive loop: the wait for the forge's address and the wait for its
|
|
// answer together keep to one bound, not one each (novox/hq issue 1277, review F1).
|
|
func TestTheTwoWaitsOfARegistrationKeepToOneBound(t *testing.T) {
|
|
w, paused := aFakeWait(60 * time.Second)
|
|
address, answer := w, w
|
|
address.first, address.most = 5*time.Second, 5*time.Second
|
|
wasAddress, wasWait, wasAsk := forgeAddressWait, forgeWait, askTheForge
|
|
forgeAddressWait, forgeWait = address, answer
|
|
askTheForge = func(ctx context.Context, owner, repo, _ string) (forgeFacts, error) {
|
|
return askedWithinBudget(ctx, owner+"/"+repo, func(context.Context) (forgeFacts, error) {
|
|
return forgeFacts{}, notServed()
|
|
})
|
|
}
|
|
t.Cleanup(func() { forgeAddressWait, forgeWait, askTheForge = wasAddress, wasWait, wasAsk })
|
|
read, _ := aWorldFailing(6) // the address comes back after 30 s of waiting; the forge then never answers
|
|
_, err := registerFrom(t, sourceFormsRead(context.Background(), time.Now, read))
|
|
if !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("not refused: %v", err)
|
|
}
|
|
var total time.Duration
|
|
for _, d := range *paused {
|
|
total += d
|
|
}
|
|
if total > 60*time.Second {
|
|
t.Fatalf("1277 review F1: the registration waited %s in all, past the one 60 s bound", total)
|
|
}
|
|
}
|
|
|
|
// An unreadable address is kept for a moment, so the callers that loop over every catalogue entry compose the mesh
|
|
// once, not once each (novox/hq issue 1277, review F2).
|
|
func TestAnUnreadableAddressIsComposedOnceForACatalogueLoop(t *testing.T) {
|
|
read, reads := aWorldFailing(1 << 30)
|
|
forms := sourceFormsRead(context.Background(), time.Now, read)
|
|
for range 180 {
|
|
forms.base(gitSeat)
|
|
}
|
|
if *reads != 1 {
|
|
t.Fatalf("1277 review F2: the mesh was composed %d times for 180 asks of the address, want once", *reads)
|
|
}
|
|
}
|