stage 8 of 8 for issue 1389: the equality test's integration part goes

This commit is contained in:
2026-10-20 11:17:16 +00:00
parent 382bc25b18
commit 198be8217c
@@ -1,310 +0,0 @@
//go:build integration
package main
import (
"context"
"flag"
"fmt"
"os"
"sort"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/compose"
snapshot "git.novox.be/novox/mesh-sdk/go/facts"
)
// 1384: nothing shows the controller's composition and mesh-sdk's compose every node alike on the live facts. This test is
// that proof (novox/hq ADR 0453 clause 3): it reads the facts snapshot MESH_FACTS names (the live one in every check and
// every try, the build seat gives it), else one captured in testdata/facts-equal.json (taken with `facts show` and
// scrubbed as the snapshot is), else the snapshot of a small mesh of its own; raises it into throwaway stores as the merge
// gate does; and composes every machine both ways.
//
// It runs only when named (a try with this test's name as the tests pattern); a whole-suite run skips it.
//
// (a) Foreseeing on the fresh store: the same error or none, the same validator problems, LeftOut and its reasons, Withheld,
// Unbound, Unplaced, HandedOnly and the own secrets named foreseen; and the ports mesh-sdk names foreseen are the ones the
// controller allocates when it sends.
// (b) the send: on two stores raised from the same snapshot, the controller's Allocating composition of every machine, and the
// adapter's compose, perform and compose again; the same ports, pair credentials and own secrets were made, by key.
// (c) after (b), Reading on one store, both ways: the declaration byte for byte.
//
// It sends nothing and opens no store but the throwaway ones. A difference names the machine, the comparison and the first
// field that differs; a machine both paths refuse alike is said, not failed. The test is deleted with the controller's
// composition (issue 1389).
func TestEveryMachineComposesAlikeBothWays(t *testing.T) {
// It is slow (it composes every machine of the live mesh several times each way) and is deleted with the controller's
// composition (issue 1389), so it runs only when asked for by name: a try or a `go test -run` that names it. A merge check
// runs every test and does not.
if run := flag.Lookup("test.run"); run == nil || !strings.Contains(run.Value.String(), "TestEveryMachineComposesAlikeBothWays") {
t.Skip("runs only when named: a try with the tests pattern TestEveryMachineComposesAlikeBothWays")
}
ctx := t.Context()
admin := os.Getenv("MESH_TEST_POSTGRES")
if admin == "" {
t.Skip("no throwaway PostgreSQL (MESH_TEST_POSTGRES) to raise the mesh in")
}
started := time.Now()
phase := func(what string) { t.Logf("[%s] %s", time.Since(started).Round(time.Second), what) }
f, source := equalityFacts(t)
if len(f.Machines) < 1 {
t.Fatalf("the facts (%s) hold no machine, so nothing was composed and nothing is proved", source)
}
t.Logf("the facts of %s (%s): %d machine(s)", f.Taken.Format("2006-01-02 15:04:05"), source, len(f.Machines))
shelf, err := shelfOfFacts(f)
if err != nil {
t.Fatal(err)
}
sources := sourcesOfFacts(f)
// Three stores, raised from the same snapshot, one after the other: (a) both ways on a fresh store, (b) and (c) the
// controller's sends and readings, (b) mesh-sdk's sends. They cannot run together: throwawayStores finds its databases by
// the process environment (and a store opened later finds them there too), so a second one raised at the same time takes
// the first one's connection from under it (the live try: database gate_inventory_... does not exist).
var (
oldForeseen, sdkForeseen = map[string]composedBoth{}, map[string]composedBoth{}
foreseenPorts = map[string][]string{}
readOld, readSDK = map[string]composedBoth{}, map[string]composedBoth{}
oldMade, sdkMade made
oldRefused = map[string]string{}
sendRefused = map[string]string{}
errA, errOld, errSDK error
)
func() { // (a) Foreseeing on the fresh store.
phase("(a) starts: raising a fresh store")
fresh, drop, err := throwawayStores(ctx, admin)
if err != nil {
errA = err
return
}
defer drop()
if _, err := raiseFromFacts(ctx, fresh, f, shelf, sources); err != nil {
errA = err
return
}
// mesh-sdk's composition first: it writes nothing, where the controller's makes every pair credential on the way. The
// Input is read once for all of its machines.
phase("(a) the store is raised; mesh-sdk's Foreseeing starts")
cached := withInputCache(ctx)
for _, m := range f.Machines {
in, err := inputFor(cached, fresh)
if err != nil {
errA = fmt.Errorf("%s: the adapter cannot read the mesh: %w", m.Name, err)
return
}
d, problems, cerr := compose.ComposeIn(in.Input, m.Name, compose.Foreseeing)
if cerr == nil {
foreseenPorts[m.Name] = append([]string(nil), d.ForeseenPorts...)
sort.Strings(foreseenPorts[m.Name])
}
sdkForeseen[m.Name] = capturedFrom(sendableOf(d), problems, cerr)
}
phase("(a) mesh-sdk's Foreseeing of every machine")
gens, gensErr := generators(ctx, fresh)
for _, m := range f.Machines {
if gensErr != nil {
oldForeseen[m.Name] = composedBoth{Err: gensErr.Error()}
continue
}
d, problems, err := composedAndValidated(ctx, fresh, m.Name, gens, Foreseeing)
oldForeseen[m.Name] = capturedFrom(d, problems, err)
}
phase("(a) the controller's Foreseeing of every machine")
}()
if errA != nil {
t.Fatal(errA)
}
func() { // (b) and (c): the controller's send of every machine, then both ways of reading.
phase("(b) starts: the controller's send, raising a store")
oldStore, drop, err := throwawayStores(ctx, admin)
if err != nil {
errOld = err
return
}
defer drop()
if _, err := raiseFromFacts(ctx, oldStore, f, shelf, sources); err != nil {
errOld = err
return
}
oldGens, oldGensErr := generators(ctx, oldStore)
for _, m := range f.Machines {
if oldGensErr != nil {
oldRefused[m.Name] = oldGensErr.Error()
continue
}
if _, _, err := composedAndValidated(ctx, oldStore, m.Name, oldGens, Allocating); err != nil {
oldRefused[m.Name] = err.Error()
}
}
phase("(b) the controller's send of every machine")
if oldMade, err = madeIn(ctx, oldStore); err != nil {
errOld = err
return
}
cached := withInputCache(ctx)
for _, m := range f.Machines {
if oldGensErr != nil {
readOld[m.Name] = composedBoth{Err: oldGensErr.Error()}
} else {
d, problems, err := composedAndValidated(ctx, oldStore, m.Name, oldGens, Reading)
readOld[m.Name] = capturedFrom(d, problems, err)
}
d, problems, err := composeQuestion(cached, oldStore, m.Name, compose.Reading, true)
readSDK[m.Name] = capturedFrom(d, problems, err)
}
phase("(c) both ways of reading every machine after the controller's send")
}()
if errOld != nil {
t.Fatal(errOld)
}
func() { // (b) mesh-sdk's send of every machine.
phase("(b) starts: mesh-sdk's send, raising a store")
sdkStore, drop, err := throwawayStores(ctx, admin)
if err != nil {
errSDK = err
return
}
defer drop()
if _, err := raiseFromFacts(ctx, sdkStore, f, shelf, sources); err != nil {
errSDK = err
return
}
for _, m := range f.Machines {
if _, err := composeSend(ctx, sdkStore, m.Name); err != nil {
sendRefused[m.Name] = err.Error()
}
}
phase("(b) mesh-sdk's send of every machine")
sdkMade, errSDK = madeIn(ctx, sdkStore)
}()
if errSDK != nil {
t.Fatal(errSDK)
}
var failures []string
for _, m := range f.Machines {
failures = append(failures, bothWaysDiffer(m.Name, "(a) Foreseeing", oldForeseen[m.Name], sdkForeseen[m.Name], false)...)
failures = append(failures, bothWaysDiffer(m.Name, "(c) Reading after the send", readOld[m.Name], readSDK[m.Name], true)...)
}
// **The send must compose every machine both ways.** A machine either send refuses is no proof of anything: what the
// controller made on the way to refusing it is no send's, and a refusal alike says nothing about what a send of it makes.
// So it fails here, naming the machine and both refusals (a refusal alike in (a), the Foreseeing on a store that holds no
// secret a send made, is said and stays). A pass proves that every machine of the facts was sent and compared.
refused := map[string]bool{}
for _, m := range f.Machines {
why, oldNo := oldRefused[m.Name]
sdkWhy, sdkNo := sendRefused[m.Name]
if !oldNo && !sdkNo {
continue
}
refused[m.Name] = true
failures = append(failures, fmt.Sprintf("%s, (b) the send: it is not composed both ways, so nothing of it is compared: "+
"the controller's refusal %s, mesh-sdk's %s", m.Name, quoted(oneLine(why)), quoted(oneLine(sdkWhy))))
}
failures = append(failures, madeDiffer("(b) the send", oldMade, sdkMade, refused)...)
// (a)'s ports: those mesh-sdk names foreseen, for each machine, are the ones the controller's send allocated to it.
for _, m := range f.Machines {
if refused[m.Name] {
continue
}
var allocated []string
for _, p := range oldMade.Ports {
if node, rest, ok := strings.Cut(p, " "); ok && node == m.Name {
module, port, _ := strings.Cut(strings.SplitN(rest, "->", 2)[0], ":")
allocated = append(allocated, module+":"+port)
}
}
sort.Strings(allocated)
// A port the snapshot already holds is not allocated by a send, and one a module is given is not allocated either: the
// ones mesh-sdk names must all be among those the send allocated.
for _, want := range foreseenPorts[m.Name] {
found := false
for _, got := range allocated {
found = found || got == want
}
if !found {
failures = append(failures, fmt.Sprintf("%s, (a) Foreseeing: mesh-sdk names the port %s foreseen and the "+
"controller's send allocates none for it", m.Name, want))
}
}
}
for machine, why := range sendRefused {
t.Logf("%s: mesh-sdk's send of it is refused: %s", machine, why)
}
for machine, c := range oldForeseen {
if c.Err != "" {
t.Logf("%s: the controller refuses it, and mesh-sdk alike: %s", machine, oneLine(c.Err))
}
}
if len(failures) > 0 {
t.Fatalf("the two compositions differ on %d point(s):\n %s", len(failures), strings.Join(failures, "\n "))
}
t.Logf("%d machine(s) were sent and compose alike both ways", len(f.Machines))
}
// equalityFacts is the snapshot the comparison composes: MESH_FACTS, else the one captured in testdata, else the facts of a
// mesh of its own (the same shape, small). Source says which.
func equalityFacts(t *testing.T) (snapshot.Facts, string) {
t.Helper()
ctx := t.Context()
if path := os.Getenv("MESH_FACTS"); path != "" {
f, err := readFacts(ctx, path)
if err != nil {
t.Fatalf("MESH_FACTS names %s and it is not a snapshot: %v", path, err)
}
return f, "MESH_FACTS " + path
}
if _, err := os.Stat("testdata/facts-equal.json"); err == nil {
f, err := readFacts(ctx, "testdata/facts-equal.json")
if err != nil {
t.Fatal(err)
}
return f, "captured in testdata/facts-equal.json"
}
open, _ := aMeshWithSecrets(t)
f, err := gatherFacts(ctx, open, "2.11.17")
if err != nil {
t.Fatal(err)
}
return f, "gathered from a mesh of the test's own"
}
// madeIn is what a store holds that a send makes, by key and never by value.
func madeIn(ctx context.Context, open *stores) (made, error) {
var out made
inv := open.inventory
nodes, err := inv.Nodes(ctx)
if err != nil {
return out, err
}
for _, n := range nodes {
ports, err := inv.PortsFor(ctx, n.Name)
if err != nil {
return out, err
}
for _, p := range ports {
out.Ports = append(out.Ports, fmt.Sprintf("%s %s:%d->%d", n.Name, p.Module, p.Wanted, p.Machine))
}
rows, err := inv.SecretsFrom(ctx, n.Name)
if err != nil {
return out, err
}
for _, s := range rows {
out.Pairs = append(out.Pairs, fmt.Sprintf("%s %s on %s from %s local=%s", s.Name, s.ConsumerModule, s.Consumer, n.Name, s.Local))
}
}
own, err := inv.OwnSecretRows(ctx)
if err != nil {
return out, err
}
for _, r := range own {
out.OwnSecrets = append(out.OwnSecrets, fmt.Sprintf("%s %s/%s (%s)", r.Node, r.Module, r.Name, r.Origin))
}
sort.Strings(out.Ports)
sort.Strings(out.Pairs)
sort.Strings(out.OwnSecrets)
return out, nil
}