Fix the bake scan report's unsound absence-as-signal claim
Gate the summary on any unanswered key and on SetProjExtState's return, print one line per scanned key, and make the undo block stack RAII.
This commit is contained in:
+128
-23
@@ -392,39 +392,71 @@ int main() {
|
||||
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.
|
||||
// --- The scan summary: silent ONLY when nothing went unanswered ---------------------
|
||||
// `answered` is pass-wide and counts a QUEUED write, so gating on it alone let two real
|
||||
// faults print nothing: a pass that answered another key while skipping ours, and a
|
||||
// pass whose own answer write was rejected. Those two are what the gate below pins.
|
||||
{
|
||||
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
|
||||
BakeScanTally clean;
|
||||
clean.tabsScanned = 1;
|
||||
clean.keysFound = 1;
|
||||
clean.activeTabKeys = 1;
|
||||
clean.answered = 1;
|
||||
clean.landed = 1;
|
||||
CHECK(describeBakeScan(clean).empty());
|
||||
// A refusal is still an answer, so it silences the summary the same way a landing
|
||||
// does — the refusal's own sentence has already been printed.
|
||||
BakeScanTally refusedOne = answeredOne;
|
||||
BakeScanTally refusedOne = clean;
|
||||
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.
|
||||
// THE regression: some OTHER key was answered while ours was skipped. Under the old
|
||||
// `answered > 0` gate this printed nothing at all, and the instrument then told the
|
||||
// user a silent console proved the action never ran.
|
||||
for (int BakeScanTally::*skip :
|
||||
{&BakeScanTally::unreadable, &BakeScanTally::notARequest,
|
||||
&BakeScanTally::staleCleared}) {
|
||||
BakeScanTally mixed = clean;
|
||||
mixed.keysFound = 2;
|
||||
mixed.*skip = 1;
|
||||
const std::string said = describeBakeScan(mixed);
|
||||
CHECK(!said.empty());
|
||||
CHECK(said.find("left 1 unanswered") != std::string::npos);
|
||||
}
|
||||
|
||||
// THE other regression: our answer was queued (so `answered` counted it) and the
|
||||
// SetProjExtState write was rejected. Nothing else went wrong, and it must still
|
||||
// speak.
|
||||
BakeScanTally rejected = clean;
|
||||
rejected.writeFailed = 1;
|
||||
const std::string wrote = describeBakeScan(rejected);
|
||||
CHECK(!wrote.empty());
|
||||
CHECK(wrote.find("1 answer could not be written back") != std::string::npos);
|
||||
CHECK(wrote.find("sees no answer at all") != std::string::npos);
|
||||
// Plural agrees, and the count is the tally's own, not a hardcoded 1.
|
||||
BakeScanTally rejectedTwo = clean;
|
||||
rejectedTwo.answered = 2;
|
||||
rejectedTwo.writeFailed = 2;
|
||||
CHECK(describeBakeScan(rejectedTwo).find("2 answers could not be written back") !=
|
||||
std::string::npos);
|
||||
|
||||
// Nothing found at all: the observation is stated and the candidates listed, with
|
||||
// no winner picked — the enumeration's visibility of a key set but not yet saved is
|
||||
// undocumented, so "not the same project's ext state" may not name it.
|
||||
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.find("No rsbake_ key was visible") != std::string::npos);
|
||||
CHECK(none.find("this pass cannot tell which apart") != std::string::npos);
|
||||
CHECK(none.find("are not reading the same project") == 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);
|
||||
CHECK(describeBakeScan(oneTab).find("1 project tab.") != std::string::npos);
|
||||
|
||||
// Found but unreadable — the fact the old scan discarded.
|
||||
BakeScanTally unreadable;
|
||||
@@ -433,9 +465,12 @@ int main() {
|
||||
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);
|
||||
// keysFound counts EVERY rsbake_ key, not pending requests only — a label that
|
||||
// said otherwise produced "3 pending request keys ... 3 held something else".
|
||||
CHECK(unread.find("1 rsbake_ key") != std::string::npos);
|
||||
CHECK(unread.find("pending request key") == std::string::npos);
|
||||
|
||||
// Found, readable, but not a request.
|
||||
BakeScanTally foreign;
|
||||
@@ -456,16 +491,86 @@ int main() {
|
||||
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".
|
||||
// activeTabKeys is REAPER's ACTIVE tab and nothing more. It cannot discriminate the
|
||||
// multi-tab mis-target — a genuine background-tab request classifies as
|
||||
// RefuseWrongProject, which is an ANSWER — so no sentence may claim it does, and
|
||||
// none may call it "the tab this bake was fired against" (whether REAPER makes the
|
||||
// invoking instance's project current is DAW-unverified).
|
||||
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);
|
||||
CHECK(away.find("0 of them in REAPER's active tab") != std::string::npos);
|
||||
CHECK(away.find("fired against") == std::string::npos);
|
||||
}
|
||||
|
||||
// --- The per-key line: the only thing that names WHICH key ---------------------------
|
||||
// The tally counts; it cannot say whose key was skipped. This line is printed for every
|
||||
// enumerated key, answered or not, and the key carries the asking instance's guid — so
|
||||
// it is what lets one instance find its own verdict in a multi-instance session.
|
||||
{
|
||||
const std::string key = "rsbake_0123abcd";
|
||||
|
||||
BakeKeyOutcome landed;
|
||||
landed.verdict = BakeScanVerdict::Land;
|
||||
landed.landed = true;
|
||||
landed.answerWritten = true;
|
||||
const std::string ok = describeBakeKey(key, landed);
|
||||
CHECK(ok.find(key) != std::string::npos);
|
||||
CHECK(ok.find("landed into the bank") != std::string::npos);
|
||||
CHECK(ok.find("the answer was written back") != std::string::npos);
|
||||
CHECK(ok.back() == '\n');
|
||||
|
||||
// A landing whose answer write was REJECTED — the state the instrument reads as
|
||||
// Unanswered. This line is the only place it is ever named.
|
||||
BakeKeyOutcome lost = landed;
|
||||
lost.answerWritten = false;
|
||||
const std::string dropped = describeBakeKey(key, lost);
|
||||
CHECK(dropped.find("landed into the bank") != std::string::npos);
|
||||
CHECK(dropped.find("could NOT be written back") != std::string::npos);
|
||||
CHECK(dropped.find("will report no answer") != std::string::npos);
|
||||
|
||||
BakeKeyOutcome bankRefused;
|
||||
bankRefused.verdict = BakeScanVerdict::Land;
|
||||
bankRefused.landed = false;
|
||||
bankRefused.answerWritten = true;
|
||||
CHECK(describeBakeKey(key, bankRefused).find("the landing was refused") !=
|
||||
std::string::npos);
|
||||
|
||||
BakeKeyOutcome wrongTab;
|
||||
wrongTab.verdict = BakeScanVerdict::RefuseWrongProject;
|
||||
wrongTab.answerWritten = true;
|
||||
const std::string refused = describeBakeKey(key, wrongTab);
|
||||
CHECK(refused.find("not the one this extension has loaded") != std::string::npos);
|
||||
CHECK(refused.find("the answer was written back") != std::string::npos);
|
||||
|
||||
BakeKeyOutcome cleared;
|
||||
cleared.verdict = BakeScanVerdict::ClearStale;
|
||||
CHECK(describeBakeKey(key, cleared).find("cleared unanswered") != std::string::npos);
|
||||
|
||||
BakeKeyOutcome notRequest;
|
||||
notRequest.verdict = BakeScanVerdict::IgnoreNotARequest;
|
||||
CHECK(describeBakeKey(key, notRequest).find("other than a pending request") !=
|
||||
std::string::npos);
|
||||
|
||||
// Absent and Overflow both yield IgnoreUnreadable, but they are different faults to
|
||||
// go fix, so the line keeps them apart where the verdict cannot.
|
||||
BakeKeyOutcome empty;
|
||||
empty.verdict = BakeScanVerdict::IgnoreUnreadable;
|
||||
BakeKeyOutcome huge = empty;
|
||||
huge.oversized = true;
|
||||
const std::string emptyLine = describeBakeKey(key, empty);
|
||||
const std::string hugeLine = describeBakeKey(key, huge);
|
||||
CHECK(emptyLine.find("read back empty") != std::string::npos);
|
||||
CHECK(hugeLine.find("too large to read back whole") != std::string::npos);
|
||||
CHECK(emptyLine != hugeLine);
|
||||
|
||||
// Every line names its own key, so two instances' lines are never confusable.
|
||||
CHECK(describeBakeKey("rsbake_ffff0000", landed).find("rsbake_ffff0000") !=
|
||||
std::string::npos);
|
||||
CHECK(describeBakeKey("rsbake_ffff0000", landed) != ok);
|
||||
}
|
||||
|
||||
// --- Every outcome bake_land actually emits survives the key round trip -------------
|
||||
|
||||
Reference in New Issue
Block a user