From c8ef0e81205a66e3fb276119e235de30959d02ee Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 04:47:55 -0400 Subject: [PATCH 1/9] Script Studio's pane tests check what they say they check Old test bugs TM24's idiom pass found (83b0c1dd65), fixed. - alscriptcrumbsbar: part() handed out a pointer into a static vector that the next call replaced, so a check comparing two parts held only because the compiler evaluated its operands left to right, and the vector carried one case's parts into the next. Each part is kept for the case now. The parts, the jump bar and its path are required before they are read. - alscriptreferencespane: "copied" checked only that Ctrl-C was taken; it reads the clipboard for the chosen row's text and neither other. The rows and places are required before they are indexed. - alscriptproblemspane: the rows' messages were built and never shown; they are captured with the row count. "NOLINT name" never touched NOLINT; it checks that the name the row shows is the one a NOLINT comment turns off. Escape is checked to add a reveal, not to find an old one. - alscriptsearchpane: the search's sources were emptied only at the case's end, so a failed REQUIRE left them for the cases after; RAII empties them. - alscriptoutlinepane: "chosen" checks the whole list it chose. - Finds, cells, rows, choices, pins, reveals and entries are required before they are read in these and alscriptexplorermodel, alscriptexplorertree, alscriptinspectorpane, alscriptoutputpane and alscriptweightspane, through require_list, require_head, require_choice and the like where a file reads many. Each new or corrected check fails under a small change to the test's own input or expectation, at the check and not by crashing. Co-Authored-By: Claude Opus 5.5 --- .../newview/tests/alscriptcrumbsbar_test.cpp | 94 ++++++++++++++----- .../tests/alscriptexplorermodel_test.cpp | 53 ++++++++--- .../tests/alscriptexplorertree_test.cpp | 8 +- .../tests/alscriptinspectorpane_test.cpp | 8 +- .../tests/alscriptoutlinepane_test.cpp | 26 ++++- .../newview/tests/alscriptoutputpane_test.cpp | 40 +++++--- .../tests/alscriptproblemspane_test.cpp | 37 ++++++-- .../tests/alscriptreferencespane_test.cpp | 27 +++++- .../newview/tests/alscriptsearchpane_test.cpp | 29 +++++- .../tests/alscriptweightspane_test.cpp | 73 ++++++++------ 10 files changed, 292 insertions(+), 103 deletions(-) diff --git a/indra/newview/tests/alscriptcrumbsbar_test.cpp b/indra/newview/tests/alscriptcrumbsbar_test.cpp index 9c71cd0680..8106cf3a29 100644 --- a/indra/newview/tests/alscriptcrumbsbar_test.cpp +++ b/indra/newview/tests/alscriptcrumbsbar_test.cpp @@ -41,6 +41,7 @@ #include +#include #include namespace @@ -158,6 +159,10 @@ namespace { SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } + // The jump bar the skin gives the bar, which every check after + // reads what the bar shows from. + INFO("the skin gives the bar its jump bar"); + REQUIRE(jump() != nullptr); return found; } ALJumpBar* jump() { return window.find("breadcrumb"); } @@ -229,11 +234,11 @@ namespace } return joined(out); } + // The part that begins so, or none. const Part* part(const std::string& begins) { - static std::vector held; - held = parts(); - for (const Part& one : held) + const std::vector& now = held.emplace_back(parts()); + for (const Part& one : now) { if (one.text.rfind(begins, 0) == 0) { @@ -242,6 +247,10 @@ namespace } return nullptr; } + // The parts each part() looked through, kept for the case: a part one + // call found is still there after the next, so a check that compares + // two reads both, whichever is found first. + std::list> held; }; TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: the caret's path offers each step's siblings and follows changes", "[alscriptcrumbsbar]") @@ -253,7 +262,8 @@ namespace const std::string top = "Script a [default, f, g, h, other]"; CHECK(path() == top + ", default [default, f, g, h, other], state_entry [state_entry, touch_start]"); UNSCOPED_INFO("kept"); - REQUIRE(doc.caret->crumbPath.size() == size_t(2)); + CHECK(doc.caret->crumbPath.size() == size_t(2)); + REQUIRE(jump()->path().size() == size_t(3)); UNSCOPED_INFO("the script's step"); CHECK(jump()->path()[0].value == std::string("top")); UNSCOPED_INFO("said"); @@ -279,6 +289,7 @@ namespace CHECK(path().find("state_entry [") != std::string::npos); doc.editor->setCaret(ALTextPos(13, 5)); place(crumbs, doc); + REQUIRE_FALSE(jump()->path().empty()); UNSCOPED_INFO("where two meet: the later"); CHECK(jump()->path().back().label == std::string("h")); doc.editor->setCaret(ALTextPos(17, 0)); @@ -288,11 +299,13 @@ namespace doc.name = "Renamed"; place(crumbs, doc); + REQUIRE_FALSE(jump()->path().empty()); UNSCOPED_INFO("a new name: again"); CHECK(jump()->path()[0].label == std::string("Renamed")); Doc& other = tab("b"); other.name = "Other"; place(crumbs, other); + REQUIRE_FALSE(jump()->path().empty()); UNSCOPED_INFO("not in front: nothing"); CHECK(jump()->path()[0].label == std::string("Renamed")); crumbs->forget(); @@ -302,6 +315,7 @@ namespace CHECK(jump()->trailer().empty()); } place(crumbs, doc); + REQUIRE_FALSE(jump()->path().empty()); UNSCOPED_INFO("and shown again whatever"); CHECK(jump()->path()[0].label == std::string("Renamed")); } @@ -317,8 +331,10 @@ namespace CHECK(trailer() == std::string("Ln 3, Col 7, Tab Size: 4")); { INFO("pressed for a line"); - CHECK(part("Ln")->value == "line"); - CHECK(part("Ln")->toolTip == "line tip"); + const Part* line = part("Ln"); + REQUIRE(line != nullptr); + CHECK(line->value == "line"); + CHECK(line->toolTip == "line tip"); } doc.modifiable = false; @@ -344,20 +360,32 @@ namespace CHECK(raw[11].between); CHECK_FALSE(raw[12].between); } + const Part* indentation = part("Tab"); + const Part* selection = part("2 lines"); + const Part* errors = part("2 errors"); + const Part* warnings = part("1 warning"); + const Part* readOnly = part("Read"); + const Part* position = part("Ln"); + REQUIRE(indentation != nullptr); + REQUIRE(selection != nullptr); + REQUIRE(errors != nullptr); + REQUIRE(readOnly != nullptr); + REQUIRE(position != nullptr); { INFO("where narrow: the indentation first, then the selection, the counts and read only last, the place never"); - CHECK(part("Tab")->drop > part("2 lines")->drop); - CHECK(part("2 lines")->drop > part("2 errors")->drop); - CHECK(part("2 errors")->drop > 0); - CHECK(part("Read")->drop > 0); - CHECK(part("Ln")->drop == 0); + CHECK(indentation->drop > selection->drop); + CHECK(selection->drop > errors->drop); + CHECK(errors->drop > 0); + CHECK(readOnly->drop > 0); + CHECK(position->drop == 0); } UNSCOPED_INFO("read only, said"); - CHECK(part("Read")->toolTip == std::string("You can view this script but not change it")); + CHECK(readOnly->toolTip == std::string("You can view this script but not change it")); { INFO("the problems pressable"); - CHECK(part("2 errors")->value == "problems"); - CHECK(part("1 warning")->toolTip == "problems tip"); + CHECK(errors->value == "problems"); + REQUIRE(warnings != nullptr); + CHECK(warnings->toolTip == "problems tip"); } doc.editor->setSelection(ALTextRange(ALTextPos(2, 0), ALTextPos(4, 2))); crumbs->showTrailer(doc); @@ -370,9 +398,10 @@ namespace CHECK(part("4 characters selected") != nullptr); doc.editor->setCaret(ALTextPos(0, 2)); crumbs->showTrailer(doc); + const Part* column = part("Ln"); + REQUIRE(column != nullptr); UNSCOPED_INFO("the column as seen, past the tab"); - CHECK(part("Ln")->text == - "Ln 1, Col " + std::to_string(doc.editor->getTabWidth() + 1)); + CHECK(column->text == "Ln 1, Col " + std::to_string(doc.editor->getTabWidth() + 1)); doc.loaded = false; crumbs->showTrailer(doc); { @@ -407,14 +436,18 @@ namespace doc.weighing->sent = true; doc.weighing->weight->total = 60000; crumbs->showTrailer(doc); + weighed = part("LSL (Mono)"); + REQUIRE(weighed != nullptr); UNSCOPED_INFO("as sent"); - CHECK(part("LSL (Mono)")->toolTip.find("as it is saved") != std::string::npos); + CHECK(weighed->toolTip.find("as it is saved") != std::string::npos); UNSCOPED_INFO("near: the warning's"); - CHECK(part("LSL (Mono)")->color == warn); + CHECK(weighed->color == warn); doc.weighing->weight->total = 70000; crumbs->showTrailer(doc); + weighed = part("LSL (Mono)"); + REQUIRE(weighed != nullptr); UNSCOPED_INFO("past: the error's"); - CHECK(part("LSL (Mono)")->color == error); + CHECK(weighed->color == error); doc.weighing->weight->estimate = true; crumbs->showTrailer(doc); UNSCOPED_INFO("an estimate"); @@ -437,10 +470,12 @@ namespace } doc.weighing->assetBytes = LIMIT * 9 / 10 + 1; crumbs->showTrailer(doc); + REQUIRE_FALSE(parts().empty()); UNSCOPED_INFO("near: the warning's"); CHECK(parts().back().color == warn); doc.weighing->assetBytes = LIMIT + 1; crumbs->showTrailer(doc); + REQUIRE_FALSE(parts().empty()); UNSCOPED_INFO("over: the error's"); CHECK(parts().back().color == error); UNSCOPED_INFO("and by how much"); @@ -448,6 +483,7 @@ namespace doc.notecard = true; doc.weighing->assetBytes = static_cast(LLNotecard::MAX_SIZE) / 2 + 1; crumbs->showTrailer(doc); + REQUIRE_FALSE(parts().empty()); UNSCOPED_INFO("a notecard's by its own limit"); CHECK(parts().back().toolTip.find("Notecard text") != std::string::npos); doc.notecard = false; @@ -456,7 +492,8 @@ namespace crumbs->setTips({ "line tip", "problems tip", "source tip", "expanded tip" }); doc.expandedEditor = editor("expanded_a", "integer x;\n"); crumbs->showTrailer(doc); - const Part shown = parts().back(); + REQUIRE_FALSE(parts().empty()); + const Part shown = parts().back(); { INFO("the source in front"); CHECK(shown.text == "Source"); @@ -466,6 +503,7 @@ namespace doc.view = Doc::View::Expanded; doc.expandedEditor->setCaret(ALTextPos(0, 3)); crumbs->showTrailer(doc); + REQUIRE_FALSE(parts().empty()); { INFO("the expansion"); CHECK(parts().back().text == "Preprocessed"); @@ -493,18 +531,24 @@ namespace doc.uploaded.codeAfter = 10240; told().target = ALScriptWeight::Target::Mono; crumbs->showTrailer(doc); + const Part* weighed = part("LSL (Mono)"); + REQUIRE(weighed != nullptr); UNSCOPED_INFO("before and after"); - CHECK(part("LSL (Mono)")->text == std::string("LSL (Mono) ~20.0 \xE2\x86\x92 ~10.0 of 64 KB used")); + CHECK(weighed->text == std::string("LSL (Mono) ~20.0 \xE2\x86\x92 ~10.0 of 64 KB used")); UNSCOPED_INFO("once"); CHECK(parts().size() == size_t(4)); doc.uploaded.codeAfter = 60000; crumbs->showTrailer(doc); + weighed = part("LSL (Mono)"); + REQUIRE(weighed != nullptr); UNSCOPED_INFO("near: the warning's"); - CHECK(part("LSL (Mono)")->color == doc.editor->markColor(ALCodeEditor::Mark::Warning)); + CHECK(weighed->color == doc.editor->markColor(ALCodeEditor::Mark::Warning)); doc.uploaded.codeAfter = 70000; crumbs->showTrailer(doc); + weighed = part("LSL (Mono)"); + REQUIRE(weighed != nullptr); UNSCOPED_INFO("past: the error's"); - CHECK(part("LSL (Mono)")->color == doc.editor->markColor(ALCodeEditor::Mark::Error)); + CHECK(weighed->color == doc.editor->markColor(ALCodeEditor::Mark::Error)); told().target = ALScriptWeight::Target::LSO; crumbs->showTrailer(doc); { @@ -671,7 +715,9 @@ namespace { INFO("other tips: said again"); CHECK(said > 2); - CHECK(part("3 errors")->toolTip == "problems"); + const Part* problems = part("3 errors"); + REQUIRE(problems != nullptr); + CHECK(problems->toolTip == "problems"); } said = 0; Doc& other = tab("b"); diff --git a/indra/newview/tests/alscriptexplorermodel_test.cpp b/indra/newview/tests/alscriptexplorermodel_test.cpp index 207b7cced7..6254da059e 100644 --- a/indra/newview/tests/alscriptexplorermodel_test.cpp +++ b/indra/newview/tests/alscriptexplorermodel_test.cpp @@ -209,6 +209,18 @@ namespace const auto found = std::find_if(all.begin(), all.end(), [&name](const Model::Row& row) { return row.name == name; }); return found != all.end() ? &*found : nullptr; } + + // What the first row of that name stands for, as a click on it + // would choose it. + Model::Choice require_choice(const std::vector& all, const std::string& name) const + { + CAPTURE(name); + const Model::Row* row = rowNamed(all, name); + REQUIRE(row != nullptr); + const std::optional choice = Model::Choice::of(row->value); + REQUIRE(choice.has_value()); + return *choice; + } }; TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: pins come first then the selection then the open scripts' objects", "[alscriptexplorermodel]") @@ -226,7 +238,7 @@ namespace selected = { chair, lamp }; open = { id(21) }; list(); - CHECK(names() == std::string("Lamp* Gone(away)* (unnamed)(away)* Chair Table")); + REQUIRE(names() == std::string("Lamp* Gone(away)* (unnamed)(away)* Chair Table")); { INFO("an object by a prim of it, listed by its root"); CHECK(model.objects().back().root == table); @@ -254,10 +266,14 @@ namespace const LLUUID chair = object(10, "Chair"); selected = { chair }; list(); + const std::vector unknown = model.rows(std::string()); + REQUIRE_FALSE(unknown.empty()); UNSCOPED_INFO("what a prim holds not known yet, a folder of it may still be opened"); - CHECK_FALSE(model.rows(std::string()).front().known); + CHECK_FALSE(unknown.front().known); holds(chair, { "sit.lsl", "readme" }); - CHECK(model.rows(std::string()).front().known); + const std::vector known = model.rows(std::string()); + REQUIRE_FALSE(known.empty()); + CHECK(known.front().known); CHECK(rows() == std::string("O:Chair I:sit.lsl I:readme")); list(); UNSCOPED_INFO("kept through a listing"); @@ -436,8 +452,8 @@ namespace holds(id(10), { "a.lsl", "b.lsl", "notes" }); holds(id(11), { "c.lsl" }); holds(id(12), {}); - const std::vector all = model.rows(std::string()); - Model::Choice whole = *Model::Choice::of(all.front().value); + const std::vector all = model.rows(std::string()); + const Model::Choice whole = require_choice(all, "House"); { INFO("an object row"); CHECK_FALSE(whole.isItem()); @@ -449,9 +465,9 @@ namespace UNSCOPED_INFO("each script once"); CHECK(model.scriptsReached({ whole }) == 3); - Model::Choice prim = *Model::Choice::of(rowNamed(all, "House.1")->value); - Model::Choice c = *Model::Choice::of(rowNamed(all, "c.lsl")->value); - Model::Choice a = *Model::Choice::of(rowNamed(all, "a.lsl")->value); + const Model::Choice prim = require_choice(all, "House.1"); + const Model::Choice c = require_choice(all, "c.lsl"); + const Model::Choice a = require_choice(all, "a.lsl"); { INFO("a prim row"); CHECK(prim.primRow); @@ -471,8 +487,9 @@ namespace } UNSCOPED_INFO("counted once"); CHECK(model.scriptsReached({ prim, c, a }) == 2); + const Model::Choice notes = require_choice(all, "notes"); UNSCOPED_INFO("a notecard chosen reaches no script"); - CHECK(model.scriptsReached({ *Model::Choice::of(rowNamed(all, "notes")->value) }) == 0); + CHECK(model.scriptsReached({ notes }) == 0); } TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: a rename shows at once and a root's new name reaches its object's pin", "[alscriptexplorermodel]") @@ -482,6 +499,8 @@ namespace model.togglePinned(house, "House"); list(); model.renamed(id(11), "Door"); + REQUIRE(model.objects().size() == size_t(1)); + REQUIRE(model.objects().front().prims.size() == size_t(2)); CHECK(model.objects().front().prims[1].name == std::string("Door")); { INFO("a prim is not its object"); @@ -493,6 +512,7 @@ namespace INFO("the root is"); CHECK(model.objects().front().name == "Home"); CHECK(model.takePinsChanged()); + REQUIRE(model.pins().size() == size_t(1)); CHECK(model.pins().front().name == "Home"); } UNSCOPED_INFO("told once"); @@ -543,14 +563,14 @@ namespace // Pinned from the rows: every object among them, as the first is not. const std::vector all = model.rows(std::string()); - model.pin({ *Model::Choice::of(all[0].value), *Model::Choice::of(rowNamed(all, "b.lsl")->value), *Model::Choice::of(rowNamed(all, "Chair")->value) }); + model.pin({ require_choice(all, "House"), require_choice(all, "b.lsl"), require_choice(all, "Chair") }); { INFO("both pinned, each once"); CHECK(model.pins().size() == 2); CHECK(model.isPinned(house)); CHECK(model.isPinned(chair)); } - model.pin({ *Model::Choice::of(rowNamed(all, "Chair")->value) }); + model.pin({ require_choice(all, "Chair") }); { INFO("let go"); CHECK(model.pins().size() == 1); @@ -571,7 +591,7 @@ namespace CHECK(ask(false, false).empty()); model.askRegion(id(11)); UNSCOPED_INFO("the one dropped into, though its object says current"); - CHECK(ask(false, false) == std::vector({ id(11) })); + REQUIRE(ask(false, false) == std::vector({ id(11) })); { INFO("and of the region"); CHECK(model.asksRegion(id(11))); @@ -717,7 +737,10 @@ namespace { INFO("a step is no new place"); CHECK_FALSE(model.takePinsChanged()); - CHECK(model.pinOf(house)->position->mdV[VX] == 256010.0); + const Model::Pin* stepped = model.pinOf(house); + REQUIRE(stepped); + REQUIRE(stepped->position); + CHECK(stepped->position->mdV[VX] == 256010.0); } world[house].position = LLVector3d(256000.0 + 100.0, 256000.0 + 20.0, 30.0); world[house].region = "Morris"; @@ -725,7 +748,9 @@ namespace { INFO("far, and elsewhere: kept, and said"); CHECK(model.takePinsChanged()); - CHECK(model.pinOf(house)->region == "Morris"); + const Model::Pin* moved = model.pinOf(house); + REQUIRE(moved); + CHECK(moved->region == "Morris"); } // Across sessions, and away. diff --git a/indra/newview/tests/alscriptexplorertree_test.cpp b/indra/newview/tests/alscriptexplorertree_test.cpp index 48a91b4445..9ba2090f1e 100644 --- a/indra/newview/tests/alscriptexplorertree_test.cpp +++ b/indra/newview/tests/alscriptexplorertree_test.cpp @@ -394,8 +394,12 @@ namespace CHECK(drags == 1); } CHECK(t.choose(objectRow(house), false)); - UNSCOPED_INFO("an object may not"); - CHECK_FALSE(t.folderView()->getCurSelectedItem()->isMovable()); + item = t.folderView()->getCurSelectedItem(); + { + INFO("an object may not"); + REQUIRE(item); + CHECK_FALSE(item->isMovable()); + } gFocusMgr.setKeyboardFocus(nullptr); } } diff --git a/indra/newview/tests/alscriptinspectorpane_test.cpp b/indra/newview/tests/alscriptinspectorpane_test.cpp index 09adf68b36..e7c72b3ffa 100644 --- a/indra/newview/tests/alscriptinspectorpane_test.cpp +++ b/indra/newview/tests/alscriptinspectorpane_test.cpp @@ -268,7 +268,9 @@ namespace inspector->inspected(doc, result, ALTextPos(0, 8)); UNSCOPED_INFO("in the include"); CHECK(shown() == std::string("integer count\nDeclared in inc.lsl on line 5.")); - const ALTextView::Substitution& to = inspector->view()->substitutions().back(); + const std::vector& to_include = inspector->view()->substitutions(); + REQUIRE_FALSE(to_include.empty()); + const ALTextView::Substitution& to = to_include.back(); { INFO("linked there"); CHECK(to.value["path"].asString() == "disk:/inc.lsl"); @@ -327,7 +329,9 @@ namespace inspector->inspected(doc, result, ALTextPos(0, 8)); UNSCOPED_INFO("in the module"); CHECK(shown() == std::string("function M.twice(n: number): number\nDeclared in util.luau on line 4.")); - const ALTextView::Substitution& to = inspector->view()->substitutions().back(); + const std::vector& to_module = inspector->view()->substitutions(); + REQUIRE_FALSE(to_module.empty()); + const ALTextView::Substitution& to = to_module.back(); { INFO("linked there"); CHECK(to.value["path"].asString() == "disk:/lib/util.luau"); diff --git a/indra/newview/tests/alscriptoutlinepane_test.cpp b/indra/newview/tests/alscriptoutlinepane_test.cpp index ea2467aa97..fa00471df5 100644 --- a/indra/newview/tests/alscriptoutlinepane_test.cpp +++ b/indra/newview/tests/alscriptoutlinepane_test.cpp @@ -174,10 +174,14 @@ namespace REQUIRE(rows() == std::string("count, ^default, .touch_start, .state_entry, alpha")); CHECK(joined(told().shown) == std::string("a")); const std::vector items = outline->list()->getAllData(); + const LLScrollListCell* kind = items[1]->getColumn(0); + const LLScrollListCell* name = items[0]->getColumn(1); + REQUIRE(kind != nullptr); + REQUIRE(name != nullptr); UNSCOPED_INFO("what it is"); - CHECK(items[1]->getColumn(0)->getToolTip() == std::string("state")); + CHECK(kind->getToolTip() == std::string("state")); UNSCOPED_INFO("and its declaration"); - CHECK(items[0]->getColumn(1)->getToolTip() == std::string("variable\ninteger")); + CHECK(name->getToolTip() == std::string("variable\ninteger")); UNSCOPED_INFO("by its index"); CHECK(items[4]->getValue().asInteger() == 4); CHECK(comment().empty()); @@ -238,6 +242,7 @@ namespace ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); LLFilterEditor* filter = window.find("outline_filter"); + REQUIRE(filter != nullptr); filter->setText(std::string("ST")); filter->onCommit(); UNSCOPED_INFO("each with the letters"); @@ -254,6 +259,7 @@ namespace UNSCOPED_INFO("kept"); CHECK(outline->sortOrder() == std::string("name")); LLComboBox* sort = window.find("outline_sort"); + REQUIRE(sort != nullptr); sort->selectByValue(LLSD("kind")); sort->onCommit(); UNSCOPED_INFO("by kind, then name"); @@ -305,7 +311,14 @@ namespace Doc& doc = tab("a"); outline->show(doc); // A mark left on the first row, which a list made again does not have. - outline->list()->getFirstData()->getColumn(1)->setValue(LLSD("marked")); + const auto require_marked = [&]() { + LLScrollListItem* first = outline->list()->getFirstData(); + REQUIRE(first != nullptr); + LLScrollListCell* words = first->getColumn(1); + REQUIRE(words != nullptr); + words->setValue(LLSD("marked")); + }; + require_marked(); outline->show(doc); UNSCOPED_INFO("the same rows"); CHECK(cell(outline->list()->getFirstData(), 1) == std::string("marked")); @@ -313,7 +326,7 @@ namespace outline->show(doc); UNSCOPED_INFO("a detail changed: made again"); CHECK(cell(outline->list()->getFirstData(), 1) != "marked"); - outline->list()->getFirstData()->getColumn(1)->setValue(LLSD("marked")); + require_marked(); Doc& other = tab("b"); other.outline[0].detail = "float"; window.services().front = 1; @@ -339,12 +352,13 @@ namespace { INFO("gone to"); CHECK(joined(told().chosen) == "count"); + REQUIRE_FALSE(window.services().reveals.empty()); CHECK_FALSE(window.services().reveals.back()); } { INFO("return: gone to, to type there"); CHECK(list->handleKeyHere(KEY_RETURN, MASK_NONE)); - CHECK(told().chosen.back() == "count editor"); + CHECK(joined(told().chosen) == "count, count editor"); } UNSCOPED_INFO("the keyboard taken"); CHECK(window.services().reveals.back()); @@ -369,6 +383,7 @@ namespace window.floater->setVisible(true); const LLRect rect = list->getItemListRect(); const LLScrollListColumn* icon = list->getColumn("icon"); + REQUIRE(icon != nullptr); const auto row = [&](S32 at) { const LLScrollListItem* item = list->hitItem(rect.mLeft + 1, at); return item ? item->getValue().asInteger() : -1; @@ -438,6 +453,7 @@ namespace Doc& doc = tab("a"); outline->show(doc); const std::vector before = outline->list()->getAllData(); + REQUIRE(before.size() == 5); doc.caret->crumbPath = { 4 }; outline->followCaret(doc); UNSCOPED_INFO("alpha chosen"); diff --git a/indra/newview/tests/alscriptoutputpane_test.cpp b/indra/newview/tests/alscriptoutputpane_test.cpp index 28d97a5a96..2cc1598a90 100644 --- a/indra/newview/tests/alscriptoutputpane_test.cpp +++ b/indra/newview/tests/alscriptoutputpane_test.cpp @@ -162,16 +162,25 @@ namespace { LLComboBox* combo = window.find("output_filter"); CAPTURE(value); + REQUIRE(combo != nullptr); REQUIRE(combo->selectByValue(LLSD(value))); combo->onCommit(); } - void find(const std::string& words) + // Words typed in the find box, as a commit of it would take them. + void require_find(const std::string& words) { LLFilterEditor* editor = window.find("output_find"); + REQUIRE(editor != nullptr); editor->setText(words); editor->onCommit(); } - const ALOutputView::Entry& last() const { return pane->view()->entries().back(); } + // What the pane said last. + const ALOutputView::Entry& require_last() const + { + const auto& entries = pane->view()->entries(); + REQUIRE_FALSE(entries.empty()); + return entries.back(); + } }; TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: the kind filter picks errors or owner words or debug or the studio's", "[ALScriptOutputPane]") @@ -226,13 +235,13 @@ namespace std::string("|lamp speaks|crate tells its owner|Saved door.lsl.")); require_whose(""); - find("DOOR"); + require_find("DOOR"); UNSCOPED_INFO("the words, in the text or whose it is"); CHECK(shown() == std::string("|door speaks|Saved door.lsl.")); - find(" lamp "); + require_find(" lamp "); UNSCOPED_INFO("trimmed"); CHECK(shown() == std::string("|lamp speaks")); - find(""); + require_find(""); UNSCOPED_INFO("everything"); CHECK(shownTexts().size() == size_t(4)); @@ -272,8 +281,10 @@ namespace CHECK(out.objects().front().first == roots[6]); UNSCOPED_INFO("the next went"); CHECK(std::none_of(out.objects().begin(), out.objects().end(), [&](const auto& one) { return one.first == roots[7]; })); + const LLComboBox* filter = window.find("output_filter"); + REQUIRE(filter != nullptr); UNSCOPED_INFO("and is still chosen"); - CHECK(window.find("output_filter")->getValue().asString() == roots[6].asString()); + CHECK(filter->getValue().asString() == roots[6].asString()); } TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: an error or failure out of sight is unread until seen or cleared", "[ALScriptOutputPane]") @@ -307,7 +318,9 @@ namespace out.said("Could not save.", true, nullptr, {}); UNSCOPED_INFO("a failure out of sight is not"); CHECK(out.unread()); - window.find("output_clear")->onCommit(); + LLButton* clear = window.find("output_clear"); + REQUIRE(clear != nullptr); + clear->onCommit(); UNSCOPED_INFO("cleared"); CHECK_FALSE(out.unread()); UNSCOPED_INFO("with what was said"); @@ -319,7 +332,7 @@ namespace ALScriptOutputPane& out = require_pane(); ALScriptStudioDoc& doc = services.addDoc("door-script", ALScriptRef(fresh(), fresh()), "door.lsl"); out.said("Could not save door.lsl.", true, &doc, { "retry", "copy" }); - const ALOutputView::Entry& entry = last(); + const ALOutputView::Entry& entry = require_last(); UNSCOPED_INFO("the name and two things to do"); REQUIRE(entry.links.size() == size_t(3)); UNSCOPED_INFO("the name, where the words say it"); @@ -348,7 +361,8 @@ namespace // Said of no tab: nothing to link. out.said("Nothing open.", false, nullptr, { "retry" }); - CHECK(last().links.empty()); + const ALOutputView::Entry& unlinked = require_last(); + CHECK(unlinked.links.empty()); // The tab closed since: nothing asked of the window. services.docs.clear(); out.choose(chosen); @@ -387,7 +401,7 @@ namespace CHECK(at.line == 4); UNSCOPED_INFO("in the script"); CHECK(at.file.empty()); - const ALOutputView::Entry& entry = last(); + const ALOutputView::Entry& entry = require_last(); UNSCOPED_INFO("a link to it"); CHECK(entry.link); UNSCOPED_INFO("at that line"); @@ -428,7 +442,7 @@ namespace services.docs.clear(); UNSCOPED_INFO("as said"); CHECK(out.heard(error).line == 7); - chosen = last(); + chosen = require_last(); out.choose(chosen); { INFO("gone to as the region counts it"); @@ -586,7 +600,7 @@ namespace Event error = failed(object, item, "Counter", "bad\rvalue", -1); error.stack = { "lua_script:8: bad\rvalue", "lua_script:3 function helper" }; out.heard(error); - const ALOutputView::Entry& entry = last(); + const ALOutputView::Entry& entry = require_last(); UNSCOPED_INFO("its breaks as the log reads them"); CHECK(entry.text == std::string("bad\nvalue\nlua_script:3 function helper")); UNSCOPED_INFO("a link for the frame"); @@ -601,7 +615,7 @@ namespace // the line it is shown on, and what to do after it. ALScriptStudioDoc& doc = services.addDoc("door-script", ALScriptRef(fresh(), fresh()), "door.lsl"); out.said("Could not save\rdoor.lsl.", true, &doc, { "retry" }); - const ALOutputView::Entry& studio_said = last(); + const ALOutputView::Entry& studio_said = require_last(); UNSCOPED_INFO("the name and what to do"); REQUIRE(studio_said.links.size() == size_t(2)); { diff --git a/indra/newview/tests/alscriptproblemspane_test.cpp b/indra/newview/tests/alscriptproblemspane_test.cpp index 998c2d11f0..963289cff2 100644 --- a/indra/newview/tests/alscriptproblemspane_test.cpp +++ b/indra/newview/tests/alscriptproblemspane_test.cpp @@ -30,6 +30,7 @@ #include "../alscriptstudioweighing.h" #include "alpanefolds.h" +#include "alscriptfixes.h" #include "alpanelist.h" #include "llcheckboxctrl.h" #include "llcombobox.h" @@ -231,12 +232,16 @@ namespace void tick(const char* box, bool on) { LLCheckBoxCtrl* check = window.find(box); + CAPTURE(box); + REQUIRE(check); check->set(on); check->onCommit(); } void pick(const char* combo, const std::string& value) { LLComboBox* box = window.find(combo); + CAPTURE(combo); + REQUIRE(box); INFO("the combo offers " << value); REQUIRE(box->selectByValue(LLSD(value))); box->onCommit(); @@ -278,7 +283,10 @@ namespace { messages.push_back(row.message); } - REQUIRE(made.rows.size() == size_t(6)); + { + CAPTURE(messages); + REQUIRE(made.rows.size() == size_t(6)); + } UNSCOPED_INFO("the weight first, at no line"); CHECK(made.rows[0].origin == services.words("OriginWeight")); UNSCOPED_INFO("the weight a warning, the studio's reckoning"); @@ -354,9 +362,10 @@ namespace CHECK(out.held() == 3); UNSCOPED_INFO("said on the tab"); CHECK(studio.counts > 0); + LLCheckBoxCtrl* errors = window.find("problems_errors"); + REQUIRE(errors); UNSCOPED_INFO("counted on the box"); - CHECK(window.find("problems_errors")->getLabel() == - services.words("FilterErrorsCount", { { "[COUNT]", "1" } })); + CHECK(errors->getLabel() == services.words("FilterErrorsCount", { { "[COUNT]", "1" } })); tick("problems_errors", false); UNSCOPED_INFO("the filter kept"); @@ -369,14 +378,16 @@ namespace UNSCOPED_INFO("the lint's alone"); CHECK(shown() == "|door warning|door note|#" + services.counted("ProblemsHidden", 1)); pick("problems_origin", ""); - window.find("problems_filter")->setText(std::string("NOTE")); - window.find("problems_filter")->onCommit(); + LLFilterEditor* filter = window.find("problems_filter"); + REQUIRE(filter); + filter->setText(std::string("NOTE")); + filter->onCommit(); UNSCOPED_INFO("the words, in either case"); CHECK(shown() == "|door note|#" + services.counted("ProblemsHidden", 2)); UNSCOPED_INFO("rows to show: nothing said over them"); CHECK_FALSE(out.list()->saysEmpty()); - window.find("problems_filter")->setText(std::string()); - window.find("problems_filter")->onCommit(); + filter->setText(std::string()); + filter->onCommit(); pick("problems_scope", "all"); UNSCOPED_INFO("every script's, each under its name"); @@ -427,6 +438,7 @@ namespace gather(d); out.fill(&d); out.selectFirstError(false); + REQUIRE_FALSE(studio.chosen.empty()); UNSCOPED_INFO("the first error, the compiler's"); CHECK(studio.chosen.back().line == 1); UNSCOPED_INFO("taken to the script"); @@ -452,10 +464,12 @@ namespace CHECK(studio.chosen.back().line == 0); REQUIRE(studio.toEditor.back()); } - const size_t gone = studio.chosen.size(); + const size_t gone = studio.chosen.size(); + const size_t revealed = services.reveals.size(); { INFO("escape"); CHECK(out.list()->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + REQUIRE(services.reveals.size() == revealed + 1); REQUIRE(services.reveals.back()); REQUIRE(studio.chosen.size() == gone); } @@ -534,6 +548,7 @@ namespace CHECK(studio.kinds[1] == std::string("door:")); out.act("copy_all"); + REQUIRE_FALSE(services.statuses.empty()); UNSCOPED_INFO("copied, and said"); CHECK(services.statuses.back() == services.counted("ProblemsCopied", 3)); @@ -604,6 +619,8 @@ namespace } UNSCOPED_INFO("the lint named"); CHECK(named); + UNSCOPED_INFO("by the name a NOLINT comment turns it off by"); + CHECK(ALScriptFixes::suppressed(unused, "integer x; // NOLINT(DeclaredButNotUsed)", "", false)); UNSCOPED_INFO("the error no number"); CHECK_FALSE(numbered); LLComboBox* origins = window.find("problems_origin"); @@ -704,7 +721,7 @@ namespace at = rows[i]->getValue()["owner"].asString().rfind("checked:", 0) == 0 ? static_cast(i) : at; } UNSCOPED_INFO("a row of it"); - CHECK(at >= 0); + REQUIRE(at >= 0); list()->selectNthItem(at); pane->choose(false); REQUIRE_FALSE(studio.chosen.empty()); @@ -792,6 +809,7 @@ namespace LLTabContainer* tabs = window.find("bottom_tabs"); Doc& door = doc("door"); door.check->analysis = { problem(S::Parser, V::Error, 1, "door error"), problem(S::Lint, V::Warning, 3, "door warning") }; + REQUIRE(tabs); tabs->selectTabByName("references_tab"); CHECK_FALSE(ALPaneFolds::inSight(&out)); const S32 said = studio.counts; @@ -1052,6 +1070,7 @@ namespace } choose("the compiler's, nowhere"); out.choose(false); + REQUIRE_FALSE(studio.chosen.empty()); UNSCOPED_INFO("chosen at none"); CHECK(studio.chosen.back().line == -1); choose("the compiler's, at 3"); diff --git a/indra/newview/tests/alscriptreferencespane_test.cpp b/indra/newview/tests/alscriptreferencespane_test.cpp index 46e7f84b33..ec222b1987 100644 --- a/indra/newview/tests/alscriptreferencespane_test.cpp +++ b/indra/newview/tests/alscriptreferencespane_test.cpp @@ -30,6 +30,7 @@ #include "alscriptstudio_fixture.h" #include "lltabcontainer.h" #include "llbutton.h" +#include "llclipboard.h" #include "llfocusmgr.h" #include "llscrolllistcell.h" #include "llscrolllistitem.h" @@ -156,6 +157,7 @@ namespace CHECK(window.pane().counts == 1); CHECK(lines() == joined(Names{ "A 1:9", "A 2:5", "B 1:9" })); const std::vector rows = refs->list()->getAllData(); + REQUIRE(rows.size() == size_t(3)); CHECK(cell(rows[0], 2) == std::string("declaration")); UNSCOPED_INFO("in the script it is declared in only"); CHECK(cell(rows[2], 2) == std::string()); @@ -166,12 +168,14 @@ namespace Found twice = found(); twice.places.push_back(place("", 0, 20, "integer count; count")); refs->show(twice); + REQUIRE(refs->list()->getItemCount() == 4); UNSCOPED_INFO("on the declaration's line, not it"); CHECK(cell(refs->list()->getAllData()[3], 2).empty()); Found in_b = found(); in_b.home = B; refs->show(in_b); + REQUIRE(refs->list()->getItemCount() == 3); UNSCOPED_INFO("declared in B: A's first line not it"); CHECK(cell(refs->list()->getAllData()[0], 2).empty()); refs->forget(); @@ -217,6 +221,7 @@ namespace refs->pump(); UNSCOPED_INFO("B's by its path"); CHECK(lines() == joined(Names{ "A 2:9", "A 3:5", "B 1:11" })); + REQUIRE(refs->found().places.size() == size_t(3)); UNSCOPED_INFO("its line's words"); CHECK(refs->found().places[2].text == std::string("foo() { count; }")); @@ -231,6 +236,7 @@ namespace refs->pump(); UNSCOPED_INFO("a place on the line edited"); CHECK(lines() == joined(Names{ "A 3:6", "B 1:11" })); + REQUIRE_FALSE(refs->found().places.empty()); UNSCOPED_INFO("its words again"); CHECK(refs->found().places[0].text == std::string("yy = count;")); const S32 counts = window.pane().counts; @@ -257,7 +263,7 @@ namespace refs->list()->onCommit(); refs->choose(true); UNSCOPED_INFO("gone to"); - CHECK(joined(window.pane().chosen) == joined(Names{ "a|" + B + "|0", "a|" + B + "|0 editor" })); + REQUIRE(joined(window.pane().chosen) == joined(Names{ "a|" + B + "|0", "a|" + B + "|0 editor" })); refs->rekey("elsewhere", "z"); UNSCOPED_INFO("another's id"); CHECK(refs->found().from == std::string("a")); @@ -273,10 +279,18 @@ namespace CHECK(window.pane().chosen.back() == "a2||0 editor"); const size_t gone = window.pane().chosen.size(); CHECK(refs->list()->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + REQUIRE_FALSE(window.services().reveals.empty()); CHECK(window.services().reveals.back()); CHECK(window.pane().chosen.size() == gone); - UNSCOPED_INFO("copied"); CHECK(refs->list()->handleKeyHere('C', MASK_CONTROL)); + std::string copied; + LLClipboard::instance().pasteFromClipboard(copied); + { + INFO("the row chosen copied, and no other: " + copied); + CHECK(copied.find("integer count;") != std::string::npos); + CHECK(copied.find("x = count;") == std::string::npos); + CHECK(copied.find("foo() { count; }") == std::string::npos); + } } TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: rows sort by a column and then by script and place", "[alscriptreferencespane]") @@ -363,6 +377,7 @@ namespace // Left out by Space on the row chosen: its box clear, its line as it is. refs->list()->selectNthItem(1); refs->list()->handleUnicodeCharHere(' '); + REQUIRE(refs->list()->getItemCount() == 3); UNSCOPED_INFO("left out"); CHECK_FALSE(refs->list()->getAllData()[1]->getColumn(2)->getValue().asBoolean()); UNSCOPED_INFO("its line as it is"); @@ -423,6 +438,7 @@ namespace CHECK_FALSE(changes->getVisible()); { INFO("each row as found"); + REQUIRE(refs->list()->getItemCount() == 3); CHECK(cell(refs->list()->getAllData()[0], 3) == "integer count;"); CHECK(refs->list()->getAllData()[0]->getColumn(2)->getValue().asString() != "1"); } @@ -442,6 +458,7 @@ namespace refs->pump(); UNSCOPED_INFO("slid, not listed again unseen"); CHECK(lines() == joined(Names{ "A 1:9", "A 2:5", "B 1:9" })); + REQUIRE_FALSE(refs->found().places.empty()); UNSCOPED_INFO("the places themselves slid"); CHECK(refs->found().places[0].span.line == 1); tabs->selectTabByName("references_tab"); @@ -459,8 +476,10 @@ namespace boost::signals2::scoped_connection in_a( a.editor->document().onChanged([refs, &a](const ALTextDocument::Edit& edit) { refs->slide(a, "object:1:a", edit); })); LLScrollListItem* in_b = refs->list()->rowWithKey("3"); - LLScrollListCell* b_line = in_b ? in_b->getColumn(1) : nullptr; + REQUIRE(in_b); + LLScrollListCell* b_line = in_b->getColumn(1); LLScrollListItem* second = refs->list()->rowWithKey("2"); + CHECK(second); refs->list()->selectByValue(LLSD(2)); a.editor->replaceAll({ { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "// one\n" } }); refs->pump(); @@ -486,7 +505,7 @@ namespace } refs->show(many); UNSCOPED_INFO("a thousand and one row saying the rest"); - CHECK(refs->list()->getItemCount() == 1001); + REQUIRE(refs->list()->getItemCount() == 1001); CHECK(cell(refs->list()->getAllData().back(), 3) == window.services().counted("ReferencesUnlisted", 3)); UNSCOPED_INFO("all of them counted over the list"); CHECK(head().find("1003") != std::string::npos); diff --git a/indra/newview/tests/alscriptsearchpane_test.cpp b/indra/newview/tests/alscriptsearchpane_test.cpp index c79d345d47..ee38d65c10 100644 --- a/indra/newview/tests/alscriptsearchpane_test.cpp +++ b/indra/newview/tests/alscriptsearchpane_test.cpp @@ -172,7 +172,9 @@ namespace UNSCOPED_INFO("the skin builds the tab as the pane"); REQUIRE(pane != nullptr); // In sight, its tab the one chosen, as when it is worked in. - window.find("bottom_tabs")->selectTabByName("search_tab"); + LLTabContainer* bottom = window.find("bottom_tabs"); + REQUIRE(bottom != nullptr); + bottom->selectTabByName("search_tab"); return *pane; } @@ -240,11 +242,13 @@ namespace } { INFO("escape"); - REQUIRE(out.list()->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + CHECK(out.list()->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + REQUIRE_FALSE(services.reveals.empty()); CHECK(services.reveals.back()); } UNSCOPED_INFO("copied"); CHECK(out.list()->handleKeyHere('C', MASK_CONTROL)); + REQUIRE(out.search().found()[0].lines.size() == size_t(2)); UNSCOPED_INFO("the line as listed, trimmed"); CHECK(out.search().found()[0].lines[1] == std::string("timer two")); CHECK(counted() == services.words("SearchCount", { { "[HITS]", services.counted("Matches", 2) }, @@ -304,6 +308,7 @@ namespace out.fetched(studio.fetches[1].generation, "Thing", away, "away.lsl", std::make_shared("timer\nother\n"), false); UNSCOPED_INFO("the second's kept"); REQUIRE(out.search().found().size() == size_t(1)); + REQUIRE_FALSE(out.search().found()[0].places.empty()); UNSCOPED_INFO("for what it looked for"); CHECK(out.search().found()[0].places[0].begin.line == 1); } @@ -331,16 +336,24 @@ namespace door.editor->insertText("yy"); out.typedIn(door); out.pump(); + // A script searched again keeps its place in found(), whether it + // holds places now or none: found() sized once, its places after + // each pump. + REQUIRE(out.search().found().size() == size_t(1)); + REQUIRE_FALSE(out.search().found()[0].places.empty()); UNSCOPED_INFO("not at once"); CHECK(out.search().found()[0].places[0].begin.column == 1); std::this_thread::sleep_for(std::chrono::milliseconds(700)); LLTabContainer* tabs = window.find("bottom_tabs"); + REQUIRE(tabs != nullptr); tabs->selectTabByName("problems_tab"); out.pump(); + REQUIRE_FALSE(out.search().found()[0].places.empty()); UNSCOPED_INFO("due, but out of sight: waiting"); CHECK(out.search().found()[0].places[0].begin.column == 1); tabs->selectTabByName("search_tab"); out.pump(); + REQUIRE_FALSE(out.search().found()[0].places.empty()); UNSCOPED_INFO("then, seen, where it is now"); CHECK(out.search().found()[0].places[0].begin.column == 3); out.replaceAll(); @@ -361,6 +374,7 @@ namespace studio.objects = { { root, "Thing", { door.ref, away, card } } }; studio.inHand = root; find("timer", "object"); + REQUIRE(studio.fetches.size() == size_t(2)); out.fetched(studio.fetches[0].generation, "Thing", away, "away.lsl", std::make_shared("timer\n"), false); out.fetched(studio.fetches[1].generation, "Thing", card, "card", std::make_shared("a timer\n"), true); UNSCOPED_INFO("three found"); @@ -401,6 +415,7 @@ namespace } UNSCOPED_INFO("and each is made"); CHECK(studio.applied.size() == size_t(2)); + REQUIRE_FALSE(services.reports.empty()); CHECK(services.reports.back().text == services.words("SearchReplaced", { { "[PLACES]", services.counted("Places", 3) }, { "[SCRIPTS]", services.counted("ScriptsNotecards", 3) } }) + "; " + @@ -560,8 +575,16 @@ namespace TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: Sources keeps a script by its text and drops the oldest past the limit", "[ALScriptSearchPane]") { + // The viewer's one store: emptied before the case and after it, + // however it ends. + struct Emptied + { + ALScriptSearch::Sources& sources; + ~Emptied() { sources.clear(); } + }; ALScriptSearch::Sources& sources = ALScriptSearch::Sources::instance(); sources.clear(); + const Emptied emptied{ sources }; const ALScriptRef one(LLUUID::generateNewID(), LLUUID::generateNewID()); const LLUUID asset = LLUUID::generateNewID(); sources.keep(one, LLUUID::null, { std::make_shared("timer"), "one.lsl", false }); @@ -610,7 +633,6 @@ namespace CHECK(sources.find(refs[0], assets[0])); CHECK(sources.find(refs[2], assets[2])); } - sources.clear(); } TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: a fetched script is sought apart and an earlier search's is dropped", "[ALScriptSearchPane]") @@ -664,6 +686,7 @@ namespace out.typedIn(door); std::this_thread::sleep_for(std::chrono::milliseconds(700)); out.pump(); + REQUIRE_FALSE(out.search().found()[0].places.empty()); UNSCOPED_INFO("typed in: searched again"); CHECK(out.search().found()[0].places[0].begin.column == 1); } diff --git a/indra/newview/tests/alscriptweightspane_test.cpp b/indra/newview/tests/alscriptweightspane_test.cpp index 11aec4dba9..86e7f432f1 100644 --- a/indra/newview/tests/alscriptweightspane_test.cpp +++ b/indra/newview/tests/alscriptweightspane_test.cpp @@ -62,7 +62,21 @@ namespace REQUIRE(pane != nullptr); return *pane; } - ALPaneList* list(const char* name) const { return floater ? floater->findChild(name, true) : nullptr; } + // One of the tab's lists, by its name in the skin. + ALPaneList* require_list(const char* name) const + { + ALPaneList* found = floater ? floater->findChild(name, true) : nullptr; + CAPTURE(name); + REQUIRE(found != nullptr); + return found; + } + // The head over the lists, which says whose weights they are. + LLTextBox* require_head() const + { + LLTextBox* head = floater ? floater->findChild("weights_head", true) : nullptr; + REQUIRE(head != nullptr); + return head; + } static std::string cell(const LLScrollListItem* item, S32 column) { @@ -148,8 +162,8 @@ namespace REQUIRE(tab() != nullptr); ALScriptWeightsPane& pane = require_pane(); pane.show(lsl()); - ALPaneList* targets = list("weights_targets"); - ALPaneList* parts = list("weights_parts"); + ALPaneList* targets = require_list("weights_targets"); + ALPaneList* parts = require_list("weights_parts"); REQUIRE(targets->getItemCount() == 3); UNSCOPED_INFO("its own first"); CHECK(cell(targets->getAllData()[0], 0) == std::string("LSL (LSO)")); @@ -178,7 +192,7 @@ namespace CHECK(std::find(lines.begin(), lines.end(), "lib.lsl:1") != lines.end()); UNSCOPED_INFO("the script's own at its line" + joined(lines)); CHECK(std::find(lines.begin(), lines.end(), "8") != lines.end()); - const std::string head = floater->findChild("weights_head", true)->getText(); + const std::string head = require_head()->getText(); { INFO("the head says whose and on what: " + head); CHECK(head.find("Door") != std::string::npos); @@ -196,8 +210,8 @@ namespace } ALScriptWeightsPane& pane = require_pane(); pane.show(lsl()); - ALPaneList* targets = list("weights_targets"); - ALPaneList* parts = list("weights_parts"); + ALPaneList* targets = require_list("weights_targets"); + ALPaneList* parts = require_list("weights_parts"); parts->selectByValue(LLSD(2)); std::optional place = pane.chosenPlace(parts); @@ -246,8 +260,10 @@ namespace saved.parts.erase(saved.parts.begin() + 2); shown.saved = { saved }; pane.show(shown); - ALPaneList* targets = list("weights_targets"); - ALPaneList* parts = list("weights_parts"); + ALPaneList* targets = require_list("weights_targets"); + ALPaneList* parts = require_list("weights_parts"); + REQUIRE(targets->getItemCount() == 3); + REQUIRE(parts->getItemCount() == 5); UNSCOPED_INFO("the target's whole"); CHECK(cell(targets->getAllData()[0], 3) == std::string("+96")); UNSCOPED_INFO("nothing for a target not weighed then"); @@ -291,10 +307,13 @@ namespace ALScriptWeightsPane& pane = require_pane(); pane.show(lsl()); pane.showNothing("Nothing open to weigh."); - CHECK(list("weights_targets")->getItemCount() == 0); - CHECK(list("weights_parts")->getItemCount() == 0); + ALPaneList* targets = require_list("weights_targets"); + ALPaneList* parts = require_list("weights_parts"); + LLTextBox* head = require_head(); + CHECK(targets->getItemCount() == 0); + CHECK(parts->getItemCount() == 0); CHECK_FALSE(pane.chosenPlace(pane.partsList())); - CHECK(floater->findChild("weights_head", true)->getText() == std::string("Nothing open to weigh.")); + CHECK(head->getText() == std::string("Nothing open to weigh.")); UNSCOPED_INFO("and whose it is forgotten"); CHECK(pane.shownId().empty()); } @@ -321,7 +340,7 @@ namespace shown.weights = { slua }; shown.saved = { slua }; pane.show(shown); - std::vector changes = column(list("weights_parts"), 4); + std::vector changes = column(require_list("weights_parts"), 4); UNSCOPED_INFO("nothing changed"); CHECK(joined(changes) == std::string("|||")); // The middle one heavier, and one added after them. @@ -330,7 +349,7 @@ namespace now.parts.push_back(part(Kind::Function, "", 40, 12)); shown.weights = { now }; pane.show(shown); - changes = column(list("weights_parts"), 4); + changes = column(require_list("weights_parts"), 4); UNSCOPED_INFO("the middle one, and one new"); CHECK(joined(changes) == std::string("||+50||new")); } @@ -355,7 +374,7 @@ namespace shown.weights = { slua }; shown.handlers[0] = "touch_start"; pane.show(shown); - const std::string names = joined(column(list("weights_parts"), 0)); + const std::string names = joined(column(require_list("weights_parts"), 0)); UNSCOPED_INFO("the handler by its event: " + names); CHECK(names.find("touch_start") != std::string::npos); UNSCOPED_INFO("the other with none: " + names); @@ -374,7 +393,7 @@ namespace } ALScriptWeightsPane& pane = require_pane(); ALScriptWeightsPane::Shown shown = lsl(); - LLTextBox* head = floater->findChild("weights_head", true); + LLTextBox* head = require_head(); pane.show(shown); UNSCOPED_INFO("nothing of the region"); CHECK(head->getText().find("region") == std::string::npos); @@ -449,11 +468,11 @@ namespace shown.weights[2].total = 2048; std::swap(shown.weights[0], shown.weights[2]); pane.show(shown); - const std::string luau = floater->findChild("weights_head", true)->getText(); + const std::string luau = require_head()->getText(); UNSCOPED_INFO("said for LSL on Luau: " + luau); CHECK(luau.find("records no lines") != std::string::npos); pane.show(lsl()); - const std::string lso = floater->findChild("weights_head", true)->getText(); + const std::string lso = require_head()->getText(); UNSCOPED_INFO("not for LSO: " + lso); CHECK(lso.find("records no lines") == std::string::npos); } @@ -498,12 +517,9 @@ namespace shown.weights = { slua }; pane.show(shown); - ALPaneList* strings = list("weights_strings"); - { - INFO("the list there"); - REQUIRE(strings); - CHECK(strings->getParent()->getVisible()); - } + ALPaneList* strings = require_list("weights_strings"); + UNSCOPED_INFO("the list there"); + CHECK(strings->getParent()->getVisible()); UNSCOPED_INFO("a start, then each string"); REQUIRE(strings->getItemCount() == 5); const std::vector texts = column(strings, 0); @@ -540,8 +556,9 @@ namespace REQUIRE(place); CHECK(place->line == 2); } + ALPaneList* parts = require_list("weights_parts"); UNSCOPED_INFO("not the parts' choice"); - CHECK_FALSE(pane.chosenPlace(list("weights_parts"))); + CHECK_FALSE(pane.chosenPlace(parts)); // Kept chosen through a refill, by what it is. strings->selectByValue(LLSD(1)); @@ -585,8 +602,10 @@ namespace kept = start; of = texts; }); + LLButton* keep = floater->findChild("weights_keep_start", true); + REQUIRE(keep); strings->selectByValue(LLSD(0)); - floater->findChild("weights_keep_start", true)->onCommit(); + keep->onCommit(); { INFO("asked with the start and its strings"); CHECK(kept == "You have touched me, "); @@ -595,13 +614,13 @@ namespace } kept.clear(); strings->selectByValue(LLSD(1)); - floater->findChild("weights_keep_start", true)->onCommit(); + keep->onCommit(); UNSCOPED_INFO("not for a string"); CHECK(kept.empty()); // Nor in a script that cannot be written to. pane.setKeepStart([&](const std::string& start, const std::vector&) { kept = start; }, []() { return false; }); strings->selectByValue(LLSD(0)); - floater->findChild("weights_keep_start", true)->onCommit(); + keep->onCommit(); UNSCOPED_INFO("not where the script cannot be written to"); CHECK(kept.empty()); From caa291f03b10334874902fbe7decdfbff8bf5181 Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 04:47:55 -0400 Subject: [PATCH 2/9] Script Studio's checking, lookup and comparison tests check what they say they check Old test bugs TM24's idiom pass found (83b0c1dd65), fixed. - alscriptstudiocaret: a required module's second answer could pass on the first's status; the statuses are counted. - alscriptstudiohistory: the sizes said were checked for "longer" only; "shorter" too. - alscriptanalyzers: the outline's first entry, the function the message names, is checked. - alscriptlookup: where a later round is read through .back(), its vectors are counted, so it is that round's request and not the first's. - alscriptstudiowords: every word looked up goes through known(), which requires it. - Asks, expansions, analyses, fixes and their edits, candidates, listings, statuses, the comparison's view and titles, held selections and request texts are required before they are read in these and alscriptstudiochecking, alscriptobjectcheck, alscriptstudiocomparewith, alscriptstudiocomparepairs, alscriptstudioexpandedcompare, alscriptstudiomerging, alscriptstudioselections, alscripttypes and alscriptstudioweighing. Each new or corrected check fails under a small change to the test's own input or expectation, at the check and not by crashing. Co-Authored-By: Claude Opus 5.5 --- .../newview/tests/alscriptanalyzers_test.cpp | 5 ++ indra/newview/tests/alscriptlookup_test.cpp | 38 ++++++-- .../tests/alscriptobjectcheck_test.cpp | 3 + .../tests/alscriptstudiocaret_test.cpp | 7 ++ .../tests/alscriptstudiochecking_test.cpp | 54 ++++++++++- .../tests/alscriptstudiocomparepairs_test.cpp | 2 + .../tests/alscriptstudiocomparewith_test.cpp | 7 ++ .../alscriptstudioexpandedcompare_test.cpp | 3 + .../tests/alscriptstudiohistory_test.cpp | 25 +++++- .../tests/alscriptstudiomerging_test.cpp | 15 +++- .../tests/alscriptstudioselections_test.cpp | 9 +- .../tests/alscriptstudioweighing_test.cpp | 5 ++ .../tests/alscriptstudiowords_test.cpp | 89 +++++++++++++------ indra/newview/tests/alscripttypes_test.cpp | 1 + 14 files changed, 224 insertions(+), 39 deletions(-) diff --git a/indra/newview/tests/alscriptanalyzers_test.cpp b/indra/newview/tests/alscriptanalyzers_test.cpp index 5e380e0503..70d54baf19 100644 --- a/indra/newview/tests/alscriptanalyzers_test.cpp +++ b/indra/newview/tests/alscriptanalyzers_test.cpp @@ -72,6 +72,11 @@ namespace lsl.answer(shaped, *shaped.text, setup, lsl_shape); UNSCOPED_INFO("LSL: the function, the state, its event"); REQUIRE(lsl_shape.outline.size() == size_t(3)); + { + INFO("the function first"); + CHECK(lsl_shape.outline[0].kind == ALScriptSymbolKind::Function); + CHECK(lsl_shape.outline[0].name == "go"); + } { INFO("the event in its state"); CHECK(lsl_shape.outline[1].kind == ALScriptSymbolKind::State); diff --git a/indra/newview/tests/alscriptlookup_test.cpp b/indra/newview/tests/alscriptlookup_test.cpp index 446e97c222..7becc51526 100644 --- a/indra/newview/tests/alscriptlookup_test.cpp +++ b/indra/newview/tests/alscriptlookup_test.cpp @@ -487,8 +487,11 @@ namespace REQUIRE(studio.loads.size() == size_t(1)); UNSCOPED_INFO("the open one passed over"); CHECK(studio.expands.empty()); - UNSCOPED_INFO("waiting said"); - CHECK(has(services.statuses.back(), "LookingAcross [COUNT]=1 [NAME]=count")); + { + INFO("waiting said"); + REQUIRE_FALSE(services.statuses.empty()); + CHECK(has(services.statuses.back(), "LookingAcross [COUNT]=1 [NAME]=count")); + } UNSCOPED_INFO("nothing shown yet"); CHECK(studio.shows == 0); @@ -559,6 +562,7 @@ namespace } studio.expands[0].expanded(expansion("B", ALScriptPreprocessor::pathOf(b), B_TEXT, "inc.lsl", inc, "integer count;\n")); + REQUIRE(studio.asks.size() == size_t(1)); studio.asks[0].answered(answer({ span(0, 8, 5) })); { INFO("still one to come"); @@ -585,7 +589,11 @@ namespace studio.lines.clear(); unit->start(doc, ALEditorCommand::FindReferences, refsOf("count"), true, inc, span(0, 8, 5), {}, doc.editor->document().version()); + // This lookup's own questions answered, not the last one's. + CHECK(studio.expands.size() == size_t(2)); + CHECK(studio.loads.size() == size_t(2)); studio.expands.back().expanded(expansion("B", ALScriptPreprocessor::pathOf(b), B_TEXT, "inc.lsl", inc, "integer count;\n")); + CHECK(studio.asks.size() == size_t(2)); studio.asks.back().answered(answer({ span(0, 8, 5) })); studio.loads.back().loaded(LLUUID::null, "default { }\n"); UNSCOPED_INFO("shown again"); @@ -606,6 +614,7 @@ namespace lookUp(ALEditorCommand::FindReferences, false); UNSCOPED_INFO("the second shown"); CHECK(studio.shows == 1); + REQUIRE(studio.loads.size() == size_t(1)); studio.loads[0].loaded(LLUUID::null, B_TEXT); { INFO("the first's answer dropped"); @@ -614,7 +623,9 @@ namespace } lookUp(ALEditorCommand::FindReferences); + CHECK(studio.loads.size() == size_t(2)); studio.loads.back().loaded(LLUUID::null, B_TEXT); + REQUIRE(studio.expands.size() == size_t(1)); studio.expands.back().expanded(expansion("B", ALScriptPreprocessor::pathOf(b), B_TEXT)); UNSCOPED_INFO("no declaration in it: nothing asked"); CHECK(studio.asks.empty()); @@ -624,12 +635,15 @@ namespace const U32 generation = doc.lookup->generation; lookUp(ALEditorCommand::FindReferences); CHECK(doc.lookup->generation != generation); + CHECK(studio.loads.size() == size_t(3)); studio.loads.back().loaded(LLUUID::null, B_TEXT); + CHECK(studio.expands.size() == size_t(2)); // A lookup of another tab's, still waiting on its read, as this // one waits on its expansion: both current, and both gone with it. Doc& other = tab("x", c, A_TEXT, "X"); unit->start(other, ALEditorCommand::FindReferences, refsOf("count"), true, std::string(), span(0, 8, 5), {}, other.editor->document().version()); + CHECK(studio.loads.size() == size_t(4)); const size_t expands = studio.expands.size(); unit.reset(); studio.expands.back().expanded( @@ -650,6 +664,7 @@ namespace lookUp(ALEditorCommand::Rename, false, false); { INFO("not renamable"); + REQUIRE_FALSE(services.statuses.empty()); CHECK(has(services.statuses.back(), "NotRenamable")); CHECK(studio.askedName.empty()); } @@ -702,6 +717,7 @@ namespace CHECK(has(studio.hint("sum"), "RenameToAcross [COUNT]=9 [FILES]=6")); studio.chosen("9lives"); + REQUIRE_FALSE(services.statuses.empty()); CHECK(has(services.statuses.back(), "RenameBadName")); CHECK(doc.editor->text() == A_TEXT); studio.chosen("state"); @@ -743,8 +759,12 @@ namespace } UNSCOPED_INFO("a file opened"); CHECK(studio.filesOpened == Names{ "/e.lsl" }); - UNSCOPED_INFO("and changed"); - CHECK(services.findDoc("disk:/e.lsl")->editor->text() == std::string("integer total = 4;\n")); + const Doc* file = services.findDoc("disk:/e.lsl"); + { + INFO("and changed"); + REQUIRE(file); + CHECK(file->editor->text() == std::string("integer total = 4;\n")); + } UNSCOPED_INFO("said once"); REQUIRE(services.reports.size() == size_t(1)); const al_studio_test::FakeServices::Said& said = services.reports[0]; @@ -767,6 +787,7 @@ namespace Doc& doc = lookUp(ALEditorCommand::Rename, false); doc.editor->insertText(" "); studio.chosen("total"); + REQUIRE_FALSE(services.statuses.empty()); CHECK(has(services.statuses.back(), "RenameStale [NAME]=total")); CHECK(services.reports.empty()); @@ -943,6 +964,7 @@ namespace studio.previewed("9lives"); { INFO("no name: nothing shown"); + REQUIRE_FALSE(services.statuses.empty()); CHECK(has(services.statuses.back(), "RenameBadName")); CHECK_FALSE(studio.apply); } @@ -959,7 +981,9 @@ namespace CHECK_FALSE(has(studio.previewSaid, "RenamePreviewHint")); } studio.apply({ 1 }); - Doc& doc = *services.findDoc("a"); + Doc* found = services.findDoc("a"); + REQUIRE(found); + Doc& doc = *found; UNSCOPED_INFO("made at the one kept"); CHECK(doc.editor->text() == std::string("integer count;\ndefault { state_entry() { sum = 1; } }\n")); @@ -1017,6 +1041,7 @@ namespace CHECK(studio.shows == 0); studio.loads[1].loaded(LLUUID::null, std::string("default { }\n")); CHECK(studio.shows == 1); + REQUIRE_FALSE(services.statuses.empty()); const std::string said = services.statuses.back(); { INFO("found, then what was passed over: " + said); @@ -1101,6 +1126,7 @@ namespace UNSCOPED_INFO("beside it as it is"); CHECK(studio.compared.back() == "a: " + std::string(A_TEXT) + " | integer count;\ndefault { state_entry() { sum = 1; } }\n (CompareNow | RenameAfter [NAME]=sum [SCRIPT]=A)"); + REQUIRE_FALSE(studio.activated.empty()); UNSCOPED_INFO("in its tab, brought forward"); CHECK(studio.activated.back() == "a"); UNSCOPED_INFO("nothing changed"); @@ -1118,6 +1144,7 @@ namespace { INFO("not open: said, not shown"); CHECK(studio.compared.size() == before); + REQUIRE_FALSE(services.statuses.empty()); CHECK(has(services.statuses.back(), "RenameChangesNotHere [NAME]=sum [SCRIPT]=D")); CHECK(services.statusFailures.back()); } @@ -1251,6 +1278,7 @@ namespace } studio.expands[0].expanded(expansion("lib.luau", LIB, LIB_TEXT)); studio.expands[1].expanded(expansion("b.luau", B, B_LUA, "lib.luau", LIB, LIB_TEXT)); + REQUIRE(studio.asks.size() == size_t(2)); studio.asks[0].answered(answer({ span(1, 11, 5) })); studio.asks[1].answered(answer({ span(1, 11, 5), span(4, 10, 5) })); UNSCOPED_INFO("the other's place, by its path"); diff --git a/indra/newview/tests/alscriptobjectcheck_test.cpp b/indra/newview/tests/alscriptobjectcheck_test.cpp index b8547080a5..f1382caa83 100644 --- a/indra/newview/tests/alscriptobjectcheck_test.cpp +++ b/indra/newview/tests/alscriptobjectcheck_test.cpp @@ -184,6 +184,7 @@ namespace { INFO("a check of what was read"); REQUIRE(window.asks[0].request.kind == ALScriptAnalysis::Kind::Check); + REQUIRE(window.asks[0].request.text); CHECK(*window.asks[0].request.text == "default {}\n"); } { @@ -269,6 +270,7 @@ namespace result.problems.push_back(said); window.expands[0].expanded(result); REQUIRE(window.asks.size() == size_t(1)); + REQUIRE(window.asks[0].request.text); UNSCOPED_INFO("about the expansion"); CHECK(*window.asks[0].request.text == std::string("integer x;\ndefault {}\n")); @@ -300,6 +302,7 @@ namespace { house(); check.check(root, "House"); + REQUIRE_FALSE(window.asks.empty()); std::function stale = std::move(window.asks[0].answered); window.asks.clear(); window.checked.clear(); diff --git a/indra/newview/tests/alscriptstudiocaret_test.cpp b/indra/newview/tests/alscriptstudiocaret_test.cpp index 0c8de212b1..623bdfbd91 100644 --- a/indra/newview/tests/alscriptstudiocaret_test.cpp +++ b/indra/newview/tests/alscriptstudiocaret_test.cpp @@ -231,6 +231,7 @@ namespace INFO("none: said, no jump"); REQUIRE(studio.said.size() == 1); CHECK(navigation->unit.places(false) == 1); + REQUIRE_FALSE(services.statuses.empty()); CHECK(services.statuses.back().find("NoDefinition [NAME]=count") == 0); } ask(doc, ALEditorCommand::GoToDefinition); @@ -263,6 +264,7 @@ namespace { INFO("finding it: not"); REQUIRE(studio.said.size() == 1); + REQUIRE_FALSE(services.statuses.empty()); CHECK(services.statuses.back().find("NothingKnown") == 0); } caret.ask(doc, ALEditorCommand::GoToDefinition, ALTextRange(ALTextPos(1, 37), ALTextPos(1, 42))); @@ -352,10 +354,12 @@ namespace ask(doc, ALEditorCommand::GoToDefinition); result.references.definition = span(7, 0, 5); const size_t said = studio.said.size(); + const size_t told = services.statuses.size(); caret.answered(doc, result, ALTextPos(0, 8)); { INFO("declared where no source is: none"); REQUIRE(studio.said.size() == said); + REQUIRE(services.statuses.size() == told + 1); CHECK(services.statuses.back().find("NoDefinition") == 0); } } @@ -600,6 +604,7 @@ namespace { INFO("a module the expansion has not: none"); REQUIRE(studio.said.size() == 1); + REQUIRE(services.statuses.size() == size_t(1)); CHECK(services.statuses.back().find("NoDefinition") == 0); } @@ -610,6 +615,8 @@ namespace { INFO("an expansion of another text: none"); REQUIRE(studio.said.size() == 1); + // Said again, not the module's none still standing. + REQUIRE(services.statuses.size() == size_t(2)); CHECK(services.statuses.back().find("NoDefinition") == 0); } } diff --git a/indra/newview/tests/alscriptstudiochecking_test.cpp b/indra/newview/tests/alscriptstudiochecking_test.cpp index 69f0babf54..9bb2bac785 100644 --- a/indra/newview/tests/alscriptstudiochecking_test.cpp +++ b/indra/newview/tests/alscriptstudiochecking_test.cpp @@ -384,6 +384,7 @@ namespace CHECK(asked.kind == Kind::Check); CHECK(asked.id == "a"); CHECK(asked.version == version(doc)); + REQUIRE(asked.text); CHECK(*asked.text == doc.editor->text()); } UNSCOPED_INFO("with the window's settings"); @@ -438,6 +439,7 @@ namespace checking.ask(doc, Kind::Hover, ALTextPos(0, 1), ALTextPos(0, 1)); { INFO("a newer text its own"); + REQUIRE(studio.asks.back().request.text); REQUIRE(studio.asks.back().request.text != studio.asks[0].request.text); CHECK(*studio.asks.back().request.text == doc.editor->text()); } @@ -454,6 +456,7 @@ namespace entry.name = "count"; result.outline = { entry }; doc.editor->insertText("x"); + REQUIRE(studio.asks.size() == size_t(1)); studio.asks[0].answered(result); { INFO("of another text: dropped"); @@ -530,6 +533,8 @@ namespace REQUIRE(studio.asks.size() == size_t(1)); { INFO("of the expansion, at its place"); + REQUIRE(studio.asks[0].request.text); + REQUIRE(doc.expanded.text); CHECK(*studio.asks[0].request.text == *doc.expanded.text); CHECK(studio.asks[0].request.line == 4); } @@ -544,6 +549,7 @@ namespace result.line = 4; result.column = 20; studio.asks[0].answered(result); + REQUIRE_FALSE(studio.told.empty()); UNSCOPED_INFO("at the source's place"); CHECK(studio.told.back() == std::string("inspect a 3:20")); result.line = 0; @@ -555,6 +561,7 @@ namespace checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); doc.check->expansions += 1; doc.expanded.generation += 1; + REQUIRE(studio.asks.size() == size_t(3)); studio.asks[2].answered(answer(doc)); { INFO("an answer of an expansion since replaced: the check asked again"); @@ -633,6 +640,7 @@ namespace Doc& doc = tab("a"); preprocessing = true; checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); + REQUIRE(expansions.size() == size_t(1)); expansions[0].second(expansion(SCRIPT)); ALScriptProblem own = problem(4, "own"); own.fixes = { fix("Put right", 4, 0, 3, "abc") }; @@ -653,6 +661,7 @@ namespace mine.span.endLine = 1; theirs.name = "helper"; result.outline = { mine, theirs }; + REQUIRE(studio.asks.size() == size_t(1)); studio.asks[0].answered(result); const ALScriptProblems& got = doc.check->analysis; UNSCOPED_INFO("the include's unused dropped"); @@ -664,6 +673,7 @@ namespace CHECK(got[0].line == 3); REQUIRE(got[0].file.empty()); REQUIRE(got[0].fixes.size() == 1); + REQUIRE_FALSE(got[0].fixes[0].edits.empty()); CHECK(got[0].fixes[0].edits[0].line == 3); } { @@ -701,6 +711,8 @@ namespace CHECK(doc.check->fragment->second); } checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); + REQUIRE(studio.asks.size() == size_t(1)); + REQUIRE(studio.asks[0].request.text); UNSCOPED_INFO("a state put after it"); CHECK(*studio.asks[0].request.text == doc.editor->text() + "\ndefault{state_entry(){}}\n"); ALScriptProblem unused = problem(0, "helper unused", ALScriptProblem::Severity::Warning); @@ -752,14 +764,17 @@ namespace ALScriptAnalysis::Result result = answer(doc, { undeclared, other }); result.problems[0].line = 4; result.problems[1].line = 4; + REQUIRE(studio.asks.size() == size_t(1)); studio.asks[0].answered(result); + REQUIRE(doc.check->analysis.size() == size_t(2)); const ALScriptProblem& given = doc.check->analysis[0]; UNSCOPED_INFO("offered"); REQUIRE(given.fixes.size() == size_t(1)); { INFO("the include put in, preferred"); CHECK(given.fixes[0].preferred); - REQUIRE(given.fixes[0].edits[0].text.find("#include \"lib.lsl\"") != std::string::npos); + REQUIRE_FALSE(given.fixes[0].edits.empty()); + CHECK(given.fixes[0].edits[0].text.find("#include \"lib.lsl\"") != std::string::npos); } UNSCOPED_INFO("nothing gives the other"); REQUIRE(doc.check->analysis[1].fixes.empty()); @@ -776,6 +791,7 @@ namespace studio.asks.back().answered(answer(doc, { undeclared })); { INFO("not preprocessed: nothing to include with"); + REQUIRE(doc.check->analysis.size() == size_t(1)); REQUIRE(doc.check->analysis[0].fixes.empty()); REQUIRE(nearby.size() == 1); } @@ -786,6 +802,7 @@ namespace studio.asks.back().answered(given_all); { INFO("all given: nothing fetched"); + REQUIRE(doc.check->analysis.size() == size_t(1)); REQUIRE_FALSE(doc.check->analysis[0].fixes.empty()); REQUIRE(nearby.size() == 1); } @@ -818,6 +835,7 @@ namespace ALScriptProblem theirs = parse; theirs.file = "disk:/lib.lsl"; checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); + REQUIRE(studio.asks.size() == size_t(1)); studio.asks[0].answered(answer(doc, { quiet, said, parse, theirs })); UNSCOPED_INFO("the one a comment quiets dropped"); REQUIRE(doc.check->analysis.size() == size_t(3)); @@ -840,6 +858,7 @@ namespace plain.map.finish(); expansions.back().second(plain); studio.asks.back().answered(answer(doc, { parse })); + REQUIRE(doc.check->analysis.size() == size_t(1)); UNSCOPED_INFO("its transform on: not"); CHECK(doc.check->analysis[0].message == std::string("syntax error")); @@ -866,6 +885,7 @@ namespace Doc& marked = tab("m", "integer f() inline { return 1; }\n"); checking.ask(marked, Kind::Check, ALTextPos(), ALTextPos()); studio.asks.back().answered(answer(marked, { problem(0, "syntax error") })); + REQUIRE(marked.check->analysis.size() == size_t(1)); UNSCOPED_INFO("inline explained: " << marked.check->analysis[0].message); CHECK(marked.check->analysis[0].message.find("syntax error PreprocHintInline [WORD]=inline") == 0); preprocessing = true; @@ -877,6 +897,7 @@ namespace stripped.map.finish(); expansions.back().second(stripped); studio.asks.back().answered(answer(marked, { problem(0, "syntax error") })); + REQUIRE(marked.check->analysis.size() == size_t(1)); UNSCOPED_INFO("preprocessing, the extensions off: not"); CHECK(marked.check->analysis[0].message == std::string("syntax error")); @@ -885,6 +906,7 @@ namespace Doc& constant = tab("c", "f(const integer n) { }\n"); checking.ask(constant, Kind::Check, ALTextPos(), ALTextPos()); studio.asks.back().answered(answer(constant, { problem(0, "syntax error") })); + REQUIRE(constant.check->analysis.size() == size_t(1)); UNSCOPED_INFO("const explained: " << constant.check->analysis[0].message); CHECK(constant.check->analysis[0].message.find("syntax error PreprocHintConst [WORD]=const") == 0); @@ -942,6 +964,7 @@ namespace CHECK(doc.editor->undoJournal().undoLabel() == std::string("fix")); { INFO("said, checked again"); + REQUIRE_FALSE(services.statuses.empty()); CHECK(services.statuses.back() == "Rename it"); CHECK(doc.check->analysisDue == 1.0); } @@ -976,11 +999,13 @@ namespace { INFO("not checked: checked first"); REQUIRE(doc.check->fixAllAfterCheck.has_value()); + REQUIRE_FALSE(services.statuses.empty()); CHECK(services.statuses.back() == "FixChecking [NAME]=a"); } checking.pump(1.0); ALScriptProblem one = problem(0, "one"); one.fixes = { fix("First", 0, 0, 7, "float") }; + REQUIRE(studio.asks.size() == size_t(1)); studio.asks[0].answered(answer(doc, { one })); { INFO("then made"); @@ -1031,6 +1056,7 @@ namespace ALScriptProblem one = problem(0, "one"); one.fixes = { fix("First", 0, 0, 7, "float") }; checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); + REQUIRE(studio.asks.size() == size_t(1)); studio.asks[0].answered(answer(doc, { one, problem(2, "two") })); { INFO("asked for, made with the next frame"); @@ -1044,6 +1070,7 @@ namespace REQUIRE(studio.waiting.empty()); REQUIRE(fixes.size() == 1); CHECK(fixes[0].title == "First"); + REQUIRE_FALSE(fixes[0].edits.empty()); CHECK(fixes[0].edits[0].second == "float"); } const Doc::Shown* shown = checking.shownOf(fixes[0].value); @@ -1104,6 +1131,7 @@ namespace ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); checking.ask(doc, Kind::Actions, ALTextPos(0, 8), ALTextPos(0, 13)); + REQUIRE(studio.asks.size() == size_t(1)); UNSCOPED_INFO("a stretch"); CHECK(studio.asks[0].request.endColumn == 13); { @@ -1141,6 +1169,7 @@ namespace refs.line = 0; refs.column = 8; studio.asks.back().answered(refs); + REQUIRE_FALSE(studio.told.empty()); UNSCOPED_INFO("handed on"); CHECK(studio.told.back() == std::string("references a 0:8")); doc.language.compileTarget = "none"; @@ -1233,6 +1262,7 @@ namespace Doc& doc = tab("a"); checking.schedule(doc, true); checking.pump(1.0); + REQUIRE(studio.asks.size() == size_t(1)); studio.asks[0].answered(answer(doc, { problem(0, "wrong"), problem(1, "unused", ALScriptProblem::Severity::Warning) })); UNSCOPED_INFO("both taken"); REQUIRE(doc.check->analysis.size() == size_t(2)); @@ -1317,6 +1347,7 @@ namespace UNSCOPED_INFO("asked"); REQUIRE(studio.asks.size() == size_t(1)); const ALScriptAnalysis::Request& asked = studio.asks[0].request; + REQUIRE(asked.text); UNSCOPED_INFO("of the script apart"); CHECK(*asked.text == doc.editor->text()); { @@ -1362,7 +1393,11 @@ namespace // `count` declared const: in the expansion, a line down. ALPreprocessor::Result expanded = expansion(SCRIPT); expanded.consts = { { "count", 1, 8, false } }; + REQUIRE(expansions.size() == size_t(1)); expansions[0].second(expanded); + // The check, asked once the expansion came: what each inspect + // below is asked after. + REQUIRE(studio.asks.size() == size_t(1)); const auto inspect = [&](S32 line, S32 column, const std::string& label) { checking.ask(doc, Kind::Inspect, ALTextPos(0, 8), ALTextPos(0, 8)); ALScriptAnalysis::Result result = answer(doc); @@ -1432,8 +1467,10 @@ namespace checking.ask(doc, Kind::Inspect, ALTextPos(0, 10), ALTextPos(0, 10)); UNSCOPED_INFO("the inspector, of the same word: not asked"); REQUIRE(studio.asks.size() == size_t(1)); + REQUIRE_FALSE(studio.told.empty()); UNSCOPED_INFO("but told"); CHECK(studio.told.back() == std::string("inspect a 0:10")); + REQUIRE_FALSE(studio.labels.empty()); UNSCOPED_INFO("what the tip was"); CHECK(studio.labels.back() == std::string("integer count")); checking.ask(doc, Kind::Inspect, ALTextPos(1, 0), ALTextPos(1, 0)); @@ -1511,12 +1548,14 @@ namespace ALScriptProblem two = problem(3, "two"); two.fixes = { fix("Second", 3, 4, 15, "touch_start") }; const std::string before = doc.editor->wholeText(); + REQUIRE(studio.asks.size() == size_t(1)); studio.asks.back().answered(answer(doc, { one, two })); UNSCOPED_INFO("asked, with a preview"); REQUIRE((bool)studio.preview); studio.preview(); UNSCOPED_INFO("nothing made yet"); CHECK(doc.editor->wholeText() == before); + REQUIRE_FALSE(studio.told.empty()); const std::string& compared = studio.told.back(); { INFO("the text now beside it after: " << compared); @@ -1526,6 +1565,7 @@ namespace } { INFO("Apply offered"); + REQUIRE_FALSE(services.reports.empty()); CHECK(services.reports.back().actions == Names{ "apply_fixes" }); CHECK(services.reports.back().doc == "a"); } @@ -1557,6 +1597,7 @@ namespace } { INFO("said"); + REQUIRE_FALSE(services.statuses.empty()); CHECK(services.statuses.back() == "FixAllChangedSince"); CHECK(services.statusFailures.back()); } @@ -1578,6 +1619,7 @@ namespace compat.source = ALScriptProblem::Source::Lint; compat.code = "SlCompatCall"; compat.fixes = { fix("Write it ll.GetSubString(s, 1, 3)", 2, 6, 36, "ll.GetSubString(s, 1, 3)") }; + REQUIRE(studio.asks.size() == size_t(1)); studio.asks.back().answered(answer(doc, { compat })); const ALScriptProblems& said = doc.check->analysis; UNSCOPED_INFO("two notes, then the lint"); @@ -1640,6 +1682,7 @@ namespace const ALScriptProblem zero = lint(0, "SlZeroIndex", fix("First", 0, 0, 7, "float", false)); const ALScriptProblem paren = lint(3, "SlParenCondition", fix("Second", 3, 4, 15, "touch_start", true)); const ALScriptProblem shadow = lint(1, "LocalShadow", fix("Third", 1, 0, 1, "x", true)); + REQUIRE(studio.asks.size() == size_t(1)); studio.asks.back().answered(answer(doc, { zero, paren, shadow })); Doc::FixPick pick; pick.migration = true; @@ -1647,12 +1690,14 @@ namespace checking.askFixAll(doc, pick); UNSCOPED_INFO("not asked: seen"); REQUIRE_FALSE(studio.preview); + REQUIRE_FALSE(studio.told.empty()); const std::string& compared = studio.told.back(); { INFO("the two from LSL, not the other: " << compared); REQUIRE(compared.find("FixAllAfter [FIXES]=Fixes [COUNT]=2") != std::string::npos); REQUIRE(compared.find("float count;") != std::string::npos); } + REQUIRE_FALSE(services.reports.empty()); UNSCOPED_INFO("Apply offered"); CHECK(services.reports.back().actions == Names{ "apply_fixes" }); { @@ -1685,6 +1730,7 @@ namespace truth.source = ALScriptProblem::Source::Lint; truth.code = "SlNumberTruth"; truth.fixes = { fix("Compare it: #t ~= 0", 1, 3, 5, "#t ~= 0", false) }; + REQUIRE(studio.asks.size() == size_t(1)); studio.asks.back().answered(answer(doc, { truth })); Doc::FixPick pick; pick.migration = true; @@ -1692,8 +1738,10 @@ namespace { INFO("previewed, not asked"); REQUIRE_FALSE(studio.preview); + REQUIRE_FALSE(studio.told.empty()); CHECK(studio.told.back().rfind("compare a: ", 0) == 0); } + REQUIRE_FALSE(services.reports.empty()); UNSCOPED_INFO("Apply offered"); CHECK(services.reports.back().actions == Names{ "apply_fixes" }); UNSCOPED_INFO("made by Apply"); @@ -1703,6 +1751,7 @@ namespace { INFO("Apply again: nothing waiting, and said"); CHECK_FALSE(checking.applyPreviewed(doc)); + REQUIRE_FALSE(services.statuses.empty()); CHECK(services.statuses.back() == "FixAllNotPreviewed"); CHECK(services.statusFailures.back()); } @@ -1755,6 +1804,7 @@ namespace CHECK(named == std::vector{ "inc=/lib" }); { INFO("said"); + REQUIRE_FALSE(services.statuses.empty()); CHECK(services.statuses.back().find("AliasTaken [NAME]=inc") == 0); CHECK(services.statusFailures.back()); } @@ -1799,6 +1849,7 @@ namespace highlighter.tokens(last); UNSCOPED_INFO("the text not lexed to its end for it: " << highlighter.lastLexed() << " of " << last + 1 << " left"); CHECK(highlighter.lastLexed() > last / 2); + REQUIRE(doc.check->fragment); UNSCOPED_INFO("nor kept for the text as it stands"); CHECK(doc.check->fragment->first != version(doc)); { @@ -1814,6 +1865,7 @@ namespace checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); UNSCOPED_INFO("asked once"); REQUIRE(studio.asks.size() == size_t(1)); + REQUIRE(studio.asks[0].request.text); UNSCOPED_INFO("of the script as it stands"); CHECK(*studio.asks[0].request.text == doc.editor->text()); UNSCOPED_INFO("and a script now"); diff --git a/indra/newview/tests/alscriptstudiocomparepairs_test.cpp b/indra/newview/tests/alscriptstudiocomparepairs_test.cpp index 1c47b0a435..0336717edd 100644 --- a/indra/newview/tests/alscriptstudiocomparepairs_test.cpp +++ b/indra/newview/tests/alscriptstudiocomparepairs_test.cpp @@ -59,6 +59,7 @@ namespace ALScriptAnalysis::Result result; result.kind = one.request.kind; result.id = one.request.id; + REQUIRE(one.request.text); result.outline = ALLuauService::shape(*one.request.text); one.answered(result); } @@ -134,6 +135,7 @@ namespace CHECK(left.kind == ALScriptAnalysis::Kind::Shape); CHECK(left.lua); CHECK(left.front); + REQUIRE(left.text); CHECK(*left.text == LEFT); CHECK(left.id != studio.asked[1].request.id); } diff --git a/indra/newview/tests/alscriptstudiocomparewith_test.cpp b/indra/newview/tests/alscriptstudiocomparewith_test.cpp index fe46751c14..ce6b585ab4 100644 --- a/indra/newview/tests/alscriptstudiocomparewith_test.cpp +++ b/indra/newview/tests/alscriptstudiocomparewith_test.cpp @@ -228,6 +228,7 @@ namespace Doc& doc = tab("door", "as saved\n"); require_changed(doc, "as changed\n"); unit->show(doc); + REQUIRE_FALSE(studio.candidates.empty()); CHECK(studio.values().front() == std::string("saved")); CHECK(studio.candidates[0].label == said("CompareSaved")); studio.choose("saved"); @@ -238,6 +239,7 @@ namespace studio.choose("saved"); UNSCOPED_INFO("not compared"); CHECK(studio.did.size() == 1U); + REQUIRE_FALSE(services().statuses.empty()); CHECK(services().statuses.back() == said("CompareNothingSaved")); UNSCOPED_INFO("as a failure"); CHECK(services().statusFailures.back()); @@ -264,6 +266,7 @@ namespace studio.choose("saved_there"); UNSCOPED_INFO("nothing more"); CHECK(studio.did.size() == 1U); + REQUIRE_FALSE(services().statuses.empty()); CHECK(services().statuses.back() == said("CompareSavedThereGone")); UNSCOPED_INFO("as a failure"); CHECK(services().statusFailures.back()); @@ -294,6 +297,7 @@ namespace unsaved["[TITLE]"] = "lamp"; // Titled as a copy of it as it was, which stays as it is. const auto snapshot = [this](const std::string& name) { return this->said("CompareTabSnapshot", { { "[NAME]", name } }); }; + REQUIRE_FALSE(studio.did.empty()); UNSCOPED_INFO("unsaved, as it is now"); CHECK(studio.did.back() == "compare door: lamp later\n | door\n (" + snapshot(said("CompareUnsaved", unsaved)) + " | )"); @@ -308,6 +312,7 @@ namespace studio.choose("tab:sign"); UNSCOPED_INFO("nothing more"); CHECK(studio.did.size() == 2U); + REQUIRE_FALSE(services().statuses.empty()); CHECK(services().statuses.back() == said("CompareTabClosed", { { "[NAME]", "sign" } })); UNSCOPED_INFO("as a failure"); CHECK(services().statusFailures.back()); @@ -345,6 +350,7 @@ namespace studio.choose("clipboard"); UNSCOPED_INFO("not once emptied"); CHECK(studio.did.size() == 1U); + REQUIRE_FALSE(services().statuses.empty()); CHECK(services().statuses.back() == said("CompareWithClipboardEmpty")); } @@ -378,6 +384,7 @@ namespace CHECK(studio.did.size() == 1U); LLStringUtil::format_map_t args; args["[FILE]"] = missing; + REQUIRE_FALSE(services().statuses.empty()); CHECK(services().statuses.back() == said("LoadFromFileFailed", args)); UNSCOPED_INFO("as a failure"); CHECK(services().statusFailures.back()); diff --git a/indra/newview/tests/alscriptstudioexpandedcompare_test.cpp b/indra/newview/tests/alscriptstudioexpandedcompare_test.cpp index 5c42c6bd93..1bbd84c7ab 100644 --- a/indra/newview/tests/alscriptstudioexpandedcompare_test.cpp +++ b/indra/newview/tests/alscriptstudioexpandedcompare_test.cpp @@ -185,6 +185,7 @@ namespace CHECK(studio.titles == "door | " + services().words("ComparePreprocessed")); UNSCOPED_INFO("lined up by line"); CHECK(said(studio.given) == std::string("[0-0 1-1][1-1 2-2]")); + REQUIRE(door.compareView); ALDiffView& view = *door.compareView; UNSCOPED_INFO("the source's first line beside the expansion's second"); CHECK(view.left()->layout().lineTop(0) == view.right()->layout().lineTop(1)); @@ -293,6 +294,7 @@ namespace door.uploaded.text = std::make_shared(result.text); door.uploaded.map = result.map; unit->compare(door); + REQUIRE(door.compareView); ALDiffView& view = *door.compareView; const std::vector out = ALTextDiff::split(result.text); const auto where = [&out](std::string_view what) { @@ -340,6 +342,7 @@ namespace door.editor->insertText("// "); unit->compare(door); CHECK(studio.runs == 1); + REQUIRE_FALSE(services().statuses.empty()); UNSCOPED_INFO("said while it waits"); CHECK(services().statuses.back() == services().words("Preprocessing", { { "[NAME]", "door" } })); // The window was busy: the run that answers is the one before. diff --git a/indra/newview/tests/alscriptstudiohistory_test.cpp b/indra/newview/tests/alscriptstudiohistory_test.cpp index 43586af280..358cb9d0b8 100644 --- a/indra/newview/tests/alscriptstudiohistory_test.cpp +++ b/indra/newview/tests/alscriptstudiohistory_test.cpp @@ -193,6 +193,7 @@ namespace REQUIRE(studio.candidates.size() == 3U); CHECK(studio.picked == said("HistoryTitle", args)); const std::vector listed = history->list(ALScriptStudioHistory::keyOf(doc)); + REQUIRE(listed.size() == 3U); CHECK(studio.candidates[0].label == ALRecoveryEntry::sayWhen(listed[0].when)); const std::string& newest = studio.candidates[0].detail; { @@ -209,8 +210,9 @@ namespace } { INFO("the first kept says nothing of one before"); - REQUIRE(studio.candidates[2].detail.find(said("HistorySameLength")) == std::string::npos); - REQUIRE(studio.candidates[2].detail.find("longer") == std::string::npos); + CHECK(studio.candidates[2].detail.find(said("HistorySameLength")) == std::string::npos); + CHECK(studio.candidates[2].detail.find("longer") == std::string::npos); + CHECK(studio.candidates[2].detail.find("shorter") == std::string::npos); } } @@ -223,6 +225,7 @@ namespace Doc& doc = tab("door", "the text now"); require_saved(doc, "the text then", 100.0); unit.show(doc); + REQUIRE(studio.candidates.size() == 1U); studio.choose(studio.candidates[0].value); UNSCOPED_INFO("compared"); REQUIRE(studio.did.size() == 1U); @@ -243,6 +246,7 @@ namespace Doc& loading = tab("lamp", "", false); require_saved(loading, "a lamp's text", 50.0); unit.show(loading); + REQUIRE(studio.candidates.size() == 1U); studio.choose(studio.candidates[0].value); UNSCOPED_INFO("not compared yet"); REQUIRE(studio.did.size() == 1U); @@ -321,8 +325,10 @@ namespace require_saved(doc, "second\nsame", 200.0); require_saved(doc, "third\nsame", 100.0); unit.show(doc); + REQUIRE(studio.candidates.size() == 3U); // Listed newest first: the middle one. studio.choose(studio.candidates[1].value); + REQUIRE(doc.compareView); ALDiffView& view = *doc.compareView; UNSCOPED_INFO("the slider"); CHECK(view.bar()->getChild("versions")->getVisible()); @@ -340,9 +346,11 @@ namespace CHECK(doc.historyShown->text == "third\nsame"); } const std::vector listed = history->list(ALScriptStudioHistory::keyOf(doc)); + REQUIRE(listed.size() == 3U); LLStringUtil::format_map_t when; when["[NAME]"] = "door"; when["[WHEN]"] = ALRecoveryEntry::sayWhen(listed[0].when); + REQUIRE(doc.compareTitles); UNSCOPED_INFO("titled by it"); CHECK(doc.compareTitles->theirs == said("HistorySavedAt", when)); { @@ -375,6 +383,7 @@ namespace require_saved(loading, "one", 200.0); require_saved(loading, "two", 100.0); unit.show(loading); + REQUIRE(studio.candidates.size() == 2U); studio.choose(studio.candidates[0].value); { INFO("waiting, with something to do once shown"); @@ -397,6 +406,7 @@ namespace Doc& alone = tab("gate", "now"); require_saved(alone, "only", 100.0); unit.show(alone); + REQUIRE(studio.candidates.size() == 1U); studio.choose(studio.candidates[0].value); { INFO("one save: no slider"); @@ -416,24 +426,29 @@ namespace require_saved(doc, "older", 200.0); require_saved(doc, "newer", 100.0); unit.show(doc); + REQUIRE(studio.candidates.size() == 2U); studio.choose(studio.candidates[0].value); + REQUIRE(doc.compareView); ALDiffView& view = *doc.compareView; UNSCOPED_INFO("at the newest"); CHECK(view.bar()->versionShown() == 1); // The older one gone from disk since it was listed. const std::vector listed = history->list(ALScriptStudioHistory::keyOf(doc)); + REQUIRE(listed.size() == 2U); std::error_code gone; std::filesystem::remove(fsyspath(listed[1].path), gone); ALViewType::as(view.bar()->getChild("older"))->press(); LLStringUtil::format_map_t args; args["[NAME]"] = "door"; args["[WHEN]"] = ALRecoveryEntry::sayWhen(listed[1].when); + REQUIRE_FALSE(services().statuses.empty()); CHECK(services().statuses.back() == said("HistoryUnreadable", args)); UNSCOPED_INFO("the slider put back"); CHECK(view.bar()->versionShown() == 1); { INFO("the left as it was"); CHECK(view.leftText() == "newer"); + REQUIRE(doc.historyShown); CHECK(doc.historyShown->text == "newer"); } } @@ -450,7 +465,9 @@ namespace require_saved(doc, "second", 200.0); require_saved(doc, "third", 100.0); unit.show(doc); + REQUIRE(studio.candidates.size() == 3U); studio.choose(studio.candidates[1].value); + REQUIRE(doc.compareView); ALDiffView& view = *doc.compareView; const auto press = [&view](const char* name) { ALViewType::as(view.bar()->getChild(name))->press(); }; press("older"); @@ -458,6 +475,7 @@ namespace CHECK(view.leftText() == std::string("first")); press("newer"); const std::vector listed = history->list(ALScriptStudioHistory::keyOf(doc)); + REQUIRE(listed.size() == 3U); std::error_code gone; std::filesystem::remove(fsyspath(listed[2].path), gone); press("older"); @@ -465,6 +483,7 @@ namespace INFO("the oldest again, as it was read"); CHECK(view.leftText() == "first"); CHECK(view.bar()->versionShown() == 0); + REQUIRE(doc.historyShown); CHECK(doc.historyShown->text == "first"); } } @@ -480,7 +499,9 @@ namespace require_saved(doc, "first", 300.0); require_saved(doc, "second", 200.0); unit.show(doc); + REQUIRE(studio.candidates.size() == 2U); studio.choose(studio.candidates[0].value); + REQUIRE(doc.compareView); ALDiffView& view = *doc.compareView; { INFO("the slider"); diff --git a/indra/newview/tests/alscriptstudiomerging_test.cpp b/indra/newview/tests/alscriptstudiomerging_test.cpp index 98e3df496c..769daadc1c 100644 --- a/indra/newview/tests/alscriptstudiomerging_test.cpp +++ b/indra/newview/tests/alscriptstudiomerging_test.cpp @@ -168,12 +168,15 @@ namespace REQUIRE_FALSE(doc.savedThere); CHECK(studio.notices == 1); } + REQUIRE_FALSE(services().reports.empty()); CHECK(services().reports.back().text == services().counted("MergeConflicts", 1, named(doc))); // Settled from the comparison's bar: an edit of the tab. doc.compareView->right()->setFocus(true); doc.compareView->right()->goTo(ALTextPos(6, 0)); - ALViewType::as(doc.compareView->bar()->getChild("keep_both"))->press(); + ALFlatButton* keep_both = ALViewType::as(doc.compareView->bar()->findChild("keep_both")); + REQUIRE(keep_both); + keep_both->press(); UNSCOPED_INFO("mine then theirs"); CHECK(doc.editor->wholeText() == lines(9, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" }, { 7, "theirs 6" }, { 8, "line 7" } })); @@ -195,8 +198,10 @@ namespace doc.savedThere = lines(8, { { 0, "theirs 0" } }); unit->mergeSaved(doc); CHECK(doc.editor->wholeText() == lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" } })); + REQUIRE(doc.compareView); REQUIRE(doc.compareView->merging()); CHECK(doc.compareView->conflictCount() == 0); + REQUIRE_FALSE(services().reports.empty()); CHECK(services().reports.back().text == said("MergeClean", named(doc))); } @@ -223,6 +228,7 @@ namespace REQUIRE(doc.savedThere); CHECK(studio.notices == 0); } + REQUIRE_FALSE(services().statuses.empty()); CHECK(services().statuses.back() == said("MergeNoBase", named(doc))); Doc& locked = tab("lamp"); @@ -257,7 +263,9 @@ namespace CHECK(doc.editor->wholeText() == lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" } })); { INFO("under the world's title, as a merge"); + REQUIRE(studio.titles.size() == 1); CHECK(studio.titles.back() == said("CompareWorld")); + REQUIRE(doc.compareView); CHECK(doc.compareView->conflictCount() == 1); } UNSCOPED_INFO("made from what the world holds"); @@ -289,6 +297,7 @@ namespace REQUIRE_FALSE(doc.savedThere); REQUIRE(doc.merged); } + REQUIRE_FALSE(services().reports.empty()); UNSCOPED_INFO("the conflicts said with the comparison to show"); CHECK(services().reports.back().actions == std::vector{ "show_compare" }); // Typed in, or any change not a step back: nothing. @@ -357,6 +366,7 @@ namespace CHECK(doc.assetId == was); REQUIRE_FALSE(doc.merged); } + REQUIRE_FALSE(services().reports.empty()); UNSCOPED_INFO("reload, merge and compare offered again"); CHECK(services().reports.back().actions == std::vector{ "reload_world", "merge_world", "compare_world" }); } @@ -371,6 +381,7 @@ namespace doc.savedThere = theirs; unit->mergeSaved(doc); // As the window keeps what was said, and shows the comparison. + REQUIRE_FALSE(services().reports.empty()); doc.offer = Doc::Offer{ services().reports.back().text, services().reports.back().actions }; UNSCOPED_INFO("not while the comparison is not in front"); CHECK_FALSE(ALScriptStudioMerging::canSettle(doc)); @@ -382,6 +393,7 @@ namespace REQUIRE(notice.buttons[1].first.empty()); } + REQUIRE(doc.compareView); doc.compareView->right()->setFocus(true); doc.compareView->right()->goTo(ALTextPos(2, 0)); UNSCOPED_INFO("not where the caret is in no conflict"); @@ -422,6 +434,7 @@ namespace CHECK(doc.compareView->rightText() == doc.editor->wholeText()); REQUIRE(doc.compareView->merging()); CHECK(doc.compareView->conflictCount() == 0); + REQUIRE_FALSE(services().reports.empty()); CHECK(services().reports.back().text == said("MergeClean", named(doc))); } } diff --git a/indra/newview/tests/alscriptstudioselections_test.cpp b/indra/newview/tests/alscriptstudioselections_test.cpp index d2450aa493..123fe04137 100644 --- a/indra/newview/tests/alscriptstudioselections_test.cpp +++ b/indra/newview/tests/alscriptstudioselections_test.cpp @@ -160,13 +160,15 @@ namespace CHECK(unit->held()->title == titled("SelectionLines", "door", 3, 4)); LLStringUtil::format_map_t args; args["[TITLE]"] = unit->held()->title; + REQUIRE_FALSE(services().statuses.empty()); CHECK(services().statuses.back() == said("SelectionHeld", args)); door.editor->goTo(ALTextRange(ALTextPos(5, 0), ALTextPos(5, 4))); unit->hold(door); { INFO("one line, in place of the other"); - REQUIRE(unit->held()->text == "line"); + REQUIRE(unit->held().has_value()); + CHECK(unit->held()->text == "line"); CHECK(unit->held()->title == titled("SelectionLine", "door", 6)); } } @@ -214,6 +216,7 @@ namespace unit->hold(door); door.editor->goTo(ALTextRange(ALTextPos(5, 0), ALTextPos(7, 0))); unit->compare(door); + REQUIRE(door.compareView); ALDiffView& view = *door.compareView; UNSCOPED_INFO("its changes may be taken back"); CHECK(view.canTakeBack()); @@ -290,6 +293,8 @@ namespace CHECK(unit->canCompare(door)); } unit->compare(door); + // Compared, and so in its place: the window makes the view as it compares. + REQUIRE_FALSE(studio.did.empty()); UNSCOPED_INFO("its text"); CHECK(studio.did.back() == "line 0\nline 1\n | line 0\nexpanded 1\n (" + titled("SelectionLines", "door", 1, 2) + " | " + titled("SelectionLines", "door", 1, 2) + ")"); @@ -313,12 +318,14 @@ namespace unit->hold(door); door.editor->goTo(ALTextRange(ALTextPos(1, 12), ALTextPos(2, 9))); unit->compare(door); + REQUIRE(door.compareView); ALDiffView& view = *door.compareView; UNSCOPED_INFO("the stretch"); CHECK(view.rightText() == std::string("beta;\ninteger c")); view.right()->setFocus(true); view.right()->goTo(ALTextPos(0, 2)); view.right()->handleUnicodeChar('x', false); + REQUIRE_FALSE(studio.typedAt.empty()); UNSCOPED_INFO("on its first line, from where it starts"); CHECK(studio.typedAt.back() == ALTextPos(1, 14)); UNSCOPED_INFO("typed there"); diff --git a/indra/newview/tests/alscriptstudioweighing_test.cpp b/indra/newview/tests/alscriptstudioweighing_test.cpp index 751d5ff9d3..3dabe20fb8 100644 --- a/indra/newview/tests/alscriptstudioweighing_test.cpp +++ b/indra/newview/tests/alscriptstudioweighing_test.cpp @@ -241,6 +241,7 @@ namespace { INFO("asked of what was sent, for its own target"); REQUIRE(studio.requests.size() == 1); + REQUIRE(studio.requests[0].text); CHECK(*studio.requests[0].text == "sent text"); CHECK(studio.requests[0].targets == std::vector{ Target::Mono }); } @@ -265,6 +266,9 @@ namespace } a.uploaded.disabled = true; unit.weighSent(a); + // Each question's answer is kept beside it: answers[1] below is there too. + REQUIRE(studio.requests.size() == 2); + REQUIRE(studio.requests[1].text); UNSCOPED_INFO("the preprocessor off: the text as written"); CHECK(*studio.requests[1].text == "default {}"); a.editor->setCaret(a.editor->document().end()); @@ -431,6 +435,7 @@ namespace result.version = versionOf(a); result.variantTotals = { 100, 90, 100 }; studio.answers[0](result); + REQUIRE(e.fixes().size() == 3); UNSCOPED_INFO("the lighter one said"); CHECK(e.fixes()[0].note.find("FixLighterEstimate") == 0); { diff --git a/indra/newview/tests/alscriptstudiowords_test.cpp b/indra/newview/tests/alscriptstudiowords_test.cpp index 378661423b..2481c0d5ef 100644 --- a/indra/newview/tests/alscriptstudiowords_test.cpp +++ b/indra/newview/tests/alscriptstudiowords_test.cpp @@ -110,6 +110,17 @@ namespace made->setText(text); return *made; } + // A word the definitions give, for a case to read: one missing ends + // the case here rather than reading through nothing. Asked before an + // UNSCOPED_INFO, not between it and its check, which this REQUIRE + // passing would take the message from. + static const Vocab& known(bool lua, const std::string& name) + { + const Vocab* found = Words::word(lua, name); + CAPTURE(name); + REQUIRE(found); + return *found; + } static std::vector texts(const std::vector& completions) { std::vector out; @@ -135,24 +146,30 @@ namespace } { INFO("and what returns"); - CHECK(Words::word(false, "llOld")->detail == "integer llOld()"); - CHECK(Words::word(false, "llOld")->deprecated); + const Vocab& old = known(false, "llOld"); + CHECK(old.detail == "integer llOld()"); + CHECK(old.deprecated); } + const Vocab& touch = known(false, "touch_start"); UNSCOPED_INFO("an event's"); - CHECK(Words::word(false, "touch_start")->detail == std::string("touch_start(integer num_detected)")); + CHECK(touch.detail == std::string("touch_start(integer num_detected)")); + const Vocab& pi = known(false, "PI"); UNSCOPED_INFO("a constant's type and value"); - CHECK(Words::word(false, "PI")->detail == std::string("float PI = 3.14159265")); + CHECK(pi.detail == std::string("float PI = 3.14159265")); + const Vocab& integer = known(false, "integer"); UNSCOPED_INFO("a type's tooltip's first line"); - CHECK(Words::word(false, "integer")->detail == std::string("A whole number.")); - CHECK(Words::word(false, "jump")->kind == ALSyntaxKind::Control); + CHECK(integer.detail == std::string("A whole number.")); + const Vocab& jump = known(false, "jump"); + CHECK(jump.kind == ALSyntaxKind::Control); { INFO("not a word"); CHECK_FALSE(Words::word(false, "x")); CHECK_FALSE(Words::word(false, "")); CHECK_FALSE(Words::word(false, "nothing")); } + const Vocab& slua_say = known(true, "ll.Say"); UNSCOPED_INFO("SLua's () returns nothing worth saying"); - CHECK(Words::word(true, "ll.Say")->detail == std::string("ll.Say(number channel)")); + CHECK(slua_say.detail == std::string("ll.Say(number channel)")); } TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: the vocabulary is kept until forgotten and then built again", "[alscriptstudiowords]") @@ -224,22 +241,24 @@ namespace TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: completions fill in handlers where they go and offer snippets", "[alscriptstudiowords]") { - const ALCodeEditor::Completion lsl = Words::completionFor(*Words::word(false, "touch_start"), false); + const ALCodeEditor::Completion lsl = Words::completionFor(known(false, "touch_start"), false); UNSCOPED_INFO("LSL's handler"); CHECK(lsl.snippet == std::string("touch_start(integer num_detected)\n{\n $0\n}")); - const ALCodeEditor::Completion slua = Words::completionFor(*Words::word(true, "touch_start"), true); + const Vocab& slua_touch = known(true, "touch_start"); + const ALCodeEditor::Completion slua = Words::completionFor(slua_touch, true); UNSCOPED_INFO("SLua's, a function of LLEvents'"); CHECK(slua.snippet == std::string("function LLEvents.touch_start(detected)\n $0\nend")); UNSCOPED_INFO("typed where asked, as the definitions type it"); - CHECK(Words::completionFor(*Words::word(true, "touch_start"), true, true).snippet == + CHECK(Words::completionFor(slua_touch, true, true).snippet == std::string("function LLEvents.touch_start(detected: {DetectedEvent})\n $0\nend")); + const Vocab& listen = known(true, "listen"); UNSCOPED_INFO("each parameter typed"); - CHECK(Words::completionFor(*Words::word(true, "listen"), true, true).snippet == + CHECK(Words::completionFor(listen, true, true).snippet == std::string("function LLEvents.listen(channel: number, name: string, id: uuid, msg: string)\n $0\nend")); UNSCOPED_INFO("each untyped"); - CHECK(Words::completionFor(*Words::word(true, "listen"), true).snippet == + CHECK(Words::completionFor(listen, true).snippet == std::string("function LLEvents.listen(channel, name, id, msg)\n $0\nend")); - const ALCodeEditor::Completion say = Words::completionFor(*Words::word(false, "llSay"), false); + const ALCodeEditor::Completion say = Words::completionFor(known(false, "llSay"), false); { INFO("a function as itself"); CHECK(say.snippet.empty()); @@ -332,8 +351,9 @@ namespace CHECK(Words::helpUrl(true, "LLEvents") == std::string("https://wiki.secondlife.com/wiki/Lua_Alpha")); UNSCOPED_INFO("nothing: the portal"); CHECK(Words::helpUrl(false, "") == std::string("https://wiki.example/LSL_Portal")); + const Vocab& pi = known(false, "PI"); UNSCOPED_INFO("the reference's words"); - CHECK(Words::referenceText(*Words::word(false, "PI"), false) == + CHECK(Words::referenceText(pi, false) == std::string("float PI = 3.14159265\n\nThe ratio.\nhttps://wiki.example/PI")); } @@ -517,20 +537,27 @@ namespace viewer.slots.definitionsYaml = [] { return std::string("functions:\n llSay:\n categories:\n - avatar_communication\n"); }; lsl["constants"]["PRIM_POSITION"] = LLSD().with("type", "integer").with("value", "6"); Words::forget(); + const Vocab& say = known(false, "llSay"); UNSCOPED_INFO("LSL, in words"); - CHECK(Words::word(false, "llSay")->group == std::string("Avatar communication")); + CHECK(say.group == std::string("Avatar communication")); + const Vocab& old = known(false, "llOld"); UNSCOPED_INFO("none where none is said"); - CHECK(Words::word(false, "llOld")->group.empty()); + CHECK(old.group.empty()); + const Vocab& touch = known(false, "touch_start"); UNSCOPED_INFO("an event none"); - CHECK(Words::word(false, "touch_start")->group.empty()); + CHECK(touch.group.empty()); + const Vocab& position = known(false, "PRIM_POSITION"); UNSCOPED_INFO("a constant by family"); - CHECK(Words::word(false, "PRIM_POSITION")->group == std::string("PRIM_...")); + CHECK(position.group == std::string("PRIM_...")); + const Vocab& pi = known(false, "PI"); UNSCOPED_INFO("one without a family none"); - CHECK(Words::word(false, "PI")->group.empty()); + CHECK(pi.group.empty()); + const Vocab& slua_say = known(true, "ll.Say"); UNSCOPED_INFO("SLua's ll function as its twin"); - CHECK(Words::word(true, "ll.Say")->group == std::string("Avatar communication")); + CHECK(slua_say.group == std::string("Avatar communication")); + const Vocab& math_pi = known(true, "math.pi"); UNSCOPED_INFO("another SLua function by library"); - CHECK(Words::word(true, "math.pi")->group == std::string("math")); + CHECK(math_pi.group == std::string("math")); } TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: the units ask the attached viewer and default what it leaves unsaid", "[alscriptstudiowords]") @@ -620,21 +647,25 @@ namespace slua["events"]["varied"] = LLSD().with("arguments", LLSD().with(0, argument("first", "number")).with(1, argument("...", "any"))); slua["events"]["bare"] = LLSD().with("arguments", LLSD().with(0, argument("...any", ""))); Words::forget(); - const auto handler = [](const char* name, bool typed) { return Words::completionFor(*Words::word(true, name), true, typed).snippet; }; + const auto handler = [](const Vocab& word, bool typed) { return Words::completionFor(word, true, typed).snippet; }; + const Vocab& mapped = known(true, "mapped"); UNSCOPED_INFO("a generic's comma"); - CHECK(handler("mapped", true) == std::string("function LLEvents.mapped(pairs: Map, n: number)\n $0\nend")); + CHECK(handler(mapped, true) == std::string("function LLEvents.mapped(pairs: Map, n: number)\n $0\nend")); UNSCOPED_INFO("and untyped"); - CHECK(handler("mapped", false) == std::string("function LLEvents.mapped(pairs, n)\n $0\nend")); + CHECK(handler(mapped, false) == std::string("function LLEvents.mapped(pairs, n)\n $0\nend")); + const Vocab& called = known(true, "called"); UNSCOPED_INFO("a function type's arrow, a table type's colon"); - CHECK(handler("called", true) == + CHECK(handler(called, true) == std::string("function LLEvents.called(back: (number) -> (), data: {[string]: any})\n $0\nend")); + const Vocab& varied = known(true, "varied"); UNSCOPED_INFO("a variadic typed"); - CHECK(handler("varied", true) == std::string("function LLEvents.varied(first: number, ...: any)\n $0\nend")); + CHECK(handler(varied, true) == std::string("function LLEvents.varied(first: number, ...: any)\n $0\nend")); UNSCOPED_INFO("and not"); - CHECK(handler("varied", false) == std::string("function LLEvents.varied(first, ...)\n $0\nend")); + CHECK(handler(varied, false) == std::string("function LLEvents.varied(first, ...)\n $0\nend")); + const Vocab& bare = known(true, "bare"); UNSCOPED_INFO("one written ...any"); - CHECK(handler("bare", true) == std::string("function LLEvents.bare(...: any)\n $0\nend")); + CHECK(handler(bare, true) == std::string("function LLEvents.bare(...: any)\n $0\nend")); UNSCOPED_INFO("and untyped"); - CHECK(handler("bare", false) == std::string("function LLEvents.bare(...)\n $0\nend")); + CHECK(handler(bare, false) == std::string("function LLEvents.bare(...)\n $0\nend")); } } diff --git a/indra/newview/tests/alscripttypes_test.cpp b/indra/newview/tests/alscripttypes_test.cpp index 95f9f63ff9..13e169fdd1 100644 --- a/indra/newview/tests/alscripttypes_test.cpp +++ b/indra/newview/tests/alscripttypes_test.cpp @@ -120,6 +120,7 @@ TEST_CASE_METHOD(alscripttypes_data, "alscripttypes: inSource maps diagnostics b const std::vector wrapped = result.inSource(1); { INFO("a line down in an envelope, no column"); + REQUIRE(wrapped.size() == size_t(4)); CHECK(wrapped[0].hasLine); CHECK(wrapped[0].line == 4); CHECK_FALSE(wrapped[0].hasColumn); From b31b28520dba942d026211fc1aeb108786bb8692 Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 04:47:56 -0400 Subject: [PATCH 3/9] Script Studio's saving, file and recovery tests check what they say they check Old test bugs TM24's idiom pass found (83b0c1dd65), fixed. - alscriptstudiovim: "off already: left" turned an option off, and ":wq over something unsaved" ran :x; each runs what it says, and the old steps stay under names of their own. - alrecoverykeeper: "among the discarded" is checked, and so is the write the case relied on; each entry looked up is held and required. - alscriptstudiorecovery: "behind the new one's" checks the order, not only the count; a save with no key is checked to keep nothing; the window's taker, which captured the case's locals, is put back by RAII. - alscriptstudioaccount: the switches said to stay shared are tested with one. - alscriptnavigation: a check the setup already decided checks that nothing was noted. - alscriptregionusage: an answer for an id already recorded checks that the live asker heard it; usageOf() is required before it is read, as review on #474 asked. - alnotecardembedded: a button is checked over each item, and one undo is checked to take only the last away. - alscriptkeymap: the keys are compared, not whether both lists are empty; off the Mac, back and forward are checked apart. - alscriptsnippets: running as root skips, as alfilewrite's test does. - Reports, statuses, sends, tidies, analyses, closed tabs, offers, logs, recent files, watches and prims are required before they are read in these and alscriptstudiosaving, alscriptstudiomasters, alscriptstudioorphans, alscriptstudiofiles, alscriptstudiodoc, alscriptexternaleditor, alscriptrecompile, alscriptcontentsindex and alscriptstudiocommands. Unused variables inherited from TUT are gone. Each new or corrected check fails under a small change to the test's own input or expectation, at the check and not by crashing. Co-Authored-By: Claude Opus 5.5 --- .../newview/tests/alnotecardembedded_test.cpp | 12 +++- indra/newview/tests/alrecoverykeeper_test.cpp | 28 +++++++--- .../tests/alscriptcontentsindex_test.cpp | 6 +- .../tests/alscriptexternaleditor_test.cpp | 4 +- indra/newview/tests/alscriptkeymap_test.cpp | 10 ++-- .../newview/tests/alscriptnavigation_test.cpp | 9 ++- .../newview/tests/alscriptrecompile_test.cpp | 5 +- .../tests/alscriptregionusage_test.cpp | 17 ++++-- indra/newview/tests/alscriptsnippets_test.cpp | 11 ++-- .../tests/alscriptstudioaccount_test.cpp | 6 +- .../tests/alscriptstudiocommands_test.cpp | 4 +- .../newview/tests/alscriptstudiodoc_test.cpp | 8 ++- .../tests/alscriptstudiofiles_test.cpp | 12 +++- .../tests/alscriptstudiomasters_test.cpp | 19 ++++++- .../tests/alscriptstudioorphans_test.cpp | 11 ++++ .../tests/alscriptstudiorecovery_test.cpp | 55 +++++++++++++++---- .../tests/alscriptstudiosaving_test.cpp | 16 ++++-- .../newview/tests/alscriptstudiovim_test.cpp | 18 +++++- 18 files changed, 191 insertions(+), 60 deletions(-) diff --git a/indra/newview/tests/alnotecardembedded_test.cpp b/indra/newview/tests/alnotecardembedded_test.cpp index 907946fce4..5b5831e23d 100644 --- a/indra/newview/tests/alnotecardembedded_test.cpp +++ b/indra/newview/tests/alnotecardembedded_test.cpp @@ -188,8 +188,11 @@ namespace { const auto a = item("A"), b = item("B"), c = item("C"); ALNotecardEmbedded& card = make("one " + at(2) + " two " + at(0) + "\n", { a, b, c }); - UNSCOPED_INFO("a button over each item the text stands"); - REQUIRE(view->atomAt(ALTextPos(0, 4)) != nullptr); + { + INFO("a button over each item the text stands"); + CHECK(view->atomAt(ALTextPos(0, 4)) != nullptr); + CHECK(view->atomAt(ALTextPos(0, 13)) != nullptr); + } std::string text; ALNotecardEmbedded::items_t items; card.forSave(text, items); @@ -226,8 +229,9 @@ namespace UNSCOPED_INFO("a new frame's drop lands where the pointer is"); CHECK(view->document().line(where.line).substr(where.column, 4) == at(2)); REQUIRE(tab->items().size() == size_t(3)); + view->undo(); UNSCOPED_INFO("one undo takes the last away"); - CHECK(view->undoJournal().canUndo()); + CHECK(view->document().line(where.line).substr(where.column, 8) == at(0) + at(1)); } TEST_CASE_METHOD(alnotecardembedded_data, "ALNotecardEmbedded: a drop of what a notecard may not carry is refused and says why", "[ALNotecardEmbedded]") @@ -332,7 +336,9 @@ namespace REQUIRE(back[1].isNull()); { INFO("the rest as they were"); + REQUIRE(back[0].notNull()); CHECK(back[0]->getUUID() == a->getUUID()); + REQUIRE(back[2].notNull()); CHECK(back[2]->getName() == "B"); } } diff --git a/indra/newview/tests/alrecoverykeeper_test.cpp b/indra/newview/tests/alrecoverykeeper_test.cpp index c08fe7f254..8372893390 100644 --- a/indra/newview/tests/alrecoverykeeper_test.cpp +++ b/indra/newview/tests/alrecoverykeeper_test.cpp @@ -115,17 +115,19 @@ namespace keeper.pump(3, 10.0 + ALRecoveryKeeper::DELAY); { INFO("a moment after it first changed, whatever was typed meanwhile"); - REQUIRE(mine()); - CHECK(mine()->text == "typed"); - CHECK(mine()->name == "Card"); + const std::optional typed = mine(); + REQUIRE(typed); + CHECK(typed->text == "typed"); + CHECK(typed->name == "Card"); } text = "typed more"; keeper.pump(4, 12.0); keeper.pump(4, 12.0 + ALRecoveryKeeper::DELAY); { INFO("again for the next change"); - REQUIRE(mine()); - CHECK(mine()->text == "typed more"); + const std::optional again = mine(); + REQUIRE(again); + CHECK(again->text == "typed more"); } unsaved = false; keeper.pump(4, 20.0); @@ -171,8 +173,9 @@ namespace keeper.took(*offered, 7); { INFO("the window's own written"); - REQUIRE(mine()); - CHECK(mine()->text == "left behind"); + const std::optional own = mine(); + REQUIRE(own); + CHECK(own->text == "left behind"); } UNSCOPED_INFO("and the one taken up gone"); CHECK_FALSE(keeper.left()); @@ -184,7 +187,7 @@ namespace left.key = keeper.key(); left.item = item; left.text = "not wanted"; - before.write(left); + CHECK(before.write(left)); } offered = keeper.left(); REQUIRE(offered); @@ -192,6 +195,15 @@ namespace keeper.turnDown(*offered); UNSCOPED_INFO("turned down: offered no more"); CHECK_FALSE(keeper.left()); + // Among the discarded as the session that left it: told apart so from + // this session's own, set aside above. + bool turned_down = false; + for (const ALRecoveryEntry& entry : store->list()) + { + turned_down |= entry.state == ALRecoveryEntry::State::Discarded && entry.key == keeper.key() && + entry.session == "another-session"; + } + CHECK(turned_down); } TEST_CASE_METHOD(alrecoverykeeper_data, "ALRecoveryKeeper: a new item or none moves the key and a failed write is said once", "[ALRecoveryKeeper]") diff --git a/indra/newview/tests/alscriptcontentsindex_test.cpp b/indra/newview/tests/alscriptcontentsindex_test.cpp index 31c5289d54..8e5dd0e956 100644 --- a/indra/newview/tests/alscriptcontentsindex_test.cpp +++ b/indra/newview/tests/alscriptcontentsindex_test.cpp @@ -157,10 +157,10 @@ namespace CHECK(asked.size() == size_t(1)); CHECK_FALSE(index.fetched(chair)); CHECK(index.prim(chair) == nullptr); - // Unanswered, the prim would not be known, and it is read below. - REQUIRE(answer(chair, { "sit.lsl", "readme" }, true, "Chair")); + CHECK(answer(chair, { "sit.lsl", "readme" }, true, "Chair")); CHECK(heard == 1); CHECK(names(chair) == std::string("sit.lsl readme")); + REQUIRE(index.prim(chair) != nullptr); CHECK(index.prim(chair)->name == std::string("Chair")); CHECK_FALSE(index.asking(chair)); @@ -330,7 +330,7 @@ namespace index.refresh(11.0); { INFO("its serial moved: asked"); - CHECK(pending.size() == 1); + REQUIRE(pending.size() == 1); CHECK(asked.back().first == chair); } answer(chair, { "sit.lsl", "new.lsl" }); diff --git a/indra/newview/tests/alscriptexternaleditor_test.cpp b/indra/newview/tests/alscriptexternaleditor_test.cpp index 5803af8385..73ae638ae0 100644 --- a/indra/newview/tests/alscriptexternaleditor_test.cpp +++ b/indra/newview/tests/alscriptexternaleditor_test.cpp @@ -182,7 +182,7 @@ namespace CHECK(a.external->log->path() == file + ".log"); CHECK(studio.subscribed == Names{ "a" }); CHECK(a.external->subscribed); - CHECK(studio.started == Names{ file }); + REQUIRE(studio.started == Names{ file }); UNSCOPED_INFO("what it holds, known"); CHECK(a.external->written == std::string("default {}")); @@ -321,6 +321,7 @@ namespace ALScriptCompileResult result; result.messages = { "(1, 2) : ERROR : Syntax error\x07" }; unit.log(a, result); + REQUIRE(a.external->log); const std::string log = contents(a.external->log->path()); { INFO("the log: when, then the words, what cannot be printed dropped"); @@ -348,6 +349,7 @@ namespace ALScriptCompileResult result; unit.log(a, result); const std::string file = unit.fileName(a); + REQUIRE(a.external->log); const std::string log = a.external->log->path(); CHECK(exists(file)); CHECK(exists(log)); diff --git a/indra/newview/tests/alscriptkeymap_test.cpp b/indra/newview/tests/alscriptkeymap_test.cpp index 9979bdd2f1..009e895a73 100644 --- a/indra/newview/tests/alscriptkeymap_test.cpp +++ b/indra/newview/tests/alscriptkeymap_test.cpp @@ -332,8 +332,10 @@ namespace UNSCOPED_INFO("back on the Mac's Control-minus"); CHECK(has("back", ALKeyChord{ '-', MASK_MAC_CONTROL })); #else - UNSCOPED_INFO("back on Alt-Left and forward on Alt-Right"); - CHECK((has("back", ALKeyChord{ KEY_LEFT, MASK_ALT }) && has("forward", ALKeyChord{ KEY_RIGHT, MASK_ALT }))); + UNSCOPED_INFO("back on Alt-Left"); + CHECK(has("back", ALKeyChord{ KEY_LEFT, MASK_ALT })); + UNSCOPED_INFO("forward on Alt-Right"); + CHECK(has("forward", ALKeyChord{ KEY_RIGHT, MASK_ALT })); #endif } @@ -492,8 +494,8 @@ namespace UNSCOPED_INFO("and a problem's"); CHECK(has(ALScriptKeymap::menuKeys("next_problem"), ALKeyChord{ KEY_F8, MASK_NONE })); UNSCOPED_INFO("the editor's command of that name untouched"); - REQUIRE(ALScriptKeymap::keysOf(ALScriptKeymap::current(), C::NextChange).empty() == - ALScriptKeymap::keysOf(ALKeymap::standard(), C::NextChange).empty()); + CHECK(ALScriptKeymap::keysOf(ALScriptKeymap::current(), C::NextChange) == + ALScriptKeymap::keysOf(ALKeymap::standard(), C::NextChange)); ALScriptKeymap::restoreAll(); } } diff --git a/indra/newview/tests/alscriptnavigation_test.cpp b/indra/newview/tests/alscriptnavigation_test.cpp index b5688d568c..ab5e04378f 100644 --- a/indra/newview/tests/alscriptnavigation_test.cpp +++ b/indra/newview/tests/alscriptnavigation_test.cpp @@ -229,12 +229,17 @@ namespace a.editor->setCaret(ALTextPos(2, 0)); unit.noteJump(true); unit.goBack(false); + REQUIRE(studio.shown.size() == 3); UNSCOPED_INFO("a walk ended: the next is a jump of its own"); CHECK(studio.shown.back() == "a:2"); a.loaded = false; unit.noteJump(); - UNSCOPED_INFO("a tab not loaded: nothing noted"); - CHECK((!unit.canGo(false) || studio.shown.size() == 3)); + { + // Noting a place drops the way forward, a:2 here. + INFO("a tab not loaded: nothing noted"); + CHECK(unit.places(false) == 1); + CHECK(unit.canGo(true)); + } } TEST_CASE_METHOD(alscriptnavigation_data, "alscriptnavigation: a row stayed on opens as a preview unless already open or passed or left", "[alscriptnavigation]") diff --git a/indra/newview/tests/alscriptrecompile_test.cpp b/indra/newview/tests/alscriptrecompile_test.cpp index fe67d2fabf..47c5491713 100644 --- a/indra/newview/tests/alscriptrecompile_test.cpp +++ b/indra/newview/tests/alscriptrecompile_test.cpp @@ -156,6 +156,7 @@ namespace window.runs = { { id(11), false } }; run.recompile({ one(10, "A") }, { { prim, "House" } }, "mono"); REQUIRE(window.listedFor.size() == 1); + REQUIRE(window.listedFor[0].size() == 1); CHECK(window.listedFor[0].front().first == prim); CHECK(run.running()); REQUIRE(window.asked.size() == size_t(Recompile::AT_ONCE)); @@ -172,7 +173,7 @@ namespace UNSCOPED_INFO("A not known: the save asks"); CHECK_FALSE(window.asked[0].running.has_value()); - for (size_t i = 0; i < 4; ++i) + for (size_t i = 0; i < size_t(Recompile::AT_ONCE); ++i) { window.asked[i].told(compiled()); } @@ -224,6 +225,8 @@ namespace CHECK(said("CompileFailed", true)); UNSCOPED_INFO("one not sent said with why"); CHECK(said("SaveFailed", true)); + UNSCOPED_INFO("each but B said"); + REQUIRE(window.scripts.size() == 5); const Recompile::Script& c = window.scripts[1]; { INFO("C's rows, by the compiler"); diff --git a/indra/newview/tests/alscriptregionusage_test.cpp b/indra/newview/tests/alscriptregionusage_test.cpp index 5e1c3d1616..74cc9e1d73 100644 --- a/indra/newview/tests/alscriptregionusage_test.cpp +++ b/indra/newview/tests/alscriptregionusage_test.cpp @@ -169,6 +169,7 @@ namespace CHECK(heard == 1); REQUIRE(usage.usageOf(id(1))); REQUIRE(usage.usageOf(id(1))->urls == 2); + REQUIRE(usage.usageOf(id(2))); CHECK(usage.usageOf(id(2))->memory == 16384); LLSD parcel; parcel["parcels"][0]["objects"].append(object(id(3), 131072, 0)); @@ -212,20 +213,21 @@ namespace UNSCOPED_INFO("the attachments once a minute, whichever asks"); REQUIRE(attachmentAsks.size() == 1U); - // An answer after it is gone goes nowhere. + // An answer after its asker is gone goes nowhere, and passes by + // returning; one to an asker still here is heard. Told late = landAsks.back().second; - { - Usage gone(fake()); - } std::optional other; other.emplace(fake()); other->ask({ id(3) }, 300.0); + UNSCOPED_INFO("the other asks for itself"); + REQUIRE(landAsks.size() == 4U); Told orphaned = landAsks.back().second; other.reset(); + const S32 before = heard; orphaned(parcel); late(parcel); UNSCOPED_INFO("the one still here heard"); - REQUIRE(usage.usageOf(id(3)) != nullptr); + CHECK(heard == before + 1); } TEST_CASE_METHOD(alscriptregionusage_data, "ALScriptRegionUsage: an estate manager asks each owner's script time once a minute", "[ALScriptRegionUsage]") @@ -265,6 +267,7 @@ namespace // Memory first, then time: both kept. LLSD details; details["parcels"][0]["objects"].append(object(id(1), 65536, 0)); + REQUIRE_FALSE(landAsks.empty()); landAsks[0].second(details); Usage::times_t times; times[id(1)] = 0.125f; @@ -285,6 +288,7 @@ namespace } // Memory again: the time stays. landAsks[0].second(details); + REQUIRE(usage.usageOf(id(1))); UNSCOPED_INFO("time kept through memory"); CHECK(usage.usageOf(id(1))->time == 0.125f); @@ -296,9 +300,11 @@ namespace timeAsks[0].second(later); { INFO("left out: no time now, its memory kept"); + REQUIRE(usage.usageOf(id(1))); REQUIRE_FALSE(usage.usageOf(id(1))->hasTime()); CHECK(usage.usageOf(id(1))->hasMemory()); } + REQUIRE(usage.usageOf(id(2))); UNSCOPED_INFO("named: its time"); CHECK(usage.usageOf(id(2))->time == 0.5f); Usage::times_t full; @@ -307,6 +313,7 @@ namespace full[id(100 + n)] = 0.01f; } timeAsks[0].second(full); + REQUIRE(usage.usageOf(id(2))); UNSCOPED_INFO("a full answer leaving it out: its time stands"); CHECK(usage.usageOf(id(2))->time == 0.5f); } diff --git a/indra/newview/tests/alscriptsnippets_test.cpp b/indra/newview/tests/alscriptsnippets_test.cpp index 5fde82db5a..99ace32c58 100644 --- a/indra/newview/tests/alscriptsnippets_test.cpp +++ b/indra/newview/tests/alscriptsnippets_test.cpp @@ -175,11 +175,11 @@ namespace CHECK_FALSE(LLFile::isfile(ALFileWrite::besideOf(file))); // A file that cannot be written is not touched: never opened for - // writing and left cut short. Root writes what it likes. + // writing and left cut short. #if !LL_WINDOWS if (geteuid() == 0) { - return; + SKIP("root writes what it likes"); } #endif const std::string written = read(file); @@ -278,8 +278,11 @@ namespace // A third body takes the next number free. merged = ALScriptSnippets::merge(own, { snippet("say", "llSay(0, \"c\");") }); - UNSCOPED_INFO("numbered past the taken"); - CHECK(own.back().name == std::string("say (3)")); + { + INFO("numbered past the taken"); + CHECK(merged.renamed == 1U); + CHECK(own.back().name == std::string("say (3)")); + } } TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: a followed notecard's snippets go to their languages and are never saved", "[alscriptsnippets]") diff --git a/indra/newview/tests/alscriptstudioaccount_test.cpp b/indra/newview/tests/alscriptstudioaccount_test.cpp index ac0b930104..fe537260b7 100644 --- a/indra/newview/tests/alscriptstudioaccount_test.cpp +++ b/indra/newview/tests/alscriptstudioaccount_test.cpp @@ -37,6 +37,7 @@ namespace { LLSD state; state["rect"] = LLSD().with(0, 1).with(1, 2); + state["scroll_map"] = true; state["open"]["tabs"] = LLSD::emptyArray(); state["pinned"] = LLSD().with(0, "a"); state["recent_scripts"] = LLSD().with(0, "s"); @@ -52,8 +53,9 @@ namespace } { INFO("the shape, the switches and the files left"); - REQUIRE(state.has("rect")); - REQUIRE(state.has("recent_files")); + CHECK(state.has("rect")); + CHECK(state.has("scroll_map")); + CHECK(state.has("recent_files")); } { INFO("into the account's"); diff --git a/indra/newview/tests/alscriptstudiocommands_test.cpp b/indra/newview/tests/alscriptstudiocommands_test.cpp index 6ce86db936..31f965fbed 100644 --- a/indra/newview/tests/alscriptstudiocommands_test.cpp +++ b/indra/newview/tests/alscriptstudiocommands_test.cpp @@ -87,7 +87,7 @@ namespace CHECK_FALSE(commands.addUnlisted("save", [this]() { done.push_back("third"); })); commands.run("save"); UNSCOPED_INFO("the first's"); - CHECK(done == std::vector{ "first" }); + REQUIRE(done == std::vector{ "first" }); UNSCOPED_INFO("an unlisted one is a command like any"); CHECK(commands.addUnlisted("reveal", [this]() { done.push_back("reveal"); })); commands.run("reveal"); @@ -142,7 +142,7 @@ namespace }); commands.run("first"); UNSCOPED_INFO("ran to its end"); - CHECK(done == std::vector{ "first" }); + REQUIRE(done == std::vector{ "first" }); commands.run("made_63"); UNSCOPED_INFO("and what it made runs"); CHECK(done.back() == std::string("made")); diff --git a/indra/newview/tests/alscriptstudiodoc_test.cpp b/indra/newview/tests/alscriptstudiodoc_test.cpp index 5fe96f5b05..d939000fad 100644 --- a/indra/newview/tests/alscriptstudiodoc_test.cpp +++ b/indra/newview/tests/alscriptstudiodoc_test.cpp @@ -204,8 +204,12 @@ namespace } UNSCOPED_INFO("the whole line"); CHECK(util->range == ALTextRange(ALTextPos(1, 0), ALTextPos(1, lsl.lineLength(1)))); - UNSCOPED_INFO("angled"); - CHECK(Doc::namedIn(lsl, ALTextPos(2, 12), false, none)->name == "sys.lsl"); + const std::optional sys = Doc::namedIn(lsl, ALTextPos(2, 12), false, none); + { + INFO("angled"); + REQUIRE(sys.has_value()); + CHECK(sys->name == "sys.lsl"); + } { INFO("not a string elsewhere"); CHECK_FALSE(Doc::namedIn(lsl, ALTextPos(3, 12), false, none)); diff --git a/indra/newview/tests/alscriptstudiofiles_test.cpp b/indra/newview/tests/alscriptstudiofiles_test.cpp index d8b9eafdc0..dc50501e0c 100644 --- a/indra/newview/tests/alscriptstudiofiles_test.cpp +++ b/indra/newview/tests/alscriptstudiofiles_test.cpp @@ -330,7 +330,9 @@ namespace REQUIRE_FALSE(services.opened.empty()); CHECK(services.opened.back().ref == a.ref); } - studio.menu->findChild("clear_recent")->onCommit(); + LLMenuItemCallGL* clear = studio.menu->findChild("clear_recent"); + REQUIRE(clear != nullptr); + clear->onCommit(); { INFO("the clear item clears"); CHECK(unit.recentFiles().empty()); @@ -350,6 +352,7 @@ namespace CHECK(contents(f.file) == std::string("new")); { INFO("its own write is no change to the watch"); + REQUIRE(f.watch); CHECK_FALSE(f.watch->check()); CHECK_FALSE(f.watch->check()); } @@ -472,8 +475,8 @@ namespace TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: Save to File writes a copy and Save As makes the tab the new file", "[alscriptstudiofiles]") { ALScriptStudioFiles& unit = make(); - Doc& s = scriptTab("s", "script text"); - services.front = 0; + scriptTab("s", "script text"); + services.front = 0; unit.saveCopy(); UNSCOPED_INFO("named after the tab"); CHECK(studio.savedAs == std::string("s")); @@ -503,8 +506,11 @@ namespace } { INFO("noted, settled, checked"); + REQUIRE_FALSE(unit.recentFiles().empty()); CHECK(unit.recentFiles().front() == in("g.luau")); + REQUIRE_FALSE(studio.settled.empty()); CHECK(studio.settled.back() == f.id); + REQUIRE_FALSE(studio.checked.empty()); CHECK(studio.checked.back() == f.id + " now"); } UNSCOPED_INFO("told of as written"); diff --git a/indra/newview/tests/alscriptstudiomasters_test.cpp b/indra/newview/tests/alscriptstudiomasters_test.cpp index b6e84bf597..a5b706fa2c 100644 --- a/indra/newview/tests/alscriptstudiomasters_test.cpp +++ b/indra/newview/tests/alscriptstudiomasters_test.cpp @@ -322,7 +322,12 @@ namespace return link; } - const al_studio_test::FakeServices::Said& said() const { return services.reports.back(); } + // The last thing said; there must be something. + const al_studio_test::FakeServices::Said& said() const + { + REQUIRE_FALSE(services.reports.empty()); + return services.reports.back(); + } static bool says(const std::string& text, const std::string& word) { return text.find(word) != std::string::npos; } typedef std::vector Names; }; @@ -420,8 +425,14 @@ namespace studio->picked = in(name); const size_t before = services.reports.size(); unit->linkToFile(doc); - return !disk.linkOf(doc.ref) && services.reports.size() == before + 1 && - alscriptstudiomasters_data::says(said().text, word) && said().failure && said().doc == doc.id && services.findDoc(doc.id) == &doc; + // Read straight, not through said(): the count checked first guards it, and an assertion in + // here would clear the message of the CHECK this is called in. + if (disk.linkOf(doc.ref) || services.reports.size() != before + 1) + { + return false; + } + const al_studio_test::FakeServices::Said& last = services.reports.back(); + return alscriptstudiomasters_data::says(last.text, word) && last.failure && last.doc == doc.id && services.findDoc(doc.id) == &doc; }; UNSCOPED_INFO("SLua for an LSL script"); CHECK(refused(lsl, "door.luau", "MasterNotLSLFile")); @@ -987,6 +998,7 @@ namespace { INFO("Switch to File: the file's tab in its place, said there"); REQUIRE_FALSE(services.findDoc(door_ref)); + REQUIRE_FALSE(studio->closed.empty()); CHECK(studio->closed.back() == "door"); CHECK(says(said().text, "MasterGaveWay")); CHECK(said().doc == "disk:" + in("door.lsl")); @@ -1004,6 +1016,7 @@ namespace { INFO("kept once clean"); REQUIRE(services.findDoc(lamp_ref) == &lamp); + REQUIRE(lamp.offer); CHECK(says(lamp.offer->text, "MasterLinkedClean")); } lamp.editor->setText("default {}\n"); diff --git a/indra/newview/tests/alscriptstudioorphans_test.cpp b/indra/newview/tests/alscriptstudioorphans_test.cpp index 1203f8fccc..c2fac1d300 100644 --- a/indra/newview/tests/alscriptstudioorphans_test.cpp +++ b/indra/newview/tests/alscriptstudioorphans_test.cpp @@ -258,6 +258,7 @@ namespace } { INFO("and said, with what can be done"); + REQUIRE_FALSE(services().reports.empty()); CHECK(services().reports.back().text == said("OrphanRemovedKept", "[NAME]", "b")); CHECK(services().reports.back().actions == Names{ "copy", "export" }); } @@ -417,10 +418,12 @@ namespace INFO("restored, the source in front"); CHECK(studio.did == Names{ "source a", "carried a: restored text" }); CHECK_FALSE(a.recoverable); + REQUIRE_FALSE(services().reports.empty()); CHECK(services().reports.back().text == said("RecoveryRestored", "[NAME]", "a")); } a.recoverable = ALRecoveryEntry(); unit.noticeAction("discard_left"); + REQUIRE_FALSE(studio.did.empty()); CHECK(studio.did.back() == "discard"); CHECK_FALSE(a.recoverable); a.recoverable = ALRecoveryEntry(); @@ -460,6 +463,7 @@ namespace a.orphan->kind = Orphan::Away; unit.refreshNotice(); LLView* holder = bar->getParent(); + REQUIRE(holder != nullptr); CHECK(holder->getVisible()); CHECK(bar->getChild("notice_text")->getText() == said("NoticeAway")); LLButton* first = bar->getChild("notice_first"); @@ -508,6 +512,7 @@ namespace } a.uploaded.text = std::make_shared("default { }"); unit.noticeAction("compare_compiled"); + REQUIRE_FALSE(studio.did.empty()); UNSCOPED_INFO("compared: as saved, beside what the source makes"); CHECK(studio.did.back() == "compare a: default { touch_start(integer n) { llDie(); } } | default { } (" + said("CompareCompiled") + " | " + said("CompareMade") + ")"); @@ -524,6 +529,7 @@ namespace CHECK(studio.did == Names{ "source a", "carried a: default { touch_start(integer n) { llDie(); } }" }); CHECK_FALSE(a.compiledDiffers); } + REQUIRE_FALSE(services().reports.empty()); UNSCOPED_INFO("and said"); CHECK(services().reports.back().text == said("CompiledTaken", "[NAME]", "a")); a.compiledDiffers = std::string("x"); @@ -531,6 +537,7 @@ namespace { INFO("kept: let go of, the next save replacing it"); CHECK_FALSE(a.compiledDiffers); + REQUIRE_FALSE(studio.did.empty()); CHECK(studio.did.back() == "source a"); } UNSCOPED_INFO("nothing more to say"); @@ -548,6 +555,7 @@ namespace UNSCOPED_INFO("offered as listed"); CHECK_FALSE(kept.whole); unit.noticeAction("compare_kept"); + REQUIRE_FALSE(studio.did.empty()); UNSCOPED_INFO("now beside kept"); CHECK(studio.did.back() == "compare a: default {} | default { state_entry() { } } (" + said("CompareNow") + " | " + said("CompareKept", "[WHEN]", kept.whenSaid()) + ")"); @@ -556,6 +564,7 @@ namespace unit.noticeAction("restore"); { INFO("the source in front first"); + REQUIRE(studio.did.size() >= 2); CHECK(studio.did[studio.did.size() - 2] == "source a"); CHECK(studio.did.back() == "carried a: default { state_entry() { } }"); } @@ -587,6 +596,7 @@ namespace unit.noticeAction("retry"); { INFO("taken up, and let go of"); + REQUIRE_FALSE(studio.did.empty()); CHECK(studio.did.back() == "offer a: retry"); CHECK_FALSE(a.offer); } @@ -719,6 +729,7 @@ namespace LLStringUtil::format_map_t args; args["[NAME]"] = "a"; args["[WHEN]"] = ALRecoveryEntry::sayWhen(shown.when); + REQUIRE_FALSE(services().reports.empty()); UNSCOPED_INFO("and said"); CHECK(services().reports.back().text == services().words("HistoryRestored", args)); diff --git a/indra/newview/tests/alscriptstudiorecovery_test.cpp b/indra/newview/tests/alscriptstudiorecovery_test.cpp index 93f723e8dc..6adde634cb 100644 --- a/indra/newview/tests/alscriptstudiorecovery_test.cpp +++ b/indra/newview/tests/alscriptstudiorecovery_test.cpp @@ -38,6 +38,7 @@ #include #include #include +#include namespace { @@ -452,7 +453,9 @@ namespace { INFO("a file there, opened and taken up"); REQUIRE(studio().activated.size() == 1); - REQUIRE(services().findDoc("disk:" + file.file)->carriedText); + const Doc* on_disk = services().findDoc("disk:" + file.file); + REQUIRE(on_disk != nullptr); + REQUIRE(on_disk->carriedText); } studio().inHand = false; @@ -480,7 +483,9 @@ namespace { INFO("its script opened, and the text to go in once loaded"); REQUIRE(services().opened.size() == 1); - CHECK(services().findDoc(ALScriptRef(entry.object, entry.item))->carriedText == std::optional("kept\n")); + const Doc* reopened = services().findDoc(ALScriptRef(entry.object, entry.item)); + REQUIRE(reopened != nullptr); + CHECK(reopened->carriedText == std::optional("kept\n")); } } @@ -497,9 +502,9 @@ namespace Doc& mine = tab("mine", "a\n"); type(mine, "b"); r.keep(mine); - Doc& theirs = tab("theirs", "c\n"); - const Entry left = require_left_for(theirs, "left\n"); - Doc& gone = tab("gone", "d\n"); + Doc& theirs = tab("theirs", "c\n"); + require_left_for(theirs, "left\n"); + Doc& gone = tab("gone", "d\n"); type(gone, "e"); CHECK(r.setAside(gone)); r.show(); @@ -536,7 +541,11 @@ namespace theirs.carriedText.reset(); theirs.loaded = false; + // The offer made afresh, not the one before still standing. + studio().candidates.clear(); r.show(); + UNSCOPED_INFO("the one thrown away, offered still"); + REQUIRE(studio().candidates.size() == 1); studio().chosen(studio().candidates.front().value); UNSCOPED_INFO("picked, taken up"); REQUIRE(theirs.carriedText.has_value()); @@ -603,8 +612,11 @@ namespace // Loaded clean, as a tab opened again is: what it keeps of its own // forgets nothing of that. r.keep(again); - UNSCOPED_INFO("still there to take"); - CHECK(std::filesystem::exists(fsyspath(again.recoverable->path))); + { + INFO("still there to take"); + REQUIRE(again.recoverable); + CHECK(std::filesystem::exists(fsyspath(again.recoverable->path))); + } const Entry offered = *again.recoverable; r.takeUp(again, offered); UNSCOPED_INFO("taken up"); @@ -754,7 +766,8 @@ namespace UNSCOPED_INFO("not another session's"); REQUIRE_FALSE(back); const std::vector listed = store->list(); - Entry read = listed.front(); + REQUIRE(listed.size() == 1); + Entry read = listed.front(); { INFO("read whole, the picks with it"); CHECK(store->load(read)); @@ -801,6 +814,12 @@ namespace std::vector taken; bool has = true; + // The windows' taker is the process's, and holds this case's own: + // let go of however this ends. + struct TakerBack + { + ~TakerBack() { ALRecovery::takeWindowsTo(nullptr); } + } taker_back; ALRecovery::takeWindowsTo([&taken, &has](const Entry& entry) { taken.push_back(entry.name); return has; @@ -831,7 +850,6 @@ namespace REQUIRE(taken.size() == 2); REQUIRE(studio().orphans.size() == 2); } - ALRecovery::takeWindowsTo(nullptr); } TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: rekey lets go of the old key's entry and writes under the new one", "[alscriptstudiorecovery]") @@ -900,9 +918,26 @@ namespace UNSCOPED_INFO("the old item's moved"); REQUIRE(history->list("task:p:old").empty()); const std::vector moved = history->list("task:p:new"); - UNSCOPED_INFO("behind the new one's"); REQUIRE(moved.size() == 2U); + { + // Newest first: the new item's own save, then the old item's. + INFO("behind the new one's"); + ALSavedText newer = moved[0]; + ALSavedText older = moved[1]; + REQUIRE(history->load(newer)); + REQUIRE(history->load(older)); + CHECK(newer.text == "b"); + CHECK(older.text == "a"); + } + // Each item's saves are a folder of the history's. + const auto folders = [&folder]() { + std::error_code ec; + return std::distance(std::filesystem::directory_iterator(fsyspath(folder), ec), std::filesystem::directory_iterator()); + }; + const auto before = folders(); ALRecovery::keepSaved(save("", "nobody's", 5.0)); written(); + UNSCOPED_INFO("a save of nobody's kept nowhere"); + CHECK(folders() == before); } } diff --git a/indra/newview/tests/alscriptstudiosaving_test.cpp b/indra/newview/tests/alscriptstudiosaving_test.cpp index c41f2ac5fa..704d0489b5 100644 --- a/indra/newview/tests/alscriptstudiosaving_test.cpp +++ b/indra/newview/tests/alscriptstudiosaving_test.cpp @@ -338,6 +338,7 @@ namespace Doc& unchecked = tab("unchecked", "default {}"); saving.save(unchecked); + REQUIRE_FALSE(studio.tidied.empty()); UNSCOPED_INFO("no fixes over a text not checked"); CHECK(studio.tidied.back() == std::string("mt")); @@ -349,6 +350,7 @@ namespace // They changed the text: checked again before it goes. { INFO("checked again"); + REQUIRE_FALSE(studio.analysed.empty()); CHECK(studio.analysed.back() == "a"); CHECK(studio.sent.empty()); } @@ -387,7 +389,7 @@ namespace CHECK(doc.save.checked()); saving.save(doc); UNSCOPED_INFO("its errors stop it"); - CHECK(lastSaid() == std::string("PreflightErrors")); + REQUIRE(lastSaid() == std::string("PreflightErrors")); const al_studio_test::FakeServices::Said& said = services.reports.back(); { INFO("a failure, of the tab, with Save Anyway"); @@ -488,7 +490,7 @@ namespace Doc& big = tab("big", too_much); saving.save(big); UNSCOPED_INFO("too large: said"); - CHECK(lastSaid() == std::string("SaveTooLarge")); + REQUIRE(lastSaid() == std::string("SaveTooLarge")); UNSCOPED_INFO("with what would shrink it"); CHECK(services.reports.back().text.find("SaveTooLargePlain") != std::string::npos); { @@ -539,7 +541,7 @@ namespace UNSCOPED_INFO("sent all the same"); CHECK(studio.sent.size() == size_t(1)); UNSCOPED_INFO("said as it went"); - CHECK(lastSaid() == std::string("SavingWithErrors")); + REQUIRE(lastSaid() == std::string("SavingWithErrors")); { INFO("a failure of the tab, nothing to press"); CHECK(services.reports.back().failure); @@ -580,6 +582,7 @@ namespace REQUIRE(studio.runs.size() == 3); type(doc, "\n// b"); studio.answerRun(made); + REQUIRE_FALSE(studio.analysed.empty()); UNSCOPED_INFO("checked again"); CHECK(studio.analysed.back() == "a"); { @@ -708,7 +711,7 @@ namespace CHECK(services.findDoc(std::string_view("typed"))); } UNSCOPED_INFO("and said so"); - CHECK(lastSaid() == std::string("CopiedToKeptOpen")); + REQUIRE(lastSaid() == std::string("CopiedToKeptOpen")); UNSCOPED_INFO("said of the tab kept open"); CHECK(services.reports.back().doc == std::string("typed")); } @@ -923,6 +926,7 @@ namespace saving.save(doc); UNSCOPED_INFO("on its way"); CHECK(doc.save.sending()); + REQUIRE(studio.sent.size() == size_t(1)); const U64 mine = studio.sent.back().options.sender.request; { INFO("sent as a request of the studio's"); @@ -972,7 +976,7 @@ namespace type(dirty, "\n// mine"); saving.savedElsewhere(saved(dirty, "default {}\n// theirs", ALScriptOrigin::Bridge)); UNSCOPED_INFO("asked whose to keep"); - CHECK(lastSaid() == std::string("SavedElsewhereConflict")); + REQUIRE(lastSaid() == std::string("SavedElsewhereConflict")); UNSCOPED_INFO("with the four answers"); CHECK(services.reports.back().actions == Names{ "take_saved", "keep_saved", "merge_saved", "compare_saved" }); { @@ -1058,7 +1062,7 @@ namespace type(doc, "x"); saving.save(doc); UNSCOPED_INFO("moved: said"); - CHECK(lastSaid() == std::string("SaveWorldMoved")); + REQUIRE(lastSaid() == std::string("SaveWorldMoved")); UNSCOPED_INFO("with what can be done"); CHECK(services.reports.back().actions == Names{ "save_anyway", "reload_world", "merge_world", "compare_world" }); { diff --git a/indra/newview/tests/alscriptstudiovim_test.cpp b/indra/newview/tests/alscriptstudiovim_test.cpp index b37ba2a8e2..cc056f1214 100644 --- a/indra/newview/tests/alscriptstudiovim_test.cpp +++ b/indra/newview/tests/alscriptstudiovim_test.cpp @@ -377,6 +377,12 @@ namespace a.editor->insertText(" "); { INFO("wq over something unsaved saves it to close"); + CHECK(ex(a, "wq")); + CHECK(studio.savedToClose == Names{ "a" }); + } + studio.savedToClose.clear(); + { + INFO("and x the same"); CHECK(ex(a, "x")); CHECK(studio.savedToClose == Names{ "a" }); } @@ -420,11 +426,17 @@ namespace CHECK(ex(a, "set", "rnu")); CHECK(relativeNumbers); { - INFO("off already: left"); + INFO("on: turned off"); CHECK(ex(a, "set", "norelativenumber")); CHECK_FALSE(relativeNumbers); CHECK(ran.size() == 4); } + { + INFO("off already: left"); + CHECK(ex(a, "set", "nornu")); + CHECK_FALSE(relativeNumbers); + CHECK(ran.size() == 4); + } UNSCOPED_INFO("an option it does not know is vim's"); CHECK_FALSE(ex(a, "set", "hlsearch")); } @@ -626,6 +638,7 @@ namespace go("tabn", "9"); UNSCOPED_INFO("nor to go to"); CHECK(vim->message() == std::string("VimBadArgument")); + REQUIRE(services.frontDoc()); UNSCOPED_INFO("still where it was"); CHECK(services.frontDoc()->id == std::string("delta")); } @@ -661,6 +674,7 @@ namespace CHECK((go("tabm", "-1"), order()) == std::string("alpha beta gamma delta")); UNSCOPED_INFO(":tabm 3 after the third, past itself"); CHECK((go("tabm", "3"), order()) == std::string("alpha gamma beta delta")); + REQUIRE(services.frontDoc()); UNSCOPED_INFO("still in front"); CHECK(services.frontDoc()->id == std::string("beta")); go("tabm", "+5"); @@ -739,10 +753,12 @@ namespace CHECK(said->message() == std::string("VimArgumentRequired")); ex(a, "e", "beta"); + REQUIRE(services.frontDoc()); UNSCOPED_INFO("a tab by its name"); CHECK(services.frontDoc()->id == std::string("beta")); vim.pump(); ex(a, "e", "#"); + REQUIRE(services.frontDoc()); UNSCOPED_INFO("# the one before"); CHECK(services.frontDoc()->id == std::string("alpha")); Doc& main = tab("mainfile"); From e075a73c2f8deefc85c2566289b0aa6486be32f2 Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 04:47:56 -0400 Subject: [PATCH 4/9] The viewer's model tests check what they say they check Old test bugs TM24's idiom pass found (83b0c1dd65), fixed. - alselectionoutline_gl: the HUD case counted only black as hidden, but these draws show a hidden part dimmed, so a HUD pass reading the scene's depth could not fail it; it counts anything short of full, as the world case does. The line's coverage, said to be gone within a pixel and a half, was required to be zero only three pixels out; it is required gone two pixels along the row. diagonal(), which asserts, is no longer called inside another assertion. - alscopedata: "nothing anywhere else" checked one bin; every channel is checked to light exactly one. The case saying the binning is the inverse of chromaCellCentre checks it, over four colours. - alfarplane: "eyeToSky absent from hazeF.glsl" passed with the file missing; it requires the call that hands over eye space first. - alagentlocationformat: "only LANDMARK truncates" checked one other format; it checks all five against a 200-byte name. - altexturetable: "a reservation changes nothing but the allocations" compares a reserved table with an unreserved one, position by position. - alterrainsurfacemaps: every coordinate is checked to be visited once, as the comment says, and none off the map. - aldaycyclelandmarks: each landmark is required found before its range is checked; an absent one stayed at 0, inside the range. - llvocache: the inverted settings' loop checked ranges a final clamp always met; it checks the exact factor either side of the low setting. "Still comes out ordered" checks the factor, not >= 0. - alwhitebalancesolver: the neutral gain is checked exactly 1, which is what the shader's fast path tests for. - cppfeatures: the overrides are called through a base that answers otherwise, so a missing one fails at run time; "forbids copies" checks it. - alhuddepth_gl: "nothing cleared" counts the depths that differ and says where the first is. A member nothing read is gone. - Points, texels, records, history steps, labels and the cube's size are required before they are read in these and alcurvemodel, algradehistory, alcrashreporter and lutcube. Each new or corrected check fails under a small change to the test's own input or expectation, at the check and not by crashing. Co-Authored-By: Claude Opus 5.5 --- .../tests/alagentlocationformat_test.cpp | 18 ++++++++-- indra/newview/tests/alcrashreporter_test.cpp | 3 ++ indra/newview/tests/alcurvemodel_test.cpp | 2 ++ .../tests/aldaycyclelandmarks_test.cpp | 7 +++- indra/newview/tests/alfarplane_test.cpp | 7 +++- indra/newview/tests/algradehistory_test.cpp | 17 +++++++-- indra/newview/tests/alhuddepth_gl_test.cpp | 22 ++++++++++-- indra/newview/tests/alscopedata_test.cpp | 36 +++++++++++++++++-- .../tests/alselectionoutline_gl_test.cpp | 17 +++++---- .../tests/alterrainsurfacemaps_test.cpp | 20 ++++++++++- indra/newview/tests/altexturetable_test.cpp | 15 ++++++++ .../tests/alwhitebalancesolver_test.cpp | 6 ++-- indra/newview/tests/cppfeatures_test.cpp | 31 +++++++++++----- indra/newview/tests/llvocache_test.cpp | 22 ++++++------ indra/newview/tests/lutcube_test.cpp | 1 + 15 files changed, 179 insertions(+), 45 deletions(-) diff --git a/indra/newview/tests/alagentlocationformat_test.cpp b/indra/newview/tests/alagentlocationformat_test.cpp index 3f35a050b8..078660c839 100644 --- a/indra/newview/tests/alagentlocationformat_test.cpp +++ b/indra/newview/tests/alagentlocationformat_test.cpp @@ -202,9 +202,21 @@ namespace TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: only LANDMARK truncates a long parcel name", "[ALAgentLocationFormat]") { const std::string long_name(200, 'y'); - ALAgentLocationFormat::format(out, LLAgentUI::LOCATION_FORMAT_NORMAL, - long_name, "Region", "", 0, 0, 0); + const auto shown = [&](LLAgentUI::ELocationFormat format) { + ALAgentLocationFormat::format(out, format, long_name, "Region", "General", 0, 0, 0); + return out; + }; UNSCOPED_INFO("NORMAL does not truncate"); - CHECK(out == long_name + ", Region"); + CHECK(shown(LLAgentUI::LOCATION_FORMAT_NORMAL) == long_name + ", Region"); + UNSCOPED_INFO("NORMAL_COORDS does not truncate"); + CHECK(shown(LLAgentUI::LOCATION_FORMAT_NORMAL_COORDS) == long_name + " (0, 0, 0)"); + UNSCOPED_INFO("NO_MATURITY does not truncate"); + CHECK(shown(LLAgentUI::LOCATION_FORMAT_NO_MATURITY) == long_name + ", Region (0, 0, 0)"); + UNSCOPED_INFO("NO_COORDS does not truncate"); + CHECK(shown(LLAgentUI::LOCATION_FORMAT_NO_COORDS) == long_name + ", Region - General"); + UNSCOPED_INFO("FULL does not truncate"); + CHECK(shown(LLAgentUI::LOCATION_FORMAT_FULL) == long_name + ", Region (0, 0, 0) - General"); + UNSCOPED_INFO("LANDMARK does"); + CHECK(shown(LLAgentUI::LOCATION_FORMAT_LANDMARK) == std::string(ALAgentLocationFormat::LANDMARK_NAME_MAX_BYTES, 'y')); } } diff --git a/indra/newview/tests/alcrashreporter_test.cpp b/indra/newview/tests/alcrashreporter_test.cpp index ba248bff05..ad5d0d26c1 100644 --- a/indra/newview/tests/alcrashreporter_test.cpp +++ b/indra/newview/tests/alcrashreporter_test.cpp @@ -121,15 +121,18 @@ namespace const LLVector3 position(128.4f, -63.6f, 22.5f); char buffer[64]; size_t length = ALCrashReporter::locationTag(buffer, sizeof(buffer), "Hippotropolis", position); + REQUIRE(length < sizeof(buffer)); CHECK(std::string(buffer, length) == "Hippotropolis/128/-64/23"); CHECK(buffer[length] == '\0'); char small[12]; length = ALCrashReporter::locationTag(small, sizeof(small), "Hippotropolis", position); + REQUIRE(length < sizeof(small)); CHECK(std::string(small, length) == "Hippotropol"); CHECK(small[length] == '\0'); length = ALCrashReporter::locationTag(small, sizeof(small), "Ahern", position); + REQUIRE(length < sizeof(small)); UNSCOPED_INFO("cut in the numbers"); CHECK(std::string(small, length) == "Ahern/128/-"); } diff --git a/indra/newview/tests/alcurvemodel_test.cpp b/indra/newview/tests/alcurvemodel_test.cpp index dbabd9c9ad..953bc84282 100644 --- a/indra/newview/tests/alcurvemodel_test.cpp +++ b/indra/newview/tests/alcurvemodel_test.cpp @@ -163,6 +163,7 @@ namespace mCurve.movePoint(1, 0.95f, 0.5f); const auto& p = mCurve.getPoints(); + REQUIRE(p.size() == size_t(4)); UNSCOPED_INFO("clamped below its right neighbour"); CHECK(p[1].mX < p[2].mX); UNSCOPED_INFO("clamped to exactly the gap"); @@ -882,6 +883,7 @@ namespace const S32 n = ALToneCurveSet::LUT_SIZE; std::vector texels; set.bake(texels, n); + REQUIRE((S32)texels.size() == n * 4); std::vector xs; for (S32 i = 0; i <= 4096; ++i) diff --git a/indra/newview/tests/aldaycyclelandmarks_test.cpp b/indra/newview/tests/aldaycyclelandmarks_test.cpp index 8dc2b48b34..28c930c042 100644 --- a/indra/newview/tests/aldaycyclelandmarks_test.cpp +++ b/indra/newview/tests/aldaycyclelandmarks_test.cpp @@ -129,7 +129,8 @@ namespace } // Landmarks are cycle positions, so they stay inside [0, 1) even when the - // crossing they came from sits across the seam. + // crossing they came from sits across the seam. Each is found first: an + // absent one is left at 0, which is inside the cycle whatever happened. TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: landmarks stay inside the cycle when a crossing is across the seam", "[ALDayCycleLandmarks]") { for (S32 i = 0; i < 20; ++i) @@ -138,6 +139,10 @@ namespace const auto marks = ALDayCycleLandmarks::find(sine(phase)); CAPTURE(phase); + REQUIRE(marks.has_sunrise); + REQUIRE(marks.has_noon); + REQUIRE(marks.has_sunset); + REQUIRE(marks.has_midnight); REQUIRE(marks.sunrise >= 0.f); REQUIRE(marks.sunrise < 1.f); REQUIRE(marks.noon >= 0.f); diff --git a/indra/newview/tests/alfarplane_test.cpp b/indra/newview/tests/alfarplane_test.cpp index 404027be15..f14475154d 100644 --- a/indra/newview/tests/alfarplane_test.cpp +++ b/indra/newview/tests/alfarplane_test.cpp @@ -354,7 +354,12 @@ namespace REQUIRE(funcs.find("abs(rel_pos.y)") == std::string::npos); UNSCOPED_INFO("the rotation is in the Environment block"); REQUIRE(shader_source("class1/deferred/environmentBlock.glsl").find("mat3 eyeToSky;") != std::string::npos); + // Read, and calling the haze with the position it unprojected, so the turn's absence below is not the + // absence of the file. + const std::string haze = shader_source("class3/deferred/hazeF.glsl"); UNSCOPED_INFO("the haze pass hands over eye space"); - REQUIRE(shader_source("class3/deferred/hazeF.glsl").find("eyeToSky") == std::string::npos); + REQUIRE(haze.find("calcAtmosphericVarsLinear(pos.xyz,") != std::string::npos); + UNSCOPED_INFO("and does not turn it itself"); + REQUIRE(haze.find("eyeToSky") == std::string::npos); } } diff --git a/indra/newview/tests/algradehistory_test.cpp b/indra/newview/tests/algradehistory_test.cpp index a903e326fb..d7ff9ebbaa 100644 --- a/indra/newview/tests/algradehistory_test.cpp +++ b/indra/newview/tests/algradehistory_test.cpp @@ -71,6 +71,7 @@ namespace const auto* t = mHistory.undo(); REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)1); UNSCOPED_INFO("from where the drag began"); CHECK(t->front().mBefore.asReal() == 0.0); UNSCOPED_INFO("to where it ended"); @@ -153,6 +154,7 @@ namespace const auto* t = mHistory.redo(); REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)1); CHECK(t->front().mAfter.asReal() == 1.0); CHECK_FALSE(mHistory.canRedo()); CHECK(mHistory.canUndo()); @@ -172,6 +174,7 @@ namespace CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)1); UNSCOPED_INFO("carrying the new values"); CHECK(t->front().mBefore.asReal() == 1.0); } @@ -189,6 +192,7 @@ namespace // The newest is still the newest after the drop. const auto* t = mHistory.undo(); REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)1); UNSCOPED_INFO("which is the last one recorded"); CHECK(t->front().mAfter.asReal() == (F64)(ALGradeHistory::MAX_DEPTH + 20)); } @@ -297,14 +301,15 @@ namespace UNSCOPED_INFO("the group's step carries its name"); CHECK(mHistory.labelOf(0) == std::string("Reset Bloom")); UNSCOPED_INFO("a lone write's step has none"); - REQUIRE(mHistory.labelOf(1).empty()); + CHECK(mHistory.labelOf(1).empty()); // And a group that follows an unnamed one is not named after it. mHistory.beginGroup(); mHistory.record("D", LLSD(0.0), LLSD(1.0), 9.f); mHistory.endGroup(); + REQUIRE(mHistory.depth() == (size_t)3); UNSCOPED_INFO("an unnamed group stays unnamed"); - REQUIRE(mHistory.labelOf(2).empty()); + CHECK(mHistory.labelOf(2).empty()); } // Groups nest, and the one the user started is the one the step is named @@ -398,6 +403,7 @@ namespace CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)1); UNSCOPED_INFO("from the value the drag began at"); CHECK(t->front().mBefore.asReal() == 0.0); CHECK(t->front().mAfter.asReal() == 2.0); @@ -410,6 +416,7 @@ namespace mHistory.record("A", LLSD(1.0), LLSD(0.0), 5.1f); UNSCOPED_INFO("the earlier step stays"); REQUIRE(mHistory.depth() == (size_t)1); + REQUIRE(mHistory.at(0).size() == (size_t)1); CHECK(mHistory.at(0).front().mName == std::string("B")); } @@ -445,7 +452,7 @@ namespace UNSCOPED_INFO("a write moves it"); CHECK(recorded != start); - mHistory.depth(); + REQUIRE(mHistory.depth() == (size_t)1); mHistory.cursor(); mHistory.at(0); mHistory.labelOf(0); @@ -476,7 +483,9 @@ namespace mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); mHistory.record("B", LLSD(2.0), LLSD(3.0), 5.f); + REQUIRE(mHistory.depth() == (size_t)2); const ALGradeHistory::Transaction shown = mHistory.at(1); + REQUIRE(shown.size() == (size_t)1); const auto* undone = mHistory.undo(); REQUIRE(undone != nullptr); REQUIRE(undone->size() == shown.size()); @@ -500,12 +509,14 @@ namespace } mHistory.undo(); mHistory.undo(); + REQUIRE(mHistory.depth() == (size_t)3); UNSCOPED_INFO("in force"); CHECK(mHistory.labelOf(0) == std::string("one")); UNSCOPED_INFO("the next redo"); CHECK(mHistory.labelOf(1) == std::string("two")); UNSCOPED_INFO("the last redo"); CHECK(mHistory.labelOf(2) == std::string("three")); + REQUIRE(mHistory.at(2).size() == (size_t)1); UNSCOPED_INFO("a redo step's changes"); CHECK(mHistory.at(2).front().mName == std::string("three")); diff --git a/indra/newview/tests/alhuddepth_gl_test.cpp b/indra/newview/tests/alhuddepth_gl_test.cpp index 63f453baa7..877c1e3208 100644 --- a/indra/newview/tests/alhuddepth_gl_test.cpp +++ b/indra/newview/tests/alhuddepth_gl_test.cpp @@ -164,7 +164,6 @@ namespace void setUp(bool reverse) { tearDown(); - mReverse = reverse; setConvention(reverse); std::map defines; @@ -396,7 +395,6 @@ namespace LLMatrix4a mSceneProjection; LLMatrix4a mHUDProjection; std::vector mRects; - bool mReverse = false; }; @@ -601,8 +599,26 @@ namespace blit(); const std::vector before = depths(mWindow); clearUnderHUD(&unloaded); + const std::vector after = depths(mWindow); + // Counted, and the first that differs placed, rather than every depth printed. + size_t differing = 0; + S32 first_x = -1; + S32 first_y = -1; + for (size_t i = 0; i < after.size(); ++i) + { + if (after[i] != before[i]) + { + if (differing == 0) + { + first_x = (S32)(i % W); + first_y = (S32)(i / W); + } + ++differing; + } + } + CAPTURE(first_x, first_y); UNSCOPED_INFO("nothing cleared"); - REQUIRE(depths(mWindow) == before); + REQUIRE(differing == size_t(0)); drawHUD(); restoreUnderHUD(&unloaded); overlay(40, 24, 8.f); diff --git a/indra/newview/tests/alscopedata_test.cpp b/indra/newview/tests/alscopedata_test.cpp index 523b9af84c..c018677d6a 100644 --- a/indra/newview/tests/alscopedata_test.cpp +++ b/indra/newview/tests/alscopedata_test.cpp @@ -17,6 +17,7 @@ #include #include "altestmatchers.h" +#include "../alcolorwheelmodel.h" #include "../alscopedata.h" #include @@ -120,8 +121,18 @@ namespace CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 64), ll_test::WithinBits(1.f, 5)); CHECK_THAT(mScope.getBin(ALScopeData::CH_GREEN, 128), ll_test::WithinBits(1.f, 5)); CHECK_THAT(mScope.getBin(ALScopeData::CH_BLUE, 192), ll_test::WithinBits(1.f, 5)); - UNSCOPED_INFO("nothing anywhere else"); - CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 65), ll_test::WithinBits(0.f, 6)); + + // Nothing anywhere else: one bin lit in each channel, luma's too. + for (S32 c = 0; c < ALScopeData::CH_COUNT; ++c) + { + S32 lit = 0; + for (S32 i = 0; i < ALScopeData::BIN_COUNT; ++i) + { + lit += mScope.getBin((ALScopeData::EChannel)c, i) > 0.f ? 1 : 0; + } + CAPTURE(c); + CHECK(lit == 1); + } } // Bins are shares of the sample, so they sum to one whatever the size. @@ -447,7 +458,7 @@ namespace // Cell centres run -1 to 1 in units of MAX_CHROMA, and the binning is the // inverse of that mapping -- so a plot laid out from chromaCellCentre puts // the trace where accumulate() put it, not half a cell away. - TEST_CASE_METHOD(scope_data, "ALScopeData: chroma cell centres rise from -1 to 1 and straddle zero", "[ALScopeData]") + TEST_CASE_METHOD(scope_data, "ALScopeData: chroma cell centres rise from -1 to 1 and the binning is their inverse", "[ALScopeData]") { const S32 last = ALScopeData::CHROMA_SIZE - 1; UNSCOPED_INFO("first cell is at the low edge"); @@ -463,6 +474,25 @@ namespace CAPTURE(i); REQUIRE(ALScopeData::chromaCellCentre(i) > ALScopeData::chromaCellCentre(i - 1)); } + + // The inverse: a colour lands in the cell whose centre is within half + // a cell of its chroma. None of these sits on a cell's edge. + struct Colour + { + S32 r, g, b; + }; + const F32 half_cell = 1.f / (F32)ALScopeData::CHROMA_SIZE; + for (const Colour& c : { Colour{ 210, 40, 70 }, Colour{ 30, 160, 90 }, Colour{ 60, 80, 220 }, Colour{ 180, 150, 40 } }) + { + mScope.accumulate(flat((U8)c.r, (U8)c.g, (U8)c.b, 4).data(), 4, 1); + S32 u = -1, v = -1; + soleCell(mScope, u, v); + F32 cu = 0.f, cv = 0.f; + ALColorWheelModel::toChroma(LLVector3((F32)c.r / 255.f, (F32)c.g / 255.f, (F32)c.b / 255.f), cu, cv); + CAPTURE(c.r, c.g, c.b, u, v); + CHECK_THAT(cu / ALColorWheelModel::MAX_CHROMA, ll_test::WithinDistance(ALScopeData::chromaCellCentre(u), half_cell)); + CHECK_THAT(cv / ALColorWheelModel::MAX_CHROMA, ll_test::WithinDistance(ALScopeData::chromaCellCentre(v), half_cell)); + } } // The whole reason the waveform exists: a histogram cannot tell a blown sky diff --git a/indra/newview/tests/alselectionoutline_gl_test.cpp b/indra/newview/tests/alselectionoutline_gl_test.cpp index 295e8f8388..d7e8109dd1 100644 --- a/indra/newview/tests/alselectionoutline_gl_test.cpp +++ b/indra/newview/tests/alselectionoutline_gl_test.cpp @@ -1333,7 +1333,7 @@ namespace if (idAt(ids, x, y) == ID_A) { ++covered; - hidden += at(ids, x, y).b == 0 ? 1 : 0; + hidden += at(ids, x, y).b != 255 ? 1 : 0; } } } @@ -2205,7 +2205,6 @@ namespace // read as the sum of its coverage down a column, the line's width over the cosine of its slope. TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the wireframe's lines are as wide in the window wherever they are", "[ALSelectionOutline]") { - // The rows a column's runs of drawn pixels cover, bottom up. // The width down column x of the quad's diagonal, the middle of the three lines a column crosses. auto diagonal = [&](const std::vector& px, S32 x) { @@ -2237,10 +2236,12 @@ namespace REQUIRE(fabsf(far_end - near_end) < 0.1f); const std::vector far_px = worldWireframe({ { quad(-4.f, 6.f, -4.f, 4.f, -12.f, -48.f), ID_A } }); + const F32 farther_near_end = diagonal(far_px, 26); + const F32 farther_far_end = diagonal(far_px, 34); { INFO("the same four times as far"); - REQUIRE(fabsf(diagonal(far_px, 26) - near_end) < 0.1f); - REQUIRE(fabsf(diagonal(far_px, 34) - far_end) < 0.1f); + REQUIRE(fabsf(farther_near_end - near_end) < 0.1f); + REQUIRE(fabsf(farther_far_end - far_end) < 0.1f); } const std::vector wide_px = worldWireframe({ { quad(-1.f, 1.5f, -1.f, 1.f, -3.f, -12.f), ID_A } }, 2); @@ -2254,7 +2255,8 @@ namespace } } - // Across a line, coverage falls from its middle on either side, and is gone within a pixel and a half. + // Across a line, coverage falls from its middle on either side, and along a row is gone within a pixel and a + // half. TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a line's coverage falls from its middle to nothing on either side", "[ALSelectionOutline]") { for (bool reverse : conventions()) @@ -2272,6 +2274,7 @@ namespace REQUIRE(at(px, y, y).r > 127); for (S32 step : { -1, 1 }) { + CAPTURE(step); U32 last = at(px, y, y).r; for (S32 x = y + step; x != y + 4 * step; x += step) { @@ -2280,8 +2283,8 @@ namespace REQUIRE(r <= last); last = r; } - UNSCOPED_INFO("and is gone"); - REQUIRE(last == 0U); + UNSCOPED_INFO("and is gone two pixels along the row"); + REQUIRE(at(px, y + 2 * step, y).r == 0U); } } } diff --git a/indra/newview/tests/alterrainsurfacemaps_test.cpp b/indra/newview/tests/alterrainsurfacemaps_test.cpp index 52e0cb6b1a..553e40512c 100644 --- a/indra/newview/tests/alterrainsurfacemaps_test.cpp +++ b/indra/newview/tests/alterrainsurfacemaps_test.cpp @@ -31,6 +31,7 @@ #include "../alterrainsurfacemaps.h" +#include #include #include @@ -60,8 +61,25 @@ namespace TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: fill() lays the grid out APRON in with rows along y", "[ALTerrainSurfaceMaps]") { std::vector out; - ALTerrainSurfaceMaps::fill(out, GPE, 1, [](S32 gx, S32 gy, F32* t) { t[0] = (F32)(gx * 100 + gy); }); + // How many times each coordinate on the map is visited, and how many + // visits are off it. + std::vector visits(static_cast(SIZE) * SIZE, 0); + S32 off_map = 0; + ALTerrainSurfaceMaps::fill(out, GPE, 1, [&visits, &off_map](S32 gx, S32 gy, F32* t) { + t[0] = (F32)(gx * 100 + gy); + if (gx < -APRON || gx >= GPE + APRON || gy < -APRON || gy >= GPE + APRON) + { + ++off_map; + return; + } + ++visits[texel(gx, gy)]; + }); REQUIRE(out.size() == static_cast(SIZE) * SIZE); + { + INFO("every coordinate visited once, and none off the map"); + CHECK(off_map == 0); + CHECK(std::count_if(visits.begin(), visits.end(), [](S32 n) { return n != 1; }) == 0); + } UNSCOPED_INFO("first texel is the south-west apron corner"); CHECK(out[texel(-APRON, -APRON)] == (F32)(-APRON * 100 - APRON)); UNSCOPED_INFO("grid origin sits APRON in"); diff --git a/indra/newview/tests/altexturetable_test.cpp b/indra/newview/tests/altexturetable_test.cpp index 88194be953..ead002d5e3 100644 --- a/indra/newview/tests/altexturetable_test.cpp +++ b/indra/newview/tests/altexturetable_test.cpp @@ -408,15 +408,30 @@ namespace REQUIRE(((i % 3 == 0) ? found == nullptr : found == items[i - 1])); } + // The same inserts and erases with a reservation and without: the + // same items, in the same places. Table reserved; reserved.reserve(count); + Table unreserved; for (U32 i = 1; i <= 100; ++i) { CAPTURE(i); REQUIRE(reserved.insert(key(i), new Item(i))); + REQUIRE(unreserved.insert(key(i), new Item(i))); } CHECK(reserved.size() == (size_t)100); + for (U32 i = 3; i <= 100; i += 3) + { + reserved.erase(key(i)); + unreserved.erase(key(i)); + } CHECK(reserved.consistent()); + REQUIRE(reserved.size() == unreserved.size()); + for (size_t i = 0; i < reserved.size(); ++i) + { + CAPTURE(i); + REQUIRE(reserved[i]->mId == unreserved[i]->mId); + } } // --- find-or-insert ------------------------------------------------------ diff --git a/indra/newview/tests/alwhitebalancesolver_test.cpp b/indra/newview/tests/alwhitebalancesolver_test.cpp index 7422ad4d66..67c68ba437 100644 --- a/indra/newview/tests/alwhitebalancesolver_test.cpp +++ b/indra/newview/tests/alwhitebalancesolver_test.cpp @@ -42,9 +42,9 @@ namespace TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: neutral in gives a gain of one", "[ALWhiteBalanceSolver]") { const LLVector3 g = ALWhiteBalanceSolver::gain(0.f, 0.f); - CHECK_THAT(g.mV[VX], ll_test::WithinBits(1.f, 6)); - CHECK_THAT(g.mV[VY], ll_test::WithinBits(1.f, 6)); - CHECK_THAT(g.mV[VZ], ll_test::WithinBits(1.f, 6)); + CHECK(g.mV[VX] == 1.f); + CHECK(g.mV[VY] == 1.f); + CHECK(g.mV[VZ] == 1.f); } // Green is pinned across the whole range, which is what makes temperature diff --git a/indra/newview/tests/cppfeatures_test.cpp b/indra/newview/tests/cppfeatures_test.cpp index a256a6dfb7..f6559d6cfe 100644 --- a/indra/newview/tests/cppfeatures_test.cpp +++ b/indra/newview/tests/cppfeatures_test.cpp @@ -31,6 +31,8 @@ #include "linden_common.h" #include +#include + namespace { @@ -130,7 +132,8 @@ TEST_CASE("LLCPPFeatures: range for walks a container or a C array", "[LLCPPFeat class Foo { public: - virtual bool is_happy() const = 0; + // Not happy itself, so that a happy answer through a Foo is Bar's. + virtual bool is_happy() const { return false; } }; class Bar: public Foo @@ -146,8 +149,10 @@ class Bar: public Foo TEST_CASE("LLCPPFeatures: an override specifier marks a virtual override", "[LLCPPFeatures]") { + // Asked through its base, which only an override answers. Bar b; - CHECK(b.is_happy()); + const Foo& as_foo = b; + CHECK(as_foo.is_happy()); } // final @@ -160,7 +165,9 @@ TEST_CASE("LLCPPFeatures: an override specifier marks a virtual override", "[LLC class Vehicle { public: - virtual bool has_wheels() const = 0; + // No wheels itself, so that wheels through a Vehicle are the final + // override's. + virtual bool has_wheels() const { return false; } }; class WheeledVehicle: public Vehicle @@ -179,8 +186,11 @@ class Bicycle: public WheeledVehicle TEST_CASE("LLCPPFeatures: a class derives from one with a final override", "[LLCPPFeatures]") { + // Asked through the root, a Bicycle answers with the final override it + // inherits. Bicycle bi; - CHECK(bi.has_wheels()); + const Vehicle& as_vehicle = bi; + CHECK(as_vehicle.has_wheels()); } // deleted function declaration @@ -206,11 +216,14 @@ class DoNotCopy }; -TEST_CASE("LLCPPFeatures: a deleted copy constructor leaves default construction", "[LLCPPFeatures]") +// Default construction is checked by compiling; the copy is asked of the +// type, since a copy written out would not compile. +TEST_CASE("LLCPPFeatures: a deleted copy constructor forbids copies but not default construction", "[LLCPPFeatures]") { DoNotCopy nc; // OK, default constructor //DoNotCopy nc2(nc); // No, can't copy //DoNotCopy nc3 = nc; // No, this also calls copy constructor (even though it looks like an assignment) + CHECK_FALSE(std::is_copy_constructible_v); } // defaulted function declaration @@ -321,15 +334,15 @@ TEST_CASE("LLCPPFeatures: constexpr functions compute values", "[LLCPPFeatures]" // // You can add asserts to be checked at compile time. The thing to be checked must be a constexpr. // There are two forms: -// * static_assert(expr); +// * static_assert(expr); (since C++17) // * static_assert(expr, message); // -// Currently only the 2-parameter form works on windows. The 1-parameter form needs a flag we don't set. +// The case's checks are the compiler's: it passes by compiling, and then by returning. TEST_CASE("LLCPPFeatures: static_assert checks a constexpr at compile time", "[LLCPPFeatures]") { - // static_assert(ce_factorial(6)==720); No, needs a flag we don't currently set. - static_assert(ce_factorial(6)==720, "bad factorial"); // OK + static_assert(ce_factorial(6)==720); + static_assert(ce_factorial(6)==720, "bad factorial"); } // type aliases diff --git a/indra/newview/tests/llvocache_test.cpp b/indra/newview/tests/llvocache_test.cpp index d1c4fb8856..2f286df188 100644 --- a/indra/newview/tests/llvocache_test.cpp +++ b/indra/newview/tests/llvocache_test.cpp @@ -509,6 +509,8 @@ namespace UNSCOPED_INFO("version line"); CHECK(bytes.substr(0, line_end) == LLGLTFOverrideCacheEntry::VERSION_LABEL + ":" + std::to_string(LLGLTFOverrideCacheEntry::VERSION)); size_t cursor = line_end + 1 + UUID_BYTES; + UNSCOPED_INFO("the cache id, the count and the first record's length inside the file"); + REQUIRE(cursor + 2 * sizeof(U32) <= bytes.size()); U32 count = 0; memcpy(&count, bytes.data() + cursor, sizeof(U32)); cursor += sizeof(U32); @@ -597,19 +599,15 @@ namespace UNSCOPED_INFO("past the cap is still a factor"); CHECK(factor(2.f * cap, 65536.f, cap, 750.f, 2048.f) == 0.f); - // settings the wrong way round + // settings the wrong way round: the low one is the bound, and the high + // one is taken as just past it for (F32 allocated : { 0.f, 2000.f, 2500.f, 3000.f, 4000.f }) { const F32 f = factor(allocated, 0.f, no_cap, 3000.f, 2048.f); - CAPTURE(allocated); - { - INFO("inverted settings still give a factor in [0, 1]"); - REQUIRE(f >= 0.f); - REQUIRE(f <= 1.f); - } + CAPTURE(allocated, f); + INFO("inverted settings: up to the low setting nothing is pulled in, past it everything is"); + CHECK(close_to(f, allocated <= 3000.f ? 1.f : 0.f)); } - UNSCOPED_INFO("inverted settings: below the low setting nothing is pulled in"); - CHECK(close_to(factor(2000.f, 0.f, no_cap, 3000.f, 2048.f), 1.f)); // a bound set by hand is the bound, tighter than RAM would make it too auto explicit_factor = [](F32 allocated, F32 physical, F32 cap, F32 low, F32 high) @@ -622,8 +620,10 @@ namespace CHECK(close_to(explicit_factor(2000.f, 65536.f, no_cap, 1000.f, 2000.f), 0.f)); UNSCOPED_INFO("explicit bounds on a big machine: halfway is half"); CHECK(close_to(explicit_factor(1500.f, 65536.f, no_cap, 1000.f, 2000.f), 0.5f)); - UNSCOPED_INFO("an explicit high with a default low still comes out ordered"); - CHECK(LLVOCacheEntry::memoryAdjustFactor(16000.f, 65536.f, no_cap, 750.f, 2048.f, false, true) >= 0.f); + // The default low follows RAM to 16384, past the explicit high of + // 2048, so the high is taken as just past the low. + UNSCOPED_INFO("an explicit high with a default low still comes out ordered: below the low one nothing is pulled in"); + CHECK(close_to(LLVOCacheEntry::memoryAdjustFactor(16000.f, 65536.f, no_cap, 750.f, 2048.f, false, true), 1.f)); } TEST_CASE_METHOD(vocacheTest, "LLVOCache: hostile extras records meet a depth cap and a budget and lose bad faces", "[LLVOCache]") diff --git a/indra/newview/tests/lutcube_test.cpp b/indra/newview/tests/lutcube_test.cpp index ee8d42590a..09386e7441 100644 --- a/indra/newview/tests/lutcube_test.cpp +++ b/indra/newview/tests/lutcube_test.cpp @@ -157,6 +157,7 @@ namespace } const LutCube cube = parse(text.str()); + REQUIRE(cube.size == 2); UNSCOPED_INFO("above domain clamps to white"); CHECK(at(cube, 0, 0) == FULL); UNSCOPED_INFO("below domain clamps to black"); From fbb85fe864efe6e1a3155034ae22a86ba631b581 Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 04:47:57 -0400 Subject: [PATCH 5/9] The Linden-era viewer tests check what they say they check Old test bugs TM24's idiom pass found (83b0c1dd65), fixed. - llagentaccess: "pref adult, actual pg" set the maturity after the preference, which pulled the preference back to PG, so the checks passed on PG and the low-rating branch never ran. The maturity is set first, and the preference is checked. - llremoteparcelrequest: the case with a deleted observer checked nothing; a live observer after it is checked to hear the reply. - llsechandler_basic: "delete credential: authenticator" checked the identifier again; it checks the authenticator. Two grid checks read a credential nothing changed; they read the one loaded and deleted. A valid-from date set and overwritten unused is validated at, exactly. TRUSTED | TRUSTED is TRUSTED, and a comment saying the flag was not passed says it was. - llslurl: the same type check ran twice where "region + coords" was said; the second checks the coordinates. - llworldmipmap: the empty case checks that a tile never loaded is none. - llworldmap: a check of the mipmap stub it forwards to is gone; the case says it checks that the calls do not throw. - llviewerhelputil: checks of the file's own stub of expandURLSubstitutions are gone; one reaches buildHelpURL's encoding. - llviewercontrollistener: the empty "no listener" case is gone. The settings group's listener is a global, so there is no way to have none without one that swallows events. - lllogininstance: the responder helpers and constants nothing used are gone; production never asks the notifications interface. - llhttpretrypolicy and llviewernetwork: comments that said otherwise than the checks say what they check, and a print naming an old TUT test is gone. Each new or corrected check fails under a small change to the test's own input or expectation, at the check and not by crashing. Co-Authored-By: Claude Opus 5.5 --- indra/newview/tests/llagentaccess_test.cpp | 7 ++- .../newview/tests/llhttpretrypolicy_test.cpp | 5 +- indra/newview/tests/lllogininstance_test.cpp | 43 ---------------- .../tests/llremoteparcelrequest_test.cpp | 10 +++- .../newview/tests/llsechandler_basic_test.cpp | 13 +++-- indra/newview/tests/llslurl_test.cpp | 4 +- .../tests/llviewercontrollistener_test.cpp | 7 +-- indra/newview/tests/llviewerhelputil_test.cpp | 49 ++++--------------- indra/newview/tests/llviewernetwork_test.cpp | 2 +- indra/newview/tests/llworldmap_test.cpp | 8 +-- indra/newview/tests/llworldmipmap_test.cpp | 9 ++-- 11 files changed, 47 insertions(+), 110 deletions(-) diff --git a/indra/newview/tests/llagentaccess_test.cpp b/indra/newview/tests/llagentaccess_test.cpp index 4fd2c2ca93..0defc92ccc 100644 --- a/indra/newview/tests/llagentaccess_test.cpp +++ b/indra/newview/tests/llagentaccess_test.cpp @@ -244,12 +244,15 @@ namespace #endif // HACKED_GODLIKE_VIEWER // make sure that even if pref is high, if access is low we block access - // this shouldn't occur in real life but we want to be safe - cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); + // this shouldn't occur in real life but we want to be safe. The + // preference goes after the rating, which setMaturity would pull it + // down to. aa.setMaturity('P'); + cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); #ifndef HACKED_GODLIKE_VIEWER { INFO("pref adult, actual pg"); + CHECK(aa.prefersAdult()); CHECK(aa.wantsPGOnly()); CHECK_FALSE(aa.canAccessMature()); CHECK_FALSE(aa.canAccessAdult()); diff --git a/indra/newview/tests/llhttpretrypolicy_test.cpp b/indra/newview/tests/llhttpretrypolicy_test.cpp index 2ed768cb56..a50025f547 100644 --- a/indra/newview/tests/llhttpretrypolicy_test.cpp +++ b/indra/newview/tests/llhttpretrypolicy_test.cpp @@ -243,8 +243,6 @@ TEST_CASE("retry_policy: getSecondsUntilRetryAfter parses seconds and an HTTP da TEST_CASE("retry_policy: retry-after is honoured in both llmessage and CoreHttp headers", "[retry_policy]") { - std::cerr << "7 starts" << std::endl; - LLSD sd_headers; time_t nowseconds; time(&nowseconds); @@ -261,7 +259,8 @@ TEST_CASE("retry_policy: retry-after is honoured in both llmessage and CoreHttp CHECK(should_retry); CHECK_THAT(seconds_to_wait, ll_test::WithinBits(17.0F, 6)); - // retry header should override, give delay of 0 + // A retry-after date 7 seconds off overrides the default: a delay of + // about 7 seconds. std::string date_string = LLDate((F64)(nowseconds+7)).asRFC1123(); sd_headers[HTTP_IN_HEADER_RETRY_AFTER] = date_string; policy.onFailure(503,sd_headers); diff --git a/indra/newview/tests/lllogininstance_test.cpp b/indra/newview/tests/lllogininstance_test.cpp index 4c02155661..2f3671686a 100644 --- a/indra/newview/tests/lllogininstance_test.cpp +++ b/indra/newview/tests/lllogininstance_test.cpp @@ -50,12 +50,6 @@ // Constants const std::string VIEWERLOGIN_URI("viewerlogin_uri"); -const std::string VIEWERLOGIN_GRIDLABEL("viewerlogin_grid"); - -const std::string APPVIEWER_SERIALNUMBER("appviewer_serialno"); - -const std::string VIEWERLOGIN_CHANNEL("invalid_channel"); -const std::string VIEWERLOGIN_VERSION("invalid_version"); // Link seams. @@ -259,16 +253,7 @@ namespace { class MockNotifications : public LLNotificationsInterface { - std::function mResponder; - int mAddedCount; - public: - MockNotifications() : - mResponder(0), - mAddedCount(0) - { - } - virtual ~MockNotifications() {} /* virtual */ LLNotificationPtr add( @@ -277,36 +262,8 @@ class MockNotifications : public LLNotificationsInterface const LLSD& payload, LLNotificationFunctorRegistry::ResponseFunctor functor) { - mResponder = functor; - mAddedCount++; return LLNotificationPtr((LLNotification*)NULL); } - - void sendYesResponse() - { - LLSD notification; - LLSD response; - response = 1; - mResponder(notification, response); - } - - void sendNoResponse() - { - LLSD notification; - LLSD response; - response = 2; - mResponder(notification, response); - } - - void sendBogusResponse() - { - LLSD notification; - LLSD response; - response = 666; - mResponder(notification, response); - } - - int addedCount() { return mAddedCount; } }; } // anonymous namespace diff --git a/indra/newview/tests/llremoteparcelrequest_test.cpp b/indra/newview/tests/llremoteparcelrequest_test.cpp index 76bdad4bca..c438dc6458 100644 --- a/indra/newview/tests/llremoteparcelrequest_test.cpp +++ b/indra/newview/tests/llremoteparcelrequest_test.cpp @@ -88,17 +88,23 @@ namespace CHECK(observer->mProcessed); } - // CHOP-220: a dangling observer pointer. - TEST_CASE_METHOD(RemoteParcelRequestData, "LLRemoteParcelRequest: a reply after its observer is deleted does not crash", "[LLRemoteParcelRequest]") + // CHOP-220: a dangling observer pointer. The deleted observer comes + // before a live one for the same parcel: the reply passes it over, does + // not crash on it, and still reaches the live one. + TEST_CASE_METHOD(RemoteParcelRequestData, "LLRemoteParcelRequest: a reply after an observer is deleted passes it over and reaches the rest", "[LLRemoteParcelRequest]") { LLRemoteParcelInfoObserver * observer = new TestObserver(); + TestObserver live; LLRemoteParcelInfoProcessor & processor = LLRemoteParcelInfoProcessor::instance(); processor.addObserver(LLUUID(TEST_PARCEL_ID), observer); + processor.addObserver(LLUUID(TEST_PARCEL_ID), &live); delete observer; observer = NULL; processor.processParcelData(testParcelData()); + CHECK(live.mProcessed); + processor.removeObserver(LLUUID(TEST_PARCEL_ID), &live); } } diff --git a/indra/newview/tests/llsechandler_basic_test.cpp b/indra/newview/tests/llsechandler_basic_test.cpp index 34bba3d616..090cbc080b 100644 --- a/indra/newview/tests/llsechandler_basic_test.cpp +++ b/indra/newview/tests/llsechandler_basic_test.cpp @@ -946,7 +946,7 @@ namespace UNSCOPED_INFO("load a known credential: authenticator"); CHECK_THAT(my_authenticator, ll_test::LLSDEquals(my_new_cred->getAuthenticator())); UNSCOPED_INFO("load a known credential: grid"); - CHECK(my_cred->getGrid() == "my_grid"); + CHECK(my_new_cred->getGrid() == "my_grid"); // test deletion of a credential handler->deleteCredential(my_new_cred); @@ -954,9 +954,9 @@ namespace UNSCOPED_INFO("delete credential: identifier"); REQUIRE(my_new_cred->getIdentifier().isUndefined()); UNSCOPED_INFO("delete credential: authenticator"); - REQUIRE(my_new_cred->getIdentifier().isUndefined()); + REQUIRE(my_new_cred->getAuthenticator().isUndefined()); UNSCOPED_INFO("delete credential: grid"); - CHECK(my_cred->getGrid() == "my_grid"); + CHECK(my_new_cred->getGrid() == "my_grid"); // load unknown cred my_new_cred = handler->loadCredential("my_grid"); @@ -1404,7 +1404,7 @@ namespace // trusted test_chain->add(new LLBasicCertificate(mX509ChildCert, &mValidationDate)); - // validate without the trust flag. + // validate with the trust flag: the store's cache of validated certs vouches for the child. test_store->validate(VALIDATION_POLICY_TRUSTED, test_chain, validation_params); // Validate with child cert but no parent, and no parent in CA store @@ -1429,7 +1429,7 @@ namespace LLCertValidationTrustException, (*test_chain)[1], test_store->validate, - VALIDATION_POLICY_TRUSTED | VALIDATION_POLICY_TRUSTED, + VALIDATION_POLICY_TRUSTED, test_chain, validation_params); // validate without the trust flag @@ -1443,8 +1443,11 @@ namespace test_store->validate(VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, test_chain, validation_params); + // valid from its first second validation_params = LLSD::emptyMap(); validation_params[CERT_VALIDATION_DATE] = child_info[CERT_VALID_FROM].asDate(); + CHECK_NOTHROW(test_store->validate(VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, + test_chain, validation_params)); validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_FROM].asDate().secondsSinceEpoch() - 1.0); diff --git a/indra/newview/tests/llslurl_test.cpp b/indra/newview/tests/llslurl_test.cpp index e53340a1fc..30cbbd92cd 100644 --- a/indra/newview/tests/llslurl_test.cpp +++ b/indra/newview/tests/llslurl_test.cpp @@ -340,8 +340,8 @@ namespace { INFO("secondlife:///secondlife/ slurl naming no grid"); CHECK(slurl.getType() == LLSLURL::LOCATION); - CHECK(slurl.getType() == LLSLURL::LOCATION); CHECK("myregion" == slurl.getRegion()); + CHECK(slurl.getPosition() == LLVector3(1, 2, 3)); CHECK(slurl.getGrid() == "util.agni.lindenlab.com"); } @@ -349,8 +349,8 @@ namespace { INFO("secondlife://Aditi/secondlife/ slurl"); CHECK(slurl.getType() == LLSLURL::LOCATION); - CHECK(slurl.getType() == LLSLURL::LOCATION); CHECK("myregion" == slurl.getRegion()); + CHECK(slurl.getPosition() == LLVector3(1, 2, 3)); CHECK(slurl.getGrid() == "Aditi"); } diff --git a/indra/newview/tests/llviewercontrollistener_test.cpp b/indra/newview/tests/llviewercontrollistener_test.cpp index 43af275f4a..c698bc2332 100644 --- a/indra/newview/tests/llviewercontrollistener_test.cpp +++ b/indra/newview/tests/llviewercontrollistener_test.cpp @@ -48,11 +48,8 @@ namespace } }; - TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl with no listener is left untested", "[llviewercontrollistener]") - { - // Not implemented: the linker drags in LLViewerControlListener when - // we bring in LLViewerControl. - } + // CommonControl with no listener is not tested: the linker drags in + // LLViewerControlListener when we bring in LLViewerControl. TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::get of an unknown group names the group", "[llviewercontrollistener]") { diff --git a/indra/newview/tests/llviewerhelputil_test.cpp b/indra/newview/tests/llviewerhelputil_test.cpp index 5ab4347a6e..42c922af4b 100644 --- a/indra/newview/tests/llviewerhelputil_test.cpp +++ b/indra/newview/tests/llviewerhelputil_test.cpp @@ -98,7 +98,12 @@ std::string LLWeb::expandURLSubstitutions(const std::string &url, namespace { - TEST_CASE("LLViewerHelpUtil: buildHelpURL substitutes the topic and the viewer tags into the format", "[LLViewerHelpUtil]") + // What buildHelpURL does itself: the format read from HelpURLFormat, and + // the topic made safe for a URL and handed to the expansion as [TOPIC]. + // The viewer's own tags ([LANGUAGE], [VERSION], [OS] and the rest) are + // LLWeb::expandURLSubstitutions's, which this test stubs, so they are not + // tested here. + TEST_CASE("LLViewerHelpUtil: buildHelpURL expands the format from the settings with the topic encoded", "[LLViewerHelpUtil]") { std::string topic("test_topic"); std::string subresult; @@ -118,43 +123,9 @@ namespace UNSCOPED_INFO("topic name"); CHECK(subresult == "test_topic"); - gHelpURL = "[LANGUAGE]"; - gLanguage = ""; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - UNSCOPED_INFO("simple substitution with blank"); - CHECK(subresult == ""); - - gHelpURL = "[LANGUAGE]"; - gLanguage = "Esperanto"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - UNSCOPED_INFO("simple substitution"); - CHECK(subresult == "Esperanto"); - - gHelpURL = "http://secondlife.com/[LANGUAGE]"; - gLanguage = "Gaelic"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - UNSCOPED_INFO("simple substitution with url"); - CHECK(subresult == "http://secondlife.com/Gaelic"); - - gHelpURL = "[XXX]"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - UNSCOPED_INFO("unknown substitution"); - CHECK(subresult == "[XXX]"); - - gHelpURL = "[LANGUAGE]/[LANGUAGE]"; - gLanguage = "Esperanto"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - UNSCOPED_INFO("multiple substitution"); - CHECK(subresult == "Esperanto/Esperanto"); - - gHelpURL = "http://[CHANNEL]/[VERSION]/[LANGUAGE]/[OS]/[GRID]/[XXX]"; - gChannel = "Second Life Test"; - gVersion = "2.0"; - gLanguage = "gaelic"; - gOS = "AmigaOS 2.1"; - gGrid = "mysim"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - UNSCOPED_INFO("complex substitution"); - CHECK(subresult == "http://Second Life Test/2.0/gaelic/AmigaOS 2.1/mysim/[XXX]"); + gHelpURL = "http://help/[TOPIC]"; + subresult = LLViewerHelpUtil::buildHelpURL("two words/a~b"); + UNSCOPED_INFO("all but letters, digits, '-', '.' and '_' escaped, the tilde too"); + CHECK(subresult == "http://help/two%20words%2Fa%7Eb"); } } diff --git a/indra/newview/tests/llviewernetwork_test.cpp b/indra/newview/tests/llviewernetwork_test.cpp index db2a97725f..bcf8558eb4 100644 --- a/indra/newview/tests/llviewernetwork_test.cpp +++ b/indra/newview/tests/llviewernetwork_test.cpp @@ -348,7 +348,7 @@ namespace // validate grid selection TEST_CASE_METHOD(viewerNetworkTest, "LLViewerNetwork: setGridChoice selects the grid the getters answer for", "[LLViewerNetwork]") { - // adding a grid with simply a name will populate the values. + // The sample grid file, for a grid besides Agni and Aditi to choose. llofstream gridfile(TEST_FILENAME); gridfile << gSampleGridFile; gridfile.close(); diff --git a/indra/newview/tests/llworldmap_test.cpp b/indra/newview/tests/llworldmap_test.cpp index 76a04d4e8f..eb959ae8ef 100644 --- a/indra/newview/tests/llworldmap_test.cpp +++ b/indra/newview/tests/llworldmap_test.cpp @@ -349,7 +349,7 @@ namespace // Test the LLWorldMap interface // --------------------------------------------------------------------------------------- // Test Setters and Accessors methods - TEST_CASE_METHOD(worldmap_test, "LLWorldMap: the image and region calls do not throw and no tile is loaded", "[LLWorldMap]") + TEST_CASE_METHOD(worldmap_test, "LLWorldMap: the image and region calls do not throw", "[LLWorldMap]") { // Test 1 : reset() try { @@ -387,10 +387,10 @@ namespace } catch (...) { FAIL("LLWorldMap::equalizeBoostLevels() threw"); } - // Test 7 : getObjectsTile() + // Test 7 : getObjectsTile(), which the mipmap answers: a stub here, + // whose tile says nothing of the map's. try { - LLPointer image = mWorld->getObjectsTile((U32)(X_WORLD_TEST/REGION_WIDTH_METERS), (U32)(Y_WORLD_TEST/REGION_WIDTH_METERS), 1); - CHECK(image.isNull()); + mWorld->getObjectsTile((U32)(X_WORLD_TEST/REGION_WIDTH_METERS), (U32)(Y_WORLD_TEST/REGION_WIDTH_METERS), 1); } catch (...) { FAIL("LLWorldMap::getObjectsTile() threw"); } diff --git a/indra/newview/tests/llworldmipmap_test.cpp b/indra/newview/tests/llworldmipmap_test.cpp index e3cc6f2237..f8d6ec4f5d 100644 --- a/indra/newview/tests/llworldmipmap_test.cpp +++ b/indra/newview/tests/llworldmipmap_test.cpp @@ -108,11 +108,12 @@ namespace CHECK(grid_x == 0); CHECK(grid_y == 0); } - TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: getObjectsTile (not yet tested)", "[LLWorldMipmap]") + TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: getObjectsTile without loading gives nothing for a tile not loaded", "[LLWorldMipmap]") { - // Depends on some inline methods in LLViewerImage... Thinking about how to make this work - // LLPointer img = mMap->getObjectsTile(0, 0, 1); - // ensure("getObjectsTile() test failed", img.isNull()); + // Loading is not tested: it boosts the texture the texture manager + // fetches, and this binary's stub fetches none. + LLPointer img = mMap->getObjectsTile(0, 0, 1, false); + CHECK(img.isNull()); } TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: equalizeBoostLevels does not throw", "[LLWorldMipmap]") { From 06a4daf0cf144a1948dfccc87ac5884541d6f65c Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 04:52:50 -0400 Subject: [PATCH 6/9] A running LLTrace recording restarted starts from nothing The newest recording running on a thread gathers in a partial buffer of its own, which update() leaves where it is and every read adds in. restart() reset the recording's own buffers but not that one, so whatever had come in since the recording last stood behind another was still counted after it. LLTextureInfo restarts its recording, through resetTextureStatistics(), each time it sends a bundle of texture statistics to the simulator, so each bundle counted what the one before it had sent. handleReset() brings a running recording up to date first, through the thread recorder's deactivate and activate, which hands what it holds down to the recordings beneath it before the reset clears it from this one. - lltrace_test: a new case restarts the newest recording and checks that it starts from nothing while the one beneath keeps all. It fails without the fix. The fixture starts each case's caffeine level at nothing, which the first case's expectations assume and the new case would otherwise raise in a random order. - lltextureinfo_test: getAverages was skipped, and the reset case never started the recording or logged to the simulator, so there was nothing to clear and it could not fail. Both start the recording and log, with the threshold set past what they download and both settings put back by RAII, and check rates worked out by hand. The reset case checks there was something to clear. The first case's CHECK(true) is gone. Co-Authored-By: Claude Opus 5.5 --- indra/llcommon/lltracerecording.cpp | 11 +++ indra/llcommon/tests/lltrace_test.cpp | 33 ++++++++- indra/newview/tests/lltextureinfo_test.cpp | 84 ++++++++++++++++------ 3 files changed, 107 insertions(+), 21 deletions(-) diff --git a/indra/llcommon/lltracerecording.cpp b/indra/llcommon/lltracerecording.cpp index f010f0e961..4d13c459ec 100644 --- a/indra/llcommon/lltracerecording.cpp +++ b/indra/llcommon/lltracerecording.cpp @@ -117,6 +117,17 @@ void Recording::handleReset() { LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; #if LL_TRACE_ENABLED + // A running recording that is the newest on its thread gathers in a + // buffer of its own, which update() leaves where it is, and every read + // adds in. Brought up to date first -- what it holds handed down to the + // recordings beneath this one, and into this one's own, which the reset + // then clears -- or a reset while running would keep it. + if (isStarted() && mActiveBuffers && LLTrace::get_thread_recorder()) + { + AccumulatorBufferGroup* buffers = mBuffers.write(); + LLTrace::get_thread_recorder()->deactivate(buffers); + mActiveBuffers = LLTrace::get_thread_recorder()->activate(buffers); + } mBuffers.write()->reset(); mElapsedSeconds = F64Seconds(0.0); diff --git a/indra/llcommon/tests/lltrace_test.cpp b/indra/llcommon/tests/lltrace_test.cpp index 00685d3b1a..3f7a84d3e0 100644 --- a/indra/llcommon/tests/lltrace_test.cpp +++ b/indra/llcommon/tests/lltrace_test.cpp @@ -88,8 +88,14 @@ namespace } }; + // what drink_coffee() has raised the level to + static F32 sCaffeineLevel(0.f); + struct trace { + // Each case drinks from nothing, whichever ran before it. + trace() { sCaffeineLevel = 0.f; } + // before mRecorder, so it is put back after mRecorder is gone ThreadRecorderRestore mRestore; ThreadRecorder mRecorder; @@ -99,7 +105,6 @@ namespace static SampleStatHandle sCaffeineLevelStat("caffeinelevel", "Coffee buzz quotient"); static EventStatHandle sOuncesPerCup("cupsize", "Large, huge, or ginormous"); - static F32 sCaffeineLevel(0.f); const F32Milligrams sCaffeinePerOz(18.f); static void drink_coffee(S32 num_cups, S32Ounces cup_size) @@ -182,4 +187,30 @@ namespace } } + // The newest recording running on a thread gathers in a buffer of its + // own until something brings it up to date. Restarted while it runs, it + // starts from nothing all the same, and what it had gathered still + // reaches the older recording running beneath it. + TEST_CASE_METHOD(trace, "LLTrace: a running recording restarted starts from nothing and the one beneath keeps all", "[LLTrace]") + { + Recording beneath; + Recording newest; + beneath.start(); + newest.start(); + drink_coffee(2, S32GrandeCup(1)); + newest.restart(); + { + INFO("nothing counted once restarted"); + CHECK(newest.getSum(sCupsOfCoffeeConsumed) == 0); + CHECK(newest.getSum(sOuncesPerCup) == S32Ounces(0)); + } + drink_coffee(1, S32TallCup(1)); + UNSCOPED_INFO("only what came after the restart"); + CHECK(newest.getSum(sCupsOfCoffeeConsumed) == 1); + UNSCOPED_INFO("the one beneath kept what came before it too"); + CHECK(beneath.getSum(sCupsOfCoffeeConsumed) == 3); + newest.stop(); + beneath.stop(); + } + } diff --git a/indra/newview/tests/lltextureinfo_test.cpp b/indra/newview/tests/lltextureinfo_test.cpp index 3bbc085b68..805ac9ddbd 100644 --- a/indra/newview/tests/lltextureinfo_test.cpp +++ b/indra/newview/tests/lltextureinfo_test.cpp @@ -37,6 +37,7 @@ #include "../llmeshrepository.h" #include "../lltexturecache.h" #include "../lltexturefetch.h" +#include "../llviewercontrol.h" #include "../llvocache.h" // Tut header @@ -91,6 +92,40 @@ LLVOCache::~LLVOCache() {} namespace { + // Texture downloads logged to the simulator, which is what getAverages() + // reports on, as a person would set it, and put back when the case ends, + // a failed check included. The amount a bundle of statistics is sent at + // is set past what any case here downloads: sending reaches for the + // texture fetcher and cache, which this test has none of. + struct LoggedToSimulator + { + LoggedToSimulator() + { + if (!gSavedSettings.controlExists("LogTextureDownloadsToSimulator")) + { + gSavedSettings.declareBOOL("LogTextureDownloadsToSimulator", false, "Declared In Code", LLControlVariable::PERSIST_NO); + } + if (!gSavedSettings.controlExists("TextureLoggingThreshold")) + { + gSavedSettings.declareU32("TextureLoggingThreshold", 1, "Declared In Code", LLControlVariable::PERSIST_NO); + } + mLoggedWas = gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"); + mThresholdWas = gSavedSettings.getU32("TextureLoggingThreshold"); + gSavedSettings.setU32("TextureLoggingThreshold", 1000000); + gSavedSettings.setBOOL("LogTextureDownloadsToSimulator", true); + } + ~LoggedToSimulator() + { + gSavedSettings.setBOOL("LogTextureDownloadsToSimulator", mLoggedWas); + gSavedSettings.setU32("TextureLoggingThreshold", mThresholdWas); + } + LoggedToSimulator(const LoggedToSimulator&) = delete; + LoggedToSimulator& operator=(const LoggedToSimulator&) = delete; + + bool mLoggedWas = false; + U32 mThresholdWas = 1; + }; + // Test wrapper declarations struct textureinfo_test { @@ -115,11 +150,12 @@ namespace // Test the LLTextureInfo // --------------------------------------------------------------------------------------- + // Made, set to log, and destroyed: there is nothing to ask of it, and the + // case passes by returning. TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: an LLTextureInfo is made with logging on", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); - CHECK(true); } TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: has() is false for a UUID never added", "[LLTectureInfo]") @@ -204,52 +240,54 @@ namespace CHECK(tex_info.getRequestOffset(id) == 1234); } + // What the averages are made of is what was logged to the simulator, in + // the texture info's own recording, as the texture fetcher keeps it. TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: getAverages gives the bit rate and bytes and transport", "[LLTectureInfo]") { - SKIP("Test skipped due to use of LLTrace stats failing in test"); - + LoggedToSimulator logged; LLTextureInfo tex_info; tex_info.setLogging(true); + tex_info.startRecording(); - S32 requestStartTimeOne = 200; - U64Microseconds requestEndTimeOne(400); + // A kilobyte in a second, in microseconds. + U64 requestStartTimeOne = 1000000; + U64Microseconds requestEndTimeOne(2000000); S32 requestSizeOne = 1024; - S32 requestSizeOneBits = requestSizeOne * 8; LLUUID id1("10e65d70-46fd-429f-841a-bf698e9424d3"); tex_info.setRequestStartTime(id1, requestStartTimeOne); tex_info.setRequestSize(id1, requestSizeOne); tex_info.setRequestType(id1, LLTextureInfoDetails::REQUEST_TYPE_HTTP); tex_info.setRequestCompleteTimeAndLog(id1, requestEndTimeOne); - U32 requestStartTimeTwo = 100; - U64Microseconds requestEndTimeTwo(500); + // Two kilobytes in two seconds. + U64 requestStartTimeTwo = 1000000; + U64Microseconds requestEndTimeTwo(3000000); U32 requestSizeTwo = 2048; - S32 requestSizeTwoBits = requestSizeTwo * 8; LLUUID id2("10e65d70-46fd-429f-841a-bf698e9424d4"); tex_info.setRequestStartTime(id2, requestStartTimeTwo); tex_info.setRequestSize(id2, requestSizeTwo); tex_info.setRequestType(id2, LLTextureInfoDetails::REQUEST_TYPE_HTTP); tex_info.setRequestCompleteTimeAndLog(id2, requestEndTimeTwo); - S32 averageBitRate = ((requestSizeOneBits / (S32(requestEndTimeOne.value()) - requestStartTimeOne)) + - (requestSizeTwoBits / (S32(requestEndTimeTwo.value()) - requestStartTimeTwo))) / - 2; - - S32 totalBytes = requestSizeOne + requestSizeTwo; - + // 3072 bytes, 24576 bits, over three seconds of downloading: 8192 + // bits a second, which is each download's rate too, so the rate does + // not hang on how the two are averaged. LLSD results = tex_info.getAverages(); - CHECK(results["bits_per_second"].asInteger() == averageBitRate); - CHECK(results["bytes_downloaded"].asInteger() == totalBytes); + CHECK(results["bits_per_second"].asInteger() == 8192); + CHECK(results["bytes_downloaded"].asInteger() == 3072); CHECK(results["transport"].asString() == std::string("HTTP")); } TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: resetTextureStatistics clears the averages", "[LLTectureInfo]") { + LoggedToSimulator logged; LLTextureInfo tex_info; tex_info.setLogging(true); + tex_info.startRecording(); - S32 requestStartTimeOne = 200; - U64Microseconds requestEndTimeOne(400); + // A kilobyte in a second, in microseconds. + U64 requestStartTimeOne = 1000000; + U64Microseconds requestEndTimeOne(2000000); S32 requestSizeOne = 1024; LLUUID id1("10e65d70-46fd-429f-841a-bf698e9424d3"); tex_info.setRequestStartTime(id1, requestStartTimeOne); @@ -257,7 +295,13 @@ namespace tex_info.setRequestType(id1, LLTextureInfoDetails::REQUEST_TYPE_HTTP); tex_info.setRequestCompleteTimeAndLog(id1, requestEndTimeOne); - tex_info.getAverages(); + LLSD before = tex_info.getAverages(); + { + INFO("something to clear"); + CHECK(before["bits_per_second"].asInteger() == 8192); + CHECK(before["bytes_downloaded"].asInteger() == 1024); + CHECK(before["transport"].asString() == std::string("HTTP")); + } tex_info.resetTextureStatistics(); LLSD results = tex_info.getAverages(); CHECK(results["bits_per_second"].asInteger() == 0); From 1916055ab09bef6eae0a94383dbc8ea912a6c72c Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 04:47:57 -0400 Subject: [PATCH 7/9] Asset metrics report zeros where nothing was answered With no responses in a category, the asset metrics reported the response time's minimum as the largest float, its maximum as the smallest and its mean as NaN, and a category with nothing fetched reported a NaN mean size. They report 0 now. llviewerassetstats_test read sd[0] of the root map, and indexed the regions array by a handle string, so its zero checks read nothing and could not fail; they read the region's slot. The global stats a failed REQUIRE left behind are deleted by the fixture, and the handle strings nothing used are gone. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llviewerassetstats.cpp | 16 +++++++++++----- indra/newview/tests/llviewerassetstats_test.cpp | 15 ++++++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/indra/newview/llviewerassetstats.cpp b/indra/newview/llviewerassetstats.cpp index 011b8afd73..853505c855 100644 --- a/indra/newview/llviewerassetstats.cpp +++ b/indra/newview/llviewerassetstats.cpp @@ -273,13 +273,19 @@ void LLViewerAssetStats::getStat(LLTrace::Recording& rec, T& req, LLViewerAssetS || rec.getSampleCount(sDequeued[cat]) || rec.getSampleCount(sResponse[cat])) { + // With nothing answered, or nothing fetched, there is nothing to take + // the least, the most or the mean of: zeros, rather than what an + // empty minimum, maximum and mean come out as (the largest float, + // the smallest, and NaN). + const S32 responses = rec.getSampleCount(sResponse[cat]); + const S32 fetches = rec.getSampleCount(sBytesFetched[cat]); req .enqueued(rec.getSampleCount(sEnqueued[cat])) .dequeued(rec.getSampleCount(sDequeued[cat])) - .resp_count(rec.getSampleCount(sResponse[cat])) - .resp_min(rec.getMin(sResponse[cat]).value()) - .resp_max(rec.getMax(sResponse[cat]).value()) - .resp_mean(rec.getMean(sResponse[cat]).value()) - .resp_mean_bytes(rec.getMean(sBytesFetched[cat])); + .resp_count(responses) + .resp_min(responses ? rec.getMin(sResponse[cat]).value() : 0.0) + .resp_max(responses ? rec.getMax(sResponse[cat]).value() : 0.0) + .resp_mean(responses ? rec.getMean(sResponse[cat]).value() : 0.0) + .resp_mean_bytes(fetches ? rec.getMean(sBytesFetched[cat]) : 0.0); } } diff --git a/indra/newview/tests/llviewerassetstats_test.cpp b/indra/newview/tests/llviewerassetstats_test.cpp index 2c1354b94a..2543d27d3a 100644 --- a/indra/newview/tests/llviewerassetstats_test.cpp +++ b/indra/newview/tests/llviewerassetstats_test.cpp @@ -130,8 +130,6 @@ static const LLUUID region1("4e2d81a3-6263-6ffe-ad5c-8ce04bee07e8"); static const LLUUID region2("68762cc8-b68b-4e45-854b-e830734f2d4a"); static const U64 region1_handle(0x0000040000003f00ULL); static const U64 region2_handle(0x0000030000004200ULL); -static const std::string region1_handle_str("0000040000003f00"); -static const std::string region2_handle_str("0000030000004200"); #if 0 static bool @@ -277,6 +275,11 @@ namespace ~tst_viewerassetstats_index() { + // The global stats a case made, where a failed REQUIRE stopped it + // before it deleted them, are not left for the cases after it; + // deleted while the case's recorder is still there. + delete gViewerAssetStats; + gViewerAssetStats = nullptr; LLTrace::set_master_thread_recorder(NULL); } @@ -349,7 +352,7 @@ namespace } } - TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: a new non-global instance's counts start at zero", "[tst_viewerassetstats_test]") + TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: a new non-global instance's counts and response times start at zero", "[tst_viewerassetstats_test]") { LLViewerAssetStats * it = new LLViewerAssetStats(); it->setRegion(region1_handle); @@ -357,7 +360,7 @@ namespace LLSD sd = it->asLLSD(false); CHECK(is_double_key_map(sd, "regions", "duration")); CHECK(is_single_slot_array(sd["regions"], region1_handle)); - sd = sd[0]; + sd = sd["regions"][0]; delete it; @@ -392,7 +395,9 @@ namespace // Reset and check zeros... // Reset leaves current region in place gViewerAssetStats->reset(); - sd = gViewerAssetStats->asLLSD(false)["regions"][region1_handle_str]; + sd = gViewerAssetStats->asLLSD(false); + CHECK(is_single_slot_array(sd["regions"], region1_handle)); + sd = sd["regions"][0]; delete gViewerAssetStats; gViewerAssetStats = NULL; From 08d1697b6afaeffd51017b1332d02632a1568586 Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 04:47:58 -0400 Subject: [PATCH 8/9] A feedback DSN with a second user part is refused ALFeedback::endpointFromDsn took the host from after the key's user part without checking for another. http://k@127.0.0.1:8765@evil.example.com/1 passed the loopback check that allows plain HTTP, and so did http://k@[::1]@evil.example.com/1 through the bracketed branch; the endpoint made from either sends to evil.example.com in the clear. The DSN is the build's, so only a build configured with one was exposed. A DSN with more than one user part is refused. alfeedback_test checks both, and the same over HTTPS. Its "loopback as a user name" check put loopback in the path; it uses loopback as a user name now, and the old check stays as "loopback in the path". Co-Authored-By: Claude Opus 5.5 --- indra/newview/alfeedback.cpp | 6 ++++++ indra/newview/tests/alfeedback_test.cpp | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/indra/newview/alfeedback.cpp b/indra/newview/alfeedback.cpp index e05c47997c..6d8376067a 100644 --- a/indra/newview/alfeedback.cpp +++ b/indra/newview/alfeedback.cpp @@ -134,6 +134,12 @@ std::optional ALFeedback::endpointFromDsn(std::string_view { return std::nullopt; } + // One user part, the key's. A second would make what follows it the host + // the address names, whatever the host below was taken to be. + if (host_and_path.find('@') != std::string_view::npos) + { + return std::nullopt; + } // What goes is the user's: in the clear only to this machine. if (scheme == "http") { diff --git a/indra/newview/tests/alfeedback_test.cpp b/indra/newview/tests/alfeedback_test.cpp index 938904eb43..56e34cbeee 100644 --- a/indra/newview/tests/alfeedback_test.cpp +++ b/indra/newview/tests/alfeedback_test.cpp @@ -393,7 +393,17 @@ namespace UNSCOPED_INFO("a host that starts like loopback"); REQUIRE_FALSE(ALFeedback::endpointFromDsn("http://k@127.0.0.1.example.com/1")); UNSCOPED_INFO("loopback as a user name"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("http://127.0.0.1@evil.example.com/1")); + UNSCOPED_INFO("loopback in the path"); REQUIRE_FALSE(ALFeedback::endpointFromDsn("http://k@evil.example.com/127.0.0.1/1")); + // A second user part, after the key's, would make what follows it the + // host the address names, whatever the loopback check took for one. + UNSCOPED_INFO("loopback as the user name of another host"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("http://k@127.0.0.1:8765@evil.example.com/1")); + UNSCOPED_INFO("IPv6 loopback as the user name of another host"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("http://k@[::1]@evil.example.com/1")); + UNSCOPED_INFO("a second user part, encrypted or not"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("https://k@o42.ingest.sentry.io@evil.example.com/4501")); } TEST_CASE("alfeedback: a report's logs hide capability addresses and the given words", "[alfeedback]") From ffecd3cd2131a3e25ff47bdb17860f2017877a0d Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 14 Oct 2026 06:23:46 -0400 Subject: [PATCH 9/9] A feedback DSN's key is found before its host, and its path may hold an @ The check for a second user part (c94acf610c) looked for an @ in all that followed the key, path included, so a self-hosted DSN with an @ in its path, which a URI allows, was refused, as review on #475 found. And the key was taken from before the first @ anywhere: a DSN with no key but an @ in its path, https://sentry.example.com/team@blue/7, was read as the key "sentry.example.com/team" and the host "blue". The key is the user part of the address's authority, which ends at the first '/', and the authority holds exactly one @. Each DSN with a second user part is still refused. alfeedback_test checks that an @ in a path stays in the path, and that a DSN with no key and an @ in its path is refused. Each new check fails with the key looked for in all of the DSN, and the second user parts are accepted without the one-@ rule. Co-Authored-By: Claude Opus 5.5 --- indra/newview/alfeedback.cpp | 15 +++++++-------- indra/newview/tests/alfeedback_test.cpp | 12 ++++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/indra/newview/alfeedback.cpp b/indra/newview/alfeedback.cpp index 6d8376067a..1e6fa66547 100644 --- a/indra/newview/alfeedback.cpp +++ b/indra/newview/alfeedback.cpp @@ -109,8 +109,13 @@ std::optional ALFeedback::endpointFromDsn(std::string_view return std::nullopt; } - const size_t at = rest.find('@'); - if (at == std::string_view::npos) + // The key is the address's user part, which comes before the host, in + // what ends at the first '/': an '@' after it is the path's. One user + // part: a second would make what follows it the host the address names, + // whatever the host below was taken to be. + const std::string_view authority = rest.substr(0, rest.find('/')); + const size_t at = authority.find('@'); + if (at == std::string_view::npos || authority.find('@', at + 1) != std::string_view::npos) { return std::nullopt; } @@ -134,12 +139,6 @@ std::optional ALFeedback::endpointFromDsn(std::string_view { return std::nullopt; } - // One user part, the key's. A second would make what follows it the host - // the address names, whatever the host below was taken to be. - if (host_and_path.find('@') != std::string_view::npos) - { - return std::nullopt; - } // What goes is the user's: in the clear only to this machine. if (scheme == "http") { diff --git a/indra/newview/tests/alfeedback_test.cpp b/indra/newview/tests/alfeedback_test.cpp index 56e34cbeee..5ed16472bb 100644 --- a/indra/newview/tests/alfeedback_test.cpp +++ b/indra/newview/tests/alfeedback_test.cpp @@ -77,6 +77,16 @@ namespace CHECK(endpoint->publicKey == "key"); } + // A path may hold an '@': it is the path's, after the host, and not a + // user part. + TEST_CASE("alfeedback: an @ in a self-hosted DSN's path stays in its path", "[alfeedback]") + { + const auto endpoint = ALFeedback::endpointFromDsn("https://k@sentry.example.com/team@blue/7"); + REQUIRE(endpoint.has_value()); + CHECK(endpoint->url == "https://sentry.example.com/team@blue/api/7/envelope/"); + CHECK(endpoint->publicKey == "k"); + } + TEST_CASE("alfeedback: a DSN missing a part is refused", "[alfeedback]") { UNSCOPED_INFO("no scheme"); @@ -85,6 +95,8 @@ namespace REQUIRE_FALSE(ALFeedback::endpointFromDsn("ftp://key@host/1")); UNSCOPED_INFO("no key"); REQUIRE_FALSE(ALFeedback::endpointFromDsn("https://host/1")); + UNSCOPED_INFO("no key, and an @ in the path, which names no user"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("https://sentry.example.com/team@blue/7")); UNSCOPED_INFO("empty key"); REQUIRE_FALSE(ALFeedback::endpointFromDsn("https://@host/1")); UNSCOPED_INFO("no project");