From 19b270f3543a458eb21f77f49b0addd27f843261 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 03:19:00 -0400 Subject: [PATCH 1/6] alscript's preprocessor tests check what they say they check Old test bugs #462's idiom pass found, fixed. - alpreprocessor: - "yes4 on its line" compared a find() with itself; it now checks yes4 is output line 19, where the source has it; - three messages were built before the call that fills their reason (the grid builtins' load, and the @line reader's twice): each call comes first now; - guards before reads that could fail: a source location found before its file is read, the size-weighed note's five arguments, a module note's three, and a problem said before problems.back() in the five cases that overrun a limit (CHECK(r.overran) alone did not say there was one). - alincludesearch: the problems an explanation adds, and the suggestions in ./lib/, are sized before they are indexed. - aldiskcache: "kept a moment" could not fail. It held a reference to the list the cache keeps, which the next call for the same folder refills, so it compared that list with itself; it holds a copy now. The later list is sized before [1] is read. - alscriptlexicon: a line comment's end is checked against the text's length before the character there is read; s.closed was no guard, since a line comment is always closed. 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/alscript/tests/aldiskcache_test.cpp | 8 +++-- indra/alscript/tests/alincludesearch_test.cpp | 7 +++- indra/alscript/tests/alpreprocessor_test.cpp | 33 +++++++++++++++---- indra/alscript/tests/alscriptlexicon_test.cpp | 1 + 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/indra/alscript/tests/aldiskcache_test.cpp b/indra/alscript/tests/aldiskcache_test.cpp index b4eefea5db..194a746154 100644 --- a/indra/alscript/tests/aldiskcache_test.cpp +++ b/indra/alscript/tests/aldiskcache_test.cpp @@ -140,7 +140,9 @@ namespace s.write("a/.luaurc", "{}"); s.write("a/b/c/.luaurc", "{}"); - const std::vector& up = cache.upwards(s.at("a/b/c"), ".luaurc", 1, 10.0); + // A copy: upwards() answers with what it keeps, which a later call + // for the same folder fills again. + const std::vector up = cache.upwards(s.at("a/b/c"), ".luaurc", 1, 10.0); { INFO("nearest first, to the root"); REQUIRE(up.size() >= 2); @@ -150,7 +152,9 @@ namespace s.write("a/b/.luaurc", "{}"); UNSCOPED_INFO("kept a moment"); REQUIRE(cache.upwards(s.at("a/b/c"), ".luaurc", 1, 11.0).size() == up.size()); + const std::vector& later = cache.upwards(s.at("a/b/c"), ".luaurc", 1, 10.0 + ALDiskCache::FRESH_SECONDS + 1.0); + REQUIRE(later.size() > 1); UNSCOPED_INFO("then found"); - CHECK(cache.upwards(s.at("a/b/c"), ".luaurc", 1, 10.0 + ALDiskCache::FRESH_SECONDS + 1.0)[1] == s.at("a/b/.luaurc")); + CHECK(later[1] == s.at("a/b/.luaurc")); } } diff --git a/indra/alscript/tests/alincludesearch_test.cpp b/indra/alscript/tests/alincludesearch_test.cpp index d41eb3b09a..181454bd31 100644 --- a/indra/alscript/tests/alincludesearch_test.cpp +++ b/indra/alscript/tests/alincludesearch_test.cpp @@ -423,6 +423,7 @@ namespace tut facts.noFolders = false; facts.inWorld = [](const std::string& name) { return name == "lib.lsl"; }; ALIncludeSearch::explainMissing(problems, facts); + REQUIRE(problems.size() == size_t(3)); UNSCOPED_INFO("the silence said first"); CHECK(problems.front().key == std::string("PreprocObjectUnanswered")); UNSCOPED_INFO("one in the world, not taken from"); @@ -435,6 +436,7 @@ namespace tut facts.disk = true; facts.noFolders = true; ALIncludeSearch::explainMissing(problems, facts); + REQUIRE(problems.size() == size_t(2)); UNSCOPED_INFO("no folder of the scripter's: the disk not looked in"); CHECK(problems[0].key == std::string("PreprocModuleNotOnDisk")); UNSCOPED_INFO("but one asked from a file on disk is not second-guessed"); @@ -992,6 +994,7 @@ namespace tut ALIncludeSearch::explainMissing(problems, facts); { INFO("the disk not looked in"); + REQUIRE(problems.size() == size_t(1)); CHECK(problems[0].key == "PreprocModuleNotOnDisk"); CHECK(problems[0].args == std::vector{ "./nowhere" }); } @@ -1059,8 +1062,10 @@ namespace tut ALPreprocessor::Found::Yes); } } + const std::vector in_lib = search.suggest(own.self, "./lib/", true, own, disk); + REQUIRE(in_lib.size() > 1); UNSCOPED_INFO("each one's whole path"); - CHECK(search.suggest(own.self, "./lib/", true, own, disk)[1].path == std::string("./lib/http")); + CHECK(in_lib[1].path == std::string("./lib/http")); // An include's: the file's own folder, by its search, names whole -- // an include's own `.lslh` and `.lsli` as well as `.lsl`. diff --git a/indra/alscript/tests/alpreprocessor_test.cpp b/indra/alscript/tests/alpreprocessor_test.cpp index ea92613d1f..b4bf4366e1 100644 --- a/indra/alscript/tests/alpreprocessor_test.cpp +++ b/indra/alscript/tests/alpreprocessor_test.cpp @@ -202,8 +202,9 @@ namespace { ALLSLService service; std::string error; + const bool loaded = service.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error); INFO("builtins: " << error); - REQUIRE(service.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error)); + REQUIRE(loaded); } // The same but the notes of a require nothing reads, which a script @@ -364,15 +365,20 @@ namespace CHECK(squeeze(r.text) == std::string("yes\nyes2\nyes3\nyes4\nyes5\nyes6\nend\n")); std::istringstream in(r.text); std::string line; - S32 n = 0; + S32 n = 0; + S32 yes4 = -1; while (std::getline(in, line)) { + if (line == "yes4") + { + yes4 = n; + } ++n; } UNSCOPED_INFO("one line each"); CHECK(n == 37); UNSCOPED_INFO("yes4 on its line"); - CHECK(r.text.find("yes4") == r.text.find("yes4")); + CHECK(yes4 == 19); ALSourceMap::Loc loc = r.map.toSource(19, 0); UNSCOPED_INFO("yes4 from line 19"); CHECK(loc.line == 19); @@ -605,8 +611,12 @@ namespace "local a = require(1)\nlocal b = require(2)\nlocal x = require(name)\n"); CHECK(r.text == bundled); REQUIRE(r.includes.size() == 2u); - UNSCOPED_INFO("b mapped to its file"); - CHECK(r.map.files()[r.map.toSource(7, 0).file].name == std::string("b")); + const ALSourceMap::Loc in_b = r.map.toSource(7, 0); + { + INFO("b mapped to its file"); + REQUIRE(in_b.found()); + CHECK(r.map.files()[in_b.file].name == std::string("b")); + } // The modules' own lines, which the studio shows nothing of: not // the table's, which are no one's, nor the script's. std::string others; @@ -1416,6 +1426,7 @@ namespace } UNSCOPED_INFO("the whole said"); REQUIRE(sizes != nullptr); + REQUIRE(sizes->args.size() == size_t(5)); UNSCOPED_INFO("in code"); CHECK(sizes->args[2] == std::to_string(r.codeBefore)); UNSCOPED_INFO("on its target"); @@ -1597,6 +1608,7 @@ namespace { INFO("ran away: " << messages(r)); CHECK(r.overran); + REQUIRE_FALSE(r.problems.empty()); CHECK(r.problems.back().key == std::string("PreprocTooMuch")); } UNSCOPED_INFO("and gave the source back"); @@ -1613,6 +1625,7 @@ namespace { INFO("pasting stopped: " << messages(r)); CHECK(r.overran); + REQUIRE_FALSE(r.problems.empty()); CHECK(r.problems.back().key == std::string("PreprocTokenTooLong")); } @@ -1626,6 +1639,7 @@ namespace { INFO("stringizing stopped: " << messages(r)); CHECK(r.overran); + REQUIRE_FALSE(r.problems.empty()); CHECK(r.problems.back().key == std::string("PreprocTokenTooLong")); } @@ -1637,6 +1651,7 @@ namespace { INFO("the bytes: " << messages(r)); CHECK(r.overran); + REQUIRE_FALSE(r.problems.empty()); CHECK(r.problems.back().key == std::string("PreprocTooMuch")); } r = ALPreprocessor::run("#define L \"" + std::string(100, 'a') + "\"\nL L\n", small); @@ -1684,6 +1699,7 @@ namespace { INFO("stopped at the depth: " << messages(r)); CHECK(r.overran); + REQUIRE_FALSE(r.problems.empty()); CHECK(r.problems.back().key == std::string("PreprocNestsTooDeep")); } @@ -2358,8 +2374,9 @@ namespace const std::string script = "--!strict\nlocal util = require(\"util\")\n\nlocal deep = require(\"deep\")\nprint(util.f(), deep.k)\n"; ALPreprocessor::Result r = ALPreprocessor::run(script, lua); CHECK(messages(r) == std::string()); + bool reads = readsTrue(r, "main.luau", why); UNSCOPED_INFO("the reader finds every line: " << why); - CHECK(readsTrue(r, "main.luau", why)); + CHECK(reads); UNSCOPED_INFO("the header still first: " << r.text); CHECK(r.text.rfind("--!strict\ndo\n", 0) == 0); UNSCOPED_INFO("a module's first: " << r.text); @@ -2383,8 +2400,9 @@ namespace lsl.lineComments = true; r = ALPreprocessor::run("integer a;\n#include \"inc.lsl\"\n#if 0\ninteger gone;\n\n#endif\n#define TWICE(v) \\\n ((v) * 2)\ninteger b = TWICE(3);\ndefault { state_entry() { } }\n", lsl); CHECK(messages(r) == std::string()); + reads = readsTrue(r, "main.lsl", why); UNSCOPED_INFO("the reader finds every line: " << why); - CHECK(readsTrue(r, "main.lsl", why)); + CHECK(reads); UNSCOPED_INFO("in LSL's comment: " << r.text); REQUIRE(r.text.find("// @line 1 \"inc.lsl\"\ninteger x;\n") != std::string::npos); @@ -2456,6 +2474,7 @@ namespace INFO("in its file, at its require"); REQUIRE(said[0]->file == "disk:/lib/wrap.luau"); REQUIRE(said[0]->line == 0); + REQUIRE(said[0]->args.size() == 3); CHECK(said[0]->args[0] == "inner"); } } diff --git a/indra/alscript/tests/alscriptlexicon_test.cpp b/indra/alscript/tests/alscriptlexicon_test.cpp index 68459e2cf6..27a4b9d804 100644 --- a/indra/alscript/tests/alscriptlexicon_test.cpp +++ b/indra/alscript/tests/alscriptlexicon_test.cpp @@ -152,6 +152,7 @@ namespace REQUIRE(s.kind == Kind::Comment); REQUIRE(s.closed); REQUIRE(s.close == 0); + REQUIRE(s.end < text.size()); CHECK(text[s.end] == '\n'); CHECK(s.open == 2); } From 752f80730ea43733b711a8259dd9e507a2299241 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 03:19:08 -0400 Subject: [PATCH 2/6] alscript's Luau and LSL service tests check what they say they check Old test bugs #462's idiom pass found, fixed. - Messages built before the call that fills their reason, so they said nothing or an earlier call's error: in alluauservice, the definitions' loads, a switch of solver and back, the docs' load and a configuration's parse; in alluauconfig, the parses and the script runs. Each call now comes first, and the messages the idiom pass had cut for saying nothing are back where a failure needs them. - allslservice: "another region's builtins" built its message before the load, from a local that hid the fixture's error; the second load's result hid the fixture's loaded. Both are renamed, and the load comes first. - Guards before reads that could fail: - alluauservice: an outline's first entry, and two lints' arguments; an INFO no longer indexes a signature's parameters by an active parameter that may be past them; - allslservice: the third problem's arguments; - allsleffects: the matches and the calls each case reads two of; - allslconsts: a declaration's position, before an iterator is made from it. - Leftovers: allsleffects included linden_common.h twice, and alluauservice had a comment line its next one repeated. 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, in both solvers' runs. Three restored messages (a switch of solver, back, and the other solver's load) have none: no change to the test makes one solver refuse definitions the other took. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/tests/allslconsts_test.cpp | 1 + indra/alscript/tests/allsleffects_test.cpp | 17 +++-- indra/alscript/tests/allslservice_test.cpp | 14 ++-- indra/alscript/tests/alluauconfig_test.cpp | 85 +++++++++++++-------- indra/alscript/tests/alluauservice_test.cpp | 32 +++++--- 5 files changed, 95 insertions(+), 54 deletions(-) diff --git a/indra/alscript/tests/allslconsts_test.cpp b/indra/alscript/tests/allslconsts_test.cpp index 336b7106d4..6a45008723 100644 --- a/indra/alscript/tests/allslconsts_test.cpp +++ b/indra/alscript/tests/allslconsts_test.cpp @@ -248,6 +248,7 @@ namespace REQUIRE(r.consts.size() == size_t(3)); const auto declaredAt = [&](const std::string& word, size_t from) { const size_t at = r.text.find(word, from); + REQUIRE(at != std::string::npos); const S32 line = static_cast(std::count(r.text.begin(), r.text.begin() + at, '\n')); const size_t start = r.text.rfind('\n', at); const S32 column = static_cast(start == std::string::npos ? at : at - start - 1); diff --git a/indra/alscript/tests/allsleffects_test.cpp b/indra/alscript/tests/allsleffects_test.cpp index 8c9217db14..17624445c4 100644 --- a/indra/alscript/tests/allsleffects_test.cpp +++ b/indra/alscript/tests/allsleffects_test.cpp @@ -23,8 +23,6 @@ #include "linden_common.h" -#include "linden_common.h" - #include "../lsl/allsleffects.h" #include "../lsl/allslservice.h" @@ -184,6 +182,7 @@ namespace const std::vector bumps = p.calls("bump"); const std::vector sames = p.calls("same"); REQUIRE(bumps.size() == size_t(11)); + REQUIRE(sames.size() == size_t(2)); const auto first = [&](LSLFunctionExpression* call) { return effects.mayRunFirst(statementOf(call), call); }; UNSCOPED_INFO("0: the right operand runs first"); CHECK(first(bumps[0])); @@ -228,14 +227,18 @@ namespace " l = [bump(), same(2)];\n" // same changes nothing, and bump does "} }\n"); REQUIRE(q.script != nullptr); - const ALLSLEffects quiet(q.script); - const auto firstQ = [&](LSLFunctionExpression* call) { return quiet.mayRunFirst(statementOf(call), call); }; + const ALLSLEffects quiet(q.script); + const std::vector quiet_bumps = q.calls("bump"); + const std::vector quiet_sames = q.calls("same"); + REQUIRE(quiet_bumps.size() == size_t(2)); + REQUIRE(quiet_sames.size() == size_t(2)); + const auto firstQ = [&](LSLFunctionExpression* call) { return quiet.mayRunFirst(statementOf(call), call); }; UNSCOPED_INFO("a quiet call before a quiet one"); - CHECK(firstQ(q.calls("same")[0])); + CHECK(firstQ(quiet_sames[0])); UNSCOPED_INFO("a call that reads before one that writes"); - CHECK_FALSE(firstQ(q.calls("bump")[0])); + CHECK_FALSE(firstQ(quiet_bumps[0])); UNSCOPED_INFO("a call that writes before a quiet one"); - CHECK_FALSE(firstQ(q.calls("same")[1])); + CHECK_FALSE(firstQ(quiet_sames[1])); } TEST_CASE_METHOD(allsleffects_data, "allsleffects: a function's changes past the script and globals it reads through calls", "[allsleffects]") { diff --git a/indra/alscript/tests/allslservice_test.cpp b/indra/alscript/tests/allslservice_test.cpp index 667f12bdf2..1ece73f844 100644 --- a/indra/alscript/tests/allslservice_test.cpp +++ b/indra/alscript/tests/allslservice_test.cpp @@ -911,9 +911,10 @@ namespace TEST_CASE_METHOD(allslservice_data, "allslservice: another region's builtins: new ones known after and none declared twice", "[allslservice]") { const std::string base = std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt"; - std::string error; - UNSCOPED_INFO("the first region's: " + error); - REQUIRE(service.loadBuiltins(base, error)); + std::string why; + const bool first = service.loadBuiltins(base, why); + UNSCOPED_INFO("the first region's: " + why); + REQUIRE(first); const std::string script = "default\n{\n state_entry()\n {\n integer n = llBrandNewThing(1);\n llOwnerSay((string)n);\n }\n}\n"; bool unknown = false; for (const ALScriptProblem& problem : service.check(script)) @@ -937,13 +938,13 @@ namespace llofstream out(path, std::ios::out | std::ios::binary | std::ios::trunc); out << text << "integer llBrandNewThing( integer Value )\n"; } - const bool loaded = service.loadBuiltins(path, error); + const bool newer = service.loadBuiltins(path, why); // Tailslide's table is the process's and only ever added to: what // the file declares stays for every case after this one. ll_test::StateGuard::accept("Tailslide's builtins"); LLFile::remove(path); - UNSCOPED_INFO("loaded: " + error); - CHECK(loaded); + UNSCOPED_INFO("loaded: " + why); + CHECK(newer); unknown = false; for (const ALScriptProblem& problem : service.check(script)) { @@ -1070,6 +1071,7 @@ namespace { INFO("the outer call of two: " + said[2].message); CHECK(said[2].line == 5); + REQUIRE_FALSE(said[2].args.empty()); CHECK(said[2].args[0] == "llGetListLength"); } UNSCOPED_INFO("a NOLINT answers to the rule's name"); diff --git a/indra/alscript/tests/alluauconfig_test.cpp b/indra/alscript/tests/alluauconfig_test.cpp index f26727fe6e..824adb1df8 100644 --- a/indra/alscript/tests/alluauconfig_test.cpp +++ b/indra/alscript/tests/alluauconfig_test.cpp @@ -40,7 +40,9 @@ namespace { ALLuauConfig config; std::string error; - REQUIRE(ALLuauConfig::parse("{\n \"languageMode\": \"strict\",\n \"aliases\": { \"Lib\": \"./lib\", \"shared\": \"../shared\", \"abs\": \"/opt/lua\" }\n}\n", config, error)); + const bool parsed = ALLuauConfig::parse("{\n \"languageMode\": \"strict\",\n \"aliases\": { \"Lib\": \"./lib\", \"shared\": \"../shared\", \"abs\": \"/opt/lua\" }\n}\n", config, error); + UNSCOPED_INFO("parses: " + error); + REQUIRE(parsed); REQUIRE(config.aliases.size() == size_t(3)); UNSCOPED_INFO("by lower-case name"); CHECK(config.aliases["lib"] == std::string("./lib")); @@ -129,7 +131,9 @@ namespace ALLuauConfig config; std::string error; const ALLuauConfig defaults; - REQUIRE(ALLuauConfig::parse("{\n \"lint\": { \"LocalUnused\": false, \"LocalShadow\": true },\n \"globals\": [\"Alchemy\", \"grid\"]\n}\n", config, error)); + const bool parsed = ALLuauConfig::parse("{\n \"lint\": { \"LocalUnused\": false, \"LocalShadow\": true },\n \"globals\": [\"Alchemy\", \"grid\"]\n}\n", config, error); + UNSCOPED_INFO("parses: " + error); + REQUIRE(parsed); REQUIRE(config.globals.size() == size_t(2)); UNSCOPED_INFO("in order"); CHECK(config.globals[0] == std::string("Alchemy")); @@ -285,8 +289,9 @@ namespace } ALLuauConfig out; std::string error; - UNSCOPED_INFO("beside Luau's"); - REQUIRE(ALLuauConfig::parse("{ \"languageMode\": \"strict\", \"lint\": { \"LocalUnused\": false, \"SlCompoundAssign\": false } }", out, error)); + const bool beside = ALLuauConfig::parse("{ \"languageMode\": \"strict\", \"lint\": { \"LocalUnused\": false, \"SlCompoundAssign\": false } }", out, error); + UNSCOPED_INFO("beside Luau's: " + error); + REQUIRE(beside); { INFO("the rest read"); CHECK(out.mode == "strict"); @@ -295,31 +300,36 @@ namespace UNSCOPED_INFO("ours off"); CHECK((out.slLints & compound) == 0); { - INFO("alone"); - CHECK(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false } }", out, error)); + const bool parsed = ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false } }", out, error); + INFO("alone: " + error); + CHECK(parsed); CHECK((out.slLints & compound) == 0); } { - INFO("first"); - CHECK(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false, \"LocalUnused\": false }, \"globals\": [\"g\"] }", out, error)); + const bool parsed = ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false, \"LocalUnused\": false }, \"globals\": [\"g\"] }", out, error); + INFO("first: " + error); + CHECK(parsed); CHECK((out.slLints & compound) == 0); CHECK(out.globals == std::vector{ "g" }); CHECK((out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); } { - INFO("with // comments, over lines"); - CHECK(ALLuauConfig::parse("{\n // c style\n \"lint\": {\n \"LocalUnused\": true,\n \"SlCompoundAssign\": false\n }\n}", out, error)); + const bool parsed = ALLuauConfig::parse("{\n // c style\n \"lint\": {\n \"LocalUnused\": true,\n \"SlCompoundAssign\": false\n }\n}", out, error); + INFO("with // comments, over lines: " + error); + CHECK(parsed); CHECK((out.slLints & compound) == 0); } { - INFO("* for ours too"); - CHECK(ALLuauConfig::parse("{ \"lint\": { \"*\": false } }", out, error)); + const bool parsed = ALLuauConfig::parse("{ \"lint\": { \"*\": false } }", out, error); + INFO("* for ours too: " + error); + CHECK(parsed); CHECK(out.slLints == 0); CHECK((out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); } { - INFO("then one on"); - CHECK(ALLuauConfig::parse("{ \"lint\": { \"*\": false, \"SlCompoundAssign\": true } }", out, error)); + const bool parsed = ALLuauConfig::parse("{ \"lint\": { \"*\": false, \"SlCompoundAssign\": true } }", out, error); + INFO("then one on: " + error); + CHECK(parsed); CHECK(out.slLints == compound); } { @@ -333,8 +343,9 @@ namespace CHECK(error == "Bad setting 'fatal'. Valid options are true and false"); } { - INFO("Luau's words on the rest keep their lines"); - CHECK_FALSE(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false },\n \"globals\": 3 }", out, error)); + const bool parsed = ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false },\n \"globals\": 3 }", out, error); + INFO("Luau's words on the rest keep their lines: " + error); + CHECK_FALSE(parsed); REQUIRE(error.find("line 2") != std::string::npos); } @@ -342,8 +353,9 @@ namespace base.slLints = 0; base.slFatalLints = compound; { - INFO("the base's where the file does not say"); - CHECK(ALLuauConfig::parse("{ \"languageMode\": \"strict\" }", out, error, &base)); + const bool parsed = ALLuauConfig::parse("{ \"languageMode\": \"strict\" }", out, error, &base); + INFO("the base's where the file does not say: " + error); + CHECK(parsed); CHECK(out.slLints == 0); CHECK(out.slFatalLints == compound); } @@ -366,10 +378,13 @@ namespace " other = { tool = true },\n" "}\n"; std::string json, error; - REQUIRE(ALLuauConfigScript::asLuaurc(source, json, error)); + const bool ran = ALLuauConfigScript::asLuaurc(source, json, error); + UNSCOPED_INFO("ran: " + error); + REQUIRE(ran); ALLuauConfig config; + const bool parsed = ALLuauConfig::parse(json, config, error); UNSCOPED_INFO("and parses as a .luaurc: " + error + " in " + json); - REQUIRE(ALLuauConfig::parse(json, config, error)); + REQUIRE(parsed); CHECK(config.mode == std::string("strict")); { INFO("its aliases, by name in lower case"); @@ -407,8 +422,12 @@ namespace // as it is, and what one cannot hold is refused. { INFO("a backslash kept"); - CHECK(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'C:\\\\lua\\\\lib' } } }", json, error)); - CHECK(ALLuauConfig::parse(json, config, error)); + const bool written = ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'C:\\\\lua\\\\lib' } } }", json, error); + UNSCOPED_INFO("ran: " + error); + CHECK(written); + const bool read = ALLuauConfig::parse(json, config, error); + UNSCOPED_INFO("parses: " + error); + CHECK(read); CHECK(config.aliases["q"] == "C:\\lua\\lib"); } { @@ -511,14 +530,16 @@ namespace // many elements, at once. const auto moving = std::chrono::steady_clock::now(); { - INFO("two billion nils moved, refused at once"); - CHECK_FALSE(ALLuauConfigScript::asLuaurc("table.move({}, 1, 2147483646, 2) return {}", json, error)); + const bool ran = ALLuauConfigScript::asLuaurc("table.move({}, 1, 2147483646, 2) return {}", json, error); + INFO("two billion nils moved, refused at once: " + error); + CHECK_FALSE(ran); REQUIRE(error.find("table.move") != std::string::npos); CHECK(std::chrono::duration(std::chrono::steady_clock::now() - moving).count() < ALLuauConfigScript::MOST_SECONDS); } { - INFO("a move within it, as Luau's"); - CHECK(ALLuauConfigScript::asLuaurc("return { luau = { globals = table.move({ 'a', 'b' }, 1, 2, 2, { 'z' }) } }", json, error)); + const bool ran = ALLuauConfigScript::asLuaurc("return { luau = { globals = table.move({ 'a', 'b' }, 1, 2, 2, { 'z' }) } }", json, error); + INFO("a move within it, as Luau's: " + error); + CHECK(ran); CHECK(json == "{\"globals\": [\"z\", \"a\", \"b\"]}"); } { @@ -535,14 +556,16 @@ namespace // Second Life's VM opens Eris as `ares`; a configuration is run // without it. The rest of Luau's libraries are there. { - INFO("no ares"); - CHECK(ALLuauConfigScript::asLuaurc("return { luau = { languagemode = ares and 'strict' or 'nonstrict' } }", json, error)); + const bool ran = ALLuauConfigScript::asLuaurc("return { luau = { languagemode = ares and 'strict' or 'nonstrict' } }", json, error); + INFO("no ares: " + error); + CHECK(ran); REQUIRE(json.find("nonstrict") != std::string::npos); } { - INFO("Luau's libraries"); - CHECK(ALLuauConfigScript::asLuaurc("return { luau = { globals = { string.upper('a'), tostring(math.max(1, 2)), table.concat({ 'x', 'y' }), buffer and 'b' or 'none' } } }", - json, error)); + const bool ran = ALLuauConfigScript::asLuaurc( + "return { luau = { globals = { string.upper('a'), tostring(math.max(1, 2)), table.concat({ 'x', 'y' }), buffer and 'b' or 'none' } } }", json, error); + INFO("Luau's libraries: " + error); + CHECK(ran); CHECK(json == "{\"globals\": [\"A\", \"2\", \"xy\", \"b\"]}"); } } diff --git a/indra/alscript/tests/alluauservice_test.cpp b/indra/alscript/tests/alluauservice_test.cpp index 789220f451..d912a2e8af 100644 --- a/indra/alscript/tests/alluauservice_test.cpp +++ b/indra/alscript/tests/alluauservice_test.cpp @@ -253,7 +253,9 @@ namespace std::stringstream json; json << in.rdbuf(); std::string docs_error; - CHECK(service.loadDocs(json.str(), docs_error)); + const bool docs_loaded = service.loadDocs(json.str(), docs_error); + UNSCOPED_INFO("docs loaded: " + docs_error); + CHECK(docs_loaded); const std::string script = "local count = 1\nll.Say(0, tostring(count))\n"; ALScriptHover hover = service.hover(script, 1, 4); // on `Say` REQUIRE(hover.found); @@ -666,7 +668,9 @@ namespace CHECK(mentions(plain, "unused")); ALLuauConfig config; std::string bad; - REQUIRE(ALLuauConfig::parse("{ \"globals\": [\"Alchemy\"], \"lint\": { \"LocalUnused\": false } }", config, bad)); + const bool parsed = ALLuauConfig::parse("{ \"globals\": [\"Alchemy\"], \"lint\": { \"LocalUnused\": false } }", config, bad); + UNSCOPED_INFO("parses: " + bad); + REQUIRE(parsed); service.setConfig(config); ALScriptProblems configured = service.check(script); UNSCOPED_INFO("Alchemy known: " + said(configured)); @@ -779,7 +783,7 @@ namespace UNSCOPED_INFO("with a colon, a and b"); REQUIRE(colon.parameters.size() == size_t(2)); { - INFO("at b: " + colon.parameters[static_cast(std::max(0, colon.active))]); + INFO("at b: " + colon.label); CHECK(colon.active == 1); CHECK(colon.parameters[1].find("b") == 0); } @@ -792,7 +796,9 @@ namespace for (const bool use : { true, false, true }) { std::string why; - REQUIRE(service.setNewSolver(use, why)); + const bool switched = service.setNewSolver(use, why); + UNSCOPED_INFO("switched: " + why); + REQUIRE(switched); REQUIRE(service.newSolver() == use); UNSCOPED_INFO("the definitions with it"); REQUIRE(service.hasDefinitions()); @@ -996,7 +1002,6 @@ namespace service.hover(strict, 2, 6); UNSCOPED_INFO("and nothing checked again"); CHECK(service.typeChecks() - before == size_t(one_slot ? 1 : 2)); - // The hints the questions read are the strict ones either way. // The types a question reads are the strict ones, whatever the // script's mode: a nonstrict check's module knows none of them. const std::string typed = "local s = string.rep(\"a\", 2)\nlocal n = #s\nprint(n)\n"; @@ -1403,6 +1408,7 @@ namespace { if (p.code == "SlNumberTruth") { + REQUIRE_FALSE(p.args.empty()); out.push_back(p.key + " " + p.args[0] + " " + std::to_string(p.line)); } } @@ -1494,6 +1500,7 @@ namespace { if (p.code == "SlNilSentinel") { + REQUIRE(p.args.size() > 4); out.push_back(p.key + " " + p.args[0] + " " + p.args[4] + " " + std::to_string(p.line) + (p.severity == ALScriptProblem::Severity::Error ? "" : " not an error")); } @@ -1572,8 +1579,10 @@ namespace UNSCOPED_INFO("no type"); REQUIRE(shape[i].detail.empty()); } + const std::vector again = service.outline(script); + REQUIRE_FALSE(again.empty()); UNSCOPED_INFO("the outline still typed"); - CHECK(service.outline(script)[0].detail == "number"); + CHECK(again[0].detail == "number"); const std::vector broken = ALLuauService::shape("local function a()\n x = \nend\nlocal function b()\nend\n"); { INFO("broken in a: both still there"); @@ -2435,8 +2444,9 @@ namespace ALLuauService own; std::string why; own.setNewSolver(newSolver, why); + const bool read = own.loadDefinitions(definitions + "\ndeclare REZ_TORQUE: string\n", why); UNSCOPED_INFO("loads: " + why); - CHECK(own.loadDefinitions(definitions + "\ndeclare REZ_TORQUE: string\n", why)); + CHECK(read); const ALScriptProblems problems = own.check("--!strict\nlocal s: string = REZ_TORQUE\nlocal n: number = SKY_MIE_CONFIG\nprint(s, n)\n"); UNSCOPED_INFO("theirs, a string; ours, a number: " + said(problems)); CHECK(alluauservice_data::errors(problems) == size_t(0)); @@ -2447,13 +2457,15 @@ namespace UNSCOPED_INFO("definitions loaded: " + error); CHECK(loaded); std::string why; - UNSCOPED_INFO("the other solver"); - REQUIRE(service.setNewSolver(!newSolver, why)); + const bool other = service.setNewSolver(!newSolver, why); + UNSCOPED_INFO("the other solver: " + why); + REQUIRE(other); ALScriptProblems problems = service.check("print(REZ_TORQUE)\n"); UNSCOPED_INFO("known there: " + said(problems)); CHECK_FALSE(unknownGlobal(problems, "REZ_TORQUE")); + const bool back = service.setNewSolver(newSolver, why); UNSCOPED_INFO("and back: " + why); - CHECK(service.setNewSolver(newSolver, why)); + CHECK(back); problems = service.check("print(REZ_TORQUE)\n"); UNSCOPED_INFO("known again: " + said(problems)); CHECK_FALSE(unknownGlobal(problems, "REZ_TORQUE")); From 6f4d178292eb1cffd800b97c67ca44afdf5b0eb4 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 03:19:16 -0400 Subject: [PATCH 3/6] alscript's lint and fix tests check what they say they check Old test bugs #462's idiom pass found, fixed. - A label before CHECK(require_fixed(...) == ...) was taken by the helper's own first assertion: an unscoped message goes to the next assertion, which is the helper's, so a failing CHECK carried no label and the helper's carried the wrong one. Each is a scoped INFO around its CHECK now: 52 in alscriptfixes, 58 in alscriptlintpass. - alscriptfixes built four messages by calling require_acted_on, so the helper, its REQUIREs with it, ran twice; it runs once, and the message reads its result. - alscriptfixes, guards before reads that could fail: the fixes offered for a removal, a require, an include and a negation, each sized before front(). "Not offered a name declared after" read the first fix's second argument unchecked; it now checks no fix offers it. - Both fixtures wrote every load's error to one string, so a "builtins:" or "definitions:" message could show another load's error, or none: each load has its own. - alscriptlintpass: a comment about TUT's fifty-test limit goes. 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, in both solvers' runs. alscriptfixes runs 858 assertions where it ran 865: the helper no longer runs twice. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/tests/alscriptfixes_test.cpp | 497 +++++++++------ .../alscript/tests/alscriptlintpass_test.cpp | 595 +++++++++++------- 2 files changed, 663 insertions(+), 429 deletions(-) diff --git a/indra/alscript/tests/alscriptfixes_test.cpp b/indra/alscript/tests/alscriptfixes_test.cpp index 4ad1108daa..3b3028b295 100644 --- a/indra/alscript/tests/alscriptfixes_test.cpp +++ b/indra/alscript/tests/alscriptfixes_test.cpp @@ -47,7 +47,10 @@ namespace { ALLSLService lsl; ALLuauService luau; - std::string error; + // Each load's own: one shared would say the last load's error under + // another's name. + std::string builtinsError; + std::string definitionsError; bool lslLoaded = false; bool luauLoaded = false; // Luau's new type solver, where the run asks for it: CTest runs @@ -56,12 +59,12 @@ namespace alscriptfixes_data() { - lslLoaded = lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error); + lslLoaded = lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", builtinsError); llifstream in(std::string(AL_LSL_DEFINITIONS_DIR) + "/secondlife.d.luau", std::ios::binary); std::stringstream text; text << in.rdbuf(); - luau.setNewSolver(newSolver, error); - luauLoaded = luau.loadDefinitions(text.str(), error); + luau.setNewSolver(newSolver, definitionsError); + luauLoaded = luau.loadDefinitions(text.str(), definitionsError); } ALScriptProblems check(const std::string& script, bool lua) @@ -190,7 +193,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a missing semicolon is put where it is missing", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string made = require_fixed("default\n{\n state_entry()\n {\n integer a = 1\n llOwnerSay((string)a);\n }\n}\n", false, "LSLSyntaxMissing", "Insert ';'"); @@ -200,7 +203,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL name spelt wrong becomes the nearest in scope or builtin", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); std::string made = require_fixed("default\n{\n state_entry()\n {\n llownersay(\"hi\");\n }\n}\n", false, "LSLUndeclared", "Change 'llownersay' to 'llOwnerSay'"); @@ -217,13 +220,13 @@ namespace const ALScriptProblem* later = keyed(problems, "LSLUndeclared"); UNSCOPED_INFO("said: " + said(problems)); REQUIRE(later != nullptr); - UNSCOPED_INFO("not a name declared after: " + said(problems)); - REQUIRE((later->fixes.empty() || later->fixes.front().args[1] != "count")); + UNSCOPED_INFO("not a name declared after: " + titles(later->fixes)); + CHECK(std::none_of(later->fixes.begin(), later->fixes.end(), [](const ALScriptFix& fix) { return fix.args.size() > 1 && fix.args[1] == "count"; })); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated LSL function is replaced only where it has one replacement", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string made = require_fixed("default\n{\n state_entry()\n {\n llSoundPreload(\"boom\");\n }\n}\n", false, "LSLDeprecatedWithReplacement", "Use 'llPreloadSound' instead of 'llSoundPreload'"); @@ -240,7 +243,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an unused SLua local is marked unused safely", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); const std::string made = require_fixed("local unused = 1\n", true, "LuauLintLocalUnused", "Rename to '_unused'"); CHECK(made == std::string("local _unused = 1\n")); @@ -256,7 +259,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an SLua key spelt wrong is changed to the nearest there is", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); if (newSolver) { @@ -271,7 +274,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated SLua call is replaced as the definitions say; never safe", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); const ALScriptProblems problems = check("local n = ll.Abs(-1)\nprint(n)\n", true); const ALScriptProblem* problem = nullptr; @@ -386,26 +389,36 @@ namespace REQUIRE(ALScriptFixes::suppressed(lint, after, "", lua)); return after; }; - UNSCOPED_INFO("its own"); - CHECK(require_suppression("local x = 1", true) == std::string("local x = 1 -- NOLINT(LocalUnused)")); - UNSCOPED_INFO("the line's comment"); - CHECK(require_suppression("local x = 1 -- why", true) == std::string("local x = 1 -- why NOLINT(LocalUnused)")); - UNSCOPED_INFO("a NOLINT's list"); - CHECK(require_suppression("local x = 1 -- NOLINT(ShadowLocal)", true) == std::string("local x = 1 -- NOLINT(ShadowLocal, LocalUnused)")); - UNSCOPED_INFO("after a block comment"); - CHECK(require_suppression("local x = 1 --[[ why ]]", true) == std::string("local x = 1 --[[ why ]] -- NOLINT(LocalUnused)")); + { + INFO("its own"); + CHECK(require_suppression("local x = 1", true) == std::string("local x = 1 -- NOLINT(LocalUnused)")); + } + { + INFO("the line's comment"); + CHECK(require_suppression("local x = 1 -- why", true) == std::string("local x = 1 -- why NOLINT(LocalUnused)")); + } + { + INFO("a NOLINT's list"); + CHECK(require_suppression("local x = 1 -- NOLINT(ShadowLocal)", true) == std::string("local x = 1 -- NOLINT(ShadowLocal, LocalUnused)")); + } + { + INFO("after a block comment"); + CHECK(require_suppression("local x = 1 --[[ why ]]", true) == std::string("local x = 1 --[[ why ]] -- NOLINT(LocalUnused)")); + } UNSCOPED_INFO("nothing where a bare NOLINT says it"); REQUIRE_FALSE(ALScriptFixes::suppression(lint, "local x = 1 -- NOLINT", true).has_value()); lint.code = "20009"; lint.key = "LSLDeclaredButNotUsed"; - UNSCOPED_INFO("LSL"); - CHECK(require_suppression(" integer x;", false) == std::string(" integer x; // NOLINT(DeclaredButNotUsed)")); + { + INFO("LSL"); + CHECK(require_suppression(" integer x;", false) == std::string(" integer x; // NOLINT(DeclaredButNotUsed)")); + } } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an SLua global spelt wrong is changed to the nearest name in scope", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); const ALScriptProblems problems = check("pirnt(\"hi\")\n", true); const ALScriptProblem* problem = nullptr; @@ -426,7 +439,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL value of the wrong type is cast where LSL has the cast", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); std::string made = require_fixed("default\n{\n state_entry()\n {\n llOwnerSay(5 + 1);\n }\n}\n", false, "LSLArgumentWrongType", "Cast to string"); @@ -460,7 +473,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: the LSL warnings that say what to write are written so", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string head = "default\n{\n state_entry()\n {\n integer a = 2;\n"; const std::string tail = " llOwnerSay((string)a);\n }\n}\n"; @@ -499,7 +512,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: the SLua fixes read off what the checker marks", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); std::string made = require_fixed("--!strict\nlocal n = 5\nlocal s: string = n\nprint(s)\n", true, "LuauTypeMismatch", "Wrap in tostring()"); UNSCOPED_INFO("the value a declaration is given: " + made); @@ -521,7 +534,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an unused LSL declaration is removed unless its value does something", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string state = "default\n{\n state_entry()\n {\n llOwnerSay(\"x\");\n }\n}\n"; std::string made = require_fixed("integer unused = 5;\n" + state, false, "LSLDeclaredButNotUsed", "Remove 'unused'"); @@ -549,12 +562,14 @@ namespace ALScriptProblem shared; shared.line = 0; ALScriptFixes::offerRemoval(shared, "integer a = 1; integer b = 2;\n", 0, 8, 0, 13, "a"); + REQUIRE_FALSE(shared.fixes.empty()); UNSCOPED_INFO("only itself, and the space after it"); CHECK(ALScriptFixes::apply("integer a = 1; integer b = 2;\n", shared.fixes.front()).value_or("") == std::string("integer b = 2;\n")); // The last on its line takes the space before it instead. ALScriptProblem last; ALScriptFixes::offerRemoval(last, "integer a = 1; integer b = 2;\n", 0, 23, 0, 28, "b"); + REQUIRE_FALSE(last.fixes.empty()); UNSCOPED_INFO("and the space before it"); CHECK(ALScriptFixes::apply("integer a = 1; integer b = 2;\n", last.fixes.front()).value_or("") == std::string("integer a = 1;\n")); @@ -562,7 +577,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: what the optimizer did is offered as a change to the source", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string script = "default\n{\n state_entry()\n {\n llOwnerSay((string)(1 + 2));\n return;\n" " llOwnerSay(\"never\");\n }\n}\n"; @@ -603,7 +618,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL expression chosen goes in a local before its statement where safe", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string head = "default\n{\n state_entry()\n {\n"; const std::string tail = " }\n}\n"; @@ -624,25 +639,32 @@ namespace // Not out of a loop's condition, nor from under an if with no // braces, nor what is assigned to. std::string none = head + " integer i;\n while (i < 10) ++i;\n" + tail; - UNSCOPED_INFO("a condition: " + require_acted_on(none, false, "i < 10", true, "Put it in a local, 'value'")); - CHECK(require_acted_on(none, false, "i < 10", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + made = require_acted_on(none, false, "i < 10", true, "Put it in a local, 'value'"); + UNSCOPED_INFO("a condition: " + made); + CHECK(made.rfind("offered", 0) == 0); none = head + " integer i;\n if (i) llOwnerSay((string)(i + 1));\n" + tail; - UNSCOPED_INFO("under an if"); - CHECK(require_acted_on(none, false, "i + 1", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + { + INFO("under an if"); + CHECK(require_acted_on(none, false, "i + 1", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } none = head + " integer i;\n i = 4;\n llOwnerSay((string)i);\n" + tail; S32 line = 0, column = 0, end = 0; require_place(none, "i = 4", line, column, end); - UNSCOPED_INFO("assigned to"); - CHECK(require_acted(none, false, line, column, line, column + 1, "Put it in a local, 'value'").rfind("offered", 0) == 0); + { + INFO("assigned to"); + CHECK(require_acted(none, false, line, column, line, column + 1, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } // Nor the whole of a statement, which would leave `value;`. none = head + " llOwnerSay((string)llGetUnixTime());\n" + tail; - UNSCOPED_INFO("the whole statement"); - CHECK(require_acted_on(none, false, "llOwnerSay((string)llGetUnixTime())", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + { + INFO("the whole statement"); + CHECK(require_acted_on(none, false, "llOwnerSay((string)llGetUnixTime())", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL if-else is inverted: the condition turned and the branches swapped", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string head = "default\n{\n touch_start(integer n)\n {\n"; const std::string tail = " }\n}\n"; @@ -660,11 +682,15 @@ namespace UNSCOPED_INFO("an order under a not"); CHECK(made == head + " if (!(n > 2)) llOwnerSay(\"few\"); else llOwnerSay(\"many\");\n" + tail); const std::string chain = head + " if (n == 1) llOwnerSay(\"one\"); else if (n == 2) llOwnerSay(\"two\");\n" + tail; - UNSCOPED_INFO("not with an else if"); - CHECK(require_acted_on(chain, false, "if", false, "Invert the if").rfind("offered", 0) == 0); + { + INFO("not with an else if"); + CHECK(require_acted_on(chain, false, "if", false, "Invert the if").rfind("offered", 0) == 0); + } const std::string lone = head + " if (n == 1) llOwnerSay(\"one\");\n" + tail; - UNSCOPED_INFO("not without an else"); - CHECK(require_acted_on(lone, false, "if", false, "Invert the if").rfind("offered", 0) == 0); + { + INFO("not without an else"); + CHECK(require_acted_on(lone, false, "if", false, "Invert the if").rfind("offered", 0) == 0); + } // An else branch ending in an if of its own goes first in braces, // or the else would come to be that if's. made = require_acted_on(head + " if (n) llOwnerSay(\"a\"); else while (n--) if (n == 3) llOwnerSay(\"c\");\n" + tail, false, "if", false, @@ -675,7 +701,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL state asking for an event it does not hear is offered a handler", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string script = "default\n{\n state_entry()\n {\n llListen(0, \"\", NULL_KEY, \"\");\n llSetTimerEvent(1.0);\n" " }\n}\n"; @@ -706,7 +732,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a SLua expression chosen goes in a local where it surely runs as before", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); std::string made = require_acted_on("local function f(n: number)\n print(n * 2 + 1)\nend\nf(1)\n", true, "n * 2", true, "Put it in a local, 'value'"); UNSCOPED_INFO("before the statement"); @@ -715,43 +741,64 @@ namespace UNSCOPED_INFO("beside"); CHECK(made == "local x = 1; local value = x + 1; print(value)\n"); const std::string skipped = "local x = 1\nprint(x > 0 and tostring(x))\n"; - UNSCOPED_INFO("not what and may skip"); - CHECK(require_acted_on(skipped, true, "tostring(x)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + { + INFO("not what and may skip"); + CHECK(require_acted_on(skipped, true, "tostring(x)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } const std::string looped = "local x = 1\nwhile x < 10 do\n x += 1\nend\n"; - UNSCOPED_INFO("not a loop's condition"); - CHECK(require_acted_on(looped, true, "x < 10", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + { + INFO("not a loop's condition"); + CHECK(require_acted_on(looped, true, "x < 10", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } const std::string elseif = "local x = 1\nif x == 0 then\n print(0)\nelseif x + 1 == 2 then\n print(1)\nend\n"; - UNSCOPED_INFO("not an elseif's condition"); - CHECK(require_acted_on(elseif, true, "x + 1", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + { + INFO("not an elseif's condition"); + CHECK(require_acted_on(elseif, true, "x + 1", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } // Not a call that is the whole statement: `value` alone is no // statement. const std::string whole = "print(math.abs(-1))\n"; - UNSCOPED_INFO("not the whole statement"); - CHECK(require_acted_on(whole, true, "print(math.abs(-1))", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - UNSCOPED_INFO("its argument yes: " + require_acted_on(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'")); - CHECK(require_acted_on(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'") == "local value = math.abs(-1)\nprint(value)\n"); + { + INFO("not the whole statement"); + CHECK(require_acted_on(whole, true, "print(math.abs(-1))", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } + made = require_acted_on(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'"); + UNSCOPED_INFO("its argument yes"); + CHECK(made == "local value = math.abs(-1)\nprint(value)\n"); // Not a call where all it gives is kept, since a local keeps one. const std::string two = "local function g(n: number) return n, 2 end\n"; - UNSCOPED_INFO("not a last argument"); - CHECK(require_acted_on(two + "print(g(1))\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - UNSCOPED_INFO("not a last return"); - CHECK(require_acted_on(two + "local function h() return 0, g(1) end\nh()\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - UNSCOPED_INFO("not a table's last item"); - CHECK(require_acted_on(two + "local t = { g(1) }\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - UNSCOPED_INFO("not to two names"); - CHECK(require_acted_on(two + "local a, b = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - UNSCOPED_INFO("not a for-in's values"); - CHECK(require_acted_on("for k, v in pairs({}) do end\n", true, "pairs({})", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + { + INFO("not a last argument"); + CHECK(require_acted_on(two + "print(g(1))\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } + { + INFO("not a last return"); + CHECK(require_acted_on(two + "local function h() return 0, g(1) end\nh()\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } + { + INFO("not a table's last item"); + CHECK(require_acted_on(two + "local t = { g(1) }\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } + { + INFO("not to two names"); + CHECK(require_acted_on(two + "local a, b = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } + { + INFO("not a for-in's values"); + CHECK(require_acted_on("for k, v in pairs({}) do end\n", true, "pairs({})", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + } // Where only the first is kept anyway, yes. - UNSCOPED_INFO("an argument before the last: " + require_acted_on(two + "print(g(1), 3)\n", true, "g(1)", true, "Put it in a local, 'value'")); - CHECK(require_acted_on(two + "print(g(1), 3)\n", true, "g(1)", true, "Put it in a local, 'value'") == two + "local value = g(1)\nprint(value, 3)\n"); - UNSCOPED_INFO("to one name: " + require_acted_on(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'")); - CHECK(require_acted_on(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) != 0); + made = require_acted_on(two + "print(g(1), 3)\n", true, "g(1)", true, "Put it in a local, 'value'"); + UNSCOPED_INFO("an argument before the last"); + CHECK(made == two + "local value = g(1)\nprint(value, 3)\n"); + made = require_acted_on(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'"); + UNSCOPED_INFO("to one name: " + made); + CHECK(made.rfind("offered", 0) != 0); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a SLua if is inverted; a join interpolated; a local given its type", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); std::string made = require_acted_on("local x = 1\nif x == 1 then\n print(\"one\")\nelse\n print(\"other\")\nend\n", true, "if", false, "Invert the if"); CHECK(made == "local x = 1\nif x ~= 1 then\n print(\"other\")\nelse\n print(\"one\")\nend\n"); @@ -768,7 +815,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a SLua script asking for an event it does not hear is offered a handler", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); // Nonstrict, as the grid compiles: the new solver checking strict // cannot yet push an overloaded function's parameter types into a @@ -783,12 +830,16 @@ namespace CHECK(made == script + "\nLLEvents:on(\"listen\", function(channel: number, name: string, id: uuid, msg: string)\nend)\n"); const std::string heard = script + "LLEvents:on(\"listen\", function(channel, name, id, text) end)\n"; - UNSCOPED_INFO("heard: " + titles(actions(heard, true, 0, 3, 0, 3))); - CHECK(require_acted_on(heard, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) == 0); + { + INFO("heard: " + titles(actions(heard, true, 0, 3, 0, 3))); + CHECK(require_acted_on(heard, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) == 0); + } // A handler taken off is no handler put on. const std::string off = script + "local function said(channel, name, id, text) end\nLLEvents:off(\"listen\", said)\n"; - UNSCOPED_INFO("offered where only taken off: " + titles(actions(off, true, 0, 3, 0, 3))); - CHECK(require_acted_on(off, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) != 0); + { + INFO("offered where only taken off: " + titles(actions(off, true, 0, 3, 0, 3))); + CHECK(require_acted_on(off, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) != 0); + } } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a refactor's name is fresh and a line put in maps to its source line", "[ALScriptFixes]") @@ -863,6 +914,7 @@ namespace UNSCOPED_INFO("after the last require"); CHECK(made("--!strict\nlocal a = require(\"a\")\nlocal b = require(\"b\").b\n\nprint(util.x)\n", "util", false) == std::string("--!strict\nlocal a = require(\"a\")\nlocal b = require(\"b\").b\nlocal util = require(\"util\")\n\nprint(util.x)\n")); + REQUIRE_FALSE(problem.fixes.empty()); CHECK(problem.fixes.front().title == std::string("Require 'util'")); UNSCOPED_INFO("after the comments, the blank line kept"); CHECK(made("--!strict\n--[[ A header\n over lines ]]\n\nprint(util.x)\n", "@lib/util", false) == @@ -870,6 +922,7 @@ namespace UNSCOPED_INFO("apart from code that follows at once"); CHECK(made("print(util)\n", "helpers", true) == std::string("local util = require(\"helpers\").util\n\nprint(util)\n")); + REQUIRE_FALSE(problem.fixes.empty()); CHECK(problem.fixes.front().title == std::string("Take 'util' from 'helpers'")); UNSCOPED_INFO("past a last line with no break"); CHECK(made("-- only a comment", "util", false) == @@ -923,6 +976,7 @@ namespace UNSCOPED_INFO("after the last directive"); CHECK(made("// Hello\n#include \"a.lsl\"\n#define LONG \\\n 1\n\ndefault {}\n", "helpers.lsl") == std::string("// Hello\n#include \"a.lsl\"\n#define LONG \\\n 1\n#include \"helpers.lsl\"\n\ndefault {}\n")); + REQUIRE_FALSE(problem.fixes.empty()); CHECK(problem.fixes.front().title == std::string("Include 'helpers.lsl'")); UNSCOPED_INFO("after the comments, the blank line kept"); CHECK(made("/* A header\n over lines */\n\ndefault {}\n", "helpers") == @@ -933,7 +987,7 @@ namespace CHECK(made("default {}\n", "a\"b") == std::string("none")); // What the studio offers them on: a function, a global and a macro // alike, each undeclared by its name. - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const ALScriptProblems problems = check("default\n{\n state_entry()\n {\n greet(\"x\");\n llOwnerSay((string)gCount);\n llSay(CHANNEL, \"\");\n }\n}\n", false); @@ -1009,7 +1063,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a repeated LSL string goes into a global after the others; uses named", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string script = "string gGreeting = \"hello there\";\n" "default\n{\n state_entry()\n {\n llOwnerSay(\"hello there\");\n" @@ -1037,8 +1091,10 @@ namespace } UNSCOPED_INFO("a long one lighter on LSO"); CHECK(ALScriptWeigh::lso(long_made).total < ALScriptWeigh::lso(long_script).total); - UNSCOPED_INFO("not for a string used once"); - CHECK(require_acted_on(script, false, "\"other\"", false, "Put the string in a global, 'gOther', for its 1 uses").rfind("offered", 0) == 0); + { + INFO("not for a string used once"); + CHECK(require_acted_on(script, false, "\"other\"", false, "Put the string in a global, 'gOther', for its 1 uses").rfind("offered", 0) == 0); + } // No globals to follow: ahead of what the script opens with. const std::string bare = "default\n{\n state_entry()\n {\n llOwnerSay(\"hi\\n\");\n llOwnerSay(\"hi\\n\");\n }\n}\n"; @@ -1049,7 +1105,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a list literal is written as a sum bracketed where part of more", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string script = "list gFixed = [1, 2];\n" "default\n{\n state_entry()\n {\n integer n = 5;\n" @@ -1065,14 +1121,16 @@ namespace made = require_acted_on(script, false, "[1, 2]);", false, "Write the list as a sum"); UNSCOPED_INFO("bracketed under a cast: " + made); REQUIRE(made.find("llOwnerSay((string)((list)1 + 2));") != std::string::npos); - UNSCOPED_INFO("not a global's value, which must be written out"); - CHECK(require_acted_on(script, false, "[1, 2];", false, "Write the list as a sum").rfind("offered", 0) == 0); + { + INFO("not a global's value, which must be written out"); + CHECK(require_acted_on(script, false, "[1, 2];", false, "Write the list as a sum").rfind("offered", 0) == 0); + } } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a fix that takes code out says so and one that only renames does not", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); const std::string state = "default\n{\n state_entry()\n {\n llOwnerSay(\"x\");\n }\n}\n"; const ALScriptProblems lsl = check("integer helper(integer x)\n{\n return x;\n}\n" + state, false); @@ -1094,9 +1152,9 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: short names guess one edit away; a swap is one edit; ties not preferred", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); const std::string head = "default\n{\n state_entry()\n {\n"; const std::string tail = " }\n}\n"; @@ -1221,18 +1279,26 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: LSL's operators and zero indexes in SLua fixed; only ~= is safe", "[ALScriptFixes]") { - UNSCOPED_INFO("~="); - CHECK(require_fixed("local a, b = 1, 2\nif a != b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '!=' to '~='") == - std::string("local a, b = 1, 2\nif a ~= b then print(1) end\n")); - UNSCOPED_INFO("and, apart from what it joins"); - CHECK(require_fixed("local a, b = true, false\nif a&&b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '&&' to 'and'") == - std::string("local a, b = true, false\nif a and b then print(1) end\n")); - UNSCOPED_INFO("or"); - CHECK(require_fixed("local a, b = true, false\nif a || b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '||' to 'or'") == - std::string("local a, b = true, false\nif a or b then print(1) end\n")); - UNSCOPED_INFO("not, apart from its operand"); - CHECK(require_fixed("local a = true\nif(!a) then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '!' to 'not'") == - std::string("local a = true\nif(not a) then print(1) end\n")); + { + INFO("~="); + CHECK(require_fixed("local a, b = 1, 2\nif a != b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '!=' to '~='") == + std::string("local a, b = 1, 2\nif a ~= b then print(1) end\n")); + } + { + INFO("and, apart from what it joins"); + CHECK(require_fixed("local a, b = true, false\nif a&&b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '&&' to 'and'") == + std::string("local a, b = true, false\nif a and b then print(1) end\n")); + } + { + INFO("or"); + CHECK(require_fixed("local a, b = true, false\nif a || b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '||' to 'or'") == + std::string("local a, b = true, false\nif a or b then print(1) end\n")); + } + { + INFO("not, apart from its operand"); + CHECK(require_fixed("local a = true\nif(!a) then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '!' to 'not'") == + std::string("local a = true\nif(not a) then print(1) end\n")); + } const ALScriptProblems compared = check("local a, b = 1, 2\nif a != b then print(1) end\n", true); const ALScriptProblems denied = check("local a = true\nif !a then print(1) end\n", true); const ALScriptProblem* unequal = keyed(compared, "LuauUnexpectedDidYouMean"); @@ -1245,40 +1311,57 @@ namespace { INFO("not unsafe: 0 is true in SLua"); REQUIRE(negated); + REQUIRE_FALSE(negated->fixes.empty()); CHECK_FALSE(negated->fixes.front().safe); } - UNSCOPED_INFO("an index from 0"); - CHECK(require_fixed("local t = {}\ntable.insert(t, 0, 5)\n", true, "LuauLintTableInsertZero", "Change '0' to '1'") == - std::string("local t = {}\ntable.insert(t, 1, 5)\n")); - UNSCOPED_INFO("a loop from 0"); - CHECK(require_fixed("local t = {1, 2}\nfor i = 0, #t do print(t[i]) end\n", true, "LuauLintForRangeZero", "Change '0' to '1'") == - std::string("local t = {1, 2}\nfor i = 1, #t do print(t[i]) end\n")); + { + INFO("an index from 0"); + CHECK(require_fixed("local t = {}\ntable.insert(t, 0, 5)\n", true, "LuauLintTableInsertZero", "Change '0' to '1'") == + std::string("local t = {}\ntable.insert(t, 1, 5)\n")); + } + { + INFO("a loop from 0"); + CHECK(require_fixed("local t = {1, 2}\nfor i = 0, #t do print(t[i]) end\n", true, "LuauLintForRangeZero", "Change '0' to '1'") == + std::string("local t = {1, 2}\nfor i = 1, #t do print(t[i]) end\n")); + } } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: LSL fixes: an if's ';' and event parameters and untyped ones and braces", "[ALScriptFixes]") { - UNSCOPED_INFO("the if's ';' out"); - CHECK(require_fixed("default { state_entry() { integer x = llGetUnixTime(); if (x); llOwnerSay(\"a\"); } }", false, "LSLEmptyIf", - "Take out the ';' that ends the if") == - std::string("default { state_entry() { integer x = llGetUnixTime(); if (x) llOwnerSay(\"a\"); } }")); - UNSCOPED_INFO("a wrong type, the name kept"); - CHECK(require_fixed("default { touch_start(string n) { llOwnerSay((string)n); } }", false, "LSLArgumentWrongTypeEvent", - "Write the parameters 'touch_start' takes") == - std::string("default { touch_start(integer n) { llOwnerSay((string)n); } }")); - UNSCOPED_INFO("too few, the builtins' names"); - CHECK(require_fixed("default { touch_start() { } }", false, "LSLTooFewArgumentsEvent", "Write the parameters 'touch_start' takes") == - std::string("default { touch_start(integer num_detected) { } }")); - UNSCOPED_INFO("too many"); - CHECK(require_fixed("default { touch_start(integer n, integer m) { } }", false, "LSLTooManyArgumentsEvent", "Write the parameters 'touch_start' takes") == - std::string("default { touch_start(integer n) { } }")); - UNSCOPED_INFO("an event's with no type"); - CHECK(require_fixed("default { touch_start(n) { } }", false, "LSLParameterUntyped", "Write the parameters 'touch_start' takes") == - std::string("default { touch_start(integer n) { } }")); - UNSCOPED_INFO("a declaration as an if's body, braced"); - CHECK(require_fixed("default { state_entry() { if (llGetUnixTime()) integer i = 1; } }", false, "LSLDeclarationInvalidHere", - "Put it in braces of its own") == - std::string("default { state_entry() { if (llGetUnixTime()) { integer i = 1; } } }")); + { + INFO("the if's ';' out"); + CHECK(require_fixed("default { state_entry() { integer x = llGetUnixTime(); if (x); llOwnerSay(\"a\"); } }", false, "LSLEmptyIf", + "Take out the ';' that ends the if") == + std::string("default { state_entry() { integer x = llGetUnixTime(); if (x) llOwnerSay(\"a\"); } }")); + } + { + INFO("a wrong type, the name kept"); + CHECK(require_fixed("default { touch_start(string n) { llOwnerSay((string)n); } }", false, "LSLArgumentWrongTypeEvent", + "Write the parameters 'touch_start' takes") == + std::string("default { touch_start(integer n) { llOwnerSay((string)n); } }")); + } + { + INFO("too few, the builtins' names"); + CHECK(require_fixed("default { touch_start() { } }", false, "LSLTooFewArgumentsEvent", "Write the parameters 'touch_start' takes") == + std::string("default { touch_start(integer num_detected) { } }")); + } + { + INFO("too many"); + CHECK(require_fixed("default { touch_start(integer n, integer m) { } }", false, "LSLTooManyArgumentsEvent", "Write the parameters 'touch_start' takes") == + std::string("default { touch_start(integer n) { } }")); + } + { + INFO("an event's with no type"); + CHECK(require_fixed("default { touch_start(n) { } }", false, "LSLParameterUntyped", "Write the parameters 'touch_start' takes") == + std::string("default { touch_start(integer n) { } }")); + } + { + INFO("a declaration as an if's body, braced"); + CHECK(require_fixed("default { state_entry() { if (llGetUnixTime()) integer i = 1; } }", false, "LSLDeclarationInvalidHere", + "Put it in braces of its own") == + std::string("default { state_entry() { if (llGetUnixTime()) { integer i = 1; } } }")); + } const std::string function = "say(x) { llOwnerSay((string)x); }\ndefault { state_entry() { say(1); } }"; const ALScriptProblems untyped = check(function, false); @@ -1309,7 +1392,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a handler's missing brace goes on its own line level with the next one", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string script = "default\n{\n state_entry()\n {\n llSay(0, \"Hello, Avatar!\");\n\n" " touch_start(integer total_number)\n {\n llSay(0, \"Touched.\");\n }\n}\n"; @@ -1321,7 +1404,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlCompoundAssign's fix writes x op= y safely; a field and a join too", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); const std::string made = require_fixed("local x = 1\nx = x + 2 * x\nprint(x)\n", true, "LuauLintSlCompoundAssign", "Write it x += ..."); CHECK(made == std::string("local x = 1\nx += 2 * x\nprint(x)\n")); @@ -1329,15 +1412,17 @@ namespace const ALScriptProblem* problem = keyed(problems, "LuauLintSlCompoundAssign"); REQUIRE(problem); CHECK(problem->fixes.front().safe); - UNSCOPED_INFO("a field, joined"); - CHECK(require_fixed("local t = { n = \"a\" }\nt.n = t.n ..\"b\" .. \"c\"\nprint(t.n)\n", true, "LuauLintSlCompoundAssign", - "Write it t.n ..= ...") == - std::string("local t = { n = \"a\" }\nt.n ..= \"b\" .. \"c\"\nprint(t.n)\n")); + { + INFO("a field, joined"); + CHECK(require_fixed("local t = { n = \"a\" }\nt.n = t.n ..\"b\" .. \"c\"\nprint(t.n)\n", true, "LuauLintSlCompoundAssign", + "Write it t.n ..= ...") == + std::string("local t = { n = \"a\" }\nt.n ..= \"b\" .. \"c\"\nprint(t.n)\n")); + } } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlLoopInvariantCall's fix: the call in a fresh local before the loop", "[ALScriptFixes]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string script = "default { state_entry() {\n" " list l = [1, 2]; integer length = 3; integer i;\n" @@ -1367,18 +1452,24 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlNumberTruth's fixes: n ~= 0 bracketed where needed and not n as n == 0", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); - UNSCOPED_INFO("compared"); - CHECK(require_fixed("local n = 0\nif n then print(n) end\n", true, "LuauLintSlNumberTruth", "Compare it: n ~= 0") == - std::string("local n = 0\nif n ~= 0 then print(n) end\n")); - UNSCOPED_INFO("bracketed"); - CHECK(require_fixed("local c = true\nif (if c then 1 else 2) then print(c) end\n", true, "LuauLintSlNumberTruth", - "Compare it: if c then 1 else 2 ~= 0") == - std::string("local c = true\nif ((if c then 1 else 2) ~= 0) then print(c) end\n")); - UNSCOPED_INFO("not"); - CHECK(require_fixed("local n = 0\nprint(not n)\n", true, "LuauLintSlNumberTruthNot", "Ask whether it is 0: n == 0") == - std::string("local n = 0\nprint(n == 0)\n")); + { + INFO("compared"); + CHECK(require_fixed("local n = 0\nif n then print(n) end\n", true, "LuauLintSlNumberTruth", "Compare it: n ~= 0") == + std::string("local n = 0\nif n ~= 0 then print(n) end\n")); + } + { + INFO("bracketed"); + CHECK(require_fixed("local c = true\nif (if c then 1 else 2) then print(c) end\n", true, "LuauLintSlNumberTruth", + "Compare it: if c then 1 else 2 ~= 0") == + std::string("local c = true\nif ((if c then 1 else 2) ~= 0) then print(c) end\n")); + } + { + INFO("not"); + CHECK(require_fixed("local n = 0\nprint(not n)\n", true, "LuauLintSlNumberTruthNot", "Ask whether it is 0: n == 0") == + std::string("local n = 0\nprint(n == 0)\n")); + } const ALScriptProblems problems = check("local n = 0\nif n then print(n) end\n", true); const ALScriptProblem* problem = keyed(problems, "LuauLintSlNumberTruth"); REQUIRE(problem); @@ -1387,27 +1478,37 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlNilSentinel's fixes: nil for SLua's finds and -1 for llcompat's; safe", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); - UNSCOPED_INFO("never"); - CHECK(require_fixed("local l = {1}\nif ll.ListFindList(l, {2}) == -1 then print(l) end\n", true, "LuauLintSlNilSentinel", - "Write it ll.ListFindList(...) == nil") == - std::string("local l = {1}\nif ll.ListFindList(l, {2}) == nil then print(l) end\n")); - UNSCOPED_INFO("on the right"); - CHECK(require_fixed("local i = table.find({1}, 1)\nprint(-1 ~= i)\n", true, "LuauLintSlNilSentinelAlways", "Write it i ~= nil") == - std::string("local i = table.find({1}, 1)\nprint(i ~= nil)\n")); - UNSCOPED_INFO("ordered, bracketed"); - CHECK(require_fixed("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint((i) < 0)\n", true, "LuauLintSlNilSentinelOrder", - "Write it i == nil") == - std::string("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint((i) == nil)\n")); - UNSCOPED_INFO("ordered, on the right"); - CHECK(require_fixed("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint(0 <= i)\n", true, - "LuauLintSlNilSentinelOrder", "Write it i ~= nil") == - std::string("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint(i ~= nil)\n")); - UNSCOPED_INFO("llcompat's"); - CHECK(require_fixed("print(llcompat.ListFindList({1}, {1}) ~= nil)\n", true, "LuauLintSlNilSentinelAlways", - "Write it llcompat.ListFindList(...) ~= -1") == - std::string("print(llcompat.ListFindList({1}, {1}) ~= -1)\n")); + { + INFO("never"); + CHECK(require_fixed("local l = {1}\nif ll.ListFindList(l, {2}) == -1 then print(l) end\n", true, "LuauLintSlNilSentinel", + "Write it ll.ListFindList(...) == nil") == + std::string("local l = {1}\nif ll.ListFindList(l, {2}) == nil then print(l) end\n")); + } + { + INFO("on the right"); + CHECK(require_fixed("local i = table.find({1}, 1)\nprint(-1 ~= i)\n", true, "LuauLintSlNilSentinelAlways", "Write it i ~= nil") == + std::string("local i = table.find({1}, 1)\nprint(i ~= nil)\n")); + } + { + INFO("ordered, bracketed"); + CHECK(require_fixed("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint((i) < 0)\n", true, "LuauLintSlNilSentinelOrder", + "Write it i == nil") == + std::string("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint((i) == nil)\n")); + } + { + INFO("ordered, on the right"); + CHECK(require_fixed("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint(0 <= i)\n", true, + "LuauLintSlNilSentinelOrder", "Write it i ~= nil") == + std::string("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint(i ~= nil)\n")); + } + { + INFO("llcompat's"); + CHECK(require_fixed("print(llcompat.ListFindList({1}, {1}) ~= nil)\n", true, "LuauLintSlNilSentinelAlways", + "Write it llcompat.ListFindList(...) ~= -1") == + std::string("print(llcompat.ListFindList({1}, {1}) ~= -1)\n")); + } const ALScriptProblems problems = check("print(table.find({1}, 1) == -1)\n", true); const ALScriptProblem* problem = keyed(problems, "LuauLintSlNilSentinel"); REQUIRE(problem); @@ -1417,12 +1518,14 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated ll function is renamed only where the arguments match", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); - UNSCOPED_INFO("the same arguments"); - CHECK(require_fixed("ll.SetLinkPrimitiveParams(LINK_THIS, {})\n", true, "LuauLintDeprecatedMemberUse", - "Use 'll.SetLinkPrimitiveParamsFast' instead of 'll.SetLinkPrimitiveParams'") == - std::string("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {})\n")); + { + INFO("the same arguments"); + CHECK(require_fixed("ll.SetLinkPrimitiveParams(LINK_THIS, {})\n", true, "LuauLintDeprecatedMemberUse", + "Use 'll.SetLinkPrimitiveParamsFast' instead of 'll.SetLinkPrimitiveParams'") == + std::string("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {})\n")); + } for (const char* call : { "ll.SetPrimitiveParams({})\n", "ll.MakeFire(1, 1, 1, 1, 1, \"\", vector(0, 0, 0))\n" }) { const ALScriptProblems problems = check(call, true); @@ -1460,7 +1563,7 @@ namespace TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an unread require is offered for removal neither safe nor preferred", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); // The fixture's statics named by its class: MSVC finds none of them // from a lambda, even one holding `this`. @@ -1500,13 +1603,15 @@ namespace UNSCOPED_INFO("the statement alone gone"); CHECK(ALScriptFixes::apply(bare, *fix).value_or("(not made)") == std::string("print(1)\n")); - UNSCOPED_INFO("a module named by an expression: nothing to name"); - REQUIRE(require_removal("local name = 'util'\nlocal util = require(name)\n") == nullptr); + { + INFO("a module named by an expression: nothing to name"); + REQUIRE(require_removal("local name = 'util'\nlocal util = require(name)\n") == nullptr); + } } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated call with other arguments is rewritten as a call that works", "[ALScriptFixes]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(luauLoaded); // The fixture's statics named by its class: MSVC finds none of them // from a lambda, even one holding `this`. @@ -1542,24 +1647,36 @@ namespace return fix.title + "\n" + made; }; const std::string vectors = "local a, b = vector(1, 0, 0), vector(0, 1, 0)\n"; - UNSCOPED_INFO("a distance"); - CHECK(require_written(vectors + "print(ll.VecDist(a, ll.GetPos()))\n") == - "Write it vector.magnitude(a - ll.GetPos())\n" + vectors + "print(vector.magnitude(a - ll.GetPos()))\n"); - UNSCOPED_INFO("bracketed"); - CHECK(require_written(vectors + "print(ll.VecDist(a + b, b))\n") == - "Write it vector.magnitude((a + b) - b)\n" + vectors + "print(vector.magnitude((a + b) - b))\n"); - UNSCOPED_INFO("half up"); - CHECK(require_written("local n = 2\nprint(ll.Round(n * 0.25))\n") == - std::string("Write it math.floor((n * 0.25) + 0.5)\nlocal n = 2\nprint(math.floor((n * 0.25) + 0.5))\n")); - UNSCOPED_INFO("a link first"); - CHECK(require_written("ll.SetPrimitiveParams({PRIM_SIZE, vector(1, 1, 1)})\n") == - std::string("Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_SIZE, vector(1, 1, 1)})\n" - "ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_SIZE, vector(1, 1, 1)})\n")); - UNSCOPED_INFO("a length"); - CHECK(require_written("local t = {1, 2}\nprint(table.getn(t))\n") == - std::string("Write it #t\nlocal t = {1, 2}\nprint(#t)\n")); - UNSCOPED_INFO("a name"); - CHECK(require_written("print(ll.Abs(-1))\n") == std::string("Use 'math.abs' instead of 'll.Abs'\nprint(math.abs(-1))\n")); + { + INFO("a distance"); + CHECK(require_written(vectors + "print(ll.VecDist(a, ll.GetPos()))\n") == + "Write it vector.magnitude(a - ll.GetPos())\n" + vectors + "print(vector.magnitude(a - ll.GetPos()))\n"); + } + { + INFO("bracketed"); + CHECK(require_written(vectors + "print(ll.VecDist(a + b, b))\n") == + "Write it vector.magnitude((a + b) - b)\n" + vectors + "print(vector.magnitude((a + b) - b))\n"); + } + { + INFO("half up"); + CHECK(require_written("local n = 2\nprint(ll.Round(n * 0.25))\n") == + std::string("Write it math.floor((n * 0.25) + 0.5)\nlocal n = 2\nprint(math.floor((n * 0.25) + 0.5))\n")); + } + { + INFO("a link first"); + CHECK(require_written("ll.SetPrimitiveParams({PRIM_SIZE, vector(1, 1, 1)})\n") == + std::string("Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_SIZE, vector(1, 1, 1)})\n" + "ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_SIZE, vector(1, 1, 1)})\n")); + } + { + INFO("a length"); + CHECK(require_written("local t = {1, 2}\nprint(table.getn(t))\n") == + std::string("Write it #t\nlocal t = {1, 2}\nprint(#t)\n")); + } + { + INFO("a name"); + CHECK(require_written("print(ll.Abs(-1))\n") == std::string("Use 'math.abs' instead of 'll.Abs'\nprint(math.abs(-1))\n")); + } const ALScriptProblem* norm = require_deprecated("print(ll.VecNorm(vector(0, 0, 0)))\n"); { @@ -1579,7 +1696,7 @@ namespace REQUIRE(problem->fixes.empty()); } - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const ALScriptProblems lsl = check("default\n{\n state_entry()\n {\n llMakeFire(1, 1.0, 1.0, 1.0, 1.0, \"\", ZERO_VECTOR);\n }\n}\n", false); const ALScriptProblem* fire = alscriptfixes_data::keyed(lsl, "LSLDeprecatedWithReplacement"); diff --git a/indra/alscript/tests/alscriptlintpass_test.cpp b/indra/alscript/tests/alscriptlintpass_test.cpp index 3fd5938ac1..fecd7c7db5 100644 --- a/indra/alscript/tests/alscriptlintpass_test.cpp +++ b/indra/alscript/tests/alscriptlintpass_test.cpp @@ -42,7 +42,10 @@ namespace { ALLuauService luau; ALLSLService lsl; - std::string error; + // Each load's own: one shared would say the last load's error under + // another's name. + std::string definitionsError; + std::string builtinsError; bool loaded = false; bool lslLoaded = false; // Luau's new type solver, where the run asks for it: CTest runs @@ -54,9 +57,9 @@ namespace llifstream in(std::string(AL_LSL_DEFINITIONS_DIR) + "/secondlife.d.luau", std::ios::binary); std::stringstream text; text << in.rdbuf(); - luau.setNewSolver(newSolver, error); - loaded = luau.loadDefinitions(text.str(), error); - lslLoaded = lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error); + luau.setNewSolver(newSolver, definitionsError); + loaded = luau.loadDefinitions(text.str(), definitionsError); + lslLoaded = lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", builtinsError); } // A rule both languages have is said by both: SLua's, or LSL's. @@ -147,12 +150,10 @@ namespace REQUIRE(problem->fixes.empty()); } }; - // Raised from TUT's fifty: stages C and D of the SLua help plan add a - // test for each rule. TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlTableCompare: a table compared with {}; with items or a string no fix", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local t = {1}\n" "if t == {} then print(1) end\n" @@ -172,18 +173,22 @@ namespace "5 LuauLintSlTableCompareItems|t\n" "7 LuauLintSlTableCompareItems|s\n" "8 LuauLintSlTableCompareAlways|l|next(l) ~= nil\n")); - UNSCOPED_INFO("a list"); - CHECK(require_fixed("local t = {1}\nif t == {} then print(t) end\n", "LuauLintSlTableCompare", "Write it #t == 0", false) == - std::string("local t = {1}\nif #t == 0 then print(t) end\n")); - UNSCOPED_INFO("a table, on the right, bracketed"); - CHECK(require_fixed("local d = {a = 1}\nprint({} ~= (d))\n", "LuauLintSlTableCompareAlways", "Write it next(d) ~= nil", false) == - std::string("local d = {a = 1}\nprint(next(d) ~= nil)\n")); + { + INFO("a list"); + CHECK(require_fixed("local t = {1}\nif t == {} then print(t) end\n", "LuauLintSlTableCompare", "Write it #t == 0", false) == + std::string("local t = {1}\nif #t == 0 then print(t) end\n")); + } + { + INFO("a table, on the right, bracketed"); + CHECK(require_fixed("local d = {a = 1}\nprint({} ~= (d))\n", "LuauLintSlTableCompareAlways", "Write it next(d) ~= nil", false) == + std::string("local d = {a = 1}\nprint(next(d) ~= nil)\n")); + } require_unfixed("local t = {1}\nprint(t == {1})\n", "LuauLintSlTableCompareItems"); } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlZeroIndex: counting from 0 in lists and loops and subs and finds", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local t = {1, 2}\n" "print(t[0])\n" @@ -211,25 +216,37 @@ namespace "7 LuauLintSlZeroIndexArg|ll.GetSubString\n" "8 LuauLintSlZeroIndexFound|ll.SubStringIndex(...)|ll.SubStringIndex\n" "8 LuauLintSlZeroIndexFound|table.find(...)|table.find\n")); - UNSCOPED_INFO("a list"); - CHECK(require_fixed("local t = {1}\nprint(t[0])\n", "LuauLintSlZeroIndex", "Write it t[1]", false) == - std::string("local t = {1}\nprint(t[1])\n")); - UNSCOPED_INFO("a loop"); - CHECK(require_fixed("local t = {1}\nfor i = 0, #t - 1 do print(t[i]) end\n", "LuauLintSlZeroIndexLoop", "Write it for i = 1, #t", false) == - std::string("local t = {1}\nfor i = 1, #t do print(t[i]) end\n")); - UNSCOPED_INFO("a method to a length less 1"); - CHECK(require_fixed("local s = \"abc\"\nprint(s:sub(0, #s - 1))\n", "LuauLintSlZeroIndexSub", "Write it s:sub(1, #s)", false) == - std::string("local s = \"abc\"\nprint(s:sub(1, #s))\n")); - UNSCOPED_INFO("string's, to a number"); - CHECK(require_fixed("print(string.sub(\"abc\", 0, 2))\n", "LuauLintSlZeroIndexSub", "Write it string.sub(\"abc\", 1, 3)", false) == - std::string("print(string.sub(\"abc\", 1, 3))\n")); - UNSCOPED_INFO("ll's, one from the end kept"); - CHECK(require_fixed("print(ll.GetSubString(\"abc\", 0, -1))\n", "LuauLintSlZeroIndexArg", - "Write it ll.GetSubString(\"abc\", 1, -1)", false) == - std::string("print(ll.GetSubString(\"abc\", 1, -1))\n")); - UNSCOPED_INFO("a find"); - CHECK(require_fixed("print(ll.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlZeroIndexFound", "Write it ll.SubStringIndex(...) == 1", false) == - std::string("print(ll.SubStringIndex(\"ab\", \"a\") == 1)\n")); + { + INFO("a list"); + CHECK(require_fixed("local t = {1}\nprint(t[0])\n", "LuauLintSlZeroIndex", "Write it t[1]", false) == + std::string("local t = {1}\nprint(t[1])\n")); + } + { + INFO("a loop"); + CHECK(require_fixed("local t = {1}\nfor i = 0, #t - 1 do print(t[i]) end\n", "LuauLintSlZeroIndexLoop", "Write it for i = 1, #t", false) == + std::string("local t = {1}\nfor i = 1, #t do print(t[i]) end\n")); + } + { + INFO("a method to a length less 1"); + CHECK(require_fixed("local s = \"abc\"\nprint(s:sub(0, #s - 1))\n", "LuauLintSlZeroIndexSub", "Write it s:sub(1, #s)", false) == + std::string("local s = \"abc\"\nprint(s:sub(1, #s))\n")); + } + { + INFO("string's, to a number"); + CHECK(require_fixed("print(string.sub(\"abc\", 0, 2))\n", "LuauLintSlZeroIndexSub", "Write it string.sub(\"abc\", 1, 3)", false) == + std::string("print(string.sub(\"abc\", 1, 3))\n")); + } + { + INFO("ll's, one from the end kept"); + CHECK(require_fixed("print(ll.GetSubString(\"abc\", 0, -1))\n", "LuauLintSlZeroIndexArg", + "Write it ll.GetSubString(\"abc\", 1, -1)", false) == + std::string("print(ll.GetSubString(\"abc\", 1, -1))\n")); + } + { + INFO("a find"); + CHECK(require_fixed("print(ll.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlZeroIndexFound", "Write it ll.SubStringIndex(...) == 1", false) == + std::string("print(ll.SubStringIndex(\"ab\", \"a\") == 1)\n")); + } require_unfixed("local t = {1}\nfor i = 0, #t - 1 do print(i, t[i]) end\n", "LuauLintSlZeroIndexLoop"); require_unfixed("local s = \"abc\"\nlocal n = 2\nprint(s:sub(0, n))\n", "LuauLintSlZeroIndexSub"); require_unfixed("local s = \"abc\"\nprint(ll.GetSubString(s, 0, #s))\n", "LuauLintSlZeroIndexArg"); @@ -237,7 +254,7 @@ namespace TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlCompatCall: llcompat's where ll's means the same; not where it differs", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local s = \"abc\"\n" "llcompat.Say(0, s)\n" @@ -262,29 +279,41 @@ namespace "7 LuauLintSlCompatCall|llcompat.SubStringIndex|ll.SubStringIndex(...) == nil\n" "8 LuauLintSlCompatCall|llcompat.SameGroup|ll.SameGroup(...)\n" "12 LuauLintSlCompatCall|llcompat.List2Json|ll.List2Json(...)\n")); - UNSCOPED_INFO("the same"); - CHECK(require_fixed("llcompat.Say(0, \"hi\")\n", "LuauLintSlCompatCall", "Write it ll.Say(...)", true) == - std::string("ll.Say(0, \"hi\")\n")); - UNSCOPED_INFO("indexes"); - CHECK(require_fixed("print(llcompat.GetSubString(\"abc\", 0, 1))\n", "LuauLintSlCompatCall", "Write it ll.GetSubString(\"abc\", 1, 2)", true) == - std::string("print(ll.GetSubString(\"abc\", 1, 2))\n")); - UNSCOPED_INFO("a boolean on the right"); - CHECK(require_fixed("print(0 == llcompat.SameGroup(ll.GetOwner()))\n", "LuauLintSlCompatCall", "Write it not ll.SameGroup(...)", true) == - std::string("print(not ll.SameGroup(ll.GetOwner()))\n")); - UNSCOPED_INFO("a boolean"); - CHECK(require_fixed("print(llcompat.SameGroup(ll.GetOwner()) ~= 0)\n", "LuauLintSlCompatCall", "Write it ll.SameGroup(...)", true) == - std::string("print(ll.SameGroup(ll.GetOwner()))\n")); - UNSCOPED_INFO("a find, first"); - CHECK(require_fixed("print(llcompat.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) == 1", true) == - std::string("print(ll.SubStringIndex(\"ab\", \"a\") == 1)\n")); - UNSCOPED_INFO("a find, on the right"); - CHECK(require_fixed("print(-1 < llcompat.SubStringIndex(\"ab\", \"a\"))\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) ~= nil", true) == - std::string("print(ll.SubStringIndex(\"ab\", \"a\") ~= nil)\n")); + { + INFO("the same"); + CHECK(require_fixed("llcompat.Say(0, \"hi\")\n", "LuauLintSlCompatCall", "Write it ll.Say(...)", true) == + std::string("ll.Say(0, \"hi\")\n")); + } + { + INFO("indexes"); + CHECK(require_fixed("print(llcompat.GetSubString(\"abc\", 0, 1))\n", "LuauLintSlCompatCall", "Write it ll.GetSubString(\"abc\", 1, 2)", true) == + std::string("print(ll.GetSubString(\"abc\", 1, 2))\n")); + } + { + INFO("a boolean on the right"); + CHECK(require_fixed("print(0 == llcompat.SameGroup(ll.GetOwner()))\n", "LuauLintSlCompatCall", "Write it not ll.SameGroup(...)", true) == + std::string("print(not ll.SameGroup(ll.GetOwner()))\n")); + } + { + INFO("a boolean"); + CHECK(require_fixed("print(llcompat.SameGroup(ll.GetOwner()) ~= 0)\n", "LuauLintSlCompatCall", "Write it ll.SameGroup(...)", true) == + std::string("print(ll.SameGroup(ll.GetOwner()))\n")); + } + { + INFO("a find, first"); + CHECK(require_fixed("print(llcompat.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) == 1", true) == + std::string("print(ll.SubStringIndex(\"ab\", \"a\") == 1)\n")); + } + { + INFO("a find, on the right"); + CHECK(require_fixed("print(-1 < llcompat.SubStringIndex(\"ab\", \"a\"))\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) ~= nil", true) == + std::string("print(ll.SubStringIndex(\"ab\", \"a\") ~= nil)\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlBooleanNumber: a boolean against a number; an if of 1 and 0 a note", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local on = false\n" "local n = 1\n" @@ -300,21 +329,27 @@ namespace "3 LuauLintSlBooleanNumber|(n > 2)|5\n" "4 LuauLintSlBooleanNumberChoice|(if on then 1 else 0) == 1|on (another severity)\n" "4 LuauLintSlBooleanNumberChoice|(if n > 2 then 0 else 1) ~= 0|not (n > 2) (another severity)\n")); - UNSCOPED_INFO("true"); - CHECK(require_fixed("local on = false\nprint(on == 1)\n", "LuauLintSlBooleanNumber", "Write it on", false) == - std::string("local on = false\nprint(on)\n")); - UNSCOPED_INFO("false, on the right"); - CHECK(require_fixed("print(0 == ll.SameGroup(ll.GetOwner()))\n", "LuauLintSlBooleanNumber", "Write it not ll.SameGroup(ll.GetOwner())", false) == - std::string("print(not ll.SameGroup(ll.GetOwner()))\n")); - UNSCOPED_INFO("a choice"); - CHECK(require_fixed("local n = 1\nprint((if n > 2 then 0 else 1) ~= 0)\n", "LuauLintSlBooleanNumberChoice", "Write it not (n > 2)", false) == - std::string("local n = 1\nprint(not (n > 2))\n")); + { + INFO("true"); + CHECK(require_fixed("local on = false\nprint(on == 1)\n", "LuauLintSlBooleanNumber", "Write it on", false) == + std::string("local on = false\nprint(on)\n")); + } + { + INFO("false, on the right"); + CHECK(require_fixed("print(0 == ll.SameGroup(ll.GetOwner()))\n", "LuauLintSlBooleanNumber", "Write it not ll.SameGroup(ll.GetOwner())", false) == + std::string("print(not ll.SameGroup(ll.GetOwner()))\n")); + } + { + INFO("a choice"); + CHECK(require_fixed("local n = 1\nprint((if n > 2 then 0 else 1) ~= 0)\n", "LuauLintSlBooleanNumberChoice", "Write it not (n > 2)", false) == + std::string("local n = 1\nprint(not (n > 2))\n")); + } require_unfixed("local n = 1\nprint((n > 2) == 5)\n", "LuauLintSlBooleanNumber"); } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlGlobalAssign: globals and global functions made local; not with _G", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string script = "count = 0\n" "function bump() count += 1; total = count end\n" @@ -352,27 +387,35 @@ namespace REQUIRE(found("--!nolint SlGlobalFunction\n" + script, "SlGlobalFunction", ALScriptProblem::Severity::Note).empty()); UNSCOPED_INFO("nor the function where SlGlobalAssign alone is off"); CHECK(found("--!nolint SlGlobalAssign\n" + script, "SlGlobalFunction", ALScriptProblem::Severity::Note) == "2 LuauLintSlGlobalFunction|bump\n"); - UNSCOPED_INFO("in place"); - CHECK(require_fixed("count = 0\nprint(count)\n", "LuauLintSlGlobalAssign", "Write it local count", false) == - std::string("local count = 0\nprint(count)\n")); - UNSCOPED_INFO("declared first"); - CHECK(require_fixed("local function early() return later end\nlater = 5\nprint(early())\n", "LuauLintSlGlobalAssign", - "Declare local later before it is first named", false) == - std::string("local later\nlocal function early() return later end\nlater = 5\nprint(early())\n")); - UNSCOPED_INFO("a function"); - CHECK(require_fixed("function f() return 1 end\nprint(f())\n", "LuauLintSlGlobalFunction", "Write it local function f", false) == - std::string("local function f() return 1 end\nprint(f())\n")); - UNSCOPED_INFO("in a block"); - CHECK(require_fixed("local function outer()\n function inner() return 1 end\n return inner()\nend\nprint(outer())\n", - "LuauLintSlGlobalFunctionInScope", "Write it local function inner", false) == - std::string("local function outer()\n local function inner() return 1 end\n return inner()\nend\nprint(outer())\n")); + { + INFO("in place"); + CHECK(require_fixed("count = 0\nprint(count)\n", "LuauLintSlGlobalAssign", "Write it local count", false) == + std::string("local count = 0\nprint(count)\n")); + } + { + INFO("declared first"); + CHECK(require_fixed("local function early() return later end\nlater = 5\nprint(early())\n", "LuauLintSlGlobalAssign", + "Declare local later before it is first named", false) == + std::string("local later\nlocal function early() return later end\nlater = 5\nprint(early())\n")); + } + { + INFO("a function"); + CHECK(require_fixed("function f() return 1 end\nprint(f())\n", "LuauLintSlGlobalFunction", "Write it local function f", false) == + std::string("local function f() return 1 end\nprint(f())\n")); + } + { + INFO("in a block"); + CHECK(require_fixed("local function outer()\n function inner() return 1 end\n return inner()\nend\nprint(outer())\n", + "LuauLintSlGlobalFunctionInScope", "Write it local function inner", false) == + std::string("local function outer()\n local function inner() return 1 end\n return inner()\nend\nprint(outer())\n")); + } require_unfixed("local function outer()\n function shared() return 2 end\nend\nprint(outer, shared)\n", "LuauLintSlGlobalFunctionInScope"); require_unfixed("print(_G)\ny = 2\n", "LuauLintSlGlobalAssign"); } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlParenCondition: a whole condition in brackets; taken out safely", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local x = true\n" "if (x) then print(1) elseif (not x) then print(2) end\n" @@ -388,12 +431,16 @@ namespace "2 LuauLintSlParenCondition|while\n" "3 LuauLintSlParenCondition|until\n" "4 LuauLintSlParenCondition|if\n")); - UNSCOPED_INFO("spaced"); - CHECK(require_fixed("local x = true\nif (x) then print(1) end\n", "LuauLintSlParenCondition", "Take out the brackets", true) == - std::string("local x = true\nif x then print(1) end\n")); - UNSCOPED_INFO("run on"); - CHECK(require_fixed("local x = true\nwhile(x)do break end\n", "LuauLintSlParenCondition", "Take out the brackets", true) == - std::string("local x = true\nwhile x do break end\n")); + { + INFO("spaced"); + CHECK(require_fixed("local x = true\nif (x) then print(1) end\n", "LuauLintSlParenCondition", "Take out the brackets", true) == + std::string("local x = true\nif x then print(1) end\n")); + } + { + INFO("run on"); + CHECK(require_fixed("local x = true\nwhile(x)do break end\n", "LuauLintSlParenCondition", "Take out the brackets", true) == + std::string("local x = true\nwhile x do break end\n")); + } UNSCOPED_INFO("allowed by selene's name"); CHECK(found("local x = true\n-- selene: allow(parenthese_conditions)\nif (x) then print(1) end\n", "SlParenCondition", ALScriptProblem::Severity::Note) == @@ -402,7 +449,7 @@ namespace TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlAlmostSwapped: a = b then b = a is a warning fixed as a swap", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local a, b = 1, 2\n" "a = b\n" @@ -417,14 +464,16 @@ namespace CHECK(said == std::string("1 LuauLintSlAlmostSwapped|a|b|a, b = b, a\n" "4 LuauLintSlAlmostSwapped|t.x|t.y|t.x, t.y = t.y, t.x\n")); - UNSCOPED_INFO("swapped"); - CHECK(require_fixed("local a, b = 1, 2\na = b\nb = a\nprint(a, b)\n", "LuauLintSlAlmostSwapped", "Write it a, b = b, a", false) == - std::string("local a, b = 1, 2\na, b = b, a\nprint(a, b)\n")); + { + INFO("swapped"); + CHECK(require_fixed("local a, b = 1, 2\na = b\nb = a\nprint(a, b)\n", "LuauLintSlAlmostSwapped", "Write it a, b = b, a", false) == + std::string("local a, b = 1, 2\na, b = b, a\nprint(a, b)\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlMustUse: an answer left unread; given back to the first argument", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local l = {1, 2}\n" "local s = \"abc\"\n" @@ -446,19 +495,23 @@ namespace "5 LuauLintSlMustUse|string.len\n" "6 LuauLintSlMustUse|tostring\n" "7 LuauLintSlMustUse|llcompat.GetPos\n")); - UNSCOPED_INFO("a list"); - CHECK(require_fixed("local l = {1, 2}\nll.DeleteSubList(l, 1, 1)\nprint(l)\n", "LuauLintSlMustUse", "Write it l = ll.DeleteSubList(...)", false) == - std::string("local l = {1, 2}\nl = ll.DeleteSubList(l, 1, 1)\nprint(l)\n")); - UNSCOPED_INFO("a method"); - CHECK(require_fixed("local s = \"abc\"\ns:upper()\nprint(s)\n", "LuauLintSlMustUse", "Write it s = s:upper(...)", false) == - std::string("local s = \"abc\"\ns = s:upper()\nprint(s)\n")); + { + INFO("a list"); + CHECK(require_fixed("local l = {1, 2}\nll.DeleteSubList(l, 1, 1)\nprint(l)\n", "LuauLintSlMustUse", "Write it l = ll.DeleteSubList(...)", false) == + std::string("local l = {1, 2}\nl = ll.DeleteSubList(l, 1, 1)\nprint(l)\n")); + } + { + INFO("a method"); + CHECK(require_fixed("local s = \"abc\"\ns:upper()\nprint(s)\n", "LuauLintSlMustUse", "Write it s = s:upper(...)", false) == + std::string("local s = \"abc\"\ns = s:upper()\nprint(s)\n")); + } require_unfixed("local s = \"abc\"\nstring.len(s)\n", "LuauLintSlMustUse"); require_unfixed("string.upper(\"abc\")\n", "LuauLintSlMustUse"); } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlGeneralizedFor: pairs and ipairs and next in a for become the table", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local t = {1, 2}\n" "for k, v in pairs(t) do print(k, v) end\n" @@ -472,17 +525,21 @@ namespace std::string("1 LuauLintSlGeneralizedFor|t|pairs(t)\n" "2 LuauLintSlGeneralizedForList|t|ipairs(t)\n" "3 LuauLintSlGeneralizedFor|t|next, t\n")); - UNSCOPED_INFO("pairs"); - CHECK(require_fixed("local t = {1}\nfor k, v in pairs(t) do print(k, v) end\n", "LuauLintSlGeneralizedFor", "Write it in t", false) == - std::string("local t = {1}\nfor k, v in t do print(k, v) end\n")); - UNSCOPED_INFO("next"); - CHECK(require_fixed("local t = {1}\nfor k, v in next, t do print(k, v) end\n", "LuauLintSlGeneralizedFor", "Write it in t", false) == - std::string("local t = {1}\nfor k, v in t do print(k, v) end\n")); + { + INFO("pairs"); + CHECK(require_fixed("local t = {1}\nfor k, v in pairs(t) do print(k, v) end\n", "LuauLintSlGeneralizedFor", "Write it in t", false) == + std::string("local t = {1}\nfor k, v in t do print(k, v) end\n")); + } + { + INFO("next"); + CHECK(require_fixed("local t = {1}\nfor k, v in next, t do print(k, v) end\n", "LuauLintSlGeneralizedFor", "Write it in t", false) == + std::string("local t = {1}\nfor k, v in t do print(k, v) end\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlEmptyBlock: an empty block is a warning; an empty then or else fixed", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local c = true\n" "if c then end\n" @@ -506,15 +563,21 @@ namespace "6 LuauLintSlEmptyBlock|for\n" "7 LuauLintSlEmptyBlock|for\n" "8 LuauLintSlEmptyBlock|repeat\n")); - UNSCOPED_INFO("turned round"); - CHECK(require_fixed("local c = true\nif c then else print(1) end\n", "LuauLintSlEmptyBlock", "Write it if not c then", false) == - std::string("local c = true\nif not c then print(1) end\n")); - UNSCOPED_INFO("an else"); - CHECK(require_fixed("local c = true\nif c then print(3) else end\n", "LuauLintSlEmptyBlock", "Take out the empty else", true) == - std::string("local c = true\nif c then print(3) end\n")); - UNSCOPED_INFO("an else on its own line"); - CHECK(require_fixed("local c = true\nif c then\n print(3)\nelse\nend\n", "LuauLintSlEmptyBlock", "Take out the empty else", true) == - std::string("local c = true\nif c then\n print(3)\nend\n")); + { + INFO("turned round"); + CHECK(require_fixed("local c = true\nif c then else print(1) end\n", "LuauLintSlEmptyBlock", "Write it if not c then", false) == + std::string("local c = true\nif not c then print(1) end\n")); + } + { + INFO("an else"); + CHECK(require_fixed("local c = true\nif c then print(3) else end\n", "LuauLintSlEmptyBlock", "Take out the empty else", true) == + std::string("local c = true\nif c then print(3) end\n")); + } + { + INFO("an else on its own line"); + CHECK(require_fixed("local c = true\nif c then\n print(3)\nelse\nend\n", "LuauLintSlEmptyBlock", "Take out the empty else", true) == + std::string("local c = true\nif c then\n print(3)\nend\n")); + } UNSCOPED_INFO("selene's names"); CHECK(found("local c = true\n-- selene: allow(empty_loop)\nwhile c do end\n-- selene: allow(empty_if)\nif c then end\n", "SlEmptyBlock", ALScriptProblem::Severity::Warning) == @@ -523,7 +586,7 @@ namespace TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlIndexDivision: a / in a list's index is a warning fixed as //", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local t = {1, 2, 3}\n" "local n = #t\n" @@ -537,14 +600,16 @@ namespace "2 LuauLintSlIndexDivision|(n + 1) / 2|(n + 1) // 2\n" "2 LuauLintSlIndexDivision|n / 2|n // 2\n" "3 LuauLintSlIndexDivision|n / 2|n // 2\n")); - UNSCOPED_INFO("rounded down"); - CHECK(require_fixed("local t = {1}\nlocal n = #t\nprint(t[n / 2])\n", "LuauLintSlIndexDivision", "Write it n // 2", false) == - std::string("local t = {1}\nlocal n = #t\nprint(t[n // 2])\n")); + { + INFO("rounded down"); + CHECK(require_fixed("local t = {1}\nlocal n = #t\nprint(t[n / 2])\n", "LuauLintSlIndexDivision", "Write it n // 2", false) == + std::string("local t = {1}\nlocal n = #t\nprint(t[n // 2])\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlVectorProduct: vectors' * as a number is vector.dot; % is vector.cross", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local a = vector(1, 0, 0)\n" "local b = vector(0, 1, 0)\n" @@ -562,17 +627,21 @@ namespace UNSCOPED_INFO("a note"); CHECK(found("local a = vector(1, 0, 0)\nlocal c = a % a\nprint(c)\n", "SlVectorProduct", ALScriptProblem::Severity::Note) == std::string("1 LuauLintSlVectorCross|a % a|a|a\n")); - UNSCOPED_INFO("dot"); - CHECK(require_fixed("local a = vector(1, 0, 0)\nprint(a * a > 0.5)\n", "LuauLintSlVectorProduct", "Write it vector.dot(a, a)", false) == - std::string("local a = vector(1, 0, 0)\nprint(vector.dot(a, a) > 0.5)\n")); - UNSCOPED_INFO("cross"); - CHECK(require_fixed("local a = vector(1, 0, 0)\nlocal c = a % a\nprint(c)\n", "LuauLintSlVectorCross", "Write it vector.cross(a, a)", false) == - std::string("local a = vector(1, 0, 0)\nlocal c = vector.cross(a, a)\nprint(c)\n")); + { + INFO("dot"); + CHECK(require_fixed("local a = vector(1, 0, 0)\nprint(a * a > 0.5)\n", "LuauLintSlVectorProduct", "Write it vector.dot(a, a)", false) == + std::string("local a = vector(1, 0, 0)\nprint(vector.dot(a, a) > 0.5)\n")); + } + { + INFO("cross"); + CHECK(require_fixed("local a = vector(1, 0, 0)\nlocal c = a % a\nprint(c)\n", "LuauLintSlVectorCross", "Write it vector.cross(a, a)", false) == + std::string("local a = vector(1, 0, 0)\nlocal c = vector.cross(a, a)\nprint(c)\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlSleepingCall in SLua: a sleeping call warned in loops; fixed as Fast", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local v = vector(1, 2, 3)\n" "ll.SetPos(v)\n" @@ -590,25 +659,31 @@ namespace "4 LuauLintSlSleepingTexture|ll.SetTexture|0.2|PRIM_TEXTURE\n" "5 LuauLintSlSleepingCall|ll.SetLinkRenderMaterial|0.2|ll.SetLinkPrimitiveParamsFast\n" "7 LuauLintSlSleepingCall|llcompat.SetPos|0.2|llcompat.SetLinkPrimitiveParamsFast\n")); - UNSCOPED_INFO("put in"); - CHECK(require_fixed("local v = vector(1, 2, 3)\nll.SetPos(v)\n", "LuauLintSlSleepingCall", - "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_POSITION, v})", false) == - std::string("local v = vector(1, 2, 3)\nll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_POSITION, v})\n")); - UNSCOPED_INFO("a link first, in a timer"); - CHECK(require_fixed("LLTimers:every(1, function() ll.SetPrimitiveParams({}) end)\n", "LuauLintSlSleepingCallOften", - "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {})", false) == - std::string("LLTimers:every(1, function() ll.SetLinkPrimitiveParamsFast(LINK_THIS, {}) end)\n")); - UNSCOPED_INFO("written again"); - CHECK(require_fixed("ll.SetLinkRenderMaterial(LINK_THIS, \"m\", 0)\n", "LuauLintSlSleepingCall", - "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_RENDER_MATERIAL, 0, \"m\"})", false) == - std::string("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_RENDER_MATERIAL, 0, \"m\"})\n")); + { + INFO("put in"); + CHECK(require_fixed("local v = vector(1, 2, 3)\nll.SetPos(v)\n", "LuauLintSlSleepingCall", + "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_POSITION, v})", false) == + std::string("local v = vector(1, 2, 3)\nll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_POSITION, v})\n")); + } + { + INFO("a link first, in a timer"); + CHECK(require_fixed("LLTimers:every(1, function() ll.SetPrimitiveParams({}) end)\n", "LuauLintSlSleepingCallOften", + "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {})", false) == + std::string("LLTimers:every(1, function() ll.SetLinkPrimitiveParamsFast(LINK_THIS, {}) end)\n")); + } + { + INFO("written again"); + CHECK(require_fixed("ll.SetLinkRenderMaterial(LINK_THIS, \"m\", 0)\n", "LuauLintSlSleepingCall", + "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_RENDER_MATERIAL, 0, \"m\"})", false) == + std::string("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_RENDER_MATERIAL, 0, \"m\"})\n")); + } require_unfixed("ll.SetTexture(\"x\", 0)\n", "LuauLintSlSleepingTexture"); require_unfixed("ll.SetRenderMaterial(tostring(1), 0)\n", "LuauLintSlSleepingCall"); } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlSleepingCall in LSL: the same; fixed over commas and comments", "[ALScriptLintPass]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string said = found("default {\n" " state_entry() {\n" @@ -629,20 +704,24 @@ namespace "5 LSLSlSleepingCall|llSetLinkRenderMaterial|0.2|llSetLinkPrimitiveParamsFast\n" "6 LSLSlSleepingTexture|llSetTexture|0.2|PRIM_TEXTURE\n" "9 LSLSlSleepingCallOften|llSetPrimitiveParams|0.2|llSetLinkPrimitiveParamsFast (another severity)\n")); - UNSCOPED_INFO("put in"); - CHECK(require_fixed("default { state_entry() { llSetPos( <1, 2, 3> /* here */ ); } }\n", "LSLSlSleepingCall", - "Write it llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POSITION, <1, 2, 3> /* here */])", false, false) == - std::string("default { state_entry() { llSetLinkPrimitiveParamsFast( LINK_THIS, [PRIM_POSITION, <1, 2, 3> /* here */] ); } }\n")); - UNSCOPED_INFO("written again"); - CHECK(require_fixed("default { state_entry() { llSetLinkRenderMaterial(LINK_THIS, \"m\", ALL_SIDES); } }\n", "LSLSlSleepingCall", - "Write it llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_RENDER_MATERIAL, ALL_SIDES, \"m\"])", false, false) == - std::string("default { state_entry() { llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_RENDER_MATERIAL, ALL_SIDES, \"m\"]); } }\n")); + { + INFO("put in"); + CHECK(require_fixed("default { state_entry() { llSetPos( <1, 2, 3> /* here */ ); } }\n", "LSLSlSleepingCall", + "Write it llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POSITION, <1, 2, 3> /* here */])", false, false) == + std::string("default { state_entry() { llSetLinkPrimitiveParamsFast( LINK_THIS, [PRIM_POSITION, <1, 2, 3> /* here */] ); } }\n")); + } + { + INFO("written again"); + CHECK(require_fixed("default { state_entry() { llSetLinkRenderMaterial(LINK_THIS, \"m\", ALL_SIDES); } }\n", "LSLSlSleepingCall", + "Write it llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_RENDER_MATERIAL, ALL_SIDES, \"m\"])", false, false) == + std::string("default { state_entry() { llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_RENDER_MATERIAL, ALL_SIDES, \"m\"]); } }\n")); + } require_unfixed("default { state_entry() { llSetRenderMaterial(llGetInventoryName(INVENTORY_MATERIAL, 0), 0); } }\n", "LSLSlSleepingCall", false); } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlMergeablePrimParams in SLua: a run of prim-params calls merged", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_COLOR, ALL_SIDES, vector(1, 0, 0), 1})\n" "ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5})\n" @@ -654,25 +733,29 @@ namespace "ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0})\n", "SlMergeablePrimParams", ALScriptProblem::Severity::Note); CHECK(said == std::string("0 LuauLintSlMergeablePrimParams|3|ll.SetLinkPrimitiveParamsFast\n")); - UNSCOPED_INFO("a link changed"); - CHECK(require_fixed("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5})\nll.SetLinkPrimitiveParamsFast(2, {PRIM_GLOW, ALL_SIDES, 0})\n", - "LuauLintSlMergeablePrimParams", - "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 0})", - false) == - std::string("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 0})\n")); - UNSCOPED_INFO("sent elsewhere, then back"); - CHECK(require_fixed("ll.SetLinkPrimitiveParamsFast(1, {PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 1})\n" - "ll.SetLinkPrimitiveParamsFast(1, {PRIM_GLOW, ALL_SIDES, 0})\n", - "LuauLintSlMergeablePrimParams", - "Write it ll.SetLinkPrimitiveParamsFast(1, {PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 1, PRIM_LINK_TARGET, 1, " - "PRIM_GLOW, ALL_SIDES, 0})", - false) == - std::string("ll.SetLinkPrimitiveParamsFast(1, {PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 1, PRIM_LINK_TARGET, 1, PRIM_GLOW, ALL_SIDES, 0})\n")); + { + INFO("a link changed"); + CHECK(require_fixed("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5})\nll.SetLinkPrimitiveParamsFast(2, {PRIM_GLOW, ALL_SIDES, 0})\n", + "LuauLintSlMergeablePrimParams", + "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 0})", + false) == + std::string("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 0})\n")); + } + { + INFO("sent elsewhere, then back"); + CHECK(require_fixed("ll.SetLinkPrimitiveParamsFast(1, {PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 1})\n" + "ll.SetLinkPrimitiveParamsFast(1, {PRIM_GLOW, ALL_SIDES, 0})\n", + "LuauLintSlMergeablePrimParams", + "Write it ll.SetLinkPrimitiveParamsFast(1, {PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 1, PRIM_LINK_TARGET, 1, " + "PRIM_GLOW, ALL_SIDES, 0})", + false) == + std::string("ll.SetLinkPrimitiveParamsFast(1, {PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 1, PRIM_LINK_TARGET, 1, PRIM_GLOW, ALL_SIDES, 0})\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlMergeablePrimParams in LSL: the same over the text keeping the last ;", "[ALScriptLintPass]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const std::string said = found("default {\n" " state_entry() {\n" @@ -684,27 +767,31 @@ namespace "}\n", "SlMergeablePrimParams", ALScriptProblem::Severity::Note, false); CHECK(said == std::string("2 LSLSlMergeablePrimParams|2|llSetLinkPrimitiveParamsFast\n")); - UNSCOPED_INFO("a link changed"); - CHECK(require_fixed("default { state_entry() {\n llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5]);\n" - " llSetLinkPrimitiveParamsFast(2, [ PRIM_GLOW, ALL_SIDES, 0.0 ]);\n} }\n", - "LSLSlMergeablePrimParams", - "Write it llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 0.0])", - false, false) == - std::string("default { state_entry() {\n llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, " - "PRIM_GLOW, ALL_SIDES, 0.0]);\n} }\n")); - UNSCOPED_INFO("no link"); - CHECK(require_fixed("default { state_entry() { llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5]); llSetPrimitiveParams([PRIM_COLOR, " - "ALL_SIDES, <1, 0, 0>, 1.0]); } }\n", - "LSLSlMergeablePrimParams", "Write it llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5, PRIM_COLOR, ALL_SIDES, <1, 0, 0>, 1.0])", - false, false) == - std::string("default { state_entry() { llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5, PRIM_COLOR, ALL_SIDES, <1, 0, 0>, 1.0]); } }\n")); + { + INFO("a link changed"); + CHECK(require_fixed("default { state_entry() {\n llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5]);\n" + " llSetLinkPrimitiveParamsFast(2, [ PRIM_GLOW, ALL_SIDES, 0.0 ]);\n} }\n", + "LSLSlMergeablePrimParams", + "Write it llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 0.0])", + false, false) == + std::string("default { state_entry() {\n llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, " + "PRIM_GLOW, ALL_SIDES, 0.0]);\n} }\n")); + } + { + INFO("no link"); + CHECK(require_fixed("default { state_entry() { llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5]); llSetPrimitiveParams([PRIM_COLOR, " + "ALL_SIDES, <1, 0, 0>, 1.0]); } }\n", + "LSLSlMergeablePrimParams", "Write it llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5, PRIM_COLOR, ALL_SIDES, <1, 0, 0>, 1.0])", + false, false) == + std::string("default { state_entry() { llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5, PRIM_COLOR, ALL_SIDES, <1, 0, 0>, 1.0]); } }\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlCostlyListen and SlFastTimer and SlFastSensor: notes in both languages", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); const auto three = [&](const std::string& script, bool lua) { return found(script, "SlCostlyListen", ALScriptProblem::Severity::Note, lua) + @@ -746,9 +833,9 @@ namespace TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlStringBuild: a string grown in a loop; in SLua its pieces in a table", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); UNSCOPED_INFO("SLua"); CHECK(found("local s = \"\"\n" @@ -766,16 +853,20 @@ namespace "3 LuauLintSlStringBuild|t\n" "5 LuauLintSlStringBuild|g\n" "7 LuauLintSlStringBuild|u\n")); - UNSCOPED_INFO("in a table"); - CHECK(require_fixed("local s = \"\"\nfor i = 1, 3 do s ..= tostring(i) end\nprint(s)\n", "LuauLintSlStringBuild", - "Put s's pieces in a table, joined once after the loop", false) == - std::string("local s = \"\"\nlocal sParts = {}\nfor i = 1, 3 do table.insert(sParts, tostring(i)) end\ns ..= table.concat(sParts)\nprint(s)\n")); - UNSCOPED_INFO("named anew, indented"); - CHECK(require_fixed("local function f()\n local s = \"\"\n local sParts = 1\n for i = 1, 3 do\n s = s .. i\n end\n" - " return s, sParts\nend\nprint(f())\n", - "LuauLintSlStringBuild", "Put s's pieces in a table, joined once after the loop", false) == - std::string("local function f()\n local s = \"\"\n local sParts = 1\n local sParts2 = {}\n for i = 1, 3 do\n" - " table.insert(sParts2, tostring(i))\n end\n s ..= table.concat(sParts2)\n return s, sParts\nend\nprint(f())\n")); + { + INFO("in a table"); + CHECK(require_fixed("local s = \"\"\nfor i = 1, 3 do s ..= tostring(i) end\nprint(s)\n", "LuauLintSlStringBuild", + "Put s's pieces in a table, joined once after the loop", false) == + std::string("local s = \"\"\nlocal sParts = {}\nfor i = 1, 3 do table.insert(sParts, tostring(i)) end\ns ..= table.concat(sParts)\nprint(s)\n")); + } + { + INFO("named anew, indented"); + CHECK(require_fixed("local function f()\n local s = \"\"\n local sParts = 1\n for i = 1, 3 do\n s = s .. i\n end\n" + " return s, sParts\nend\nprint(f())\n", + "LuauLintSlStringBuild", "Put s's pieces in a table, joined once after the loop", false) == + std::string("local function f()\n local s = \"\"\n local sParts = 1\n local sParts2 = {}\n for i = 1, 3 do\n" + " table.insert(sParts2, tostring(i))\n end\n s ..= table.concat(sParts2)\n return s, sParts\nend\nprint(f())\n")); + } require_unfixed("local t = \"\"\nfor i = 1, 3 do t = t .. i; print(t) end\n", "LuauLintSlStringBuild"); require_unfixed("local function f()\n local s = \"\"\n for i = 1, 3 do\n s ..= i\n if i > 1 then return s end\n end\n return s\nend\nprint(f())\n", "LuauLintSlStringBuild"); @@ -800,7 +891,7 @@ namespace TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlRepeatedCall in SLua: a steady call made again kept in a local", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); CHECK(found("local function report()\n" " print(ll.GetOwner())\n" @@ -814,15 +905,17 @@ namespace std::string("1 LuauLintSlRepeatedCall|ll.GetOwner()|3\n" "2 LuauLintSlRepeatedCall|ll.GetKey()|2\n" "6 LuauLintSlRepeatedCall|ll.GetScriptName()|2\n")); - UNSCOPED_INFO("kept"); - CHECK(require_fixed("local function report()\n print(ll.GetOwner())\n print(ll.GetOwner())\nend\nprint(report)\n", - "LuauLintSlRepeatedCall", "Keep ll.GetOwner() in a local, owner", false) == - std::string("local function report()\n local owner = ll.GetOwner()\n print(owner)\n print(owner)\nend\nprint(report)\n")); + { + INFO("kept"); + CHECK(require_fixed("local function report()\n print(ll.GetOwner())\n print(ll.GetOwner())\nend\nprint(report)\n", + "LuauLintSlRepeatedCall", "Keep ll.GetOwner() in a local, owner", false) == + std::string("local function report()\n local owner = ll.GetOwner()\n print(owner)\n print(owner)\nend\nprint(report)\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlRepeatedCall in LSL: steady and pure calls repeated kept in a local", "[ALScriptLintPass]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); CHECK(found("default {\n" " touch_start(integer n) {\n" @@ -836,14 +929,18 @@ namespace "SlRepeatedCall", ALScriptProblem::Severity::Note, false) == std::string("3 LSLSlRepeatedCall|llGetOwner()|2\n" "4 LSLSlRepeatedCall|llToUpper(s)|2\n")); - UNSCOPED_INFO("a steady one"); - CHECK(require_fixed("default { touch_start(integer n) {\n llOwnerSay((string)llGetOwner());\n llOwnerSay((string)llGetOwner());\n} }\n", - "LSLSlRepeatedCall", "Keep llGetOwner() in a local, owner", false, false) == - std::string("default { touch_start(integer n) {\n key owner = llGetOwner();\n llOwnerSay((string)owner);\n llOwnerSay((string)owner);\n} }\n")); - UNSCOPED_INFO("a pure one"); - CHECK(require_fixed("default { state_entry() {\n string s = \"a\";\n llOwnerSay(llToUpper(s) + llToUpper(s));\n} }\n", - "LSLSlRepeatedCall", "Keep llToUpper(s) in a local, toUpper", false, false) == - std::string("default { state_entry() {\n string s = \"a\";\n string toUpper = llToUpper(s);\n llOwnerSay(toUpper + toUpper);\n} }\n")); + { + INFO("a steady one"); + CHECK(require_fixed("default { touch_start(integer n) {\n llOwnerSay((string)llGetOwner());\n llOwnerSay((string)llGetOwner());\n} }\n", + "LSLSlRepeatedCall", "Keep llGetOwner() in a local, owner", false, false) == + std::string("default { touch_start(integer n) {\n key owner = llGetOwner();\n llOwnerSay((string)owner);\n llOwnerSay((string)owner);\n} }\n")); + } + { + INFO("a pure one"); + CHECK(require_fixed("default { state_entry() {\n string s = \"a\";\n llOwnerSay(llToUpper(s) + llToUpper(s));\n} }\n", + "LSLSlRepeatedCall", "Keep llToUpper(s) in a local, toUpper", false, false) == + std::string("default { state_entry() {\n string s = \"a\";\n string toUpper = llToUpper(s);\n llOwnerSay(toUpper + toUpper);\n} }\n")); + } } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: migration: migration lints and notes and llcompat calls left from LSL", "[ALScriptLintPass]") @@ -873,7 +970,7 @@ namespace TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlForIndexAssign: a numeric for's variable set in its body is a warning", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local list = {\"a\", \"a\", \"b\"}\n" "local start = 1\n" @@ -904,7 +1001,7 @@ namespace TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlGlobalAssign: a global set in a function and named elsewhere goes local", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string shared = "function init()\n" " count = 0\n" @@ -917,9 +1014,11 @@ namespace UNSCOPED_INFO("set in one, named in another"); CHECK(found(shared, "SlGlobalAssign", ALScriptProblem::Severity::Note) == std::string("1 LuauLintSlGlobalInFunction|count\n")); - UNSCOPED_INFO("declared before the first that names it"); - CHECK(require_fixed(shared, "LuauLintSlGlobalInFunction", "Declare local count before it is first named", false) == - std::string("local count\n") + shared); + { + INFO("declared before the first that names it"); + CHECK(require_fixed(shared, "LuauLintSlGlobalInFunction", "Declare local count before it is first named", false) == + std::string("local count\n") + shared); + } UNSCOPED_INFO("one function alone: Luau's"); REQUIRE(found("LLEvents:on(\"touch_start\", function(detected)\n total = 1\n print(total)\nend)\n", "SlGlobalAssign", ALScriptProblem::Severity::Note) .find("LuauLintSlGlobalInFunction") == std::string::npos); @@ -939,7 +1038,7 @@ namespace TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlIntegerPast32Bits: an integer literal past 0xFFFFFFFF reads as -1", "[ALScriptLintPass]") { - UNSCOPED_INFO("builtins: " + error); + UNSCOPED_INFO("builtins: " + builtinsError); CHECK(lslLoaded); UNSCOPED_INFO("LSL"); CHECK(found("integer a = 4294967296;\n" @@ -966,7 +1065,7 @@ namespace } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlNumberTruth beyond numbers: other types read as conditions are fixed", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local id = ll.GetOwner()\n" "local v = ll.GetPos()\n" @@ -1003,31 +1102,47 @@ namespace "11 LuauLintSlQuaternionTruthNot|r|r == ZERO_ROTATION\n" "11 LuauLintSlIntegerTruthNot|i|i == 0i\n" "19 LuauLintSlUuidTruth|ll.GetOwner()|ll.GetOwner().istruthy\n")); - UNSCOPED_INFO("a uuid"); - CHECK(require_fixed("local id = ll.GetOwner()\nif id then print(id) end\n", "LuauLintSlUuidTruth", "Write it id.istruthy", false) == - std::string("local id = ll.GetOwner()\nif id.istruthy then print(id) end\n")); - UNSCOPED_INFO("a uuid under not"); - CHECK(require_fixed("if not ll.GetOwner() then print(1) end\n", "LuauLintSlUuidTruthNot", "Write it not ll.GetOwner().istruthy", false) == - std::string("if not ll.GetOwner().istruthy then print(1) end\n")); - UNSCOPED_INFO("a vector"); - CHECK(require_fixed("local v = ll.GetPos()\nwhile v do break end\n", "LuauLintSlVectorTruth", "Write it v ~= ZERO_VECTOR", false) == - std::string("local v = ll.GetPos()\nwhile v ~= ZERO_VECTOR do break end\n")); - UNSCOPED_INFO("a quaternion"); - CHECK(require_fixed("local r = ll.GetRot()\nprint(not r)\n", "LuauLintSlQuaternionTruthNot", "Write it r == ZERO_ROTATION", false) == - std::string("local r = ll.GetRot()\nprint(r == ZERO_ROTATION)\n")); - UNSCOPED_INFO("a string, an if-then-else bracketed first"); - CHECK(require_fixed("local c = true\nif (if c then \"a\" else \"\") then print(c) end\n", - "LuauLintSlStringTruth", "Write it (if c then \"a\" else \"\") ~= \"\"", false) == - std::string("local c = true\nif ((if c then \"a\" else \"\") ~= \"\") then print(c) end\n")); - UNSCOPED_INFO("a list"); - CHECK(require_fixed("local l = {1}\nif l then print(l) end\n", "LuauLintSlListTruth", "Write it #l > 0", false) == - std::string("local l = {1}\nif #l > 0 then print(l) end\n")); - UNSCOPED_INFO("a list under not"); - CHECK(require_fixed("local l = {1}\nprint(not l)\n", "LuauLintSlListTruthNot", "Write it #l == 0", false) == - std::string("local l = {1}\nprint(#l == 0)\n")); - UNSCOPED_INFO("an integer"); - CHECK(require_fixed("local i = 5i\nif i then print(i) end\n", "LuauLintSlIntegerTruth", "Write it i ~= 0i", false) == - std::string("local i = 5i\nif i ~= 0i then print(i) end\n")); + { + INFO("a uuid"); + CHECK(require_fixed("local id = ll.GetOwner()\nif id then print(id) end\n", "LuauLintSlUuidTruth", "Write it id.istruthy", false) == + std::string("local id = ll.GetOwner()\nif id.istruthy then print(id) end\n")); + } + { + INFO("a uuid under not"); + CHECK(require_fixed("if not ll.GetOwner() then print(1) end\n", "LuauLintSlUuidTruthNot", "Write it not ll.GetOwner().istruthy", false) == + std::string("if not ll.GetOwner().istruthy then print(1) end\n")); + } + { + INFO("a vector"); + CHECK(require_fixed("local v = ll.GetPos()\nwhile v do break end\n", "LuauLintSlVectorTruth", "Write it v ~= ZERO_VECTOR", false) == + std::string("local v = ll.GetPos()\nwhile v ~= ZERO_VECTOR do break end\n")); + } + { + INFO("a quaternion"); + CHECK(require_fixed("local r = ll.GetRot()\nprint(not r)\n", "LuauLintSlQuaternionTruthNot", "Write it r == ZERO_ROTATION", false) == + std::string("local r = ll.GetRot()\nprint(r == ZERO_ROTATION)\n")); + } + { + INFO("a string, an if-then-else bracketed first"); + CHECK(require_fixed("local c = true\nif (if c then \"a\" else \"\") then print(c) end\n", + "LuauLintSlStringTruth", "Write it (if c then \"a\" else \"\") ~= \"\"", false) == + std::string("local c = true\nif ((if c then \"a\" else \"\") ~= \"\") then print(c) end\n")); + } + { + INFO("a list"); + CHECK(require_fixed("local l = {1}\nif l then print(l) end\n", "LuauLintSlListTruth", "Write it #l > 0", false) == + std::string("local l = {1}\nif #l > 0 then print(l) end\n")); + } + { + INFO("a list under not"); + CHECK(require_fixed("local l = {1}\nprint(not l)\n", "LuauLintSlListTruthNot", "Write it #l == 0", false) == + std::string("local l = {1}\nprint(#l == 0)\n")); + } + { + INFO("an integer"); + CHECK(require_fixed("local i = 5i\nif i then print(i) end\n", "LuauLintSlIntegerTruth", "Write it i ~= 0i", false) == + std::string("local i = 5i\nif i ~= 0i then print(i) end\n")); + } UNSCOPED_INFO("a number read by a key: not said either"); CHECK(found("local counts: {[string]: number} = {}\nif counts[\"a\"] then print(1) end\n", "SlNumberTruth", ALScriptProblem::Severity::Warning) == @@ -1035,7 +1150,7 @@ namespace } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlMustUse over SLua's own types: their makers and readers left unread", "[ALScriptLintPass]") { - UNSCOPED_INFO("definitions: " + error); + UNSCOPED_INFO("definitions: " + definitionsError); CHECK(loaded); const std::string said = found("local i = 5i\n" "local b = buffer.create(4)\n" @@ -1064,9 +1179,11 @@ namespace "9 LuauLintSlMustUse|buffer.len\n" "10 LuauLintSlMustUse|buffer.create\n" "11 LuauLintSlMustUse|table.create\n")); - UNSCOPED_INFO("an integer's"); - CHECK(require_fixed("local i = 5i\ninteger.band(i, 3i)\nprint(i)\n", "LuauLintSlMustUse", "Write it i = integer.band(...)", false) == - std::string("local i = 5i\ni = integer.band(i, 3i)\nprint(i)\n")); + { + INFO("an integer's"); + CHECK(require_fixed("local i = 5i\ninteger.band(i, 3i)\nprint(i)\n", "LuauLintSlMustUse", "Write it i = integer.band(...)", false) == + std::string("local i = 5i\ni = integer.band(i, 3i)\nprint(i)\n")); + } require_unfixed("vector(1, 2, 3)\n", "LuauLintSlMustUse"); } } From 3fccb75c0b85e851805fa67fbb0491a47e4b9223 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 03:19:25 -0400 Subject: [PATCH 4/6] alscript's disk master, envelope and message tests check what they say they check Old test bugs #462's idiom pass found, fixed. Most were reads through what a lookup returned, with nothing checking it found anything: a miss would have crashed the binary, taking every later case with it, rather than failing its own. - Guards before the first read of each lookup: - almasterindex: linkOf(), ALMasterLinks::of() and the index read back from disk (kept in a variable, so the pointer into it stays good); - almasterwatch: the folders asked about, and each period; - almasterburst: every dueAt(); - almasterlinks: of() and find(), and the links left after a remove, which nothing sized; - almodulelook: each module named, and its names; - alscriptenvelope: the code line in its text, the lines either side, and each upload header parsed; - alnotecardformat: each notecard named; - alscriptformatter: the lines a range asked for. - almessagemap: every match shared one Match, so a match that failed showed the one before it, and args[1] was in bounds only because an earlier match had filled args. Each match has its own, and each checks its whole args, which also checks the module name is left blank, as the case says. - alscriptformatter: "a line asked for" checked a line not asked for, as written, which is what formatEach promises; the message says so. 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. almodulelook's names guard has none: every module the look finds has a name. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/tests/almasterburst_test.cpp | 48 ++++-- indra/alscript/tests/almasterindex_test.cpp | 138 +++++++++++++----- indra/alscript/tests/almasterlinks_test.cpp | 81 +++++++--- indra/alscript/tests/almasterwatch_test.cpp | 31 +++- indra/alscript/tests/almessagemap_test.cpp | 24 ++- indra/alscript/tests/almodulelook_test.cpp | 43 +++++- .../alscript/tests/alnotecardformat_test.cpp | 16 +- .../alscript/tests/alscriptenvelope_test.cpp | 20 ++- .../alscript/tests/alscriptformatter_test.cpp | 6 +- 9 files changed, 302 insertions(+), 105 deletions(-) diff --git a/indra/alscript/tests/almasterburst_test.cpp b/indra/alscript/tests/almasterburst_test.cpp index a28a2b9e25..59935e915f 100644 --- a/indra/alscript/tests/almasterburst_test.cpp +++ b/indra/alscript/tests/almasterburst_test.cpp @@ -66,8 +66,10 @@ namespace burst.changed("/s/door.lsl", 100.0); CHECK(burst.waiting("/s/door.lsl")); REQUIRE(burst.size() == 1); + std::optional due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("due a second on"); - CHECK(*burst.dueAt() == 101.0); + CHECK(*due == 101.0); { INFO("not before"); REQUIRE(burst.release(100.5).empty()); @@ -82,11 +84,15 @@ namespace // A quiet time set shorter, and one less than nothing. burst.setQuiet(0.25); burst.changed("/s/door.lsl", 300.0); + due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("the quiet time set"); - CHECK(*burst.dueAt() == 300.25); + CHECK(*due == 300.25); burst.setQuiet(-1.0); + due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("none at all is no wait"); - CHECK(*burst.dueAt() == 300.0); + CHECK(*due == 300.0); UNSCOPED_INFO("and goes at once"); CHECK(almasterburst_data::joined(burst.release(300.0)) == std::string("/s/door.lsl")); } @@ -107,8 +113,10 @@ namespace REQUIRE(burst.release(last).empty()); } REQUIRE(burst.size() == size_t(30)); + const std::optional due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("due a second after the last"); - CHECK(*burst.dueAt() == last + 1.0); + CHECK(*due == last + 1.0); UNSCOPED_INFO("not a moment before"); REQUIRE(burst.release(almasterburst_data::before(last + 1.0)).empty()); UNSCOPED_INFO("all of them, in the order heard"); @@ -127,8 +135,10 @@ namespace burst.changed("/s/build/out.lsl", now); if (n == 6) { + const std::optional due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("due when quiet, while that is sooner"); - REQUIRE(*burst.dueAt() == 4.0); + REQUIRE(*due == 4.0); } if (!burst.release(now).empty()) { @@ -142,8 +152,10 @@ namespace // The next change began a burst of its own. UNSCOPED_INFO("then ten seconds after the change that followed"); CHECK(gone[1] == 20.5); + const std::optional due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("and once more due when that one is over"); - CHECK(*burst.dueAt() == 25.0 + 1.0); + CHECK(*due == 25.0 + 1.0); } TEST_CASE_METHOD(almasterburst_data, "almasterburst: a file held past the quiet time stays and the others go first", "[almasterburst]") @@ -153,8 +165,10 @@ namespace // Found emptied: held a second and a half. burst.changed("/s/emptied.lsl", 0.25, 1.5); burst.changed("/s/b.lsl", 0.5); + std::optional due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("due when quiet"); - CHECK(*burst.dueAt() == 1.5); + CHECK(*due == 1.5); UNSCOPED_INFO("the others go"); CHECK(almasterburst_data::joined(burst.release(1.5)) == std::string("/s/a.lsl /s/b.lsl")); { @@ -162,8 +176,10 @@ namespace CHECK(burst.waiting("/s/emptied.lsl")); REQUIRE(burst.size() == 1); } + due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("due when its hold is over"); - CHECK(*burst.dueAt() == 1.75); + CHECK(*due == 1.75); UNSCOPED_INFO("not before"); REQUIRE(burst.release(almasterburst_data::before(1.75)).empty()); UNSCOPED_INFO("then it goes"); @@ -172,8 +188,10 @@ namespace // Held, but the burst it is in goes on: it waits for both. burst.changed("/s/emptied.lsl", 10.0, 1.5); burst.changed("/s/c.lsl", 11.0); + due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("past its hold, the burst not over"); - CHECK(*burst.dueAt() == 12.0); + CHECK(*due == 12.0); UNSCOPED_INFO("together then"); CHECK(almasterburst_data::joined(burst.release(12.0)) == std::string("/s/emptied.lsl /s/c.lsl")); } @@ -184,16 +202,20 @@ namespace burst.changed("/s/a.lsl", 0.0, 1.5); burst.changed("/s/b.lsl", 0.125); burst.changed("/s/a.lsl", 0.25); + std::optional due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("due when quiet, the hold gone"); - CHECK(*burst.dueAt() == 1.25); + CHECK(*due == 1.25); UNSCOPED_INFO("both, the refilled one in its first place"); CHECK(almasterburst_data::joined(burst.release(1.25)) == std::string("/s/a.lsl /s/b.lsl")); burst.changed("/s/a.lsl", 5.0); burst.changed("/s/a.lsl", 5.25, 1.5); + due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("emptied later, held from then"); - CHECK(*burst.dueAt() == 6.75); + CHECK(*due == 6.75); UNSCOPED_INFO("not when quiet"); REQUIRE(burst.release(6.25).empty()); UNSCOPED_INFO("when the later hold is over"); @@ -226,8 +248,10 @@ namespace REQUIRE(burst.release(now).empty()); } burst.changed("/s/busy.lsl", 115.0); + const std::optional due = burst.dueAt(); + REQUIRE(due.has_value()); UNSCOPED_INFO("due at the rest's longest"); - CHECK(*burst.dueAt() == 115.0); + CHECK(*due == 115.0); UNSCOPED_INFO("which it goes at"); CHECK(almasterburst_data::joined(burst.release(115.0)) == std::string("/s/busy.lsl")); diff --git a/indra/alscript/tests/almasterindex_test.cpp b/indra/alscript/tests/almasterindex_test.cpp index 99370ed00b..b9f8b0bac4 100644 --- a/indra/alscript/tests/almasterindex_test.cpp +++ b/indra/alscript/tests/almasterindex_test.cpp @@ -193,7 +193,9 @@ namespace CHECK(read->base == almasterindex_data::key(9)); REQUIRE(read->uses.size() == size_t(1)); CHECK(read->itemName == std::string("Door")); - CHECK(back.linkOf(LLUUID::null, almasterindex_data::key(3))->notecard); + const std::optional note = back.linkOf(LLUUID::null, almasterindex_data::key(3)); + REQUIRE(note.has_value()); + CHECK(note->notecard); REQUIRE(back.mastering("/s/door.lsl").size() == size_t(1)); REQUIRE(back.affectedBy("disk:/s/util.lsl").size() == size_t(1)); REQUIRE(back.linksIn(almasterindex_data::key(1)).size() == size_t(1)); @@ -269,7 +271,9 @@ namespace made.link(almasterindex_data::link(1, 1, "/s/a2.lsl")); UNSCOPED_INFO("put over, not beside"); REQUIRE(made.size() == size_t(4)); - CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))->master == std::string("/s/a2.lsl")); + const std::optional over = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)); + REQUIRE(over.has_value()); + CHECK(over->master == std::string("/s/a2.lsl")); } TEST_CASE_METHOD(almasterindex_data, "almasterindex: a link let go of and links marked pending only where linked", "[almasterindex]") @@ -288,7 +292,9 @@ namespace const size_t marked = made.markPending({ { almasterindex_data::key(1), almasterindex_data::key(1) }, { almasterindex_data::key(1), almasterindex_data::key(2) } }); UNSCOPED_INFO("the one linked"); CHECK(marked == size_t(1)); - CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->state == ALMasterLink::State::Pending); + const std::optional pending = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(pending.has_value()); + CHECK(pending->state == ALMasterLink::State::Pending); CHECK(told == 2); UNSCOPED_INFO("none linked, none marked"); CHECK(made.markPending({ { almasterindex_data::key(5), almasterindex_data::key(5) } }) == size_t(0)); @@ -297,7 +303,9 @@ namespace index.reset(); const ALMasterLinks disk = onDisk(); REQUIRE(disk.size() == size_t(1)); - CHECK(disk.of(almasterindex_data::key(1), almasterindex_data::key(2))->state == ALMasterLink::State::Pending); + const ALMasterLink* const written = disk.of(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(written); + CHECK(written->state == ALMasterLink::State::Pending); } TEST_CASE_METHOD(almasterindex_data, "almasterindex: a save heard elsewhere marks the link differing unless the master sent it", "[almasterindex]") @@ -327,6 +335,7 @@ namespace // A recompile: the same text, a new asset. CHECK(made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(2), almasterindex_data::key(101), ours) == ALMasterIndex::Heard::Same); const std::optional same = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(same.has_value()); CHECK(same->base == almasterindex_data::key(101)); CHECK(same->state == ALMasterLink::State::Active); CHECK(told == 0); @@ -337,6 +346,7 @@ namespace CHECK(made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(2), almasterindex_data::key(102), "default {}") == ALMasterIndex::Heard::Differing); const std::optional other = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(other.has_value()); CHECK(other->state == ALMasterLink::State::Differing); UNSCOPED_INFO("its base as it was"); CHECK(other->base == almasterindex_data::key(101)); @@ -353,12 +363,16 @@ namespace ALMasterIndex& made = open(); made.link(almasterindex_data::link(1, 2, "/s/door.lsl")); told = 0; - ALMasterLink sent = *made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + const std::optional linked = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(linked.has_value()); + ALMasterLink sent = *linked; sent.hash = "h1"; sent.base = almasterindex_data::key(50); CHECK(made.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent)); + const std::optional left = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(left.has_value()); UNSCOPED_INFO("with what the send left"); - CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->hash == std::string("h1")); + CHECK(left->hash == std::string("h1")); CHECK(told == 1); // Saved as another item: the link is that one's. @@ -368,7 +382,9 @@ namespace CHECK(made.finished(almasterindex_data::key(1), almasterindex_data::key(2), rekeyed)); UNSCOPED_INFO("not the old item's"); REQUIRE_FALSE(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))); - CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3))->hash == std::string("h2")); + const std::optional moved = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3)); + REQUIRE(moved.has_value()); + CHECK(moved->hash == std::string("h2")); REQUIRE(made.size() == size_t(1)); REQUIRE(made.mastering("/s/door.lsl").size() == size_t(1)); @@ -382,12 +398,15 @@ namespace { ALMasterIndex& made = open(); made.link({ almasterindex_data::link(1, 1, "/s/a.lsl"), almasterindex_data::link(1, 2, "/s/b.lsl"), almasterindex_data::link(1, 3, "/s/c.lsl") }); + const std::optional fresh = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)); + REQUIRE(fresh.has_value()); UNSCOPED_INFO("a new link knows nothing"); - CHECK(ALMasterIndex::knowsNothing(*made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)))); + CHECK(ALMasterIndex::knowsNothing(*fresh)); told = 0; CHECK(made.adopted(almasterindex_data::key(1), almasterindex_data::key(1), "/s/a.lsl", { "disk:/s/util.lsl" }, false, "ha", 77)); const std::optional a = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)); + REQUIRE(a.has_value()); REQUIRE(a->uses.size() == size_t(1)); UNSCOPED_INFO("what went up, as the world holds it"); CHECK(a->hash == std::string("ha")); @@ -395,8 +414,10 @@ namespace REQUIRE_FALSE(made.affectedBy("disk:/s/util.lsl").empty()); // The world holding something else: what it read, and no hash. CHECK(made.adopted(almasterindex_data::key(1), almasterindex_data::key(2), "/s/b.lsl", {}, true, std::string(), 0)); - CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->missed); - REQUIRE(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->hash.empty()); + const std::optional b = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(b.has_value()); + CHECK(b->missed); + REQUIRE(b->hash.empty()); UNSCOPED_INFO("not told yet"); CHECK(told == 0); pass(ALMasterIndex::TELL_SOON + 0.01); @@ -405,8 +426,10 @@ namespace UNSCOPED_INFO("knowing something now: not again"); CHECK_FALSE(made.adopted(almasterindex_data::key(1), almasterindex_data::key(1), "/s/a.lsl", {}, false, "other", 1)); + const std::optional kept = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)); + REQUIRE(kept.has_value()); UNSCOPED_INFO("as it was"); - CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))->hash == std::string("ha")); + CHECK(kept->hash == std::string("ha")); made.link(almasterindex_data::link(1, 3, "/s/elsewhere.lsl")); UNSCOPED_INFO("linked to another master: not"); CHECK_FALSE(made.adopted(almasterindex_data::key(1), almasterindex_data::key(3), "/s/c.lsl", { "disk:/s/x.lsl" }, false, "hc", 1)); @@ -414,8 +437,10 @@ namespace CHECK_FALSE(made.adopted(almasterindex_data::key(1), almasterindex_data::key(3), "/s/elsewhere.lsl", {}, false, std::string(), 0)); UNSCOPED_INFO("not linked: not"); CHECK_FALSE(made.adopted(almasterindex_data::key(9), almasterindex_data::key(9), "/s/a.lsl", { "disk:/s/x.lsl" }, false, "h", 1)); + const std::optional elsewhere = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3)); + REQUIRE(elsewhere.has_value()); UNSCOPED_INFO("still knowing nothing"); - CHECK(ALMasterIndex::knowsNothing(*made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3)))); + CHECK(ALMasterIndex::knowsNothing(*elsewhere)); const S32 before = told; pass(1.0); UNSCOPED_INFO("nothing more told"); @@ -427,7 +452,9 @@ namespace ALMasterIndex& made = open(); made.link(almasterindex_data::link(1, 2, "/s/door.lsl")); made.flush(); - ALMasterLink sent = *made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + const std::optional linked = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(linked.has_value()); + ALMasterLink sent = *linked; sent.hash = "h1"; made.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent); @@ -440,14 +467,24 @@ namespace UNSCOPED_INFO("a second after the first, not after the last: waiting still"); CHECK(made.dirty()); made.awaitWrites(); - UNSCOPED_INFO("the file as it was"); - REQUIRE(onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash.empty()); + const ALMasterLinks waited = onDisk(); + const ALMasterLink* const as_was = waited.of(almasterindex_data::key(1), almasterindex_data::key(2)); + { + INFO("the file as it was"); + REQUIRE(as_was); + REQUIRE(as_was->hash.empty()); + } pass(0.5); UNSCOPED_INFO("a second after the last: handed over"); CHECK_FALSE(made.dirty()); made.awaitWrites(); - UNSCOPED_INFO("written, the newest"); - CHECK(onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash == std::string("h2")); + const ALMasterLinks handed = onDisk(); + const ALMasterLink* const newest = handed.of(almasterindex_data::key(1), almasterindex_data::key(2)); + { + INFO("written, the newest"); + REQUIRE(newest); + CHECK(newest->hash == std::string("h2")); + } // A change every 0.8 s: written five seconds after the first all the // same. @@ -463,8 +500,13 @@ namespace UNSCOPED_INFO("5.1 s on: handed over"); CHECK_FALSE(made.dirty()); made.awaitWrites(); - UNSCOPED_INFO("written"); - CHECK(onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash == std::string("run5")); + const ALMasterLinks ran = onDisk(); + const ALMasterLink* const last = ran.of(almasterindex_data::key(1), almasterindex_data::key(2)); + { + INFO("written"); + REQUIRE(last); + CHECK(last->hash == std::string("run5")); + } } TEST_CASE_METHOD(almasterindex_data, "almasterindex: whatever waits is written to its own file as the index goes", "[almasterindex]") @@ -472,7 +514,9 @@ namespace const std::string other = in("other_account.llsd"); ALMasterIndex& first = open(); first.link(almasterindex_data::link(1, 2, "/s/door.lsl")); - ALMasterLink sent = *first.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + const std::optional linked = first.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); + REQUIRE(linked.has_value()); + ALMasterLink sent = *linked; sent.hash = "last"; first.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent); UNSCOPED_INFO("waiting"); @@ -483,9 +527,13 @@ namespace ALMasterIndex& second = open(other); UNSCOPED_INFO("the other account's has none"); REQUIRE(second.empty()); - UNSCOPED_INFO("the first's written to its own file"); - CHECK(almasterindex_data::onDisk(file).of(almasterindex_data::key(1), almasterindex_data::key(2))->hash == - std::string("last")); + const ALMasterLinks firsts = almasterindex_data::onDisk(file); + const ALMasterLink* const went = firsts.of(almasterindex_data::key(1), almasterindex_data::key(2)); + { + INFO("the first's written to its own file"); + REQUIRE(went); + CHECK(went->hash == std::string("last")); + } second.link(almasterindex_data::link(5, 6, "/s/lamp.lsl")); index.reset(); UNSCOPED_INFO("the second's to its own"); @@ -514,37 +562,51 @@ namespace REQUIRE(on_disk(1).has_value()); // A send's bookkeeping waits, and a crash now loses it. - ALMasterLink sent = *made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)); + const std::optional linked = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)); + REQUIRE(linked.has_value()); + ALMasterLink sent = *linked; sent.hash = "h1"; sent.base = almasterindex_data::key(10); sent.uses = { "disk:/s/util.lsl" }; made.finished(almasterindex_data::key(1), almasterindex_data::key(1), sent); UNSCOPED_INFO("a send's bookkeeping waits"); CHECK(made.dirty()); + const std::optional waits = on_disk(1); + REQUIRE(waits.has_value()); UNSCOPED_INFO("not on disk yet"); - REQUIRE(on_disk(1)->hash.empty()); + REQUIRE(waits->hash.empty()); // Something a person did takes it with it. made.markPending({ { almasterindex_data::key(1), almasterindex_data::key(2) } }); UNSCOPED_INFO("pending: at once"); CHECK_FALSE(made.dirty()); - CHECK(on_disk(2)->state == ALMasterLink::State::Pending); + const std::optional marked = on_disk(2); + REQUIRE(marked.has_value()); + CHECK(marked->state == ALMasterLink::State::Pending); + const std::optional with = on_disk(1); + REQUIRE(with.has_value()); UNSCOPED_INFO("and what waited with it"); - CHECK(on_disk(1)->hash == std::string("h1")); + CHECK(with->hash == std::string("h1")); // A send that changes how a link stands: at once. - sent = *made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)); + const std::optional again = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)); + REQUIRE(again.has_value()); + sent = *again; sent.state = ALMasterLink::State::Suspended; made.finished(almasterindex_data::key(1), almasterindex_data::key(1), sent); UNSCOPED_INFO("suspended: at once"); CHECK_FALSE(made.dirty()); - CHECK(on_disk(1)->state == ALMasterLink::State::Suspended); + const std::optional suspended = on_disk(1); + REQUIRE(suspended.has_value()); + CHECK(suspended->state == ALMasterLink::State::Suspended); sent.state = ALMasterLink::State::Active; made.finished(almasterindex_data::key(1), almasterindex_data::key(1), sent); { INFO("active again: at once"); CHECK_FALSE(made.dirty()); - CHECK(on_disk(1)->state == ALMasterLink::State::Active); + const std::optional active = on_disk(1); + REQUIRE(active.has_value()); + CHECK(active->state == ALMasterLink::State::Active); } // A send saved as another item: at once, or the link is lost with it. @@ -560,7 +622,9 @@ namespace } // A save heard from elsewhere: differing at once, the same later. - sent = *made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3)); + const std::optional under = made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3)); + REQUIRE(under.has_value()); + sent = *under; sent.hash = ALUploadHeader::hashOfPlain("mono", "default {}"); made.finished(almasterindex_data::key(1), almasterindex_data::key(3), sent); made.flush(); @@ -571,7 +635,9 @@ namespace { INFO("differing: at once"); CHECK_FALSE(made.dirty()); - CHECK(on_disk(3)->state == ALMasterLink::State::Differing); + const std::optional differing = on_disk(3); + REQUIRE(differing.has_value()); + CHECK(differing->state == ALMasterLink::State::Differing); } // A probe's answer, and a link let go of. @@ -579,16 +645,20 @@ namespace made.adopted(almasterindex_data::key(1), almasterindex_data::key(4), "/s/d.lsl", { "disk:/s/util.lsl" }, false, std::string(), 0); UNSCOPED_INFO("a probe's answer: later"); CHECK(made.dirty()); + const std::optional probed = on_disk(4); + REQUIRE(probed.has_value()); UNSCOPED_INFO("not on disk yet"); - REQUIRE(on_disk(4)->uses.empty()); + REQUIRE(probed->uses.empty()); made.unlink(almasterindex_data::key(1), almasterindex_data::key(2)); { INFO("let go of: at once"); CHECK_FALSE(made.dirty()); REQUIRE_FALSE(on_disk(2).has_value()); } + const std::optional answered = on_disk(4); + REQUIRE(answered.has_value()); UNSCOPED_INFO("the probe's answer with it"); - REQUIRE(on_disk(4)->uses.size() == size_t(1)); + REQUIRE(answered->uses.size() == size_t(1)); // Nothing left for the time to write. pass(10.0); UNSCOPED_INFO("nothing waiting"); diff --git a/indra/alscript/tests/almasterlinks_test.cpp b/indra/alscript/tests/almasterlinks_test.cpp index 1f3ea7e4c1..d33d95733c 100644 --- a/indra/alscript/tests/almasterlinks_test.cpp +++ b/indra/alscript/tests/almasterlinks_test.cpp @@ -101,11 +101,14 @@ namespace links.put(almasterlinks_data::link(0, 2, "/s/inventory.lsl")); links.put(almasterlinks_data::link(3, 4, "/s/lamp.lsl")); REQUIRE(links.size() == size_t(3)); + const ALMasterLink* const door = links.of(almasterlinks_data::key(1), almasterlinks_data::key(2)); + REQUIRE(door); UNSCOPED_INFO("by its object and item"); - CHECK(links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))->master == std::string("/s/door.lsl")); + CHECK(door->master == std::string("/s/door.lsl")); + const ALMasterLink* const inventory = links.of(LLUUID::null, almasterlinks_data::key(2)); + REQUIRE(inventory); UNSCOPED_INFO("the same item in the inventory is another"); - CHECK(links.of(LLUUID::null, almasterlinks_data::key(2))->master == - std::string("/s/inventory.lsl")); + CHECK(inventory->master == std::string("/s/inventory.lsl")); UNSCOPED_INFO("not by the item in another object"); REQUIRE_FALSE(links.of(almasterlinks_data::key(3), almasterlinks_data::key(2))); @@ -116,8 +119,10 @@ namespace REQUIRE(links.size() == size_t(3)); UNSCOPED_INFO("what put answers is the one kept"); CHECK(put.master == std::string("/s/door2.lsl")); + const ALMasterLink* const replaced = links.of(almasterlinks_data::key(1), almasterlinks_data::key(2)); + REQUIRE(replaced); UNSCOPED_INFO("and found so"); - CHECK(links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))->made == ALMasterLink::Made::Hint); + CHECK(replaced->made == ALMasterLink::Made::Hint); UNSCOPED_INFO("in its place"); CHECK(links.all()[0].master == std::string("/s/door2.lsl")); @@ -125,8 +130,10 @@ namespace UNSCOPED_INFO("found to change"); REQUIRE(found != nullptr); found->hash = "xxh128:00"; + const ALMasterLink* const lamp = links.of(almasterlinks_data::key(3), almasterlinks_data::key(4)); + REQUIRE(lamp); UNSCOPED_INFO("changed where it is kept"); - CHECK(links.of(almasterlinks_data::key(3), almasterlinks_data::key(4))->hash == std::string("xxh128:00")); + CHECK(lamp->hash == std::string("xxh128:00")); UNSCOPED_INFO("nothing to change of an item not linked"); REQUIRE_FALSE(links.find(almasterlinks_data::key(9), almasterlinks_data::key(9))); @@ -135,11 +142,15 @@ namespace REQUIRE_FALSE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))); UNSCOPED_INFO("not twice"); CHECK_FALSE(links.remove(almasterlinks_data::key(1), almasterlinks_data::key(2))); - UNSCOPED_INFO("the rest kept, in order"); - CHECK(links.all()[0].master == std::string("/s/inventory.lsl")); + { + INFO("the rest kept, in order"); + REQUIRE(links.size() == size_t(2)); + CHECK(links.all()[0].master == std::string("/s/inventory.lsl")); + } + const ALMasterLink* const moved = links.of(almasterlinks_data::key(3), almasterlinks_data::key(4)); + REQUIRE(moved); UNSCOPED_INFO("and found where they now are"); - CHECK(links.of(almasterlinks_data::key(3), almasterlinks_data::key(4))->master == - std::string("/s/lamp.lsl")); + CHECK(moved->master == std::string("/s/lamp.lsl")); } TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: mastering lists a file's items comparing paths as their platform does", "[almasterlinks]") @@ -169,7 +180,9 @@ namespace CHECK(ALMasterLinks::keyOf("/S/Door.lsl") == std::string("/S/Door.lsl")); // Changed through find(): the index follows. - links.find(almasterlinks_data::key(2), almasterlinks_data::key(11))->master = "/s/net/lamp.lsl"; + ALMasterLink* const remastered = links.find(almasterlinks_data::key(2), almasterlinks_data::key(11)); + REQUIRE(remastered); + remastered->master = "/s/net/lamp.lsl"; UNSCOPED_INFO("a master changed through find"); CHECK(almasterlinks_data::items(links.mastering("/s/net/door.lsl")) == std::string("10")); @@ -210,7 +223,9 @@ namespace std::string("12")); // The uses of the next expansion, written through find(). - links.find(almasterlinks_data::key(1), almasterlinks_data::key(10))->uses = { "disk:/s/lib/net.lsl" }; + ALMasterLink* const expanded = links.find(almasterlinks_data::key(1), almasterlinks_data::key(10)); + REQUIRE(expanded); + expanded->uses = { "disk:/s/lib/net.lsl" }; UNSCOPED_INFO("uses changed through find"); CHECK(almasterlinks_data::items(links.usersOf("disk:/s/lib/util.lsl")) == std::string("11")); @@ -513,11 +528,15 @@ namespace std::istringstream in(out.str()); LLSD back; REQUIRE(LLSDSerialize::fromNotation(back, in, static_cast(out.str().size())) > 0); - const ALMasterLinks read = ALMasterLinks::fromLLSD(back); + const ALMasterLinks read = ALMasterLinks::fromLLSD(back); + const ALMasterLink* const set = read.of(almasterlinks_data::key(1), almasterlinks_data::key(10)); + const ALMasterLink* const unset = read.of(almasterlinks_data::key(1), almasterlinks_data::key(11)); + REQUIRE(set); + REQUIRE(unset); UNSCOPED_INFO("read back set"); - CHECK(read.of(almasterlinks_data::key(1), almasterlinks_data::key(10))->missed); + CHECK(set->missed); UNSCOPED_INFO("and not"); - CHECK_FALSE(read.of(almasterlinks_data::key(1), almasterlinks_data::key(11))->missed); + CHECK_FALSE(unset->missed); // As an older version wrote it, with no such field, and as it might // be written otherwise. @@ -535,12 +554,18 @@ namespace const ALMasterLinks old = ALMasterLinks::fromLLSD(older); UNSCOPED_INFO("all read"); REQUIRE(old.size() == size_t(3)); + const ALMasterLink* const unsaid = old.of(LLUUID::null, almasterlinks_data::key(12)); + const ALMasterLink* const number = old.of(LLUUID::null, almasterlinks_data::key(13)); + const ALMasterLink* const no_value = old.of(LLUUID::null, almasterlinks_data::key(14)); + REQUIRE(unsaid); + REQUIRE(number); + REQUIRE(no_value); UNSCOPED_INFO("none said is none missed"); - CHECK_FALSE(old.of(LLUUID::null, almasterlinks_data::key(12))->missed); + CHECK_FALSE(unsaid->missed); UNSCOPED_INFO("a number is read as one"); - CHECK(old.of(LLUUID::null, almasterlinks_data::key(13))->missed); + CHECK(number->missed); UNSCOPED_INFO("what is no value is none"); - CHECK_FALSE(old.of(LLUUID::null, almasterlinks_data::key(14))->missed); + CHECK_FALSE(no_value->missed); } TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: affectedBy lists users and links that missed but none suspended or orphaned", "[almasterlinks]") @@ -576,7 +601,9 @@ namespace std::string("11 14 15")); // Expanded again, and nothing missed now. - links.find(almasterlinks_data::key(1), almasterlinks_data::key(14))->missed = false; + ALMasterLink* const expanded = links.find(almasterlinks_data::key(1), almasterlinks_data::key(14)); + REQUIRE(expanded); + expanded->missed = false; UNSCOPED_INFO("no longer"); CHECK(almasterlinks_data::items(links.affectedBy("/s/lib/new.lsl")) == std::string("11")); UNSCOPED_INFO("nothing at all with nothing linked"); @@ -614,8 +641,12 @@ namespace REQUIRE(ALMasterLinks().watched().empty()); // Orphaned, the file it masters is watched no longer. - links.find(almasterlinks_data::key(1), almasterlinks_data::key(14))->state = State::Orphaned; - links.find(almasterlinks_data::key(1), almasterlinks_data::key(13))->state = State::Orphaned; + ALMasterLink* const gone = links.find(almasterlinks_data::key(1), almasterlinks_data::key(14)); + REQUIRE(gone); + gone->state = State::Orphaned; + ALMasterLink* const include = links.find(almasterlinks_data::key(1), almasterlinks_data::key(13)); + REQUIRE(include); + include->state = State::Orphaned; UNSCOPED_INFO("an include again where it no longer masters"); CHECK(almasterlinks_data::watched(links.watched()) == std::string("/s/a.lsl* /s/lib/u.lsl /s/lib/v.lsl /s/b.lsl* /s/c.lsl* /s/c2.lsl C:\\S\\G.lsl*")); @@ -669,10 +700,14 @@ namespace CHECK(llsd["links"][0]["notecard"].asBoolean()); UNSCOPED_INFO("and not where not"); REQUIRE_FALSE(llsd["links"][1].has("notecard")); - const ALMasterLinks read = ALMasterLinks::fromLLSD(llsd); + const ALMasterLinks read = ALMasterLinks::fromLLSD(llsd); + const ALMasterLink* const card = read.of(almasterlinks_data::key(1), almasterlinks_data::key(10)); + const ALMasterLink* const script = read.of(almasterlinks_data::key(1), almasterlinks_data::key(11)); + REQUIRE(card); + REQUIRE(script); UNSCOPED_INFO("read back a notecard's"); - CHECK(read.of(almasterlinks_data::key(1), almasterlinks_data::key(10))->notecard); + CHECK(card->notecard); UNSCOPED_INFO("and a script's"); - CHECK_FALSE(read.of(almasterlinks_data::key(1), almasterlinks_data::key(11))->notecard); + CHECK_FALSE(script->notecard); } } diff --git a/indra/alscript/tests/almasterwatch_test.cpp b/indra/alscript/tests/almasterwatch_test.cpp index 84f65708c1..dd25508899 100644 --- a/indra/alscript/tests/almasterwatch_test.cpp +++ b/indra/alscript/tests/almasterwatch_test.cpp @@ -447,8 +447,11 @@ namespace } const std::string other = in("other.lsl"); watched.watch({ almasterwatch_data::master(door), almasterwatch_data::master(lamp), almasterwatch_data::master(other) }); - UNSCOPED_INFO("lamp waits on its look still: other alone asked"); - REQUIRE(asked[3].paths.size() == size_t(1)); + { + INFO("lamp waits on its look still: other alone asked"); + REQUIRE(asked.size() == size_t(4)); + REQUIRE(asked[3].paths.size() == size_t(1)); + } answer(3); CHECK(watched.watching(other)); UNSCOPED_INFO("lamp waits on its own"); @@ -470,8 +473,12 @@ namespace return true; }); watch->watch({ almasterwatch_data::master(door), almasterwatch_data::include(util) }); - CHECK(*watch->periodOf(door) == ALMasterWatch::MASTER_PERIOD); - CHECK(*watch->periodOf(util) == ALMasterWatch::INCLUDE_PERIOD); + std::optional door_period = watch->periodOf(door); + std::optional util_period = watch->periodOf(util); + REQUIRE(door_period.has_value()); + REQUIRE(util_period.has_value()); + CHECK(*door_period == ALMasterWatch::MASTER_PERIOD); + CHECK(*util_period == ALMasterWatch::INCLUDE_PERIOD); UNSCOPED_INFO("both looked at first"); CHECK(looks == size_t(2)); @@ -482,16 +489,24 @@ namespace } watch->watch(many); REQUIRE(watch->size() == size_t(257)); + door_period = watch->periodOf(door); + util_period = watch->periodOf(util); + REQUIRE(door_period.has_value()); + REQUIRE(util_period.has_value()); UNSCOPED_INFO("past 256: a master every two seconds"); - CHECK(*watch->periodOf(door) == ALMasterWatch::MANY_PERIOD); - CHECK(*watch->periodOf(util) == ALMasterWatch::MANY_PERIOD); + CHECK(*door_period == ALMasterWatch::MANY_PERIOD); + CHECK(*util_period == ALMasterWatch::MANY_PERIOD); UNSCOPED_INFO("only the new ones looked at"); CHECK(looks == size_t(257)); watch->watch({ almasterwatch_data::master(door), almasterwatch_data::include(util) }); + door_period = watch->periodOf(door); + util_period = watch->periodOf(util); + REQUIRE(door_period.has_value()); + REQUIRE(util_period.has_value()); UNSCOPED_INFO("fewer again: a master's own"); - CHECK(*watch->periodOf(door) == ALMasterWatch::MASTER_PERIOD); - CHECK(*watch->periodOf(util) == ALMasterWatch::INCLUDE_PERIOD); + CHECK(*door_period == ALMasterWatch::MASTER_PERIOD); + CHECK(*util_period == ALMasterWatch::INCLUDE_PERIOD); UNSCOPED_INFO("nothing looked at again"); CHECK(looks == size_t(257)); UNSCOPED_INFO("one not watched has no period"); diff --git a/indra/alscript/tests/almessagemap_test.cpp b/indra/alscript/tests/almessagemap_test.cpp index 47a5dc8f80..45a19b4c5e 100644 --- a/indra/alscript/tests/almessagemap_test.cpp +++ b/indra/alscript/tests/almessagemap_test.cpp @@ -214,66 +214,76 @@ namespace TEST_CASE_METHOD(almessagemap_data, "almessagemap: why a SLua require found nothing is keyed with the module name left blank", "[almessagemap]") { - ALMessageMap::Match m; { INFO("a child"); + ALMessageMap::Match m; REQUIRE(ALMessageMap::luauRequire("could not resolve child component \"util\"", m)); CHECK(m.key == "PreprocRequireNoChild"); CHECK(m.args == std::vector({ "", "util" })); } { INFO("ambiguous, the fuller shape"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("could not resolve child component \"util\" (ambiguous)", m)); CHECK(m.key == "PreprocRequireNoChildAmbiguous"); } { INFO("an alias"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("@lib is not a valid alias", m)); CHECK(m.key == "PreprocRequireNotAlias"); - CHECK(m.args[1] == "lib"); + CHECK(m.args == std::vector({ "", "lib" })); } { INFO("a cycle"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("detected alias cycle (@a -> @b -> @a)", m)); CHECK(m.key == "PreprocRequireAliasCycle"); - CHECK(m.args[1] == "@a -> @b -> @a"); + CHECK(m.args == std::vector({ "", "@a -> @b -> @a" })); } { INFO("the studio's own"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("the alias '@sl-std' is reserved: aliases starting @sl- are Second Life's", m)); CHECK(m.key == "PreprocRequireReserved"); - CHECK(m.args[1] == "sl-std"); + CHECK(m.args == std::vector({ "", "sl-std" })); } { INFO("a folder with both configurations"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("could not resolve alias \"lib\" (ambiguous configuration file)", m)); CHECK(m.key == "PreprocRequireConfigAmbiguous"); - CHECK(m.args[1] == "lib"); + CHECK(m.args == std::vector({ "", "lib" })); } { INFO("a file nothing lets in"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("'C:/lib/x.luau' is there, but no include folder or alias lets a require read it", m)); CHECK(m.key == "PreprocRequireNotAdmitted"); - CHECK(m.args[1] == "C:/lib/x.luau"); + CHECK(m.args == std::vector({ "", "C:/lib/x.luau" })); } { INFO("an alias past its configuration's folder"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("the configuration in 'C:/lib' names 'D:/out', which is outside it and outside the include folders", m)); CHECK(m.key == "PreprocRequireAliasRefused"); CHECK(m.args == std::vector({ "", "C:/lib", "D:/out" })); } { INFO("a configuration that could not be read"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("the configuration 'C:/lib/.luaurc' could not be read: expected value", m)); CHECK(m.key == "PreprocRequireConfigBroken"); CHECK(m.args == std::vector({ "", "C:/lib/.luaurc", "expected value" })); } { INFO("a world alias naming the disk"); + ALMessageMap::Match m; CHECK(ALMessageMap::luauRequire("the alias stands for '/x', a path on disk, which a configuration in the world may not name", m)); CHECK(m.key == "PreprocRequireAliasNamesDisk"); - CHECK(m.args[1] == "/x"); + CHECK(m.args == std::vector({ "", "/x" })); } + ALMessageMap::Match m; UNSCOPED_INFO("a reason of no known shape"); CHECK_FALSE(ALMessageMap::luauRequire("something else went wrong", m)); { diff --git a/indra/alscript/tests/almodulelook_test.cpp b/indra/alscript/tests/almodulelook_test.cpp index 199e7262eb..7ef61c54e2 100644 --- a/indra/alscript/tests/almodulelook_test.cpp +++ b/indra/alscript/tests/almodulelook_test.cpp @@ -138,6 +138,7 @@ namespace { INFO("by its stem, under the prefix"); REQUIRE(util); + REQUIRE_FALSE(util->names.empty()); CHECK(util->names[0] == "@lib/util"); CHECK(util->exports == std::vector{ "clamp" }); } @@ -145,6 +146,7 @@ namespace { INFO("a folder's init by the folder's name first"); REQUIRE(folder); + REQUIRE_FALSE(folder->names.empty()); CHECK(folder->names[0] == "@lib/net"); } const ALModuleLook::Candidate* open = named(found, "open"); @@ -158,11 +160,17 @@ namespace // The same version, another text: kept as it was read. input.texts[0].text = std::make_shared("local M = {}\nfunction M.two() end\nreturn M\n"); + found = look.look(input); + open = named(found, "open"); + REQUIRE(open); UNSCOPED_INFO("the same version: as read"); - CHECK(named(look.look(input), "open")->exports == std::vector{ "one" }); + CHECK(open->exports == std::vector{ "one" }); input.texts[0].version = 2; + found = look.look(input); + open = named(found, "open"); + REQUIRE(open); UNSCOPED_INFO("a new one: read again"); - CHECK(named(look.look(input), "open")->exports == std::vector{ "two" }); + CHECK(open->exports == std::vector{ "two" }); } TEST_CASE_METHOD(almodulelook_data, "almodulelook: SLua: checked exports of the same text win for files and open texts alike", "[almodulelook]") @@ -174,21 +182,33 @@ namespace input.lua = true; input.self = "object:self"; input.folders = { { std::string(), s.at("mods") } }; + std::vector found = look.look(input); + const ALModuleLook::Candidate* one = named(found, "made"); + REQUIRE(one); UNSCOPED_INFO("the text alone says nothing of a table made by a call"); - REQUIRE(named(look.look(input), "made")->exports.empty()); + REQUIRE(one->exports.empty()); ALLuauExports::checked(ALIncludeIdentity::ofFile(made), made_text, { "alpha", "beta" }); + found = look.look(input); + one = named(found, "made"); + REQUIRE(one); UNSCOPED_INFO("checked: what its type says"); - CHECK(named(look.look(input), "made")->exports == std::vector({ "alpha", "beta" })); + CHECK(one->exports == std::vector({ "alpha", "beta" })); auto text = std::make_shared(made_text); input.texts.push_back({ "inventory:open", "open.luau", 1, text }); ALLuauExports::checked("inventory:open", made_text, { "gamma" }); + found = look.look(input); + one = named(found, "open"); + REQUIRE(one); UNSCOPED_INFO("an open text checked"); - CHECK(named(look.look(input), "open")->exports == std::vector{ "gamma" }); + CHECK(one->exports == std::vector{ "gamma" }); input.texts[0].text = std::make_shared("local M = {}\nfunction M.delta() end\nreturn M\n"); input.texts[0].version = 2; + found = look.look(input); + one = named(found, "open"); + REQUIRE(one); UNSCOPED_INFO("its text moved on since: read as it says"); - CHECK(named(look.look(input), "open")->exports == std::vector{ "delta" }); + CHECK(one->exports == std::vector{ "delta" }); } TEST_CASE_METHOD(almodulelook_data, "almodulelook: extensions match in any case and a later check of the same text is taken", "[almodulelook]") @@ -206,16 +226,23 @@ namespace { INFO("listed, by its stem"); REQUIRE(one); + REQUIRE_FALSE(one->names.empty()); CHECK(one->names[0] == "@lib/Upper"); CHECK(one->exports == std::vector{ "one" }); } ALLuauExports::checked(ALIncludeIdentity::ofFile(upper), text, { "first" }); + found = look.look(input); + one = named(found, "Upper"); + REQUIRE(one); UNSCOPED_INFO("checked"); - CHECK(named(look.look(input), "Upper")->exports == std::vector{ "first" }); + CHECK(one->exports == std::vector{ "first" }); ALLuauExports::checked(ALIncludeIdentity::ofFile(upper), text, { "second" }); + found = look.look(input); + one = named(found, "Upper"); + REQUIRE(one); UNSCOPED_INFO("checked again, the text the same: taken"); - CHECK(named(look.look(input), "Upper")->exports == std::vector{ "second" }); + CHECK(one->exports == std::vector{ "second" }); input.lua = false; found = look.look(input); diff --git a/indra/alscript/tests/alnotecardformat_test.cpp b/indra/alscript/tests/alnotecardformat_test.cpp index 0c237fb14c..e0d807dfe2 100644 --- a/indra/alscript/tests/alnotecardformat_test.cpp +++ b/indra/alscript/tests/alnotecardformat_test.cpp @@ -142,10 +142,18 @@ namespace REQUIRE(ALNotecardFormat::namedAt(lsl, 30).has_value()); UNSCOPED_INFO("not on the call's name"); REQUIRE_FALSE(ALNotecardFormat::namedAt(lsl, 16)); - UNSCOPED_INFO("SLua's, single quotes, blanks about the bracket"); - CHECK(ALNotecardFormat::namedAt("ll.GetNumberOfNotecardLines ( 'dialog' )", 32)->name == "dialog"); - UNSCOPED_INFO("an escape taken out"); - CHECK(ALNotecardFormat::namedAt("llGetNotecardLineSync(\"say \\\"hi\\\"\", 0)", 25)->name == "say \"hi\""); + named = ALNotecardFormat::namedAt("ll.GetNumberOfNotecardLines ( 'dialog' )", 32); + { + INFO("SLua's, single quotes, blanks about the bracket"); + REQUIRE(named); + CHECK(named->name == "dialog"); + } + named = ALNotecardFormat::namedAt("llGetNotecardLineSync(\"say \\\"hi\\\"\", 0)", 25); + { + INFO("an escape taken out"); + REQUIRE(named); + CHECK(named->name == "say \"hi\""); + } UNSCOPED_INFO("a string another call is given, not"); REQUIRE_FALSE(ALNotecardFormat::namedAt("llSay(0, \"config\");", 12)); UNSCOPED_INFO("the second argument, not"); diff --git a/indra/alscript/tests/alscriptenvelope_test.cpp b/indra/alscript/tests/alscriptenvelope_test.cpp index 56f5dd0e8f..845a0e35a5 100644 --- a/indra/alscript/tests/alscriptenvelope_test.cpp +++ b/indra/alscript/tests/alscriptenvelope_test.cpp @@ -314,6 +314,7 @@ namespace } const int at = envelope.codeLine(); CAPTURE(lua); + REQUIRE(static_cast(at) < lines.size()); REQUIRE(lines[static_cast(at)] == std::string("first")); UNSCOPED_INFO("after the source's lines and the envelope's own"); REQUIRE(at == 9); @@ -445,6 +446,8 @@ namespace const std::vector plain = alscriptenvelope_data::linesOf(without.wrap()); const std::vector headed = alscriptenvelope_data::linesOf(with.wrap()); const std::vector code = alscriptenvelope_data::linesOf(with.expanded); + REQUIRE(static_cast(without.codeLine()) + code.size() <= plain.size()); + REQUIRE(static_cast(with.codeLine()) + code.size() <= headed.size()); for (size_t i = 0; i < code.size(); ++i) { CAPTURE(i); @@ -466,11 +469,13 @@ namespace for (const bool lua : { false, true }) { CAPTURE(lua); - const ALScriptEnvelope sent = alscriptenvelope_data::made(lua, true); - ALScriptEnvelope later = alscriptenvelope_data::made(lua, true); - ALUploadHeader again = *ALUploadHeader::parse(later.header, lua); - again.date = "2026-10-08 09:00:00"; - later.header = again.write(lua); + const ALScriptEnvelope sent = alscriptenvelope_data::made(lua, true); + ALScriptEnvelope later = alscriptenvelope_data::made(lua, true); + const std::optional parsed = ALUploadHeader::parse(later.header, lua); + REQUIRE(parsed.has_value()); + ALUploadHeader again = *parsed; + again.date = "2026-10-08 09:00:00"; + later.header = again.write(lua); const std::optional held = ALScriptEnvelope::parse(sent.wrap()); const std::optional came = ALScriptEnvelope::parse(later.wrap()); @@ -491,9 +496,10 @@ namespace REQUIRE(ALScriptEnvelope::comparable(came->source, lua, true, false)); // The hash is of what was sent, the header aside: worked out // again from the halves read back, it is what the header says. + const std::optional says = ALUploadHeader::parse(came->header, lua); + REQUIRE(says.has_value()); UNSCOPED_INFO("the hash again"); - REQUIRE(ALUploadHeader::hashOf(came->compileTarget, came->source, came->expanded) == - ALUploadHeader::parse(came->header, lua)->hash); + REQUIRE(ALUploadHeader::hashOf(came->compileTarget, came->source, came->expanded) == says->hash); } } } diff --git a/indra/alscript/tests/alscriptformatter_test.cpp b/indra/alscript/tests/alscriptformatter_test.cpp index 9b2153db23..46f4935112 100644 --- a/indra/alscript/tests/alscriptformatter_test.cpp +++ b/indra/alscript/tests/alscriptformatter_test.cpp @@ -343,8 +343,10 @@ namespace REQUIRE(each.size() == size_t(6)); UNSCOPED_INFO("the broken line's, breaks and all"); CHECK(each[1] + "\n" == want.substr(want.find(" local r"), want.find(" print(string") - want.find(" local r"))); - UNSCOPED_INFO("a line asked for"); - CHECK(ALScriptFormatter::formatEach(in, options, 2, 2)[1] == in.substr(in.find(" local r"), in.find(" -- sent") + 8 - in.find(" local r"))); + const std::vector asked = ALScriptFormatter::formatEach(in, options, 2, 2); + REQUIRE(asked.size() > size_t(1)); + UNSCOPED_INFO("a line not asked for: as written"); + CHECK(asked[1] == in.substr(in.find(" local r"), in.find(" -- sent") + 8 - in.find(" local r"))); // From below the first line: every line still one of its own, the // blank ones that the whole text's runs would take out too. const std::vector blanks = ALScriptFormatter::formatEach("\n\n\n\n\nprint(1)\n", options, -1, 5); From 212f053b0ac3019d2bd6ad35a528d82faa83acd5 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 03:19:34 -0400 Subject: [PATCH 5/6] alscript's conversion, optimizer and weight tests check what they say they check Old test bugs #462's idiom pass found, fixed. - allsltoslua: - "state_entry's, kept" looked for ll.OwnerSay, which the conversion writes as print, so the order it checked was against npos and could not fail; it finds both, then checks the order; - two order checks passed when what they ordered was missing; each first checks it is there; - the "handler over both" checks ran only if some span started on LSL line 4; the span is required now. - allsloptimizer: - "Mono: what it wrote weighs what it says" weighed nothing: when Mono came to weigh a list's shapes against the helpers they bring in, its "l + a" check was loosened to "l = l" and given that message. Its weight is now checked against the weigher's, and "l = l" keeps a message of its own; - each ALLSLTraits::of() is required before it is read. - alscriptweight: - three messages were built before the call that fills their reason (a definitions load, two rewrites): each call comes first now; - "where it is declared" and "where it ends" stood one check off their checks; count's end was checked as at least line 8, and is line 10, its end; - a handler looked up by name is required before it is read. - allslreferencecheck (lscript): "Tailslide compiles it" was built before the compile filled its reason. 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, in both solvers' runs where the file has two. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/tests/allsloptimizer_test.cpp | 76 +++++++++++++------ indra/alscript/tests/allsltoslua_test.cpp | 27 ++++--- indra/alscript/tests/alscriptweight_test.cpp | 32 +++++--- .../tests/allslreferencecheck_test.cpp | 3 +- 4 files changed, 91 insertions(+), 47 deletions(-) diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index 5f936f1ae4..f3dac85cb2 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -1504,47 +1504,69 @@ namespace { INFO("llGetPos is not"); CHECK_FALSE(ALLSLTraits::pure("llGetPos")); - CHECK(ALLSLTraits::of("llGetPos")->mustUse); + const ALLSLTraits::Trait* get_pos = ALLSLTraits::of("llGetPos"); + REQUIRE(get_pos); + CHECK(get_pos->mustUse); } // What SLua makes of each, from the same definitions. { INFO("llGetSubString takes its second and third from one in SLua's ll"); - CHECK(ALLSLTraits::of("llGetSubString")->slua == ALLSLTraits::SluaIndexArgs); - CHECK(ALLSLTraits::of("llGetSubString")->sluaIndexArgs == 0x6); + const ALLSLTraits::Trait* sub_string = ALLSLTraits::of("llGetSubString"); + REQUIRE(sub_string); + CHECK(sub_string->slua == ALLSLTraits::SluaIndexArgs); + CHECK(sub_string->sluaIndexArgs == 0x6); } + const ALLSLTraits::Trait* find_list = ALLSLTraits::of("llListFindList"); + REQUIRE(find_list); UNSCOPED_INFO("llListFindList answers an index, and SLua deprecates it"); - CHECK(ALLSLTraits::of("llListFindList")->slua == (ALLSLTraits::SluaIndexResult | ALLSLTraits::SluaDeprecated)); + CHECK(find_list->slua == (ALLSLTraits::SluaIndexResult | ALLSLTraits::SluaDeprecated)); + const ALLSLTraits::Trait* same_group = ALLSLTraits::of("llSameGroup"); + REQUIRE(same_group); UNSCOPED_INFO("llSameGroup answers a boolean"); - CHECK(ALLSLTraits::of("llSameGroup")->slua == ALLSLTraits::SluaBool); + CHECK(same_group->slua == ALLSLTraits::SluaBool); + const ALLSLTraits::Trait* timer_event = ALLSLTraits::of("llSetTimerEvent"); + REQUIRE(timer_event); UNSCOPED_INFO("llSetTimerEvent is llcompat's alone"); - CHECK(ALLSLTraits::of("llSetTimerEvent")->slua == ALLSLTraits::SluaRemoved); + CHECK(timer_event->slua == ALLSLTraits::SluaRemoved); { INFO("llSay is as it was"); - CHECK(ALLSLTraits::of("llSay")->slua == 0); - REQUIRE_FALSE(ALLSLTraits::of("llSay")->sluaUse); + const ALLSLTraits::Trait* say = ALLSLTraits::of("llSay"); + REQUIRE(say); + CHECK(say->slua == 0); + CHECK_FALSE(say->sluaUse); } { INFO("llAbs has math.abs in its stead"); - REQUIRE(ALLSLTraits::of("llAbs")->sluaUse); - CHECK(std::string(ALLSLTraits::of("llAbs")->sluaUse) == "math.abs"); + const ALLSLTraits::Trait* ll_abs = ALLSLTraits::of("llAbs"); + REQUIRE(ll_abs); + REQUIRE(ll_abs->sluaUse); + CHECK(std::string(ll_abs->sluaUse) == "math.abs"); } // How long each sleeps, under LSO and under Mono. { INFO("llSetPos sleeps 0.2 s"); - CHECK(ALLSLTraits::of("llSetPos")->sleep == 0.2f); - CHECK(ALLSLTraits::of("llSetPos")->monoSleep == 0.2f); + const ALLSLTraits::Trait* set_pos = ALLSLTraits::of("llSetPos"); + REQUIRE(set_pos); + CHECK(set_pos->sleep == 0.2f); + CHECK(set_pos->monoSleep == 0.2f); } + const ALLSLTraits::Trait* params_fast = ALLSLTraits::of("llSetLinkPrimitiveParamsFast"); + REQUIRE(params_fast); UNSCOPED_INFO("llSetLinkPrimitiveParamsFast does not"); - CHECK(ALLSLTraits::of("llSetLinkPrimitiveParamsFast")->sleep == 0.0f); + CHECK(params_fast->sleep == 0.0f); { INFO("llTextBox sleeps under LSO alone"); - CHECK(ALLSLTraits::of("llTextBox")->sleep == 1.0f); - CHECK(ALLSLTraits::of("llTextBox")->monoSleep == 0.0f); + const ALLSLTraits::Trait* text_box = ALLSLTraits::of("llTextBox"); + REQUIRE(text_box); + CHECK(text_box->sleep == 1.0f); + CHECK(text_box->monoSleep == 0.0f); } { INFO("llOpenFloater under Mono alone"); - CHECK(ALLSLTraits::of("llOpenFloater")->sleep == 0.0f); - CHECK(ALLSLTraits::of("llOpenFloater")->monoSleep == 0.2f); + const ALLSLTraits::Trait* open_floater = ALLSLTraits::of("llOpenFloater"); + REQUIRE(open_floater); + CHECK(open_floater->sleep == 0.0f); + CHECK(open_floater->monoSleep == 0.2f); } // What item of the object's an argument names, by its LSL name or SLua's. using Item = ALLSLTraits::Item; @@ -1999,11 +2021,11 @@ namespace UNSCOPED_INFO("no error in what was written: " + p.message + "\n" + r.text); REQUIRE(p.severity != ALScriptProblem::Severity::Error); } - return r.text; + return r; }; const auto has = [](const std::string& text, const std::string& what) { return text.find(what) != std::string::npos; }; - const std::string lso = run(ALLSLOptimizer::Target::LSO); + const std::string lso = run(ALLSLOptimizer::Target::LSO).text; UNSCOPED_INFO("x != -1 as ~x, in a condition: " + lso); CHECK(has(lso, "if (~a)")); UNSCOPED_INFO("finds, both ways round, inside &&: " + lso); @@ -2041,7 +2063,8 @@ namespace UNSCOPED_INFO("elements whose order would show stay together"); CHECK_FALSE(has(lso, "l + llFrand(1) + llFrand(2)")); - const std::string mono = run(ALLSLOptimizer::Target::Mono); + const ALLSLOptimizer::Result monoRun = run(ALLSLOptimizer::Target::Mono); + const std::string& mono = monoRun.text; UNSCOPED_INFO("Mono: x != -1 as ~x: " + mono); CHECK(has(mono, "if (~a)")); // Mono's ldc.i4 is five bytes, -1 and 1 too. @@ -2053,10 +2076,17 @@ namespace CHECK(has(mono, "g = ~-b;")); UNSCOPED_INFO("Mono: ++a"); CHECK(has(mono, "++a;")); - UNSCOPED_INFO("Mono: what it wrote weighs what it says"); - REQUIRE(mono.find("l = l") != std::string::npos); + // Its list's shapes weighed against the helpers they bring in, and + // whichever was smaller kept, with its weight. + UNSCOPED_INFO("Mono: l still added to, in whichever shape was kept"); + CHECK(has(mono, "l = l")); + { + INFO("Mono: what it wrote weighs what it says"); + CHECK(monoRun.weight.compiled); + CHECK(monoRun.weight.total == ALScriptWeigh::mono(mono).total); + } - const std::string luau = run(ALLSLOptimizer::Target::Luau); + const std::string luau = run(ALLSLOptimizer::Target::Luau).text; UNSCOPED_INFO("Luau: x != -1 stays, ~ a library call there: " + luau); CHECK(has(luau, "if (a != -1)")); UNSCOPED_INFO("Luau: x - 1 stays"); diff --git a/indra/alscript/tests/allsltoslua_test.cpp b/indra/alscript/tests/allsltoslua_test.cpp index c85486f357..4301c6f0ae 100644 --- a/indra/alscript/tests/allsltoslua_test.cpp +++ b/indra/alscript/tests/allsltoslua_test.cpp @@ -257,7 +257,9 @@ namespace CHECK(has(r, "ll.Say(0, `{greet(detected[1]:getName())} {gCount}`)")); { INFO("state_entry last"); - CHECK(r.text.find("-- state_entry") > r.text.find("LLEvents:on")); + const size_t entered = r.text.find("-- state_entry"); + CHECK(entered != std::string::npos); + CHECK(entered > r.text.find("LLEvents:on")); CHECK(has(r, "ll.Say(0, greet(\"world\"))")); } UNSCOPED_INFO("the note over its line"); @@ -415,7 +417,9 @@ namespace { INFO("a state not there said"); REQUIRE(looked != std::string::npos); - CHECK(looked < r.text.find("local leaving")); + const size_t leaving = r.text.find("local leaving"); + CHECK(leaving != std::string::npos); + CHECK(looked < leaving); } require_checks_clean(r); ALLSLToSLua::Options typed; @@ -2415,14 +2419,12 @@ namespace UNSCOPED_INFO("their SLua apart"); CHECK((shared[0]->sluaLast < shared[1]->sluaFirst || shared[1]->sluaLast < shared[0]->sluaFirst)); // The handler, over more than one LSL line, still holds both. - for (const ALLSLToSLua::Span& each : r.spans) + const auto handler = std::find_if(r.spans.begin(), r.spans.end(), [](const ALLSLToSLua::Span& each) { return each.lslFirst == 4; }); { - if (each.lslFirst == 4) - { - INFO("the handler over both"); - REQUIRE(each.sluaFirst < shared[0]->sluaFirst); - REQUIRE(each.sluaLast >= shared[1]->sluaLast); - } + INFO("the handler over both"); + REQUIRE(handler != r.spans.end()); + CHECK(handler->sluaFirst < shared[0]->sluaFirst); + CHECK(handler->sluaLast >= shared[1]->sluaLast); } } @@ -2559,8 +2561,11 @@ namespace CAPTURE(entry.text); { INFO("state_entry's, kept"); - CHECK(has(entry, "-- set up\n")); - CHECK(entry.text.find("-- set up") < entry.text.find("ll.OwnerSay")); + const size_t set_up = entry.text.find("-- set up\n"); + const size_t said = entry.text.find("print(\"hi\")"); + CHECK(set_up != std::string::npos); + CHECK(said != std::string::npos); + CHECK(set_up < said); } } diff --git a/indra/alscript/tests/alscriptweight_test.cpp b/indra/alscript/tests/alscriptweight_test.cpp index af1b88a4c4..ba50732577 100644 --- a/indra/alscript/tests/alscriptweight_test.cpp +++ b/indra/alscript/tests/alscriptweight_test.cpp @@ -192,12 +192,13 @@ namespace REQUIRE(greet); REQUIRE(count); } - UNSCOPED_INFO("where it is declared"); - CHECK(greet->line == 0); - UNSCOPED_INFO("and where it ends"); - CHECK(count->line == 4); - UNSCOPED_INFO("to its last line"); - CHECK(count->endLine >= 8); + { + INFO("where each is declared"); + CHECK(greet->line == 0); + CHECK(count->line == 4); + } + UNSCOPED_INFO("and where count ends: the line of its end"); + CHECK(count->endLine == 10); const ALScriptWeight::Part* nothing = named(weight, "nothing"); { INFO("a function that does nothing weighs least:" + listed(weight)); @@ -719,8 +720,12 @@ namespace REQUIRE(named(lost, "twice") != nullptr); UNSCOPED_INFO("without a place"); CHECK(named(lost, "twice")->line == -1); - UNSCOPED_INFO("the handler still placed"); - CHECK(named(lost, "state_entry")->line == 3); + const ALScriptWeight::Part* lost_entry = named(lost, "state_entry"); + { + INFO("the handler still placed" + listed(lost)); + REQUIRE(lost_entry); + CHECK(lost_entry->line == 3); + } } // A long list parses on every platform: each element nests the parse two @@ -1100,8 +1105,9 @@ namespace llifstream in(std::string(AL_LSL_DEFINITIONS_DIR) + "/secondlife.d.luau", std::ios::binary); std::stringstream definitions; definitions << in.rdbuf(); + const bool loaded = service.loadDefinitions(definitions.str(), error); UNSCOPED_INFO("definitions: " + error); - REQUIRE(service.loadDefinitions(definitions.str(), error)); + REQUIRE(loaded); std::string said; for (const ALScriptProblem& p : service.check(after)) { @@ -1227,14 +1233,16 @@ namespace UNSCOPED_INFO("nothing made of it"); CHECK(rewrite.edits.empty()); { + const bool made = ALLuauSharedStart::rewrite("ll.OwnerSay(\"" + start + "alpha\")\n", start, strings, rewrite, error); INFO("a string not written out: " + error); - CHECK_FALSE(ALLuauSharedStart::rewrite("ll.OwnerSay(\"" + start + "alpha\")\n", start, strings, rewrite, error)); + CHECK_FALSE(made); CHECK(error.find("not every one") != std::string::npos); } { + const bool made = ALLuauSharedStart::rewrite("local greeting = \"" + start + "\"\nll.OwnerSay(greeting .. \"alpha\")\nll.OwnerSay(greeting .. \"bravo\")\n", + start, strings, rewrite, error); INFO("none written out, a local joined on: " + error); - CHECK_FALSE(ALLuauSharedStart::rewrite("local greeting = \"" + start + "\"\nll.OwnerSay(greeting .. \"alpha\")\nll.OwnerSay(greeting .. \"bravo\")\n", - start, strings, rewrite, error)); + CHECK_FALSE(made); CHECK(error.find("none of these strings") != std::string::npos); } diff --git a/indra/lscript/tests/allslreferencecheck_test.cpp b/indra/lscript/tests/allslreferencecheck_test.cpp index 2ed9ed57c8..77c6d89d8c 100644 --- a/indra/lscript/tests/allslreferencecheck_test.cpp +++ b/indra/lscript/tests/allslreferencecheck_test.cpp @@ -384,8 +384,9 @@ namespace "default { state_entry() { llOwnerSay((string)(a + b + c + d + 4294967297 - 0x1FFFFFFFF)); } }\n"; std::vector ours; std::string why; + const bool compiled = ALScriptWeigh::tailslideLSO(text, ours, why); UNSCOPED_INFO("Tailslide compiles it: " + why); - CHECK(ALScriptWeigh::tailslideLSO(text, ours, why)); + CHECK(compiled); const ALLSLReference::Result ll = ALLSLReference::compile(text, Target::LSO); CHECK(ll.ok); CHECK(ours == ll.image); From 36be676c70bf5b6886b704481739425fd57f8c8a Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 04:09:47 -0400 Subject: [PATCH 6/6] alluaufragment says why the grid's builtins did not load Its message was built from error before loadBuiltins filled it, so a failed load said nothing; the load comes first now. Found by CodeRabbit's review of #462, the same mistake the other follow-up commits fix elsewhere. A missing builtins file now fails the check with the load's reason, in both solvers' runs. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/tests/alluaufragment_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/alscript/tests/alluaufragment_test.cpp b/indra/alscript/tests/alluaufragment_test.cpp index ffec37d75f..69265f73fa 100644 --- a/indra/alscript/tests/alluaufragment_test.cpp +++ b/indra/alscript/tests/alluaufragment_test.cpp @@ -550,8 +550,9 @@ namespace // The converter reads Tailslide's table of LSL's builtins, which the // LSL service fills for the whole process. ALLSLService lsl; + const bool builtins = lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error); UNSCOPED_INFO("builtins loaded: " + error); - CHECK(lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error)); + CHECK(builtins); Tally tally; for (const std::string& name : files) {