Files
mesh-catalog/modules/zfs/cmd/zfs-tools/hand_test.go
T
2026-11-13 00:41:21 +01:00

81 lines
3.0 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"strings"
"testing"
"time"
)
// §23, §24, A-12, A-17, A-18: a hand act writes its key and starts the run's unit with {scope, unit}; the tool
// answers from the act's event when it comes within the wait, else "started", never "done"; a failed start
// leaves no key; pause writes `paused`, resume deletes it.
func TestAHandActWritesItsKeyStartsTheRunAndAnswersFromItsEvent(t *testing.T) {
cases := []struct {
name, act, why string
event map[string]any
callErr error
outcome string
err string
keyLeft bool
waited []string
}{
{name: "event seen", act: ActSnapshots, why: "before the move", event: map[string]any{"snapshot": "tank@mesh-hourly"}, outcome: "seen", keyLeft: true,
waited: handEnds[ActSnapshots]},
{name: "event not seen in time", act: ActPrune, why: "free space", outcome: "started", keyLeft: true, waited: handEnds[ActPrune]},
{name: "apply", act: ActApply, why: "now", event: map[string]any{"key": "hostid"}, outcome: "seen", keyLeft: true, waited: handEnds[ActApply]},
{name: "start refused", act: ActSnapshots, why: "x", callErr: errors.New("refused"), err: "did not start"},
{name: "no why", act: ActSnapshots, why: "", err: "say why"},
{name: "unknown act", act: "destroy", why: "x", err: "not an act"},
}
for _, c := range cases {
f := newFake(t)
m := f.machine()
st := memStates{}
m.States, m.Node = st, "ace"
var key string
var body any
m.Call = func(k string, b any) (json.RawMessage, error) { key, body = k, b; return nil, c.callErr }
var bound time.Duration
var kinds []string
m.Await = func(k []string, d time.Duration) (map[string]any, bool) {
kinds, bound = k, d
return c.event, c.event != nil
}
a, err := m.HandAct(context.Background(), c.act, "caller@node", c.why)
if c.err != "" {
if err == nil || !strings.Contains(err.Error(), c.err) {
t.Errorf("%s: %v", c.name, err)
}
} else if err != nil || a.Outcome != c.outcome {
t.Errorf("%s: %+v %v", c.name, a, err)
}
if _, left := st["schedule/"+c.act]; left != c.keyLeft {
t.Errorf("%s: key left %v, want %v", c.name, left, c.keyLeft)
}
if c.err == "" || c.callErr != nil {
args, _ := body.(map[string]any)
if key != "seat:node-service-manager.start@ace" || args["unit"] != "zfs-mesh-run.service" || args["scope"] != "system" || len(args) != 2 {
t.Errorf("%s: called %s with %v", c.name, key, body)
}
}
if c.err == "" && (bound != 30*time.Second || strings.Join(kinds, ",") != strings.Join(c.waited, ",")) {
t.Errorf("%s: waited %v for %v", c.name, bound, kinds)
}
if c.outcome == "started" && strings.Contains(a.Said, "done") {
t.Errorf("%s: said done for what it did not see: %s", c.name, a.Said)
}
}
f := newFake(t)
m := f.machine()
m.States = memStates{}
if err := m.Pause("p@n", "maintenance"); err != nil || !m.Paused() {
t.Fatal(err, m.Paused())
}
if err := m.Resume(); err != nil || m.Paused() {
t.Fatal(err, m.Paused())
}
}