44ae4dea70
gates / gates (push) Successful in 26s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
83 lines
3.4 KiB
Go
83 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// v0.271.0 — the automatic update leg (`09` §6.4 part 7).
|
|
|
|
// TestChainUpdateLeg_EveryPath — the leg runs after the off-site half on EVERY path it can take:
|
|
// nothing to do (no target), ran ok, returned an error (incl. every RunOffboxBackup early return, which
|
|
// returns to here), and panicked. The off-site error reaches the scheduler unchanged; a panic becomes one.
|
|
//
|
|
// COMPANION RED-PROOF (REPORT.md): call the leg after `return offsite(ctx)` instead of in the defer —
|
|
// the error and panic cases fail at "the leg did not run after the off-site half".
|
|
func TestChainUpdateLeg_EveryPath(t *testing.T) {
|
|
boom := errors.New("sftp: connection refused")
|
|
cases := []struct {
|
|
name string
|
|
offsite func(context.Context) error
|
|
wantErr string
|
|
}{
|
|
{"no off-site target (nothing to do)", func(context.Context) error { return nil }, ""},
|
|
{"off-site ran ok", func(context.Context) error { return nil }, ""},
|
|
{"off-site failed / an early return", func(context.Context) error { return boom }, "connection refused"},
|
|
{"off-site panicked", func(context.Context) error { panic("nil map") }, "panicked"},
|
|
}
|
|
for _, c := range cases {
|
|
ran := 0
|
|
err := chainUpdateLeg(context.Background(), c.offsite, func(context.Context) { ran++ })
|
|
if ran != 1 {
|
|
t.Errorf("%s: the leg did not run after the off-site half (ran %d times)", c.name, ran)
|
|
}
|
|
if c.wantErr == "" && err != nil {
|
|
t.Errorf("%s: unexpected error %v", c.name, err)
|
|
}
|
|
if c.wantErr != "" && (err == nil || !strings.Contains(err.Error(), c.wantErr)) {
|
|
t.Errorf("%s: error %v, want one containing %q", c.name, err, c.wantErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestUpdateLegIsWiredAtStartup — the leg, its chain and the gate's interlock are CALLED from main.go (an
|
|
// AST walk; a comment naming them would not count). A seam built and never wired is this project's
|
|
// commonest defect.
|
|
//
|
|
// COMPANION RED-PROOF (REPORT.md): comment out the SetUpdateLeg call — this fails.
|
|
func TestUpdateLegIsWiredAtStartup(t *testing.T) {
|
|
lines, _, _ := slice4CallLines(t)
|
|
for _, callee := range []string{"SetUpdateLeg", "chainUpdateLeg", "RunUpdateLeg", "FreshWholeCopy"} {
|
|
if len(lines[callee]) == 0 {
|
|
t.Errorf("main.go never calls %s — the automatic update leg is not wired", callee)
|
|
}
|
|
}
|
|
src, err := os.ReadFile("main.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(src), "Enabled: sett.GetAppUpdateUnattended,") {
|
|
t.Error("the leg's switch must be the household's app_update.unattended (sett.GetAppUpdateUnattended)")
|
|
}
|
|
if !strings.Contains(string(src), "UpdateLegFn: stackMgr.UpdateLegState") {
|
|
t.Error("the quiesce loop must be given stackMgr.UpdateLegState (decision 20's interlock)")
|
|
}
|
|
// the chain must sit INSIDE the offbox-backup job, not beside it
|
|
i := strings.Index(string(src), `sched.Daily("offbox-backup"`)
|
|
if i < 0 || !strings.Contains(string(src)[i:i+600], "chainUpdateLeg(") {
|
|
t.Error("the offbox-backup job must run through chainUpdateLeg")
|
|
}
|
|
}
|
|
|
|
// TestUndoCopyRemoverIsWiredAtStartup — R-671's seam is CALLED from main.go (AST walk).
|
|
// COMPANION RED-PROOF (REPORT.md): drop the SetUndoCopyRemover call — this fails.
|
|
func TestUndoCopyRemoverIsWiredAtStartup(t *testing.T) {
|
|
lines, _, _ := slice4CallLines(t)
|
|
if len(lines["SetUndoCopyRemover"]) == 0 {
|
|
t.Fatal("main.go never calls SetUndoCopyRemover — a lifted hold's undo copies would stay for ever")
|
|
}
|
|
}
|