package hostlife import ( "crypto/sha256" "fmt" "os" "regexp" "strings" "testing" ) // TestOneBoxCreateInterruptionTrace is a deterministic trace of the // interruptions a one-box create recovers from (two-box plan rev 4, // Astra round 1 finding 1): the create's record persists, then its // selected box stops, then recovery runs for longer than its 15-step // wait; the box starts again (stopped) or the region's group replaces // it (destroyed). It writes the whole trace to $HOSTLIFE_TRACE_OUT when // set, so the trace at the baseline and after each task can be compared // byte for byte; the one-box shape must not change it. func TestOneBoxCreateInterruptionTrace(t *testing.T) { saved := boxesPerRegion boxesPerRegion = 1 // the one-box shape, whatever HOSTLIFE_BOXES says (final review minor 5) defer func() { boxesPerRegion = saved }() var out []string for _, how := range []string{"restarted", "replaced"} { w := newWitness() w.console = append(w.console, &consolePlan{Kind: "create", Host: "h", App: app0, OpID: w.newID("cr"), Inc: 1, Region: 0}) w.admit[app0].Reg[w.console[0].OpID] = regEntry{Until: w.now + 1000} w.drive(60, func() bool { return w.rec != nil && w.claim != nil && w.claim.Op.Kind == "creating" }, w.runConsole) b := w.boxes[0] b.Destroyed, b.Procs = true, nil w.logf("trace: box %d stopped after the record persisted", b.ID) w.drive(40, func() bool { return false }, w.runConsole, w.recoveryPass) if how == "restarted" { b.Destroyed = false b.forget() } else { w.replaceDestroyed() } w.logf("trace: box %s", how) w.drive(200, func() bool { return w.claim != nil && w.claim.Op.Kind == "serving" }, w.runConsole, w.recoveryPass, w.actAll()) out = append(out, "== "+how) out = append(out, w.trace...) c := w.claim out = append(out, fmt.Sprintf("final: op=%s/%s exec=%d holder=%+v rec=%+v lost=%v obl=%d", c.Op.Kind, c.Op.ID, c.Op.Exec, c.Holder, w.rec, c.Lost, len(c.Obl))) } if p := os.Getenv("HOSTLIFE_TRACE_OUT"); p != "" { if err := os.WriteFile(p, []byte(strings.Join(out, "\n")+"\n"), 0o644); err != nil { t.Fatal(err) } } } // TestOneBoxSeedTraces writes one line per seed of the one-box sweep, // the SHA-256 of its whole trace, to $HOSTLIFE_TRACE_OUT_SEEDS (and is // skipped without it): the per-seed evidence that a task leaves every // one-box trace as the baseline's, beyond the aggregate COUNTERS line. // Map-ordered fields in state dumps are masked. func TestOneBoxSeedTraces(t *testing.T) { p := os.Getenv("HOSTLIFE_TRACE_OUT_SEEDS") if p == "" { t.Skip("HOSTLIFE_TRACE_OUT_SEEDS is not set") } saved := boxesPerRegion boxesPerRegion = 1 // the one-box shape, whatever HOSTLIFE_BOXES says (final review minor 5) defer func() { boxesPerRegion = saved }() mask := regexp.MustCompile(`admission=\[[^\]]*\]`) var out []string for seed := int64(1); seed <= int64(seedsFromEnv(300)); seed++ { // the gate's seeds, 1..n (rev 11, Astra round 8) w := runSeed(seed, true) sum := sha256.Sum256([]byte(mask.ReplaceAllString(strings.Join(w.trace, "\n"), "admission=[masked]"))) out = append(out, fmt.Sprintf("%d %x %d", seed, sum[:8], len(w.trace))) } if err := os.WriteFile(p, []byte(strings.Join(out, "\n")+"\n"), 0o644); err != nil { t.Fatal(err) } }