bake: make the landing scan say what it saw, so a no-answer names its own cause
Splits Ignore into unreadable vs not-a-request and counts every verdict; the report prints only when the pass answered nobody, so its absence proves the action never ran.
This commit is contained in:
+102
-11
@@ -333,13 +333,13 @@ int main() {
|
||||
// steady state a project opened from disk reaches on the next timer tick.
|
||||
const BakeScanContext loadedAndActive{true};
|
||||
|
||||
BakeScanKey own{true, now, true};
|
||||
BakeScanKey own{true, true, now, true};
|
||||
CHECK(classifyBakeScan(loadedAndActive, own, now) == BakeScanVerdict::Land);
|
||||
|
||||
// A request found in a tab the extension has NOT loaded — the multi-tab case. It is
|
||||
// REFUSED, which is an answer the asking instance can read; it is never landed into
|
||||
// the loaded tab's bank, and never dropped silently.
|
||||
BakeScanKey otherTab{true, now, false};
|
||||
BakeScanKey otherTab{true, true, now, false};
|
||||
CHECK(classifyBakeScan(loadedAndActive, otherTab, now) ==
|
||||
BakeScanVerdict::RefuseWrongProject);
|
||||
|
||||
@@ -351,30 +351,121 @@ int main() {
|
||||
// ANY key here — a key can only match a project that is loaded — so this is the
|
||||
// shell-reachable stand-in for "no book yet", not `own` (which the shell could
|
||||
// never actually pair with an unloaded session).
|
||||
CHECK(classifyBakeScan(BakeScanContext{false}, BakeScanKey{true, now, false}, now) ==
|
||||
BakeScanVerdict::RefuseWrongProject);
|
||||
CHECK(classifyBakeScan(BakeScanContext{false}, BakeScanKey{true, true, now, false},
|
||||
now) == BakeScanVerdict::RefuseWrongProject);
|
||||
|
||||
// Stale in EITHER direction (a clock that moved backwards counts too).
|
||||
BakeScanKey old{true, now - kMaxRequestAgeSeconds - 1, true};
|
||||
BakeScanKey future{true, now + kMaxRequestAgeSeconds + 1, true};
|
||||
BakeScanKey old{true, true, now - kMaxRequestAgeSeconds - 1, true};
|
||||
BakeScanKey future{true, true, now + kMaxRequestAgeSeconds + 1, true};
|
||||
CHECK(classifyBakeScan(loadedAndActive, old, now) == BakeScanVerdict::ClearStale);
|
||||
CHECK(classifyBakeScan(loadedAndActive, future, now) == BakeScanVerdict::ClearStale);
|
||||
// Exactly at the bound is still landable — the ceiling is inclusive.
|
||||
BakeScanKey atBound{true, now - kMaxRequestAgeSeconds, true};
|
||||
BakeScanKey atBound{true, true, now - kMaxRequestAgeSeconds, true};
|
||||
CHECK(classifyBakeScan(loadedAndActive, atBound, now) == BakeScanVerdict::Land);
|
||||
// Staleness outranks the project check: a request nobody can read is cleared, not
|
||||
// answered, wherever it sits.
|
||||
BakeScanKey oldElsewhere{true, now - kMaxRequestAgeSeconds - 1, false};
|
||||
BakeScanKey oldElsewhere{true, true, now - kMaxRequestAgeSeconds - 1, false};
|
||||
CHECK(classifyBakeScan(loadedAndActive, oldElsewhere, now) ==
|
||||
BakeScanVerdict::ClearStale);
|
||||
|
||||
// A value that is not a request (an outcome the writer has not collected) is left
|
||||
// alone — answering it would clobber an answer in flight.
|
||||
BakeScanKey notARequest{false, 0, true};
|
||||
BakeScanKey notARequest{true, false, 0, true};
|
||||
CHECK(classifyBakeScan(loadedAndActive, notARequest, now) ==
|
||||
BakeScanVerdict::Ignore);
|
||||
BakeScanVerdict::IgnoreNotARequest);
|
||||
CHECK(classifyBakeScan(BakeScanContext{false}, notARequest, now) ==
|
||||
BakeScanVerdict::Ignore);
|
||||
BakeScanVerdict::IgnoreNotARequest);
|
||||
|
||||
// A key the enumerator listed but the reader could not return whole is its OWN
|
||||
// verdict, not folded into the one above: the extension failing to read a request
|
||||
// that may well be there is a different fault from a key holding something else,
|
||||
// and neither is visible from the instrument's end.
|
||||
BakeScanKey unreadable{false, false, 0, true};
|
||||
CHECK(classifyBakeScan(loadedAndActive, unreadable, now) ==
|
||||
BakeScanVerdict::IgnoreUnreadable);
|
||||
CHECK(classifyBakeScan(BakeScanContext{false}, unreadable, now) ==
|
||||
BakeScanVerdict::IgnoreUnreadable);
|
||||
// Unreadable outranks even staleness — an age read off a request that was never
|
||||
// decoded would be a made-up number.
|
||||
BakeScanKey unreadableOld{false, false, now - kMaxRequestAgeSeconds - 1, true};
|
||||
CHECK(classifyBakeScan(loadedAndActive, unreadableOld, now) ==
|
||||
BakeScanVerdict::IgnoreUnreadable);
|
||||
}
|
||||
|
||||
// --- The scan report: what the action tells a user it actually saw ------------------
|
||||
// The report exists because every non-answering verdict leaves the asking instance
|
||||
// with the identical evidence (its own untouched request), so only these counts
|
||||
// discriminate them. It must therefore SAY the count that fired, and must stay silent
|
||||
// whenever an answer was written.
|
||||
{
|
||||
BakeScanTally answeredOne;
|
||||
answeredOne.tabsScanned = 1;
|
||||
answeredOne.keysFound = 1;
|
||||
answeredOne.activeTabKeys = 1;
|
||||
answeredOne.answered = 1;
|
||||
answeredOne.landed = 1;
|
||||
CHECK(describeBakeScan(answeredOne).empty());
|
||||
// A refusal is still an answer, so it silences the report the same way a landing
|
||||
// does — the refusal's own sentence has already been printed.
|
||||
BakeScanTally refusedOne = answeredOne;
|
||||
refusedOne.landed = 0;
|
||||
CHECK(describeBakeScan(refusedOne).empty());
|
||||
|
||||
// Nothing found at all: the state Daniel's repro produces if the request key never
|
||||
// becomes visible to the extension.
|
||||
BakeScanTally nothing;
|
||||
nothing.tabsScanned = 2;
|
||||
const std::string none = describeBakeScan(nothing);
|
||||
CHECK(!none.empty());
|
||||
CHECK(none.find("2 project tabs") != std::string::npos);
|
||||
CHECK(none.find("No pending bake request was visible") != std::string::npos);
|
||||
CHECK(none.back() == '\n');
|
||||
|
||||
// One tab is singular, not "1 project tabs".
|
||||
BakeScanTally oneTab;
|
||||
oneTab.tabsScanned = 1;
|
||||
CHECK(describeBakeScan(oneTab).find("1 project tab,") != std::string::npos);
|
||||
|
||||
// Found but unreadable — the fact the old scan discarded.
|
||||
BakeScanTally unreadable;
|
||||
unreadable.tabsScanned = 1;
|
||||
unreadable.keysFound = 1;
|
||||
unreadable.activeTabKeys = 1;
|
||||
unreadable.unreadable = 1;
|
||||
const std::string unread = describeBakeScan(unreadable);
|
||||
CHECK(unread.find("1 pending request key") != std::string::npos);
|
||||
CHECK(unread.find("1 could not be read back") != std::string::npos);
|
||||
CHECK(unread.find("held something other than a request") == std::string::npos);
|
||||
|
||||
// Found, readable, but not a request.
|
||||
BakeScanTally foreign;
|
||||
foreign.tabsScanned = 1;
|
||||
foreign.keysFound = 1;
|
||||
foreign.activeTabKeys = 1;
|
||||
foreign.notARequest = 1;
|
||||
const std::string other = describeBakeScan(foreign);
|
||||
CHECK(other.find("1 held something other than a request") != std::string::npos);
|
||||
CHECK(other.find("could not be read back") == std::string::npos);
|
||||
|
||||
// Cleared as stale: an answer was never written, so this too must report.
|
||||
BakeScanTally stale;
|
||||
stale.tabsScanned = 1;
|
||||
stale.keysFound = 1;
|
||||
stale.activeTabKeys = 1;
|
||||
stale.staleCleared = 1;
|
||||
const std::string aged = describeBakeScan(stale);
|
||||
CHECK(aged.find("past the age bound") != std::string::npos);
|
||||
|
||||
// Keys exist, but none in the tab the bake was fired against — the multi-tab
|
||||
// mis-target, called out explicitly rather than left to be inferred from "0 of them".
|
||||
BakeScanTally elsewhere;
|
||||
elsewhere.tabsScanned = 2;
|
||||
elsewhere.keysFound = 1;
|
||||
elsewhere.activeTabKeys = 0;
|
||||
elsewhere.notARequest = 1;
|
||||
const std::string away = describeBakeScan(elsewhere);
|
||||
CHECK(away.find("0 of them in the active tab") != std::string::npos);
|
||||
CHECK(away.find("fired against held none of them") != std::string::npos);
|
||||
}
|
||||
|
||||
// --- Every outcome bake_land actually emits survives the key round trip -------------
|
||||
|
||||
Reference in New Issue
Block a user