Review of 3baeaec: TestAwaitCredential took 1.9 s, over the unit limit; it now
sets the watch's settle to 10 ms, writes at 20 ms and gives up at 100 ms (0.2 s).
The watch's journal line for a failure named a bundle and a restart; the
credential wait now says it is read again every 30 s instead.
126 lines
4.9 KiB
Go
126 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-tools/node-tools/internal/bus"
|
|
"github.com/novox/mesh-tools/node-tools/internal/runtime"
|
|
)
|
|
|
|
// A runtime started before its credential is on the machine waits for it, woken by the watch of its directory, and gives
|
|
// up loudly at its bound; a file that is no credential ends the wait at once (issue 1496: on ace mesh-build-node's and
|
|
// mesh-build-image's runtimes exited twice on "cannot read the broker credential ... no such file").
|
|
func TestAwaitCredential(t *testing.T) {
|
|
const good = `{"url":"tls://bus.example:4222","module":"m"}`
|
|
rereadEvery = time.Hour // only the watch can wake the read in time
|
|
settle := runtime.Settle
|
|
runtime.Settle = 10 * time.Millisecond // a unit test stays well under a second
|
|
t.Cleanup(func() { rereadEvery, runtime.Settle = 30*time.Second, settle })
|
|
place := func(path, body string) {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, []byte(body), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Rename(tmp, path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, c := range []struct {
|
|
name string
|
|
before string // written before the wait, "" none
|
|
later string // written 20ms into the wait, "" none
|
|
deeper bool // the file's directory is not there at the start
|
|
bound time.Duration
|
|
want string // "" a credential; else a word of the error
|
|
said int
|
|
}{
|
|
{name: "there at the start", before: good, bound: time.Second, said: 0},
|
|
{name: "written during the wait", later: good, bound: 5 * time.Second, said: 2},
|
|
{name: "empty, then filled", before: " ", later: good, bound: 5 * time.Second, said: 2},
|
|
{name: "its directory made during the wait", later: good, deeper: true, bound: 5 * time.Second, said: 2},
|
|
{name: "never written", bound: 100 * time.Millisecond, want: "waited", said: 1},
|
|
{name: "no url", before: `{"module":"m"}`, bound: 5 * time.Second, want: "not a broker credential", said: 0},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
file := filepath.Join(dir, "broker")
|
|
if c.deeper {
|
|
file = filepath.Join(dir, "var", "lib", "m", "broker")
|
|
}
|
|
if c.before != "" {
|
|
place(file, c.before)
|
|
}
|
|
if c.later != "" {
|
|
go func() { time.Sleep(20 * time.Millisecond); place(file, c.later) }()
|
|
}
|
|
var said []string
|
|
began := time.Now()
|
|
cred, err := awaitCredential(file, time.Now().Add(c.bound), func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) })
|
|
switch {
|
|
case c.want == "" && (err != nil || cred.URL == ""):
|
|
t.Fatalf("no credential: %v", err)
|
|
case c.want != "" && (err == nil || !strings.Contains(err.Error(), c.want)):
|
|
t.Fatalf("error %v, want one saying %q", err, c.want)
|
|
case c.want == "" && c.later != "" && time.Since(began) > 3*time.Second:
|
|
t.Fatalf("read after %s: the watch did not wake it", time.Since(began))
|
|
}
|
|
if len(said) != c.said {
|
|
t.Fatalf("said %d lines, want %d: %q", len(said), c.said, said)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A bus that refuses the account at a start is asked again until the start's bound, said once; a refusal past it, a pin
|
|
// that does not match and an address that is no URL give up (issue 1496, issue 058).
|
|
func TestConnectWithin(t *testing.T) {
|
|
refused := errors.New("nats: Authorization Violation")
|
|
unreachable := errors.New("nats: no servers available for connection")
|
|
for _, c := range []struct {
|
|
name string
|
|
errs []error // what each dial answers, then a connection
|
|
bound time.Duration
|
|
fails string
|
|
saidAt int // lines said
|
|
}{
|
|
{name: "refused twice, then accepted", errs: []error{refused, refused}, bound: time.Hour, saidAt: 2},
|
|
{name: "unreachable, then accepted", errs: []error{unreachable}, bound: time.Hour, saidAt: 1},
|
|
{name: "refused past the bound", errs: []error{refused, refused}, bound: 0, fails: "refused this account"},
|
|
{name: "refused, then the bound ends", errs: []error{refused, refused, refused, refused, refused, refused}, bound: 5 * time.Second,
|
|
fails: "still refuses"},
|
|
{name: "pin", errs: []error{fmt.Errorf("tls: %w", bus.ErrPin)}, bound: time.Hour, fails: "pin"},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
dials := 0
|
|
dial := func(bus.Credential) (*bus.Conn, error) {
|
|
dials++
|
|
if dials <= len(c.errs) {
|
|
return nil, c.errs[dials-1]
|
|
}
|
|
return nil, nil
|
|
}
|
|
until := time.Now().Add(c.bound)
|
|
var said []string
|
|
_, err := connectWithin(bus.Credential{URL: "tls://bus.example:4222"}, until, dial, func(time.Duration) {},
|
|
func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) })
|
|
switch {
|
|
case c.fails == "" && err != nil:
|
|
t.Fatalf("gave up: %v", err)
|
|
case c.fails != "" && (err == nil || !strings.Contains(err.Error(), c.fails)):
|
|
t.Fatalf("error %v, want one saying %q", err, c.fails)
|
|
case c.fails == "" && len(said) != c.saidAt:
|
|
t.Fatalf("said %d lines, want %d: %q", len(said), c.saidAt, said)
|
|
}
|
|
})
|
|
}
|
|
}
|