assign read the whole mesh before its write, to record the seats held by derivation, and that read printed on standard error that the node did not resolve for lack of the seat the act was about to give it. The seat verb answers with both streams, so the stale line came last and named the assignment just made as its remedy. The reads inside an act no longer print 'left out' lines; the node's unmet seat dependencies and whether it resolves come from one planFor taken after the write, and every other machine from blockedElsewhere. unassign now says when its node does not resolve, from the same resolution.
113 lines
3.9 KiB
Go
113 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// novox/hq issue 237: every line of an act's answer comes from one resolution of the node taken after
|
|
// its write, so the answer agrees with `plan` for the node right after.
|
|
|
|
func aProviderAndItsConsumer(t *testing.T, open *stores) {
|
|
t.Helper()
|
|
register(t, open, catalogue.Manifest{Module: "db", Version: "1",
|
|
Provides: []catalogue.Offer{{Name: "rows", Scope: catalogue.ScopeNode}}})
|
|
register(t, open, catalogue.Manifest{Module: "app", Version: "1", Requires: []string{"rows"}})
|
|
}
|
|
|
|
// twoRivalsAndANote is a node that does not resolve — two modules claim one node seat there — and a
|
|
// module beside them that changes nothing about it.
|
|
func twoRivalsAndANote(t *testing.T, open *stores) {
|
|
t.Helper()
|
|
one, two := rivals()
|
|
register(t, open, one)
|
|
register(t, open, two)
|
|
register(t, open, catalogue.Manifest{Module: "notes", Version: "1"})
|
|
}
|
|
|
|
func TestAnUnassignThatLeavesItsNodeUnresolvedSaysSoOnceInItsAnswer(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
twoRivalsAndANote(t, open)
|
|
// Straight into the store, as a node that came to this before the act.
|
|
for _, m := range []string{"rival-one", "rival-two", "notes"} {
|
|
if _, err := open.inventory.Assign(ctx, "laptop", m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
said := answered(t, func() error { return assignCommand(ctx, "unassign", []string{"laptop", "notes"}) })
|
|
|
|
if _, _, err := planFor(ctx, open, "laptop"); err == nil || !unresolvable(err) {
|
|
t.Fatalf("laptop resolves with two claimants of one seat: %v", err)
|
|
}
|
|
if n := strings.Count(said, "laptop does not resolve now"); n != 1 {
|
|
t.Errorf("the answer says laptop does not resolve %d times, want once:\n%s", n, said)
|
|
}
|
|
if strings.Contains(said, "is left out of the rest of the mesh") {
|
|
t.Errorf("the answer carries a line from a read that is not its resolution:\n%s", said)
|
|
}
|
|
}
|
|
|
|
func TestAnUnassignThatLeavesItsNodeResolvedSaysNothingOfIt(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
aProviderAndItsConsumer(t, open)
|
|
if _, err := assign(ctx, open, "laptop", "db", "app"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
said, err := unassign(ctx, open, "laptop", "app")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := planFor(ctx, open, "laptop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(said, "does not resolve") || strings.Contains(said, "could not be read") {
|
|
t.Errorf("a node that resolves is said not to:\n%s", said)
|
|
}
|
|
}
|
|
|
|
func TestAnAssignThatLeavesItsNodeUnresolvedSaysOnlyThePlansRefusal(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
twoRivalsAndANote(t, open)
|
|
if _, err := assign(ctx, open, "laptop", "rival-one"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Kept and not resolvable: a second claimant of the seat, which assignment keeps.
|
|
said, err := assign(ctx, open, "laptop", "rival-two")
|
|
if err == nil {
|
|
t.Fatalf("two claimants of one node seat resolved:\n%s", said)
|
|
}
|
|
_, _, planErr := planFor(ctx, open, "laptop")
|
|
if planErr == nil || planErr.Error() != err.Error() {
|
|
t.Errorf("the act's refusal is not the plan's right after:\nact: %v\nplan: %v", err, planErr)
|
|
}
|
|
if strings.Contains(said, "now has") {
|
|
t.Errorf("the answer says a seat is now held on a node that does not resolve:\n%s", said)
|
|
}
|
|
}
|
|
|
|
// The other readers of the whole mesh still say a machine they leave out, on standard error (novox/hq
|
|
// issue 188); an act sends it elsewhere.
|
|
func TestAMachineLeftOutIsSaidWhereTheContextSays(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
sayLeftOut(leftOutSaidBy(context.Background(), &buf), "laptop", errors.New("why"))
|
|
if buf.String() != "laptop is left out of the rest of the mesh: it does not resolve: why\n" {
|
|
t.Errorf("said %q", buf.String())
|
|
}
|
|
got := answered(t, func() error {
|
|
sayLeftOut(context.Background(), "laptop", errors.New("why"))
|
|
return nil
|
|
})
|
|
if !strings.Contains(got, "laptop is left out") {
|
|
t.Errorf("without an act's writer the line did not reach standard error: %q", got)
|
|
}
|
|
}
|