v0.295.0: a box that was off at its backup time catches up once (R-871, decision 109); the missed-backup banner (decision 110); a late daily timer after a host suspend is skipped
gates / gates (push) Successful in 31s
gates / gates (push) Successful in 31s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/ast"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/nightchain"
|
||||
)
|
||||
|
||||
// TestR871_CatchUpWiring — v0.295.0 (`09` decision 109): the catch-up is built, triggered at start and on a host
|
||||
// resume, and holds the whole-guest cycle — the seam-built-but-never-wired class (four earlier instances).
|
||||
// COMPANION RED-PROOF: delete the `catchUp.Evaluate(ctx, "controller start")` line → this fails.
|
||||
func TestR871_CatchUpWiring(t *testing.T) {
|
||||
lines, _, _ := slice4CallLines(t)
|
||||
for _, call := range []string{"ResumeWatch", "SetCatchUpFn", "catchUpLegs", "NotifyBackupCatchUp"} {
|
||||
if len(lines[call]) == 0 {
|
||||
t.Errorf("%s is never called in main.go — the catch-up is built but not wired", call)
|
||||
}
|
||||
}
|
||||
// Both triggers, by their reason argument: the start (a literal) and the resume (inside ResumeWatch's callback).
|
||||
_, f, _ := slice4CallLines(t)
|
||||
start := 0
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if call, ok := n.(*ast.CallExpr); ok {
|
||||
if sel, ok := call.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "Evaluate" && len(call.Args) == 2 {
|
||||
if lit, ok := call.Args[1].(*ast.BasicLit); ok && lit.Value == `"controller start"` {
|
||||
start++
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if start != 1 {
|
||||
t.Errorf("the catch-up's START trigger (Evaluate(ctx, \"controller start\")) is called %d times, want 1", start)
|
||||
}
|
||||
}
|
||||
|
||||
// The catch-up's leg table holds the three BACKUP legs and nothing else, and the off-site body it shares with the
|
||||
// night never calls the update leg (that is chained only around the SCHEDULED off-site job).
|
||||
// COMPANION RED-PROOF: put `stackMgr.RunUpdateLeg` inside offsiteOnly's body → "the update leg is inside".
|
||||
func TestR871_CatchUpRunsNoUpdateLeg(t *testing.T) {
|
||||
noop := func(context.Context) error { return nil }
|
||||
legs := catchUpLegs(noop, noop, noop)
|
||||
if len(legs) != 3 || legs[nightchain.LegDBDump] == nil || legs[nightchain.LegTier2] == nil || legs[nightchain.LegOffsite] == nil {
|
||||
t.Fatalf("catchUpLegs = %v, want exactly db-dump, tier2, offsite", legs)
|
||||
}
|
||||
src, err := os.ReadFile("main.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := string(src)
|
||||
i := strings.Index(s, "offsiteOnly := withLeg(nightchain.LegOffsite,")
|
||||
j := strings.Index(s[i:], "sched.Daily(\"offbox-backup\"")
|
||||
if i < 0 || j < 0 {
|
||||
t.Fatal("the shared off-site leg body was not found in main.go")
|
||||
}
|
||||
if body := s[i : i+j]; strings.Contains(body, "RunUpdateLeg") || strings.Contains(body, "chainUpdateLeg") {
|
||||
t.Fatal("the update leg is inside the off-site body the catch-up runs")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user