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