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/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/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/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/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/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/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) { 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")); 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/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/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/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/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); 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); } 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"); } } 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);