stage 8 of 8 for issue 1389: the equality test's integration part goes
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user