closedNames returned as soon as the bus's closed-connections list held anything, which could be the test's own setup connection closing before the verb subprocess's connection did. That raced TestAVerbsOwnProcessNamesItsConnection red about once in 80 runs. It now waits until the connection it is named for appears, or the deadline passes.
190 lines
6.3 KiB
Go
190 lines
6.3 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/nats-io/nats.go"
|
||
|
||
"github.com/novox/mesh-controller/internal/broker"
|
||
"github.com/novox/mesh-controller/internal/conditions"
|
||
"github.com/novox/mesh-controller/internal/link"
|
||
"github.com/novox/mesh-controller/internal/testbus"
|
||
)
|
||
|
||
// The serving controller's own connection serves what it does for a moment: no connection is opened,
|
||
// and closing what it was lent leaves the serving one open (novox/hq issue 327).
|
||
func TestTheServingControllerLendsItsOwnConnection(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
serving, err := broker.Dial(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer serving.Close()
|
||
if err := serving.EnsureControllerBuckets(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
was := servingBus.Load()
|
||
servingBus.Store(serving)
|
||
defer servingBus.Store(was)
|
||
t.Setenv(broker.NATSVar, bus.ClientURL())
|
||
|
||
before, _ := bus.Varz(nil)
|
||
lent, err := aBus()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
lent.Close()
|
||
if !serving.Conn().IsConnected() {
|
||
t.Fatal("closing a lent connection closed the serving controller's")
|
||
}
|
||
if err := onTheBus(func(*nats.Conn) error { return nil }); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
handlers, _, err := seatToolHandlers()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
answer, err := handlers["hand-acts"](context.Background(), json.RawMessage(`{}`))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if a := answer.(verbAnswer); !a.OK || a.Answer == nil {
|
||
t.Fatalf("hand-acts answered %+v", a)
|
||
}
|
||
_, _ = handlers["queue"](context.Background(), json.RawMessage(`{}`))
|
||
after, _ := bus.Varz(nil)
|
||
if opened := after.TotalConnections - before.TotalConnections; opened != 0 {
|
||
t.Fatalf("the serving controller opened %d connection(s) of its own", opened)
|
||
}
|
||
}
|
||
|
||
// A verb that still runs as a process of its own says which in the bus's list of connections.
|
||
func TestAVerbsOwnProcessNamesItsConnection(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
setup, err := broker.Dial(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := setup.EnsureControllerBuckets(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
setup.Close()
|
||
asAProcess(t, bus.ClientURL())
|
||
handlers, _, err := seatToolHandlers()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// A silence acts, so it is still its own process; it dials, and finds no such condition.
|
||
_, _ = handlers["conditions"](context.Background(),
|
||
json.RawMessage(`{"silence":"bus.nothing.here","for":"1h","why":"a test"}`))
|
||
names := closedNames(t, bus, "mesh-controller verb conditions")
|
||
found := false
|
||
for _, n := range names {
|
||
found = found || n == "mesh-controller verb conditions"
|
||
}
|
||
if !found {
|
||
t.Fatalf("the verb's connection was named %q", names)
|
||
}
|
||
}
|
||
|
||
func TestEachProcessNamesItsConnectionsForWhatItIs(t *testing.T) {
|
||
for _, c := range []struct{ command, verb, want string }{
|
||
{"serve", "", "mesh-controller serving"},
|
||
{"conditions", "conditions", "mesh-controller verb conditions"},
|
||
{"delivery", "delivery-check", "mesh-controller verb delivery-check"},
|
||
{"push", "", "mesh-controller command push"},
|
||
} {
|
||
if got := connectionName(c.command, c.verb); got != c.want {
|
||
t.Errorf("%s/%s: %q", c.command, c.verb, got)
|
||
}
|
||
}
|
||
}
|
||
|
||
// D15: a user whose connections are dropped more than twelve times an hour is said, in plain words; one
|
||
// whose client closes its own short connections is not.
|
||
func TestAClientReconnectingInALoopIsSaid(t *testing.T) {
|
||
var read closedConnections
|
||
if err := json.Unmarshal([]byte(`{"hours":1,"reaches":true,"users":[
|
||
{"user":"ace.node-tools","closed":40,"dropped":40,"dropped_per_hour":40,"names":[
|
||
{"name":"ace.node-tools","closed":40,"dropped":40,"reasons":{"Stale Connection":30,"Read Error":10}}]},
|
||
{"user":"controller","closed":300,"dropped":0,"names":[
|
||
{"name":"mesh-controller verb delivery-check","closed":300,"dropped":0,"reasons":{"Client Closed":300}}]},
|
||
{"user":"(no user: refused before it logged in)","closed":90,"dropped":90,"names":[{"name":"","closed":90,
|
||
"dropped":90,"reasons":{"Authentication Failure":90}}]},
|
||
{"user":"node.anchor","closed":5,"dropped":5,"names":[{"name":"mesh-host/anchor","closed":5,"dropped":5,
|
||
"reasons":{"Read Error":5}}]}]}`), &read); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
found := reconnecting(read)
|
||
if len(found) != 1 {
|
||
t.Fatalf("%+v", found)
|
||
}
|
||
o := found[0]
|
||
if o.ID != "ace.node-tools" || o.Machine != "ace" || o.Kind != kindBusReconnects ||
|
||
o.Headline != "The tool runner on ace keeps losing the bus" || !strings.Contains(o.Said, "Stale Connection ×30") {
|
||
t.Fatalf("%+v", o)
|
||
}
|
||
if why, ok := conditions.PlainWords(conditions.Words{Headline: o.Headline, Explanation: o.Explanation,
|
||
Resolved: o.Resolved}, "ace"); !ok {
|
||
t.Fatalf("not plain: %s", why)
|
||
}
|
||
}
|
||
|
||
// The bus's module is asked on its machine, over the bus.
|
||
func TestTheBusModuleIsAskedWhoClosedConnections(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
conn, err := nats.Connect(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer conn.Close()
|
||
sub, err := conn.Subscribe(link.ModuleToolOn("nats", "nats_closed_connections", "anchor"), func(m *nats.Msg) {
|
||
_ = m.Respond([]byte(`{"result":{"hours":1,"reaches":true,"users":[{"user":"controller","closed":2,"dropped":0}]}}`))
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer func() { _ = sub.Unsubscribe() }()
|
||
read, err := askClosed(context.Background(), conn, "anchor")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(read.Users) != 1 || read.Users[0].Closed != 2 {
|
||
t.Fatalf("%+v", read)
|
||
}
|
||
}
|
||
|
||
// A bus module older than the tool answers nothing to the question: the probe passes over it quietly.
|
||
func TestAnOlderBusModuleIsPassedOverQuietly(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
conn, err := nats.Connect(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer conn.Close()
|
||
found, err := reconnectsOn(context.Background(), conn, "anchor")
|
||
if err != nil || len(found) != 0 {
|
||
t.Fatalf("a bus module without the tool: %v %v", found, err)
|
||
}
|
||
}
|
||
|
||
// A verb answered in the serving controller says its flag errors in its answer, not in the controller's log.
|
||
func TestAFlagErrorIsSaidInTheAnswer(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
serving, err := broker.Dial(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer serving.Close()
|
||
was := servingBus.Load()
|
||
servingBus.Store(serving)
|
||
defer servingBus.Store(was)
|
||
answer, read := readHere(context.Background(), []string{"hand-acts", "--bogus"})
|
||
if !read || answer.OK || !strings.Contains(answer.Output, "flag provided but not defined") {
|
||
t.Fatalf("answered %+v", answer)
|
||
}
|
||
}
|