test(run): full runs end to end on a claude stand-in
The Runs test drives this binary as crafter-review on a scratch repository, with a /bin/sh claude that answers every pass from its system file and prompt: a dry run; a run SIGKILLed in its last gate (every pass goes with it through its supervisor); the next run resuming the first gate from the checkpoint, salvaging the killed run's outputs and buying only the pass that never answered; a run with nothing left to buy; a FIX FIRST with --no-reuse --no-salvage; a stop by the stop file and by SIGTERM, each leaving no process behind; a stale stop file dropped by the run that claims its dir; a stop between gates. Two fixes it led to: - A stop that lands while a gate's pool is set up is no longer lost: the stop is checked under the same lock that publishes the pool. - A run never takes the number of a dump on record. Past a killed run that left no dump, a later run that bought nothing could have its number taken again and its dump written over. Drivers gains the --no-reuse store check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
dd0929b705
commit
952eb13f5c
4 changed files with 455 additions and 11 deletions
|
|
@ -4,8 +4,7 @@
|
|||
// Several drivers at once: the board (seats, memory, the cap), one full run
|
||||
// per hash, one driver per scratch dir, the merged unit-state, the locked
|
||||
// ledger, the shared memory record. The checks that run the Scheduler,
|
||||
// Admission, Stacks, the stack keys and the run's state store come with the
|
||||
// pool and the driver.
|
||||
// Admission, Stacks and the stack keys are in the Scheduler test.
|
||||
//
|
||||
// Another driver is a live process of its own (`sleep 300`), so its entries
|
||||
// stand until it dies; two drivers recording into one memory file are two
|
||||
|
|
@ -449,6 +448,33 @@ namespace {
|
|||
const Json old = {{"version", 1}, {"entries", {{"q", {{"unit", "Q"}}}}}, {"personaPass", nullptr}};
|
||||
State::SaveWhole(sp, old);
|
||||
Check("...sets=None is the whole-file write as before", Dyn::Equal(Dyn::LoadJsonFile(sp), old));
|
||||
{
|
||||
Run::Run r;
|
||||
r.records = {{{"label", "L:block:X"}, {"lens", "L"}, {"lensFile", "repo-bug-prover.md"}, {"unit", "X"}, {"kind", "block"}, {"status", "ok"}, {"reuseKey", "kx"}}};
|
||||
r.hash = "H";
|
||||
r.h8 = "H";
|
||||
r.round = 1;
|
||||
r.dumpPath = td / "run-1.json";
|
||||
r.repo = td;
|
||||
r.results = {{{"lens", "L"},
|
||||
{"coverage", Json::array({Json {{"item", "X"}, {"verdict", "clear"}, {"basis", ""}}})},
|
||||
{"findings", Json::array()},
|
||||
{"notes", Json::array()},
|
||||
{"parts", Json::array({Json {{"label", "L:block:X"}, {"kind", "block"}, {"unit", "X"}, {"verdict", "SEND-READY"}, {"findings", 0}}})}}};
|
||||
const fs::path sp2 = td / "unit-state-nr.json";
|
||||
const Json kx = {{"unit", "X"}, {"hash", "H"}, {"run", "run-0.json"}, {"reads", Json::array({Json {{"hash", "H"}, {"round", 0}, {"run", "run-0.json"}}})}};
|
||||
State::SaveWhole(sp2, {{"version", 1}, {"entries", {{"kx", kx}, {"ko", {{"unit", "O"}}}}}, {"personaPass", nullptr}});
|
||||
r.state = {{"version", 1}, {"entries", Json::object()}, {"personaPass", nullptr}};
|
||||
r.statePath = sp2;
|
||||
r.a.date = "2026-10-31";
|
||||
r.a.noReuse = true;
|
||||
r.log = [](const std::string&) {};
|
||||
const Json merged = r.StoreState();
|
||||
Check("a --no-reuse run: the store returns the merged file (its own read on top of the file's, another unit's entry kept) and keeps its empty view for the reuse decisions",
|
||||
Plan::ReadsOf(merged.at("entries").at("kx")) == 2 && merged.at("entries").contains("ko") && r.state.at("entries").empty()
|
||||
&& Plan::ReadsOf(State::LoadState(sp2).at("entries").at("kx")) == 2,
|
||||
merged.dump());
|
||||
}
|
||||
|
||||
// The ledger: two drivers on one file, every write a locked delta.
|
||||
const fs::path lp = td / "seat-ledger.json";
|
||||
|
|
|
|||
Loading…
Reference in a new issue