128 lines
4.5 KiB
Go
128 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// itemBusFake records every call, in order, as the backup holder makes them.
|
|
type itemBusFake struct {
|
|
log []string
|
|
restoring []string // what each read of the recovery module's restores answers, in turn
|
|
unended []ItemRef
|
|
runFailure error
|
|
}
|
|
|
|
func (f *itemBusFake) Say(event string, ref ItemRef, body map[string]any) error {
|
|
line := event + " " + ref.Module + "." + ref.Item
|
|
if o, ok := body["outcome"]; ok {
|
|
line += " " + fmt.Sprint(o)
|
|
}
|
|
if r, ok := body["restore"]; ok {
|
|
line += " restore=" + fmt.Sprint(r)
|
|
}
|
|
if e, ok := body["error"]; ok {
|
|
line += " error=" + fmt.Sprint(e)
|
|
}
|
|
if _, ok := body["at"]; !ok {
|
|
line += " NO-AT"
|
|
}
|
|
f.log = append(f.log, line)
|
|
return nil
|
|
}
|
|
func (f *itemBusFake) Unended() ([]ItemRef, error) { return f.unended, nil }
|
|
func (f *itemBusFake) Restoring(module string) (string, error) {
|
|
f.log = append(f.log, "read restores of "+module)
|
|
if len(f.restoring) == 0 {
|
|
return "", nil
|
|
}
|
|
act := f.restoring[0]
|
|
f.restoring = f.restoring[1:]
|
|
return act, nil
|
|
}
|
|
func (f *itemBusFake) WaitRestored(_ context.Context, act string) error {
|
|
f.log = append(f.log, "restored "+act)
|
|
return nil
|
|
}
|
|
|
|
func itemsBackups(t *testing.T, bus *itemBusFake, broker string, runErr error) *Backups {
|
|
t.Helper()
|
|
return &Backups{Where: withData(t, composed, composedData), Node: "ace", Now: func() time.Time { return time.Date(2026, 11, 12, 3, 0, 0, 0, time.UTC) },
|
|
Say: quiet, Bus: bus, BrokerModule: func() (string, error) { return broker, nil },
|
|
Run: func(_ context.Context, name string, args ...string) (string, error) {
|
|
if name == "restic" && len(args) > 5 && args[5] == "backup" {
|
|
return "", runErr
|
|
}
|
|
return "[]", nil
|
|
}}
|
|
}
|
|
|
|
func declaredOf(t *testing.T, b *Backups, module string) Declared {
|
|
t.Helper()
|
|
all, err := b.Declared()
|
|
must(t, err)
|
|
for _, d := range all {
|
|
if d.Module == module {
|
|
return d
|
|
}
|
|
}
|
|
t.Fatalf("%s is not declared", module)
|
|
return Declared{}
|
|
}
|
|
|
|
// Design 47 G §563 (BKP4): every item of a backed-up module says item-started, then item-ended with its outcome and `at`; a
|
|
// cache item says nothing; a failed backup ends its items failed with why; the holder reads no restore for a module that does
|
|
// not hold the seat mesh-broker.
|
|
func TestEveryItemOfABackedUpModuleSaysItsStartAndItsEnd(t *testing.T) {
|
|
bus := &itemBusFake{}
|
|
b := itemsBackups(t, bus, "somebody-else", nil)
|
|
if n := b.one(context.Background(), declaredOf(t, b, "postgres"), false); !n.OK {
|
|
t.Fatalf("%+v", n)
|
|
}
|
|
want := []string{"item-started postgres.store", "item-started postgres.dumps", "item-ended postgres.store done", "item-ended postgres.dumps done"}
|
|
if !reflect.DeepEqual(bus.log, want) {
|
|
t.Fatalf("said %q", bus.log)
|
|
}
|
|
bus.log = nil
|
|
b = itemsBackups(t, bus, "somebody-else", errors.New("disk full"))
|
|
if n := b.one(context.Background(), declaredOf(t, b, "postgres"), false); n.OK {
|
|
t.Fatal("a failed backup was ok")
|
|
}
|
|
if len(bus.log) != 4 || bus.log[2] != "item-ended postgres.store failed error=disk full" {
|
|
t.Fatalf("said %q", bus.log)
|
|
}
|
|
}
|
|
|
|
// Design 47 G §563 (BKP5): for the holder of mesh-broker the holder says its start, then reads the recovery module's restores;
|
|
// a restore that runs puts the items off naming its act, waits for that act's restored, starts again, and reads once more.
|
|
func TestTheBackupOfTheBusGivesWayToARestoreThatRuns(t *testing.T) {
|
|
bus := &itemBusFake{restoring: []string{"act-7"}}
|
|
b := itemsBackups(t, bus, "postgres", nil)
|
|
if n := b.one(context.Background(), declaredOf(t, b, "postgres"), false); !n.OK {
|
|
t.Fatalf("%+v", n)
|
|
}
|
|
want := []string{
|
|
"item-started postgres.store", "item-started postgres.dumps", "read restores of postgres",
|
|
"item-ended postgres.store put-off restore=act-7", "item-ended postgres.dumps put-off restore=act-7",
|
|
"restored act-7",
|
|
"item-started postgres.store", "item-started postgres.dumps", "read restores of postgres",
|
|
"item-ended postgres.store done", "item-ended postgres.dumps done"}
|
|
if !reflect.DeepEqual(bus.log, want) {
|
|
t.Fatalf("said %q", bus.log)
|
|
}
|
|
}
|
|
|
|
// Design 47 G §563 (BKP4): at its start the holder ends every item it said started and never ended, failed with the error stopped.
|
|
func TestItemsLeftStartedAreEndedFailedStoppedAtTheStart(t *testing.T) {
|
|
bus := &itemBusFake{unended: []ItemRef{{"postgres", "store"}}}
|
|
b := itemsBackups(t, bus, "postgres", nil)
|
|
must(t, b.StopStale())
|
|
if !reflect.DeepEqual(bus.log, []string{"item-ended postgres.store failed error=stopped"}) {
|
|
t.Fatalf("said %q", bus.log)
|
|
}
|
|
}
|