diff --git a/cmd/mesh-controller/compose_equal_integration_test.go b/cmd/mesh-controller/compose_equal_integration_test.go index 57b62ecd..cd965f0a 100644 --- a/cmd/mesh-controller/compose_equal_integration_test.go +++ b/cmd/mesh-controller/compose_equal_integration_test.go @@ -4,11 +4,14 @@ package main import ( "context" + "flag" "fmt" "os" "sort" "strings" + "sync" "testing" + "time" "git.novox.be/novox/mesh-sdk/go/compose" snapshot "git.novox.be/novox/mesh-sdk/go/facts" @@ -20,6 +23,8 @@ import ( // 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. @@ -31,11 +36,19 @@ import ( // 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) 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) @@ -43,108 +56,149 @@ func TestEveryMachineComposesAlikeBothWays(t *testing.T) { 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) + // Three stores, raised from the same snapshot, are three independent pieces of work and run together: (a) both ways on a + // fresh store, (b) and (c) the controller's sends and readings, (b) mesh-sdk's sends. + var ( + wg sync.WaitGroup + 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]bool{} + sendRefused = map[string]string{} + errA, errOld, errSDK error + ) + wg.Add(3) + go func() { // (a) Foreseeing on the fresh store. + defer wg.Done() + fresh, drop, err := throwawayStores(ctx, admin) if err != nil { - dropFresh() - t.Fatalf("%s: the adapter cannot read the mesh: %v", m.Name, err) + errA = 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]) + 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. + 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") + }() + go func() { // (b) and (c): the controller's send of every machine, then both ways of reading. + defer wg.Done() + 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 { + break + } + if _, _, err := composedAndValidated(ctx, oldStore, m.Name, oldGens, Allocating); err != nil { + oldRefused[m.Name] = true + } + } + 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") + }() + go func() { // (b) mesh-sdk's send of every machine. + defer wg.Done() + 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) + }() + wg.Wait() + for _, err := range []error{errA, errOld, errSDK} { + if err != nil { + t.Fatal(err) } - 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() + + var failures []string 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) + // A machine refused on either side sends nothing, so what the controller made on the way to refusing it is no send's. + refused := map[string]bool{} + for m := range sendRefused { + refused[m] = true } - if _, err := raiseFromFacts(ctx, sdkStore, f, shelf, sources); err != nil { - dropSDK() - t.Fatal(err) + for m := range oldRefused { + refused[m] = true } - sendRefused := map[string]string{} - for _, m := range f.Machines { - if _, err := composeSend(ctx, sdkStore, m.Name); err != nil { - sendRefused[m.Name] = err.Error() - } + for m := range refused { + t.Logf("%s: refused on one side or both, so its ports and secrets are left out of the comparison of the send", m) } - sdkMade, err := madeIn(ctx, sdkStore) - dropSDK() - if err != nil { - t.Fatal(err) - } - failures = append(failures, madeDiffer("(b) the send", oldMade, sdkMade)...) + 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 { @@ -162,7 +216,7 @@ func TestEveryMachineComposesAlikeBothWays(t *testing.T) { } 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)) + "controller's send allocates none for it", m.Name, want)) } } }