The controller asks no build of anybody (ADR 0501 §5, ADR 0525 clause 6, ADR 0503 §3). Every module of a walk is taken from the builder's done key; a key not done yet waits, named in the plan, and never fails it. - C2b/C2c: internal/builder and cmd/mesh-controller's asks go (askABuild, onlyTheBuildersOwn, buildOneAsked, takeIn from an outcome, buildAgentsOwn, the builder's say and its fallback, the daemon hearing an outcome). The server follows the retired seats' built subjects only to let go of them, because the installer's carried user list names them. - C2d: replay, queue, cancel, clear, kill, pause, resume, delivery-check, check-here and merge-gate are refused by name. - C3: the check queues (CHECKS_READY, CHECKS_OTHER) and their workers, the checks-running and plans-building buckets and their grants, the build queue and its hold (plans_building.go, queue.go, link/holds.go, builds_nats.go, checks_nats.go). - C4: merge_gate*.go, the pull request's repo-check path, the Checked event and its layers; a pull request asks the controller nothing but to tell mesh-config what its head declares. - C5: the builds bucket is written only by key_takein. - C10 (help and refusals): the terminal's help lists only what is served. Integration tests of the plan's tiers, sends, stops and retries now run on a stand-in builder with a done key for every module (walk_keys_scaffold_integration_test.go); the tests of the asks, the queue, the merge gate and the replays are deleted.
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},
|
|
registrationAsk{Build: "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}, registrationAsk{Build: "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)
|
|
}
|
|
}
|