Compose every machine both ways and show the two agree (issue 1384, ADR 0453 clause 3)
TestEveryMachineComposesAlikeBothWays reads the facts snapshot MESH_FACTS names (the live one in every check and try), else testdata/facts-equal.json, else the snapshot of a mesh of its own; raises it into throwaway stores as the merge gate does; and compares, machine by machine: (a) Foreseeing on the fresh store (error or none, the validator's problems, LeftOut and its reasons, Withheld, Unbound, Unplaced, HandedOnly, the own secrets named foreseen, and the ports mesh-sdk names foreseen against the ones the controller's send allocates); (b) the send, on two stores, the controller's Allocating composition and the adapter's compose, perform and compose again (ports, pair credentials and own secrets made, by key); (c) after the send, Reading both ways on one store, byte for byte. A difference names the machine, the comparison and the first resource and field. The comparison itself is tested without a store, and failed first when the body was not read. The test is deleted with the controller's composition (issue 1389).
This commit is contained in:
@@ -0,0 +1,245 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||||
|
snapshot "github.com/novox/mesh-controller/internal/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.
|
||||||
|
//
|
||||||
|
// (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) {
|
||||||
|
ctx := t.Context()
|
||||||
|
admin := os.Getenv("MESH_TEST_POSTGRES")
|
||||||
|
if admin == "" {
|
||||||
|
t.Skip("no throwaway PostgreSQL (MESH_TEST_POSTGRES) to raise the mesh in")
|
||||||
|
}
|
||||||
|
f, source := equalityFacts(t)
|
||||||
|
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)
|
||||||
|
var failures []string
|
||||||
|
|
||||||
|
// (a) on the fresh store. mesh-sdk's composition first: it writes nothing, where the controller's makes every pair
|
||||||
|
// credential on the way.
|
||||||
|
fresh, dropFresh, err := throwawayStores(ctx, admin)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := raiseFromFacts(ctx, fresh, f, shelf, sources); err != nil {
|
||||||
|
dropFresh()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sdkForeseen := map[string]composedBoth{}
|
||||||
|
foreseenPorts := map[string][]string{}
|
||||||
|
for _, m := range f.Machines {
|
||||||
|
in, err := readComposeInput(ctx, fresh)
|
||||||
|
if err != nil {
|
||||||
|
dropFresh()
|
||||||
|
t.Fatalf("%s: the adapter cannot read the mesh: %v", m.Name, err)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
oldForeseen := map[string]composedBoth{}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
dropFresh()
|
||||||
|
for _, m := range f.Machines {
|
||||||
|
failures = append(failures, bothWaysDiffer(m.Name, "(a) Foreseeing", oldForeseen[m.Name], sdkForeseen[m.Name], false)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// (b) and (c): the controller's send of every machine, then both ways of reading, on one store.
|
||||||
|
oldStore, dropOld, err := throwawayStores(ctx, admin)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := raiseFromFacts(ctx, oldStore, f, shelf, sources); err != nil {
|
||||||
|
dropOld()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
oldGens, oldGensErr := generators(ctx, oldStore)
|
||||||
|
for _, m := range f.Machines {
|
||||||
|
if oldGensErr != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, _, _ = composedAndValidated(ctx, oldStore, m.Name, oldGens, Allocating)
|
||||||
|
}
|
||||||
|
oldMade, err := madeIn(ctx, oldStore)
|
||||||
|
if err != nil {
|
||||||
|
dropOld()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
readOld, readSDK := map[string]composedBoth{}, map[string]composedBoth{}
|
||||||
|
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(ctx, oldStore, m.Name, compose.Reading, true)
|
||||||
|
readSDK[m.Name] = capturedFrom(d, problems, err)
|
||||||
|
}
|
||||||
|
dropOld()
|
||||||
|
for _, m := range f.Machines {
|
||||||
|
failures = append(failures, bothWaysDiffer(m.Name, "(c) Reading after the send", readOld[m.Name], readSDK[m.Name], true)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkStore, dropSDK, err := throwawayStores(ctx, admin)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := raiseFromFacts(ctx, sdkStore, f, shelf, sources); err != nil {
|
||||||
|
dropSDK()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sendRefused := map[string]string{}
|
||||||
|
for _, m := range f.Machines {
|
||||||
|
if _, err := composeSend(ctx, sdkStore, m.Name); err != nil {
|
||||||
|
sendRefused[m.Name] = err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sdkMade, err := madeIn(ctx, sdkStore)
|
||||||
|
dropSDK()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
failures = append(failures, madeDiffer("(b) the send", oldMade, sdkMade)...)
|
||||||
|
|
||||||
|
// (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 {
|
||||||
|
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 (it allocates %v)", m.Name, want, allocated))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The comparison of the controller's composition and mesh-sdk's (novox/hq ADR 0453 clause 3, design 47 R4b, issue 1384),
|
||||||
|
// the part of it that needs no store. The test that raises a mesh and composes every machine both ways is
|
||||||
|
// compose_equal_integration_test.go; both are deleted with the controller's own composition (issue 1389).
|
||||||
|
|
||||||
|
// composedBoth is what one composition of a machine said, in the terms the two paths are compared in.
|
||||||
|
type composedBoth struct {
|
||||||
|
Err string
|
||||||
|
Problems []string
|
||||||
|
LeftOut map[string]string
|
||||||
|
Withheld []string
|
||||||
|
Unbound []string
|
||||||
|
Unplaced []string
|
||||||
|
Handed []string
|
||||||
|
// Foreseen are the own secrets the composition names foreseen, as `module/name`.
|
||||||
|
Foreseen []string
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// capturedFrom is a sendable, its validator problems and the error that came with it, as compared.
|
||||||
|
func capturedFrom(d sendable, problems []string, err error) composedBoth {
|
||||||
|
if err != nil {
|
||||||
|
return composedBoth{Err: err.Error()}
|
||||||
|
}
|
||||||
|
c := composedBoth{Problems: append([]string(nil), problems...), LeftOut: d.leftOutWhy,
|
||||||
|
Unplaced: append([]string(nil), d.unplaced...), Handed: append([]string(nil), d.handedOnly...),
|
||||||
|
Foreseen: append([]string(nil), d.foreseen...)}
|
||||||
|
for _, o := range d.withheld {
|
||||||
|
c.Withheld = append(c.Withheld, o.String())
|
||||||
|
}
|
||||||
|
for _, u := range d.unbound {
|
||||||
|
c.Unbound = append(c.Unbound, u.String())
|
||||||
|
}
|
||||||
|
sort.Strings(c.Problems)
|
||||||
|
sort.Strings(c.Withheld)
|
||||||
|
sort.Strings(c.Unbound)
|
||||||
|
sort.Strings(c.Unplaced)
|
||||||
|
sort.Strings(c.Handed)
|
||||||
|
sort.Strings(c.Foreseen)
|
||||||
|
c.Body, _ = d.Body()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// bothWaysDiffer says how two compositions of one machine differ, one line for each field that does, naming the machine and
|
||||||
|
// the comparison. A machine both refuse alike is no difference. withBody compares the declaration byte for byte and names
|
||||||
|
// the first resource that differs.
|
||||||
|
func bothWaysDiffer(machine, comparison string, old, sdk composedBoth, withBody bool) []string {
|
||||||
|
var out []string
|
||||||
|
say := func(field string, a, b any) {
|
||||||
|
out = append(out, fmt.Sprintf("%s, %s: %s differs: the controller's %v, mesh-sdk's %v", machine, comparison, field, a, b))
|
||||||
|
}
|
||||||
|
if old.Err != sdk.Err {
|
||||||
|
say("the refusal", quoted(old.Err), quoted(sdk.Err))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
if old.Err != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, f := range []struct {
|
||||||
|
name string
|
||||||
|
old, sdk []string
|
||||||
|
}{
|
||||||
|
{"the validator's problems", old.Problems, sdk.Problems},
|
||||||
|
{"Withheld", old.Withheld, sdk.Withheld},
|
||||||
|
{"Unbound", old.Unbound, sdk.Unbound},
|
||||||
|
{"Unplaced", old.Unplaced, sdk.Unplaced},
|
||||||
|
{"HandedOnly", old.Handed, sdk.Handed},
|
||||||
|
{"the own secrets named foreseen", old.Foreseen, sdk.Foreseen},
|
||||||
|
} {
|
||||||
|
if !sameStrings(f.old, f.sdk) {
|
||||||
|
say(f.name, f.old, f.sdk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sameLeftOut(old.LeftOut, sdk.LeftOut) {
|
||||||
|
say("LeftOut and its reasons", old.LeftOut, sdk.LeftOut)
|
||||||
|
}
|
||||||
|
if withBody && !bytes.Equal(old.Body, sdk.Body) {
|
||||||
|
out = append(out, fmt.Sprintf("%s, %s: the declaration differs byte for byte; %s", machine, comparison,
|
||||||
|
firstDifferingResource(old.Body, sdk.Body)))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func quoted(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameStrings(a, b []string) bool {
|
||||||
|
if len(a) == 0 && len(b) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return reflect.DeepEqual(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameLeftOut(a, b map[string]string) bool {
|
||||||
|
if len(a) == 0 && len(b) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return reflect.DeepEqual(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// firstDifferingResource names the first resource of two declaration bodies that is not the same, and the first field of it.
|
||||||
|
func firstDifferingResource(a, b []byte) string {
|
||||||
|
var x, y struct {
|
||||||
|
Resources []map[string]any `json:"resources"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal(a, &x) != nil || json.Unmarshal(b, &y) != nil {
|
||||||
|
return "a body is not a declaration"
|
||||||
|
}
|
||||||
|
byID := func(rs []map[string]any) map[string]map[string]any {
|
||||||
|
out := map[string]map[string]any{}
|
||||||
|
for _, r := range rs {
|
||||||
|
out[fmt.Sprint(r["id"])] = r
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
ids := map[string]bool{}
|
||||||
|
left, right := byID(x.Resources), byID(y.Resources)
|
||||||
|
for id := range left {
|
||||||
|
ids[id] = true
|
||||||
|
}
|
||||||
|
for id := range right {
|
||||||
|
ids[id] = true
|
||||||
|
}
|
||||||
|
var order []string
|
||||||
|
for id := range ids {
|
||||||
|
order = append(order, id)
|
||||||
|
}
|
||||||
|
sort.Strings(order)
|
||||||
|
for _, id := range order {
|
||||||
|
l, inLeft := left[id]
|
||||||
|
r, inRight := right[id]
|
||||||
|
switch {
|
||||||
|
case !inRight:
|
||||||
|
return fmt.Sprintf("resource %s is only in the controller's", id)
|
||||||
|
case !inLeft:
|
||||||
|
return fmt.Sprintf("resource %s is only in mesh-sdk's", id)
|
||||||
|
}
|
||||||
|
var fields []string
|
||||||
|
for k := range l {
|
||||||
|
fields = append(fields, k)
|
||||||
|
}
|
||||||
|
for k := range r {
|
||||||
|
if _, have := l[k]; !have {
|
||||||
|
fields = append(fields, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(fields)
|
||||||
|
for _, k := range fields {
|
||||||
|
if !reflect.DeepEqual(l[k], r[k]) {
|
||||||
|
return fmt.Sprintf("resource %s, field %s: the controller's %v, mesh-sdk's %v", id, k, l[k], r[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(left, right) {
|
||||||
|
return "the envelopes differ"
|
||||||
|
}
|
||||||
|
return "the resources are the same; the envelope differs"
|
||||||
|
}
|
||||||
|
|
||||||
|
// made is what a send made in a store, by key and never by value: the machine ports given, the pair credentials and the
|
||||||
|
// own secrets.
|
||||||
|
type made struct {
|
||||||
|
Ports []string
|
||||||
|
Pairs []string
|
||||||
|
OwnSecrets []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// madeDiffer says how what two sends made differs.
|
||||||
|
func madeDiffer(comparison string, old, sdk made) []string {
|
||||||
|
var out []string
|
||||||
|
for _, f := range []struct {
|
||||||
|
name string
|
||||||
|
old, sdk []string
|
||||||
|
}{
|
||||||
|
{"the ports allocated", old.Ports, sdk.Ports},
|
||||||
|
{"the pair credentials made", old.Pairs, sdk.Pairs},
|
||||||
|
{"the own secrets made", old.OwnSecrets, sdk.OwnSecrets},
|
||||||
|
} {
|
||||||
|
if !sameStrings(f.old, f.sdk) {
|
||||||
|
out = append(out, fmt.Sprintf("%s: %s differ: the controller's %s, mesh-sdk's %s", comparison, f.name,
|
||||||
|
strings.Join(f.old, ", "), strings.Join(f.sdk, ", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1384/AC3: the comparison itself. A composition that differs by one setting's worth is named, with its machine and the
|
||||||
|
// resource and field that first differ; two that agree, and two that refuse alike, are not differences. It failed first,
|
||||||
|
// before bothWaysDiffer read the body: a changed setting went unseen.
|
||||||
|
func TestTheComparisonNamesTheMachineWhoseCompositionDiffers(t *testing.T) {
|
||||||
|
body := func(port string) []byte {
|
||||||
|
raw, err := json.Marshal(map[string]any{"declaration": 1, "resources": []map[string]any{
|
||||||
|
{"id": "files.config", "type": "file", "content": "port=" + port},
|
||||||
|
{"id": "files.dir", "type": "directory"}}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
same := composedBoth{Body: body("8080"), Foreseen: []string{"files/admin"}}
|
||||||
|
if got := bothWaysDiffer("laptop", "Reading", same, same, true); len(got) != 0 {
|
||||||
|
t.Fatalf("a composition differs from itself: %v", got)
|
||||||
|
}
|
||||||
|
changed := composedBoth{Body: body("8081"), Foreseen: []string{"files/admin"}}
|
||||||
|
got := bothWaysDiffer("laptop", "Reading", same, changed, true)
|
||||||
|
if len(got) != 1 || !strings.Contains(got[0], "laptop, Reading") ||
|
||||||
|
!strings.Contains(got[0], "resource files.config, field content") ||
|
||||||
|
!strings.Contains(got[0], "port=8080") || !strings.Contains(got[0], "port=8081") {
|
||||||
|
t.Fatalf("the changed setting is not named with its machine, resource and field: %v", got)
|
||||||
|
}
|
||||||
|
// A field of the lists.
|
||||||
|
other := composedBoth{Body: body("8080"), Foreseen: []string{"files/admin", "files/token"}}
|
||||||
|
if got := bothWaysDiffer("anchor", "Foreseeing", same, other, false); len(got) != 1 ||
|
||||||
|
!strings.Contains(got[0], "anchor, Foreseeing: the own secrets named foreseen differs") {
|
||||||
|
t.Fatalf("a secret named foreseen on one path alone is not named: %v", got)
|
||||||
|
}
|
||||||
|
// Refused alike is said, not failed; refused differently is a difference.
|
||||||
|
a, b := composedBoth{Err: "files on laptop needs a credential"}, composedBoth{Err: "files on laptop needs a credential"}
|
||||||
|
if got := bothWaysDiffer("laptop", "Foreseeing", a, b, true); len(got) != 0 {
|
||||||
|
t.Fatalf("two compositions that refuse a machine alike differ: %v", got)
|
||||||
|
}
|
||||||
|
if got := bothWaysDiffer("laptop", "Foreseeing", a, composedBoth{Body: body("1")}, true); len(got) != 1 ||
|
||||||
|
!strings.Contains(got[0], "the refusal differs") {
|
||||||
|
t.Fatalf("a machine one path refuses and the other composes is not named: %v", got)
|
||||||
|
}
|
||||||
|
// And what a send made.
|
||||||
|
if got := madeDiffer("the send", made{Ports: []string{"anchor files:80->8080"}}, made{}); len(got) != 1 ||
|
||||||
|
!strings.Contains(got[0], "the ports allocated differ") {
|
||||||
|
t.Fatalf("a port allocated on one path alone is not named: %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user