From 44a16314b025099ffd0b16ae5f55e91664636d5f Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 01:24:59 -0400 Subject: [PATCH 1/7] The Catch2 main tags every case with its test file's name Each test file's CTest entry chose its cases by the [#] tag Catch2's --filenames-as-tags gives, passed on its command line. The main now sets that itself, as it sets declared order, so a file's run names its file with one tag, and a filter of several files is one argument. That one argument is what a per-case discovery needs: Catch.cmake re-reads its arguments through ARGN, and a TEST_SPEC holding the flag and a filter splits at the ; between them, keeping only the flag. The per-file entries and /stray_cases no longer pass the flag; a main that stopped tagging would fail them all, the files' filters matching nothing (exit 2) and the strays' everything. Co-Authored-By: Claude Opus 5.5 --- indra/cmake/ALAddTestBinary.cmake | 29 +++++++++++++++-------------- indra/test/altestmain.cpp | 4 ++++ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index 15cd931401..15dcbfe082 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -30,17 +30,18 @@ endif() # own, since every other test links the code it tests. # # Each test file is a CTest entry, /, that runs the file's -# test cases -- chosen by the tag Catch2's --filenames-as-tags gives each case -# from the name of its file -- in one process, as an executable to a test file -# ran them. Naming a tag would also choose the file's hidden cases, such as its -# benchmarks, so the filter leaves those out, as a run with no filter does. A -# filter that matches nothing exits 2, so a file whose test cases went missing -# fails rather than passing empty. A stem names one file in a binary, and a -# file belongs to one binary, or to one at each ISA tier. The tag is the -# file's name less its last extension, the stem here too. A case written in a -# header, or in a SUPPORT library, is tagged with that file's name and no -# file's entry would run it, so /stray_cases fails when the binary has -# one: it runs whatever no file's tag names, and expects nothing to run. +# test cases -- chosen by the tag the main gives each case from the name of +# its file, as Catch2's --filenames-as-tags does -- in one process, as an +# executable to a test file ran them. Naming a tag would also choose the +# file's hidden cases, such as its benchmarks, so the filter leaves those out, +# as a run with no filter does. A filter that matches nothing exits 2, so a +# file whose test cases went missing fails rather than passing empty. A stem +# names one file in a binary, and a file belongs to one binary, or to one at +# each ISA tier. The tag is the file's name less its last extension, the stem +# here too. A case written in a header, or in a SUPPORT library, is tagged +# with that file's name and no file's entry would run it, so +# /stray_cases fails when the binary has one: it runs whatever no file's +# tag names, and expects nothing to run. # # /combined runs all of them in one process. It is labelled # combined_pending and manual, which the default run leaves out, until the @@ -247,7 +248,7 @@ function(al_add_test_binary name) foreach(stem IN LISTS stems) add_test( NAME ${target}/${stem} - COMMAND ${arg_LAUNCHER} $ --filenames-as-tags "[#${stem}]~[.]" ${arguments} + COMMAND ${arg_LAUNCHER} $ "[#${stem}]~[.]" ${arguments} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) set_tests_properties( @@ -276,8 +277,8 @@ function(al_add_test_binary name) add_test( NAME ${target}/stray_cases COMMAND - ${CMAKE_COMMAND} "-DEXE=$" "-DARGS=--filenames-as-tags|${strays}~[.]" - -DEXPECT_EXIT=2 -P ${INDRA_SOURCE_DIR}/test/run_and_expect.cmake + ${CMAKE_COMMAND} "-DEXE=$" "-DARGS=${strays}~[.]" -DEXPECT_EXIT=2 -P + ${INDRA_SOURCE_DIR}/test/run_and_expect.cmake WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) set_tests_properties( diff --git a/indra/test/altestmain.cpp b/indra/test/altestmain.cpp index 7869441d52..849c67cd37 100644 --- a/indra/test/altestmain.cpp +++ b/indra/test/altestmain.cpp @@ -145,6 +145,10 @@ int main(int argc, char* argv[]) // Declared order, as TUT ran a group's tests, until no test leans on the // ones before it. --order on the command line still wins. session.configData().runOrder = Catch::TestRunOrder::Declared; + // Every case tagged with its file's name, [#], as + // --filenames-as-tags tags them: a test file's run names its file with + // one tag, and a filter of several files is one argument. + session.configData().filenamesAsTags = true; if (const int result = session.applyCommandLine(argc, argv); result != 0) { From 156563a974d0269e42ced1dbb371132919689732 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 01:25:09 -0400 Subject: [PATCH 2/7] al_add_test_binary runs some files again under another environment alscript's tests that check SLua run a second time under Luau's new type solver, told by AL_TEST_LUAU_SOLVER=new, as their TUT executables each had a second CTest entry. RERUN names the second run, RERUN_SOURCES its files, which must be among SOURCES, and RERUN_ENVIRONMENT what it adds to the binary's environment. Each file of the run is an entry of its own, _/, in a process of its own as the binary's own entries are. With AL_TEST_PER_CASE, a second discovery registers each of their cases as _/, chosen by their files' tags, with the run's environment set on each. Co-Authored-By: Claude Opus 5.5 --- indra/cmake/ALAddTestBinary.cmake | 173 ++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 59 deletions(-) diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index 15dcbfe082..ac294aa44c 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -13,6 +13,7 @@ endif() # [LIBRARIES ...] [SUPPORT ...] # [INCLUDES ...] [DEFINES ...] [ENVIRONMENT ...] # [GL] [PYTHON] [ISA_TIER ] [RESOURCE_LOCK ...] [LAUNCHER ...] +# [RERUN RERUN_SOURCES ... RERUN_ENVIRONMENT ...] # [COMBINED]) # # Builds the test files into one Catch2 executable, , with the harness @@ -62,13 +63,19 @@ endif() # a command each entry runs the binary through, its path and arguments after # the command's own: llcorehttp's tests run under the Python peer that serves # their requests. A per-case entry, and its discovery, go through it too. +# +# RERUN runs some of the files a second time, each in a process of its own, +# with RERUN_ENVIRONMENT added to the binary's: _/, or +# with AL_TEST_PER_CASE each of their cases as _/. alscript's +# tests that check SLua run again under Luau's new type solver, as a scripter +# may choose it. function(al_add_test_binary name) cmake_parse_arguments( PARSE_ARGV 1 arg "GL;PYTHON;COMBINED" - "PROJECT;ISA_TIER" - "SOURCES;UNIT;LIBRARIES;SUPPORT;INCLUDES;DEFINES;ENVIRONMENT;RESOURCE_LOCK;LAUNCHER" + "PROJECT;ISA_TIER;RERUN" + "SOURCES;UNIT;LIBRARIES;SUPPORT;INCLUDES;DEFINES;ENVIRONMENT;RESOURCE_LOCK;LAUNCHER;RERUN_SOURCES;RERUN_ENVIRONMENT" ) if(NOT arg_PROJECT) message(FATAL_ERROR "al_add_test_binary(${name}): PROJECT is required") @@ -82,6 +89,12 @@ function(al_add_test_binary name) "al_add_test_binary(${name}): unexpected arguments: ${arg_UNPARSED_ARGUMENTS}" ) endif() + if(arg_RERUN_SOURCES AND NOT arg_RERUN) + message(FATAL_ERROR "al_add_test_binary(${name}): RERUN_SOURCES needs RERUN to name the run") + endif() + if(arg_RERUN AND NOT arg_RERUN_SOURCES) + message(FATAL_ERROR "al_add_test_binary(${name}): RERUN ${arg_RERUN} names no RERUN_SOURCES") + endif() set(target ${name}) if(arg_ISA_TIER) if(NOT arg_ISA_TIER MATCHES "^(baseline|v2|v3|v4)$") @@ -117,6 +130,17 @@ function(al_add_test_binary name) endif() set_property(GLOBAL PROPERTY AL_TEST_SOURCE_${key} ${target}) endforeach() + set(rerun_stems) + foreach(source IN LISTS arg_RERUN_SOURCES) + get_filename_component(stem "${source}" NAME_WLE) + if(NOT stem IN_LIST stems) + message( + FATAL_ERROR + "al_add_test_binary(${name}): RERUN_SOURCES' ${source} is not one of its SOURCES" + ) + endif() + list(APPEND rerun_stems ${stem}) + endforeach() # vcpkg's toolchain hangs an app-local copy step on every executable it # sees declared; a test runs from the build tree, as al_add_test explains. @@ -183,6 +207,13 @@ function(al_add_test_binary name) endif() endif() + # The runs, each a prefix to its entries' names: the binary's own, and + # RERUN's of its files. + set(prefixes ${target}) + if(arg_RERUN) + list(APPEND prefixes ${target}_${arg_RERUN}) + endif() + if(AL_TEST_PER_CASE) # Beside Catch2's package config, where vcpkg puts it, and not on the # module path. @@ -203,67 +234,91 @@ function(al_add_test_binary name) if(arg_LAUNCHER) set_target_properties(${target} PROPERTIES CROSSCOMPILING_EMULATOR "${arg_LAUNCHER}") endif() - # The main makes Catch2's "all skipped" CTest's 125, so a skip is not - # left to the discovery's own code for it. - catch_discover_tests( - ${target} - TEST_PREFIX "${target}/" - TEST_LIST ${target}_cases - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - EXTRA_ARGS ${arguments} - DISCOVERY_MODE PRE_TEST - SKIP_IS_FAILURE - DL_PATHS ${SHARED_LIB_STAGING_DIR} - PROPERTIES ${per_case_properties} - ) - # The environment, labels and lock a file's entry has are lists, which - # catch_discover_tests flattens with the rest of its PROPERTIES, and a - # CTest script cannot append to a property: a script run after the - # discovery sets them on each case whole. The labels are a file entry's, - # not the cases' Catch2 tags, so that -L and -LE choose alike either way. - # The library path is DL_PATHS', and not AL_TEST_ENVIRONMENT's, whose - # generator expressions a script written here would leave unevaluated. - set(case_script "${CMAKE_CURRENT_BINARY_DIR}/${target}_cases.cmake") - set(case_properties "LABELS [==[${labels}]==]") - if(environment) - string(APPEND case_properties " ENVIRONMENT [==[${environment}]==]") - endif() - if(arg_RESOURCE_LOCK) - string(APPEND case_properties " RESOURCE_LOCK [==[${arg_RESOURCE_LOCK}]==]") - endif() - # The list holds each name as Catch2 escaped it for a list: [ and ] as - # the bytes 2 and 3. The test is registered under the name itself. - file( - WRITE "${case_script}" - "string(ASCII 2 open)\n" - "string(ASCII 3 close)\n" - "foreach(case IN LISTS ${target}_cases)\n" - " string(REPLACE \"\${open}\" \"[\" case \"\${case}\")\n" - " string(REPLACE \"\${close}\" \"]\" case \"\${case}\")\n" - " set_tests_properties(\"\${case}\" PROPERTIES ${case_properties})\n" - "endforeach()\n" - ) - set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${case_script}") - else() - foreach(stem IN LISTS stems) - add_test( - NAME ${target}/${stem} - COMMAND ${arg_LAUNCHER} $ "[#${stem}]~[.]" ${arguments} + foreach(prefix IN LISTS prefixes) + # A rerun's cases are its files', by the tag each file's name gives + # them, its hidden ones left out as the binary's own run leaves them. + set(spec) + set(run_environment ${environment}) + if(NOT prefix STREQUAL target) + set(files) + foreach(stem IN LISTS rerun_stems) + list(APPEND files "[#${stem}]~[.]") + endforeach() + list(JOIN files "," files) + set(spec "${files}") + list(APPEND run_environment ${arg_RERUN_ENVIRONMENT}) + endif() + # The main makes Catch2's "all skipped" CTest's 125, so a skip is not + # left to the discovery's own code for it. + catch_discover_tests( + ${target} + TEST_PREFIX "${prefix}/" + TEST_LIST ${prefix}_cases + TEST_SPEC ${spec} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + EXTRA_ARGS ${arguments} + DISCOVERY_MODE PRE_TEST + SKIP_IS_FAILURE + DL_PATHS ${SHARED_LIB_STAGING_DIR} + PROPERTIES ${per_case_properties} ) - set_tests_properties( - ${target}/${stem} - PROPERTIES - ENVIRONMENT "${environment}" - ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" - LABELS "${labels}" - DISABLED ${disabled} - SKIP_RETURN_CODE 125 - TIMEOUT ${timeout} - ) + # The environment, labels and lock a file's entry has are lists, which + # catch_discover_tests flattens with the rest of its PROPERTIES, and a + # CTest script cannot append to a property: a script run after the + # discovery sets them on each case whole. The labels are a file entry's, + # not the cases' Catch2 tags, so that -L and -LE choose alike either way. + # The library path is DL_PATHS', and not AL_TEST_ENVIRONMENT's, whose + # generator expressions a script written here would leave unevaluated. + set(case_script "${CMAKE_CURRENT_BINARY_DIR}/${prefix}_cases.cmake") + set(case_properties "LABELS [==[${labels}]==]") + if(run_environment) + string(APPEND case_properties " ENVIRONMENT [==[${run_environment}]==]") + endif() if(arg_RESOURCE_LOCK) - set_tests_properties(${target}/${stem} PROPERTIES RESOURCE_LOCK "${arg_RESOURCE_LOCK}") + string(APPEND case_properties " RESOURCE_LOCK [==[${arg_RESOURCE_LOCK}]==]") + endif() + # The list holds each name as Catch2 escaped it for a list: [ and ] as + # the bytes 2 and 3. The test is registered under the name itself. + file( + WRITE "${case_script}" + "string(ASCII 2 open)\n" + "string(ASCII 3 close)\n" + "foreach(case IN LISTS ${prefix}_cases)\n" + " string(REPLACE \"\${open}\" \"[\" case \"\${case}\")\n" + " string(REPLACE \"\${close}\" \"]\" case \"\${case}\")\n" + " set_tests_properties(\"\${case}\" PROPERTIES ${case_properties})\n" + "endforeach()\n" + ) + set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${case_script}") + endforeach() + else() + foreach(prefix IN LISTS prefixes) + set(run_stems ${stems}) + set(run_environment ${environment}) + if(NOT prefix STREQUAL target) + set(run_stems ${rerun_stems}) + list(APPEND run_environment ${arg_RERUN_ENVIRONMENT}) endif() + foreach(stem IN LISTS run_stems) + add_test( + NAME ${prefix}/${stem} + COMMAND ${arg_LAUNCHER} $ "[#${stem}]~[.]" ${arguments} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties( + ${prefix}/${stem} + PROPERTIES + ENVIRONMENT "${run_environment}" + ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" + LABELS "${labels}" + DISABLED ${disabled} + SKIP_RETURN_CODE 125 + TIMEOUT ${timeout} + ) + if(arg_RESOURCE_LOCK) + set_tests_properties(${prefix}/${stem} PROPERTIES RESOURCE_LOCK "${arg_RESOURCE_LOCK}") + endif() + endforeach() endforeach() # Every case no file's tag names: a filter of nothing but exclusions From abc1c90ca9d3fbcff21a97c47acf794b6375d0cb Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 01:25:15 -0400 Subject: [PATCH 3/7] tut_parity.py holds a file's second run to TUT's second run of it A TUT executable run twice, its second CTest entry named for its test, an underscore and the run (alscript's _newsolver), is recorded apart, under @, as a test built at another ISA tier is. The run is told from the executable in the entry's command, so a test whose own name has an underscore is not taken for one. check finds a file's second Catch2 run by its entries' binary, _/ (al_add_test_binary's RERUN), and holds it to TUT's run of that name. --unrun takes a key naming one run of a file, as the table names it (" (newsolver)"), for a site only that run leaves out: a branch the other solver's run alone takes is reviewed for the run that skips it, and still held to running in the other. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 96 +++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 9342074861..3067150886 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -15,7 +15,10 @@ under a Python peer), as check finds a Catch2 entry's binary. A test built at other x86-64 tiers too (an entry ending _baseline, _v2, _v3, _v4) is - kept a tier apart, under @. + kept a tier apart, under @; so is a second run of a + test's executable under another environment, an entry named for + the executable's test, an underscore and the run (alscript's + _newsolver), under @. check after converting and building: runs each converted file's CTest entry, /, as XML, and matches the cases to the TUT results through tut2catch.py's mapping. tut2catch.py must be @@ -25,6 +28,8 @@ tier; the tree's own binary is held to TUT's untiered run (or, where there is none, its run at the tree's tier, AL_ISA_TIER). A tier the host does not run skips on both sides, which is listed. + A file run a second time has an entry _/ + (al_add_test_binary's RERUN), held to TUT's run of that name. Parity holds for a file when: - its inputs are the ones the run is of: the converted file is no newer @@ -73,7 +78,8 @@ command, one under an #if that never ran is listed. So is one --unrun names, reviewed as never running in a passing case on this host: in a loop that should not turn, or a branch of an if constexpr the build - discards; + discards -- or, named for one run of the file, in a branch only the + other runs take; A test the mapping says TUT never ran (past its group's limit) or this host does not compile is listed, not counted against parity, as is an assertion @@ -183,13 +189,25 @@ def isa_tier(name): return m.group(1) if m else None -def tut_identity(name): - """The test file a TUT CTest entry runs, by stem, and its ISA tier.""" +def tut_identity(name, command=()): + """The test file a TUT CTest entry runs, by stem, and which of its runs + the entry is: its ISA tier, or the name of a second run of the test's + executable under another environment -- the entry is named for the + executable's test, an underscore and the run, which the command, where + given, tells from a test whose own name has an underscore. None for the + test's own run.""" m = re.search(r"TEST_RUNNER_(\w+)$", name) or re.search(r"_TEST_(\w+)$", name) if not m: return name, None - tier = isa_tier(m.group(1)) - return (m.group(1)[:-len(tier) - 1] if tier else m.group(1)) + "_test", tier + test = m.group(1) + tier = isa_tier(test) + if tier: + return test[:-len(tier) - 1] + "_test", tier + for word in command: + runs = re.search(r"_TEST_(\w+)$", os.path.splitext(os.path.basename(word))[0]) + if runs and test.startswith(runs.group(1) + "_"): + return runs.group(1) + "_test", test[len(runs.group(1)) + 1:] + return test + "_test", None def executable_in(command, target): @@ -237,7 +255,8 @@ def record(args): if test is None: print(f"{name}: no such CTest entry", file=sys.stderr) return 2 - stem, tier = tut_identity(name) + stem, variant = tut_identity(name, test["command"]) + tier = variant if variant in TIERS else None # What the results are of, so that check can tell they are of the # source the mapping was made from. source, digest = tut_source(stem) @@ -263,9 +282,11 @@ def record(args): "result": {"ok": "ok", "skipped known failure": "skip"}.get(result, "fail"), "said": line[m.start("result"):].strip()}) # Each tier's run is kept apart: one the host cannot run exits 125 - # with no results, which must not stand for the others. - out[f"{stem}@{tier}" if tier else stem] = {"ctest": name, "tier": tier, "exit": code, "tut_file": source, - "tut_sha256": digest, "results": results} + # with no results, which must not stand for the others. So is each + # second run, whose results are of its own environment. + out[f"{stem}@{variant}" if variant else stem] = {"ctest": name, "stem": stem, "variant": variant, + "tier": tier, "exit": code, "tut_file": source, + "tut_sha256": digest, "results": results} counts = {} for r in results: counts[r["result"]] = counts.get(r["result"], 0) + 1 @@ -561,6 +582,22 @@ def read_mapping(path): return mapping["tests"], {record["file"]: record for record in mapping.get("files", [])} +def catch2_variant(binary, command): + """Which run of a file a Catch2 entry under / is: the ISA tier + the binary's name ends in, or, where the name is the binary's own + executable's, an underscore and more, a second run of it under another + environment (al_add_test_binary's RERUN), that run's name. None for the + file's own run.""" + tier = isa_tier(binary) + if tier: + return tier + for word in command: + target = os.path.splitext(os.path.basename(word))[0] + if binary.startswith(target + "_") and os.path.isfile(word): + return binary[len(target) + 1:] + return None + + def tree_tier(build_dir): """The ISA tier the build tree is built for: AL_ISA_TIER in its cache.""" try: @@ -580,7 +617,11 @@ def check_run(args, label, path, entry_name, test, recorded, entries, file_cases first_problem = len(problems) # A converted file changed since its binary was built is not what the # binary runs: its cases, lines and counts would not be the run's. - executable = executable_in(test["command"], entry_name.split("/", 1)[0]) + binary = entry_name.split("/", 1)[0] + variant = catch2_variant(binary, test["command"]) + if variant and variant not in TIERS: + binary = binary[:-len(variant) - 1] + executable = executable_in(test["command"], binary) if executable and os.path.isfile(executable) and \ os.path.getmtime(os.path.join(ROOT, path)) > os.path.getmtime(executable): problems.append(f"{label}: changed since {executable} was built; build it again to check it") @@ -652,7 +693,9 @@ def compiled(first, last, conditional): continue here = compiled(first, last, bool(blocks - body["held"])) if here: - why = args.expect_unrun.get(path, {}).get(str(first)) + # Reviewed for this run of the file, or for every run. + why = args.expect_unrun.get(label, {}).get(str(first)) or \ + args.expect_unrun.get(path, {}).get(str(first)) if why: notes.append(f"{case['name']!r}: the {macro} on line {first} never ran, as reviewed: {why}") else: @@ -755,12 +798,16 @@ def check(args): args.expect_unrun = json.load(f) with open(args.tut) as f: tut = json.load(f) - # By stem and tier, the tier read from each record's CTest name: results - # recorded before they were kept by tier, the last tier's under the stem, - # still say which tier they are of. + # By stem and run, which each record names; one recorded before records + # did has its tier read from its CTest name: results recorded before they + # were kept by tier, the last tier's under the stem, still say which tier + # they are of. records = {} for key, recorded in tut.items(): - records[tut_identity(recorded["ctest"]) if recorded.get("ctest") else (key, None)] = recorded + if "variant" in recorded: + records[(recorded["stem"], recorded["variant"])] = recorded + else: + records[tut_identity(recorded["ctest"]) if recorded.get("ctest") else (key, None)] = recorded own_tier = tree_tier(args.build_dir) tests = ctest_tests(args.build_dir, args.config) compiled_lines = CompiledLines(args.build_dir, args.config) @@ -810,16 +857,17 @@ def check(args): # is built for: one at the tree's own, checked against TUT's # untiered entry (or, where there is none, its entry at the tree's # tier), and at most one, _, at each other tier, - # checked against TUT's entry at that tier. + # checked against TUT's entry at that tier. A second run of the + # binary, _, is checked against TUT's run of that name. runs = {} for name in tests: if name.endswith("/" + stem): - runs.setdefault(isa_tier(name[:-len(stem) - 1]), []).append(name) + runs.setdefault(catch2_variant(name[:-len(stem) - 1], tests[name]["command"]), []).append(name) if len(runs.get(None, [])) != 1: file_problems.append(f"{path}: {len(runs.get(None, []))} Catch2 CTest entries end in /{stem}") used = set() file_rows = [] - for tier in sorted(runs, key=lambda t: TIERS.index(t) + 1 if t else 0): + for tier in sorted(runs, key=lambda t: (t in TIERS, TIERS.index(t) if t in TIERS else 0, t or "")): label = f"{path} ({tier})" if tier else path if len(runs[tier]) != 1: if tier: @@ -829,7 +877,8 @@ def check(args): if tier is None and key not in records and (stem, own_tier) in records: key = (stem, own_tier) if key not in records: - file_problems.append(f"{label}: no TUT results recorded for {stem}" + (f" at {tier}" if tier else "")) + file_problems.append(f"{label}: no TUT results recorded for {stem}" + (f" at {tier}" if tier in TIERS else + f" in its {tier} run" if tier else "")) continue used.add(key) row = check_run(args, label, path, runs[tier][0], tests[runs[tier][0]], records[key], entries, @@ -843,7 +892,8 @@ def check(args): print(f"{path}: listed: TUT's run at {own_tier}, the tree's own tier: {runs[None][0]} runs that " f"tier, and was checked against TUT's untiered run") else: - file_problems.append(f"{path}: TUT ran {stem} " + (f"at {key[1]}" if key[1] else "untiered") + file_problems.append(f"{path}: TUT ran {stem} " + (f"at {key[1]}" if key[1] in TIERS else + f"in its {key[1]} run" if key[1] else "untiered") + f" ({records[key]['ctest']}), and no Catch2 entry does") problems.extend(file_problems) for row in file_rows: @@ -875,7 +925,9 @@ def main(): help="the files were edited by hand since conversion: list each case's assertion count " "rather than hold it to the mapping's (names and results still must match)") chk.add_argument("--unrun", help="JSON of assertion sites reviewed as never running in a passing case on " - "this host, {file: {line: why}}: each is listed rather than a mismatch") + "this host, {file: {line: why}}: each is listed rather than a mismatch. A key may name " + "one run of a file, as the table does (\" (newsolver)\"), for a site only that run " + "leaves out") chk.add_argument("files", nargs="*", help="only these converted files (default: all the mapping has)") args = parser.parse_args() return record(args) if args.step == "record" else check(args) From 94c12482255ea12d446759c917cc9337ae36723a Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 01:35:21 -0400 Subject: [PATCH 4/7] alscript's and lscript's tests are Catch2 binaries, converted by tut2catch.py TM21, the fourth wave of stage 3: alscript's 43 TUT files and lscript's 2, 657 tests, become two Catch2 binaries, each file still run in a process of its own: - alscript_tests, 43 files. The six that check SLua -- alluauservice, alluaufragment, alluaunavigation, alscriptfixes, alscriptlintpass and allsltoslua -- run again under Luau's new type solver, as alscript_tests_newsolver/, where each had a second TUT entry; - lscript_tests: LL's compiler over its scripts, and Tailslide held against it. lscript's directory is configured only where its build needs are met, and the binary with it. The files are tut2catch.py's output, with the names the idiom pass that follows chose. It marked nothing TM-CHECK. One hand edit: alpreprocessor includes , which lltut.h brought it. tut_parity.py, against the TUT executables' recorded results (657 tests and 230 in the new solver's runs, all ok), holds plain for 44 files and their 6 second runs, and for all 45 under --recounted, with 24 assertion sites reviewed as never running in a passing case: - ten in loops over what a check reports of text that checks clean, which should not turn (allsloptimizer's, and alscriptfixes'); - alscriptfixes' REQUIRE(false), reached only to fail; - 13 in branches one solver's run alone takes (allsltoslua's and alluauservice's if (newSolver)), each listed for the run that leaves it. All 53 entries pass. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/CMakeLists.txt | 217 +- indra/alscript/tests/aldiskcache_test.cpp | 84 +- indra/alscript/tests/aldiskincludes_test.cpp | 384 +- .../alscript/tests/alincludeidentity_test.cpp | 57 +- indra/alscript/tests/alincludesearch_test.cpp | 782 ++-- indra/alscript/tests/allinelabel_test.cpp | 245 +- indra/alscript/tests/allslconsts_test.cpp | 176 +- indra/alscript/tests/allslcosts_test.cpp | 119 +- indra/alscript/tests/allsleffects_test.cpp | 155 +- indra/alscript/tests/allsloptimizer_test.cpp | 2697 +++++++----- indra/alscript/tests/allslservice_test.cpp | 897 ++-- indra/alscript/tests/allsltoslua_test.cpp | 3627 +++++++++++------ indra/alscript/tests/alluauconfig_test.cpp | 635 ++- indra/alscript/tests/alluaufragment_test.cpp | 110 +- .../alscript/tests/alluaunavigation_test.cpp | 269 +- indra/alscript/tests/alluauservice_test.cpp | 1804 +++++--- indra/alscript/tests/almasterburst_test.cpp | 227 +- indra/alscript/tests/almasterindex_test.cpp | 420 +- indra/alscript/tests/almasterlinks_test.cpp | 454 ++- indra/alscript/tests/almastermatch_test.cpp | 280 +- indra/alscript/tests/almasterplan_test.cpp | 77 +- indra/alscript/tests/almasterqueue_test.cpp | 255 +- indra/alscript/tests/almasterwatch_test.cpp | 226 +- indra/alscript/tests/almessagemap_test.cpp | 358 +- indra/alscript/tests/almodulelook_test.cpp | 110 +- .../alscript/tests/alnotecardformat_test.cpp | 151 +- indra/alscript/tests/alpreprocessor_test.cpp | 1820 ++++++--- .../alscript/tests/alpreprocessorc11_test.cpp | 816 ++-- .../alscript/tests/alscriptenvelope_test.cpp | 364 +- indra/alscript/tests/alscriptfixes_test.cpp | 1291 +++--- .../alscript/tests/alscriptformatter_test.cpp | 309 +- indra/alscript/tests/alscriptjoblane_test.cpp | 75 +- indra/alscript/tests/alscriptlexicon_test.cpp | 265 +- .../alscript/tests/alscriptlintpass_test.cpp | 631 +-- .../alscript/tests/alscriptmessages_test.cpp | 329 +- .../alscript/tests/alscriptnameindex_test.cpp | 75 +- .../tests/alscriptoutlinepairs_test.cpp | 55 +- .../alscript/tests/alscriptsnapshot_test.cpp | 40 +- indra/alscript/tests/alscriptstack_test.cpp | 51 +- .../alscript/tests/alscripttempfiles_test.cpp | 123 +- .../alscript/tests/alscripttextcache_test.cpp | 86 +- indra/alscript/tests/alscriptweight_test.cpp | 925 +++-- indra/alscript/tests/alsourcemap_test.cpp | 151 +- indra/alscript/tests/aluploadheader_test.cpp | 176 +- indra/lscript/CMakeLists.txt | 13 +- indra/lscript/tests/allslreference_test.cpp | 143 +- .../tests/allslreferencecheck_test.cpp | 74 +- 47 files changed, 14351 insertions(+), 8277 deletions(-) diff --git a/indra/alscript/CMakeLists.txt b/indra/alscript/CMakeLists.txt index a7ae6d5182..a96651b029 100644 --- a/indra/alscript/CMakeLists.txt +++ b/indra/alscript/CMakeLists.txt @@ -237,168 +237,71 @@ if(AL_BUILD_TESTS) templates_define "AL_SCRIPT_TEMPLATES_DIR=\"${INDRA_SOURCE_DIR}/newview/app_settings/script_templates\"" ) - al_add_test( - alluauservice + # One Catch2 binary for alscript's tests, each test file run in a process + # of its own. The SLua checks, and LSL written again as SLua, run again + # with Luau's new type solver, which a scripter may choose + # (ALScriptLuauSolver): the same tests, told by the environment. + al_add_test_binary( + alscript_tests PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} ${templates_define} - ) - # Completion and signature help over a fragment, against the whole - # script, at the places a scripter asks, over every SLua script to hand; - # the places found by Luau's own lexer. - al_add_test( - alluaufragment - PROJECT alscript - LIBRARIES alscript llcommon ll::slua + SOURCES + tests/aldiskcache_test.cpp + tests/aldiskincludes_test.cpp + tests/alincludeidentity_test.cpp + tests/alincludesearch_test.cpp + tests/allinelabel_test.cpp + tests/allslconsts_test.cpp + tests/allslcosts_test.cpp + tests/allsleffects_test.cpp + tests/allsloptimizer_test.cpp + tests/allslservice_test.cpp + tests/allsltoslua_test.cpp + tests/alluauconfig_test.cpp + tests/alluaufragment_test.cpp + tests/alluaunavigation_test.cpp + tests/alluauservice_test.cpp + tests/almasterburst_test.cpp + tests/almasterindex_test.cpp + tests/almasterlinks_test.cpp + tests/almastermatch_test.cpp + tests/almasterplan_test.cpp + tests/almasterqueue_test.cpp + tests/almasterwatch_test.cpp + tests/almessagemap_test.cpp + tests/almodulelook_test.cpp + tests/alnotecardformat_test.cpp + tests/alpreprocessor_test.cpp + tests/alpreprocessorc11_test.cpp + tests/alscriptenvelope_test.cpp + tests/alscriptfixes_test.cpp + tests/alscriptformatter_test.cpp + tests/alscriptjoblane_test.cpp + tests/alscriptlexicon_test.cpp + tests/alscriptlintpass_test.cpp + tests/alscriptmessages_test.cpp + tests/alscriptnameindex_test.cpp + tests/alscriptoutlinepairs_test.cpp + tests/alscriptsnapshot_test.cpp + tests/alscriptstack_test.cpp + tests/alscripttempfiles_test.cpp + tests/alscripttextcache_test.cpp + tests/alscriptweight_test.cpp + tests/alsourcemap_test.cpp + tests/aluploadheader_test.cpp + LIBRARIES alscript llcommon ll::slua ll::tailslide DEFINES ${definitions_defines} ${templates_define} "AL_ALSCRIPT_TEST_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/tests\"" - ) - # Where a name is declared and used, across a script and the modules it - # requires. - al_add_test( - alluaunavigation - PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} - ) - al_add_test( - allslservice - PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} ${templates_define} - ) - # Each fix made over a real check, applied, and the text checked again. - al_add_test( - alscriptfixes - PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} - ) - # The studio's own lints, each over real checks, with its fix made. - al_add_test( - alscriptlintpass - PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} - ) - # The SLua checks, and LSL written again as SLua, again with Luau's new - # type solver, which a scripter may choose (ALScriptLuauSolver): the same - # tests, told by the environment. - foreach( - test - alluauservice - alluaufragment - alluaunavigation - alscriptfixes - alscriptlintpass - allsltoslua - ) - add_test( - NAME INTEGRATION_TEST_RUNNER_${test}_newsolver - COMMAND $ - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) - set_tests_properties( - INTEGRATION_TEST_RUNNER_${test}_newsolver - PROPERTIES - ENVIRONMENT "AL_TEST_LUAU_SOLVER=new" - ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" - SKIP_RETURN_CODE 125 - ) - endforeach() - # What a script weighs: SLua by its bytecode, LSO by Tailslide's image, - # with the grid's builtins loaded. - al_add_test( - alscriptweight - PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} - ) - al_add_test(aldiskincludes PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(aldiskcache PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alincludeidentity PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alincludesearch PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(allinelabel PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(almodulelook PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alsourcemap PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptjoblane PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptnameindex PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptoutlinepairs PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptsnapshot PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscripttextcache PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptstack PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptenvelope PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(aluploadheader PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(almessagemap PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alluauconfig PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptformatter PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptlexicon PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscriptmessages PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alnotecardformat PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(alscripttempfiles PROJECT alscript LIBRARIES alscript llcommon) - # Disk masters: the changes gathered over a burst, the links and their - # index, the index kept in a file, a master found from what a script says - # or its name, what a send does, the sends underway, and the files watched - # for saves made outside. - al_add_test(almasterburst PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(almasterindex PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(almasterlinks PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(almastermatch PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(almasterplan PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(almasterqueue PROJECT alscript LIBRARIES alscript llcommon) - al_add_test(almasterwatch PROJECT alscript LIBRARIES alscript llcommon) - # The preprocessor against C11's 6.10, clause by clause. - al_add_test(alpreprocessorc11 PROJECT alscript LIBRARIES alscript llcommon) - # What LSL may change and the order it runs in, over scripts parsed with - # the grid's builtins loaded. - al_add_test( - allsleffects - PROJECT alscript - LIBRARIES alscript llcommon ll::tailslide - DEFINES ${definitions_defines} - ) - # What LSL forms cost on each target: snippet pairs compiled by - # Tailslide, against what the optimizer takes them to cost. - al_add_test( - allslcosts - PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} - ) - # The const keyword over scripts parsed with the grid's builtins loaded. - al_add_test( - allslconsts - PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} - ) - # The optimizer's golden files under tests/optimizer/, checked with the - # grid's builtins loaded. - al_add_test( - allsloptimizer - PROJECT alscript - LIBRARIES alscript llcommon - DEFINES ${definitions_defines} "AL_ALSCRIPT_TEST_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/tests\"" - ) - # LSL written again as SLua, each result checked as SLua against the - # grid's definitions; and Tailslide's own test scripts under - # tests/tailslide/, for their breadth. The comments a toggle makes, as - # Luau's own lexer reads them. - al_add_test( - allsltoslua - PROJECT alscript - LIBRARIES alscript llcommon ll::slua - DEFINES ${definitions_defines} "AL_ALSCRIPT_TEST_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/tests\"" - ) - # The golden files under tests/preprocessor/: every test_*.lsl beside - # its expected output. - al_add_test( - alpreprocessor - PROJECT alscript - LIBRARIES alscript llcommon ll::slua - DEFINES ${definitions_defines} "AL_ALSCRIPT_TEST_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/tests\"" + RERUN newsolver + RERUN_SOURCES + tests/alluaufragment_test.cpp + tests/alluaunavigation_test.cpp + tests/alluauservice_test.cpp + tests/allsltoslua_test.cpp + tests/alscriptfixes_test.cpp + tests/alscriptlintpass_test.cpp + RERUN_ENVIRONMENT AL_TEST_LUAU_SOLVER=new ) # The preprocessor, the formatter, the optimizer and one analysis of each # language over big scripts, with the grid's definitions loaded. diff --git a/indra/alscript/tests/aldiskcache_test.cpp b/indra/alscript/tests/aldiskcache_test.cpp index c23183e32f..9330b1051d 100644 --- a/indra/alscript/tests/aldiskcache_test.cpp +++ b/indra/alscript/tests/aldiskcache_test.cpp @@ -29,7 +29,7 @@ #include "fsyspath.h" #include "llfile.h" -#include "../test/lltut.h" +#include #include #include @@ -69,20 +69,10 @@ namespace }; } -namespace tut +namespace { - struct aldiskcache_data + TEST_CASE("aldiskcache: what is blessed and admitted kept until stale or the settings move", "[aldiskcache]") { - }; - - typedef test_group aldiskcache_group; - typedef aldiskcache_group::object aldiskcache_object; - aldiskcache_group aldiskcache_instance("aldiskcache"); - - template<> template<> - void aldiskcache_object::test<1>() - { - set_test_name("the folders blessed as the preprocessor blesses them, kept a moment or until the settings move; what they admit kept with them"); Scratch s; const std::string part = s.write("includes/lib/part.lsl", "integer part;\n"); s.write("includes/.lslrc", "{\"include\": [\"lib\"]}"); @@ -91,51 +81,79 @@ namespace tut const std::vector own{ s.at("includes") }; ALDiskCache::Blessed& first = cache.blessed(own, true, s.at("project/a"), {}, 1, 100.0); - ensure_equals("the scripter's, and what its .lslrc lists", first.includes.folders().size(), size_t(2)); - ensure_equals("a file under them admitted", cache.admits(first, part).value_or(""), part); - ensure("what is not there, not", !cache.admits(first, s.at("includes/lib/none.lsl"))); + UNSCOPED_INFO("the scripter's, and what its .lslrc lists"); + REQUIRE(first.includes.folders().size() == size_t(2)); + UNSCOPED_INFO("a file under them admitted"); + CHECK(cache.admits(first, part).value_or("") == part); + UNSCOPED_INFO("what is not there, not"); + REQUIRE_FALSE(cache.admits(first, s.at("includes/lib/none.lsl"))); // Taken away: still admitted while what is kept is fresh, and the // folders as they were. fs::remove(s.under("includes/lib/part.lsl")); ALDiskCache::Blessed& again = cache.blessed(own, true, s.at("project/a"), {}, 1, 101.0); - ensure("the same kept", &again == &first && cache.admits(again, part).has_value()); + { + INFO("the same kept"); + CHECK(&again == &first); + REQUIRE(cache.admits(again, part).has_value()); + } std::string text; - ensure("but not read", !cache.read(part, text)); + UNSCOPED_INFO("but not read"); + CHECK_FALSE(cache.read(part, text)); ALDiskCache::Blessed& later = cache.blessed(own, true, s.at("project/a"), {}, 1, 100.0 + ALDiskCache::FRESH_SECONDS + 0.5); - ensure("a moment later, asked again", !cache.admits(later, part)); + UNSCOPED_INFO("a moment later, asked again"); + REQUIRE_FALSE(cache.admits(later, part)); s.write("includes/lib/part.lsl", "integer part;\n"); - ensure("kept as asked until then", !cache.admits(later, part)); + UNSCOPED_INFO("kept as asked until then"); + REQUIRE_FALSE(cache.admits(later, part)); ALDiskCache::Blessed& moved = cache.blessed(own, true, s.at("project/a"), {}, 2, 103.0); - ensure("the settings moved: asked again at once", cache.admits(moved, part).has_value()); + UNSCOPED_INFO("the settings moved: asked again at once"); + REQUIRE(cache.admits(moved, part).has_value()); ALDiskCache::Blessed& plain = cache.blessed(own, false, s.at("project/a"), {}, 2, 103.0); - ensure_equals("SLua's: no .lslrc read", plain.includes.folders().size(), size_t(1)); + UNSCOPED_INFO("SLua's: no .lslrc read"); + REQUIRE(plain.includes.folders().size() == size_t(1)); ALDiskCache::Blessed& aliased = cache.blessed(own, false, std::string(), { s.at("project") }, 2, 103.0); - ensure_equals("an alias's folder blessed outright", aliased.includes.folders().size(), size_t(2)); - ensure_equals("the file at the top of each that has it", cache.atTop(moved, ".lslrc").size(), size_t(1)); + UNSCOPED_INFO("an alias's folder blessed outright"); + REQUIRE(aliased.includes.folders().size() == size_t(2)); + UNSCOPED_INFO("the file at the top of each that has it"); + REQUIRE(cache.atTop(moved, ".lslrc").size() == size_t(1)); } - template<> template<> - void aldiskcache_object::test<2>() + TEST_CASE("aldiskcache: a text read again once its time or size changes; upwards kept a moment", "[aldiskcache]") { - set_test_name("a text read again only once its time or size changes; the configurations up the folders found once a moment"); Scratch s; const std::string file = s.write("lib/util.lsl", "integer one;\n"); ALDiskCache cache; std::string text; - ensure("read", cache.read(file, text) && text == "integer one;\n"); + { + INFO("read"); + REQUIRE(cache.read(file, text)); + CHECK(text == "integer one;\n"); + } s.write("lib/util.lsl", "integer three;\n"); - ensure("read again once it changed", cache.read(file, text) && text == "integer three;\n"); - ensure("nothing where nothing is", !cache.read(s.at("lib/none.lsl"), text)); + { + INFO("read again once it changed"); + CHECK(cache.read(file, text)); + CHECK(text == "integer three;\n"); + } + UNSCOPED_INFO("nothing where nothing is"); + CHECK_FALSE(cache.read(s.at("lib/none.lsl"), text)); 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); - ensure("nearest first, to the root", up.size() >= 2 && up[0] == s.at("a/b/c/.luaurc") && up[1] == s.at("a/.luaurc")); + { + INFO("nearest first, to the root"); + REQUIRE(up.size() >= 2); + CHECK(up[0] == s.at("a/b/c/.luaurc")); + CHECK(up[1] == s.at("a/.luaurc")); + } s.write("a/b/.luaurc", "{}"); - ensure_equals("kept a moment", cache.upwards(s.at("a/b/c"), ".luaurc", 1, 11.0).size(), up.size()); - ensure("then found", cache.upwards(s.at("a/b/c"), ".luaurc", 1, 10.0 + ALDiskCache::FRESH_SECONDS + 1.0)[1] == s.at("a/b/.luaurc")); + UNSCOPED_INFO("kept a moment"); + REQUIRE(cache.upwards(s.at("a/b/c"), ".luaurc", 1, 11.0).size() == up.size()); + 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")); } } diff --git a/indra/alscript/tests/aldiskincludes_test.cpp b/indra/alscript/tests/aldiskincludes_test.cpp index c1749798f4..bbcd435820 100644 --- a/indra/alscript/tests/aldiskincludes_test.cpp +++ b/indra/alscript/tests/aldiskincludes_test.cpp @@ -28,7 +28,7 @@ #include "fsyspath.h" -#include "../test/lltut.h" +#include #include #include @@ -79,48 +79,52 @@ namespace }; } -namespace tut +namespace { - struct aldiskincludes_data - { - }; - - typedef test_group aldiskincludes_group; - typedef aldiskincludes_group::object aldiskincludes_object; - aldiskincludes_group aldiskincludes_instance("aldiskincludes"); - - template<> template<> - void aldiskincludes_object::test<1>() + TEST_CASE("aldiskincludes: nothing is read but under a blessed folder however the path is put", "[aldiskincludes]") { - set_test_name("nothing on the disk is read but under a blessed folder, however the path is put"); Scratch s; const std::string lib = s.write("lib/util.lsl", "integer util() { return 1; }\n"); const std::string secret = s.write("private/secret.txt", "the author's own\n"); s.write("lib2/other.lsl", "x\n"); ALDiskIncludes none; - ensure("nothing blessed, nothing read", !none.blessed() && !none.admits(lib)); + { + INFO("nothing blessed, nothing read"); + CHECK_FALSE(none.blessed()); + REQUIRE_FALSE(none.admits(lib)); + } ALDiskIncludes blessed; blessed.bless(s.at("lib")); - ensure("blessed", blessed.blessed() && blessed.folders().size() == 1); - ensure_equals("a file under it is read, as it stands", blessed.admits(lib).value_or(""), lib); - ensure("a path from a root elsewhere is not", !blessed.admits(secret)); - ensure("nor one climbing out of the blessed folder", !blessed.admits(s.at("lib/../private/secret.txt"))); - ensure("nor one in a folder whose name only begins with it", !blessed.admits(s.at("lib2/other.lsl"))); - ensure("nor the folder itself", !blessed.admits(s.at("lib"))); - ensure("nor what is not there", !blessed.admits(s.at("lib/missing.lsl"))); + { + INFO("blessed"); + CHECK(blessed.blessed()); + REQUIRE(blessed.folders().size() == 1); + } + UNSCOPED_INFO("a file under it is read, as it stands"); + CHECK(blessed.admits(lib).value_or("") == lib); + UNSCOPED_INFO("a path from a root elsewhere is not"); + REQUIRE_FALSE(blessed.admits(secret)); + UNSCOPED_INFO("nor one climbing out of the blessed folder"); + REQUIRE_FALSE(blessed.admits(s.at("lib/../private/secret.txt"))); + UNSCOPED_INFO("nor one in a folder whose name only begins with it"); + REQUIRE_FALSE(blessed.admits(s.at("lib2/other.lsl"))); + UNSCOPED_INFO("nor the folder itself"); + REQUIRE_FALSE(blessed.admits(s.at("lib"))); + UNSCOPED_INFO("nor what is not there"); + REQUIRE_FALSE(blessed.admits(s.at("lib/missing.lsl"))); blessed.bless(s.at("nowhere")); blessed.bless(lib); - ensure_equals("what is not a folder blesses no folder", blessed.folders().size(), size_t(1)); + UNSCOPED_INFO("what is not a folder blesses no folder"); + REQUIRE(blessed.folders().size() == size_t(1)); blessed.bless(s.at("lib/../lib")); - ensure_equals("and one folder is blessed once however it is named", blessed.folders().size(), size_t(1)); + UNSCOPED_INFO("and one folder is blessed once however it is named"); + REQUIRE(blessed.folders().size() == size_t(1)); } - template<> template<> - void aldiskincludes_object::test<2>() + TEST_CASE("aldiskincludes: links followed before judging; only sensibly sized ordinary files read", "[aldiskincludes]") { - set_test_name("a link is followed before it is judged, and only an ordinary file of a sensible size is read"); Scratch s; const std::string secret = s.write("private/secret.txt", "the author's own\n"); s.write("lib/util.lsl", "x\n"); @@ -131,66 +135,95 @@ namespace tut fs::create_symlink(fsyspath(secret), s.root / "lib" / "innocent.lsl", ec); if (!ec) { - ensure("a link out of the folder is not read", !blessed.admits(s.at("lib/innocent.lsl"))); + UNSCOPED_INFO("a link out of the folder is not read"); + REQUIRE_FALSE(blessed.admits(s.at("lib/innocent.lsl"))); } fs::create_directory_symlink(s.root / "private", s.root / "lib" / "door", ec); if (!ec) { - ensure("nor a file through a linked folder", !blessed.admits(s.at("lib/door/secret.txt"))); + UNSCOPED_INFO("nor a file through a linked folder"); + REQUIRE_FALSE(blessed.admits(s.at("lib/door/secret.txt"))); } const std::string big = s.write("lib/big.lsl", std::string(static_cast(ALDiskIncludes::MAX_BYTES) + 1, 'x')); - ensure("a file past the limit is not read", !blessed.admits(big)); + UNSCOPED_INFO("a file past the limit is not read"); + REQUIRE_FALSE(blessed.admits(big)); std::string text; - ensure("nor read by the reader", !ALDiskIncludes::readOrdinary(big, text) && text.empty()); - ensure("a folder is not read", !ALDiskIncludes::readOrdinary(s.at("lib"), text)); - ensure("an ordinary one is", ALDiskIncludes::readOrdinary(s.at("lib/util.lsl"), text) && text == "x\n"); - ensure("and held at its own size, not the limit's", text.capacity() < 1024); + { + INFO("nor read by the reader"); + REQUIRE_FALSE(ALDiskIncludes::readOrdinary(big, text)); + REQUIRE(text.empty()); + } + UNSCOPED_INFO("a folder is not read"); + CHECK_FALSE(ALDiskIncludes::readOrdinary(s.at("lib"), text)); + { + INFO("an ordinary one is"); + CHECK(ALDiskIncludes::readOrdinary(s.at("lib/util.lsl"), text)); + CHECK(text == "x\n"); + } + UNSCOPED_INFO("and held at its own size, not the limit's"); + CHECK(text.capacity() < 1024); const std::string whole(100000, 'y'); const std::string mid = s.write("lib/mid.lsl", whole); - ensure("one of some size, whole", ALDiskIncludes::readOrdinary(mid, text) && text == whole); - ensure("held at about its size", text.capacity() < whole.size() + 1024); + { + INFO("one of some size, whole"); + CHECK(ALDiskIncludes::readOrdinary(mid, text)); + CHECK(text == whole); + } + UNSCOPED_INFO("held at about its size"); + REQUIRE(text.capacity() < whole.size() + 1024); const std::string at_limit = s.write("lib/limit.lsl", std::string(static_cast(ALDiskIncludes::MAX_BYTES), 'z')); - ensure("one at the limit, whole", ALDiskIncludes::readOrdinary(at_limit, text) && text.size() == ALDiskIncludes::MAX_BYTES); + { + INFO("one at the limit, whole"); + CHECK(ALDiskIncludes::readOrdinary(at_limit, text)); + REQUIRE(text.size() == ALDiskIncludes::MAX_BYTES); + } #if LL_DARWIN || LL_LINUX // What would be read for ever, or never answer: refused at once. ALDiskIncludes devices; devices.bless("/dev"); - ensure("a device is not an include", !devices.admits("/dev/zero")); - ensure("nor read", !ALDiskIncludes::readOrdinary("/dev/zero", text)); + UNSCOPED_INFO("a device is not an include"); + REQUIRE_FALSE(devices.admits("/dev/zero")); + UNSCOPED_INFO("nor read"); + CHECK_FALSE(ALDiskIncludes::readOrdinary("/dev/zero", text)); const std::string pipe = s.at("lib/pipe"); if (mkfifo(pipe.c_str(), 0600) == 0) { - ensure("a pipe is not an include", !blessed.admits(pipe)); - ensure("nor read, and the reading does not wait on it", !ALDiskIncludes::readOrdinary(pipe, text)); + UNSCOPED_INFO("a pipe is not an include"); + REQUIRE_FALSE(blessed.admits(pipe)); + UNSCOPED_INFO("nor read, and the reading does not wait on it"); + CHECK_FALSE(ALDiskIncludes::readOrdinary(pipe, text)); } #endif } - template<> template<> - void aldiskincludes_object::test<3>() + TEST_CASE("aldiskincludes: a .lslrc lists folders from beside it and the nearest up is found", "[aldiskincludes]") { - set_test_name("a .lslrc lists folders from beside it, and the nearest up from a folder is found"); Scratch s; s.write("project/.lslrc", "{\"include\": [\"../lib\", \"shared/\"]}"); s.write("project/src/deep/main.lsl", "x\n"); const std::vector listed = ALDiskIncludes::lslrcFolders(s.at("project")); - ensure_equals("both", listed.size(), size_t(2)); - ensure_equals("the one above, from beside the file", listed[0], s.at("lib")); - ensure_equals("the one below, without its slash", listed[1], s.at("project/shared")); + UNSCOPED_INFO("both"); + REQUIRE(listed.size() == size_t(2)); + UNSCOPED_INFO("the one above, from beside the file"); + CHECK(listed[0] == s.at("lib")); + UNSCOPED_INFO("the one below, without its slash"); + CHECK(listed[1] == s.at("project/shared")); std::string found_in; - ensure("the nearest up", ALDiskIncludes::nearestLslrcFolders(s.at("project/src/deep"), &found_in) == listed); - ensure_equals("and where it is", found_in, s.at("project")); - ensure("none where there is none", ALDiskIncludes::lslrcFolders(s.at("project/src")).empty()); + UNSCOPED_INFO("the nearest up"); + CHECK(ALDiskIncludes::nearestLslrcFolders(s.at("project/src/deep"), &found_in) == listed); + UNSCOPED_INFO("and where it is"); + CHECK(found_in == s.at("project")); + UNSCOPED_INFO("none where there is none"); + REQUIRE(ALDiskIncludes::lslrcFolders(s.at("project/src")).empty()); s.write("broken/.lslrc", "{not json"); - ensure("nor where it is not a configuration", ALDiskIncludes::lslrcFolders(s.at("broken")).empty()); + UNSCOPED_INFO("nor where it is not a configuration"); + REQUIRE(ALDiskIncludes::lslrcFolders(s.at("broken")).empty()); } - template<> template<> - void aldiskincludes_object::test<4>() + TEST_CASE("aldiskincludes: a blessed folder's files listed by path from it no deeper than asked", "[aldiskincludes]") { - set_test_name("a blessed folder's files are listed through its folders, by their paths from it, and no further than asked"); Scratch s; s.write("lib/util.luau", "return {}\n"); s.write("lib/net/http.luau", "return {}\n"); @@ -213,76 +246,103 @@ namespace tut return said; }; ALDiskIncludes blessed; - ensure("nothing of a folder not blessed", blessed.filesUnder(s.at("lib"), { ".luau" }, 3, 100, 100).empty()); + UNSCOPED_INFO("nothing of a folder not blessed"); + REQUIRE(blessed.filesUnder(s.at("lib"), { ".luau" }, 3, 100, 100).empty()); blessed.bless(s.at("lib")); const std::vector found = blessed.filesUnder(s.at("lib"), { ".luau", ".lua" }, 2, 100, 100); - ensure_equals("two folders down, no hidden one, only what is asked for", listed(found), std::string("net/http.luau util.luau")); + UNSCOPED_INFO("two folders down, no hidden one, only what is asked for"); + CHECK(listed(found) == std::string("net/http.luau util.luau")); for (const ALDiskIncludes::Listed& one : found) { - ensure_equals("each where it stands", one.file, s.at("lib/" + one.relative)); + UNSCOPED_INFO("each where it stands"); + REQUIRE(one.file == s.at("lib/" + one.relative)); } - ensure_equals("deeper where asked", listed(blessed.filesUnder(s.at("lib"), { ".luau" }, 4, 100, 100)), + UNSCOPED_INFO("deeper where asked"); + CHECK(listed(blessed.filesUnder(s.at("lib"), { ".luau" }, 4, 100, 100)) == std::string("net/deep/deeper/far.luau net/http.luau util.luau")); - ensure_equals("a folder under a blessed one, from itself", listed(blessed.filesUnder(s.at("lib/net"), { ".luau" }, 1, 100, 100)), + UNSCOPED_INFO("a folder under a blessed one, from itself"); + CHECK(listed(blessed.filesUnder(s.at("lib/net"), { ".luau" }, 1, 100, 100)) == std::string("http.luau")); - ensure_equals("no more than asked for", blessed.filesUnder(s.at("lib"), { ".luau" }, 4, 100, 1).size(), size_t(1)); - ensure("nothing outside", blessed.filesUnder(s.at("elsewhere"), { ".luau" }, 4, 100, 100).empty()); + UNSCOPED_INFO("no more than asked for"); + REQUIRE(blessed.filesUnder(s.at("lib"), { ".luau" }, 4, 100, 1).size() == size_t(1)); + UNSCOPED_INFO("nothing outside"); + REQUIRE(blessed.filesUnder(s.at("elsewhere"), { ".luau" }, 4, 100, 100).empty()); #if LL_DARWIN || LL_LINUX // A link to a folder outside is not followed down. std::error_code ec; fs::create_directory_symlink(s.under("elsewhere"), s.under("lib/linked"), ec); - ensure("linked", !ec); - ensure_equals("not through a link", listed(blessed.filesUnder(s.at("lib"), { ".luau" }, 4, 100, 100)), + UNSCOPED_INFO("linked"); + REQUIRE_FALSE(ec); + UNSCOPED_INFO("not through a link"); + CHECK(listed(blessed.filesUnder(s.at("lib"), { ".luau" }, 4, 100, 100)) == std::string("net/deep/deeper/far.luau net/http.luau util.luau")); #endif } - template<> template<> - void aldiskincludes_object::test<5>() + TEST_CASE("aldiskincludes: a configuration blesses only under its own folder or the scripter's", "[aldiskincludes]") { - set_test_name("a configuration blesses a folder under its own, or under one the scripter blessed, and no further"); Scratch s; s.write("project/shared/a.lsl", "a\n"); s.write("lib/b.lsl", "b\n"); s.write("secret/key.txt", "k\n"); ALDiskIncludes none; - ensure("under its own", none.mayFromConfig(s.at("project/shared"), s.at("project"))); - ensure("its own", none.mayFromConfig(s.at("project"), s.at("project"))); - ensure("not a sibling", !none.mayFromConfig(s.at("lib"), s.at("project"))); - ensure("not a root", !none.mayFromConfig(fsyspath(s.root.root_path()).string(), s.at("project"))); - ensure("not by going up", !none.mayFromConfig(s.at("project/../secret"), s.at("project"))); - ensure("not what is not there", !none.mayFromConfig(s.at("project/missing"), s.at("project"))); + UNSCOPED_INFO("under its own"); + CHECK(none.mayFromConfig(s.at("project/shared"), s.at("project"))); + UNSCOPED_INFO("its own"); + CHECK(none.mayFromConfig(s.at("project"), s.at("project"))); + UNSCOPED_INFO("not a sibling"); + CHECK_FALSE(none.mayFromConfig(s.at("lib"), s.at("project"))); + UNSCOPED_INFO("not a root"); + CHECK_FALSE(none.mayFromConfig(fsyspath(s.root.root_path()).string(), s.at("project"))); + UNSCOPED_INFO("not by going up"); + CHECK_FALSE(none.mayFromConfig(s.at("project/../secret"), s.at("project"))); + UNSCOPED_INFO("not what is not there"); + CHECK_FALSE(none.mayFromConfig(s.at("project/missing"), s.at("project"))); // The sibling, where the scripter's own folders hold it. ALDiskIncludes own; own.bless(fsyspath(s.root).string()); - ensure("under the scripter's", own.mayFromConfig(s.at("lib"), s.at("project"))); + UNSCOPED_INFO("under the scripter's"); + CHECK(own.mayFromConfig(s.at("lib"), s.at("project"))); ALDiskIncludes blessed; blessed.bless(s.at("lib")); - ensure("blessed from it", blessed.blessFromConfig(s.at("lib"), s.at("project"))); - ensure("and refused past it", !blessed.blessFromConfig(s.at("secret"), s.at("project")) && !blessed.admits(s.at("secret/key.txt"))); + UNSCOPED_INFO("blessed from it"); + CHECK(blessed.blessFromConfig(s.at("lib"), s.at("project"))); + { + INFO("and refused past it"); + CHECK_FALSE(blessed.blessFromConfig(s.at("secret"), s.at("project"))); + REQUIRE_FALSE(blessed.admits(s.at("secret/key.txt"))); + } #if LL_DARWIN || LL_LINUX // A link inside that leads outside is where it leads. std::error_code ec; fs::create_directory_symlink(s.under("secret"), s.under("project/escape"), ec); - ensure("linked: " + ec.message(), !ec); - ensure("not through a link out", !none.mayFromConfig(s.at("project/escape"), s.at("project"))); + UNSCOPED_INFO("linked: " + ec.message()); + REQUIRE_FALSE(ec); + UNSCOPED_INFO("not through a link out"); + CHECK_FALSE(none.mayFromConfig(s.at("project/escape"), s.at("project"))); #endif } - template<> template<> - void aldiskincludes_object::test<6>() + TEST_CASE("aldiskincludes: a require with no extension takes its own files before a folder's init", "[aldiskincludes]") { - set_test_name("a require of a name with no extension finds a folder's init.luau after the name's own files"); typedef std::vector names; - ensure("SLua's require", ALDiskIncludes::namesFor("lib", true, true) == + UNSCOPED_INFO("SLua's require"); + CHECK(ALDiskIncludes::namesFor("lib", true, true) == names{ "lib", "lib.luau", "lib.lua", "lib/init.luau", "lib/init.lua" }); const names folder = ALDiskIncludes::namesFor("./shared/lib/", true, true); - ensure("a path's last folder, however it ends", - folder.size() == 5 && folder[3] == "./shared/lib/init.luau" && folder[4] == "./shared/lib/init.lua"); - ensure("a name with its extension is that file", ALDiskIncludes::namesFor("lib.Luau", true, true) == + { + INFO("a path's last folder, however it ends"); + REQUIRE(folder.size() == 5); + CHECK(folder[3] == "./shared/lib/init.luau"); + CHECK(folder[4] == "./shared/lib/init.lua"); + } + UNSCOPED_INFO("a name with its extension is that file"); + CHECK(ALDiskIncludes::namesFor("lib.Luau", true, true) == names{ "lib.Luau", "lib.Luau.luau", "lib.Luau.lua" }); - ensure("an #include in SLua is a file", ALDiskIncludes::namesFor("lib", true, false) == names{ "lib", "lib.luau", "lib.lua" }); - ensure("an LSL include", ALDiskIncludes::namesFor("lib", false, false) == names{ "lib", "lib.lsl" }); + UNSCOPED_INFO("an #include in SLua is a file"); + CHECK(ALDiskIncludes::namesFor("lib", true, false) == names{ "lib", "lib.luau", "lib.lua" }); + UNSCOPED_INFO("an LSL include"); + CHECK(ALDiskIncludes::namesFor("lib", false, false) == names{ "lib", "lib.lsl" }); // As a folder is looked in: the first of the names it admits. Scratch s; @@ -299,19 +359,21 @@ namespace tut } return std::string(); }; - ensure_equals("the folder's module", first("lib"), init); + UNSCOPED_INFO("the folder's module"); + CHECK(first("lib") == init); const std::string own = s.write("modules/lib.luau", "return 1\n"); - ensure_equals("a file of the name before the folder", first("lib"), own); - ensure_equals("init by its own name too", first("lib/init"), init); + UNSCOPED_INFO("a file of the name before the folder"); + CHECK(first("lib") == own); + UNSCOPED_INFO("init by its own name too"); + CHECK(first("lib/init") == init); } - template<> template<> - void aldiskincludes_object::test<7>() + TEST_CASE("aldiskincludes: atTop finds each blessed folder's own configuration in blessing order", "[aldiskincludes]") { - set_test_name("the configuration at the top of each blessed folder that has one, in their order, read as any file under it is"); Scratch s; ALDiskIncludes none; - ensure("nothing blessed, nothing found", none.atTop(".luaurc").empty()); + UNSCOPED_INFO("nothing blessed, nothing found"); + REQUIRE(none.atTop(".luaurc").empty()); s.write("first/util.luau", "return 1\n"); const std::string second = s.write("second/.luaurc", "{\"aliases\": {\"lib\": \"./lib\"}}\n"); @@ -322,27 +384,30 @@ namespace tut blessed.bless(s.at("third")); blessed.bless(s.at("second")); typedef std::vector files; - ensure("each folder's own, in the order blessed, not one further down", blessed.atTop(".luaurc") == files{ third, second }); + UNSCOPED_INFO("each folder's own, in the order blessed, not one further down"); + CHECK(blessed.atTop(".luaurc") == files{ third, second }); // A folder so named is no file. fs::create_directories(s.under("first/.luaurc")); - ensure("a folder so named passed over", blessed.atTop(".luaurc") == files{ third, second }); + UNSCOPED_INFO("a folder so named passed over"); + CHECK(blessed.atTop(".luaurc") == files{ third, second }); #if LL_DARWIN || LL_LINUX // A link out of the blessed folders is where it leads, and not read. s.write("elsewhere/.luaurc", "{}\n"); std::error_code ec; fs::remove(s.under("first/.luaurc"), ec); fs::create_symlink(s.under("elsewhere/.luaurc"), s.under("first/.luaurc"), ec); - ensure("linked: " + ec.message(), !ec); - ensure("not through a link out", blessed.atTop(".luaurc") == files{ third, second }); + UNSCOPED_INFO("linked: " + ec.message()); + REQUIRE_FALSE(ec); + UNSCOPED_INFO("not through a link out"); + CHECK(blessed.atTop(".luaurc") == files{ third, second }); #endif - ensure("nor a name no folder has", blessed.atTop(".lslrc").empty()); + UNSCOPED_INFO("nor a name no folder has"); + REQUIRE(blessed.atTop(".lslrc").empty()); } - template<> template<> - void aldiskincludes_object::test<8>() + TEST_CASE("aldiskincludes: scriptsUnder lists each script of a language once within the limits", "[aldiskincludes]") { - set_test_name("the scripts of a language under folders: each folder blessed for the look, each script once, no more than asked, nothing outside"); Scratch s; s.write("lib/util.luau", "return {}\n"); s.write("lib/old.lua", "return {}\n"); @@ -361,33 +426,43 @@ namespace tut } return said; }; - ensure_equals("SLua's, both folders, through their folders", - names(ALDiskIncludes::scriptsUnder({ s.at("lib"), s.at("tools") }, true, 4, 100)), + UNSCOPED_INFO("SLua's, both folders, through their folders"); + CHECK(names(ALDiskIncludes::scriptsUnder({ s.at("lib"), s.at("tools") }, true, 4, 100)) == std::string("lib/net/http.luau lib/old.lua lib/util.luau tools/t.luau")); - ensure_equals("LSL's and its includes'", names(ALDiskIncludes::scriptsUnder({ s.at("lib") }, false, 4, 100)), + UNSCOPED_INFO("LSL's and its includes'"); + CHECK(names(ALDiskIncludes::scriptsUnder({ s.at("lib") }, false, 4, 100)) == std::string("lib/door.lsl lib/door.lslh")); - ensure_equals("a folder given twice, or under another, each once", - ALDiskIncludes::scriptsUnder({ s.at("lib"), s.at("lib/net"), s.at("lib") }, true, 4, 100).size(), size_t(3)); - ensure_equals("no more than asked", ALDiskIncludes::scriptsUnder({ s.at("lib"), s.at("tools") }, true, 4, 2).size(), size_t(2)); - ensure_equals("not deeper than asked", names(ALDiskIncludes::scriptsUnder({ s.at("lib") }, true, 0, 100)), + UNSCOPED_INFO("a folder given twice, or under another, each once"); + REQUIRE(ALDiskIncludes::scriptsUnder({ s.at("lib"), s.at("lib/net"), s.at("lib") }, true, 4, 100).size() == size_t(3)); + UNSCOPED_INFO("no more than asked"); + REQUIRE(ALDiskIncludes::scriptsUnder({ s.at("lib"), s.at("tools") }, true, 4, 2).size() == size_t(2)); + UNSCOPED_INFO("not deeper than asked"); + CHECK(names(ALDiskIncludes::scriptsUnder({ s.at("lib") }, true, 0, 100)) == std::string("lib/old.lua lib/util.luau")); - ensure("nothing of a folder not there", ALDiskIncludes::scriptsUnder({ s.at("nowhere") }, true, 4, 100).empty()); + UNSCOPED_INFO("nothing of a folder not there"); + REQUIRE(ALDiskIncludes::scriptsUnder({ s.at("nowhere") }, true, 4, 100).empty()); } - template<> template<> - void aldiskincludes_object::test<9>() + TEST_CASE("aldiskincludes: extensions in any case; a file blessed alone; paths judged lexically", "[aldiskincludes]") { - set_test_name("a script's extensions, in any case; a file blessed alone, nothing beside it; a path as written under a folder or not, nothing on the disk asked"); typedef std::vector names; const names& lua = ALDiskIncludes::scriptExtensions(true); - ensure("SLua's, the modern first", lua == names{ ".luau", ".lua" }); - ensure("LSL's and its includes'", ALDiskIncludes::scriptExtensions(false) == names{ ".lsl", ".lslh", ".lsli" }); - ensure_equals("in any case", ALDiskIncludes::extensionOf("Util.LUAU", lua), size_t(5)); - ensure_equals("the old one", ALDiskIncludes::extensionOf("old.Lua", lua), size_t(4)); - ensure_equals("an include's", ALDiskIncludes::extensionOf("defs.LSLH", ALDiskIncludes::scriptExtensions(false)), size_t(5)); - ensure_equals("past a name of its own", ALDiskIncludes::extensionOf(".luau", lua), size_t(0)); - ensure_equals("none", ALDiskIncludes::extensionOf("notes.txt", lua), size_t(0)); - ensure("a require of one in any case is that file", ALDiskIncludes::namesFor("Util.LUAU", true, true) == + UNSCOPED_INFO("SLua's, the modern first"); + CHECK(lua == names{ ".luau", ".lua" }); + UNSCOPED_INFO("LSL's and its includes'"); + CHECK(ALDiskIncludes::scriptExtensions(false) == names{ ".lsl", ".lslh", ".lsli" }); + UNSCOPED_INFO("in any case"); + CHECK(ALDiskIncludes::extensionOf("Util.LUAU", lua) == size_t(5)); + UNSCOPED_INFO("the old one"); + CHECK(ALDiskIncludes::extensionOf("old.Lua", lua) == size_t(4)); + UNSCOPED_INFO("an include's"); + CHECK(ALDiskIncludes::extensionOf("defs.LSLH", ALDiskIncludes::scriptExtensions(false)) == size_t(5)); + UNSCOPED_INFO("past a name of its own"); + CHECK(ALDiskIncludes::extensionOf(".luau", lua) == size_t(0)); + UNSCOPED_INFO("none"); + CHECK(ALDiskIncludes::extensionOf("notes.txt", lua) == size_t(0)); + UNSCOPED_INFO("a require of one in any case is that file"); + CHECK(ALDiskIncludes::namesFor("Util.LUAU", true, true) == names{ "Util.LUAU", "Util.LUAU.luau", "Util.LUAU.lua" }); Scratch s; @@ -397,36 +472,65 @@ namespace tut ALDiskIncludes blessed; blessed.bless(s.at("lib")); const std::vector listed = blessed.filesUnder(s.at("lib"), lua, 1, 100, 100); - ensure("listed in any case", listed.size() == 1 && listed[0].file == shout); + { + INFO("listed in any case"); + REQUIRE(listed.size() == 1); + CHECK(listed[0].file == shout); + } blessed.bless(module); - ensure_equals("a file blessed alone", blessed.admits(module).value_or(""), module); - ensure("nothing beside it", !blessed.admits(beside)); - ensure_equals("and no folder", blessed.folders().size(), size_t(1)); + UNSCOPED_INFO("a file blessed alone"); + CHECK(blessed.admits(module).value_or("") == module); + UNSCOPED_INFO("nothing beside it"); + REQUIRE_FALSE(blessed.admits(beside)); + UNSCOPED_INFO("and no folder"); + REQUIRE(blessed.folders().size() == size_t(1)); const std::vector folders{ s.at("lib") }; std::string upper = s.at("lib/net/x.luau"); std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); - ensure("under, as written", ALDiskIncludes::lexicallyUnder(s.at("lib/net/x.luau"), folders)); - ensure("in any case", ALDiskIncludes::lexicallyUnder(upper, folders)); - ensure("whatever is there", ALDiskIncludes::lexicallyUnder(s.at("lib/missing/x.luau"), folders)); - ensure("not climbing out", !ALDiskIncludes::lexicallyUnder(s.at("lib/../vendor/json.luau"), folders)); - ensure("not a folder whose name it only begins with", !ALDiskIncludes::lexicallyUnder(s.at("lib2/x.luau"), folders)); - ensure("not another machine's share", !ALDiskIncludes::lexicallyUnder("//alincludes.invalid/share/x", folders) && - !ALDiskIncludes::lexicallyUnder("\\\\alincludes.invalid\\share\\x", folders)); - ensure("nor a device's path to one under it", !ALDiskIncludes::lexicallyUnder("\\\\?\\" + s.at("lib/x.luau"), folders)); - ensure("nor under nothing", !ALDiskIncludes::lexicallyUnder(s.at("lib/x.luau"), {}) && - !ALDiskIncludes::lexicallyUnder(std::string(), folders)); + UNSCOPED_INFO("under, as written"); + CHECK(ALDiskIncludes::lexicallyUnder(s.at("lib/net/x.luau"), folders)); + UNSCOPED_INFO("in any case"); + CHECK(ALDiskIncludes::lexicallyUnder(upper, folders)); + UNSCOPED_INFO("whatever is there"); + CHECK(ALDiskIncludes::lexicallyUnder(s.at("lib/missing/x.luau"), folders)); + UNSCOPED_INFO("not climbing out"); + CHECK_FALSE(ALDiskIncludes::lexicallyUnder(s.at("lib/../vendor/json.luau"), folders)); + UNSCOPED_INFO("not a folder whose name it only begins with"); + CHECK_FALSE(ALDiskIncludes::lexicallyUnder(s.at("lib2/x.luau"), folders)); + { + INFO("not another machine's share"); + CHECK_FALSE(ALDiskIncludes::lexicallyUnder("//alincludes.invalid/share/x", folders)); + CHECK_FALSE(ALDiskIncludes::lexicallyUnder("\\\\alincludes.invalid\\share\\x", folders)); + } + UNSCOPED_INFO("nor a device's path to one under it"); + CHECK_FALSE(ALDiskIncludes::lexicallyUnder("\\\\?\\" + s.at("lib/x.luau"), folders)); + { + INFO("nor under nothing"); + CHECK_FALSE(ALDiskIncludes::lexicallyUnder(s.at("lib/x.luau"), {})); + CHECK_FALSE(ALDiskIncludes::lexicallyUnder(std::string(), folders)); + } // What a configuration lists of another machine's share: not // blessed, and not asked after. - ensure("a configuration's share", !blessed.mayFromConfig("//alincludes.invalid/share", s.at("lib")) && - !blessed.blessFromConfig("\\\\alincludes.invalid\\share", s.at("lib"))); + { + INFO("a configuration's share"); + CHECK_FALSE(blessed.mayFromConfig("//alincludes.invalid/share", s.at("lib"))); + CHECK_FALSE(blessed.blessFromConfig("\\\\alincludes.invalid\\share", s.at("lib"))); + } // Nor by the NT namespace's names, which Windows passes on as they // are: a share's, and even a blessed folder's own. - ensure("the NT namespace's share", !blessed.mayFromConfig("\\??\\UNC\\alincludes.invalid\\share", s.at("lib")) && - !blessed.blessFromConfig("/?\?/UNC/alincludes.invalid/share", s.at("lib"))); + { + INFO("the NT namespace's share"); + CHECK_FALSE(blessed.mayFromConfig("\\??\\UNC\\alincludes.invalid\\share", s.at("lib"))); + CHECK_FALSE(blessed.blessFromConfig("/?\?/UNC/alincludes.invalid/share", s.at("lib"))); + } fs::create_directories(s.under("lib/net")); - ensure("its name for a folder under the configuration's", !blessed.mayFromConfig("\\??\\" + s.at("lib/net"), s.at("lib")) && - !ALDiskIncludes::lexicallyUnder("\\??\\" + s.at("lib/net"), folders)); - ensure("which, as itself, it may name", blessed.mayFromConfig(s.at("lib/net"), s.at("lib"))); + { + INFO("its name for a folder under the configuration's"); + CHECK_FALSE(blessed.mayFromConfig("\\??\\" + s.at("lib/net"), s.at("lib"))); + CHECK_FALSE(ALDiskIncludes::lexicallyUnder("\\??\\" + s.at("lib/net"), folders)); + } + UNSCOPED_INFO("which, as itself, it may name"); + CHECK(blessed.mayFromConfig(s.at("lib/net"), s.at("lib"))); } } diff --git a/indra/alscript/tests/alincludeidentity_test.cpp b/indra/alscript/tests/alincludeidentity_test.cpp index 72edc93253..10dbc833ed 100644 --- a/indra/alscript/tests/alincludeidentity_test.cpp +++ b/indra/alscript/tests/alincludeidentity_test.cpp @@ -25,34 +25,49 @@ #include "../preprocessor/alincludeidentity.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { - struct alincludeidentity_data + TEST_CASE("alincludeidentity: object and inventory items and files read back but nothing else does", "[alincludeidentity]") { - }; - - typedef test_group alincludeidentity_group; - typedef alincludeidentity_group::object alincludeidentity_object; - alincludeidentity_group alincludeidentity_instance("alincludeidentity"); - - template<> template<> - void alincludeidentity_object::test<1>() - { - set_test_name("an item in an object, one in the inventory, a file: made and read back; anything else not"); const LLUUID prim = LLUUID::generateNewID(), item = LLUUID::generateNewID(); LLUUID object, found; std::string file; const std::string in_object = ALIncludeIdentity::ofItem(prim, item); - ensure("an object's item", ALIncludeIdentity::itemOf(in_object, object, found) && object == prim && found == item); + { + INFO("an object's item"); + REQUIRE(ALIncludeIdentity::itemOf(in_object, object, found)); + CHECK(object == prim); + CHECK(found == item); + } const std::string held = ALIncludeIdentity::ofItem(LLUUID::null, item); - ensure("an inventory item", held.rfind("inventory:", 0) == 0 && ALIncludeIdentity::itemOf(held, object, found) && object.isNull() && found == item); - ensure("both in the world", ALIncludeIdentity::inWorld(in_object) && ALIncludeIdentity::inWorld(held)); - ensure("a file", ALIncludeIdentity::fileOf(ALIncludeIdentity::ofFile("/lib/a.lsl"), file) && file == "/lib/a.lsl" && - !ALIncludeIdentity::inWorld("disk:/lib/a.lsl")); - ensure("an object's with no item", !ALIncludeIdentity::itemOf("object:" + prim.asString(), object, found)); - ensure("nor a file's item, nor an item's file", !ALIncludeIdentity::itemOf("disk:/a", object, found) && !ALIncludeIdentity::fileOf(held, file)); - ensure("an empty file", !ALIncludeIdentity::fileOf("disk:", file)); + { + INFO("an inventory item"); + CHECK(held.rfind("inventory:", 0) == 0); + CHECK(ALIncludeIdentity::itemOf(held, object, found)); + CHECK(object.isNull()); + CHECK(found == item); + } + { + INFO("both in the world"); + CHECK(ALIncludeIdentity::inWorld(in_object)); + CHECK(ALIncludeIdentity::inWorld(held)); + } + { + INFO("a file"); + REQUIRE(ALIncludeIdentity::fileOf(ALIncludeIdentity::ofFile("/lib/a.lsl"), file)); + CHECK(file == "/lib/a.lsl"); + CHECK_FALSE(ALIncludeIdentity::inWorld("disk:/lib/a.lsl")); + } + UNSCOPED_INFO("an object's with no item"); + CHECK_FALSE(ALIncludeIdentity::itemOf("object:" + prim.asString(), object, found)); + { + INFO("nor a file's item, nor an item's file"); + CHECK_FALSE(ALIncludeIdentity::itemOf("disk:/a", object, found)); + CHECK_FALSE(ALIncludeIdentity::fileOf(held, file)); + } + UNSCOPED_INFO("an empty file"); + CHECK_FALSE(ALIncludeIdentity::fileOf("disk:", file)); } } diff --git a/indra/alscript/tests/alincludesearch_test.cpp b/indra/alscript/tests/alincludesearch_test.cpp index cc5875d713..282d872231 100644 --- a/indra/alscript/tests/alincludesearch_test.cpp +++ b/indra/alscript/tests/alincludesearch_test.cpp @@ -30,7 +30,7 @@ #include "fsyspath.h" #include "llfile.h" -#include "../test/lltut.h" +#include #include #include @@ -234,76 +234,99 @@ namespace tut } }; - typedef test_group alincludesearch_group; - typedef alincludesearch_group::object alincludesearch_object; - alincludesearch_group alincludesearch_instance("alincludesearch"); - - template<> template<> - void alincludesearch_object::test<1>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: the world in the settings' order; a failed text skipped until retried", "[alincludesearch]") { - set_test_name("the world in the order the settings say, only where let in; a text not in hand wanted, one held taken, one that failed not asked again unless retried"); const ALIncludeWorld::Item held = item("lib.lsl", true); const ALIncludeWorld::Item theirs = item("lib.lsl", false); world.inventory["lib.lsl"] = { held }; world.object = { theirs }; ALPreprocessor::Include found; ALIncludeSearch::wanted_t wanted; - ensure_equals("the world not let in: nothing", int(search.resolve(ask("lib.lsl"), found, asking(), where(false, false), &wanted, false)), + UNSCOPED_INFO("the world not let in: nothing"); + CHECK(int(search.resolve(ask("lib.lsl"), found, asking(), where(false, false), &wanted, false)) == int(ALPreprocessor::Found::No)); - ensure_equals("let in: the inventory's first, not in hand", int(search.resolve(ask("lib.lsl"), found, asking(), where(true, false), &wanted, false)), + UNSCOPED_INFO("let in: the inventory's first, not in hand"); + CHECK(int(search.resolve(ask("lib.lsl"), found, asking(), where(true, false), &wanted, false)) == int(ALPreprocessor::Found::Pending)); - ensure("named, and wanted", found.path == held.path && wanted.contains(held.path)); + { + INFO("named, and wanted"); + CHECK(found.path == held.path); + CHECK(wanted.contains(held.path)); + } texts.put(held.path, held.assetId, "integer lib;\n"); - ensure("in hand: taken", search.resolve(ask("lib.lsl"), found, asking(), where(true, false), nullptr, false) == ALPreprocessor::Found::Yes && - found.text == "integer lib;\n" && found.assetId == held.assetId.asString()); + { + INFO("in hand: taken"); + CHECK(search.resolve(ask("lib.lsl"), found, asking(), where(true, false), nullptr, false) == ALPreprocessor::Found::Yes); + CHECK(found.text == "integer lib;\n"); + CHECK(found.assetId == held.assetId.asString()); + } ALIncludeSearch::Where object_first = where(true, false); object_first.order = { "object", "inventory" }; texts.failed(theirs.path); - ensure("the object's, failed before: passed over for the next", search.resolve(ask("lib.lsl"), found, asking(), object_first, nullptr, false) == - ALPreprocessor::Found::Yes && found.path == held.path); + { + INFO("the object's, failed before: passed over for the next"); + CHECK(search.resolve(ask("lib.lsl"), found, asking(), object_first, nullptr, false) == + ALPreprocessor::Found::Yes); + CHECK(found.path == held.path); + } wanted.clear(); - ensure("retried: wanted again", search.resolve(ask("lib.lsl"), found, asking(), object_first, &wanted, true) == ALPreprocessor::Found::Pending && - found.path == theirs.path && wanted.contains(theirs.path)); + { + INFO("retried: wanted again"); + CHECK(search.resolve(ask("lib.lsl"), found, asking(), object_first, &wanted, true) == ALPreprocessor::Found::Pending); + CHECK(found.path == theirs.path); + CHECK(wanted.contains(theirs.path)); + } world.inventory.clear(); world.object.clear(); world.objectSaid = false; - ensure("nowhere, and the object not said: it may be there", search.resolve(ask("lib.lsl"), found, asking(), where(true, false), nullptr, false) == + UNSCOPED_INFO("nowhere, and the object not said: it may be there"); + CHECK(search.resolve(ask("lib.lsl"), found, asking(), where(true, false), nullptr, false) == ALPreprocessor::Found::Pending); } - template<> template<> - void alincludesearch_object::test<2>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: the disk: the scripter's folders and .lslrc lists and nowhere else", "[alincludesearch]") { - set_test_name("the disk: under the scripter's folders and what their .lslrc lists, a name with its extension, beside a file asking; never elsewhere"); Scratch s; const std::string part = s.write("includes/lib/part.lsl", "integer part;\n"); s.write("includes/.lslrc", "{\"include\": [\"lib\"]}"); s.write("elsewhere/secret.lsl", "integer secret;\n"); const std::string own = s.at("includes"); ALPreprocessor::Include found; - ensure("the disk off: nothing", search.resolve(ask("part"), found, asking(), where(false, false, { own }), nullptr, false) == ALPreprocessor::Found::No); - ensure("on: through the .lslrc, with its extension", - search.resolve(ask("part"), found, asking(), where(false, true, { own }), nullptr, false) == ALPreprocessor::Found::Yes && - found.path == ALIncludeIdentity::ofFile(part) && found.name == "part.lsl" && found.text == "integer part;\n"); + UNSCOPED_INFO("the disk off: nothing"); + CHECK(search.resolve(ask("part"), found, asking(), where(false, false, { own }), nullptr, false) == ALPreprocessor::Found::No); + { + INFO("on: through the .lslrc, with its extension"); + CHECK(search.resolve(ask("part"), found, asking(), where(false, true, { own }), nullptr, false) == ALPreprocessor::Found::Yes); + CHECK(found.path == ALIncludeIdentity::ofFile(part)); + CHECK(found.name == "part.lsl"); + CHECK(found.text == "integer part;\n"); + } std::string text; - ensure("admitted: its text may be asked", search.heldText(found.path, text) && text == "integer part;\n"); - ensure("not a path out of the folders", search.resolve(ask(s.at("elsewhere/secret.lsl")), found, asking(), where(false, true, { own }), nullptr, + { + INFO("admitted: its text may be asked"); + REQUIRE(search.heldText(found.path, text)); + CHECK(text == "integer part;\n"); + } + UNSCOPED_INFO("not a path out of the folders"); + CHECK(search.resolve(ask(s.at("elsewhere/secret.lsl")), found, asking(), where(false, true, { own }), nullptr, false) == ALPreprocessor::Found::No); - ensure("nor its text", !search.heldText(ALIncludeIdentity::ofFile(s.at("elsewhere/secret.lsl")), text)); + UNSCOPED_INFO("nor its text"); + CHECK_FALSE(search.heldText(ALIncludeIdentity::ofFile(s.at("elsewhere/secret.lsl")), text)); // Beside a file asking, where the file is in a blessed folder. const std::string beside = s.write("includes/lib/near.lsl", "integer near;\n"); - ensure("beside the asking file", search.resolve(ask("near.lsl", false, ALIncludeIdentity::ofFile(part)), found, asking(), where(false, true, { own }), - nullptr, false) == ALPreprocessor::Found::Yes && - found.path == ALIncludeIdentity::ofFile(beside)); + { + INFO("beside the asking file"); + CHECK(search.resolve(ask("near.lsl", false, ALIncludeIdentity::ofFile(part)), found, asking(), where(false, true, { own }), + nullptr, false) == ALPreprocessor::Found::Yes); + CHECK(found.path == ALIncludeIdentity::ofFile(beside)); + } } - template<> template<> - void alincludesearch_object::test<3>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: SLua: a .luaurc alias on disk found and its folder blessed; mode read", "[alincludesearch]") { - set_test_name("SLua: an alias through the .luaurc chain over a file on disk, its folder blessed only as far as a configuration may reach; the chain's mode read"); Scratch s; const std::string script = s.write("project/main.luau", "local net = require(\"@lib/net\")\n"); s.write("project/.luaurc", "{\"aliases\": {\"lib\": \"./modules\"}, \"languageMode\": \"strict\"}"); @@ -313,50 +336,78 @@ namespace tut const ALIncludeSearch::Where disk = where(false, true, { s.at("project") }); ALPreprocessor::Include found; std::vector aliases; - ensure("through the alias", search.resolve(ask("@lib/net", true), found, from_disk, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes && - found.path == ALIncludeIdentity::ofFile(net)); - ensure("its folder blessed for the run", aliases.size() == 1 && aliases[0].find("modules") != std::string::npos); - ensure("an alias nobody says: nothing", search.resolve(ask("@other/x", true), found, from_disk, disk, nullptr, false) == ALPreprocessor::Found::No); + { + INFO("through the alias"); + CHECK(search.resolve(ask("@lib/net", true), found, from_disk, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); + CHECK(found.path == ALIncludeIdentity::ofFile(net)); + } + { + INFO("its folder blessed for the run"); + REQUIRE(aliases.size() == 1); + REQUIRE(aliases[0].find("modules") != std::string::npos); + } + UNSCOPED_INFO("an alias nobody says: nothing"); + CHECK(search.resolve(ask("@other/x", true), found, from_disk, disk, nullptr, false) == ALPreprocessor::Found::No); std::vector chain; - ensure("the chain over the file", search.configsFor(from_disk.self, from_disk, disk, nullptr, false, chain) == ALPreprocessor::Found::Yes && - !chain.empty() && chain[0].path == ALIncludeIdentity::ofFile(s.at("project/.luaurc"))); + { + INFO("the chain over the file"); + CHECK(search.configsFor(from_disk.self, from_disk, disk, nullptr, false, chain) == ALPreprocessor::Found::Yes); + REQUIRE_FALSE(chain.empty()); + CHECK(chain[0].path == ALIncludeIdentity::ofFile(s.at("project/.luaurc"))); + } ALLuauConfig config; - ensure("its mode", search.configOf(from_disk, disk, config) && config.mode == "strict"); + { + INFO("its mode"); + CHECK(search.configOf(from_disk, disk, config)); + CHECK(config.mode == "strict"); + } const std::vector> folders = search.moduleFolders(from_disk, disk); - ensure("the module folders: the scripter's, and the alias's under its name", - folders.size() == 2 && folders[0].first.empty() && folders[1].first == "@lib/"); + { + INFO("the module folders: the scripter's, and the alias's under its name"); + REQUIRE(folders.size() == 2); + REQUIRE(folders[0].first.empty()); + CHECK(folders[1].first == "@lib/"); + } // A script in the world: the world's configurations, Pending while // the object has not said; above them the one at the top of the // scripter's folders. world.configsPending = true; - ensure("the object not said", search.configsFor(asking(true).self, asking(true), where(true, true, { s.at("project") }), nullptr, false, chain) == + UNSCOPED_INFO("the object not said"); + CHECK(search.configsFor(asking(true).self, asking(true), where(true, true, { s.at("project") }), nullptr, false, chain) == ALPreprocessor::Found::Pending); world.configsPending = false; - ensure("the top of the scripter's folder", search.configsFor(asking(true).self, asking(true), where(true, true, { s.at("project") }), nullptr, false, - chain) == ALPreprocessor::Found::Yes && - chain.size() == 1); + { + INFO("the top of the scripter's folder"); + CHECK(search.configsFor(asking(true).self, asking(true), where(true, true, { s.at("project") }), nullptr, false, + chain) == ALPreprocessor::Found::Yes); + REQUIRE(chain.size() == 1); + } } - template<> template<> - void alincludesearch_object::test<4>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: every file a text includes and theirs in turn each once", "[alincludesearch]") { - set_test_name("every file a text includes, and theirs in turn, each once"); Scratch s; const std::string a = s.write("inc/a.lsl", "#include \"b.lsl\"\ninteger a;\n"); const std::string b = s.write("inc/b.lsl", "#include \"a.lsl\"\ninteger b;\n"); const std::vector found = search.includedBy("#include \"a.lsl\"\n#include \ndefault { state_entry() { } }\n", asking(), where(false, true, { s.at("inc") })); - ensure_equals("both, once", found.size(), size_t(2)); - ensure("in the order met", found[0].path == ALIncludeIdentity::ofFile(a) && found[1].path == ALIncludeIdentity::ofFile(b)); - ensure("names of an include", ALIncludeSearch::itemNameOf("./lib/util.lsl") == "util.lsl" && - ALIncludeSearch::foldersOf("../lib/util.lsl") == std::vector({ "..", "lib" })); + UNSCOPED_INFO("both, once"); + REQUIRE(found.size() == size_t(2)); + { + INFO("in the order met"); + CHECK(found[0].path == ALIncludeIdentity::ofFile(a)); + CHECK(found[1].path == ALIncludeIdentity::ofFile(b)); + } + { + INFO("names of an include"); + CHECK(ALIncludeSearch::itemNameOf("./lib/util.lsl") == "util.lsl"); + CHECK(ALIncludeSearch::foldersOf("../lib/util.lsl") == std::vector({ "..", "lib" })); + } } - template<> template<> - void alincludesearch_object::test<5>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: explainMissing: the object's silence first then the world and the disk", "[alincludesearch]") { - set_test_name("a name not found said as why: the object's silence first, one in the world that is not taken from, the disk not looked in"); const auto missing = [](const char* key, const char* name, const std::string& file = std::string()) { ALScriptProblem p; p.key = key; @@ -372,23 +423,27 @@ namespace tut facts.noFolders = false; facts.inWorld = [](const std::string& name) { return name == "lib.lsl"; }; ALIncludeSearch::explainMissing(problems, facts); - ensure_equals("the silence said first", problems.front().key, std::string("PreprocObjectUnanswered")); - ensure_equals("one in the world, not taken from", problems[1].key, std::string("PreprocIncludeInWorld")); - ensure_equals("one nowhere, the disk looked in: as it was", problems[2].key, std::string("PreprocModuleNotFound")); + UNSCOPED_INFO("the silence said first"); + CHECK(problems.front().key == std::string("PreprocObjectUnanswered")); + UNSCOPED_INFO("one in the world, not taken from"); + CHECK(problems[1].key == std::string("PreprocIncludeInWorld")); + UNSCOPED_INFO("one nowhere, the disk looked in: as it was"); + CHECK(problems[2].key == std::string("PreprocModuleNotFound")); problems = { missing("PreprocModuleNotFound", "util"), missing("PreprocIncludeNotFound", "x.lsl", "disk:/lib/a.lsl") }; facts = ALIncludeSearch::Missing(); facts.disk = true; facts.noFolders = true; ALIncludeSearch::explainMissing(problems, facts); - ensure_equals("no folder of the scripter's: the disk not looked in", problems[0].key, std::string("PreprocModuleNotOnDisk")); - ensure_equals("but one asked from a file on disk is not second-guessed", problems[1].key, std::string("PreprocIncludeNotFound")); - ensure("said with its name", problems[0].message.find("'util'") != std::string::npos); + 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"); + CHECK(problems[1].key == std::string("PreprocIncludeNotFound")); + UNSCOPED_INFO("said with its name"); + REQUIRE(problems[0].message.find("'util'") != std::string::npos); } - template<> template<> - void alincludesearch_object::test<6>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: leftOut: the names once in order and the problems and the disk's route", "[alincludesearch]") { - set_test_name("what a run left out: the names not found each once in order, the problems that said so, and whether a folder on disk would have let one in"); const auto said = [](const char* key, const char* name) { ALScriptProblem p; p.key = key; @@ -403,15 +458,27 @@ namespace tut said("PreprocSomethingElse", "c.lsl"), said("PreprocModuleNotFound", "util"), said("PreprocIncludeNotFound", "b.lsl") }; ALIncludeSearch::LeftOut left = ALIncludeSearch::leftOut(problems); - ensure("each once, in order", left.names == std::vector{ "b.lsl", "util" }); - ensure_equals("every problem that said so", left.problems, 3); - ensure("found nowhere: nothing a folder would change", !left.diskRoute); + UNSCOPED_INFO("each once, in order"); + CHECK(left.names == std::vector{ "b.lsl", "util" }); + UNSCOPED_INFO("every problem that said so"); + CHECK(left.problems == 3); + UNSCOPED_INFO("found nowhere: nothing a folder would change"); + CHECK_FALSE(left.diskRoute); left = ALIncludeSearch::leftOut({ said("PreprocIncludeInWorld", "a.lsl") }); - ensure("one in the world, which is not taken from: the disk's route", left.diskRoute && left.names.size() == 1); + { + INFO("one in the world, which is not taken from: the disk's route"); + CHECK(left.diskRoute); + REQUIRE(left.names.size() == 1); + } left = ALIncludeSearch::leftOut({ said("PreprocModuleNotOnDisk", "m") }); - ensure("the disk not looked in: the disk's route", left.diskRoute && left.names == std::vector{ "m" }); - ensure("nothing said: nothing left out", ALIncludeSearch::leftOut({}).names.empty()); + { + INFO("the disk not looked in: the disk's route"); + CHECK(left.diskRoute); + CHECK(left.names == std::vector{ "m" }); + } + UNSCOPED_INFO("nothing said: nothing left out"); + REQUIRE(ALIncludeSearch::leftOut({}).names.empty()); } // The require parity suite (LA13): what a SLua require finds on disk, @@ -449,10 +516,8 @@ namespace tut }; } // anonymous namespace - template<> template<> - void alincludesearch_object::test<7>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: the require parity suite: Luau's and the plugin's cases as LAD9 picks", "[alincludesearch]") { - set_test_name("the require parity suite: each case from Luau's tests, the plugin's and ours, as LAD9 picks, beside what the search found before"); Scratch s; // Luau's without_config, in lw/. s.write("proj/lw/dependency.luau", "return 'dependency'\n"); @@ -587,46 +652,65 @@ namespace tut { under = under_tree(file); } - ensure_equals(std::string(one.what) + ": as LAD9 has it", under, std::string(one.chosen)); + UNSCOPED_INFO(std::string(one.what) + ": as LAD9 has it"); + REQUIRE(under == std::string(one.chosen)); if (*one.name != '@') { // A name the search before took: asked of it. const std::string before = searched(one.name, own, disk); - ensure_equals(std::string(one.what) + ": as before", before.empty() ? before : under_tree(before), std::string(one.before)); + UNSCOPED_INFO(std::string(one.what) + ": as before"); + REQUIRE((before.empty() ? before : under_tree(before)) == std::string(one.before)); } else if (!*one.rule) { // An alias's, where all agree: found so before. - ensure_equals(std::string(one.what) + ": as before", under, std::string(one.before)); + UNSCOPED_INFO(std::string(one.what) + ": as before"); + REQUIRE(under == std::string(one.before)); } } } - template<> template<> - void alincludesearch_object::test<8>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: navigatorPath: ./ for no prefix; @self; @sl- reserved; no alias climbs", "[alincludesearch]") { - set_test_name("a require's path in the navigator's terms: no prefix is ./; @self from beside the file; @sl-* reserved; an alias may not climb"); const auto path = [](const std::string& name, const std::string& stem = "main") { return ALRequireNavigation::navigatorPath(name, stem); }; - ensure_equals("no prefix", path("util").path, std::string("./util")); - ensure_equals("a folder in it", path("lib/util").path, std::string("./lib/util")); - ensure_equals("./ as it is", path("./util").path, std::string("./util")); - ensure_equals("../ as it is", path("../util").path, std::string("../util")); - ensure_equals("a separator of Windows's", path("lib\\util").path, std::string("./lib/util")); - ensure_equals("an alias as it is", path("@lib/util").path, std::string("@lib/util")); - ensure_equals("@self from a file: the folder of its name", path("@self/child").path, std::string("./main/child")); - ensure_equals("@self from a folder's init: beside it", path("@self/child", "init").path, std::string("./child")); - ensure_equals("@self alone", path("@self").path, std::string("./main")); - ensure_equals("@SELF in any case", path("@SELF/child", "init").path, std::string("./child")); - ensure("@sl-* reserved", !path("@sl-std/util").error.empty() && !path("@SL-std").error.empty()); - ensure("an alias may not climb", !path("@lib/../secret").error.empty() && !path("@lib/a/../../b").error.empty()); - ensure("@self may, as Luau's own", path("@self/../x").error.empty()); - ensure("a name with .. in it is no climb", path("@lib/a..b").error.empty()); + UNSCOPED_INFO("no prefix"); + CHECK(path("util").path == std::string("./util")); + UNSCOPED_INFO("a folder in it"); + CHECK(path("lib/util").path == std::string("./lib/util")); + UNSCOPED_INFO("./ as it is"); + CHECK(path("./util").path == std::string("./util")); + UNSCOPED_INFO("../ as it is"); + CHECK(path("../util").path == std::string("../util")); + UNSCOPED_INFO("a separator of Windows's"); + CHECK(path("lib\\util").path == std::string("./lib/util")); + UNSCOPED_INFO("an alias as it is"); + CHECK(path("@lib/util").path == std::string("@lib/util")); + UNSCOPED_INFO("@self from a file: the folder of its name"); + CHECK(path("@self/child").path == std::string("./main/child")); + UNSCOPED_INFO("@self from a folder's init: beside it"); + CHECK(path("@self/child", "init").path == std::string("./child")); + UNSCOPED_INFO("@self alone"); + CHECK(path("@self").path == std::string("./main")); + UNSCOPED_INFO("@SELF in any case"); + CHECK(path("@SELF/child", "init").path == std::string("./child")); + { + INFO("@sl-* reserved"); + REQUIRE_FALSE(path("@sl-std/util").error.empty()); + REQUIRE_FALSE(path("@SL-std").error.empty()); + } + { + INFO("an alias may not climb"); + REQUIRE_FALSE(path("@lib/../secret").error.empty()); + REQUIRE_FALSE(path("@lib/a/../../b").error.empty()); + } + UNSCOPED_INFO("@self may, as Luau's own"); + REQUIRE(path("@self/../x").error.empty()); + UNSCOPED_INFO("a name with .. in it is no climb"); + REQUIRE(path("@lib/a..b").error.empty()); } - template<> template<> - void alincludesearch_object::test<9>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: a require walked through the world; Pending where it has not said", "[alincludesearch]") { - set_test_name("a require walked through the world: an inventory folder's items and folders, its .luaurc notecard, an object's contents; Pending where the world has not said; nothing of it with world includes off but the scripter's folders' own .luaurc"); // The inventory: a project folder in a root, with a library folder, // a module's folder with its init, and a configuration. const auto add = [this](const std::string& folder, const std::string& name) { @@ -664,35 +748,50 @@ namespace tut const ALPreprocessor::Found said = search.resolve(alincludesearch_data::ask(name, true, own.self), include, own, in, wanted, false, &aliases); return said == ALPreprocessor::Found::Yes ? include.path : said == ALPreprocessor::Found::Pending ? std::string("pending") : std::string(); }; - ensure_equals("beside it", found("./util", on), util.path); - ensure_equals("with no prefix", found("util", on), util.path); - ensure_equals("up a folder", found("../shared", on), shared.path); - ensure_equals("a folder's init", found("./mod", on), init.path); - ensure_equals("in a folder", found("./lib/net", on), net.path); - ensure_equals("through the folder's .luaurc", found("@lib/net", on), net.path); - ensure_equals("one there is not", found("./nothere", on), std::string()); - ensure_equals("a world .luaurc names nothing on disk", found("@far/x", on), std::string()); - ensure_equals("past the world's top, the scripter's folders' own .luaurc", found("@top/x", on), ALIncludeIdentity::ofFile(x)); + UNSCOPED_INFO("beside it"); + CHECK(found("./util", on) == util.path); + UNSCOPED_INFO("with no prefix"); + CHECK(found("util", on) == util.path); + UNSCOPED_INFO("up a folder"); + CHECK(found("../shared", on) == shared.path); + UNSCOPED_INFO("a folder's init"); + CHECK(found("./mod", on) == init.path); + UNSCOPED_INFO("in a folder"); + CHECK(found("./lib/net", on) == net.path); + UNSCOPED_INFO("through the folder's .luaurc"); + CHECK(found("@lib/net", on) == net.path); + UNSCOPED_INFO("one there is not"); + CHECK(found("./nothere", on) == std::string()); + UNSCOPED_INFO("a world .luaurc names nothing on disk"); + CHECK(found("@far/x", on) == std::string()); + UNSCOPED_INFO("past the world's top, the scripter's folders' own .luaurc"); + CHECK(found("@top/x", on) == ALIncludeIdentity::ofFile(x)); // What the world has not said yet: Pending, and what is on its way // wanted. ALIncludeSearch::wanted_t wanted; world.folders["lib"].known = false; - ensure_equals("a folder not known: Pending", found("./lib/net", on, &wanted), std::string("pending")); + UNSCOPED_INFO("a folder not known: Pending"); + CHECK(found("./lib/net", on, &wanted) == std::string("pending")); world.folders["lib"].known = true; ALScriptTextCache fresh; ALIncludeSearch unread(fresh, world); ALPreprocessor::Include include; std::vector aliases; - ensure("the .luaurc not in hand: Pending", unread.resolve(ask("@lib/net", true, own.self), include, own, on, &wanted, false, &aliases) == + UNSCOPED_INFO("the .luaurc not in hand: Pending"); + CHECK(unread.resolve(ask("@lib/net", true, own.self), include, own, on, &wanted, false, &aliases) == ALPreprocessor::Found::Pending); - ensure("and wanted", wanted.contains(config.path)); + UNSCOPED_INFO("and wanted"); + CHECK(wanted.contains(config.path)); // With world includes off, nothing of the world; the scripter's // folders' own .luaurc still. - ensure_equals("off: not beside it", found("./util", off), std::string()); - ensure_equals("off: no .luaurc of the world's", found("@lib/net", off), std::string()); - ensure_equals("off: the scripter's folders' own", found("@top/x", off), ALIncludeIdentity::ofFile(x)); + UNSCOPED_INFO("off: not beside it"); + CHECK(found("./util", off) == std::string()); + UNSCOPED_INFO("off: no .luaurc of the world's"); + CHECK(found("@lib/net", off) == std::string()); + UNSCOPED_INFO("off: the scripter's folders' own"); + CHECK(found("@top/x", off) == ALIncludeIdentity::ofFile(x)); // An object's contents, one folder. world.folders["contents"].parent = std::string(); @@ -701,17 +800,19 @@ namespace tut texts.put(helper.path, helper.assetId, "return 'helper'\n"); const ALIncludeSearch::Asking in_object{ script.path, true }; ALPreprocessor::Include got; - ensure("an object's: beside it", search.resolve(ask("./helper", true, script.path), got, in_object, on, nullptr, false, &aliases) == - ALPreprocessor::Found::Yes && - got.path == helper.path); - ensure("an object has no folder above", search.resolve(ask("../helper", true, script.path), got, in_object, on, nullptr, false, &aliases) == + { + INFO("an object's: beside it"); + CHECK(search.resolve(ask("./helper", true, script.path), got, in_object, on, nullptr, false, &aliases) == + ALPreprocessor::Found::Yes); + CHECK(got.path == helper.path); + } + UNSCOPED_INFO("an object has no folder above"); + CHECK(search.resolve(ask("../helper", true, script.path), got, in_object, on, nullptr, false, &aliases) == ALPreprocessor::Found::No); } - template<> template<> - void alincludesearch_object::test<10>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: studio aliases reach the disk from an object; a project's .luaurc wins", "[alincludesearch]") { - set_test_name("studio aliases: a script in an object reaches a library on disk through one, world includes off; a project's own .luaurc wins; naming blesses only for a require"); Scratch s; const std::string util = s.write("library/util.luau", "return 'util'\n"); const std::string helper = s.write("library/helper.lsl", "integer helper;\n"); @@ -729,32 +830,37 @@ namespace tut return search.resolve(alincludesearch_data::ask(name, true, who.self), include, who, in, nullptr, false, &aliases) == ALPreprocessor::Found::Yes ? include.path : std::string(); }; - ensure_equals("an object's script, through the studio's alias", found("@lib/util", in_object, disk), ALIncludeIdentity::ofFile(util)); - ensure_equals("in any case", found("@LIB/util", in_object, disk), ALIncludeIdentity::ofFile(util)); - ensure_equals("one nobody named", found("@other/util", in_object, disk), std::string()); + UNSCOPED_INFO("an object's script, through the studio's alias"); + CHECK(found("@lib/util", in_object, disk) == ALIncludeIdentity::ofFile(util)); + UNSCOPED_INFO("in any case"); + CHECK(found("@LIB/util", in_object, disk) == ALIncludeIdentity::ofFile(util)); + UNSCOPED_INFO("one nobody named"); + CHECK(found("@other/util", in_object, disk) == std::string()); ALIncludeSearch::Where unnamed = disk; unnamed.aliases.clear(); - ensure_equals("unnamed: not read, the folder blessed by nothing", found("@lib/util", in_object, unnamed), std::string()); + UNSCOPED_INFO("unnamed: not read, the folder blessed by nothing"); + CHECK(found("@lib/util", in_object, unnamed) == std::string()); ALIncludeSearch::Where off = disk; off.disk = false; - ensure_equals("the disk off: nothing", found("@lib/util", in_object, off), std::string()); + UNSCOPED_INFO("the disk off: nothing"); + CHECK(found("@lib/util", in_object, off) == std::string()); // A project's own `.luaurc` names the alias first. const ALIncludeSearch::Asking from_disk{ ALIncludeIdentity::ofFile(script), true }; - ensure_equals("the project's own alias", found("@shared/util", from_disk, disk), ALIncludeIdentity::ofFile(own)); - ensure_equals("where it names none, the studio's", found("@shared/util", in_object, disk), ALIncludeIdentity::ofFile(util)); + UNSCOPED_INFO("the project's own alias"); + CHECK(found("@shared/util", from_disk, disk) == ALIncludeIdentity::ofFile(own)); + UNSCOPED_INFO("where it names none, the studio's"); + CHECK(found("@shared/util", in_object, disk) == ALIncludeIdentity::ofFile(util)); // Naming blesses the folder for a require, and adds nothing to an // include's search. ALPreprocessor::Include include; - ensure("an LSL include does not search an alias's folder", - search.resolve(ask("helper.lsl"), include, asking(), disk, nullptr, false) == ALPreprocessor::Found::No); + UNSCOPED_INFO("an LSL include does not search an alias's folder"); + CHECK(search.resolve(ask("helper.lsl"), include, asking(), disk, nullptr, false) == ALPreprocessor::Found::No); } - template<> template<> - void alincludesearch_object::test<11>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: a require only the old search found: where and each way to say it now", "[alincludesearch]") { - set_test_name("a require the search found before, and finds nothing now: where it found it, and each way to say it now that finds that very file"); Scratch s; const std::string script = s.write("proj/main.luau", "return require('util')\n"); s.write("proj/.luaurc", "{\"aliases\": {\"shared\": \"../inc\"}}"); @@ -768,7 +874,8 @@ namespace tut const auto missed = [&](const std::string& name, const ALIncludeSearch::Asking& who) { ALPreprocessor::Include include; std::vector aliases; - ensure(name + ": found nothing now", search.resolve(alincludesearch_data::ask(name, true, who.self), include, who, disk, nullptr, false, &aliases) == + UNSCOPED_INFO(name + ": found nothing now"); + REQUIRE(search.resolve(alincludesearch_data::ask(name, true, who.self), include, who, disk, nullptr, false, &aliases) == ALPreprocessor::Found::No); return include; }; @@ -782,32 +889,47 @@ namespace tut }; ALPreprocessor::Include include = missed("util2", from_disk); - ensure_equals("found under the file's own folder", include.searched, under); - ensure_equals("written from beside it", said(include), std::string("./lib/util2")); + UNSCOPED_INFO("found under the file's own folder"); + CHECK(include.searched == under); + UNSCOPED_INFO("written from beside it"); + CHECK(said(include) == std::string("./lib/util2")); include = missed("util", from_disk); - ensure_equals("found in another include folder", include.searched, util); - ensure_equals("through the .luaurc's alias that reaches it", said(include), std::string("@shared/util")); + UNSCOPED_INFO("found in another include folder"); + CHECK(include.searched == util); + UNSCOPED_INFO("through the .luaurc's alias that reaches it"); + CHECK(said(include) == std::string("@shared/util")); include = missed("util", in_object); - ensure_equals("from an object: through the alias of the .luaurc at the top of an include folder", said(include), std::string("@shared/util")); + UNSCOPED_INFO("from an object: through the alias of the .luaurc at the top of an include folder"); + CHECK(said(include) == std::string("@shared/util")); include = missed("solo", in_object); - ensure_equals("from an object, where none reaches it", include.searched, solo); - ensure_equals("through a studio alias of the folder that holds it, named after it", said(include), std::string("@other/solo +@other")); - ensure_equals("the folder named", include.moves[0].folder, s.at("other")); + UNSCOPED_INFO("from an object, where none reaches it"); + CHECK(include.searched == solo); + UNSCOPED_INFO("through a studio alias of the folder that holds it, named after it"); + CHECK(said(include) == std::string("@other/solo +@other")); + UNSCOPED_INFO("the folder named"); + CHECK(include.moves[0].folder == s.at("other")); include = missed("deep/net", in_object); - ensure("a folder in it", include.searched == deep && said(include) == "@other/deep/net +@other"); + { + INFO("a folder in it"); + CHECK(include.searched == deep); + CHECK(said(include) == "@other/deep/net +@other"); + } include = missed("nowhere", from_disk); - ensure("nowhere before either", include.searched.empty() && include.moves.empty()); + { + INFO("nowhere before either"); + REQUIRE(include.searched.empty()); + REQUIRE(include.moves.empty()); + } include = missed("@missing/util", from_disk); - ensure("an alias's is no searched name", include.searched.empty()); + UNSCOPED_INFO("an alias's is no searched name"); + REQUIRE(include.searched.empty()); } - template<> template<> - void alincludesearch_object::test<12>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: a file beside a folder's init of its name is taken and the init said", "[alincludesearch]") { - set_test_name("a file and a folder's init of one name: the file taken, the init passed over said; either alone, nothing to say"); Scratch s; const std::string script = s.write("proj/main.luau", "return require('./both')\n"); const std::string file = s.write("proj/both.luau", "return 'file'\n"); @@ -817,24 +939,31 @@ namespace tut const ALIncludeSearch::Asking own{ ALIncludeIdentity::ofFile(script), true }; ALPreprocessor::Include found; std::vector aliases; - ensure("found", search.resolve(ask("./both", true, own.self), found, own, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); - ensure_equals("the file", found.path, ALIncludeIdentity::ofFile(file)); - ensure_equals("the init passed over, by its path from the folder both are in", found.passedOver, std::string("both/init.luau")); - ensure_equals("as the file taken is, by its name", found.name, std::string("both.luau")); + UNSCOPED_INFO("found"); + CHECK(search.resolve(ask("./both", true, own.self), found, own, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); + UNSCOPED_INFO("the file"); + CHECK(found.path == ALIncludeIdentity::ofFile(file)); + UNSCOPED_INFO("the init passed over, by its path from the folder both are in"); + CHECK(found.passedOver == std::string("both/init.luau")); + UNSCOPED_INFO("as the file taken is, by its name"); + CHECK(found.name == std::string("both.luau")); ALPreprocessor::Include alone; - ensure("a folder's init alone", search.resolve(ask("./alone", true, own.self), alone, own, disk, nullptr, false, &aliases) == - ALPreprocessor::Found::Yes && - alone.passedOver.empty()); + { + INFO("a folder's init alone"); + CHECK(search.resolve(ask("./alone", true, own.self), alone, own, disk, nullptr, false, &aliases) == + ALPreprocessor::Found::Yes); + REQUIRE(alone.passedOver.empty()); + } ALPreprocessor::Include named; - ensure("named with its extension: the file alone, nothing passed over", - search.resolve(ask("./both.luau", true, own.self), named, own, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes && - named.passedOver.empty()); + { + INFO("named with its extension: the file alone, nothing passed over"); + CHECK(search.resolve(ask("./both.luau", true, own.self), named, own, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); + REQUIRE(named.passedOver.empty()); + } } - template<> template<> - void alincludesearch_object::test<13>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: why a require found nothing: Luau's navigator's words or studio rules", "[alincludesearch]") { - set_test_name("why a require found nothing, in Luau's navigator's words or the studio's rules'; said as a module not found, and as the disk not looked in where it was not"); Scratch s; const std::string script = s.write("proj/main.luau", "return require('./nowhere')\n"); s.write("proj/.luaurc", "{\"aliases\": {\"gone\": \"./gone\", \"c1\": \"@c2\", \"c2\": \"@c1\", \"lib\": \"./lib\"}}"); @@ -846,12 +975,18 @@ namespace tut search.resolve(alincludesearch_data::ask(name, true, own.self), found, own, disk, nullptr, false, &aliases); return found.why; }; - ensure_equals("nothing so named", why("./nowhere"), std::string("could not resolve child component \"nowhere\"")); - ensure_equals("an alias nobody names", why("@missing/x"), std::string("@missing is not a valid alias")); - ensure_equals("aliases in a circle", why("@c1"), std::string("detected alias cycle (@c1 -> @c2 -> @c1)")); - ensure_equals("an alias whose folder is not there", why("@gone/x"), std::string("the alias stands for './gone', which is not there")); - ensure("reserved", why("@sl-std/x").find("reserved") != std::string::npos); - ensure("climbing", why("@lib/../x").find("may not climb") != std::string::npos); + UNSCOPED_INFO("nothing so named"); + CHECK(why("./nowhere") == std::string("could not resolve child component \"nowhere\"")); + UNSCOPED_INFO("an alias nobody names"); + CHECK(why("@missing/x") == std::string("@missing is not a valid alias")); + UNSCOPED_INFO("aliases in a circle"); + CHECK(why("@c1") == std::string("detected alias cycle (@c1 -> @c2 -> @c1)")); + UNSCOPED_INFO("an alias whose folder is not there"); + CHECK(why("@gone/x") == std::string("the alias stands for './gone', which is not there")); + UNSCOPED_INFO("reserved"); + REQUIRE(why("@sl-std/x").find("reserved") != std::string::npos); + UNSCOPED_INFO("climbing"); + REQUIRE(why("@lib/../x").find("may not climb") != std::string::npos); // Said as a module not found, for what explains it. ALScriptProblem said; @@ -861,14 +996,17 @@ namespace tut ALIncludeSearch::Missing facts; facts.disk = false; ALIncludeSearch::explainMissing(problems, facts); - ensure("the disk not looked in", problems[0].key == "PreprocModuleNotOnDisk" && problems[0].args == std::vector{ "./nowhere" }); - ensure("left out", ALIncludeSearch::leftOut({ said }).names == std::vector{ "./nowhere" }); + { + INFO("the disk not looked in"); + CHECK(problems[0].key == "PreprocModuleNotOnDisk"); + CHECK(problems[0].args == std::vector{ "./nowhere" }); + } + UNSCOPED_INFO("left out"); + CHECK(ALIncludeSearch::leftOut({ said }).names == std::vector{ "./nowhere" }); } - template<> template<> - void alincludesearch_object::test<14>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: suggestions as a require is typed: aliases first; each module resolves", "[alincludesearch]") { - set_test_name("paths suggested as a require is typed, by Luau's own suggester over the places: aliases, ./ and ../ first, then a folder's modules and folders; every module offered resolves; an include's by its search"); Scratch s; const std::string script = s.write("proj/main.luau", "return require('./util')\n"); s.write("proj/util.luau", "return 1\n"); @@ -892,15 +1030,24 @@ namespace tut } return out; }; - ensure_equals("first: the aliases in reach, none reserved, then ./ and ../", labels(""), std::string("@shared/ @tools/ .// ..//")); - ensure_equals("the file's own folder: modules and folders, none hidden, nothing else", labels("./"), std::string("../ lib/ main util")); - ensure_equals("a name part typed: the same, for the editor to narrow", labels("./li"), labels("./")); - ensure_equals("in a folder: its own, and up", labels("./lib/"), std::string("../ http net sub/")); - ensure_equals("a name part typed there", labels("./lib/ne"), labels("./lib/")); - ensure_equals("through a .luaurc's alias, which may not climb: no way up", labels("@shared/"), std::string("http net sub/")); - ensure_equals("nor further in", labels("@shared/sub/"), std::string("deep")); - ensure_equals("through the studio's", labels("@tools/"), std::string("t")); - ensure_equals("a reserved alias walks nowhere", labels("@sl-std/"), std::string()); + UNSCOPED_INFO("first: the aliases in reach, none reserved, then ./ and ../"); + CHECK(labels("") == std::string("@shared/ @tools/ .// ..//")); + UNSCOPED_INFO("the file's own folder: modules and folders, none hidden, nothing else"); + CHECK(labels("./") == std::string("../ lib/ main util")); + UNSCOPED_INFO("a name part typed: the same, for the editor to narrow"); + CHECK(labels("./li") == labels("./")); + UNSCOPED_INFO("in a folder: its own, and up"); + CHECK(labels("./lib/") == std::string("../ http net sub/")); + UNSCOPED_INFO("a name part typed there"); + CHECK(labels("./lib/ne") == labels("./lib/")); + UNSCOPED_INFO("through a .luaurc's alias, which may not climb: no way up"); + CHECK(labels("@shared/") == std::string("http net sub/")); + UNSCOPED_INFO("nor further in"); + CHECK(labels("@shared/sub/") == std::string("deep")); + UNSCOPED_INFO("through the studio's"); + CHECK(labels("@tools/") == std::string("t")); + UNSCOPED_INFO("a reserved alias walks nowhere"); + CHECK(labels("@sl-std/") == std::string()); // Every module offered is what a require of it finds. for (const std::string& typed : { std::string("./"), std::string("./lib/"), std::string("./lib/sub/"), std::string("@shared/"), std::string("@tools/") }) @@ -913,11 +1060,13 @@ namespace tut } ALPreprocessor::Include found; std::vector aliases; - ensure("resolves: " + one.path, search.resolve(ask(one.path, true, own.self), found, own, disk, nullptr, false, &aliases) == + UNSCOPED_INFO("resolves: " + one.path); + REQUIRE(search.resolve(ask(one.path, true, own.self), found, own, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); } } - ensure_equals("each one's whole path", search.suggest(own.self, "./lib/", true, own, disk)[1].path, std::string("./lib/http")); + UNSCOPED_INFO("each one's whole path"); + CHECK(search.suggest(own.self, "./lib/", true, own, disk)[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`. @@ -930,20 +1079,23 @@ namespace tut } return said; }; - ensure_equals("an LSL include's", included(""), std::string("defs.lslh inc.lsl lib/")); - ensure_equals("in a folder under it", included("lib/"), std::string("lib/sub/")); + UNSCOPED_INFO("an LSL include's"); + CHECK(included("") == std::string("defs.lslh inc.lsl lib/")); + UNSCOPED_INFO("in a folder under it"); + CHECK(included("lib/") == std::string("lib/sub/")); // Only under the folders the search looks in, however it is typed: // not up out of them, nor from a root elsewhere, nor another // machine's share. - ensure_equals("not up out of them", included("../"), std::string()); - ensure_equals("nor from a root elsewhere", included(fsyspath(s.root).string() + "/"), std::string()); - ensure_equals("nor a share", included("\\\\alincludesearch.invalid\\share\\"), std::string()); + UNSCOPED_INFO("not up out of them"); + CHECK(included("../") == std::string()); + UNSCOPED_INFO("nor from a root elsewhere"); + CHECK(included(fsyspath(s.root).string() + "/") == std::string()); + UNSCOPED_INFO("nor a share"); + CHECK(included("\\\\alincludesearch.invalid\\share\\") == std::string()); } - template<> template<> - void alincludesearch_object::test<15>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: a .config.luau is read as the .luaurc it returns; beside one neither", "[alincludesearch]") { - set_test_name("a .config.luau on disk is read as the .luaurc it says: its aliases walked and suggested, its mode the analyzer's, in the chain as Luau reads one; a folder with both reads as neither; one that fails is passed over; at the top of the scripter's folder for a script in the world"); Scratch s; const std::string script = s.write("proj/src/main.luau", "return require('@lib/net')\n"); const std::string net = s.write("proj/modules/net.luau", "return 1\n"); @@ -988,24 +1140,41 @@ namespace tut }; ALLuauConfig config; - ensure_equals("through its alias", resolved("@lib/net"), ALIncludeIdentity::ofFile(net)); - ensure_equals("in the chain by its own name", chain(), std::string(".config.luau")); - ensure("its mode the analyzer's", search.configOf(own, disk(), config) && config.mode == "strict"); - ensure_equals("its alias suggested", suggested(), std::string("@lib ./ ../")); + UNSCOPED_INFO("through its alias"); + CHECK(resolved("@lib/net") == ALIncludeIdentity::ofFile(net)); + UNSCOPED_INFO("in the chain by its own name"); + CHECK(chain() == std::string(".config.luau")); + { + INFO("its mode the analyzer's"); + REQUIRE(search.configOf(own, disk(), config)); + CHECK(config.mode == "strict"); + } + UNSCOPED_INFO("its alias suggested"); + CHECK(suggested() == std::string("@lib ./ ../")); // A nearer .luaurc over it, as Luau reads a chain. s.write("proj/src/.luaurc", "{\"languageMode\": \"nonstrict\"}"); - ensure_equals("both, the nearer first", chain(), std::string(".luaurc .config.luau")); - ensure("the nearer's mode", search.configOf(own, disk(), config) && config.mode == "nonstrict"); - ensure_equals("the further one's alias still", resolved("@lib/net"), ALIncludeIdentity::ofFile(net)); + UNSCOPED_INFO("both, the nearer first"); + CHECK(chain() == std::string(".luaurc .config.luau")); + { + INFO("the nearer's mode"); + CHECK(search.configOf(own, disk(), config)); + CHECK(config.mode == "nonstrict"); + } + UNSCOPED_INFO("the further one's alias still"); + CHECK(resolved("@lib/net") == ALIncludeIdentity::ofFile(net)); // Both in one folder: neither, as Luau reads it. const std::string rc = s.write("proj/.luaurc", "{\"aliases\": {\"lib\": \"./modules\"}}"); std::string why; - ensure("both: not found", resolved("@lib/net", &why).empty()); - ensure_equals("in Luau's words", why, std::string("could not resolve alias \"lib\" (ambiguous configuration file)")); - ensure_equals("passed over in the chain", chain(), std::string(".luaurc")); - ensure_equals("nor suggested", suggested(), std::string("./ ../")); + UNSCOPED_INFO("both: not found"); + REQUIRE(resolved("@lib/net", &why).empty()); + UNSCOPED_INFO("in Luau's words"); + CHECK(why == std::string("could not resolve alias \"lib\" (ambiguous configuration file)")); + UNSCOPED_INFO("passed over in the chain"); + CHECK(chain() == std::string(".luaurc")); + UNSCOPED_INFO("nor suggested"); + CHECK(suggested() == std::string("./ ../")); std::error_code ec; fs::remove(fsyspath(rc), ec); @@ -1014,10 +1183,17 @@ namespace tut // why; passed over in the chain the analyzer reads, as such a // .luaurc is. s.write("proj/.config.luau", "error('broken')\n"); - ensure("broken: its alias gone", resolved("@lib/net", &why).empty()); - ensure("said why, naming it: " + why, why.find("the configuration '") == 0 && why.find(".config.luau' could not be read: ") != std::string::npos); - ensure_equals("nor suggested past it", suggested(), std::string("./ ../")); - ensure_equals("not in the chain", chain(), std::string(".luaurc")); + UNSCOPED_INFO("broken: its alias gone"); + REQUIRE(resolved("@lib/net", &why).empty()); + { + INFO("said why, naming it: " + why); + CHECK(why.find("the configuration '") == 0); + REQUIRE(why.find(".config.luau' could not be read: ") != std::string::npos); + } + UNSCOPED_INFO("nor suggested past it"); + CHECK(suggested() == std::string("./ ../")); + UNSCOPED_INFO("not in the chain"); + CHECK(chain() == std::string(".luaurc")); // A script in the world, world includes off: the one at the top of // the scripter's include folder governs it. @@ -1026,18 +1202,22 @@ namespace tut const ALIncludeSearch::Where off = where(false, true, { s.at("inc") }); ALPreprocessor::Include found; std::vector aliases; - ensure("a world script, through the top's alias", - search.resolve(ask("@top/x", true, asking(true).self), found, asking(true), off, nullptr, false, &aliases) == ALPreprocessor::Found::Yes && - found.path == ALIncludeIdentity::ofFile(x)); + { + INFO("a world script, through the top's alias"); + CHECK(search.resolve(ask("@top/x", true, asking(true).self), found, asking(true), off, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); + CHECK(found.path == ALIncludeIdentity::ofFile(x)); + } std::vector top; - ensure("and its chain", search.configsFor(asking(true).self, asking(true), off, nullptr, false, top) == ALPreprocessor::Found::Yes && - top.size() == 1 && top[0].text.find("\"top\": \"./libs\"") != std::string::npos); + { + INFO("and its chain"); + CHECK(search.configsFor(asking(true).self, asking(true), off, nullptr, false, top) == ALPreprocessor::Found::Yes); + REQUIRE(top.size() == 1); + REQUIRE(top[0].text.find("\"top\": \"./libs\"") != std::string::npos); + } } - template<> template<> - void alincludesearch_object::test<16>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: a studio alias to an inventory folder is walked only with the world in", "[alincludesearch]") { - set_test_name("a studio alias naming a folder of the world, an inventory folder by its id: walked as the world is while the world is let in, and offered then; nothing of it otherwise, and nothing of it blessed on disk"); // An inventory folder of modules, by the id the world knows it by. const auto add = [this](const std::string& folder, const std::string& name) { const ALIncludeWorld::Item one{ ALIncludeIdentity::ofItem(LLUUID::null, LLUUID::generateNewID()), name, LLUUID::generateNewID() }; @@ -1065,9 +1245,12 @@ namespace tut ? include.path : std::string(); }; - ensure_equals("through the alias, the world let in", found("@inv/util", on), util.path); - ensure_equals("a folder under it", found("@inv/net/http", on), http.path); - ensure_equals("the world not let in: nothing", found("@inv/util", off), std::string()); + UNSCOPED_INFO("through the alias, the world let in"); + CHECK(found("@inv/util", on) == util.path); + UNSCOPED_INFO("a folder under it"); + CHECK(found("@inv/net/http", on) == http.path); + UNSCOPED_INFO("the world not let in: nothing"); + CHECK(found("@inv/util", off) == std::string()); const auto offered = [&](const ALIncludeSearch::Where& in) { std::string out; for (const ALRequireNavigation::Suggestion& one : search.suggest(in_object.self, "", true, in_object, in)) @@ -1076,22 +1259,24 @@ namespace tut } return out; }; - ensure("offered while the world is let in: " + offered(on), offered(on).find("@inv") != std::string::npos); - ensure("not otherwise: " + offered(off), offered(off).find("@inv") == std::string::npos); + UNSCOPED_INFO("offered while the world is let in: " + offered(on)); + REQUIRE(offered(on).find("@inv") != std::string::npos); + UNSCOPED_INFO("not otherwise: " + offered(off)); + REQUIRE(offered(off).find("@inv") == std::string::npos); // Beside a folder on disk, which is read only while the disk is. Scratch s; const std::string disk_util = s.write("library/util.luau", "return 'disk'\n"); ALIncludeSearch::Where both = where(true, true); both.aliases = { { "inv", "libfolder" }, { "lib", s.at("library") } }; - ensure_equals("each its own", found("@lib/util", both), ALIncludeIdentity::ofFile(disk_util)); - ensure_equals("and the world's", found("@inv/util", both), util.path); + UNSCOPED_INFO("each its own"); + CHECK(found("@lib/util", both) == ALIncludeIdentity::ofFile(disk_util)); + UNSCOPED_INFO("and the world's"); + CHECK(found("@inv/util", both) == util.path); } - template<> template<> - void alincludesearch_object::test<17>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: written paths reach only folders let in and never a share or device", "[alincludesearch]") { - set_test_name("a path a script or a configuration wrote is asked of the disk only where it is, as written, under a folder a require reads or the configuration's own: never another machine's share, a device, or another drive by a part of a name"); Scratch s; const std::string script = s.write("proj/main.luau", "return 1\n"); const std::string util = s.write("proj/lib/util.luau", "return 'util'\n"); @@ -1121,8 +1306,10 @@ namespace tut // Opened from the disk, in no include folder: what its configuration // names from a root under its own folder is all it reaches. const ALIncludeSearch::Where loose = where(false, true); - ensure_equals("from a root, under the configuration's own folder", found("@own/util", loose), ALIncludeIdentity::ofFile(util)); - ensure_equals("not a require's own path from a root, which no folder lets in", found(util, loose), std::string()); + UNSCOPED_INFO("from a root, under the configuration's own folder"); + CHECK(found("@own/util", loose) == ALIncludeIdentity::ofFile(util)); + UNSCOPED_INFO("not a require's own path from a root, which no folder lets in"); + CHECK(found(util, loose) == std::string()); const ALIncludeSearch::Where disk = where(false, true, { s.at("proj") }); std::string why; @@ -1131,22 +1318,52 @@ namespace tut const auto outside = [&s](const std::string& named) { return "the configuration in '" + s.at("proj") + "' names '" + named + "', which is outside it and outside the include folders"; }; - ensure_equals("under an include folder", found(util, disk), ALIncludeIdentity::ofFile(util)); - ensure("an alias naming another machine's share: not asked", - found("@share/x", disk, &why).empty() && why == outside("//alincludesearch.invalid/share")); - ensure("nor by the NT namespace's name for one", found("@nt/x", disk, &why).empty() && why == outside("/?\?/UNC/alincludesearch.invalid/share")); - ensure("nor its name for a local folder", found("@ntlocal/util", disk, &why).empty() && why == outside("/?\?/" + lib)); - ensure("a folder that is there, out of every folder it may name: not said to be missing", - found("@abroad/e", disk, &why).empty() && why == outside(abroad)); - ensure("a require naming one", found("//alincludesearch.invalid/share/x", disk, &why).empty() && - why == "could not find '//alincludesearch.invalid/share/x'"); - ensure("in Windows's separators", found("\\\\alincludesearch.invalid\\share\\x", disk).empty() && - found("\\??\\UNC\\alincludesearch.invalid\\share\\x", disk).empty()); - ensure("a device's path to a file under the folder", found("\\\\?\\" + util, disk).empty() && found("\\\\.\\" + util, disk).empty() && - found("\\??\\" + util, disk).empty()); - ensure("another drive's folder by a part of a name", found("./D:/x", disk, &why).empty() && why == "could not resolve child component \"D:\""); + UNSCOPED_INFO("under an include folder"); + CHECK(found(util, disk) == ALIncludeIdentity::ofFile(util)); + { + INFO("an alias naming another machine's share: not asked"); + REQUIRE(found("@share/x", disk, &why).empty()); + CHECK(why == outside("//alincludesearch.invalid/share")); + } + { + INFO("nor by the NT namespace's name for one"); + REQUIRE(found("@nt/x", disk, &why).empty()); + CHECK(why == outside("/?\?/UNC/alincludesearch.invalid/share")); + } + { + INFO("nor its name for a local folder"); + REQUIRE(found("@ntlocal/util", disk, &why).empty()); + CHECK(why == outside("/?\?/" + lib)); + } + { + INFO("a folder that is there, out of every folder it may name: not said to be missing"); + REQUIRE(found("@abroad/e", disk, &why).empty()); + CHECK(why == outside(abroad)); + } + { + INFO("a require naming one"); + REQUIRE(found("//alincludesearch.invalid/share/x", disk, &why).empty()); + CHECK(why == "could not find '//alincludesearch.invalid/share/x'"); + } + { + INFO("in Windows's separators"); + REQUIRE(found("\\\\alincludesearch.invalid\\share\\x", disk).empty()); + REQUIRE(found("\\??\\UNC\\alincludesearch.invalid\\share\\x", disk).empty()); + } + { + INFO("a device's path to a file under the folder"); + REQUIRE(found("\\\\?\\" + util, disk).empty()); + REQUIRE(found("\\\\.\\" + util, disk).empty()); + REQUIRE(found("\\??\\" + util, disk).empty()); + } + { + INFO("another drive's folder by a part of a name"); + REQUIRE(found("./D:/x", disk, &why).empty()); + CHECK(why == "could not resolve child component \"D:\""); + } ALPreprocessor::Include include; - ensure("nor an LSL include naming a share", search.resolve(ask("//alincludesearch.invalid/share/x.lsl"), include, asking(), disk, nullptr, false) == + UNSCOPED_INFO("nor an LSL include naming a share"); + CHECK(search.resolve(ask("//alincludesearch.invalid/share/x.lsl"), include, asking(), disk, nullptr, false) == ALPreprocessor::Found::No); // Nor are those modules' folders, for what a script may name; nor // an alias Second Life keeps. @@ -1155,7 +1372,8 @@ namespace tut { prefixes += prefix + " "; } - ensure_equals("the module folders: the scripter's, and the alias's that may be read", prefixes, std::string(" @own/ ")); + UNSCOPED_INFO("the module folders: the scripter's, and the alias's that may be read"); + CHECK(prefixes == std::string(" @own/ ")); // A configuration's folder reached through a link names its own // folder as it stands, and is let in so. @@ -1167,16 +1385,17 @@ namespace tut const ALIncludeSearch::Asking linked{ through, true }; ALPreprocessor::Include reached; std::vector aliases; - ensure("through a link", search.resolve(ask("@own/util", true, through), reached, linked, loose, nullptr, false, &aliases) == - ALPreprocessor::Found::Yes && - reached.path == ALIncludeIdentity::ofFile(util)); + { + INFO("through a link"); + CHECK(search.resolve(ask("@own/util", true, through), reached, linked, loose, nullptr, false, &aliases) == + ALPreprocessor::Found::Yes); + CHECK(reached.path == ALIncludeIdentity::ofFile(util)); + } } } - template<> template<> - void alincludesearch_object::test<18>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: why a require that reached a file found nothing or stopped at a config", "[alincludesearch]") { - set_test_name("why a require found nothing where the walk reached a file: no folder lets it in, or its configuration may not bless what an alias reached; a configuration that cannot be read ends a walk for an alias past it; a world .luaurc naming the disk"); Scratch s; const std::string loose = s.write("loose/main.luau", "return 1\n"); s.write("loose/sibling.luau", "return 1\n"); @@ -1191,9 +1410,11 @@ namespace tut return said == ALPreprocessor::Found::Yes ? std::string("found") : include.why; }; const ALIncludeSearch::Where disk = where(false, true, { s.at("inc") }); - ensure_equals("a file beside one opened from the disk, in no include folder", why("./sibling", ALIncludeIdentity::ofFile(loose), disk), + UNSCOPED_INFO("a file beside one opened from the disk, in no include folder"); + CHECK(why("./sibling", ALIncludeIdentity::ofFile(loose), disk) == "'" + s.at("loose/sibling.luau") + "' is there, but no include folder or alias lets a require read it"); - ensure_equals("an alias reaching out of its configuration's folder", why("@out/x", ALIncludeIdentity::ofFile(loose), disk), + UNSCOPED_INFO("an alias reaching out of its configuration's folder"); + CHECK(why("@out/x", ALIncludeIdentity::ofFile(loose), disk) == "the configuration in '" + s.at("loose") + "' names '" + s.at("outside") + "', which is outside it and outside the include folders"); @@ -1207,8 +1428,10 @@ namespace tut const std::string inner = ALIncludeIdentity::ofFile(s.write("proj/src/main.luau", "return 1\n")); const ALIncludeSearch::Where both = where(false, true, { s.at("inc"), s.at("proj") }); const std::string said = why("@lib/x", inner, both); - ensure("not reached through it: " + said, said.find("the configuration '" + s.at("proj/src/.luaurc") + "' could not be read: ") == 0); - ensure_equals("a path walks as ever", why("./near", inner, both), std::string("found")); + UNSCOPED_INFO("not reached through it: " + said); + CHECK(said.find("the configuration '" + s.at("proj/src/.luaurc") + "' could not be read: ") == 0); + UNSCOPED_INFO("a path walks as ever"); + CHECK(why("./near", inner, both) == std::string("found")); // A world .luaurc's alias naming the disk: why it is not walked. const ALIncludeWorld::Item main{ ALIncludeIdentity::ofItem(LLUUID::null, LLUUID::generateNewID()), "main", LLUUID::generateNewID() }; @@ -1217,14 +1440,13 @@ namespace tut world.folders["wproj"].items = { main, config }; world.itemFolders[main.path] = "wproj"; texts.put(config.path, config.assetId, "{\"aliases\": {\"far\": \"/somewhere/on/disk\"}}"); - ensure_equals("a world configuration naming the disk", why("@far/x", main.path, where(true, true, { s.at("inc") })), + UNSCOPED_INFO("a world configuration naming the disk"); + CHECK(why("@far/x", main.path, where(true, true, { s.at("inc") })) == std::string("the alias stands for '/somewhere/on/disk', a path on disk, which a configuration in the world may not name")); } - template<> template<> - void alincludesearch_object::test<19>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: suggestions in an object with the world off: the top .luaurc's aliases", "[alincludesearch]") { - set_test_name("paths suggested from a script in no place -- in an object, world includes off -- offer the aliases of the .luaurc at the top of the scripter's include folders, as a require from it walks them; a module and a folder of one name, as @self lays one out, both"); Scratch s; s.write("inc/.luaurc", "{\"aliases\": {\"top\": \"./libs\"}}"); s.write("inc/libs/x.luau", "return 1\n"); @@ -1240,19 +1462,21 @@ namespace tut } return out; }; - ensure_equals("the top's alias, then ./ and ../", labels(""), std::string("@top/ .// ..//")); - ensure_equals("a module and its folder, each", labels("@top/"), std::string("selfish selfish/ x")); - ensure_equals("into the folder", labels("@top/selfish/"), std::string("child")); + UNSCOPED_INFO("the top's alias, then ./ and ../"); + CHECK(labels("") == std::string("@top/ .// ..//")); + UNSCOPED_INFO("a module and its folder, each"); + CHECK(labels("@top/") == std::string("selfish selfish/ x")); + UNSCOPED_INFO("into the folder"); + CHECK(labels("@top/selfish/") == std::string("child")); ALPreprocessor::Include found; std::vector aliases; - ensure("and walked so", search.resolve(ask("@top/selfish/child", true, in_object.self), found, in_object, off, nullptr, false, &aliases) == + UNSCOPED_INFO("and walked so"); + CHECK(search.resolve(ask("@top/selfish/child", true, in_object.self), found, in_object, off, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); } - template<> template<> - void alincludesearch_object::test<20>() + TEST_CASE_METHOD(alincludesearch_data, "alincludesearch: an alias to a module lets in its file alone; one to @sl- walks nowhere", "[alincludesearch]") { - set_test_name("an alias of a .luaurc on disk naming a module lets in its file alone; one naming an alias Second Life keeps walks nowhere; a studio alias offered for an include folder is named past every alias in reach"); Scratch s; const std::string script = s.write("loose/main.luau", "return 1\n"); const std::string json = s.write("loose/vendor/json.luau", "return 'json'\n"); @@ -1274,11 +1498,22 @@ namespace tut return yes ? include.path : std::string(); }; std::string why; - ensure_equals("an alias naming a module", found("@json"), ALIncludeIdentity::ofFile(json)); - ensure("blessed for the run, alone", run.size() == 1 && run[0] == s.at("loose/vendor/json.luau")); - ensure_equals("its file alone, the rest of the run: nothing beside it", found("./vendor/other"), std::string()); - ensure_equals("an alias naming a folder", found("@lib/util"), ALIncludeIdentity::ofFile(util)); - ensure("one naming an alias Second Life keeps: no configuration's", found("@x/util", &why).empty() && why == "@sl-std is not a valid alias"); + UNSCOPED_INFO("an alias naming a module"); + CHECK(found("@json") == ALIncludeIdentity::ofFile(json)); + { + INFO("blessed for the run, alone"); + REQUIRE(run.size() == 1); + CHECK(run[0] == s.at("loose/vendor/json.luau")); + } + UNSCOPED_INFO("its file alone, the rest of the run: nothing beside it"); + CHECK(found("./vendor/other") == std::string()); + UNSCOPED_INFO("an alias naming a folder"); + CHECK(found("@lib/util") == ALIncludeIdentity::ofFile(util)); + { + INFO("one naming an alias Second Life keeps: no configuration's"); + REQUIRE(found("@x/util", &why).empty()); + CHECK(why == "@sl-std is not a valid alias"); + } // A studio alias named after an include folder, where an alias of // the .luaurc at its top -- in reach of a script in an object -- has @@ -1289,9 +1524,16 @@ namespace tut const ALIncludeSearch::Where mine = where(false, true, { s.at("other") }); ALPreprocessor::Include include; std::vector aliases; - ensure("found nothing now", search.resolve(ask("solo", true, asking(true).self), include, asking(true), mine, nullptr, false, &aliases) == + UNSCOPED_INFO("found nothing now"); + CHECK(search.resolve(ask("solo", true, asking(true).self), include, asking(true), mine, nullptr, false, &aliases) == ALPreprocessor::Found::No); - ensure_equals("found before", include.searched, solo); - ensure("named past the .luaurc's own", include.moves.size() == 1 && include.moves[0].require == "@other2/solo" && include.moves[0].alias == "other2"); + UNSCOPED_INFO("found before"); + CHECK(include.searched == solo); + { + INFO("named past the .luaurc's own"); + REQUIRE(include.moves.size() == 1); + CHECK(include.moves[0].require == "@other2/solo"); + CHECK(include.moves[0].alias == "other2"); + } } } diff --git a/indra/alscript/tests/allinelabel_test.cpp b/indra/alscript/tests/allinelabel_test.cpp index 0332e0fefa..dc7de09fd8 100644 --- a/indra/alscript/tests/allinelabel_test.cpp +++ b/indra/alscript/tests/allinelabel_test.cpp @@ -27,7 +27,7 @@ #include "../preprocessor/allinelabel.h" #include "../preprocessor/alpreprocessor.h" -#include "../test/lltut.h" +#include #include @@ -48,7 +48,7 @@ namespace } } -namespace tut +namespace { struct allinelabel_data { @@ -84,157 +84,214 @@ namespace tut } }; - typedef test_group allinelabel_group; - typedef allinelabel_group::object allinelabel_object; - allinelabel_group allinelabel_instance("allinelabel"); - - template<> template<> - void allinelabel_object::test<1>() + TEST_CASE_METHOD(allinelabel_data, "allinelabel: a POSIX path from a folder goes down with `/` and up with `..`; case counts", "[allinelabel]") { - set_test_name("a path from a folder on a POSIX root: down with `/`, up with `..`, none to the folder itself or above it, and case tells parts apart"); - ensure_equals("beside", ALLineLabel::relative("/home/me/proj", "/home/me/proj/util.luau"), std::string("util.luau")); - ensure_equals("down", ALLineLabel::relative("/home/me/proj", "/home/me/proj/lib/util.luau"), std::string("lib/util.luau")); - ensure_equals("up and across", ALLineLabel::relative("/home/me/proj", "/home/me/shared/util.luau"), std::string("../shared/util.luau")); - ensure_equals("up to the root", ALLineLabel::relative("/home/me/proj", "/opt/lib/util.lsl"), std::string("../../../opt/lib/util.lsl")); - ensure_equals("a trailing slash and `.` passed over, `..` taken back", ALLineLabel::relative("/home/me/proj/./", "/home/me/x/../proj/lib//util.luau"), + UNSCOPED_INFO("beside"); + CHECK(ALLineLabel::relative("/home/me/proj", "/home/me/proj/util.luau") == std::string("util.luau")); + UNSCOPED_INFO("down"); + CHECK(ALLineLabel::relative("/home/me/proj", "/home/me/proj/lib/util.luau") == std::string("lib/util.luau")); + UNSCOPED_INFO("up and across"); + CHECK(ALLineLabel::relative("/home/me/proj", "/home/me/shared/util.luau") == std::string("../shared/util.luau")); + UNSCOPED_INFO("up to the root"); + CHECK(ALLineLabel::relative("/home/me/proj", "/opt/lib/util.lsl") == std::string("../../../opt/lib/util.lsl")); + UNSCOPED_INFO("a trailing slash and `.` passed over, `..` taken back"); + CHECK(ALLineLabel::relative("/home/me/proj/./", "/home/me/x/../proj/lib//util.luau") == std::string("lib/util.luau")); - ensure_equals("a name it only begins with is no folder of it", ALLineLabel::relative("/home/me/proj", "/home/me/project/util.luau"), + UNSCOPED_INFO("a name it only begins with is no folder of it"); + CHECK(ALLineLabel::relative("/home/me/proj", "/home/me/project/util.luau") == std::string("../project/util.luau")); - ensure_equals("another case another folder", ALLineLabel::relative("/home/me/proj", "/home/me/Proj/util.luau"), std::string("../Proj/util.luau")); - ensure_equals("a backslash no separator", ALLineLabel::relative("/home/me", "/home/me/a\\b.lsl"), std::string("a\\b.lsl")); - ensure("the folder itself", ALLineLabel::relative("/home/me/proj", "/home/me/proj/").empty()); - ensure("a folder it is in", ALLineLabel::relative("/home/me/proj", "/home/me").empty()); - ensure("not from a root", ALLineLabel::relative("proj", "proj/util.luau").empty() && ALLineLabel::relative("/home/me", "util.luau").empty()); - ensure("nothing", ALLineLabel::relative("", "").empty()); + UNSCOPED_INFO("another case another folder"); + CHECK(ALLineLabel::relative("/home/me/proj", "/home/me/Proj/util.luau") == std::string("../Proj/util.luau")); + UNSCOPED_INFO("a backslash no separator"); + CHECK(ALLineLabel::relative("/home/me", "/home/me/a\\b.lsl") == std::string("a\\b.lsl")); + UNSCOPED_INFO("the folder itself"); + REQUIRE(ALLineLabel::relative("/home/me/proj", "/home/me/proj/").empty()); + UNSCOPED_INFO("a folder it is in"); + REQUIRE(ALLineLabel::relative("/home/me/proj", "/home/me").empty()); + { + INFO("not from a root"); + REQUIRE(ALLineLabel::relative("proj", "proj/util.luau").empty()); + REQUIRE(ALLineLabel::relative("/home/me", "util.luau").empty()); + } + UNSCOPED_INFO("nothing"); + REQUIRE(ALLineLabel::relative("", "").empty()); } - template<> template<> - void allinelabel_object::test<2>() + TEST_CASE_METHOD(allinelabel_data, "allinelabel: a Windows path from a folder takes either separator and case and long forms", "[allinelabel]") { - set_test_name("a path from a folder as Windows writes it: either separator, any case, the long forms read as the paths they stand for; none to another drive or share"); - ensure_equals("down", ALLineLabel::relative("C:\\Users\\me\\proj", "C:\\Users\\me\\proj\\lib\\util.luau"), std::string("lib/util.luau")); - ensure_equals("up and across", ALLineLabel::relative("C:\\Users\\me\\proj", "C:\\Users\\me\\shared\\util.luau"), std::string("../shared/util.luau")); - ensure_equals("any case, the file's parts kept as written", ALLineLabel::relative("c:\\users\\ME\\Proj", "C:\\Users\\me\\proj\\Lib\\Util.luau"), + UNSCOPED_INFO("down"); + CHECK(ALLineLabel::relative("C:\\Users\\me\\proj", "C:\\Users\\me\\proj\\lib\\util.luau") == std::string("lib/util.luau")); + UNSCOPED_INFO("up and across"); + CHECK(ALLineLabel::relative("C:\\Users\\me\\proj", "C:\\Users\\me\\shared\\util.luau") == std::string("../shared/util.luau")); + UNSCOPED_INFO("any case, the file's parts kept as written"); + CHECK(ALLineLabel::relative("c:\\users\\ME\\Proj", "C:\\Users\\me\\proj\\Lib\\Util.luau") == std::string("Lib/Util.luau")); - ensure_equals("either separator", ALLineLabel::relative("C:/Users/me/proj/", "C:\\Users\\me\\proj/lib\\util.lsl"), std::string("lib/util.lsl")); - ensure_equals("a long path", ALLineLabel::relative("C:\\Users\\me\\proj", "\\\\?\\C:\\Users\\me\\proj\\lib\\util.luau"), std::string("lib/util.luau")); - ensure_equals("the NT namespace's", ALLineLabel::relative("\\??\\C:\\proj", "C:\\proj\\util.luau"), std::string("util.luau")); - ensure_equals("a share", ALLineLabel::relative("\\\\host\\share\\proj", "\\\\HOST\\Share\\proj\\lib\\util.lsl"), std::string("lib/util.lsl")); - ensure_equals("a share's long path", ALLineLabel::relative("\\\\?\\UNC\\host\\share\\proj", "//host/share/shared/util.lsl"), + UNSCOPED_INFO("either separator"); + CHECK(ALLineLabel::relative("C:/Users/me/proj/", "C:\\Users\\me\\proj/lib\\util.lsl") == std::string("lib/util.lsl")); + UNSCOPED_INFO("a long path"); + CHECK(ALLineLabel::relative("C:\\Users\\me\\proj", "\\\\?\\C:\\Users\\me\\proj\\lib\\util.luau") == std::string("lib/util.luau")); + UNSCOPED_INFO("the NT namespace's"); + CHECK(ALLineLabel::relative("\\??\\C:\\proj", "C:\\proj\\util.luau") == std::string("util.luau")); + UNSCOPED_INFO("a share"); + CHECK(ALLineLabel::relative("\\\\host\\share\\proj", "\\\\HOST\\Share\\proj\\lib\\util.lsl") == std::string("lib/util.lsl")); + UNSCOPED_INFO("a share's long path"); + CHECK(ALLineLabel::relative("\\\\?\\UNC\\host\\share\\proj", "//host/share/shared/util.lsl") == std::string("../shared/util.lsl")); - ensure("another drive", ALLineLabel::relative("C:\\proj", "D:\\proj\\util.luau").empty()); - ensure("another share", ALLineLabel::relative("\\\\host\\one\\proj", "\\\\host\\two\\proj\\util.luau").empty()); - ensure("a drive and a share", ALLineLabel::relative("C:\\proj", "\\\\host\\share\\proj\\util.luau").empty()); - ensure("a share with no share", ALLineLabel::relative("\\\\host", "\\\\host\\share\\util.luau").empty()); - ensure("a device", ALLineLabel::relative("\\\\.\\pipe", "\\\\.\\pipe\\util.luau").empty()); - ensure("Windows's and a POSIX root", ALLineLabel::relative("/proj", "C:\\proj\\util.luau").empty() && - ALLineLabel::relative("C:\\proj", "/proj/util.luau").empty()); - ensure("no further up than the root", ALLineLabel::relative("C:\\proj", "C:\\..\\..\\proj\\util.luau") == "util.luau"); + UNSCOPED_INFO("another drive"); + REQUIRE(ALLineLabel::relative("C:\\proj", "D:\\proj\\util.luau").empty()); + UNSCOPED_INFO("another share"); + REQUIRE(ALLineLabel::relative("\\\\host\\one\\proj", "\\\\host\\two\\proj\\util.luau").empty()); + UNSCOPED_INFO("a drive and a share"); + REQUIRE(ALLineLabel::relative("C:\\proj", "\\\\host\\share\\proj\\util.luau").empty()); + UNSCOPED_INFO("a share with no share"); + REQUIRE(ALLineLabel::relative("\\\\host", "\\\\host\\share\\util.luau").empty()); + UNSCOPED_INFO("a device"); + REQUIRE(ALLineLabel::relative("\\\\.\\pipe", "\\\\.\\pipe\\util.luau").empty()); + { + INFO("Windows's and a POSIX root"); + REQUIRE(ALLineLabel::relative("/proj", "C:\\proj\\util.luau").empty()); + REQUIRE(ALLineLabel::relative("C:\\proj", "/proj/util.luau").empty()); + } + UNSCOPED_INFO("no further up than the root"); + CHECK(ALLineLabel::relative("C:\\proj", "C:\\..\\..\\proj\\util.luau") == "util.luau"); } - template<> template<> - void allinelabel_object::test<3>() + TEST_CASE_METHOD(allinelabel_data, "allinelabel: quotable makes a label safe for a reader with no escapes", "[allinelabel]") { - set_test_name("a label quotable: a quote, a backslash and a line break made what a reader with no escapes reads whole"); - ensure_equals("each", ALLineLabel::quotable("a\"b\\c\nd\re\tf\x7fg"), std::string("a'b/c_d_e_f_g")); - ensure_equals("the rest as it was", ALLineLabel::quotable("lib/my util (2).luau \xc3\xa9"), std::string("lib/my util (2).luau \xc3\xa9")); + UNSCOPED_INFO("each"); + CHECK(ALLineLabel::quotable("a\"b\\c\nd\re\tf\x7fg") == std::string("a'b/c_d_e_f_g")); + UNSCOPED_INFO("the rest as it was"); + CHECK(ALLineLabel::quotable("lib/my util (2).luau \xc3\xa9") == std::string("lib/my util (2).luau \xc3\xa9")); } - template<> template<> - void allinelabel_object::test<4>() + TEST_CASE_METHOD(allinelabel_data, "allinelabel: a script on disk labels files from its folder and the world's by name", "[allinelabel]") { - set_test_name("a script on disk: itself by its base name, every file on disk from its folder however reached, the world's by name, nothing from a root"); const ALLineLabel label("disk:/home/me/proj/main.luau", { "/home/me/includes", "/home/me/proj/lib", "/home/me/shared" }); - ensure_equals("itself, by its own name on disk", label.of(fileOf("Main script", ""), true), std::string("main.luau")); - ensure_equals("beside it", label.of(fileOf("util.luau", "disk:/home/me/proj/util.luau"), false), std::string("util.luau")); - ensure_equals("under it", label.of(fileOf("util.luau", "disk:/home/me/proj/lib/util.luau"), false), std::string("lib/util.luau")); - ensure_equals("an alias's, from its folder all the same", label.of(fileOf("util.luau", "disk:/home/me/shared/util.luau"), false), + UNSCOPED_INFO("itself, by its own name on disk"); + CHECK(label.of(fileOf("Main script", ""), true) == std::string("main.luau")); + UNSCOPED_INFO("beside it"); + CHECK(label.of(fileOf("util.luau", "disk:/home/me/proj/util.luau"), false) == std::string("util.luau")); + UNSCOPED_INFO("under it"); + CHECK(label.of(fileOf("util.luau", "disk:/home/me/proj/lib/util.luau"), false) == std::string("lib/util.luau")); + UNSCOPED_INFO("an alias's, from its folder all the same"); + CHECK(label.of(fileOf("util.luau", "disk:/home/me/shared/util.luau"), false) == std::string("../shared/util.luau")); - ensure_equals("an include folder's", label.of(fileOf("util.lsl", "disk:/home/me/includes/net/util.lsl"), false), + UNSCOPED_INFO("an include folder's"); + CHECK(label.of(fileOf("util.lsl", "disk:/home/me/includes/net/util.lsl"), false) == std::string("../includes/net/util.lsl")); - ensure_equals("one under no blessed folder", label.of(fileOf("x.luau", "disk:/opt/x.luau"), false), std::string("../../../opt/x.luau")); - ensure_equals("one from the world, by its name", label.of(fileOf("Util \"v2\"", "object:5d2c6a5b-8a5e-4d1e-9b8a-3c2a1f0e9d8c:1a2b3c4d-1111-2222-3333-444455556666"), false), + UNSCOPED_INFO("one under no blessed folder"); + CHECK(label.of(fileOf("x.luau", "disk:/opt/x.luau"), false) == std::string("../../../opt/x.luau")); + UNSCOPED_INFO("one from the world, by its name"); + CHECK(label.of(fileOf("Util \"v2\"", "object:5d2c6a5b-8a5e-4d1e-9b8a-3c2a1f0e9d8c:1a2b3c4d-1111-2222-3333-444455556666"), false) == std::string("Util 'v2'")); - ensure_equals("one named no other way", label.of(fileOf("lib", "lib"), false), std::string("lib")); - ensure_equals("a call", label(fileOf("util.luau", "disk:/home/me/proj/lib/util.luau"), false), std::string("lib/util.luau")); + UNSCOPED_INFO("one named no other way"); + CHECK(label.of(fileOf("lib", "lib"), false) == std::string("lib")); + UNSCOPED_INFO("a call"); + CHECK(label(fileOf("util.luau", "disk:/home/me/proj/lib/util.luau"), false) == std::string("lib/util.luau")); for (const char* path : { "disk:/home/me/proj/lib/util.luau", "disk:/home/me/shared/util.luau", "disk:/opt/x.luau" }) { - ensure("never from a root: " + std::string(path), !fromRoot(label.of(fileOf("x", path), false))); + UNSCOPED_INFO("never from a root: " + std::string(path)); + REQUIRE_FALSE(fromRoot(label.of(fileOf("x", path), false))); } } - template<> template<> - void allinelabel_object::test<5>() + TEST_CASE_METHOD(allinelabel_data, "allinelabel: off disk a file is labelled from the outermost blessed folder holding it", "[allinelabel]") { - set_test_name("a script not on disk, or a file on another drive: from the outermost blessed folder that holds it, as an #include names it; under none, by its name"); const ALLineLabel world("object:5d2c6a5b-8a5e-4d1e-9b8a-3c2a1f0e9d8c:1a2b3c4d-1111-2222-3333-444455556666", { "C:\\Users\\me\\includes\\net", "C:\\Users\\me\\includes", "D:\\lib" }); - ensure_equals("itself, by the name it is given", world.of(fileOf("Door \\ main", ""), true), std::string("Door / main")); - ensure_equals("the outermost folder's", world.of(fileOf("util.lsl", "disk:C:\\Users\\me\\includes\\net\\util.lsl"), false), + UNSCOPED_INFO("itself, by the name it is given"); + CHECK(world.of(fileOf("Door \\ main", ""), true) == std::string("Door / main")); + UNSCOPED_INFO("the outermost folder's"); + CHECK(world.of(fileOf("util.lsl", "disk:C:\\Users\\me\\includes\\net\\util.lsl"), false) == std::string("net/util.lsl")); - ensure_equals("one folder's", world.of(fileOf("util.lsl", "disk:c:\\users\\me\\Includes\\util.lsl"), false), std::string("util.lsl")); - ensure_equals("another drive's", world.of(fileOf("util.luau", "disk:D:\\lib\\x\\util.luau"), false), std::string("x/util.luau")); - ensure_equals("under none, by its name", world.of(fileOf("util.luau", "disk:E:\\util.luau"), false), std::string("util.luau")); + UNSCOPED_INFO("one folder's"); + CHECK(world.of(fileOf("util.lsl", "disk:c:\\users\\me\\Includes\\util.lsl"), false) == std::string("util.lsl")); + UNSCOPED_INFO("another drive's"); + CHECK(world.of(fileOf("util.luau", "disk:D:\\lib\\x\\util.luau"), false) == std::string("x/util.luau")); + UNSCOPED_INFO("under none, by its name"); + CHECK(world.of(fileOf("util.luau", "disk:E:\\util.luau"), false) == std::string("util.luau")); const ALLineLabel nothing("", {}); - ensure_equals("no identity at all: itself by its name", nothing.of(fileOf("main.lsl", ""), true), std::string("main.lsl")); - ensure_equals("and a file by its name", nothing.of(fileOf("util.lsl", "disk:/x/util.lsl"), false), std::string("util.lsl")); + UNSCOPED_INFO("no identity at all: itself by its name"); + CHECK(nothing.of(fileOf("main.lsl", ""), true) == std::string("main.lsl")); + UNSCOPED_INFO("and a file by its name"); + CHECK(nothing.of(fileOf("util.lsl", "disk:/x/util.lsl"), false) == std::string("util.lsl")); // On disk, but its include on another drive: from the folder that // holds that. const ALLineLabel drive("disk:C:\\Users\\me\\proj\\main.lsl", { "D:\\includes" }); - ensure_equals("itself", drive.of(fileOf("main.lsl", ""), true), std::string("main.lsl")); - ensure_equals("the same drive's from its folder", drive.of(fileOf("a.lsl", "disk:C:\\Users\\me\\lib\\a.lsl"), false), std::string("../lib/a.lsl")); - ensure_equals("another drive's from its blessed folder", drive.of(fileOf("a.lsl", "disk:D:\\includes\\net\\a.lsl"), false), std::string("net/a.lsl")); + UNSCOPED_INFO("itself"); + CHECK(drive.of(fileOf("main.lsl", ""), true) == std::string("main.lsl")); + UNSCOPED_INFO("the same drive's from its folder"); + CHECK(drive.of(fileOf("a.lsl", "disk:C:\\Users\\me\\lib\\a.lsl"), false) == std::string("../lib/a.lsl")); + UNSCOPED_INFO("another drive's from its blessed folder"); + CHECK(drive.of(fileOf("a.lsl", "disk:D:\\includes\\net\\a.lsl"), false) == std::string("net/a.lsl")); const ALLineLabel share("disk:\\\\host\\share\\proj\\main.lsl", { "C:\\includes" }); - ensure_equals("a script on a share: the share's from its folder", share.of(fileOf("a.lsl", "disk:\\\\host\\share\\proj\\lib\\a.lsl"), false), + UNSCOPED_INFO("a script on a share: the share's from its folder"); + CHECK(share.of(fileOf("a.lsl", "disk:\\\\host\\share\\proj\\lib\\a.lsl"), false) == std::string("lib/a.lsl")); - ensure_equals("and a drive's from its blessed folder", share.of(fileOf("a.lsl", "disk:C:\\includes\\a.lsl"), false), std::string("a.lsl")); + UNSCOPED_INFO("and a drive's from its blessed folder"); + CHECK(share.of(fileOf("a.lsl", "disk:C:\\includes\\a.lsl"), false) == std::string("a.lsl")); } - template<> template<> - void allinelabel_object::test<6>() + TEST_CASE_METHOD(allinelabel_data, "allinelabel: through a run: two SLua modules of one name read apart by their folders", "[allinelabel]") { - set_test_name("through a run: two SLua modules of one name in different folders read apart, the script by its name, and what the map calls each is as before"); add("lib/util", "util.luau", "disk:/home/me/proj/lib/util.luau", "return { a = 1 }\n"); add("@shared/util", "util.luau", "disk:/home/me/shared/util.luau", "return { b = 2 }\n"); ALPreprocessor::Options lua = options(true); lua.lineLabel = ALLineLabel("disk:/home/me/proj/main.luau", { "/home/me/shared" }); const std::string script = "local a = require(\"lib/util\")\nlocal b = require(\"@shared/util\")\nprint(a.a, b.b)\n"; ALPreprocessor::Result r = ALPreprocessor::run(script, lua); - ensure("no errors: " + r.text, !r.hasErrors()); - ensure("the one under it: " + r.text, r.text.find("-- @line 1 \"lib/util.luau\"\nreturn { a = 1 }\n") != std::string::npos); - ensure("the alias's, from its folder: " + r.text, r.text.find("-- @line 1 \"../shared/util.luau\"\nreturn { b = 2 }\n") != std::string::npos); - ensure("the script's: " + r.text, r.text.find("-- @line 1 \"main.luau\"\nlocal a = require(1)\n") != std::string::npos); - ensure_equals("three files", r.map.files().size(), size_t(3)); - ensure("each still called its __SHORTFILE__", r.map.files()[0].name == "main.luau" && r.map.files()[1].name == "util.luau" && - r.map.files()[2].name == "util.luau"); + UNSCOPED_INFO("no errors: " + r.text); + CHECK_FALSE(r.hasErrors()); + UNSCOPED_INFO("the one under it: " + r.text); + REQUIRE(r.text.find("-- @line 1 \"lib/util.luau\"\nreturn { a = 1 }\n") != std::string::npos); + UNSCOPED_INFO("the alias's, from its folder: " + r.text); + REQUIRE(r.text.find("-- @line 1 \"../shared/util.luau\"\nreturn { b = 2 }\n") != std::string::npos); + UNSCOPED_INFO("the script's: " + r.text); + REQUIRE(r.text.find("-- @line 1 \"main.luau\"\nlocal a = require(1)\n") != std::string::npos); + UNSCOPED_INFO("three files"); + REQUIRE(r.map.files().size() == size_t(3)); + { + INFO("each still called its __SHORTFILE__"); + CHECK(r.map.files()[0].name == "main.luau"); + CHECK(r.map.files()[1].name == "util.luau"); + CHECK(r.map.files()[2].name == "util.luau"); + } // Unset, each by its name, as before. ALPreprocessor::Options plain = options(true); r = ALPreprocessor::run(script, plain); - ensure("unset, by name: " + r.text, r.text.find("-- @line 1 \"util.luau\"\nreturn { a = 1 }\n") != std::string::npos && - r.text.find("-- @line 1 \"main.luau\"\n") != std::string::npos); + { + INFO("unset, by name: " + r.text); + REQUIRE(r.text.find("-- @line 1 \"util.luau\"\nreturn { a = 1 }\n") != std::string::npos); + REQUIRE(r.text.find("-- @line 1 \"main.luau\"\n") != std::string::npos); + } } - template<> template<> - void allinelabel_object::test<7>() + TEST_CASE_METHOD(allinelabel_data, "allinelabel: through a run: LSL includes labelled from disk or by name and made quotable", "[allinelabel]") { - set_test_name("through a run: an LSL include from the world keeps its name, one on disk is from the script's folder, and whatever a label holds is made quotable"); add("net/util.lsl", "util.lsl", "disk:C:\\Users\\me\\includes\\net\\util.lsl", "integer x;\n"); add("World lib", "World \"lib\"", "inventory:1a2b3c4d-1111-2222-3333-444455556666", "integer y;\n"); ALPreprocessor::Options lsl = options(false); lsl.lineLabel = ALLineLabel("", { "C:\\Users\\me\\includes" }); const std::string script = "integer a;\n#include \"net/util.lsl\"\n#include \"World lib\"\ndefault { state_entry() { } }\n"; ALPreprocessor::Result r = ALPreprocessor::run(script, lsl); - ensure("no errors: " + r.text, !r.hasErrors()); - ensure("the disk's, as an #include names it: " + r.text, r.text.find("// @line 1 \"net/util.lsl\"\ninteger x;\n") != std::string::npos); - ensure("the world's, by its name: " + r.text, r.text.find("// @line 1 \"World 'lib'\"\ninteger y;\n") != std::string::npos); - ensure("the script's after: " + r.text, r.text.find("// @line 4 \"main.lsl\"\ndefault") != std::string::npos); + UNSCOPED_INFO("no errors: " + r.text); + CHECK_FALSE(r.hasErrors()); + UNSCOPED_INFO("the disk's, as an #include names it: " + r.text); + REQUIRE(r.text.find("// @line 1 \"net/util.lsl\"\ninteger x;\n") != std::string::npos); + UNSCOPED_INFO("the world's, by its name: " + r.text); + REQUIRE(r.text.find("// @line 1 \"World 'lib'\"\ninteger y;\n") != std::string::npos); + UNSCOPED_INFO("the script's after: " + r.text); + REQUIRE(r.text.find("// @line 4 \"main.lsl\"\ndefault") != std::string::npos); // A caller's own, quote and all, made quotable. lsl.lineLabel = [](const ALSourceMap::File& file, bool self) { return self ? std::string("my \"main\"") : "C:\\" + file.name; }; r = ALPreprocessor::run(script, lsl); - ensure("quotable: " + r.text, r.text.find("// @line 1 \"C:/util.lsl\"\n") != std::string::npos && - r.text.find("// @line 4 \"my 'main'\"\n") != std::string::npos); + { + INFO("quotable: " + r.text); + REQUIRE(r.text.find("// @line 1 \"C:/util.lsl\"\n") != std::string::npos); + REQUIRE(r.text.find("// @line 4 \"my 'main'\"\n") != std::string::npos); + } } } diff --git a/indra/alscript/tests/allslconsts_test.cpp b/indra/alscript/tests/allslconsts_test.cpp index 4c5c93ae29..8334ad58ac 100644 --- a/indra/alscript/tests/allslconsts_test.cpp +++ b/indra/alscript/tests/allslconsts_test.cpp @@ -28,9 +28,9 @@ #include "../lsl/allslservice.h" #include "../preprocessor/alpreprocessor.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct allslconsts_data { @@ -58,63 +58,60 @@ namespace tut return out; } }; - typedef test_group allslconsts_group; - typedef allslconsts_group::object allslconsts_object; - allslconsts_group allslconsts_instance("ALLSLConsts"); - template<> template<> - void allslconsts_object::test<1>() + TEST_CASE_METHOD(allslconsts_data, "ALLSLConsts: a const variable is given its value where declared and set nowhere else", "[ALLSLConsts]") { - set_test_name("a const variable is given its value where it is declared, and set nowhere else"); - ensure("builtins: " + error, lslLoaded); - ensure_equals("nothing wrong", said("const integer A = 1;\n" - "default { state_entry() { const float f = A * 2; llOwnerSay((string)(f + A)); } }\n"), + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); + UNSCOPED_INFO("nothing wrong"); + CHECK(said("const integer A = 1;\n" + "default { state_entry() { const float f = A * 2; llOwnerSay((string)(f + A)); } }\n") == std::string()); - ensure_equals("each write, where it is", - said("const integer A = 1;\n" + UNSCOPED_INFO("each write, where it is"); + CHECK(said("const integer A = 1;\n" "f(const integer n) { n = 2; }\n" "default\n{\n state_entry()\n {\n const vector v = <1, 2, 3>;\n A += 1;\n v.x = 2;\n ++A;\n" - " llOwnerSay((string)v);\n }\n}\n"), + " llOwnerSay((string)v);\n }\n}\n") == std::string("ConstAssigned 1:21 n\n" "ConstAssigned 7:8 A\n" "ConstAssigned 8:8 v\n" "ConstAssigned 9:8 A\n")); - ensure_equals("no value", said("const integer A;\ndefault { state_entry() { const string s; llOwnerSay(s + (string)A); } }\n"), + UNSCOPED_INFO("no value"); + CHECK(said("const integer A;\ndefault { state_entry() { const string s; llOwnerSay(s + (string)A); } }\n") == std::string("ConstWithoutValue 0:14 A\n" "ConstWithoutValue 1:39 s\n")); - ensure_equals("a script that does not compile is the compiler's", said("const integer A = ;\n"), std::string()); + UNSCOPED_INFO("a script that does not compile is the compiler's"); + CHECK(said("const integer A = ;\n") == std::string()); } - template<> template<> - void allslconsts_object::test<2>() + TEST_CASE_METHOD(allslconsts_data, "ALLSLConsts: a const function works out what it gives back from its arguments alone", "[ALLSLConsts]") { - set_test_name("a const function works out what it gives back from its arguments alone"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string globals = "integer counter;\ninteger fixed = 3;\n"; const std::string handler = "default { state_entry() { counter = 1; } }\n"; - ensure_equals("pure, calling what is pure, reading what never changes", - said(globals + + UNSCOPED_INFO("pure, calling what is pure, reading what never changes"); + CHECK(said(globals + "integer twice(integer x) { return x * 2; }\n" - "const integer f(integer x) { integer y = twice(x) + fixed; return llAbs(y); }\n" + handler), + "const integer f(integer x) { integer y = twice(x) + fixed; return llAbs(y); }\n" + handler) == std::string()); - ensure_equals("each thing it may not do, where it does it", - said(globals + + UNSCOPED_INFO("each thing it may not do, where it does it"); + CHECK(said(globals + "const integer f(integer x)\n{\n counter = x;\n llOwnerSay(\"hi\");\n if (x) state other;\n return x + counter;\n}\n" - "default { state_entry() { counter = 1; } }\nstate other { state_entry() { } }\n"), + "default { state_entry() { counter = 1; } }\nstate other { state_entry() { } }\n") == std::string("ConstFunctionSets 4:4 f counter\n" "ConstFunctionCalls 5:4 f llOwnerSay\n" "ConstFunctionChangesState 6:11 f\n" "ConstFunctionReads 7:15 f counter\n")); - ensure_equals("a function it calls that sets a global", - said(globals + "bump() { counter += 1; }\nconst integer f(integer x) { bump(); return x; }\n" + handler), + UNSCOPED_INFO("a function it calls that sets a global"); + CHECK(said(globals + "bump() { counter += 1; }\nconst integer f(integer x) { bump(); return x; }\n" + handler) == std::string("ConstFunctionCalls 3:29 f bump\n")); } - template<> template<> - void allslconsts_object::test<3>() + TEST_CASE_METHOD(allslconsts_data, "ALLSLConsts: optimizer off: a const global is written as the literal of its value", "[ALLSLConsts]") { - set_test_name("a const global's value is worked out, and written as the literal LSL takes there, with the optimizer off"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string source = "const integer FLAGS = PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION;\n" "const float STEP = TWO_PI / 12;\n" "const integer TWICE = FLAGS * 2;\n" @@ -127,9 +124,10 @@ namespace tut "const integer C = changing + 1;\n" "default { state_entry() { changing = 2; llOwnerSay((string)[FLAGS, STEP, TWICE, UP, NAME, SAME, PLAIN, C] + (string)L); } }\n"; const ALPreprocessor::Result r = ALPreprocessor::run(source, ALPreprocessor::Options()); - ensure_equals("nothing wrong", said(source), std::string()); - ensure_equals("each written", - r.text, + UNSCOPED_INFO("nothing wrong"); + CHECK(said(source) == std::string()); + UNSCOPED_INFO("each written"); + CHECK(r.text == std::string("integer FLAGS = 20;\n" "float STEP = 0.5235988;\n" "integer TWICE = 40;\n" @@ -143,60 +141,80 @@ namespace tut "default { state_entry() { changing = 2; llOwnerSay((string)[FLAGS, STEP, TWICE, UP, NAME, SAME, PLAIN, C] + (string)L); } }\n")); // What was written maps to what it was written for. const ALSourceMap::Loc twenty = r.map.toSource(0, 16); - ensure("the value maps to the expression", twenty.found() && twenty.line == 0 && twenty.column == 22); + { + INFO("the value maps to the expression"); + REQUIRE(twenty.found()); + CHECK(twenty.line == 0); + CHECK(twenty.column == 22); + } // The names are where they are now: the list's two lines are one. - ensure("nine", r.consts.size() == 9); - ensure("a name after the folded list, a line up", r.consts[6].name == "SAME" && r.consts[6].line == 6 && r.consts[6].column == 8); + UNSCOPED_INFO("nine"); + REQUIRE(r.consts.size() == 9); + { + INFO("a name after the folded list, a line up"); + CHECK(r.consts[6].name == "SAME"); + CHECK(r.consts[6].line == 6); + CHECK(r.consts[6].column == 8); + } const ALSourceMap::Loc same = r.map.toSource(6, 8); - ensure("and mapped to where it was", same.found() && same.line == 7); + { + INFO("and mapped to where it was"); + REQUIRE(same.found()); + CHECK(same.line == 7); + } // The target's arithmetic: Luau's doubles. ALPreprocessor::Options luau = ALPreprocessor::Options(); luau.optimizer.target = ALLSLOptimizer::Target::Luau; const ALPreprocessor::Result wide = ALPreprocessor::run("const float STEP = TWO_PI / 12;\ndefault { state_entry() { llOwnerSay((string)STEP); } }\n", luau); - ensure("in doubles on Luau: " + wide.text, wide.text.find("float STEP = 0.5235987") == 0 && wide.text.find(";") > 26); + { + INFO("in doubles on Luau: " + wide.text); + CHECK(wide.text.find("float STEP = 0.5235987") == 0); + CHECK(wide.text.find(";") > 26); + } } - template<> template<> - void allslconsts_object::test<4>() + TEST_CASE_METHOD(allslconsts_data, "ALLSLConsts: a const global known only at run time or not writable is an error", "[ALLSLConsts]") { - set_test_name("a const global whose value cannot be worked out before the script runs is an error, and so is one that cannot be written"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); // A global the script changes is no bar: globals are given their // values as the script starts, when each has its first. - ensure_equals("each, at its value", - said("const integer T = llGetUnixTime();\n" + UNSCOPED_INFO("each, at its value"); + CHECK(said("const integer T = llGetUnixTime();\n" "const float BIG = 1e30 * 1e30;\n" "const list KEYS = [(key)NULL_KEY] + [];\n" - "default { state_entry() { llOwnerSay((string)[T, BIG] + (string)KEYS); } }\n"), + "default { state_entry() { llOwnerSay((string)[T, BIG] + (string)KEYS); } }\n") == std::string("ConstNotKnown 0:18 T\n" "ConstNotKnown 1:18 BIG\n" "ConstNotKnown 2:18 KEYS\n")); // A plain global is left to the compiler: only a const is worked out. const std::string plain = "integer A = 2 * 3;\ndefault { state_entry() { llOwnerSay((string)A); } }\n"; - ensure_equals("not a const", ALPreprocessor::run(plain, ALPreprocessor::Options()).text, plain); + UNSCOPED_INFO("not a const"); + CHECK(ALPreprocessor::run(plain, ALPreprocessor::Options()).text == plain); } - template<> template<> - void allslconsts_object::test<5>() + TEST_CASE_METHOD(allslconsts_data, "ALLSLConsts: optimizer on: a const's value goes where it is read and its global goes", "[ALLSLConsts]") { - set_test_name("with the optimizer on, a const's value goes where it is read, and the global with it"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); ALPreprocessor::Options o = ALPreprocessor::Options(); o.optimize = true; const ALPreprocessor::Result r = ALPreprocessor::run("const integer FLAGS = PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION;\n" "default { state_entry() { llRequestPermissions(llGetOwner(), FLAGS); } }\n", o); - ensure("optimized: " + r.text, r.optimized); - ensure("the value in place: " + r.text, r.text.find("llRequestPermissions(llGetOwner(), 20);") != std::string::npos); - ensure("and no global: " + r.text, r.text.find("FLAGS") == std::string::npos); + UNSCOPED_INFO("optimized: " + r.text); + CHECK(r.optimized); + UNSCOPED_INFO("the value in place: " + r.text); + REQUIRE(r.text.find("llRequestPermissions(llGetOwner(), 20);") != std::string::npos); + UNSCOPED_INFO("and no global: " + r.text); + REQUIRE(r.text.find("FLAGS") == std::string::npos); } - template<> template<> - void allslconsts_object::test<6>() + TEST_CASE_METHOD(allslconsts_data, "ALLSLConsts: optimizer on: a const function folds on constants and other calls stay", "[ALLSLConsts]") { - set_test_name("with the optimizer on, a const function called with constants folds to its answer, and a call with anything else stays"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string source = "const integer sq(integer x) { return x * x; }\n" "const float deg(float d) { return d * DEG_TO_RAD; }\n" "const integer clampi(integer v, integer lo, integer hi)\n{\n if (v < lo)\n return lo;\n" @@ -206,29 +224,38 @@ namespace tut ALPreprocessor::Options o = ALPreprocessor::Options(); o.optimize = true; const ALPreprocessor::Result r = ALPreprocessor::run(source, o); - ensure("optimized: " + r.text, r.optimized); - ensure_equals("nothing wrong", said(source), std::string()); + UNSCOPED_INFO("optimized: " + r.text); + CHECK(r.optimized); + UNSCOPED_INFO("nothing wrong"); + CHECK(said(source) == std::string()); // The list stays a literal: as a sum it would need a helper the // script has no other use for, which costs Mono more than it saves. - ensure("each worked out: " + r.text, r.text.find("[9, 1.5707964, 10, sq(n)]") != std::string::npos); - ensure("the functions no call is left to go: " + r.text, r.text.find("deg(") == std::string::npos && r.text.find("clampi(") == std::string::npos); - ensure("and the one a call is left to stays: " + r.text, r.text.find("integer sq(integer x)") != std::string::npos); + UNSCOPED_INFO("each worked out: " + r.text); + REQUIRE(r.text.find("[9, 1.5707964, 10, sq(n)]") != std::string::npos); + { + INFO("the functions no call is left to go: " + r.text); + REQUIRE(r.text.find("deg(") == std::string::npos); + REQUIRE(r.text.find("clampi(") == std::string::npos); + } + UNSCOPED_INFO("and the one a call is left to stays: " + r.text); + REQUIRE(r.text.find("integer sq(integer x)") != std::string::npos); // With the optimizer off, a call is a call. const ALPreprocessor::Result off = ALPreprocessor::run(source, ALPreprocessor::Options()); - ensure("calls stay: " + off.text, off.text.find("sq(3), deg(90), clampi(12, 0, 10), sq(n)") != std::string::npos); + UNSCOPED_INFO("calls stay: " + off.text); + REQUIRE(off.text.find("sq(3), deg(90), clampi(12, 0, 10), sq(n)") != std::string::npos); } - template<> template<> - void allslconsts_object::test<7>() + TEST_CASE_METHOD(allslconsts_data, "ALLSLConsts: the analyzer and the preprocessor agree where each const is declared", "[ALLSLConsts]") { - set_test_name("where the analyzer says a name was declared is where the preprocessor says a const is: a global, a parameter, a function"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const ALPreprocessor::Result r = ALPreprocessor::run("const integer LIMIT = 3 * 4;\n" "const integer twice(const integer n) { return n * 2; }\n" "default { state_entry() { llOwnerSay((string)twice(LIMIT)); } }\n", ALPreprocessor::Options()); - ensure_equals("three", r.consts.size(), size_t(3)); + UNSCOPED_INFO("three"); + 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); const S32 line = static_cast(std::count(r.text.begin(), r.text.begin() + at, '\n')); @@ -240,11 +267,16 @@ namespace tut for (const auto& [word, from] : std::vector>{ { "LIMIT", handler }, { "twice", handler }, { "n * 2", 0 } }) { const ALScriptHover hover = declaredAt(word, from); - ensure(word + ": found", hover.found && hover.hasDefinition); + { + INFO(word + ": found"); + REQUIRE(hover.found); + REQUIRE(hover.hasDefinition); + } const bool matches = std::any_of(r.consts.begin(), r.consts.end(), [&](const ALPreprocessor::Result::Const& c) { return c.line == hover.definitionLine && c.column == hover.definitionColumn; }); - ensure(word + ": at a const's place, " + std::to_string(hover.definitionLine) + ":" + std::to_string(hover.definitionColumn), matches); + UNSCOPED_INFO(word + ": at a const's place, " + std::to_string(hover.definitionLine) + ":" + std::to_string(hover.definitionColumn)); + REQUIRE(matches); } } } diff --git a/indra/alscript/tests/allslcosts_test.cpp b/indra/alscript/tests/allslcosts_test.cpp index eea81dc9ef..f6dfcae33a 100644 --- a/indra/alscript/tests/allslcosts_test.cpp +++ b/indra/alscript/tests/allslcosts_test.cpp @@ -38,14 +38,14 @@ #include "../lsl/allslservice.h" #include "../core/alscriptweight.h" -#include "../test/lltut.h" +#include #include #include #include #include -namespace tut +namespace { struct allslcosts_data { @@ -90,7 +90,8 @@ namespace tut const ALScriptWeight weight = t == Target::LSO ? ALScriptWeigh::lso(text) : t == Target::Mono ? ALScriptWeigh::mono(text) : ALScriptWeigh::lslLuau(text); - ensure("compiles for " + std::string(nameOf(t)) + ": " + weight.error + "\n" + text, weight.compiled); + UNSCOPED_INFO("compiles for " + std::string(nameOf(t)) + ": " + weight.error + "\n" + text); + REQUIRE(weight.compiled); return S32(weight.total); } @@ -154,18 +155,15 @@ namespace tut } } }; - typedef test_group allslcosts_group; - typedef allslcosts_group::object allslcosts_object; - allslcosts_group allslcosts_instance("ALLSLCosts"); // The rewrites: each pair's verdict on LSO, Mono and Luau in turn -- // '<' where the second form is smaller -- and the flag that stands for // it where the optimizer has one. The rest are what rewrites still to // come will read: here so that what was measured stays measured. - template<> template<> - void allslcosts_object::test<1>() + TEST_CASE_METHOD(allslcosts_data, "ALLSLCosts: the rewrites: each pair's verdict on each target and the flag for it", "[ALLSLCosts]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); using Flag = bool ALLSLCosts::*; struct Fact { @@ -387,23 +385,25 @@ namespace tut const Delta d = delta(t, fact.a, fact.b); said(fact.what, t, d, 0); const char v = fact.once ? verdict({ d.first, d.first }) : verdict(d); - ensure_equals(std::string(fact.what) + " on " + nameOf(t), v, fact.verdicts[n]); + UNSCOPED_INFO(std::string(fact.what) + " on " + nameOf(t)); + REQUIRE(v == fact.verdicts[n]); // A flag on is a form smaller there; one off may be for what // a first use costs a script that the pair does not show // (allslcosts.cpp says where). if (fact.flag && ALLSLCosts::of(t).*fact.flag) { - ensure(std::string(fact.what) + ": the flag on " + nameOf(t) + ", and the form not smaller", v == '<'); + UNSCOPED_INFO(std::string(fact.what) + ": the flag on " + nameOf(t) + ", and the form not smaller"); + REQUIRE(v == '<'); } } } } // What a function costs, and a call of it, a local and a jump. - template<> template<> - void allslcosts_object::test<2>() + TEST_CASE_METHOD(allslcosts_data, "ALLSLCosts: what a function costs and a call of it and a local and a jump", "[ALLSLCosts]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const Snippet none{ "", "" }; const Snippet two{ "f1() { }", "" }; const Snippet eighteen{ "a_much_longer_name() { }", "" }; @@ -415,44 +415,54 @@ namespace tut const Delta local = delta(t, { "", "integer z = i; j = z;" }, { "", "j = i;" }); said("a local", t, local, -c.local); - ensure_equals("a local" + where, -local.each, c.local); + UNSCOPED_INFO("a local" + where); + REQUIRE(-local.each == c.local); const Delta jump = delta(t, { "", "jump L; @L;" }, { "", "" }); said("a jump", t, jump, -c.jump); - ensure("a jump" + where + ": " + std::to_string(-jump.first), -jump.first == c.jump); + UNSCOPED_INFO("a jump" + where + ": " + std::to_string(-jump.first)); + REQUIRE(-jump.first == c.jump); const Delta f2 = delta(t, none, two); const Delta f18 = delta(t, none, eighteen); said("a function", t, f2, c.function + 2 * c.functionChar); - ensure_equals("a function's name, by the character" + where, (f18.first - f2.first) / 16, c.functionChar); - ensure_equals("a function" + where, f2.first - 2 * c.functionChar, c.function); + UNSCOPED_INFO("a function's name, by the character" + where); + REQUIRE((f18.first - f2.first) / 16 == c.functionChar); + UNSCOPED_INFO("a function" + where); + REQUIRE(f2.first - 2 * c.functionChar == c.function); const Delta p = delta(t, two, param); said("a parameter", t, p, c.parameter); - ensure_equals("a parameter" + where, p.first, c.parameter); + UNSCOPED_INFO("a parameter" + where); + REQUIRE(p.first == c.parameter); const Delta call = delta(t, { "f1() { }", "f1();" }, { "f1() { }", "f1(); f1();" }); said("a call", t, call, c.call); - ensure_equals("a call" + where, call.each, c.call); + UNSCOPED_INFO("a call" + where); + REQUIRE(call.each == c.call); const Delta argued = delta(t, { "f1(integer a) { }", "f1(i);" }, { "f1(integer a) { }", "f1(i); f1(i);" }); said("a call with an argument", t, argued, c.call + c.argument); - ensure_equals("an argument" + where, argued.each - c.call, c.argument); + UNSCOPED_INFO("an argument" + where); + REQUIRE(argued.each - c.call == c.argument); for (const char* text : { "hello", "hello world, hello" }) { const S32 length = S32(strlen(text)); const Delta string = delta(t, { "", "s = s;" }, { "", "s = \"" + std::string(text) + "\";" }); said("a string of " + std::to_string(length) + ", held once", t, string, c.stringHeld + length * c.stringHeldChar); - ensure_equals("a string of " + std::to_string(length) + ", held once" + where, string.first - string.each, + UNSCOPED_INFO("a string of " + std::to_string(length) + ", held once" + where); + REQUIRE(string.first - string.each == c.stringHeld + length * c.stringHeldChar); } const Delta first2 = delta(t, two, { "f1() { }", "f1();" }); const Delta first18 = delta(t, eighteen, { "a_much_longer_name() { }", "a_much_longer_name();" }); said("a first call", t, first2, c.call + c.reference + 2 * c.referenceChar); - ensure_equals("a first call's reference, by the character" + where, (first18.first - first2.first) / 16, c.referenceChar); - ensure_equals("a first call's reference" + where, first2.first - c.call - 2 * c.referenceChar, c.reference); + UNSCOPED_INFO("a first call's reference, by the character" + where); + REQUIRE((first18.first - first2.first) / 16 == c.referenceChar); + UNSCOPED_INFO("a first call's reference" + where); + REQUIRE(first2.first - c.call - 2 * c.referenceChar == c.reference); // The most elements of a literal added to a list bare: at it // smaller, one past it larger; with none, eight smaller. @@ -468,21 +478,24 @@ namespace tut }; if (c.elementsForListMost > 0) { - ensure("the most added bare, smaller" + where, verdict(bare(c.elementsForListMost)) == '<'); - ensure("one more, larger" + where, verdict(bare(c.elementsForListMost + 1)) == '>'); + UNSCOPED_INFO("the most added bare, smaller" + where); + REQUIRE(verdict(bare(c.elementsForListMost)) == '<'); + UNSCOPED_INFO("one more, larger" + where); + REQUIRE(verdict(bare(c.elementsForListMost + 1)) == '>'); } else { - ensure("eight added bare, smaller" + where, verdict(bare(8)) == '<'); + UNSCOPED_INFO("eight added bare, smaller" + where); + REQUIRE(verdict(bare(8)) == '<'); } } } // A global never assigned, against its value written where it is read. - template<> template<> - void allslcosts_object::test<3>() + TEST_CASE_METHOD(allslcosts_data, "ALLSLCosts: a global never assigned against its value written where it is read", "[ALLSLCosts]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); struct Kind { const char* what; @@ -519,9 +532,12 @@ namespace tut const ALLSLCosts::Held& held = c.*kind.held; said(std::string(kind.what) + " global", t, own, held.global); said(std::string(kind.what) + " written out", t, out, held.first); - ensure_equals(std::string(kind.what) + " global" + where, own.first, held.global); - ensure_equals(std::string(kind.what) + " written out, first" + where, out.first, held.first); - ensure_equals(std::string(kind.what) + " written out, each" + where, out.each, held.each); + UNSCOPED_INFO(std::string(kind.what) + " global" + where); + REQUIRE(own.first == held.global); + UNSCOPED_INFO(std::string(kind.what) + " written out, first" + where); + REQUIRE(out.first == held.first); + UNSCOPED_INFO(std::string(kind.what) + " written out, each" + where); + REQUIRE(out.each == held.each); } // A string at two lengths, what a character adds the same. for (const char* text : { "hello", "hello world, hello" }) @@ -535,9 +551,12 @@ namespace tut const std::string what = "a string of " + std::to_string(length); said(what + " global", t, own, held.global); said(what + " written out", t, out, held.first); - ensure_equals(what + " global" + where, own.first, held.global); - ensure_equals(what + " written out, first" + where, out.first, held.first); - ensure_equals(what + " written out, each" + where, out.each, held.each); + UNSCOPED_INFO(what + " global" + where); + REQUIRE(own.first == held.global); + UNSCOPED_INFO(what + " written out, first" + where); + REQUIRE(out.first == held.first); + UNSCOPED_INFO(what + " written out, each" + where); + REQUIRE(out.each == held.each); } } } @@ -545,10 +564,10 @@ namespace tut // What putting a function in place is estimated to come to, against the // script written both ways and weighed: the same side of nothing, and // near it. - template<> template<> - void allslcosts_object::test<4>() + TEST_CASE_METHOD(allslcosts_data, "ALLSLCosts: what putting a function in place is estimated to come to", "[ALLSLCosts]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); struct Case { const char* what; @@ -583,14 +602,18 @@ namespace tut { bytes = part.name == one.name ? S32(part.bytes) : bytes; } - ensure(std::string(one.what) + ": the function weighed on " + nameOf(t) + ": " + whole.error, bytes > 0); + UNSCOPED_INFO(std::string(one.what) + ": the function weighed on " + nameOf(t) + ": " + whole.error); + REQUIRE(bytes > 0); const S32 measured = size(t, one.inlined, 1) - size(t, one.called, 1); ALLSLCosts::Inlining f = one.f; f.bytes = bytes; const S32 estimate = ALLSLCosts::of(t).inlined(f); said(std::string(one.what) + " (estimated)", t, { measured, measured }, estimate); - ensure(std::string(one.what) + " on " + nameOf(t) + ": estimated " + std::to_string(estimate) + ", measured " + std::to_string(measured), - (estimate < 0) == (measured < 0) && std::abs(estimate - measured) <= std::max(12, std::abs(measured) / 4)); + { + INFO(std::string(one.what) + " on " + nameOf(t) + ": estimated " + std::to_string(estimate) + ", measured " + std::to_string(measured)); + REQUIRE((estimate < 0) == (measured < 0)); + REQUIRE(std::abs(estimate - measured) <= std::max(12, std::abs(measured) / 4)); + } } } } @@ -598,10 +621,10 @@ namespace tut // Where a list's helpers are held once for the script: the least a // list's shape saves at a place, and the most a helper new to the // script costs. - template<> template<> - void allslcosts_object::test<5>() + TEST_CASE_METHOD(allslcosts_data, "ALLSLCosts: where a list's helpers are held once for the script", "[ALLSLCosts]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); for (const Target t : TARGETS) { const ALLSLCosts& c = ALLSLCosts::of(t); @@ -625,7 +648,8 @@ namespace tut least = std::min(least, -delta(t, a, b).each); } said("the least a list's shape saves", t, { least, least }, c.listShapeLeast); - ensure_equals("the least a list's shape saves at a place" + where, least, c.listShapeLeast); + UNSCOPED_INFO("the least a list's shape saves at a place" + where); + REQUIRE(least == c.listShapeLeast); // A helper each: a cast to a list, something added after one, // something added before one. const Snippet had{ "", "l = l + [i];" }; @@ -636,7 +660,8 @@ namespace tut most = std::max(most, d.first - d.each); } said("the most a helper costs", t, { most, most }, c.listHelperMost); - ensure_equals("the most a list's helper new to the script costs" + where, most, c.listHelperMost); + UNSCOPED_INFO("the most a list's helper new to the script costs" + where); + REQUIRE(most == c.listHelperMost); } } } diff --git a/indra/alscript/tests/allsleffects_test.cpp b/indra/alscript/tests/allsleffects_test.cpp index c61fde9763..ed64bb7d6d 100644 --- a/indra/alscript/tests/allsleffects_test.cpp +++ b/indra/alscript/tests/allsleffects_test.cpp @@ -28,7 +28,7 @@ #include "../lsl/allsleffects.h" #include "../lsl/allslservice.h" -#include "../test/lltut.h" +#include #include @@ -94,7 +94,7 @@ namespace } } -namespace tut +namespace { struct allsleffects_data { @@ -108,20 +108,14 @@ namespace tut loaded = service.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error); if (!loaded) { - fail("the builtins did not load: " + error); + FAIL("the builtins did not load: " + error); } } } }; - typedef test_group allsleffects_group; - typedef allsleffects_group::object allsleffects_object; - allsleffects_group allsleffects_instance("allsleffects"); - - template<> template<> - void allsleffects_object::test<1>() + TEST_CASE_METHOD(allsleffects_data, "allsleffects: a function's writes and impurity take in its callees even round a loop", "[allsleffects]") { - set_test_name("a function's writes are the globals it assigns, itself and through what it calls, round a loop of calls too; a library call that is not pure makes it impure"); const Parsed p("integer g;\ninteger h;\ninteger k;\n" "a() { g = 1; }\n" "b() { a(); h++; }\n" @@ -132,7 +126,8 @@ namespace tut "r() { k += 2; s(); }\n" "s() { r(); }\n" "default { state_entry() { f(); } }\n"); - ensure("parsed", p.script != nullptr); + UNSCOPED_INFO("parsed"); + REQUIRE(p.script != nullptr); const ALLSLEffects effects(p.script); const auto is = [&](const char* name, std::initializer_list globals, bool impure) { const ALLSLEffects::Writes& w = effects.ofFunction(p.function(name)); @@ -143,20 +138,29 @@ namespace tut } return same; }; - ensure("a writes g", is("a", { "g" }, false)); - ensure("b writes h, and g through a", is("b", { "g", "h" }, false)); - ensure("c writes only its own local, which no caller sees", is("c", {}, false)); - ensure("d says something: impure", is("d", {}, true)); - ensure("e's library call is pure", is("e", { "k" }, false)); - ensure("f through b through a", is("f", { "g", "h" }, false)); - ensure("r and s call each other, and both write k", is("r", { "k" }, false) && is("s", { "k" }, false)); - ensure("one the script does not define: impure", effects.ofFunction(nullptr).impure); + UNSCOPED_INFO("a writes g"); + CHECK(is("a", { "g" }, false)); + UNSCOPED_INFO("b writes h, and g through a"); + CHECK(is("b", { "g", "h" }, false)); + UNSCOPED_INFO("c writes only its own local, which no caller sees"); + CHECK(is("c", {}, false)); + UNSCOPED_INFO("d says something: impure"); + CHECK(is("d", {}, true)); + UNSCOPED_INFO("e's library call is pure"); + CHECK(is("e", { "k" }, false)); + UNSCOPED_INFO("f through b through a"); + CHECK(is("f", { "g", "h" }, false)); + { + INFO("r and s call each other, and both write k"); + CHECK(is("r", { "k" }, false)); + CHECK(is("s", { "k" }, false)); + } + UNSCOPED_INFO("one the script does not define: impure"); + CHECK(effects.ofFunction(nullptr).impure); } - template<> template<> - void allsleffects_object::test<2>() + TEST_CASE_METHOD(allsleffects_data, "allsleffects: a call may go first past what changes nothing nor reads its writes", "[allsleffects]") { - set_test_name("a call may run first where what runs before it -- a right operand, earlier arguments, a list's other elements -- changes nothing and reads nothing it changes"); const Parsed p("integer g;\ninteger h;\n" "integer bump() { return ++g; }\n" "integer same(integer v) { return v; }\n" @@ -177,27 +181,44 @@ namespace tut " x = y + same(y++);\n" // 11: and here after " g = bump();\n" // 12: the store is after "} }\n"); - ensure("parsed", p.script != nullptr); + UNSCOPED_INFO("parsed"); + REQUIRE(p.script != nullptr); const ALLSLEffects effects(p.script); const std::vector bumps = p.calls("bump"); const std::vector sames = p.calls("same"); - ensure_equals("the calls", bumps.size(), size_t(11)); + UNSCOPED_INFO("the calls"); + REQUIRE(bumps.size() == size_t(11)); const auto first = [&](LSLFunctionExpression* call) { return effects.mayRunFirst(statementOf(call), call); }; - ensure("0: the right operand runs first", first(bumps[0])); - ensure("1: g before it, and it writes g", !first(bumps[1])); - ensure("2: h before it, unwritten", first(bumps[2])); - ensure("3: an impure call before it", !first(bumps[3])); - ensure("4: a pure call before it", first(bumps[4])); - ensure("5: an increment before it", !first(bumps[5])); - ensure("6: an earlier argument reads what it writes", !first(bumps[6])); - ensure("7: the first argument", first(bumps[7])); - ensure("8: a list's other element, unwritten", first(bumps[8])); - ensure("9: a list's other element it writes, the order not known", !first(bumps[9])); - ensure("10: its own argument writes what runs before it", !first(sames[0])); - ensure("11: what reads it runs after", first(sames[1])); - ensure("12: an assignment's store is after its value", first(bumps[10])); - ensure_equals("before the right operand: nothing", ALLSLEffects::before(statementOf(bumps[0]), bumps[0]).size(), size_t(0)); - ensure_equals("before the left: the right", ALLSLEffects::before(statementOf(bumps[1]), bumps[1]).size(), size_t(1)); + UNSCOPED_INFO("0: the right operand runs first"); + CHECK(first(bumps[0])); + UNSCOPED_INFO("1: g before it, and it writes g"); + CHECK_FALSE(first(bumps[1])); + UNSCOPED_INFO("2: h before it, unwritten"); + CHECK(first(bumps[2])); + UNSCOPED_INFO("3: an impure call before it"); + CHECK_FALSE(first(bumps[3])); + UNSCOPED_INFO("4: a pure call before it"); + CHECK(first(bumps[4])); + UNSCOPED_INFO("5: an increment before it"); + CHECK_FALSE(first(bumps[5])); + UNSCOPED_INFO("6: an earlier argument reads what it writes"); + CHECK_FALSE(first(bumps[6])); + UNSCOPED_INFO("7: the first argument"); + CHECK(first(bumps[7])); + UNSCOPED_INFO("8: a list's other element, unwritten"); + CHECK(first(bumps[8])); + UNSCOPED_INFO("9: a list's other element it writes, the order not known"); + CHECK_FALSE(first(bumps[9])); + UNSCOPED_INFO("10: its own argument writes what runs before it"); + CHECK_FALSE(first(sames[0])); + UNSCOPED_INFO("11: what reads it runs after"); + CHECK(first(sames[1])); + UNSCOPED_INFO("12: an assignment's store is after its value"); + CHECK(first(bumps[10])); + UNSCOPED_INFO("before the right operand: nothing"); + REQUIRE(ALLSLEffects::before(statementOf(bumps[0]), bumps[0]).size() == size_t(0)); + UNSCOPED_INFO("before the left: the right"); + REQUIRE(ALLSLEffects::before(statementOf(bumps[1]), bumps[1]).size() == size_t(1)); // Calls of the script's own that change nothing, either side. const Parsed q("integer g;\n" @@ -210,18 +231,19 @@ namespace tut " l = [look(), bump()];\n" // bump writes what look reads " l = [bump(), same(2)];\n" // same changes nothing, and bump does "} }\n"); - ensure("parsed", q.script != nullptr); + UNSCOPED_INFO("parsed"); + REQUIRE(q.script != nullptr); const ALLSLEffects quiet(q.script); const auto firstQ = [&](LSLFunctionExpression* call) { return quiet.mayRunFirst(statementOf(call), call); }; - ensure("a quiet call before a quiet one", firstQ(q.calls("same")[0])); - ensure("a call that reads before one that writes", !firstQ(q.calls("bump")[0])); - ensure("a call that writes before a quiet one", !firstQ(q.calls("same")[1])); + UNSCOPED_INFO("a quiet call before a quiet one"); + CHECK(firstQ(q.calls("same")[0])); + UNSCOPED_INFO("a call that reads before one that writes"); + CHECK_FALSE(firstQ(q.calls("bump")[0])); + UNSCOPED_INFO("a call that writes before a quiet one"); + CHECK_FALSE(firstQ(q.calls("same")[1])); } - template<> template<> - void allsleffects_object::test<3>() + TEST_CASE_METHOD(allsleffects_data, "allsleffects: a function's changes past the script and globals it reads through calls", "[allsleffects]") { - set_test_name("what a function changes past the script, apart from what it only reads there; and the globals it reads, " - "through what it calls"); const Parsed p("integer g;\n" "integer h;\n" "key me() { return llGetKey(); }\n" @@ -231,19 +253,34 @@ namespace tut "integer near() { return look(); }\n" "integer bump() { return ++h; }\n" "default { state_entry() { me(); loud(); near(); bump(); } }\n"); - ensure("parsed", p.script != nullptr); + UNSCOPED_INFO("parsed"); + REQUIRE(p.script != nullptr); const ALLSLEffects effects(p.script); - ensure("reading the world is past the script", effects.ofFunction(p.function("me")).impure); - ensure("and changes nothing there", !effects.ofFunction(p.function("me")).changes); - ensure("saying something changes it", effects.ofFunction(p.function("say")).changes); - ensure("through what it calls too", effects.ofFunction(p.function("loud")).changes); - ensure("a read of a global", effects.reads(p.function("look"), p.global("g"))); - ensure("through what it calls", effects.reads(p.function("near"), p.global("g"))); - ensure("not one it does not name", !effects.reads(p.function("near"), p.global("h"))); - ensure("nor one only another function names", !effects.reads(p.function("me"), p.global("g"))); - ensure("one it steps", effects.reads(p.function("bump"), p.global("h"))); - ensure("a function the script does not define reads any", effects.reads(nullptr, p.global("g"))); + UNSCOPED_INFO("reading the world is past the script"); + CHECK(effects.ofFunction(p.function("me")).impure); + UNSCOPED_INFO("and changes nothing there"); + CHECK_FALSE(effects.ofFunction(p.function("me")).changes); + UNSCOPED_INFO("saying something changes it"); + CHECK(effects.ofFunction(p.function("say")).changes); + UNSCOPED_INFO("through what it calls too"); + CHECK(effects.ofFunction(p.function("loud")).changes); + UNSCOPED_INFO("a read of a global"); + CHECK(effects.reads(p.function("look"), p.global("g"))); + UNSCOPED_INFO("through what it calls"); + CHECK(effects.reads(p.function("near"), p.global("g"))); + UNSCOPED_INFO("not one it does not name"); + CHECK_FALSE(effects.reads(p.function("near"), p.global("h"))); + UNSCOPED_INFO("nor one only another function names"); + CHECK_FALSE(effects.reads(p.function("me"), p.global("g"))); + UNSCOPED_INFO("one it steps"); + CHECK(effects.reads(p.function("bump"), p.global("h"))); + UNSCOPED_INFO("a function the script does not define reads any"); + CHECK(effects.reads(nullptr, p.global("g"))); LSLASTNode* body = p.script->getStates()->getChild(0); - ensure("a statement's, its calls'", effects.of(body).changes && effects.of(body).impure); + { + INFO("a statement's, its calls'"); + CHECK(effects.of(body).changes); + CHECK(effects.of(body).impure); + } } } diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index 26c2a7eb9f..05552e0dde 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -31,7 +31,7 @@ #include "../lsl/allslservice.h" #include "../lsl/allsltraits.h" -#include "../test/lltut.h" +#include #include #include @@ -44,7 +44,7 @@ #include #include -namespace tut +namespace { struct allsloptimizer_data { @@ -58,7 +58,7 @@ namespace tut loaded = service.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error); if (!loaded) { - fail("the builtins did not load: " + error); + FAIL("the builtins did not load: " + error); } } } @@ -178,11 +178,13 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text, target != ALLSLOptimizer::Target::LSO)) { - ensure("no error in what was written: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("no error in what was written: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } return r.text; } @@ -204,206 +206,276 @@ namespace tut } }; - // Past TUT's fifty tests a group runs only what it is told it has. - typedef test_group allsloptimizer_group; - typedef allsloptimizer_group::object allsloptimizer_object; - allsloptimizer_group allsloptimizer_instance("allsloptimizer"); - - template<> template<> - void allsloptimizer_object::test<1>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: constants fold and never-assigned variables inline and unused ones go", "[allsloptimizer]") { - set_test_name("constants fold, never-assigned variables inline, and what is left unused goes"); const std::string source = "integer unused = 5;\ninteger used = 2;\n" + wrap("", " integer x = used * 3 + 1;\n llSay(0, (string)x);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); - ensure_equals("text", r.text, wrap("", " llSay(0, \"7\");\n")); - ensure("noted the global", has(r, "removed the unused global unused")); - ensure("noted the fold: " + notes(r), has(r, "folded used * 3 + 1 to 7")); - ensure("noted the local", has(r, "removed the unused local x")); - ensure("smaller", r.sizeAfter < r.sizeBefore); - } - - template<> template<> - void allsloptimizer_object::test<2>() + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("text"); + CHECK(r.text == wrap("", " llSay(0, \"7\");\n")); + UNSCOPED_INFO("noted the global"); + CHECK(has(r, "removed the unused global unused")); + UNSCOPED_INFO("noted the fold: " + notes(r)); + CHECK(has(r, "folded used * 3 + 1 to 7")); + UNSCOPED_INFO("noted the local"); + CHECK(has(r, "removed the unused local x")); + UNSCOPED_INFO("smaller"); + CHECK(r.sizeAfter < r.sizeBefore); + } + + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: floats fold in single precision and print so that they read back exactly", "[allsloptimizer]") { - set_test_name("floats fold in single precision and print so that they read back exactly"); // Each variable may be assigned again before it is read, so that // it stays a variable and the fold shows in its declaration. const std::string source = wrap("", " float a = 0.1 + 0.2;\n float b = PI * 2;\n float c = 1.0 / 3;\n if (llFrand(1) < 0.5)\n {\n a = b = c = 0;\n }\n llSay(0, (string)a + (string)b + (string)c);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); // 0.1f + 0.2f is 0.3 exactly as a single, PI * 2 is 6.2831855f, // and each reads back as the same single. - ensure("a: " + r.text, r.text.find("float a = 0.3;") != std::string::npos); - ensure("b", r.text.find("float b = 6.2831855;") != std::string::npos); - ensure("c", r.text.find("float c = 0.33333334;") != std::string::npos); + UNSCOPED_INFO("a: " + r.text); + REQUIRE(r.text.find("float a = 0.3;") != std::string::npos); + UNSCOPED_INFO("b"); + REQUIRE(r.text.find("float b = 6.2831855;") != std::string::npos); + UNSCOPED_INFO("c"); + REQUIRE(r.text.find("float c = 0.33333334;") != std::string::npos); // A float that is a whole number prints as an integer where LSL // would convert one, and stays a float where it would not. r = ALLSLOptimizer::run(wrap("", " float f = 2.0;\n list l = [2.0];\n string s = (string)2.0;\n vector v = <1.0, 2.0, 3.5>;\n if (llFrand(1) < 0.5)\n {\n f = 0; l = []; s = \"\"; v = ZERO_VECTOR;\n }\n llSay(0, (string)f + llList2CSV(l) + s + (string)v);\n"), options()); - ensure("whole float as integer: " + r.text, r.text.find("float f = 2;") != std::string::npos); + UNSCOPED_INFO("whole float as integer: " + r.text); + REQUIRE(r.text.find("float f = 2;") != std::string::npos); // As the cast of an integer on Mono, where that is smaller (ALLSLCosts::castForWholeFloat). - ensure("list element stays a float: " + r.text, r.text.find("list l = (list)((float)2);") != std::string::npos); - ensure("cast folded with six places", r.text.find("string s = \"2.000000\";") != std::string::npos); - ensure("vector components", r.text.find("vector v = <1, 2, 3.5>;") != std::string::npos); + UNSCOPED_INFO("list element stays a float: " + r.text); + REQUIRE(r.text.find("list l = (list)((float)2);") != std::string::npos); + UNSCOPED_INFO("cast folded with six places"); + REQUIRE(r.text.find("string s = \"2.000000\";") != std::string::npos); + UNSCOPED_INFO("vector components"); + REQUIRE(r.text.find("vector v = <1, 2, 3.5>;") != std::string::npos); } - template<> template<> - void allsloptimizer_object::test<3>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: integer arithmetic wraps as the VM's does and division by zero is left", "[allsloptimizer]") { - set_test_name("the arithmetic is the VM's: wrapping integers, division by zero left alone"); const std::string source = wrap("", " llSay(0, (string)(2147483647 + 1));\n llSay(0, (string)(1 / 0));\n llSay(0, (string)(-2147483648 / -1));\n llSay(0, (string)(7 / 2 * 2));\n llSay(0, (string)(1 << 40));\n llSay(0, (string)(-7 % 3));\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); - ensure("wrapped: " + r.text, r.text.find("llSay(0, \"-2147483648\");") != std::string::npos); - ensure("zero stays", r.text.find("(string)(1 / 0)") != std::string::npos); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("wrapped: " + r.text); + REQUIRE(r.text.find("llSay(0, \"-2147483648\");") != std::string::npos); + UNSCOPED_INFO("zero stays"); + REQUIRE(r.text.find("(string)(1 / 0)") != std::string::npos); // On Mono, each negative number as its cast (ALLSLCosts::castForNegative). - ensure("the overflow stays: " + r.text, r.text.find("(string)(((integer)-2147483648) / ((integer)-1))") != std::string::npos); - ensure("integer division", r.text.find("llSay(0, \"6\");") != std::string::npos); - ensure("a wide shift stays", r.text.find("(string)(1 << 40)") != std::string::npos); - ensure("modulo keeps the sign", r.text.find("llSay(0, \"-1\");") != std::string::npos); + UNSCOPED_INFO("the overflow stays: " + r.text); + REQUIRE(r.text.find("(string)(((integer)-2147483648) / ((integer)-1))") != std::string::npos); + UNSCOPED_INFO("integer division"); + REQUIRE(r.text.find("llSay(0, \"6\");") != std::string::npos); + UNSCOPED_INFO("a wide shift stays"); + REQUIRE(r.text.find("(string)(1 << 40)") != std::string::npos); + UNSCOPED_INFO("modulo keeps the sign"); + REQUIRE(r.text.find("llSay(0, \"-1\");") != std::string::npos); } - template<> template<> - void allsloptimizer_object::test<4>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: pure library calls with constant arguments are evaluated and others left", "[allsloptimizer]") { - set_test_name("pure library calls with constant arguments are evaluated, others left"); const std::string source = wrap("", " integer a = llAbs(-3);\n float b = llSqrt(16);\n string c = llToUpper(\"abc\");\n string d = llGetSubString(\"abcdef\", 4, 1);\n string e = llDeleteSubString(\"abcdef\", 1, 3);\n integer f = llStringLength(\"hello\");\n string g = llEscapeURL(\"a b\");\n string h = llBase64ToString(llStringToBase64(\"hi\"));\n integer i = llList2Integer([1, 2, 3], -1);\n string j = llList2String([1, 2.5, <1,2,3>], 1);\n list k = llList2List([1, 2, 3, 4], 1, 2);\n integer l = llFloor(2.7) + llCeil(2.2);\n string m = llGetSubString(\"h\xc3\xa9llo\", 0, 1);\n integer n = llGetUnixTime();\n string o = llList2CSV([1, \"a\", 2.0]);\n if (llFrand(1) < 0.5)\n {\n a = f = i = l = n = 0; b = 0; c = d = e = g = h = j = m = o = \"\"; k = [];\n }\n llSay(0, (string)a + (string)b + c + d + e + (string)f + g + h + (string)i + j + llList2CSV(k) + (string)l + m + (string)n + o);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); - ensure("abs: " + r.text, r.text.find("integer a = 3;") != std::string::npos); - ensure("sqrt", r.text.find("float b = 4;") != std::string::npos); - ensure("upper", r.text.find("string c = \"ABC\";") != std::string::npos); - ensure("wrapped range", r.text.find("string d = \"abef\";") != std::string::npos); - ensure("delete", r.text.find("string e = \"aef\";") != std::string::npos); - ensure("length", r.text.find("integer f = 5;") != std::string::npos); - ensure("escape", r.text.find("string g = \"a%20b\";") != std::string::npos); - ensure("base64 there and back", r.text.find("string h = \"hi\";") != std::string::npos); - ensure("list get from the end", r.text.find("integer i = 3;") != std::string::npos); - ensure("list to string", r.text.find("string j = \"2.500000\";") != std::string::npos); - ensure("list slice: " + r.text, r.text.find("list k = [2, 3];") != std::string::npos); - ensure("floor and ceil", r.text.find("integer l = 5;") != std::string::npos); - ensure("not ascii, not folded", r.text.find("llGetSubString(\"h\xc3\xa9llo\", 0, 1)") != std::string::npos); - ensure("impure, not folded", r.text.find("llGetUnixTime()") != std::string::npos); - ensure("csv", r.text.find("string o = \"1, a, 2.000000\";") != std::string::npos); - } - - template<> template<> - void allsloptimizer_object::test<5>() - { - set_test_name("identities, signs, casts and conditions are simplified"); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("abs: " + r.text); + REQUIRE(r.text.find("integer a = 3;") != std::string::npos); + UNSCOPED_INFO("sqrt"); + REQUIRE(r.text.find("float b = 4;") != std::string::npos); + UNSCOPED_INFO("upper"); + REQUIRE(r.text.find("string c = \"ABC\";") != std::string::npos); + UNSCOPED_INFO("wrapped range"); + REQUIRE(r.text.find("string d = \"abef\";") != std::string::npos); + UNSCOPED_INFO("delete"); + REQUIRE(r.text.find("string e = \"aef\";") != std::string::npos); + UNSCOPED_INFO("length"); + REQUIRE(r.text.find("integer f = 5;") != std::string::npos); + UNSCOPED_INFO("escape"); + REQUIRE(r.text.find("string g = \"a%20b\";") != std::string::npos); + UNSCOPED_INFO("base64 there and back"); + REQUIRE(r.text.find("string h = \"hi\";") != std::string::npos); + UNSCOPED_INFO("list get from the end"); + REQUIRE(r.text.find("integer i = 3;") != std::string::npos); + UNSCOPED_INFO("list to string"); + REQUIRE(r.text.find("string j = \"2.500000\";") != std::string::npos); + UNSCOPED_INFO("list slice: " + r.text); + REQUIRE(r.text.find("list k = [2, 3];") != std::string::npos); + UNSCOPED_INFO("floor and ceil"); + REQUIRE(r.text.find("integer l = 5;") != std::string::npos); + UNSCOPED_INFO("not ascii, not folded"); + REQUIRE(r.text.find("llGetSubString(\"h\xc3\xa9llo\", 0, 1)") != std::string::npos); + UNSCOPED_INFO("impure, not folded"); + REQUIRE(r.text.find("llGetUnixTime()") != std::string::npos); + UNSCOPED_INFO("csv"); + REQUIRE(r.text.find("string o = \"1, a, 2.000000\";") != std::string::npos); + } + + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: algebraic identities and redundant casts and conditions simplify", "[allsloptimizer]") + { const std::string source = wrap("integer g;\ninteger h;\n", " integer a = g + 0;\n integer b = g * 1;\n integer c = g - -1;\n integer d = g + -2;\n integer e = (integer)g;\n integer f = g * 0;\n integer i = (g == 0);\n if (g != 0) h = 1;\n if (!!g) h = 2;\n if (!(g == h)) h = 3;\n if (!g) h = 4; else h = 5;\n h = a + b + c + d + e + f + i;\n a = b = c = d = e = f = i = 0;\n g = h;\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); - ensure("plus zero: " + r.text, r.text.find("integer a = g;") != std::string::npos); - ensure("times one", r.text.find("integer b = g;") != std::string::npos); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("plus zero: " + r.text); + REQUIRE(r.text.find("integer a = g;") != std::string::npos); + UNSCOPED_INFO("times one"); + REQUIRE(r.text.find("integer b = g;") != std::string::npos); // One more as -~g on Mono, whose ldc.i4 1 is five bytes // (ALLSLCosts::negateComplementForIncrement). - ensure("minus minus: " + r.text, r.text.find("integer c = -~g;") != std::string::npos); + UNSCOPED_INFO("minus minus: " + r.text); + REQUIRE(r.text.find("integer c = -~g;") != std::string::npos); // And on Mono, two less as ~-~-g (ALLSLCosts::complementNegateForDecrement). - ensure("plus minus: " + r.text, r.text.find("integer d = ~-~-g;") != std::string::npos); - ensure("cast", r.text.find("integer e = g;") != std::string::npos); + UNSCOPED_INFO("plus minus: " + r.text); + REQUIRE(r.text.find("integer d = ~-~-g;") != std::string::npos); + UNSCOPED_INFO("cast"); + REQUIRE(r.text.find("integer e = g;") != std::string::npos); // Left at its default on Mono (ALLSLCosts::dropIntegerDefault). - ensure("times zero: " + r.text, r.text.find("integer f;") != std::string::npos); - ensure("equals zero", r.text.find("integer i = (!g);") != std::string::npos); - ensure("not equal zero as condition", r.text.find("if (g)\n h = 1;") != std::string::npos); - ensure("double not", r.text.find("if (g)\n h = 2;") != std::string::npos); + UNSCOPED_INFO("times zero: " + r.text); + REQUIRE(r.text.find("integer f;") != std::string::npos); + UNSCOPED_INFO("equals zero"); + REQUIRE(r.text.find("integer i = (!g);") != std::string::npos); + UNSCOPED_INFO("not equal zero as condition"); + REQUIRE(r.text.find("if (g)\n h = 1;") != std::string::npos); + UNSCOPED_INFO("double not"); + REQUIRE(r.text.find("if (g)\n h = 2;") != std::string::npos); // As a ^ on Mono, where only truth counts (ALLSLCosts::xorForNotEqual). - ensure("not equals: " + r.text, r.text.find("if (g ^ h)\n h = 3;") != std::string::npos); + UNSCOPED_INFO("not equals: " + r.text); + REQUIRE(r.text.find("if (g ^ h)\n h = 3;") != std::string::npos); // Turned round off its !, then a choice of two numbers as arithmetic. - ensure("swapped, then chosen by arithmetic: " + r.text, r.text.find("h = !!g + 4;") != std::string::npos); + UNSCOPED_INFO("swapped, then chosen by arithmetic: " + r.text); + REQUIRE(r.text.find("h = !!g + 4;") != std::string::npos); } - template<> template<> - void allsloptimizer_object::test<6>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: unreachable code and unused labels and functions and states go", "[allsloptimizer]") { - set_test_name("dead code goes: after a return, under a false condition, unused labels, functions and states"); const std::string source = "f()\n{\n return;\n llSay(0, \"never\");\n}\ng()\n{\n}\n" "default\n{\n state_entry()\n {\n if (0) llSay(0, \"no\");\n if (1) llSay(0, \"yes\"); else llSay(0, \"no\");\n while (0) llSay(0, \"no\");\n @unused;\n f();\n ;\n {}\n 3;\n }\n}\nstate other\n{\n state_entry()\n {\n }\n}\n"; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); - ensure_equals("text", r.text, "f()\n{\n return;\n}\n\ndefault\n{\n state_entry()\n {\n llSay(0, \"yes\");\n f();\n }\n}\n"); - ensure("noted the return", has(r, "can never run")); - ensure("noted g", has(r, "removed the unused function g")); - ensure("noted the state", has(r, "removed the state other")); - ensure("noted the label", has(r, "nothing jumps to")); - } - - template<> template<> - void allsloptimizer_object::test<7>() + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("text"); + CHECK(r.text == "f()\n{\n return;\n}\n\ndefault\n{\n state_entry()\n {\n llSay(0, \"yes\");\n f();\n }\n}\n"); + UNSCOPED_INFO("noted the return"); + CHECK(has(r, "can never run")); + UNSCOPED_INFO("noted g"); + CHECK(has(r, "removed the unused function g")); + UNSCOPED_INFO("noted the state"); + CHECK(has(r, "removed the state other")); + UNSCOPED_INFO("noted the label"); + CHECK(has(r, "nothing jumps to")); + } + + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: llGetListLength as a comparison and a list literal as a sum where smaller", "[allsloptimizer]") { - set_test_name("llGetListLength becomes a comparison, and a list literal a sum, with parentheses where they matter, each where smaller"); const std::string source = wrap("list l;\n", " integer n = llGetListLength(l) + 1;\n if (llGetListLength(l)) l = [1, \"a\"];\n l = [];\n llOwnerSay((string)n);\n"); // On LSO both are smaller. ALLSLOptimizer::Options lso = options(); lso.target = ALLSLOptimizer::Target::LSO; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, lso); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); // One more as -~x on LSO (ALLSLCosts::negateComplementForIncrement). - ensure("in a sum: " + r.text, r.text.find("integer n = -~(l != []);") != std::string::npos); - ensure("as a condition", r.text.find("if (l != [])") != std::string::npos); - ensure("list add: " + r.text, r.text.find("l = (list)1 + \"a\";") != std::string::npos); - ensure("an empty list stays", r.text.find("l = [];") != std::string::npos); + UNSCOPED_INFO("in a sum: " + r.text); + REQUIRE(r.text.find("integer n = -~(l != []);") != std::string::npos); + UNSCOPED_INFO("as a condition"); + REQUIRE(r.text.find("if (l != [])") != std::string::npos); + UNSCOPED_INFO("list add: " + r.text); + REQUIRE(r.text.find("l = (list)1 + \"a\";") != std::string::npos); + UNSCOPED_INFO("an empty list stays"); + REQUIRE(r.text.find("l = [];") != std::string::npos); // On Mono the comparison is not, though one more is -~x there too; // nor is the sum here, one sum not paying for the helper it would // be the script's only use of. r = ALLSLOptimizer::run(source, options()); - ensure("no comparison on Mono: " + r.text, r.text.find("integer n = -~llGetListLength(l);") != std::string::npos && r.text.find("l != []") == std::string::npos); - ensure("one sum not worth its helper on Mono: " + r.text, r.text.find("l = [1, \"a\"];") != std::string::npos); + { + INFO("no comparison on Mono: " + r.text); + REQUIRE(r.text.find("integer n = -~llGetListLength(l);") != std::string::npos); + REQUIRE(r.text.find("l != []") == std::string::npos); + } + UNSCOPED_INFO("one sum not worth its helper on Mono: " + r.text); + REQUIRE(r.text.find("l = [1, \"a\"];") != std::string::npos); // On Luau neither. ALLSLOptimizer::Options luau = options(); luau.target = ALLSLOptimizer::Target::Luau; r = ALLSLOptimizer::run(source, luau); - ensure("neither on Luau: " + r.text, r.text.find("l = [1, \"a\"];") != std::string::npos && r.text.find("l != []") == std::string::npos); + { + INFO("neither on Luau: " + r.text); + REQUIRE(r.text.find("l = [1, \"a\"];") != std::string::npos); + REQUIRE(r.text.find("l != []") == std::string::npos); + } } - template<> template<> - void allsloptimizer_object::test<8>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: names shrink to the shortest free ones and the map says which", "[allsloptimizer]") { - set_test_name("names shrink to the shortest that are free, most used first, and the map says which"); const std::string source = "integer counter;\nbump(integer by)\n{\n counter += by;\n}\ndefault\n{\n touch_start(integer total)\n {\n bump(total);\n bump(1);\n llSay(0, (string)counter);\n }\n}\n"; ALLSLOptimizer::Options o = options(); o.shrinknames = true; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized", r.optimized); - ensure("renamed counter", r.renamed.count("counter") == 1); - ensure("renamed bump", r.renamed.count("bump") == 1); - ensure("renamed total", r.renamed.count("total") == 1); - ensure("the event stays", r.text.find("touch_start(integer ") != std::string::npos); - ensure("default stays", r.text.find("default\n{") != std::string::npos); - ensure("single letters", r.renamed["bump"].size() == 1 && r.renamed["counter"].size() == 1); - ensure("nothing of the old names", r.text.find("counter") == std::string::npos && r.text.find("bump") == std::string::npos); - ensure("noted", has(r, "renamed the function bump to")); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("renamed counter"); + REQUIRE(r.renamed.count("counter") == 1); + UNSCOPED_INFO("renamed bump"); + REQUIRE(r.renamed.count("bump") == 1); + UNSCOPED_INFO("renamed total"); + REQUIRE(r.renamed.count("total") == 1); + UNSCOPED_INFO("the event stays"); + REQUIRE(r.text.find("touch_start(integer ") != std::string::npos); + UNSCOPED_INFO("default stays"); + REQUIRE(r.text.find("default\n{") != std::string::npos); + { + INFO("single letters"); + REQUIRE(r.renamed["bump"].size() == 1); + REQUIRE(r.renamed["counter"].size() == 1); + } + { + INFO("nothing of the old names"); + REQUIRE(r.text.find("counter") == std::string::npos); + REQUIRE(r.text.find("bump") == std::string::npos); + } + UNSCOPED_INFO("noted"); + CHECK(has(r, "renamed the function bump to")); } - template<> template<> - void allsloptimizer_object::test<9>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a script with errors is left alone and the map leads back", "[allsloptimizer]") { - set_test_name("a script with errors is left alone, and the map leads back"); ALLSLOptimizer::Result r = ALLSLOptimizer::run("default { state_entry() { integer x = ; } }", options()); - ensure("not optimized", !r.optimized); - ensure("an error", r.problems.size() == 1 && r.problems[0].severity == ALScriptProblem::Severity::Error); - ensure("the source as it was", r.text == "default { state_entry() { integer x = ; } }"); + UNSCOPED_INFO("not optimized"); + CHECK_FALSE(r.optimized); + { + INFO("an error"); + REQUIRE(r.problems.size() == 1); + CHECK(r.problems[0].severity == ALScriptProblem::Severity::Error); + } + UNSCOPED_INFO("the source as it was"); + CHECK(r.text == "default { state_entry() { integer x = ; } }"); const std::string source = "integer g = 3;\n\n\ndefault\n{\n state_entry()\n {\n llSay(0, (string)g);\n }\n}\n"; r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); - ensure_equals("text", r.text, "default\n{\n state_entry()\n {\n llSay(0, \"3\");\n }\n}\n"); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("text"); + CHECK(r.text == "default\n{\n state_entry()\n {\n llSay(0, \"3\");\n }\n}\n"); // The llSay is on output line 4 and came from source line 7. const ALSourceMap::Loc loc = r.map.toSource(4, 8); - ensure("found", loc.found()); - ensure_equals("line", loc.line, 7); - ensure_equals("column", loc.column, 8); + UNSCOPED_INFO("found"); + REQUIRE(loc.found()); + UNSCOPED_INFO("line"); + CHECK(loc.line == 7); + UNSCOPED_INFO("column"); + CHECK(loc.column == 8); } - template<> template<> - void allsloptimizer_object::test<10>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: every golden file comes out as its expected text", "[allsloptimizer]") { - set_test_name("every golden file comes out as its expected text"); namespace fs = std::filesystem; const fs::path dir = fsyspath(AL_ALSCRIPT_TEST_DIR) / "optimizer"; S32 checked = 0; @@ -429,96 +501,147 @@ namespace tut o.addstrings = first.find("addstrings") != std::string::npos; o.target = first.find("lso") != std::string::npos ? ALLSLOptimizer::Target::LSO : first.find("luau") != std::string::npos ? ALLSLOptimizer::Target::Luau : ALLSLOptimizer::Target::Mono; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized " + name + ": " + notes(r), r.optimized); + UNSCOPED_INFO("optimized " + name + ": " + notes(r)); + REQUIRE(r.optimized); if (const char* write = std::getenv("AL_OPTIMIZER_WRITE_EXPECTED"); write && *write) { llofstream out(expected, std::ios::binary); out << r.text; continue; } - ensure("expected file for " + name, fs::exists(expected)); - ensure_equals("output of " + name, r.text, read(expected)); + UNSCOPED_INFO("expected file for " + name); + REQUIRE(fs::exists(expected)); + UNSCOPED_INFO("output of " + name); + REQUIRE(r.text == read(expected)); ++checked; } - ensure("some golden files", checked > 0 || std::getenv("AL_OPTIMIZER_WRITE_EXPECTED")); + UNSCOPED_INFO("some golden files"); + REQUIRE((checked > 0 || std::getenv("AL_OPTIMIZER_WRITE_EXPECTED"))); } - template<> template<> - void allsloptimizer_object::test<11>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: rotation functions fold over unit rotations and leave the rest", "[allsloptimizer]") { - set_test_name("the rotation functions fold over unit rotations by the viewer's own quaternion, and leave the rest"); const std::string source = wrap("", " rotation a = llEuler2Rot(<0, 0, PI_BY_TWO>);\n vector b = llRot2Euler(<0, 0, 0.7071068, 0.7071068>);\n rotation c = llAxisAngle2Rot(<0, 0, 2>, PI);\n vector d = llRot2Axis(<0, 0, 0.7071068, 0.7071068>);\n float e = llRot2Angle(<0, 0, 0.7071068, 0.7071068>);\n vector f = llRot2Fwd(<0, 0, 0.7071068, 0.7071068>);\n vector h = llRot2Up(<0.7071068, 0, 0, 0.7071068>);\n vector i = llRot2Axis(<0, 0, 0, 1>);\n vector j = llRot2Fwd(<0, 0, 2, 2>);\n if (llFrand(1) < 0.5)\n {\n a = c = ZERO_ROTATION; b = d = f = h = i = j = ZERO_VECTOR; e = 0;\n }\n llSay(0, (string)a + (string)b + (string)c + (string)d + (string)e + (string)f + (string)h + (string)i + (string)j);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; // A quarter turn about z: x and y zero, z and s a half root two. - ensure("euler to rot: " + r.text, about(numbers(r.text, "rotation a"), { 0.f, 0.f, 0.70710677f, 0.70710677f })); - ensure("rot to euler: " + r.text, about(numbers(r.text, "vector b"), { 0.f, 0.f, 1.5707964f })); - ensure("axis and angle, the axis normalised: " + r.text, about(numbers(r.text, "rotation c"), { 0.f, 0.f, 1.f, 0.f }) && !has("llAxisAngle2Rot")); - ensure("the axis back: " + r.text, about(numbers(r.text, "vector d"), { 0.f, 0.f, 1.f })); - ensure("the angle back: " + r.text, about(numbers(r.text, "float e"), { 1.5707964f })); - ensure("fwd of a quarter turn is left: " + r.text, !has("llRot2Fwd(<0, 0, 0.7071068, 0.7071068>)") && has("vector f = <")); - ensure("up of a roll: " + r.text, !has("llRot2Up(") && has("vector h = <0, ((float)-")); - ensure("no rotation has no axis: left", has("llRot2Axis(<0, 0, 0, 1>)")); - ensure("not a unit rotation: left", has("llRot2Fwd(<0, 0, 2, 2>)")); - } - template<> template<> - void allsloptimizer_object::test<12>() - { - set_test_name("llJsonGetValue folds a string or a plain number out of strict JSON, and leaves the rest to the simulator"); + UNSCOPED_INFO("euler to rot: " + r.text); + CHECK(about(numbers(r.text, "rotation a"), { 0.f, 0.f, 0.70710677f, 0.70710677f })); + UNSCOPED_INFO("rot to euler: " + r.text); + CHECK(about(numbers(r.text, "vector b"), { 0.f, 0.f, 1.5707964f })); + { + INFO("axis and angle, the axis normalised: " + r.text); + CHECK(about(numbers(r.text, "rotation c"), { 0.f, 0.f, 1.f, 0.f })); + CHECK_FALSE(has("llAxisAngle2Rot")); + } + UNSCOPED_INFO("the axis back: " + r.text); + CHECK(about(numbers(r.text, "vector d"), { 0.f, 0.f, 1.f })); + UNSCOPED_INFO("the angle back: " + r.text); + CHECK(about(numbers(r.text, "float e"), { 1.5707964f })); + { + INFO("fwd of a quarter turn is left: " + r.text); + CHECK_FALSE(has("llRot2Fwd(<0, 0, 0.7071068, 0.7071068>)")); + CHECK(has("vector f = <")); + } + { + INFO("up of a roll: " + r.text); + CHECK_FALSE(has("llRot2Up(")); + CHECK(has("vector h = <0, ((float)-")); + } + UNSCOPED_INFO("no rotation has no axis: left"); + CHECK(has("llRot2Axis(<0, 0, 0, 1>)")); + UNSCOPED_INFO("not a unit rotation: left"); + CHECK(has("llRot2Fwd(<0, 0, 2, 2>)")); + } + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: the JSON functions fold strict plain JSON and leave the rest to the simulator", "[allsloptimizer]") + { const std::string source = wrap("", " string a = llJsonGetValue(\"{\\\"name\\\": \\\"Ann\\\", \\\"tags\\\": [\\\"x\\\", 7, {\\\"k\\\": \\\"v\\\"}]}\", [\"name\"]);\n string b = llJsonGetValue(\"{\\\"tags\\\": [\\\"x\\\", -7]}\", [\"tags\", 1]);\n string c = llJsonGetValue(\"{\\\"tags\\\": [\\\"x\\\", 7, {\\\"k\\\": \\\"v\\\"}]}\", [\"tags\", 2, \"k\"]);\n string d = llJsonGetValue(\"[1.5]\", [0]);\n string e = llJsonGetValue(\"[true]\", [0]);\n string f = llJsonGetValue(\"[1]\", [3]);\n string g = llJsonGetValue(\"{\\\"a\\\": 1, \\\"a\\\": 2}\", [\"a\"]);\n string h = llJsonGetValue(\"[1,]\", [0]);\n string i = llJsonGetValue(\"[\\\"a\\\\\\\"b\\\"]\", [0]);\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = i = \"\";\n }\n llSay(0, a + b + c + d + e + f + g + h + i);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; - ensure("a key: " + r.text, has("string a = \"Ann\";")); - ensure("an index, a negative integer: " + r.text, has("string b = \"-7\";")); - ensure("down a path: " + r.text, has("string c = \"v\";")); - ensure("a fraction is left: " + r.text, has("string d = llJsonGetValue")); - ensure("true is its constant: " + r.text, has("string e = JSON_TRUE;")); - ensure("out of range is JSON_INVALID: " + r.text, has("string f = JSON_INVALID;")); - ensure("duplicate keys are left", has("string g = llJsonGetValue")); - ensure("a trailing comma is left", has("string h = llJsonGetValue")); - ensure("an escape unescaped: " + r.text, has("string i = \"a\\\"b\";")); + UNSCOPED_INFO("a key: " + r.text); + CHECK(has("string a = \"Ann\";")); + UNSCOPED_INFO("an index, a negative integer: " + r.text); + CHECK(has("string b = \"-7\";")); + UNSCOPED_INFO("down a path: " + r.text); + CHECK(has("string c = \"v\";")); + UNSCOPED_INFO("a fraction is left: " + r.text); + CHECK(has("string d = llJsonGetValue")); + UNSCOPED_INFO("true is its constant: " + r.text); + CHECK(has("string e = JSON_TRUE;")); + UNSCOPED_INFO("out of range is JSON_INVALID: " + r.text); + CHECK(has("string f = JSON_INVALID;")); + UNSCOPED_INFO("duplicate keys are left"); + CHECK(has("string g = llJsonGetValue")); + UNSCOPED_INFO("a trailing comma is left"); + CHECK(has("string h = llJsonGetValue")); + UNSCOPED_INFO("an escape unescaped: " + r.text); + CHECK(has("string i = \"a\\\"b\";")); // The answers that are constants are the constants' names. const std::string more = wrap("", " string a = llJsonGetValue(\"[true, null]\", [0]);\n string b = llJsonGetValue(\"[1]\", [3]);\n string c = llJsonValueType(\"{\\\"a\\\": [1, {}]}\", [\"a\", 1]);\n string d = llJsonValueType(\"{\\\"a\\\": 1}\", [\"b\"]);\n string e = llJsonValueType(\"[1,]\", []);\n string f = llList2Json(JSON_OBJECT, [\"name\", \"Ann Lee\", \"count\", 3]);\n string g = llList2Json(JSON_ARRAY, [1, \"true\"]);\n string h = llList2Json(JSON_ARRAY, [1.5]);\n list i = llJson2List(\"[1, \\\"two\\\", -3]\");\n list j = llJson2List(\"{\\\"k\\\": \\\"v\\\"}\");\n list k = llJson2List(\"[true]\");\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = \"\"; i = j = k = [];\n }\n llSay(0, a + b + c + d + e + f + g + h + llList2CSV(i) + llList2CSV(j) + llList2CSV(k));\n"); ALLSLOptimizer::Result m = ALLSLOptimizer::run(more, options()); - ensure("optimized", m.optimized); + UNSCOPED_INFO("optimized"); + CHECK(m.optimized); auto hasm = [&m](const char* text) { return m.text.find(text) != std::string::npos; }; - ensure("true is JSON_TRUE: " + m.text, hasm("string a = JSON_TRUE;")); - ensure("a miss is JSON_INVALID: " + m.text, hasm("string b = JSON_INVALID;")); - ensure("the type of a nested object: " + m.text, hasm("string c = JSON_OBJECT;")); - ensure("the type of nothing: " + m.text, hasm("string d = JSON_INVALID;")); - ensure("malformed is left to the simulator: " + m.text, hasm("string e = llJsonValueType")); - ensure("an object from plain strings and integers: " + m.text, hasm("string f = \"{\\\"name\\\":\\\"Ann Lee\\\",\\\"count\\\":3}\";")); - ensure("a string that reads as a JSON word is left: " + m.text, hasm("string g = llList2Json")); - ensure("a float is left: " + m.text, hasm("string h = llList2Json")); - ensure("an array to a list: " + m.text, hasm("list i = [1, \"two\", ((integer)-3)];")); - ensure("an object to a list: " + m.text, hasm("list j = [\"k\", \"v\"];")); - ensure("a true in it is left: " + m.text, hasm("list k = llJson2List")); + UNSCOPED_INFO("true is JSON_TRUE: " + m.text); + CHECK(hasm("string a = JSON_TRUE;")); + UNSCOPED_INFO("a miss is JSON_INVALID: " + m.text); + CHECK(hasm("string b = JSON_INVALID;")); + UNSCOPED_INFO("the type of a nested object: " + m.text); + CHECK(hasm("string c = JSON_OBJECT;")); + UNSCOPED_INFO("the type of nothing: " + m.text); + CHECK(hasm("string d = JSON_INVALID;")); + UNSCOPED_INFO("malformed is left to the simulator: " + m.text); + CHECK(hasm("string e = llJsonValueType")); + UNSCOPED_INFO("an object from plain strings and integers: " + m.text); + CHECK(hasm("string f = \"{\\\"name\\\":\\\"Ann Lee\\\",\\\"count\\\":3}\";")); + UNSCOPED_INFO("a string that reads as a JSON word is left: " + m.text); + CHECK(hasm("string g = llList2Json")); + UNSCOPED_INFO("a float is left: " + m.text); + CHECK(hasm("string h = llList2Json")); + UNSCOPED_INFO("an array to a list: " + m.text); + CHECK(hasm("list i = [1, \"two\", ((integer)-3)];")); + UNSCOPED_INFO("an object to a list: " + m.text); + CHECK(hasm("list j = [\"k\", \"v\"];")); + UNSCOPED_INFO("a true in it is left: " + m.text); + CHECK(hasm("list k = llJson2List")); // A nested value comes out written the compact way; a set puts a // value in, at a key or an index, where the document is plain. const std::string sets = wrap("", " string a = llJsonGetValue(\"{\\\"a\\\": [1, {\\\"b\\\": true}, \\\"c\\\"]}\", [\"a\"]);\n string b = llJsonSetValue(\"{\\\"a\\\": 1}\", [\"b\"], \"two\");\n string c = llJsonSetValue(\"[1, 2]\", [1], \"9\");\n string d = llJsonSetValue(\"[1, 2]\", [JSON_APPEND], JSON_TRUE);\n string e = llJsonSetValue(\"[1, 2]\", [5], \"9\");\n string f = llJsonSetValue(\"{}\", [\"a\", \"b\"], \"x\");\n string g = llJsonSetValue(\"[1]\", [0], \"1.5\");\n string h = llJsonGetValue(\"[1.5]\", []);\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = \"\";\n }\n llSay(0, a + b + c + d + e + f + g + h);\n"); ALLSLOptimizer::Result n = ALLSLOptimizer::run(sets, options()); - ensure("optimized", n.optimized); + UNSCOPED_INFO("optimized"); + CHECK(n.optimized); auto hasn = [&n](const char* text) { return n.text.find(text) != std::string::npos; }; - ensure("a nested value, compact: " + n.text, hasn("string a = \"[1,{\\\"b\\\":true},\\\"c\\\"]\";")); - ensure("a new key at the end: " + n.text, hasn("string b = \"{\\\"a\\\":1,\\\"b\\\":\\\"two\\\"}\";")); - ensure("an index replaced with a number: " + n.text, hasn("string c = \"[1,9]\";")); - ensure("appended by JSON_APPEND with a constant: " + n.text, hasn("string d = \"[1,2,true]\";")); - ensure("past the end is JSON_INVALID: " + n.text, hasn("string e = JSON_INVALID;")); - ensure("a path to create is left: " + n.text, hasn("string f = llJsonSetValue")); - ensure("a value that is a fraction is left: " + n.text, hasn("string g = llJsonSetValue")); - ensure("a document with a fraction is left: " + n.text, hasn("string h = llJsonGetValue")); + UNSCOPED_INFO("a nested value, compact: " + n.text); + CHECK(hasn("string a = \"[1,{\\\"b\\\":true},\\\"c\\\"]\";")); + UNSCOPED_INFO("a new key at the end: " + n.text); + CHECK(hasn("string b = \"{\\\"a\\\":1,\\\"b\\\":\\\"two\\\"}\";")); + UNSCOPED_INFO("an index replaced with a number: " + n.text); + CHECK(hasn("string c = \"[1,9]\";")); + UNSCOPED_INFO("appended by JSON_APPEND with a constant: " + n.text); + CHECK(hasn("string d = \"[1,2,true]\";")); + UNSCOPED_INFO("past the end is JSON_INVALID: " + n.text); + CHECK(hasn("string e = JSON_INVALID;")); + UNSCOPED_INFO("a path to create is left: " + n.text); + CHECK(hasn("string f = llJsonSetValue")); + UNSCOPED_INFO("a value that is a fraction is left: " + n.text); + CHECK(hasn("string g = llJsonSetValue")); + UNSCOPED_INFO("a document with a fraction is left: " + n.text); + CHECK(hasn("string h = llJsonGetValue")); const std::string dels = wrap("", " string a = llJsonSetValue(\"{\\\"a\\\": 1, \\\"b\\\": 2}\", [\"a\"], JSON_DELETE);\n string b = llJsonSetValue(\"[1, 2, 3]\", [1], JSON_DELETE);\n string c = llJsonSetValue(\"[1, 2, 3]\", [7], JSON_DELETE);\n if (llFrand(1) < 0.5)\n {\n a = b = c = \"\";\n }\n llSay(0, a + b + c);\n"); ALLSLOptimizer::Result q = ALLSLOptimizer::run(dels, options()); auto hasq = [&q](const char* text) { return q.text.find(text) != std::string::npos; }; - ensure("a key deleted: " + q.text, hasq("string a = \"{\\\"b\\\":2}\";")); - ensure("an index deleted: " + q.text, hasq("string b = \"[1,3]\";")); - ensure("deleting what is not there is the simulator's: " + q.text, hasq("string c = llJsonSetValue")); + UNSCOPED_INFO("a key deleted: " + q.text); + CHECK(hasq("string a = \"{\\\"b\\\":2}\";")); + UNSCOPED_INFO("an index deleted: " + q.text); + CHECK(hasq("string b = \"[1,3]\";")); + UNSCOPED_INFO("deleting what is not there is the simulator's: " + q.text); + CHECK(hasq("string c = llJsonSetValue")); } - template<> template<> - void allsloptimizer_object::test<13>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a function called once goes in as a block or as its returned expression", "[allsloptimizer]") { - set_test_name("a function called once is put in its place: a void one as a block with its parameters as locals, a one-return one as its expression"); const std::string source = "integer g;\n" "say(string what, integer n)\n" @@ -554,38 +677,65 @@ namespace tut o.dcr = false; o.constfold = false; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); // The inliner's own text is what the optimizer read. const ALLSLInliner::Result put = ALLSLInliner::run(source); - ensure_equals("five put in place", put.inlined, 5); - ensure("the block where the call was: " + put.text, - put.text.find(" {\nstring what = \"hi\";\ninteger n = i;\n\n integer i_1 = n * 2;\n llSay(0, what + (string)i_1);\n\n}\n") != std::string::npos); - ensure("the expression where the other call was, the temporaries before the statement: " + put.text, - put.text.find(" integer _t_1 = g;\n integer _t_2 = i;\n g = (i * 2 + g) + (_t_1 + _t_1 + _t_1) + (_t_2 + _t_2 + _t_2);") != std::string::npos); + UNSCOPED_INFO("five put in place"); + CHECK(put.inlined == 5); + UNSCOPED_INFO("the block where the call was: " + put.text); + REQUIRE(put.text.find(" {\nstring what = \"hi\";\ninteger n = i;\n\n integer i_1 = n * 2;\n llSay(0, what + (string)i_1);\n\n}\n") != std::string::npos); + UNSCOPED_INFO("the expression where the other call was, the temporaries before the statement: " + put.text); + REQUIRE(put.text.find(" integer _t_1 = g;\n integer _t_2 = i;\n g = (i * 2 + g) + (_t_1 + _t_1 + _t_1) + (_t_2 + _t_2 + _t_2);") != std::string::npos); auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; - ensure("say went into a block, its i renamed past the caller's: " + r.text, has("integer i_1 = n * 2;") && has("llSay(0, what + (string)i_1);")); - ensure("the parameters became locals set to the arguments: " + r.text, has("string what = \"hi\";") && has("integer n = i;")); - ensure("say is gone: " + r.text, !has("say(string what")); - ensure("twice became its expression with the argument in: " + r.text, has("(i * 2 + g)")); - ensure("thrice reads its parameter thrice, so each name is read once into a temporary before the statement: " + r.text, - has("integer _t_1 = g;") && has("integer _t_2 = i;") && has("(_t_1 + _t_1 + _t_1)") && has("(_t_2 + _t_2 + _t_2)") && !has("integer thrice(integer x)")); - ensure("loop went in with its label and jump given a fresh name: " + r.text, !has("loop();") && !has("loop()\n") && has("@again_1;") && has("jump again_1;")); + { + INFO("say went into a block, its i renamed past the caller's: " + r.text); + CHECK(has("integer i_1 = n * 2;")); + CHECK(has("llSay(0, what + (string)i_1);")); + } + { + INFO("the parameters became locals set to the arguments: " + r.text); + CHECK(has("string what = \"hi\";")); + CHECK(has("integer n = i;")); + } + UNSCOPED_INFO("say is gone: " + r.text); + CHECK_FALSE(has("say(string what")); + UNSCOPED_INFO("twice became its expression with the argument in: " + r.text); + CHECK(has("(i * 2 + g)")); + { + INFO("thrice reads its parameter thrice, so each name is read once into a temporary before the statement: " + r.text); + CHECK(has("integer _t_1 = g;")); + CHECK(has("integer _t_2 = i;")); + CHECK(has("(_t_1 + _t_1 + _t_1)")); + CHECK(has("(_t_2 + _t_2 + _t_2)")); + CHECK_FALSE(has("integer thrice(integer x)")); + } + { + INFO("loop went in with its label and jump given a fresh name: " + r.text); + CHECK_FALSE(has("loop();")); + CHECK_FALSE(has("loop()\n")); + CHECK(has("@again_1;")); + CHECK(has("jump again_1;")); + } S32 said = 0; for (const ALScriptProblem& p : r.problems) { said += p.severity == ALScriptProblem::Severity::Note && p.message.find("in place of") != std::string::npos; } - ensure_equals("five notes: say, twice, loop, and thrice twice", said, 5); + UNSCOPED_INFO("five notes: say, twice, loop, and thrice twice"); + CHECK(said == 5); // The map leads from the block back to the function's own lines. const size_t at = r.text.find("llSay(0, what + (string)i_1);"); - ensure("found", at != std::string::npos); + UNSCOPED_INFO("found"); + REQUIRE(at != std::string::npos); S32 line = 0; for (size_t i = 0; i < at; ++i) { line += r.text[i] == '\n'; } const ALSourceMap::Loc from = r.map.toSource(line, 8); - ensure_equals("the llSay came from the function's fifth line", from.line, 4); + UNSCOPED_INFO("the llSay came from the function's fifth line"); + CHECK(from.line == 4); // With the folds on, a note about the block's code points at the // function's own lines. o.constfold = true; @@ -598,12 +748,11 @@ namespace tut folded_in_body = p.line == 3; } } - ensure("a note on the body's code is at the function's line: " + notes(r), folded_in_body); + UNSCOPED_INFO("a note on the body's code is at the function's line: " + notes(r)); + CHECK(folded_in_body); } - template<> template<> - void allsloptimizer_object::test<14>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a small expression function goes everywhere and a marked one at any size", "[allsloptimizer]") { - set_test_name("a small function returning an expression goes in every place, and a marked one whatever its size"); const std::string source = "integer sq(integer x)\n" "{\n" @@ -629,22 +778,35 @@ namespace tut o.dcr = false; o.constfold = false; ALLSLInliner::Result put = ALLSLInliner::run(source); - ensure("sq went into both places, once each round: " + put.text, put.text.find("integer a = (2 * 2) + (3 * 3);") != std::string::npos); - ensure("and is gone", put.text.find("integer sq(") == std::string::npos); - ensure("shout, called twice and unmarked, stays: " + put.text, put.text.find("shout(\"x\");") != std::string::npos && put.text.find("shout(string what)") != std::string::npos); - ensure_equals("two rounds", put.inlined, 2); + UNSCOPED_INFO("sq went into both places, once each round: " + put.text); + REQUIRE(put.text.find("integer a = (2 * 2) + (3 * 3);") != std::string::npos); + UNSCOPED_INFO("and is gone"); + REQUIRE(put.text.find("integer sq(") == std::string::npos); + { + INFO("shout, called twice and unmarked, stays: " + put.text); + REQUIRE(put.text.find("shout(\"x\");") != std::string::npos); + REQUIRE(put.text.find("shout(string what)") != std::string::npos); + } + UNSCOPED_INFO("two rounds"); + CHECK(put.inlined == 2); put = ALLSLInliner::run(source, { "shout" }); - ensure("marked, shout goes into both places: " + put.text, - put.text.find("string what = \"x\";") != std::string::npos && put.text.find("string what = \"y\";") != std::string::npos && - put.text.find("shout(string what)") == std::string::npos); - ensure_equals("four rounds", put.inlined, 4); + { + INFO("marked, shout goes into both places: " + put.text); + REQUIRE(put.text.find("string what = \"x\";") != std::string::npos); + REQUIRE(put.text.find("string what = \"y\";") != std::string::npos); + REQUIRE(put.text.find("shout(string what)") == std::string::npos); + } + UNSCOPED_INFO("four rounds"); + CHECK(put.inlined == 4); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("the optimizer reads the result: " + notes(r), r.optimized && r.text.find("(2 * 2) + (3 * 3)") != std::string::npos); + { + INFO("the optimizer reads the result: " + notes(r)); + CHECK(r.optimized); + REQUIRE(r.text.find("(2 * 2) + (3 * 3)") != std::string::npos); + } } - template<> template<> - void allsloptimizer_object::test<15>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: returns become jumps to an end label and a round skips overlapping calls", "[allsloptimizer]") { - set_test_name("a return in the body becomes a jump to a label that ends the block, and a round takes every call that does not cross another's edit"); const std::string source = "check(integer n)\n" "{\n" @@ -674,25 +836,40 @@ namespace tut "}\n"; ALLSLInliner::Result put = ALLSLInliner::run(source); auto has = [&put](const char* text) { return put.text.find(text) != std::string::npos; }; - ensure("the returns are jumps: " + put.text, has("if (n < 0) jump _ret_1;") && has(" jump _ret_1;")); - ensure("to a label that ends the block: " + put.text, has("@_ret_1;\n}") || has("@_ret_1;\n }")); + { + INFO("the returns are jumps: " + put.text); + CHECK(has("if (n < 0) jump _ret_1;")); + CHECK(has(" jump _ret_1;")); + } + UNSCOPED_INFO("to a label that ends the block: " + put.text); + CHECK((has("@_ret_1;\n}") || has("@_ret_1;\n }"))); const ALLSLInliner::Result trailing = ALLSLInliner::run("f()\n{\n llSay(0, \"x\");\n return;\n}\ndefault\n{\n state_entry()\n {\n f();\n }\n}\n"); - ensure("a return that ends the body is just dropped: " + trailing.text, trailing.text.find("jump") == std::string::npos && trailing.text.find("@_ret") == std::string::npos && - trailing.text.find("llSay(0, \"x\");") != std::string::npos); - ensure("check is gone", !has("check(integer n)")); + { + INFO("a return that ends the body is just dropped: " + trailing.text); + REQUIRE(trailing.text.find("jump") == std::string::npos); + REQUIRE(trailing.text.find("@_ret") == std::string::npos); + REQUIRE(trailing.text.find("llSay(0, \"x\");") != std::string::npos); + } + UNSCOPED_INFO("check is gone"); + CHECK_FALSE(has("check(integer n)")); // outer's one call goes in the first round; inner's call is inside // outer, whose lines go that round, so inner waits for the next. - ensure("inner went in the end: " + put.text, has("integer k = 7;") && !has("inner(integer k)") && !has("outer()\n")); - ensure_equals("three calls went", put.inlined, 3); + { + INFO("inner went in the end: " + put.text); + CHECK(has("integer k = 7;")); + CHECK_FALSE(has("inner(integer k)")); + CHECK_FALSE(has("outer()\n")); + } + UNSCOPED_INFO("three calls went"); + CHECK(put.inlined == 3); ALLSLOptimizer::Options o = options(); o.inlining = true; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("the optimizer reads it: " + notes(r), r.optimized); + UNSCOPED_INFO("the optimizer reads it: " + notes(r)); + CHECK(r.optimized); } - template<> template<> - void allsloptimizer_object::test<16>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: pure arguments go in directly or via a temporary and others force a block", "[allsloptimizer]") { - set_test_name("an argument that changes nothing goes in as a name does, a temporary where it is read twice; one that may change something keeps the expression out, and a function called once goes as a block"); const std::string source = "integer g;\n" "integer both(integer x)\n" @@ -720,19 +897,34 @@ namespace tut "}\n"; const ALLSLInliner::Result put = ALLSLInliner::run(source); auto has = [&put](const char* text) { return put.text.find(text) != std::string::npos; }; - ensure("a pure call read twice is made once into a temporary: " + put.text, has(" integer _t_1 = llAbs(g);\n integer a = (_t_1 + _t_1);")); - ensure("an expression read once goes in, in parentheses: " + put.text, has("integer b = ((g * 2) + 1);")); - ensure("so does a pure call: " + put.text, has("integer c = ((llAbs(g)) + 1);")); + UNSCOPED_INFO("a pure call read twice is made once into a temporary: " + put.text); + CHECK(has(" integer _t_1 = llAbs(g);\n integer a = (_t_1 + _t_1);")); + UNSCOPED_INFO("an expression read once goes in, in parentheses: " + put.text); + CHECK(has("integer b = ((g * 2) + 1);")); + UNSCOPED_INFO("so does a pure call: " + put.text); + CHECK(has("integer c = ((llAbs(g)) + 1);")); // An argument that may change something, or whose answer may // change, cannot go into an expression; a function called once // goes from a block before the statement instead, its value set by // the body. - ensure("bump, called once, is set by its body before the statement, and once takes its value as a name: " + put.text, - has("integer _r_1;") && has("_r_1 = ++g;") && has("integer d = (_r_1 + 1);") && !has("integer once(integer x)")); - ensure("both, its other call gone, sets e itself, the time read once into its parameter: " + put.text, - has("integer e;") && has("integer x = llRound(llGetTime());") && has("e = x + x;") && !has("integer both(integer x)")); - ensure("no stray ; where the declaration stood: " + put.text, !has("};")); - ensure_equals("six went", put.inlined, 6); + { + INFO("bump, called once, is set by its body before the statement, and once takes its value as a name: " + put.text); + CHECK(has("integer _r_1;")); + CHECK(has("_r_1 = ++g;")); + CHECK(has("integer d = (_r_1 + 1);")); + CHECK_FALSE(has("integer once(integer x)")); + } + { + INFO("both, its other call gone, sets e itself, the time read once into its parameter: " + put.text); + CHECK(has("integer e;")); + CHECK(has("integer x = llRound(llGetTime());")); + CHECK(has("e = x + x;")); + CHECK_FALSE(has("integer both(integer x)")); + } + UNSCOPED_INFO("no stray ; where the declaration stood: " + put.text); + CHECK_FALSE(has("};")); + UNSCOPED_INFO("six went"); + CHECK(put.inlined == 6); // A temporary above an if whose condition has the call, and above // a for whose first part has it; not above a while, whose // condition is read every time round, nor an else-if. @@ -758,17 +950,19 @@ namespace tut "}\n"; const ALLSLInliner::Result above = ALLSLInliner::run(heads); auto got = [&above](const char* text) { return above.text.find(text) != std::string::npos; }; - ensure("above the if: " + above.text, got(" integer _t_1 = llAbs(g);\n if ((_t_1 + _t_1) > 3)")); - ensure("above the for, an assignment after the call's part not minded: " + above.text, - got(" integer _t_2 = llAbs(g);\n for (i = (_t_2 + _t_2), g = 0; i < 9; ++i) g++;")); - ensure("the while keeps its call: " + above.text, got("while (both(llAbs(g)) < 9) g++;")); - ensure("so does the else-if: " + above.text, got("else if (both(llAbs(g))) g = 2;")); - ensure_equals("two went", above.inlined, 2); - } - template<> template<> - void allsloptimizer_object::test<17>() + UNSCOPED_INFO("above the if: " + above.text); + CHECK(got(" integer _t_1 = llAbs(g);\n if ((_t_1 + _t_1) > 3)")); + UNSCOPED_INFO("above the for, an assignment after the call's part not minded: " + above.text); + CHECK(got(" integer _t_2 = llAbs(g);\n for (i = (_t_2 + _t_2), g = 0; i < 9; ++i) g++;")); + UNSCOPED_INFO("the while keeps its call: " + above.text); + CHECK(got("while (both(llAbs(g)) < 9) g++;")); + UNSCOPED_INFO("so does the else-if: " + above.text); + CHECK(got("else if (both(llAbs(g))) g = 2;")); + UNSCOPED_INFO("two went"); + CHECK(above.inlined == 2); + } + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: recursive functions stay and a label after the call is the return's target", "[allsloptimizer]") { - set_test_name("a function that reaches itself is never put in place, marked or not; a label after the call's statement is the return's target"); const std::string source = "ping(integer n)\n" "{\n" @@ -803,36 +997,56 @@ namespace tut "}\n"; const ALLSLInliner::Result put = ALLSLInliner::run(source, { "ping", "pong", "self" }); auto has = [&put](const char* text) { return put.text.find(text) != std::string::npos; }; - ensure("ping and pong, each reaching the other, stay: " + put.text, has("ping(integer n)") && has("pong(integer n)") && has(" ping(3);")); - ensure("self stays: " + put.text, has("self(integer n)") && has(" self(3);")); - ensure("early went in, its return a jump to the label already there: " + put.text, has("if (n < 0) jump next;") && !has("_ret") && has(" @next;")); - ensure_equals("one went", put.inlined, 1); + { + INFO("ping and pong, each reaching the other, stay: " + put.text); + CHECK(has("ping(integer n)")); + CHECK(has("pong(integer n)")); + CHECK(has(" ping(3);")); + } + { + INFO("self stays: " + put.text); + CHECK(has("self(integer n)")); + CHECK(has(" self(3);")); + } + { + INFO("early went in, its return a jump to the label already there: " + put.text); + CHECK(has("if (n < 0) jump next;")); + CHECK_FALSE(has("_ret")); + CHECK(has(" @next;")); + } + UNSCOPED_INFO("one went"); + CHECK(put.inlined == 1); } - template<> template<> - void allsloptimizer_object::test<18>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a run without notes says nothing and one out of budget stops where it is", "[allsloptimizer]") { - set_test_name("a run nobody reads the notes of says nothing, and one that reaches its budget stops where it is"); const std::string source = wrap("", " integer a = 1 + 2;\n integer b = 3 * 4;\n llSay(0, (string)(a + b));\n"); ALLSLOptimizer::Options o = options(); ALLSLOptimizer::Result said = ALLSLOptimizer::run(source, o); - ensure("the folds are said", !said.problems.empty()); + UNSCOPED_INFO("the folds are said"); + REQUIRE_FALSE(said.problems.empty()); o.notes = false; ALLSLOptimizer::Result quiet = ALLSLOptimizer::run(source, o); - ensure("nothing said", quiet.problems.empty()); - ensure_equals("and the same text either way", quiet.text, said.text); + UNSCOPED_INFO("nothing said"); + REQUIRE(quiet.problems.empty()); + UNSCOPED_INFO("and the same text either way"); + CHECK(quiet.text == said.text); // A budget too small for a round stops before the first, and // says so where the notes are read. o.notes = true; o.visitBudget = 1; ALLSLOptimizer::Result stopped = ALLSLOptimizer::run(source, o); - ensure("it stopped early", stopped.stoppedEarly); - ensure("and said so: " + notes(stopped), !stopped.problems.empty() && stopped.problems.back().key == std::string("OptimizerStoppedEarly")); - ensure("what it has stands", !stopped.text.empty()); + UNSCOPED_INFO("it stopped early"); + CHECK(stopped.stoppedEarly); + { + INFO("and said so: " + notes(stopped)); + REQUIRE_FALSE(stopped.problems.empty()); + CHECK(stopped.problems.back().key == std::string("OptimizerStoppedEarly")); + } + UNSCOPED_INFO("what it has stands"); + REQUIRE_FALSE(stopped.text.empty()); } - template<> template<> - void allsloptimizer_object::test<19>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: adding to a list never drops a zero or empty string or float element", "[allsloptimizer]") { - set_test_name("a list plus anything is one element longer: no zero, empty string or float is dropped from one, nor a literal's element"); const std::string source = wrap("", " list l = llGetPrimitiveParams([PRIM_SIZE]);\n" " l = l + 0;\n" @@ -845,30 +1059,45 @@ namespace tut " llSetPrimitiveParams([PRIM_TEXTURE, 0, s, <1, 1, 0>, ZERO_VECTOR, 0.0]);\n" " llSay(n, llList2CSV(l) + s);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized", r.optimized); - ensure("a zero appended stays: " + r.text, r.text.find("l = l + 0;") != std::string::npos); - ensure("an empty string appended stays: " + r.text, r.text.find("l = l + \"\";") != std::string::npos); - ensure("a zero put in front stays: " + r.text, r.text.find("l = ((float)0) + l;") != std::string::npos); - ensure("a negative appended is not turned into a subtraction: " + r.text, r.text.find("l - 1") == std::string::npos); - ensure("an empty list adds nothing: " + r.text, r.text.find("l + [];") == std::string::npos); - ensure("nor an empty string to a string: " + r.text, r.text.find("llList2String(l, 0) + \"\"") == std::string::npos); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("a zero appended stays: " + r.text); + REQUIRE(r.text.find("l = l + 0;") != std::string::npos); + UNSCOPED_INFO("an empty string appended stays: " + r.text); + REQUIRE(r.text.find("l = l + \"\";") != std::string::npos); + UNSCOPED_INFO("a zero put in front stays: " + r.text); + REQUIRE(r.text.find("l = ((float)0) + l;") != std::string::npos); + UNSCOPED_INFO("a negative appended is not turned into a subtraction: " + r.text); + REQUIRE(r.text.find("l - 1") == std::string::npos); + UNSCOPED_INFO("an empty list adds nothing: " + r.text); + REQUIRE(r.text.find("l + [];") == std::string::npos); + UNSCOPED_INFO("nor an empty string to a string: " + r.text); + REQUIRE(r.text.find("llList2String(l, 0) + \"\"") == std::string::npos); // The count is read once, and goes where it is read. - ensure("nor a zero to an integer: " + r.text, r.text.find("llGetListLength(l) + 0") == std::string::npos && r.text.find("l != [] + 0") == std::string::npos && - r.text.find("integer n") == std::string::npos); + { + INFO("nor a zero to an integer: " + r.text); + REQUIRE(r.text.find("llGetListLength(l) + 0") == std::string::npos); + REQUIRE(r.text.find("l != [] + 0") == std::string::npos); + REQUIRE(r.text.find("integer n") == std::string::npos); + } // The literal's elements, however it is written now, all there: // the texture's face and the glow. const size_t call = r.text.find("llSetPrimitiveParams("); - ensure("the call", call != std::string::npos); + UNSCOPED_INFO("the call"); + REQUIRE(call != std::string::npos); const std::string args = r.text.substr(call, r.text.find(';', call) - call); - ensure("the face, a zero: " + args, args.find("PRIM_TEXTURE + 0") != std::string::npos || args.find("PRIM_TEXTURE, 0") != std::string::npos); - ensure("and the last element, a float: " + args, args.find("ZERO_VECTOR + ((float)0)") != std::string::npos || - args.find("ZERO_VECTOR, ((float)0)") != std::string::npos); + UNSCOPED_INFO("the face, a zero: " + args); + REQUIRE((args.find("PRIM_TEXTURE + 0") != std::string::npos || args.find("PRIM_TEXTURE, 0") != std::string::npos)); + UNSCOPED_INFO("and the last element, a float: " + args); + REQUIRE((args.find("ZERO_VECTOR + ((float)0)") != std::string::npos || + args.find("ZERO_VECTOR, ((float)0)") != std::string::npos)); // And what it wrote compiles. ALLSLService service; const ALScriptProblems said = service.check(r.text); for (const ALScriptProblem& p : said) { - ensure("no error in what was written: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("no error in what was written: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } // The preprocessor's lazy-list helper grows its list one zero at a @@ -876,14 +1105,14 @@ namespace tut const std::string lazy = "list lazy_list_set(list L, integer i, list v)\n{\n while (llGetListLength(L) < i)\n L = L + 0;\n return llListReplaceList(L, v, i, i);\n}\n" + wrap("", " list x;\n x = lazy_list_set(x, 3, [1]);\n llSay(0, llList2CSV(x));\n"); r = ALLSLOptimizer::run(lazy, options()); - ensure("the list still grows: " + r.text, r.text.find("L = L + 0;") != std::string::npos); - ensure("rather than standing still", r.text.find("L = L;") == std::string::npos); + UNSCOPED_INFO("the list still grows: " + r.text); + REQUIRE(r.text.find("L = L + 0;") != std::string::npos); + UNSCOPED_INFO("rather than standing still"); + REQUIRE(r.text.find("L = L;") == std::string::npos); } - template<> template<> - void allsloptimizer_object::test<20>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: not-equal replaces a negated equals only where it means the same", "[allsloptimizer]") { - set_test_name("not-equal only takes the place of not-equals where the two say the same: numbers, or any two that are compared not-equal"); const std::string source = wrap("", " list a = llGetPrimitiveParams([PRIM_SIZE]);\n" " list b = llGetPrimitiveParams([PRIM_TYPE]);\n" @@ -895,15 +1124,16 @@ namespace tut " llSay(0, (string)(lists + same + ints));\n" " a = b = [];\n i = j = 0;\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("a list's not-equal is the difference of the lengths, so !(a == b) stays: " + r.text, r.text.find("!(a == b)") != std::string::npos); + UNSCOPED_INFO("a list's not-equal is the difference of the lengths, so !(a == b) stays: " + r.text); + REQUIRE(r.text.find("!(a == b)") != std::string::npos); // Each read once, and gone where it is read. - ensure("!(a != b) is a == b for lists too: " + r.text, r.text.find("+ (a == b) +") != std::string::npos); - ensure("and for integers !(i == j) is i != j: " + r.text, r.text.find("(i != j)") != std::string::npos); + UNSCOPED_INFO("!(a != b) is a == b for lists too: " + r.text); + REQUIRE(r.text.find("+ (a == b) +") != std::string::npos); + UNSCOPED_INFO("and for integers !(i == j) is i != j: " + r.text); + REQUIRE(r.text.find("(i != j)") != std::string::npos); } - template<> template<> - void allsloptimizer_object::test<21>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a function stays called where a caller's local shadows a global it reads", "[allsloptimizer]") { - set_test_name("a function goes in place only where the globals it reads are the same there: a caller's local of the name keeps the call"); const std::string source = "integer count;\n" "bump()\n" @@ -930,21 +1160,31 @@ namespace tut "}\n"; const ALLSLInliner::Result put = ALLSLInliner::run(source, { "bump", "plus" }); const size_t touch = put.text.find("touch_start"); - ensure("both events there", touch != std::string::npos); + UNSCOPED_INFO("both events there"); + REQUIRE(touch != std::string::npos); const std::string entry = put.text.substr(0, touch); const std::string touched = put.text.substr(touch); - ensure("where count is the caller's own, the calls stay: " + entry, - entry.find(" bump();") != std::string::npos && entry.find("plus(1)") != std::string::npos); - ensure("where it is the global, bump goes in: " + touched, touched.find("bump();") == std::string::npos && touched.find("count = count + 1;") != std::string::npos); - ensure("and plus: " + touched, touched.find("(n + count)") != std::string::npos); - ensure("so both functions stay, for the calls that kept them: " + put.text, - put.text.find("bump()\n{") != std::string::npos && put.text.find("integer plus(integer x)") != std::string::npos); + { + INFO("where count is the caller's own, the calls stay: " + entry); + REQUIRE(entry.find(" bump();") != std::string::npos); + REQUIRE(entry.find("plus(1)") != std::string::npos); + } + { + INFO("where it is the global, bump goes in: " + touched); + REQUIRE(touched.find("bump();") == std::string::npos); + REQUIRE(touched.find("count = count + 1;") != std::string::npos); + } + UNSCOPED_INFO("and plus: " + touched); + REQUIRE(touched.find("(n + count)") != std::string::npos); + { + INFO("so both functions stay, for the calls that kept them: " + put.text); + REQUIRE(put.text.find("bump()\n{") != std::string::npos); + REQUIRE(put.text.find("integer plus(integer x)") != std::string::npos); + } } - template<> template<> - void allsloptimizer_object::test<22>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: code after a jump or in a dead branch stays when it holds the jump's label", "[allsloptimizer]") { - set_test_name("what follows a jump stays where it holds the label the jump goes to, and so does a branch never taken that holds one"); // A jump in SL goes to the last label of its name in the function, // whatever block it is in: here, the one in the block after it. const std::string source = wrap("", @@ -971,27 +1211,33 @@ namespace tut " if (llGetUnixTime() > 0) jump b;\n" " jump c;\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; - ensure("the block the jump lands in stays: " + r.text, has("llOwnerSay(\"second\");")); - ensure("and what follows it: " + r.text, has("llOwnerSay(\"third\");")); - ensure("the if whose branch a jump lands in stays: " + r.text, has("llOwnerSay(\"fourth\");")); - ensure("and the loop whose body one does: " + r.text, has("llOwnerSay(\"fifth\");")); - ensure("nothing said to be unreachable: " + notes(r), notes(r).find("can never run") == std::string::npos); + UNSCOPED_INFO("the block the jump lands in stays: " + r.text); + CHECK(has("llOwnerSay(\"second\");")); + UNSCOPED_INFO("and what follows it: " + r.text); + CHECK(has("llOwnerSay(\"third\");")); + UNSCOPED_INFO("the if whose branch a jump lands in stays: " + r.text); + CHECK(has("llOwnerSay(\"fourth\");")); + UNSCOPED_INFO("and the loop whose body one does: " + r.text); + CHECK(has("llOwnerSay(\"fifth\");")); + UNSCOPED_INFO("nothing said to be unreachable: " + notes(r)); + REQUIRE(notes(r).find("can never run") == std::string::npos); // Where nothing jumps into it, it is still gone; and a jump from // inside it to its own label is no way in. r = ALLSLOptimizer::run(wrap("", " return;\n {\n @x;\n llOwnerSay(\"never\");\n }\n"), options()); - ensure("a block after a return that nothing jumps into goes: " + r.text, r.text.find("never") == std::string::npos); + UNSCOPED_INFO("a block after a return that nothing jumps into goes: " + r.text); + REQUIRE(r.text.find("never") == std::string::npos); r = ALLSLOptimizer::run(wrap("", " integer i;\n return;\n {\n @again;\n llOwnerSay(\"never\");\n if (++i < 3) jump again;\n }\n"), options()); - ensure("nor one that only jumps to itself: " + r.text, r.text.find("never") == std::string::npos); + UNSCOPED_INFO("nor one that only jumps to itself: " + r.text); + REQUIRE(r.text.find("never") == std::string::npos); } - template<> template<> - void allsloptimizer_object::test<23>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: an inlined block evaluates its arguments left to right as a call does", "[allsloptimizer]") { - set_test_name("a function put in place as a block takes its arguments in the order a call does: left to right, as LSO and Mono both push them"); const std::string source = "integer n;\n" "integer next()\n" @@ -1013,31 +1259,39 @@ namespace tut const ALLSLInliner::Result put = ALLSLInliner::run(source); const size_t a = put.text.find("integer a = next();"); const size_t b = put.text.find("integer b = next() * 10;"); - ensure("both arguments set to locals: " + put.text, a != std::string::npos && b != std::string::npos); - ensure("the first first: " + put.text, a < b); + { + INFO("both arguments set to locals: " + put.text); + REQUIRE(a != std::string::npos); + REQUIRE(b != std::string::npos); + } + UNSCOPED_INFO("the first first: " + put.text); + CHECK(a < b); } - template<> template<> - void allsloptimizer_object::test<24>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a true branch ending in an if gets braces so the else stays the outer if's", "[allsloptimizer]") { - set_test_name("a true branch that ends in an if of its own is printed in braces where an else follows, so that the else stays the outer if's"); // if (!c) A; else if (d) B; swapped round: the inner if has no else. ALLSLOptimizer::Result r = ALLSLOptimizer::run(wrap("integer c;\ninteger d = 1;\n", " if (!c) llOwnerSay(\"A\"); else if (d) llOwnerSay(\"B\");\n c = 1; d = 0;\n"), options()); - ensure("swapped: " + notes(r), has(r, "swapped the branches")); - ensure("the inner if in braces: " + r.text, r.text.find(" if (c)\n {\n if (d)\n llOwnerSay(\"B\");\n }\n") != std::string::npos); - ensure("the else the outer if's: " + r.text, r.text.find(" }\n else\n llOwnerSay(\"A\");\n") != std::string::npos); + UNSCOPED_INFO("swapped: " + notes(r)); + CHECK(has(r, "swapped the branches")); + UNSCOPED_INFO("the inner if in braces: " + r.text); + REQUIRE(r.text.find(" if (c)\n {\n if (d)\n llOwnerSay(\"B\");\n }\n") != std::string::npos); + UNSCOPED_INFO("the else the outer if's: " + r.text); + REQUIRE(r.text.find(" }\n else\n llOwnerSay(\"A\");\n") != std::string::npos); // And one whose inner if stands in a loop. r = ALLSLOptimizer::run(wrap("integer c;\ninteger d = 1;\n", " if (!c) llOwnerSay(\"A\"); else while (d--) if (d == 3) llOwnerSay(\"B\");\n c = 1;\n"), options()); - ensure("the loop in braces: " + r.text, r.text.find(" if (c)\n {\n while (d--)\n") != std::string::npos && - r.text.find(" }\n else\n llOwnerSay(\"A\");\n") != std::string::npos); + { + INFO("the loop in braces: " + r.text); + REQUIRE(r.text.find(" if (c)\n {\n while (d--)\n") != std::string::npos); + REQUIRE(r.text.find(" }\n else\n llOwnerSay(\"A\");\n") != std::string::npos); + } // Where the inner if has an else of its own, nothing is added. r = ALLSLOptimizer::run(wrap("integer c;\ninteger d = 1;\n", " if (!c) llOwnerSay(\"A\"); else if (d) llOwnerSay(\"B\"); else llOwnerSay(\"C\");\n c = 1; d = 0;\n"), options()); - ensure("no braces an else chain does not need: " + r.text, r.text.find('{', r.text.find("if (c)")) > r.text.find("llOwnerSay(\"A\")")); + UNSCOPED_INFO("no braces an else chain does not need: " + r.text); + CHECK(r.text.find('{', r.text.find("if (c)")) > r.text.find("llOwnerSay(\"A\")")); } - template<> template<> - void allsloptimizer_object::test<25>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: an inlined call as a brace-less loop body or branch keeps its return inside", "[allsloptimizer]") { - set_test_name("a call that is the whole of a loop's body or an if's branch, written without braces, keeps its return inside the loop or the branch"); const std::string fn = "skip(integer n)\n" "{\n" @@ -1048,24 +1302,36 @@ namespace tut ALLSLInliner::Result put = ALLSLInliner::run(fn + wrap("", " integer i;\n for (i = 0; i < 5; ++i) skip(i);\n llOwnerSay(\"done\");\n")); const size_t label = put.text.find("@_ret_1;"); const size_t close = put.text.find('}', label); - ensure("went in: " + put.text, put.inlined == 1 && label != std::string::npos && put.text.find("jump _ret_1;") != std::string::npos); - ensure("the label ends the loop's body, before anything after the loop: " + put.text, - close != std::string::npos && close < put.text.find("llOwnerSay(\"done\");")); + { + INFO("went in: " + put.text); + CHECK(put.inlined == 1); + REQUIRE(label != std::string::npos); + REQUIRE(put.text.find("jump _ret_1;") != std::string::npos); + } + { + INFO("the label ends the loop's body, before anything after the loop: " + put.text); + REQUIRE(close != std::string::npos); + CHECK(close < put.text.find("llOwnerSay(\"done\");")); + } ALLSLOptimizer::Options o = options(); o.inlining = true; ALLSLOptimizer::Result r = ALLSLOptimizer::run(fn + wrap("", " integer i;\n for (i = 0; i < 5; ++i) skip(i);\n llOwnerSay(\"done\");\n"), o); - ensure("the optimizer reads it: " + notes(r) + r.text, r.optimized); + UNSCOPED_INFO("the optimizer reads it: " + notes(r) + r.text); + CHECK(r.optimized); // Between an if and its else, a label after the block was a // syntax error the user never wrote. put = ALLSLInliner::run(fn + wrap("integer g;\n", " if (g) skip(g); else llOwnerSay(\"none\");\n")); - ensure("went in: " + put.text, put.inlined == 1); + UNSCOPED_INFO("went in: " + put.text); + CHECK(put.inlined == 1); r = ALLSLOptimizer::run(fn + wrap("integer g;\n", " if (g) skip(g); else llOwnerSay(\"none\");\n g = llGetUnixTime();\n"), o); - ensure("the else still follows its if: " + notes(r) + r.text, r.optimized && r.text.find("else") != std::string::npos); + { + INFO("the else still follows its if: " + notes(r) + r.text); + CHECK(r.optimized); + REQUIRE(r.text.find("else") != std::string::npos); + } } - template<> template<> - void allsloptimizer_object::test<26>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: the engine counts a clash from another thread but not a nested hold", "[allsloptimizer]") { - set_test_name("the engine is one thread's at a time: another coming in while one is in is said, and a hold within a hold is none"); const unsigned before = al_script_engine::clashes().load(); { AL_SCRIPT_ENGINE_HELD; @@ -1073,20 +1339,21 @@ namespace tut // A hold within a hold is the same thread's. AL_SCRIPT_ENGINE_HELD; } - ensure_equals("none from within", al_script_engine::clashes().load(), before); + UNSCOPED_INFO("none from within"); + CHECK(al_script_engine::clashes().load() == before); std::thread other([]() { AL_SCRIPT_ENGINE_HELD; }); other.join(); - ensure_equals("another thread, while this one is in: said", al_script_engine::clashes().load(), before + 1); + UNSCOPED_INFO("another thread, while this one is in: said"); + CHECK(al_script_engine::clashes().load() == before + 1); } std::thread after([]() { AL_SCRIPT_ENGINE_HELD; }); after.join(); - ensure_equals("another, once it is out: nothing", al_script_engine::clashes().load(), before + 1); + UNSCOPED_INFO("another, once it is out: nothing"); + CHECK(al_script_engine::clashes().load() == before + 1); } - template<> template<> - void allsloptimizer_object::test<27>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: list literal sums bracket elements and stay literals where order shows", "[allsloptimizer]") { - set_test_name("a list literal as a sum brackets an element where the sum would bind it otherwise, and stays a literal where the order would show"); // On LSO, where a list's shapes are made wherever they are smaller: // Mono weighs them against the helpers they need (test 41). ALLSLOptimizer::Options lso = options(); @@ -1096,7 +1363,8 @@ namespace tut const ALScriptProblems said = service.check(r.text); for (const ALScriptProblem& p : said) { - ensure("no error in what was written: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("no error in what was written: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } }; // A comparison among the elements, as a preprocessor writes one: @@ -1109,10 +1377,11 @@ namespace tut " , <((float)0), ((float)0), ((float)0), ((float)1)>\n" " ];\n"), lso); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); // Each element added to the list bare (ALLSLCosts::elementForList). - ensure("the comparison bracketed: " + r.text, - r.text.find("loc_params + 41 + (-1 < --loc_sitTargetsRemaining) + <0.0, 0.0, 0.0> + <0.0, 0.0, 0.0, 1.0>") != std::string::npos); + UNSCOPED_INFO("the comparison bracketed: " + r.text); + REQUIRE(r.text.find("loc_params + 41 + (-1 < --loc_sitTargetsRemaining) + <0.0, 0.0, 0.0> + <0.0, 0.0, 0.0, 1.0>") != std::string::npos); compiles(r); // The first element under the cast, bracketed where the cast would @@ -1120,10 +1389,14 @@ namespace tut r = ALLSLOptimizer::run(wrap("", " integer a = (integer)llFrand(9);\n integer b = (integer)llFrand(9);\n list l;\n" " l = [a - b, 1];\n l = [--a, 2];\n l = [-1, 3];\n l = [a * b, a - b];\n llSay(0, llList2CSV(l));\n"), lso); - ensure("a difference: " + r.text, r.text.find("l = (list)(a - b) + 1;") != std::string::npos); - ensure("a step down: " + r.text, r.text.find("l = (list)(--a) + 2;") != std::string::npos); - ensure("a negative constant bare: " + r.text, r.text.find("l = (list)-1 + 3;") != std::string::npos); - ensure("a product under the cast, a difference after it: " + r.text, r.text.find("l = (list)(a * b) + (a - b);") != std::string::npos); + UNSCOPED_INFO("a difference: " + r.text); + REQUIRE(r.text.find("l = (list)(a - b) + 1;") != std::string::npos); + UNSCOPED_INFO("a step down: " + r.text); + REQUIRE(r.text.find("l = (list)(--a) + 2;") != std::string::npos); + UNSCOPED_INFO("a negative constant bare: " + r.text); + REQUIRE(r.text.find("l = (list)-1 + 3;") != std::string::npos); + UNSCOPED_INFO("a product under the cast, a difference after it: " + r.text); + REQUIRE(r.text.find("l = (list)(a * b) + (a - b);") != std::string::npos); compiles(r); // A sum takes its right side first, so the elements would be taken @@ -1134,52 +1407,67 @@ namespace tut " l = [i, --i];\n l = [bump(), bump()];\n l = [llGetUnixTime(), bump()];\n l = [i, g];\n" " llSay(0, llList2CSV(l));\n"), lso); - ensure("a name and its change: " + r.text, r.text.find("l = [i, --i];") != std::string::npos); - ensure("two calls that change: " + r.text, r.text.find("l = [bump(), bump()];") != std::string::npos); - ensure("a read and a call that changes: " + r.text, r.text.find("l = [llGetUnixTime(), bump()];") != std::string::npos); - ensure("reads alone, a sum: " + r.text, r.text.find("l = (list)i + g;") != std::string::npos); + UNSCOPED_INFO("a name and its change: " + r.text); + REQUIRE(r.text.find("l = [i, --i];") != std::string::npos); + UNSCOPED_INFO("two calls that change: " + r.text); + REQUIRE(r.text.find("l = [bump(), bump()];") != std::string::npos); + UNSCOPED_INFO("a read and a call that changes: " + r.text); + REQUIRE(r.text.find("l = [llGetUnixTime(), bump()];") != std::string::npos); + UNSCOPED_INFO("reads alone, a sum: " + r.text); + REQUIRE(r.text.find("l = (list)i + g;") != std::string::npos); compiles(r); } - template<> template<> - void allsloptimizer_object::test<28>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: removed unreachable code is quoted by its first line cut to a few words", "[allsloptimizer]") { - set_test_name("what can never run, removed, is said as far as its first line and a few words of it"); std::string body = " return;\n llOwnerSay(\""; body += std::string(300, 'x'); body += "\");\n"; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(wrap("", body), options()); const auto note = std::find_if(r.problems.begin(), r.problems.end(), [](const ALScriptProblem& p) { return p.key == "OptimizerRemovedUnreachable"; }); - ensure("said", note != r.problems.end() && note->args.size() == 1); - ensure("not all of it", note->args[0].size() < 80 && note->args[0].find("\xE2\x80\xA6") != std::string::npos); - ensure("from its start", note->args[0].rfind("llOwnerSay", 0) == 0); + { + INFO("said"); + REQUIRE(note != r.problems.end()); + REQUIRE(note->args.size() == 1); + } + { + INFO("not all of it"); + REQUIRE(note->args[0].size() < 80); + REQUIRE(note->args[0].find("\xE2\x80\xA6") != std::string::npos); + } + UNSCOPED_INFO("from its start"); + CHECK(note->args[0].rfind("llOwnerSay", 0) == 0); } - template<> template<> - void allsloptimizer_object::test<29>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: the inliner and optimizer share one visit budget and rounds stop within it", "[allsloptimizer]") { - set_test_name("one budget for the inliner and the optimizer: what the inliner visits is spent, and a round is not begun the budget cannot see through"); const std::string source = wrap("integer twice(integer n) { return n * 2; }\ninteger thrice(integer n) { return twice(n) + n; }\n", " llSay(0, (string)thrice(2));\n llSay(0, (string)(1 + 2));\n"); ALLSLOptimizer::Options o = options(); o.inlining = true; const ALLSLOptimizer::Result whole = ALLSLOptimizer::run(source, o); - ensure("with room: not stopped", !whole.stoppedEarly); + UNSCOPED_INFO("with room: not stopped"); + CHECK_FALSE(whole.stoppedEarly); // Room for the inliner's first round and not much more: it stops, // and leaves the optimizer nothing. const ALLSLInliner::Result once = ALLSLInliner::run(source, ALLSLInliner::Asked(), size_t(-1)); - ensure("the inliner visits", once.visited > 0); + UNSCOPED_INFO("the inliner visits"); + CHECK(once.visited > 0); o.visitBudget = once.visited / 4; const ALLSLOptimizer::Result short_of = ALLSLOptimizer::run(source, o); - ensure("the optimizer stopped for what the inliner spent", short_of.stoppedEarly); - ensure("said", std::any_of(short_of.problems.begin(), short_of.problems.end(), [](const ALScriptProblem& p) { return p.key == "OptimizerStoppedEarly"; })); + UNSCOPED_INFO("the optimizer stopped for what the inliner spent"); + CHECK(short_of.stoppedEarly); + UNSCOPED_INFO("said"); + CHECK(std::any_of(short_of.problems.begin(), short_of.problems.end(), [](const ALScriptProblem& p) { return p.key == "OptimizerStoppedEarly"; })); const ALLSLInliner::Result cut = ALLSLInliner::run(source, ALLSLInliner::Asked(), 1); - ensure("the inliner's one round done, the next not begun", cut.inlined > 0 && (cut.stoppedEarly || cut.inlined == once.inlined)); + { + INFO("the inliner's one round done, the next not begun"); + CHECK(cut.inlined > 0); + CHECK((cut.stoppedEarly || cut.inlined == once.inlined)); + } } - template<> template<> - void allsloptimizer_object::test<30>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: an always-true if stays when it guards a state change in a function", "[allsloptimizer]") { - set_test_name("an if whose condition is always true stays where it is the last around a state change in a function, which may change state only under one"); const std::string source = "f()\n{\n if (TRUE) state other;\n}\n" "g()\n{\n if (TRUE)\n {\n if (llGetUnixTime()) state other;\n }\n}\n" "h()\n{\n if (FALSE) llOwnerSay(\"x\");\n else state other;\n}\n" @@ -1191,41 +1479,51 @@ namespace tut { refused = refused || p.severity == ALScriptProblem::Severity::Error; } - ensure("a bare state change in a function does not compile", refused); + UNSCOPED_INFO("a bare state change in a function does not compile"); + CHECK(refused); const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); for (const ALScriptProblem& p : service.check(r.text)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } const auto body = [&r](const char* name) { const size_t at = r.text.find(std::string(name) + "()\n{"); return at == std::string::npos ? std::string() : r.text.substr(at, r.text.find("\n}", at) - at); }; - ensure("f keeps its if: " + r.text, body("f").find("if (") != std::string::npos && body("f").find("state other;") != std::string::npos); - ensure("g loses the outer if, the inner one standing: " + r.text, body("g").find("llGetUnixTime()") != std::string::npos); - ensure("h keeps its if too: " + r.text, body("h").find("if (") != std::string::npos); + { + INFO("f keeps its if: " + r.text); + REQUIRE(body("f").find("if (") != std::string::npos); + REQUIRE(body("f").find("state other;") != std::string::npos); + } + UNSCOPED_INFO("g loses the outer if, the inner one standing: " + r.text); + REQUIRE(body("g").find("llGetUnixTime()") != std::string::npos); + UNSCOPED_INFO("h keeps its if too: " + r.text); + REQUIRE(body("h").find("if (") != std::string::npos); const size_t entry = r.text.find("state_entry()"); - ensure("an event changes state as it likes: its if goes: " + r.text, - entry != std::string::npos && r.text.find("if (", entry) > r.text.find("state other;", entry)); + { + INFO("an event changes state as it likes: its if goes: " + r.text); + REQUIRE(entry != std::string::npos); + CHECK(r.text.find("if (", entry) > r.text.find("state other;", entry)); + } } - template<> template<> - void allsloptimizer_object::test<31>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: llList2Key out of range folds to NULL_KEY on LSO and stays a call on Mono", "[allsloptimizer]") { - set_test_name("a key out of a list's range is NULL_KEY under LSO, written by its name; under Mono an empty key, which a key cannot be written as"); const std::string source = "default\n{\n state_entry()\n {\n llOwnerSay((string)llList2Key([1, 2], 5));\n }\n}\n"; ALLSLOptimizer::Options o = options(); o.target = ALLSLOptimizer::Target::LSO; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("LSO: NULL_KEY: " + r.text, r.text.find("llOwnerSay((string)NULL_KEY);") != std::string::npos); + UNSCOPED_INFO("LSO: NULL_KEY: " + r.text); + REQUIRE(r.text.find("llOwnerSay((string)NULL_KEY);") != std::string::npos); o.target = ALLSLOptimizer::Target::Mono; r = ALLSLOptimizer::run(source, o); - ensure("Mono: still the call: " + r.text, r.text.find("llList2Key(") != std::string::npos); + UNSCOPED_INFO("Mono: still the call: " + r.text); + REQUIRE(r.text.find("llList2Key(") != std::string::npos); } - template<> template<> - void allsloptimizer_object::test<32>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: unused world reads go but never move and ALLSLTraits describes each call", "[allsloptimizer]") { - set_test_name("a read of the world or the clock whose answer nobody takes goes, but is never read at another time; a function whose answer is its arguments' alone is pure"); const std::string source = "default\n{\n state_entry()\n {\n" " llGetPos();\n" " llSetPos(<1, 2, 3>);\n" @@ -1236,44 +1534,88 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = ALLSLOptimizer::Target::Mono; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); - ensure("the unused read goes: " + r.text, r.text.find(" llGetPos();") == std::string::npos); - ensure("the change stays: " + r.text, r.text.find("llSetPos(") != std::string::npos); - ensure("so does the unused local read from the world: " + r.text, r.text.find("vector p") == std::string::npos); - ensure("two reads of the clock are not put in another order: " + r.text, r.text.find("[llGetTime(), llGetTime()]") != std::string::npos); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); + UNSCOPED_INFO("the unused read goes: " + r.text); + REQUIRE(r.text.find(" llGetPos();") == std::string::npos); + UNSCOPED_INFO("the change stays: " + r.text); + REQUIRE(r.text.find("llSetPos(") != std::string::npos); + UNSCOPED_INFO("so does the unused local read from the world: " + r.text); + REQUIRE(r.text.find("vector p") == std::string::npos); + UNSCOPED_INFO("two reads of the clock are not put in another order: " + r.text); + REQUIRE(r.text.find("[llGetTime(), llGetTime()]") != std::string::npos); for (const char* pure : { "llRound", "llListFindList", "llListSort", "llDumpList2String", "llMD5String", "llAcos", "llAsin" }) { - ensure(std::string(pure) + " is pure", ALLSLTraits::pure(pure)); + UNSCOPED_INFO(std::string(pure) + " is pure"); + REQUIRE(ALLSLTraits::pure(pure)); + } + { + INFO("llGetPos is not"); + CHECK_FALSE(ALLSLTraits::pure("llGetPos")); + CHECK(ALLSLTraits::of("llGetPos")->mustUse); } - ensure("llGetPos is not", !ALLSLTraits::pure("llGetPos") && ALLSLTraits::of("llGetPos")->mustUse); // What SLua makes of each, from the same definitions. - ensure("llGetSubString takes its second and third from one in SLua's ll", - ALLSLTraits::of("llGetSubString")->slua == ALLSLTraits::SluaIndexArgs && ALLSLTraits::of("llGetSubString")->sluaIndexArgs == 0x6); - ensure("llListFindList answers an index, and SLua deprecates it", - ALLSLTraits::of("llListFindList")->slua == (ALLSLTraits::SluaIndexResult | ALLSLTraits::SluaDeprecated)); - ensure("llSameGroup answers a boolean", ALLSLTraits::of("llSameGroup")->slua == ALLSLTraits::SluaBool); - ensure("llSetTimerEvent is llcompat's alone", ALLSLTraits::of("llSetTimerEvent")->slua == ALLSLTraits::SluaRemoved); - ensure("llSay is as it was", ALLSLTraits::of("llSay")->slua == 0 && !ALLSLTraits::of("llSay")->sluaUse); - ensure("llAbs has math.abs in its stead", ALLSLTraits::of("llAbs")->sluaUse && std::string(ALLSLTraits::of("llAbs")->sluaUse) == "math.abs"); + { + 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); + } + UNSCOPED_INFO("llListFindList answers an index, and SLua deprecates it"); + CHECK(ALLSLTraits::of("llListFindList")->slua == (ALLSLTraits::SluaIndexResult | ALLSLTraits::SluaDeprecated)); + UNSCOPED_INFO("llSameGroup answers a boolean"); + CHECK(ALLSLTraits::of("llSameGroup")->slua == ALLSLTraits::SluaBool); + UNSCOPED_INFO("llSetTimerEvent is llcompat's alone"); + CHECK(ALLSLTraits::of("llSetTimerEvent")->slua == ALLSLTraits::SluaRemoved); + { + INFO("llSay is as it was"); + CHECK(ALLSLTraits::of("llSay")->slua == 0); + REQUIRE_FALSE(ALLSLTraits::of("llSay")->sluaUse); + } + { + INFO("llAbs has math.abs in its stead"); + REQUIRE(ALLSLTraits::of("llAbs")->sluaUse); + CHECK(std::string(ALLSLTraits::of("llAbs")->sluaUse) == "math.abs"); + } // How long each sleeps, under LSO and under Mono. - ensure("llSetPos sleeps 0.2 s", ALLSLTraits::of("llSetPos")->sleep == 0.2f && ALLSLTraits::of("llSetPos")->monoSleep == 0.2f); - ensure("llSetLinkPrimitiveParamsFast does not", ALLSLTraits::of("llSetLinkPrimitiveParamsFast")->sleep == 0.0f); - ensure("llTextBox sleeps under LSO alone", ALLSLTraits::of("llTextBox")->sleep == 1.0f && ALLSLTraits::of("llTextBox")->monoSleep == 0.0f); - ensure("llOpenFloater under Mono alone", ALLSLTraits::of("llOpenFloater")->sleep == 0.0f && ALLSLTraits::of("llOpenFloater")->monoSleep == 0.2f); + { + INFO("llSetPos sleeps 0.2 s"); + CHECK(ALLSLTraits::of("llSetPos")->sleep == 0.2f); + CHECK(ALLSLTraits::of("llSetPos")->monoSleep == 0.2f); + } + UNSCOPED_INFO("llSetLinkPrimitiveParamsFast does not"); + CHECK(ALLSLTraits::of("llSetLinkPrimitiveParamsFast")->sleep == 0.0f); + { + INFO("llTextBox sleeps under LSO alone"); + CHECK(ALLSLTraits::of("llTextBox")->sleep == 1.0f); + CHECK(ALLSLTraits::of("llTextBox")->monoSleep == 0.0f); + } + { + INFO("llOpenFloater under Mono alone"); + CHECK(ALLSLTraits::of("llOpenFloater")->sleep == 0.0f); + CHECK(ALLSLTraits::of("llOpenFloater")->monoSleep == 0.2f); + } // What item of the object's an argument names, by its LSL name or SLua's. using Item = ALLSLTraits::Item; - ensure("llPlaySound's first, a sound", ALLSLTraits::itemArg("llPlaySound", 0) == Item::Sound); - ensure("its second names nothing", ALLSLTraits::itemArg("llPlaySound", 1) == Item::None); - ensure("llGiveInventory's second, whatever it is", ALLSLTraits::itemArg("llGiveInventory", 1) == Item::Any); - ensure("llMakeFountain's seventh, a texture", ALLSLTraits::itemArg("llMakeFountain", 6) == Item::Texture); - ensure("SLua's ll.GetNotecardLine, a notecard", ALLSLTraits::itemArg("ll.GetNotecardLine", 0) == Item::Notecard); - ensure("llcompat's the same", ALLSLTraits::itemArg("llcompat.RezObject", 0) == Item::Object); - ensure("llSay names none", ALLSLTraits::itemArg("llSay", 1) == Item::None && ALLSLTraits::itemArg("ll.Say", 1) == Item::None); - } - template<> template<> - void allsloptimizer_object::test<33>() - { - set_test_name("a function returning a value goes in whatever its body: into what a declaration or an assignment sets, back out of a function returning the same, into a fresh local before the statement -- where nothing run before it would see a difference"); + UNSCOPED_INFO("llPlaySound's first, a sound"); + CHECK(ALLSLTraits::itemArg("llPlaySound", 0) == Item::Sound); + UNSCOPED_INFO("its second names nothing"); + CHECK(ALLSLTraits::itemArg("llPlaySound", 1) == Item::None); + UNSCOPED_INFO("llGiveInventory's second, whatever it is"); + CHECK(ALLSLTraits::itemArg("llGiveInventory", 1) == Item::Any); + UNSCOPED_INFO("llMakeFountain's seventh, a texture"); + CHECK(ALLSLTraits::itemArg("llMakeFountain", 6) == Item::Texture); + UNSCOPED_INFO("SLua's ll.GetNotecardLine, a notecard"); + CHECK(ALLSLTraits::itemArg("ll.GetNotecardLine", 0) == Item::Notecard); + UNSCOPED_INFO("llcompat's the same"); + CHECK(ALLSLTraits::itemArg("llcompat.RezObject", 0) == Item::Object); + { + INFO("llSay names none"); + CHECK(ALLSLTraits::itemArg("llSay", 1) == Item::None); + CHECK(ALLSLTraits::itemArg("ll.Say", 1) == Item::None); + } + } + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a value-returning function goes in whatever its body where order allows", "[allsloptimizer]") + { const std::string source = "integer g;\n" "integer f(integer k)\n{\n if (k) return k + g;\n g = 7;\n return 0;\n}\n" @@ -1289,41 +1631,60 @@ namespace tut " }\n}\n"; const ALLSLInliner::Result put = ALLSLInliner::run(source, { "f" }); auto has = [&put](const std::string& text) { return put.text.find(text) != std::string::npos; }; - ensure("the declaration's variable set by the body, its returns but the last jumps: " + put.text, - has("integer x;\n{\ninteger k = 1;") && has("{ x = k + g; jump _ret_") && has(" x = 0;")); - ensure("the assignment's too: " + put.text, has("integer k = 2;")); - ensure("a value nobody takes: the returns that change nothing go: " + put.text, has("integer k = 3;\n\n if (k) jump _ret_3;\n g = 7;")); + { + INFO("the declaration's variable set by the body, its returns but the last jumps: " + put.text); + CHECK(has("integer x;\n{\ninteger k = 1;")); + CHECK(has("{ x = k + g; jump _ret_")); + CHECK(has(" x = 0;")); + } + UNSCOPED_INFO("the assignment's too: " + put.text); + CHECK(has("integer k = 2;")); + UNSCOPED_INFO("a value nobody takes: the returns that change nothing go: " + put.text); + CHECK(has("integer k = 3;\n\n if (k) jump _ret_3;\n g = 7;")); // A label is one to an event: every block's end its own, however // many rounds made them. for (const char* label : { "@_ret_1;", "@_ret_2;", "@_ret_3;", "@_ret_4;", "@_ret_5;", "@_ret_6;" }) { const size_t first = put.text.find(label); - ensure(std::string("one ") + label + ": " + put.text, first == std::string::npos || put.text.find(label, first + 1) == std::string::npos); + UNSCOPED_INFO(std::string("one ") + label + ": " + put.text); + REQUIRE((first == std::string::npos || put.text.find(label, first + 1) == std::string::npos)); + } + UNSCOPED_INFO("g runs before f(4) and f writes g: the call stays: " + put.text); + CHECK(has("x = f(4) + g;")); + { + INFO("f(5) is the right operand and runs first: set before the statement: " + put.text); + CHECK(has("integer _r_")); + CHECK(has("x = g + _r_")); + } + UNSCOPED_INFO("a branch standing alone gets braces for the block: " + put.text); + CHECK((has("if (x) {") || has("if (x) {\n"))); + { + INFO("outer returns f's value as its own, then goes where it is called: " + put.text); + CHECK_FALSE(has("integer outer(integer k)")); + CHECK(has("integer k_1 = k;")); } - ensure("g runs before f(4) and f writes g: the call stays: " + put.text, has("x = f(4) + g;")); - ensure("f(5) is the right operand and runs first: set before the statement: " + put.text, has("integer _r_") && has("x = g + _r_")); - ensure("a branch standing alone gets braces for the block: " + put.text, has("if (x) {") || has("if (x) {\n")); - ensure("outer returns f's value as its own, then goes where it is called: " + put.text, !has("integer outer(integer k)") && has("integer k_1 = k;")); - ensure("f stays, one call of it standing: " + put.text, has("integer f(integer k)")); + UNSCOPED_INFO("f stays, one call of it standing: " + put.text); + CHECK(has("integer f(integer k)")); ALLSLService service; for (const ALScriptProblem& p : service.check(put.text)) { - ensure("what was written compiles: " + p.message + "\n" + put.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + put.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } ALLSLOptimizer::Options o = options(); o.inlining = true; o.inlineNames = { "f" }; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("the optimizer reads it: " + notes(r), r.optimized); + UNSCOPED_INFO("the optimizer reads it: " + notes(r)); + CHECK(r.optimized); for (const ALScriptProblem& p : service.check(r.text)) { - ensure("and what it wrote compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("and what it wrote compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } - template<> template<> - void allsloptimizer_object::test<34>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: calls in loop conditions and steps go in with loops rewritten as jumps", "[allsloptimizer]") { - set_test_name("a call in a loop's condition or a for's step goes too: a while or a for written as its label, if and jump back, a do's value set at the end of its body"); const std::string source = "integer g;\n" "integer next()\n{\n g = g + 1;\n return g;\n}\n" @@ -1337,16 +1698,36 @@ namespace tut " }\n}\n"; const ALLSLInliner::Result put = ALLSLInliner::run(source, { "next", "tick" }); auto has = [&put](const std::string& text) { return put.text.find(text) != std::string::npos; }; - ensure("every call went, and both functions: " + put.text, !has("next()") && !has("tick(") && !has("integer next()")); - ensure("the while and the for are labels and ifs: " + put.text, !has("while (next") && !has("for (") && has("@_loop_1;") && has("jump _loop_1;") && - has("@_loop_2;") && has("jump _loop_2;")); - ensure("the for's first part runs once, before its label: " + put.text, put.text.find("i = 0;") < put.text.find("@_loop_2;")); - ensure("its step's call goes as a statement's, the argument set in the call's order: " + put.text, has("integer i_1 = i++;")); - ensure("the do stays a do, braces round the if's branch for its value's local: " + put.text, has("if (g) {") && has("do {") && has("} while (_r_")); + { + INFO("every call went, and both functions: " + put.text); + CHECK_FALSE(has("next()")); + CHECK_FALSE(has("tick(")); + CHECK_FALSE(has("integer next()")); + } + { + INFO("the while and the for are labels and ifs: " + put.text); + CHECK_FALSE(has("while (next")); + CHECK_FALSE(has("for (")); + CHECK(has("@_loop_1;")); + CHECK(has("jump _loop_1;")); + CHECK(has("@_loop_2;")); + CHECK(has("jump _loop_2;")); + } + UNSCOPED_INFO("the for's first part runs once, before its label: " + put.text); + CHECK(put.text.find("i = 0;") < put.text.find("@_loop_2;")); + UNSCOPED_INFO("its step's call goes as a statement's, the argument set in the call's order: " + put.text); + CHECK(has("integer i_1 = i++;")); + { + INFO("the do stays a do, braces round the if's branch for its value's local: " + put.text); + CHECK(has("if (g) {")); + CHECK(has("do {")); + CHECK(has("} while (_r_")); + } ALLSLService service; for (const ALScriptProblem& p : service.check(put.text)) { - ensure("what was written compiles: " + p.message + "\n" + put.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + put.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } ALLSLOptimizer::Options o = options(); o.inlining = true; @@ -1355,17 +1736,17 @@ namespace tut { o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); for (const ALScriptProblem& p : service.check(r.text)) { - ensure("and what the optimizer wrote compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("and what the optimizer wrote compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } } - template<> template<> - void allsloptimizer_object::test<35>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a pure local read once moves to its read and a jump to the next line goes", "[allsloptimizer]") { - set_test_name("a local set from what changes nothing and read once goes where it is read, where nothing between writes what it reads; a jump that goes where running on goes goes"); const std::string source = wrap("integer g;\n", " integer a = llGetUnixTime();\n" " integer b = g * 2;\n" @@ -1390,27 +1771,42 @@ namespace tut " if (a) { @twice; g = 3; }\n" " llOwnerSay((string)(a + g));\n"); const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); auto has = [&r](const std::string& text) { return r.text.find(text) != std::string::npos; }; - ensure("b goes where it is read: " + r.text, !has("integer b") && has("llOwnerSay((string)(g * 2));")); - ensure("c's g is written between: it stays: " + r.text, has("integer c = -~g;")); - ensure("d is read twice: " + r.text, has("integer d = g * 3;")); - ensure("e is read round a loop: " + r.text, has("integer e = g * 4;")); - ensure("f is read in a branch, a statement of its own: " + r.text, has("integer f = g * 5;")); - ensure("h is read after a label: " + r.text, has("integer h = g * 6;")); - ensure("the read of the clock stays: " + r.text, has("integer a = llGetUnixTime();")); - ensure("a jump to the label next goes, and the label with it: " + r.text, !has("jump out;") && !has("@out;")); - ensure("a jump to a label whose name is twice in the event stays: " + r.text, has("jump twice;")); + { + INFO("b goes where it is read: " + r.text); + CHECK_FALSE(has("integer b")); + CHECK(has("llOwnerSay((string)(g * 2));")); + } + UNSCOPED_INFO("c's g is written between: it stays: " + r.text); + CHECK(has("integer c = -~g;")); + UNSCOPED_INFO("d is read twice: " + r.text); + CHECK(has("integer d = g * 3;")); + UNSCOPED_INFO("e is read round a loop: " + r.text); + CHECK(has("integer e = g * 4;")); + UNSCOPED_INFO("f is read in a branch, a statement of its own: " + r.text); + CHECK(has("integer f = g * 5;")); + UNSCOPED_INFO("h is read after a label: " + r.text); + CHECK(has("integer h = g * 6;")); + UNSCOPED_INFO("the read of the clock stays: " + r.text); + CHECK(has("integer a = llGetUnixTime();")); + { + INFO("a jump to the label next goes, and the label with it: " + r.text); + CHECK_FALSE(has("jump out;")); + CHECK_FALSE(has("@out;")); + } + UNSCOPED_INFO("a jump to a label whose name is twice in the event stays: " + r.text); + CHECK(has("jump twice;")); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } - template<> template<> - void allsloptimizer_object::test<36>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: nested blocks flatten and declarations move down to their first assignment", "[allsloptimizer]") { - set_test_name("a block in a block is flattened where its names are its own, and a declaration moves down to its first assignment where nothing between jumps or names it"); const std::string source = wrap("integer g;\n", " { llOwnerSay(\"a\"); }\n" " { integer u = llGetUnixTime(); llOwnerSay((string)(u + u)); }\n" @@ -1426,23 +1822,37 @@ namespace tut " @over;\n" " llOwnerSay((string)(y + y));\n"); const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); auto has = [&r](const std::string& text) { return r.text.find(text) != std::string::npos; }; - ensure("a block of one statement is that statement: " + r.text, has(" llOwnerSay(\"a\");") && !has("{\n llOwnerSay(\"a\");")); - ensure("one whose local is named nowhere else too: " + r.text, has(" integer u = llGetUnixTime();")); - ensure("two whose locals share a name stay blocks: " + r.text, has(" integer v = llGetUnixTime();")); - ensure("x declared where it is first set: " + r.text, !has("integer x;") && has("integer x = llGetUnixTime();")); - ensure("y not past the jump: " + r.text, has("integer y;") && has("y = llGetUnixTime();")); + { + INFO("a block of one statement is that statement: " + r.text); + CHECK(has(" llOwnerSay(\"a\");")); + CHECK_FALSE(has("{\n llOwnerSay(\"a\");")); + } + UNSCOPED_INFO("one whose local is named nowhere else too: " + r.text); + CHECK(has(" integer u = llGetUnixTime();")); + UNSCOPED_INFO("two whose locals share a name stay blocks: " + r.text); + CHECK(has(" integer v = llGetUnixTime();")); + { + INFO("x declared where it is first set: " + r.text); + CHECK_FALSE(has("integer x;")); + CHECK(has("integer x = llGetUnixTime();")); + } + { + INFO("y not past the jump: " + r.text); + CHECK(has("integer y;")); + CHECK(has("y = llGetUnixTime();")); + } ALLSLService service; for (const ALScriptProblem& p : service.check(r.text)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } - template<> template<> - void allsloptimizer_object::test<37>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: infinity reads back on every target and uncompilable output is refused", "[allsloptimizer]") { - set_test_name("infinity is written as a literal that reads back as it, on each target (O0g); what was written is checked, and refused where it does not compile"); const std::string source = "float foo = -2.0e+9999;\nfloat bar = 2.0e+9999;\n" "default\n{\n state_entry()\n {\n llSetPos(<-2.0e+9999, 2.0e+9999, 0>);\n" " llSetRot(<-2.0e+9999, 2.0e+9999, 0, 0>);\n llOwnerSay((string)(foo + bar));\n }\n}\n"; @@ -1451,28 +1861,35 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); - ensure("written as past the range: " + r.text, r.text.find("<-1.0e+999, 1.0e+999, 0") != std::string::npos); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); + UNSCOPED_INFO("written as past the range: " + r.text); + REQUIRE(r.text.find("<-1.0e+999, 1.0e+999, 0") != std::string::npos); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text, target != ALLSLOptimizer::Target::LSO)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } // The check a run makes of what it wrote, which gives the source // back where what was written does not compile. - ensure("a script that compiles passes", !ALLSLOptimizer::checkWritten(source).has_value()); + UNSCOPED_INFO("a script that compiles passes"); + REQUIRE_FALSE(ALLSLOptimizer::checkWritten(source).has_value()); const std::optional refused = ALLSLOptimizer::checkWritten("default { state_entry() { llOwnerSay(inf.0); } }\n"); - ensure("one that does not is refused", refused.has_value()); - ensure("as a warning with the parser's words", - refused->key == "OptimizerWroteUncompilable" && refused->severity == ALScriptProblem::Severity::Warning && !refused->args[0].empty()); + UNSCOPED_INFO("one that does not is refused"); + REQUIRE(refused.has_value()); + { + INFO("as a warning with the parser's words"); + REQUIRE(refused->key == "OptimizerWroteUncompilable"); + REQUIRE(refused->severity == ALScriptProblem::Severity::Warning); + REQUIRE_FALSE(refused->args[0].empty()); + } } - template<> template<> - void allsloptimizer_object::test<38>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a global constant is inlined only where that is cheaper on the target", "[allsloptimizer]") { - set_test_name("a global's value is written where it is read only where that costs less than the global, on each target"); const auto reads = [](const std::string& global, const std::string& read, int times) { std::string body; for (int i = 0; i < times; ++i) @@ -1492,35 +1909,48 @@ namespace tut // is not on LSO, nor on Mono unless its parts are whole. const std::string V = "vector V = <1.5, 2.5, 3.5>;"; const std::string W = "vector W = <1.0, 2.0, 3.0>;"; - ensure("a vector read once goes on LSO", !kept(ALLSLOptimizer::run(reads(V, "v += V;", 1), lso), "vector V")); - ensure("read three times it stays on LSO", kept(ALLSLOptimizer::run(reads(V, "v += V;", 3), lso), "vector V")); - ensure("and on Mono", kept(ALLSLOptimizer::run(reads(V, "v += V;", 3), mono), "vector V")); - ensure("but not whole on Mono", !kept(ALLSLOptimizer::run(reads(W, "v += W;", 3), mono), "vector W")); - ensure("and not on Luau, which keeps a constant once", !kept(ALLSLOptimizer::run(reads(V, "v += V;", 3), luau), "vector V")); + UNSCOPED_INFO("a vector read once goes on LSO"); + CHECK_FALSE(kept(ALLSLOptimizer::run(reads(V, "v += V;", 1), lso), "vector V")); + UNSCOPED_INFO("read three times it stays on LSO"); + CHECK(kept(ALLSLOptimizer::run(reads(V, "v += V;", 3), lso), "vector V")); + UNSCOPED_INFO("and on Mono"); + CHECK(kept(ALLSLOptimizer::run(reads(V, "v += V;", 3), mono), "vector V")); + UNSCOPED_INFO("but not whole on Mono"); + CHECK_FALSE(kept(ALLSLOptimizer::run(reads(W, "v += W;", 3), mono), "vector W")); + UNSCOPED_INFO("and not on Luau, which keeps a constant once"); + CHECK_FALSE(kept(ALLSLOptimizer::run(reads(V, "v += V;", 3), luau), "vector V")); // A float goes on LSO and Luau however often; on Mono, up to where // nine bytes a read come to the field. const std::string F = "float F = 2.5;"; - ensure("a float read twenty times goes on LSO", !kept(ALLSLOptimizer::run(reads(F, "f += F;", 20), lso), "float F")); - ensure("and on Luau", !kept(ALLSLOptimizer::run(reads(F, "f += F;", 20), luau), "float F")); - ensure("but stays on Mono", kept(ALLSLOptimizer::run(reads(F, "f += F;", 20), mono), "float F")); - ensure("which takes it read three times", !kept(ALLSLOptimizer::run(reads(F, "f += F;", 3), mono), "float F")); + UNSCOPED_INFO("a float read twenty times goes on LSO"); + CHECK_FALSE(kept(ALLSLOptimizer::run(reads(F, "f += F;", 20), lso), "float F")); + UNSCOPED_INFO("and on Luau"); + CHECK_FALSE(kept(ALLSLOptimizer::run(reads(F, "f += F;", 20), luau), "float F")); + UNSCOPED_INFO("but stays on Mono"); + CHECK(kept(ALLSLOptimizer::run(reads(F, "f += F;", 20), mono), "float F")); + UNSCOPED_INFO("which takes it read three times"); + CHECK_FALSE(kept(ALLSLOptimizer::run(reads(F, "f += F;", 3), mono), "float F")); // A string: Mono holds it once whatever; LSO writes it at each // place, which a long one read often does not pay for. const std::string S = "string S = \"a sentence long enough to matter\";"; - ensure("a long string read five times stays on LSO", kept(ALLSLOptimizer::run(reads(S, "s += S;", 5), lso), "string S")); - ensure("and goes on Mono", !kept(ALLSLOptimizer::run(reads(S, "s += S;", 5), mono), "string S")); + UNSCOPED_INFO("a long string read five times stays on LSO"); + CHECK(kept(ALLSLOptimizer::run(reads(S, "s += S;", 5), lso), "string S")); + UNSCOPED_INFO("and goes on Mono"); + CHECK_FALSE(kept(ALLSLOptimizer::run(reads(S, "s += S;", 5), mono), "string S")); // Folded into a larger value, a global is no read of it. const ALLSLOptimizer::Result folded = ALLSLOptimizer::run(reads(V, "v += V * 2;", 3), lso); - ensure("a product folds: " + folded.text, !kept(folded, "vector V") && folded.text.find("<3.0, 5.0, 7.0>") != std::string::npos); + { + INFO("a product folds: " + folded.text); + CHECK_FALSE(kept(folded, "vector V")); + REQUIRE(folded.text.find("<3.0, 5.0, 7.0>") != std::string::npos); + } } - template<> template<> - void allsloptimizer_object::test<39>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: inlining by cost inlines a many-called function only where it is smaller", "[allsloptimizer]") { - set_test_name("with inlining, a function called from several places goes where the target's compiler says that is smaller, and says so"); const std::string source = "say(integer n)\n{\n llOwnerSay(\"n=\" + (string)n);\n}\n" "tell(integer n)\n{\n if (n > 2)\n llOwnerSay(\"many: \" + (string)n + \" of them, which is a lot\");\n" " else\n llOwnerSay(\"a few: \" + (string)n);\n llSetText((string)n, <1.0, 0.5, 0.25>, 1.0);\n}\n" @@ -1533,23 +1963,40 @@ namespace tut o.target = target; o.inlineByCost = true; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); - ensure("the small one goes: " + r.text, r.text.find("say(") == std::string::npos); - ensure("the large one stays: " + r.text, r.text.find("tell(") != std::string::npos); - ensure("weighed", r.weight.compiled && r.weight.total > 0); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); + UNSCOPED_INFO("the small one goes: " + r.text); + REQUIRE(r.text.find("say(") == std::string::npos); + UNSCOPED_INFO("the large one stays: " + r.text); + REQUIRE(r.text.find("tell(") != std::string::npos); + { + INFO("weighed"); + REQUIRE(r.weight.compiled); + REQUIRE(r.weight.total > 0); + } const ALScriptProblem* chose = nullptr; for (const ALScriptProblem& p : r.problems) { chose = p.key == "InlinerChoseFunction" ? &p : chose; } - ensure("said: " + notes(r), chose && chose->args.size() == 4 && chose->args[0] == "say" && chose->args[1] == "3"); - ensure_equals("at the function", chose->line, 0); - ensure_equals("to its end", chose->endLine, 3); + { + INFO("said: " + notes(r)); + REQUIRE(chose); + REQUIRE(chose->args.size() == 4); + REQUIRE(chose->args[0] == "say"); + REQUIRE(chose->args[1] == "3"); + } + UNSCOPED_INFO("at the function"); + REQUIRE(chose->line == 0); + UNSCOPED_INFO("to its end"); + REQUIRE(chose->endLine == 3); o.inlineByCost = false; const ALLSLOptimizer::Result left = ALLSLOptimizer::run(source, o); - ensure("left alone without it: " + left.text, left.text.find("say(") != std::string::npos); - ensure("and nothing weighed", !left.weight.compiled); + UNSCOPED_INFO("left alone without it: " + left.text); + REQUIRE(left.text.find("say(") != std::string::npos); + UNSCOPED_INFO("and nothing weighed"); + REQUIRE_FALSE(left.weight.compiled); } // On Luau, where a call costs little, putting it in place comes to // a byte more: tried, and not kept. @@ -1558,13 +2005,15 @@ namespace tut const ALLSLOptimizer::Result weighed = ALLSLOptimizer::run(source, o); o.inlineByCost = false; const ALLSLOptimizer::Result unweighed = ALLSLOptimizer::run(source, o); - ensure("never larger for trying", weighed.weight.compiled && weighed.weight.total <= ALScriptWeigh::lslLuau(unweighed.text).total); + { + INFO("never larger for trying"); + CHECK(weighed.weight.compiled); + CHECK(weighed.weight.total <= ALScriptWeigh::lslLuau(unweighed.text).total); + } } - template<> template<> - void allsloptimizer_object::test<40>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: -1 tests and small steps and list elements take each target's smaller form", "[allsloptimizer]") { - set_test_name("-1 tests, one and two either way, increments and list elements are written as each target has them smaller, and compile"); const std::string source = kept(wrap("list l;\ninteger g;\n", " integer a = (integer)llFrand(9);\n integer b = (integer)llFrand(9);\n string s = llGetObjectName();\n" " integer found = llListFindList(l, [a]);\n" @@ -1591,57 +2040,85 @@ namespace tut ALLSLOptimizer::Options o = allsloptimizer_data::options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + allsloptimizer_data::notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + allsloptimizer_data::notes(r)); + REQUIRE(r.optimized); ALLSLService service; const ALScriptProblems said = service.check(r.text, target != ALLSLOptimizer::Target::LSO); for (const ALScriptProblem& p : said) { - ensure("no error in what was written: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("no error in what was written: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } return r.text; }; 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); - ensure("x != -1 as ~x, in a condition: " + lso, has(lso, "if (~a)")); - ensure("finds, both ways round, inside &&: " + lso, has(lso, "if (~found && ~llSubStringIndex(s, \"x\"))")); - ensure("a find < 0 as !~find, under a !", has(lso, "if (!!~found)") || has(lso, "if (~found)")); - ensure("x == -1 as !~x, as a value", has(lso, "g = !~a;")); - ensure("a find < 0 as !~find, as a value, a local set to a find", has(lso, "g = !~found;")); - ensure("x + 1 as -~x", has(lso, "g = -~a;")); - ensure("2 + x as -~-~x, the product bracketed", has(lso, "g = -~-~(a * b);")); - ensure("x - 1 as ~-x", has(lso, "g = ~-b;")); - ensure("x - 2 as ~-~-x, a minus bracketed after a minus", has(lso, "g = ~-~-(-a);")); - ensure("x != -1 as a value stays", has(lso, "g = a != -1;")); - ensure("an increment before", has(lso, "++a;")); - ensure("and a for's step, the loop a do on LSO, its first check known", has(lso, "--b")); - ensure("l + [a] as l + a", has(lso, "l = l + a;")); - ensure("l + [a, s] as l + a + s", has(lso, "l = l + a + s;")); - ensure("[s] + l as s + l", has(lso, "l = s + l;")); - ensure("l + (list)s as l + s", has(lso, "l = l + s;")); - ensure("l += [b] as l += b", has(lso, "l += b;")); - ensure("elements whose order would show stay together", !has(lso, "l + llFrand(1) + llFrand(2)")); + UNSCOPED_INFO("x != -1 as ~x, in a condition: " + lso); + CHECK(has(lso, "if (~a)")); + UNSCOPED_INFO("finds, both ways round, inside &&: " + lso); + CHECK(has(lso, "if (~found && ~llSubStringIndex(s, \"x\"))")); + UNSCOPED_INFO("a find < 0 as !~find, under a !"); + CHECK((has(lso, "if (!!~found)") || has(lso, "if (~found)"))); + UNSCOPED_INFO("x == -1 as !~x, as a value"); + CHECK(has(lso, "g = !~a;")); + UNSCOPED_INFO("a find < 0 as !~find, as a value, a local set to a find"); + CHECK(has(lso, "g = !~found;")); + UNSCOPED_INFO("x + 1 as -~x"); + CHECK(has(lso, "g = -~a;")); + UNSCOPED_INFO("2 + x as -~-~x, the product bracketed"); + CHECK(has(lso, "g = -~-~(a * b);")); + UNSCOPED_INFO("x - 1 as ~-x"); + CHECK(has(lso, "g = ~-b;")); + UNSCOPED_INFO("x - 2 as ~-~-x, a minus bracketed after a minus"); + CHECK(has(lso, "g = ~-~-(-a);")); + UNSCOPED_INFO("x != -1 as a value stays"); + CHECK(has(lso, "g = a != -1;")); + UNSCOPED_INFO("an increment before"); + CHECK(has(lso, "++a;")); + UNSCOPED_INFO("and a for's step, the loop a do on LSO, its first check known"); + CHECK(has(lso, "--b")); + UNSCOPED_INFO("l + [a] as l + a"); + CHECK(has(lso, "l = l + a;")); + UNSCOPED_INFO("l + [a, s] as l + a + s"); + CHECK(has(lso, "l = l + a + s;")); + UNSCOPED_INFO("[s] + l as s + l"); + CHECK(has(lso, "l = s + l;")); + UNSCOPED_INFO("l + (list)s as l + s"); + CHECK(has(lso, "l = l + s;")); + UNSCOPED_INFO("l += [b] as l += b"); + CHECK(has(lso, "l += b;")); + 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); - ensure("Mono: x != -1 as ~x: " + mono, has(mono, "if (~a)")); + UNSCOPED_INFO("Mono: x != -1 as ~x: " + mono); + CHECK(has(mono, "if (~a)")); // Mono's ldc.i4 is five bytes, -1 and 1 too. - ensure("Mono: x == -1 as !~x: " + mono, has(mono, "g = !~a;")); - ensure("Mono: x + 1 as -~x", has(mono, "g = -~a;")); - ensure("Mono: x - 1 as ~-x", has(mono, "g = ~-b;")); - ensure("Mono: ++a", has(mono, "++a;")); - ensure("Mono: what it wrote weighs what it says", mono.find("l = l") != std::string::npos); + UNSCOPED_INFO("Mono: x == -1 as !~x: " + mono); + CHECK(has(mono, "g = !~a;")); + UNSCOPED_INFO("Mono: x + 1 as -~x"); + CHECK(has(mono, "g = -~a;")); + UNSCOPED_INFO("Mono: x - 1 as ~-x"); + 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); const std::string luau = run(ALLSLOptimizer::Target::Luau); - ensure("Luau: x != -1 stays, ~ a library call there: " + luau, has(luau, "if (a != -1)")); - ensure("Luau: x - 1 stays", has(luau, "g = b - 1;")); - ensure("Luau: a++ stays", has(luau, "a++;")); - ensure("Luau: l + a", has(luau, "l = l + a;")); + UNSCOPED_INFO("Luau: x != -1 stays, ~ a library call there: " + luau); + CHECK(has(luau, "if (a != -1)")); + UNSCOPED_INFO("Luau: x - 1 stays"); + CHECK(has(luau, "g = b - 1;")); + UNSCOPED_INFO("Luau: a++ stays"); + CHECK(has(luau, "a++;")); + UNSCOPED_INFO("Luau: l + a"); + CHECK(has(luau, "l = l + a;")); } - template<> template<> - void allsloptimizer_object::test<41>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: on Mono list shapes are kept only where they pay for the helpers they need", "[allsloptimizer]") { - set_test_name("on Mono a list's shapes are weighed against the helpers they need, and kept only where they pay for them"); // A literal whose order would show, which stays, and a list added // to a list: the helpers a literal and a list's sum need are the // script's whatever happens to the rest. @@ -1656,23 +2133,36 @@ namespace tut // Once: adding a string bare needs a helper nothing else in the // script uses, which costs more than the one place saves. const ALLSLOptimizer::Result once = ALLSLOptimizer::run(adding(1), options()); - ensure("optimized: " + notes(once), once.optimized); - ensure("once, the literal stays: " + once.text, once.text.find("l += [llGetSubString") != std::string::npos); - ensure("weighed, as written", once.weight.compiled && once.weight.total == ALScriptWeigh::mono(once.text).total); + UNSCOPED_INFO("optimized: " + notes(once)); + CHECK(once.optimized); + UNSCOPED_INFO("once, the literal stays: " + once.text); + REQUIRE(once.text.find("l += [llGetSubString") != std::string::npos); + { + INFO("weighed, as written"); + CHECK(once.weight.compiled); + CHECK(once.weight.total == ALScriptWeigh::mono(once.text).total); + } // Often: the places pay for it. const ALLSLOptimizer::Result often = ALLSLOptimizer::run(adding(4), options()); - ensure("often, bare: " + often.text, often.text.find("l += llGetSubString") != std::string::npos && often.text.find("l += [") == std::string::npos); - ensure("weighed, as written", often.weight.compiled && often.weight.total == ALScriptWeigh::mono(often.text).total); + { + INFO("often, bare: " + often.text); + REQUIRE(often.text.find("l += llGetSubString") != std::string::npos); + REQUIRE(often.text.find("l += [") == std::string::npos); + } + { + INFO("weighed, as written"); + CHECK(often.weight.compiled); + CHECK(often.weight.total == ALScriptWeigh::mono(often.text).total); + } // LSO holds no helpers for a list: bare, once or often. ALLSLOptimizer::Options lso = options(); lso.target = ALLSLOptimizer::Target::LSO; - ensure("LSO, bare once", ALLSLOptimizer::run(adding(1), lso).text.find("l += llGetSubString") != std::string::npos); + UNSCOPED_INFO("LSO, bare once"); + REQUIRE(ALLSLOptimizer::run(adding(1), lso).text.find("l += llGetSubString") != std::string::npos); } - template<> template<> - void allsloptimizer_object::test<42>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a looked-up key given as NULL_KEY or a non-key becomes the empty string", "[allsloptimizer]") { - set_test_name("NULL_KEY, or no key at all, given to a library function for a key it looks up, is given as \"\"; not to llMessageLinked, nor for a string"); const std::string source = wrap("say(key k)\n{\n llOwnerSay((string)k);\n}\n", " key o = llGetOwnerKey(NULL_KEY);\n" " llListen(0, \"\", NULL_KEY, \"\");\n" @@ -1687,27 +2177,34 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); const auto has = [&r](const std::string& what) { return r.text.find(what) != std::string::npos; }; - ensure("a lookup's null key: " + r.text, has("llGetOwnerKey(\"\")")); - ensure("a listen's", has("llListen(0, \"\", \"\", \"\")")); - ensure("no key at all", has("llGiveInventory(\"\", \"x\")")); - ensure("a key stays", has("\"01234567-89ab-cdef-0123-456789abcdef\"")); - ensure("a linked message's stays, passed on as it came", has("llMessageLinked(LINK_SET, 0, \"\", NULL_KEY)")); - ensure("a string's stays", has("llOwnerSay(NULL_KEY)")); - ensure("a function of the script's has its own", !has("say(\"\")")); + UNSCOPED_INFO("a lookup's null key: " + r.text); + REQUIRE(has("llGetOwnerKey(\"\")")); + UNSCOPED_INFO("a listen's"); + REQUIRE(has("llListen(0, \"\", \"\", \"\")")); + UNSCOPED_INFO("no key at all"); + REQUIRE(has("llGiveInventory(\"\", \"x\")")); + UNSCOPED_INFO("a key stays"); + REQUIRE(has("\"01234567-89ab-cdef-0123-456789abcdef\"")); + UNSCOPED_INFO("a linked message's stays, passed on as it came"); + REQUIRE(has("llMessageLinked(LINK_SET, 0, \"\", NULL_KEY)")); + UNSCOPED_INFO("a string's stays"); + REQUIRE(has("llOwnerSay(NULL_KEY)")); + UNSCOPED_INFO("a function of the script's has its own"); + REQUIRE_FALSE(has("say(\"\")")); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text, target != ALLSLOptimizer::Target::LSO)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } } - template<> template<> - void allsloptimizer_object::test<43>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: unreachable functions and states and write-only variables are removed", "[allsloptimizer]") { - set_test_name("functions and states nothing that runs reaches go, cycles too; a variable set and never read goes, what it was set to still run"); const std::string source = "integer count;\ninteger shown;\nlist l;\n" "ping()\n{\n pong();\n}\npong()\n{\n ping();\n}\n" "leave()\n{\n if (llFrand(1.0) > 0.5)\n state away;\n}\n" @@ -1728,33 +2225,61 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); const auto has = [&r](const std::string& what) { return r.text.find(what) != std::string::npos; }; - ensure("functions calling only each other: " + r.text, !has("ping") && !has("pong")); - ensure("one nothing calls, and the states only it and they enter", !has("leave") && !has("state away") && !has("state back")); - ensure("a state entered stays", has("state on")); - ensure("a listen's handle never read: the call stays: " + r.text, has("llListen(0, \"\", \"\", \"\");") && !has("integer h")); - ensure("a global only set, and a read of the clock that set it", !has("count") && !has("llGetUnixTime();\n integer")); - ensure("a loop's counter never read: " + r.text, !has("i = 0") && !has("i++") && !has("integer i") && has("for (; ") && has(" > 5; )")); - ensure("a global read stays", has("shown = llGetUnixTime();")); - ensure("a comparison left empty turned around the other way: " + r.text, has("if (llGetObjectName() != \"on\")")); - ensure("anything else, what it negates bracketed; Luau's odd test a remainder", - has(target == ALLSLOptimizer::Target::Luau ? "if (!(llGetUnixTime() % 2))" : "if (!(llGetUnixTime() & 1))")); - ensure("said, as set and never read", std::any_of(r.problems.begin(), r.problems.end(), [](const ALScriptProblem& p) { + { + INFO("functions calling only each other: " + r.text); + REQUIRE_FALSE(has("ping")); + REQUIRE_FALSE(has("pong")); + } + { + INFO("one nothing calls, and the states only it and they enter"); + REQUIRE_FALSE(has("leave")); + REQUIRE_FALSE(has("state away")); + REQUIRE_FALSE(has("state back")); + } + UNSCOPED_INFO("a state entered stays"); + REQUIRE(has("state on")); + { + INFO("a listen's handle never read: the call stays: " + r.text); + REQUIRE(has("llListen(0, \"\", \"\", \"\");")); + REQUIRE_FALSE(has("integer h")); + } + { + INFO("a global only set, and a read of the clock that set it"); + REQUIRE_FALSE(has("count")); + REQUIRE_FALSE(has("llGetUnixTime();\n integer")); + } + { + INFO("a loop's counter never read: " + r.text); + REQUIRE_FALSE(has("i = 0")); + REQUIRE_FALSE(has("i++")); + REQUIRE_FALSE(has("integer i")); + REQUIRE(has("for (; ")); + REQUIRE(has(" > 5; )")); + } + UNSCOPED_INFO("a global read stays"); + REQUIRE(has("shown = llGetUnixTime();")); + UNSCOPED_INFO("a comparison left empty turned around the other way: " + r.text); + REQUIRE(has("if (llGetObjectName() != \"on\")")); + UNSCOPED_INFO("anything else, what it negates bracketed; Luau's odd test a remainder"); + REQUIRE(has(target == ALLSLOptimizer::Target::Luau ? "if (!(llGetUnixTime() % 2))" : "if (!(llGetUnixTime() & 1))")); + UNSCOPED_INFO("said, as set and never read"); + REQUIRE(std::any_of(r.problems.begin(), r.problems.end(), [](const ALScriptProblem& p) { return p.key == "OptimizerRemovedWriteOnly" && p.args.size() == 1 && p.args[0] == "count"; })); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text, target != ALLSLOptimizer::Target::LSO)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } } - template<> template<> - void allsloptimizer_object::test<44>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: comparisons and their negations take each target's smaller form", "[allsloptimizer]") { - set_test_name("comparisons as each target has them smaller: the ! of one, != and || where only truth counts, an if on ==, and inclusive against a constant"); const std::string source = wrap("", " list l = llParseString2List(llGetObjectName(), [\" \"], []);\n list m = llParseString2List(llGetObjectDesc(), [\" \"], []);\n" " integer a = (integer)llFrand(9);\n integer b = (integer)llFrand(9);\n" @@ -1776,33 +2301,51 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); const auto has = [&r](const std::string& what) { return r.text.find(what) != std::string::npos; }; const bool lso = target == ALLSLOptimizer::Target::LSO; const bool mono = target == ALLSLOptimizer::Target::Mono; - ensure("the ! of a comparison of integers: " + r.text, has("a >= b") && !has("!(a < b)")); - ensure("of strings, ==: " + r.text, has("s != \"x\"") && !has("!(s == \"x\")")); - ensure("not of lists, whose != is a length", has("!(l == m)")); - ensure("not of floats, which may be no number", has("!(f < g)")); - ensure("!= where only truth counts, a difference on Luau", has(lso || mono ? "if (a ^ b)" : "if (a - b)")); - ensure("an if on ==, its branches swapped", - lso || mono ? has("if (a ^ b)\n llOwnerSay(\"ne\");\n else\n llOwnerSay(\"eq\");") : has("if (!(a - b))")); - ensure("inclusive against a constant", has(mono ? "if (a > 4)" : "if (a >= 5)") && has(mono ? "if (b < 6)" : "if (b <= 5)")); - ensure("not past the largest integer", has("if (b <= 2147483647)")); - ensure("|| where only truth counts", has(mono ? "while (a | b)" : "while (a || b)")); - ensure("a string's length where only truth counts: " + r.text, has(lso ? "if (s != \"\")" : "if (llStringLength(s))")); + { + INFO("the ! of a comparison of integers: " + r.text); + REQUIRE(has("a >= b")); + REQUIRE_FALSE(has("!(a < b)")); + } + { + INFO("of strings, ==: " + r.text); + REQUIRE(has("s != \"x\"")); + REQUIRE_FALSE(has("!(s == \"x\")")); + } + UNSCOPED_INFO("not of lists, whose != is a length"); + REQUIRE(has("!(l == m)")); + UNSCOPED_INFO("not of floats, which may be no number"); + REQUIRE(has("!(f < g)")); + UNSCOPED_INFO("!= where only truth counts, a difference on Luau"); + REQUIRE(has(lso || mono ? "if (a ^ b)" : "if (a - b)")); + UNSCOPED_INFO("an if on ==, its branches swapped"); + REQUIRE((lso || mono ? has("if (a ^ b)\n llOwnerSay(\"ne\");\n else\n llOwnerSay(\"eq\");") : has("if (!(a - b))"))); + { + INFO("inclusive against a constant"); + REQUIRE(has(mono ? "if (a > 4)" : "if (a >= 5)")); + REQUIRE(has(mono ? "if (b < 6)" : "if (b <= 5)")); + } + UNSCOPED_INFO("not past the largest integer"); + REQUIRE(has("if (b <= 2147483647)")); + UNSCOPED_INFO("|| where only truth counts"); + REQUIRE(has(mono ? "while (a | b)" : "while (a || b)")); + UNSCOPED_INFO("a string's length where only truth counts: " + r.text); + REQUIRE(has(lso ? "if (s != \"\")" : "if (llStringLength(s))")); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text, !lso)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } } - template<> template<> - void allsloptimizer_object::test<45>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: list dumps and lone object details become casts as do Mono's whole floats", "[allsloptimizer]") { - set_test_name("a list dumped with nothing between as a cast, one object detail read as a string as a cast, and a whole float as the cast of an integer on Mono"); const std::string source = wrap("list L = [2.0];\n", " list l = llParseString2List(llGetObjectName(), [\" \"], []);\n" " string d = llDumpList2String(l, \"\");\n" @@ -1817,33 +2360,39 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); const auto has = [&r](const std::string& what) { return r.text.find(what) != std::string::npos; }; const bool lso = target == ALLSLOptimizer::Target::LSO; // On LSO and Mono; on Luau a place saves nothing. - ensure("dumped with nothing between: " + r.text, - target == ALLSLOptimizer::Target::Luau ? has("llDumpList2String(l, \"\")") : has("(string)l") && !has("llDumpList2String(l, \"\")")); - ensure("with something between, a dump", has("llDumpList2String(l, \", \")")); + UNSCOPED_INFO("dumped with nothing between: " + r.text); + REQUIRE((target == ALLSLOptimizer::Target::Luau ? has("llDumpList2String(l, \"\")") : has("(string)l") && !has("llDumpList2String(l, \"\")"))); + UNSCOPED_INFO("with something between, a dump"); + REQUIRE(has("llDumpList2String(l, \", \")")); // On LSO; on Mono a byte a place, against a helper the script may lack. - ensure("one detail: " + r.text, lso == (has("(string)llGetObjectDetails(llGetOwner(), [OBJECT_NAME])") || + UNSCOPED_INFO("one detail: " + r.text); + REQUIRE(lso == (has("(string)llGetObjectDetails(llGetOwner(), [OBJECT_NAME])") || has("(string)llGetObjectDetails(llGetOwner(), (list)OBJECT_NAME)"))); - ensure("two, a read", has("llList2String(llGetObjectDetails(llGetOwner(), ")); - ensure("the second, a read", has("OBJECT_NAME]), 1)") || has("(list)OBJECT_NAME), 1)")); + UNSCOPED_INFO("two, a read"); + REQUIRE(has("llList2String(llGetObjectDetails(llGetOwner(), ")); + UNSCOPED_INFO("the second, a read"); + REQUIRE((has("OBJECT_NAME]), 1)") || has("(list)OBJECT_NAME), 1)"))); const bool mono = target == ALLSLOptimizer::Target::Mono; - ensure("a whole float in a list: " + r.text, has(mono ? "((float)-3)" : "-3.0")); - ensure("not in a global's value", has("list L = [2.0];")); + UNSCOPED_INFO("a whole float in a list: " + r.text); + REQUIRE(has(mono ? "((float)-3)" : "-3.0")); + UNSCOPED_INFO("not in a global's value"); + REQUIRE(has("list L = [2.0];")); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text, !lso)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } } - template<> template<> - void allsloptimizer_object::test<46>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: code after a reset or any dead end goes and so do harmless empty handlers", "[allsloptimizer]") { - set_test_name("what follows a reset, an if none of whose ways goes on, or a loop that never ends never runs; an empty handler goes where having it changes nothing"); const std::string source = "integer a;\n" "both()\n{\n if (a)\n {\n llOwnerSay(\"a\");\n return;\n }\n else\n {\n llOwnerSay(\"b\");\n return;\n }\n" " llOwnerSay(\"after both\");\n}\n" @@ -1856,26 +2405,36 @@ namespace tut " collision_start(integer n)\n {\n }\n}\n" "state idle\n{\n timer()\n {\n }\n}\n"; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); const auto has = [&r](const std::string& what) { return r.text.find(what) != std::string::npos; }; - ensure("after an if none of whose ways goes on: " + r.text, !has("after both")); - ensure("after a loop that never ends", !has("after forever")); - ensure("after one jumped out of, it runs", has("after out")); - ensure("after a reset", !has("after reset") && has("llResetScript();")); - ensure("an empty state_entry goes", !has("state_entry")); - ensure("an empty collision stays, which having makes the object collide so", has("collision_start(integer n)")); - ensure("a state keeps its one handler", !has("state idle") || has("timer()")); + UNSCOPED_INFO("after an if none of whose ways goes on: " + r.text); + CHECK_FALSE(has("after both")); + UNSCOPED_INFO("after a loop that never ends"); + CHECK_FALSE(has("after forever")); + UNSCOPED_INFO("after one jumped out of, it runs"); + CHECK(has("after out")); + { + INFO("after a reset"); + CHECK_FALSE(has("after reset")); + CHECK(has("llResetScript();")); + } + UNSCOPED_INFO("an empty state_entry goes"); + CHECK_FALSE(has("state_entry")); + UNSCOPED_INFO("an empty collision stays, which having makes the object collide so"); + CHECK(has("collision_start(integer n)")); + UNSCOPED_INFO("a state keeps its one handler"); + CHECK((!has("state idle") || has("timer()"))); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } - template<> template<> - void allsloptimizer_object::test<47>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: llRound and llAcos and list dumps and finds fold where the answer is known", "[allsloptimizer]") { - set_test_name("more of the library folded where its answer is known: llRound, llAcos, llAsin, llDumpList2String, llListFindList, llMD5String"); const std::string source = wrap("", " llOwnerSay((string)[llRound(2.5), llRound(-2.5), llRound(0.49999997), llAcos(1.0), llAsin(2.0)]);\n" " llOwnerSay(llDumpList2String([1, 2.5, \"a\"], \"-\"));\n" @@ -1884,25 +2443,40 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = ALLSLOptimizer::Target::LSO; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); const auto has = [&r](const std::string& what) { return r.text.find(what) != std::string::npos; }; - ensure("llRound half up: " + r.text, !has("llRound(2.5)") && !has("llRound(-2.5)")); - ensure("llRound where single and double would differ, left", has("llRound(")); - ensure("llAcos folded, llAsin out of its range left", !has("llAcos") && has("llAsin(2.0)")); - ensure("a dump folded", has("\"1-2.500000-a\"")); - ensure("a find folded, and one not found", !has("llListFindList([1, \"a\", 2]") && !has("[3])")); - ensure("an empty list to find left, and a string no integer", has("llListFindList((list)1 + 2, [])") || has("llListFindList([1, 2], [])")); + { + INFO("llRound half up: " + r.text); + CHECK_FALSE(has("llRound(2.5)")); + CHECK_FALSE(has("llRound(-2.5)")); + } + UNSCOPED_INFO("llRound where single and double would differ, left"); + CHECK(has("llRound(")); + { + INFO("llAcos folded, llAsin out of its range left"); + CHECK_FALSE(has("llAcos")); + CHECK(has("llAsin(2.0)")); + } + UNSCOPED_INFO("a dump folded"); + CHECK(has("\"1-2.500000-a\"")); + { + INFO("a find folded, and one not found"); + CHECK_FALSE(has("llListFindList([1, \"a\", 2]")); + CHECK_FALSE(has("[3])")); + } + UNSCOPED_INFO("an empty list to find left, and a string no integer"); + CHECK((has("llListFindList((list)1 + 2, [])") || has("llListFindList([1, 2], [])"))); ALLSLService service; for (const ALScriptProblem& p : service.check(r.text, false)) { - ensure("what was written compiles: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("what was written compiles: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } } - template<> template<> - void allsloptimizer_object::test<48>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a call answering a longer string folds only where the answer is smaller", "[allsloptimizer]") { - set_test_name("a call answered with a string longer than it was given is folded where the answer is the smaller"); using Target = ALLSLOptimizer::Target; const auto weigh = [](Target target, const std::string& text) { return target == Target::LSO ? ALScriptWeigh::lso(text) : target == Target::Mono ? ALScriptWeigh::mono(text) : ALScriptWeigh::lslLuau(text); @@ -1913,31 +2487,39 @@ namespace tut o.target = target; const std::string source = allsloptimizer_data::wrap("", body); const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + allsloptimizer_data::notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + allsloptimizer_data::notes(r)); + REQUIRE(r.optimized); const ALScriptWeight before = weigh(target, source); const ALScriptWeight after = weigh(target, r.text); - ensure("no larger: " + r.text, before.compiled && after.compiled && after.total <= before.total); + { + INFO("no larger: " + r.text); + REQUIRE(before.compiled); + REQUIRE(after.compiled); + REQUIRE(after.total <= before.total); + } return r.text.find("llMD5String") == std::string::npos; }; const std::string hashOfX = "\"70c6ab518c36e18d9925c3c27c048b26\""; for (const Target target : { Target::LSO, Target::Mono, Target::Luau }) { - ensure("a hash of one character is smaller as the call", !folded(target, " llOwnerSay(llMD5String(\"x\", 0));\n")); + UNSCOPED_INFO("a hash of one character is smaller as the call"); + REQUIRE_FALSE(folded(target, " llOwnerSay(llMD5String(\"x\", 0));\n")); } // LSO writes the argument where it is used, as it would the answer. - ensure("a hash of a longer string is smaller as its answer on LSO", - folded(Target::LSO, " llOwnerSay(llMD5String(\"Hello, Avatar!\", 0));\n")); + UNSCOPED_INFO("a hash of a longer string is smaller as its answer on LSO"); + CHECK(folded(Target::LSO, " llOwnerSay(llMD5String(\"Hello, Avatar!\", 0));\n")); // Mono and Luau hold a string once: where a function holds the // answer already, it costs a load. const std::string held = " llOwnerSay(llMD5String(\"x\", 0));\n llOwnerSay(" + hashOfX + ");\n"; - ensure("held already on Mono", folded(Target::Mono, held)); - ensure("held already on Luau", folded(Target::Luau, held)); - ensure("LSO writes it again", !folded(Target::LSO, held)); + UNSCOPED_INFO("held already on Mono"); + CHECK(folded(Target::Mono, held)); + UNSCOPED_INFO("held already on Luau"); + CHECK(folded(Target::Luau, held)); + UNSCOPED_INFO("LSO writes it again"); + CHECK_FALSE(folded(Target::LSO, held)); } - template<> template<> - void allsloptimizer_object::test<49>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: same-named labels in a handler keep one short name when names shrink", "[allsloptimizer]") { - set_test_name("labels of one name in one handler keep one name when names are shortened, as a jump finds its label by name"); const std::string source = "default\n{\n state_entry()\n {\n" " if (1)\n {\n jump there;\n @there;\n }\n" " if (0)\n {\n jump there;\n @there;\n llOwnerSay(\"reached\");\n }\n" @@ -1949,9 +2531,12 @@ namespace tut o.target = target; o.shrinknames = true; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized, not refused: " + notes(r), r.optimized); - ensure("the label jumped to from the first block is still there: " + r.text, r.text.find("reached") != std::string::npos); - ensure("renamed: " + r.text, r.text.find("there") == std::string::npos); + UNSCOPED_INFO("optimized, not refused: " + notes(r)); + REQUIRE(r.optimized); + UNSCOPED_INFO("the label jumped to from the first block is still there: " + r.text); + REQUIRE(r.text.find("reached") != std::string::npos); + UNSCOPED_INFO("renamed: " + r.text); + REQUIRE(r.text.find("there") == std::string::npos); // In state_entry, every jump and every label one name. const size_t entry = r.text.find("state_entry"); const size_t touch = r.text.find("touch_start"); @@ -1966,13 +2551,12 @@ namespace tut names.insert(body.substr(from, body.find(';', from) - from)); } } - ensure_equals("one name in state_entry: " + r.text, names.size(), size_t(1)); + UNSCOPED_INFO("one name in state_entry: " + r.text); + REQUIRE(names.size() == size_t(1)); } } - template<> template<> - void allsloptimizer_object::test<50>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: names the target keeps shrink first and Mono names a state for a held string", "[allsloptimizer]") { - set_test_name("names shortened first where the target's code keeps them, and a state named for a string the script holds on Mono"); const std::string source = "integer counted;\n" "default\n{\n touch_start(integer n)\n {\n" " integer busy = n;\n busy = busy * busy + busy;\n busy = busy - busy / 3;\n" @@ -1983,7 +2567,8 @@ namespace tut o.target = target; o.shrinknames = true; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + allsloptimizer_data::notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + allsloptimizer_data::notes(r)); + REQUIRE(r.optimized); return r; }; const auto nameOf = [](const ALLSLOptimizer::Result& r, const std::string& name) { @@ -1993,26 +2578,35 @@ namespace tut // Mono keeps a global's name in its field and where it is used; a // local's nowhere. const ALLSLOptimizer::Result mono = run(ALLSLOptimizer::Target::Mono); - ensure("the state takes the string it can share: " + mono.text, nameOf(mono, "waiting") == "Q"); - ensure("the global before the busier local: " + mono.text, nameOf(mono, "counted").size() == 1 && nameOf(mono, "counted") < nameOf(mono, "busy")); + UNSCOPED_INFO("the state takes the string it can share: " + mono.text); + CHECK(nameOf(mono, "waiting") == "Q"); + { + INFO("the global before the busier local: " + mono.text); + REQUIRE(nameOf(mono, "counted").size() == 1); + CHECK((nameOf(mono, "counted") < nameOf(mono, "busy"))); + } // LSO keeps no names: the most used is named first, the text's // shortest. const ALLSLOptimizer::Result lso = run(ALLSLOptimizer::Target::LSO); - ensure("on LSO the busiest first: " + lso.text, nameOf(lso, "busy") == "a"); - ensure("and no string taken for a state: " + lso.text, nameOf(lso, "waiting") != "Q"); + UNSCOPED_INFO("on LSO the busiest first: " + lso.text); + CHECK(nameOf(lso, "busy") == "a"); + UNSCOPED_INFO("and no string taken for a state: " + lso.text); + CHECK(nameOf(lso, "waiting") != "Q"); const ALScriptWeight named = ALScriptWeigh::mono(mono.text); ALLSLOptimizer::Options plain = options(); plain.shrinknames = true; plain.target = ALLSLOptimizer::Target::LSO; const ALScriptWeight asLso = ALScriptWeigh::mono(ALLSLOptimizer::run(source, plain).text); - ensure("smaller on Mono than named for LSO", named.compiled && asLso.compiled && named.total < asLso.total); + { + INFO("smaller on Mono than named for LSO"); + CHECK(named.compiled); + CHECK(asLso.compiled); + CHECK(named.total < asLso.total); + } } - template<> template<> - void allsloptimizer_object::test<51>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a repeated pure call is held in a local where no larger and safe (L12)", "[allsloptimizer]") { - set_test_name("a pure call made again, given what nothing between changes, is kept in a local where the target has that no larger (L12); " - "not across a write or a label, nor begun where its statement may not come to it"); ALLSLOptimizer::Options lso = options(); lso.target = ALLSLOptimizer::Target::LSO; const std::string head = "default\n{\n touch_start(integer n)\n {\n string s = llDetectedName(0);\n"; @@ -2024,43 +2618,53 @@ namespace tut const std::string three = head + " llOwnerSay(llToUpper(s));\n llSay(0, llToUpper(s));\n llWhisper(0, llToUpper(s));\n" + tail; ALLSLOptimizer::Result r = ALLSLOptimizer::run(three, lso); - ensure("optimized: " + notes(r), r.optimized); - ensure_equals("kept", body(r), " string toUpper = llToUpper(s);\n llOwnerSay(toUpper);\n llSay(0, toUpper);\n llWhisper(0, toUpper);\n" + tail); - ensure("noted: " + notes(r), has(r, "kept llToUpper(s) in the local toUpper, which its 3 places read")); + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); + UNSCOPED_INFO("kept"); + CHECK(body(r) == " string toUpper = llToUpper(s);\n llOwnerSay(toUpper);\n llSay(0, toUpper);\n llWhisper(0, toUpper);\n" + tail); + UNSCOPED_INFO("noted: " + notes(r)); + CHECK(has(r, "kept llToUpper(s) in the local toUpper, which its 3 places read")); // On Mono a call given one variable is smaller than a local. r = ALLSLOptimizer::run(three, options()); - ensure("not on Mono: " + r.text, r.text.find("toUpper") == std::string::npos); + UNSCOPED_INFO("not on Mono: " + r.text); + REQUIRE(r.text.find("toUpper") == std::string::npos); // A place in a loop after one its statement always comes to. r = ALLSLOptimizer::run(head + " llSay(0, llToUpper(s));\n integer i;\n for (i = 0; i < n; ++i)\n llSay(i, llToUpper(s));\n" + tail, lso); - ensure("into the loop: " + r.text, r.text.find("string toUpper = llToUpper(s);\n llSay(0, toUpper);") != std::string::npos && - r.text.find("llSay(i, toUpper);") != std::string::npos); + { + INFO("into the loop: " + r.text); + REQUIRE(r.text.find("string toUpper = llToUpper(s);\n llSay(0, toUpper);") != std::string::npos); + REQUIRE(r.text.find("llSay(i, toUpper);") != std::string::npos); + } // The outermost call, and the call it is given with it. r = ALLSLOptimizer::run(head + " llSay(0, llGetSubString(llToUpper(s), 0, 2));\n llSay(1, llGetSubString(llToUpper(s), 0, 2));\n" + tail, lso); - ensure_equals("outermost", body(r), + UNSCOPED_INFO("outermost"); + CHECK(body(r) == " string getSubString = llGetSubString(llToUpper(s), 0, 2);\n llSay(0, getSubString);\n llSay(1, getSubString);\n" + tail); // A name the script has is not taken again. r = ALLSLOptimizer::run(head + " string toUpper = llDetectedName(1);\n llSay(0, toUpper);\n llSay(0, llToUpper(s));\n llSay(1, llToUpper(s));\n" + tail, lso); - ensure("numbered: " + r.text, r.text.find("string toUpper2 = llToUpper(s);") != std::string::npos); + UNSCOPED_INFO("numbered: " + r.text); + REQUIRE(r.text.find("string toUpper2 = llToUpper(s);") != std::string::npos); // What it is given written between: two places, each alone. const std::string written = head + " llSay(0, llToUpper(s));\n s = llDetectedName(1);\n llSay(0, llToUpper(s));\n llSay(0, s);\n" + tail; r = ALLSLOptimizer::run(written, lso); - ensure("not across a write: " + r.text, r.text.find("toUpper") == std::string::npos); + UNSCOPED_INFO("not across a write: " + r.text); + REQUIRE(r.text.find("toUpper") == std::string::npos); // A label between, which a jump could bring the code in by. r = ALLSLOptimizer::run(head + " llSay(0, llToUpper(s));\n @again;\n llSay(0, llToUpper(s));\n if (llFrand(1) < 0.5)\n jump again;\n" + tail, lso); - ensure("not across a label: " + r.text, r.text.find("toUpper") == std::string::npos); + UNSCOPED_INFO("not across a label: " + r.text); + REQUIRE(r.text.find("toUpper") == std::string::npos); // The first place in a branch: kept, it would run where it did not. r = ALLSLOptimizer::run(head + " if (n)\n llSay(0, llToUpper(s));\n llSay(0, llToUpper(s));\n" + tail, lso); - ensure("not begun in a branch: " + r.text, r.text.find("toUpper") == std::string::npos); + UNSCOPED_INFO("not begun in a branch: " + r.text); + REQUIRE(r.text.find("toUpper") == std::string::npos); } - template<> template<> - void allsloptimizer_object::test<52>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: dot and cross products of constant vectors fold as the VM computes them", "[allsloptimizer]") { - set_test_name("two vectors' dot and cross products fold to what the VM gives: each part by the same part, and a vector by itself crossed to nothing"); // Each may be set again before it is read, so that its fold shows // in its declaration. const std::string source = wrap("", " float d = <1, 3, -5> * <4, -2, -1>;\n vector c = <1.0, 2.0, 3.0> % <4.0, 5.0, 6.0>;\n" @@ -2072,66 +2676,81 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); - ensure("dot: " + r.text, about(numbers(r.text, "float d"), { 3.f })); - ensure("cross: " + r.text, about(numbers(r.text, "vector c"), { -3.f, 6.f, -3.f })); - ensure("crossed by itself: " + r.text, about(numbers(r.text, "vector z"), { 0.f, 0.f, 0.f })); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); + UNSCOPED_INFO("dot: " + r.text); + REQUIRE(about(numbers(r.text, "float d"), { 3.f })); + UNSCOPED_INFO("cross: " + r.text); + REQUIRE(about(numbers(r.text, "vector c"), { -3.f, 6.f, -3.f })); + UNSCOPED_INFO("crossed by itself: " + r.text); + REQUIRE(about(numbers(r.text, "vector z"), { 0.f, 0.f, 0.f })); } } - template<> template<> - void allsloptimizer_object::test<53>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a local's constant propagates to reads every path agrees on (L15)", "[allsloptimizer]") { - set_test_name("a local set to a constant is that constant where it is read before it is set again, on every way there (L15); " - "not where the ways disagree, in a loop that sets it, past a label, nor after a write in the same statement"); const std::string head = "default\n{\n touch_start(integer n)\n {\n"; const std::string tail = " }\n}\n"; ALLSLOptimizer::Result r = ALLSLOptimizer::run(head + " integer x = 5;\n llSay(0, (string)x);\n x = 7;\n llSay(0, (string)x);\n n = 3;\n llSay(n, \"n\");\n" + tail, options()); - ensure("optimized: " + notes(r), r.optimized); - ensure_equals("each read its value, and the variable gone", r.text, + UNSCOPED_INFO("optimized: " + notes(r)); + CHECK(r.optimized); + UNSCOPED_INFO("each read its value, and the variable gone"); + CHECK(r.text == head + " llSay(0, \"5\");\n llSay(0, \"7\");\n llSay(3, \"n\");\n" + tail); - ensure("noted: " + notes(r), has(r, "folded (string)x to \"7\"") && has(r, "inlined n to 3") && has(r, "removed x, which is set and never read")); + { + INFO("noted: " + notes(r)); + CHECK(has(r, "folded (string)x to \"7\"")); + CHECK(has(r, "inlined n to 3")); + CHECK(has(r, "removed x, which is set and never read")); + } // The ways of an if: what both say goes on, what they do not stops. r = ALLSLOptimizer::run(head + " integer x = 1;\n integer y = 1;\n if (n)\n {\n x = 2;\n llSay(0, \"a\");\n }\n" " else\n {\n llSay(0, \"b\");\n }\n llSay(x, (string)y);\n x = y = 0;\n" + tail, options()); - ensure("agreed: " + r.text, r.text.find("llSay(x, \"1\");") != std::string::npos); + UNSCOPED_INFO("agreed: " + r.text); + REQUIRE(r.text.find("llSay(x, \"1\");") != std::string::npos); // A way that returns says nothing of what comes after. r = ALLSLOptimizer::run(head + " integer x = 1;\n if (n)\n {\n x = 2;\n return;\n }\n llSay(x, \"c\");\n x = 0;\n" + tail, options()); - ensure("a way that ends: " + r.text, r.text.find("llSay(1, \"c\");") != std::string::npos); + UNSCOPED_INFO("a way that ends: " + r.text); + REQUIRE(r.text.find("llSay(1, \"c\");") != std::string::npos); // A loop that sets it: not inside, nor after. r = ALLSLOptimizer::run(head + " integer i = 0;\n while (i < n)\n {\n llSay(i, \"d\");\n ++i;\n }\n llSay(i, \"e\");\n" + tail, options()); - ensure("in a loop: " + r.text, r.text.find("llSay(i, \"d\");") != std::string::npos && r.text.find("llSay(i, \"e\");") != std::string::npos); + { + INFO("in a loop: " + r.text); + REQUIRE(r.text.find("llSay(i, \"d\");") != std::string::npos); + REQUIRE(r.text.find("llSay(i, \"e\");") != std::string::npos); + } // Past a label, which a jump may come to with anything. r = ALLSLOptimizer::run(head + " integer x = 1;\n @top;\n llSay(x, \"f\");\n x = 2;\n if (llFrand(1) < 0.5)\n jump top;\n" + tail, options()); - ensure("past a label: " + r.text, r.text.find("llSay(x, \"f\");") != std::string::npos); + UNSCOPED_INFO("past a label: " + r.text); + REQUIRE(r.text.find("llSay(x, \"f\");") != std::string::npos); // A loop holding a label forgets everything as it begins. r = ALLSLOptimizer::run(head + " integer x = 1;\n while (llFrand(1) < 0.5)\n {\n @in;\n llSay(x, \"g\");\n }\n" " x = 2;\n if (llFrand(1) < 0.5)\n jump in;\n" + tail, options()); - ensure("a loop with a label: " + r.text, r.text.find("llSay(x, \"g\");") != std::string::npos); + UNSCOPED_INFO("a loop with a label: " + r.text); + REQUIRE(r.text.find("llSay(x, \"g\");") != std::string::npos); // LSL runs a binary operator's right side first: the read on the // left comes after the write. r = ALLSLOptimizer::run(head + " integer x = 1;\n llSay(0, (string)(x + (x = 3)));\n llSay(x, \"h\");\n" + tail, options()); - ensure("after a write in the statement: " + r.text, r.text.find("(string)(x + (x = 3))") != std::string::npos); + UNSCOPED_INFO("after a write in the statement: " + r.text); + REQUIRE(r.text.find("(string)(x + (x = 3))") != std::string::npos); // A read left where another read of the variable is not known: a // literal in place of a read is no smaller, and the variable stays. r = ALLSLOptimizer::run(head + " string s = \"a long string of text\";\n llSay(0, s);\n if (n)\n s = llDetectedName(0);\n llSay(1, s);\n" + tail, options()); - ensure("not one read alone: " + r.text, r.text.find("llSay(0, s);") != std::string::npos); + UNSCOPED_INFO("not one read alone: " + r.text); + REQUIRE(r.text.find("llSay(0, s);") != std::string::npos); } - template<> template<> - void allsloptimizer_object::test<54>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: comparisons settle by the ranges of their sides (L16)", "[allsloptimizer]") { - set_test_name("a comparison settled by what each side can be (L16): a count never below nought, a find never below -1, llFrand of a positive " - "magnitude, a truth 1 or 0, a type within its constants; not where the ranges meet, nor where a side changes something"); const std::string source = "integer said() { llOwnerSay(\"said\"); return 1; }\n" "default\n{\n touch_start(integer n)\n {\n" " list l = llParseString2List(llDetectedName(0), [\" \"], []);\n" @@ -2151,24 +2770,40 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); const auto said = [&r](const char* what) { return r.text.find(std::string("llSay(0, \"") + what + "\")") != std::string::npos; }; - ensure("a count below nought never: " + r.text, !said("a")); - ensure("a count's local nought or more always: " + r.text, said("b") && r.text.find("count") == std::string::npos); - ensure("llFrand of ten never below nought: " + r.text, !said("c")); - ensure("a find above -2 always: " + r.text, said("d") && r.text.find("llListFindList") == std::string::npos); - ensure("a type past TYPE_ROTATION never: " + r.text, !said("e")); - ensure("a truth never 2: " + r.text, !said("f")); - ensure("where the ranges meet, kept: " + r.text, said("g") && said("h")); - ensure("where a side changes something, kept: " + r.text, said("i")); - ensure("noted: " + notes(r), has(r, "settled llGetListLength(l) < 0 to 0") || has(r, "settled (l != []) < 0 to 0") || has(r, "settled l != [] < 0 to 0")); - } - } - template<> template<> - void allsloptimizer_object::test<55>() - { - set_test_name("the folding arithmetic is IEEE's whatever the build's float flags: llVecNorm folds where dividing by the length and " - "multiplying by its reciprocal agree, as LLVector3 does, and not where they differ; a negative zero keeps its sign"); + UNSCOPED_INFO("a count below nought never: " + r.text); + REQUIRE_FALSE(said("a")); + { + INFO("a count's local nought or more always: " + r.text); + REQUIRE(said("b")); + REQUIRE(r.text.find("count") == std::string::npos); + } + UNSCOPED_INFO("llFrand of ten never below nought: " + r.text); + REQUIRE_FALSE(said("c")); + { + INFO("a find above -2 always: " + r.text); + REQUIRE(said("d")); + REQUIRE(r.text.find("llListFindList") == std::string::npos); + } + UNSCOPED_INFO("a type past TYPE_ROTATION never: " + r.text); + REQUIRE_FALSE(said("e")); + UNSCOPED_INFO("a truth never 2: " + r.text); + REQUIRE_FALSE(said("f")); + { + INFO("where the ranges meet, kept: " + r.text); + REQUIRE(said("g")); + REQUIRE(said("h")); + } + UNSCOPED_INFO("where a side changes something, kept: " + r.text); + REQUIRE(said("i")); + UNSCOPED_INFO("noted: " + notes(r)); + REQUIRE((has(r, "settled llGetListLength(l) < 0 to 0") || has(r, "settled (l != []) < 0 to 0") || has(r, "settled l != [] < 0 to 0"))); + } + } + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: folding is IEEE's: llVecNorm only where both ways agree and -0 keeps its sign", "[allsloptimizer]") + { // Each may be set again before it is read, so that its fold shows // in its declaration. const std::string source = wrap("", " vector a = llVecNorm(<0.75, 3, 1>);\n vector b = llVecNorm(<0, 3, 4>);\n" @@ -2180,25 +2815,34 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); // 0.75 / 3.25 is 0.230769232 as a single, 0.75 * (1 / 3.25) // 0.230769247: which the VM gives is not known, so it is left. // Built with the viewer's float flags, the compiler made the // division a product with the reciprocal, and this folded. - ensure("left where the two differ: " + r.text, r.text.find("vector a = llVecNorm(<0.75, ") != std::string::npos); + UNSCOPED_INFO("left where the two differ: " + r.text); + REQUIRE(r.text.find("vector a = llVecNorm(<0.75, ") != std::string::npos); const std::vector b = numbers(r.text, "vector b"); - ensure("folded where they agree: " + r.text, b.size() == 3 && b[0] == 0.0f && b[1] == 3.0f / 5.0f && b[2] == 4.0f / 5.0f); - ensure("a length both ways the same: " + r.text, numbers(r.text, "float m") == std::vector{ 5.0f }); + { + INFO("folded where they agree: " + r.text); + REQUIRE(b.size() == 3); + REQUIRE(b[0] == 0.0f); + REQUIRE(b[1] == 3.0f / 5.0f); + REQUIRE(b[2] == 4.0f / 5.0f); + } + UNSCOPED_INFO("a length both ways the same: " + r.text); + REQUIRE(numbers(r.text, "float m") == std::vector{ 5.0f }); const size_t at = r.text.find("list l = "); - ensure("a negative zero keeps its sign: " + r.text, - at != std::string::npos && r.text.substr(at, r.text.find('\n', at) - at).find("-0.0") != std::string::npos); + { + INFO("a negative zero keeps its sign: " + r.text); + REQUIRE(at != std::string::npos); + REQUIRE(r.text.substr(at, r.text.find('\n', at) - at).find("-0.0") != std::string::npos); + } } } - template<> template<> - void allsloptimizer_object::test<56>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: one-bit tests of one value merge into one mask test (L18)", "[allsloptimizer]") { - set_test_name("bit tests of one value merged (L18): one-bit tests all asked as !~(x | ~mask), all clear as !(x & mask), an | of them as " - "x & mask, an || of them so where only truth counts; not of two values, of a test of more than one bit, nor where x changes"); const std::string source = "integer said() { llOwnerSay(\"said\"); return 4; }\n" "default\n{\n touch_start(integer n)\n {\n" " integer m = llGetLinkNumber();\n" @@ -2217,26 +2861,42 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); const auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; // -13 as its cast on Mono (ALLSLCosts::castForNegative). - ensure("all set: " + r.text, has("if (!~(n | -13))") || has("if (!~(n | ((integer)-13)))")); + UNSCOPED_INFO("all set: " + r.text); + REQUIRE((has("if (!~(n | -13))") || has("if (!~(n | ((integer)-13)))"))); // v and w are read once, and their values go where they are read. - ensure("three, a value: " + r.text, has("llSay(!~(n | -14), ") || has("llSay(!~(n | ((integer)-14)), ")); - ensure("all clear: " + r.text, has("if (!(n & 12))")); - ensure("any, where only truth counts: " + r.text, has("if (n & 20)")); - ensure("an | of them: " + r.text, has("(string)(n & 15)")); - ensure("two values left: " + r.text, has("(n & 4)") && has("(m & 8)")); - ensure("more than one bit left: " + r.text, has("(n & 6)") && has("(n & 8)")); - ensure("what changes left: " + r.text, has("(said() & 4)") && has("(said() & 8)")); - ensure("noted: " + notes(r), allsloptimizer_data::has(r, "wrote (n & 4) && (n & 8) as !~(n | -13)")); + UNSCOPED_INFO("three, a value: " + r.text); + REQUIRE((has("llSay(!~(n | -14), ") || has("llSay(!~(n | ((integer)-14)), "))); + UNSCOPED_INFO("all clear: " + r.text); + REQUIRE(has("if (!(n & 12))")); + UNSCOPED_INFO("any, where only truth counts: " + r.text); + REQUIRE(has("if (n & 20)")); + UNSCOPED_INFO("an | of them: " + r.text); + REQUIRE(has("(string)(n & 15)")); + { + INFO("two values left: " + r.text); + REQUIRE(has("(n & 4)")); + REQUIRE(has("(m & 8)")); + } + { + INFO("more than one bit left: " + r.text); + REQUIRE(has("(n & 6)")); + REQUIRE(has("(n & 8)")); + } + { + INFO("what changes left: " + r.text); + REQUIRE(has("(said() & 4)")); + REQUIRE(has("(said() & 8)")); + } + UNSCOPED_INFO("noted: " + notes(r)); + REQUIRE(allsloptimizer_data::has(r, "wrote (n & 4) && (n & 8) as !~(n | -13)")); } } - template<> template<> - void allsloptimizer_object::test<57>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: parsing and CSV and sorting and rotations fold where both VMs agree (L17)", "[allsloptimizer]") { - set_test_name("more of the library folded where the two VMs cannot part (L17): parsing, CSV and sorting where no edge is in play; a " - "rotation between vectors, the angle between rotations and a rotation from axes where every way of working it out agrees"); // Each may be set again before it is read, so that its fold shows // in its declaration. const std::string source = wrap("", " list p = llParseString2List(\"a,b;;c\", [\",\"], [\";\"]);\n" @@ -2262,34 +2922,47 @@ namespace tut ALLSLOptimizer::Options o = options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + notes(r)); + REQUIRE(r.optimized); const auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; - ensure("split, spacers kept: " + r.text, has("list p = [\"a\", \"b\", \";\", \";\", \"c\"];")); - ensure("split, empties kept: " + r.text, has("list k = [\"a\", \"\", \"b\", \"\"];")); - ensure("a separator the start of another: left: " + r.text, has("list x = llParseString2List(")); - ensure("CSV: " + r.text, has("list v = [\"a\", \"b\", \"c\"];")); - ensure("a value starting with a space: left: " + r.text, has("list w = llCSV2List(")); - ensure("sorted by stride: " + r.text, has("list s = [1, \"a\", 2, \"b\", 3, \"c\"];")); - ensure("sorted down: " + r.text, has("list t = [\"pear\", \"fig\", \"apple\"];")); - ensure("two keys the same: left: " + r.text, has("list u = llListSort(")); - ensure("keys of two cases: left: " + r.text, has("list m = llListSort(")); + UNSCOPED_INFO("split, spacers kept: " + r.text); + REQUIRE(has("list p = [\"a\", \"b\", \";\", \";\", \"c\"];")); + UNSCOPED_INFO("split, empties kept: " + r.text); + REQUIRE(has("list k = [\"a\", \"\", \"b\", \"\"];")); + UNSCOPED_INFO("a separator the start of another: left: " + r.text); + REQUIRE(has("list x = llParseString2List(")); + UNSCOPED_INFO("CSV: " + r.text); + REQUIRE(has("list v = [\"a\", \"b\", \"c\"];")); + UNSCOPED_INFO("a value starting with a space: left: " + r.text); + REQUIRE(has("list w = llCSV2List(")); + UNSCOPED_INFO("sorted by stride: " + r.text); + REQUIRE(has("list s = [1, \"a\", 2, \"b\", 3, \"c\"];")); + UNSCOPED_INFO("sorted down: " + r.text); + REQUIRE(has("list t = [\"pear\", \"fig\", \"apple\"];")); + UNSCOPED_INFO("two keys the same: left: " + r.text); + REQUIRE(has("list u = llListSort(")); + UNSCOPED_INFO("keys of two cases: left: " + r.text); + REQUIRE(has("list m = llListSort(")); const float half = static_cast(std::sqrt(0.5)); - ensure("a quarter turn between: " + r.text, numbers(r.text, "rotation r") == std::vector{ 0.f, 0.f, half, half }); - ensure("parallel: left: " + r.text, has("rotation q = llRotBetween(")); - ensure("a quarter turn the other way: " + r.text, numbers(r.text, "rotation n") == std::vector{ 0.f, -half, 0.f, half }); - ensure("no angle between the same: " + r.text, numbers(r.text, "float h") == std::vector{ 0.f }); - ensure("the axes themselves: " + r.text, numbers(r.text, "rotation z") == std::vector{ 0.f, 0.f, 0.f, 1.f }); + UNSCOPED_INFO("a quarter turn between: " + r.text); + REQUIRE(numbers(r.text, "rotation r") == std::vector{ 0.f, 0.f, half, half }); + UNSCOPED_INFO("parallel: left: " + r.text); + REQUIRE(has("rotation q = llRotBetween(")); + UNSCOPED_INFO("a quarter turn the other way: " + r.text); + REQUIRE(numbers(r.text, "rotation n") == std::vector{ 0.f, -half, 0.f, half }); + UNSCOPED_INFO("no angle between the same: " + r.text); + REQUIRE(numbers(r.text, "float h") == std::vector{ 0.f }); + UNSCOPED_INFO("the axes themselves: " + r.text); + REQUIRE(numbers(r.text, "rotation z") == std::vector{ 0.f, 0.f, 0.f, 1.f }); // Made unit, as LLQuaternion does today, a quarter turn is a bit // larger in its last place than left as it was, as in 2007. - ensure("ways that part by a bit: left: " + r.text, has("rotation e = llAxes2Rot(")); + UNSCOPED_INFO("ways that part by a bit: left: " + r.text); + REQUIRE(has("rotation e = llAxes2Rot(")); } } - template<> template<> - void allsloptimizer_object::test<58>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: increments and left-out defaults and negative casts where each is smaller", "[allsloptimizer]") { - set_test_name("Mono's ++x for x = x + 1, a default left out, and a negative number as its cast; each where its target has it smaller, and what " - "must stay stays"); const std::string source = "integer gz = 0;\n" "integer gn = -5;\n" "default\n" @@ -2331,52 +3004,89 @@ namespace tut ALLSLOptimizer::Options o = allsloptimizer_data::options(); o.target = target; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - ensure("optimized: " + allsloptimizer_data::notes(r), r.optimized); + UNSCOPED_INFO("optimized: " + allsloptimizer_data::notes(r)); + REQUIRE(r.optimized); ALLSLService service; const ALScriptProblems said = service.check(r.text, target != ALLSLOptimizer::Target::LSO); for (const ALScriptProblem& p : said) { - ensure("no error in what was written: " + p.message + "\n" + r.text, p.severity != ALScriptProblem::Severity::Error); + UNSCOPED_INFO("no error in what was written: " + p.message + "\n" + r.text); + REQUIRE(p.severity != ALScriptProblem::Severity::Error); } return r.text; }; const auto has = [](const std::string& text, const std::string& what) { return text.find(what) != std::string::npos; }; const std::string mono = run(ALLSLOptimizer::Target::Mono); - ensure("Mono: x = x + 1 as ++x: " + mono, has(mono, "++i;")); - ensure("Mono: x -= 1 as --x", has(mono, "--j;")); - ensure("Mono: as a value, 1 + x", has(mono, "j = ++i;")); - ensure("Mono: a float's stays", !has(mono, "++f") && !has(mono, "f;")); - ensure("Mono: a vector's part stays", !has(mono, "++v.x")); - ensure("Mono: an integer's default left out", has(mono, "integer z;")); - ensure("Mono: an integer global's too", has(mono, "integer gz;")); - ensure("Mono: a float's is not, there", !has(mono, "float y;")); - ensure("Mono: a negative number as its cast", has(mono, "llSay(((integer)-5), ")); - ensure("Mono: a vector's negative part", has(mono, "<((float)-1.5), 0, 0>")); - ensure("Mono: never in a global's value", has(mono, "integer gn = -5;")); + UNSCOPED_INFO("Mono: x = x + 1 as ++x: " + mono); + CHECK(has(mono, "++i;")); + UNSCOPED_INFO("Mono: x -= 1 as --x"); + CHECK(has(mono, "--j;")); + UNSCOPED_INFO("Mono: as a value, 1 + x"); + CHECK(has(mono, "j = ++i;")); + { + INFO("Mono: a float's stays"); + CHECK_FALSE(has(mono, "++f")); + CHECK_FALSE(has(mono, "f;")); + } + UNSCOPED_INFO("Mono: a vector's part stays"); + CHECK_FALSE(has(mono, "++v.x")); + UNSCOPED_INFO("Mono: an integer's default left out"); + CHECK(has(mono, "integer z;")); + UNSCOPED_INFO("Mono: an integer global's too"); + CHECK(has(mono, "integer gz;")); + UNSCOPED_INFO("Mono: a float's is not, there"); + CHECK_FALSE(has(mono, "float y;")); + UNSCOPED_INFO("Mono: a negative number as its cast"); + CHECK(has(mono, "llSay(((integer)-5), ")); + UNSCOPED_INFO("Mono: a vector's negative part"); + CHECK(has(mono, "<((float)-1.5), 0, 0>")); + UNSCOPED_INFO("Mono: never in a global's value"); + CHECK(has(mono, "integer gn = -5;")); const std::string lso = run(ALLSLOptimizer::Target::LSO); - ensure("LSO: x = x + 1 stays as LSO has it smaller: " + lso, !has(lso, "++i;") && !has(lso, "--j;")); - ensure("LSO: a float's default left out", has(lso, "float y;")); - ensure("LSO: -0.0 is not the default", !has(lso, "float n;")); - ensure("LSO: a key's default, \"\"", has(lso, "key k;")); - ensure("LSO: NULL_KEY is not a key's default", !has(lso, "key nk;")); - ensure("LSO: a string's never", !has(lso, "string s;")); - ensure("LSO: a vector's and a rotation's", has(lso, "vector w;") && has(lso, "rotation r;")); - ensure("LSO: an integer's is not, there", !has(lso, "integer z;")); - ensure("LSO: no casts of negative numbers", !has(lso, "((integer)-") && !has(lso, "((float)-")); + { + INFO("LSO: x = x + 1 stays as LSO has it smaller: " + lso); + CHECK_FALSE(has(lso, "++i;")); + CHECK_FALSE(has(lso, "--j;")); + } + UNSCOPED_INFO("LSO: a float's default left out"); + CHECK(has(lso, "float y;")); + UNSCOPED_INFO("LSO: -0.0 is not the default"); + CHECK_FALSE(has(lso, "float n;")); + UNSCOPED_INFO("LSO: a key's default, \"\""); + CHECK(has(lso, "key k;")); + UNSCOPED_INFO("LSO: NULL_KEY is not a key's default"); + CHECK_FALSE(has(lso, "key nk;")); + UNSCOPED_INFO("LSO: a string's never"); + CHECK_FALSE(has(lso, "string s;")); + { + INFO("LSO: a vector's and a rotation's"); + CHECK(has(lso, "vector w;")); + CHECK(has(lso, "rotation r;")); + } + UNSCOPED_INFO("LSO: an integer's is not, there"); + CHECK_FALSE(has(lso, "integer z;")); + { + INFO("LSO: no casts of negative numbers"); + CHECK_FALSE(has(lso, "((integer)-")); + CHECK_FALSE(has(lso, "((float)-")); + } const std::string luau = run(ALLSLOptimizer::Target::Luau); - ensure("Luau: a key's default left out: " + luau, has(luau, "key k;")); - ensure("Luau: nothing else", !has(luau, "integer z;") && !has(luau, "++i;") && !has(luau, "((integer)-")); + UNSCOPED_INFO("Luau: a key's default left out: " + luau); + CHECK(has(luau, "key k;")); + { + INFO("Luau: nothing else"); + CHECK_FALSE(has(luau, "integer z;")); + CHECK_FALSE(has(luau, "++i;")); + CHECK_FALSE(has(luau, "((integer)-")); + } } - template<> template<> - void allsloptimizer_object::test<59>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: operators take each target's smaller form with Luau's bit tests as arithmetic", "[allsloptimizer]") { - set_test_name("operators as each target has them smaller: && of truths as &, a - b as a + -b, -1 - x as ~x, a shift left as a product, a " - "doubling as a sum, a float halved as a product, x * -1 as -x; Luau's bitwise tests as arithmetic"); const std::string source = kept(wrap("integer g;\nfloat h;\n", " integer a = (integer)llFrand(9);\n integer b = (integer)llFrand(9);\n" " integer c = llStringLength(llGetObjectName());\n float f = llFrand(9);\n float e = llFrand(9);\n" @@ -2396,39 +3106,74 @@ namespace tut " g = a >> 2;\n" " llOwnerSay((string)[a, b, c, f, e, g, h]);\n"), { "g", "h" }); const std::string mono = optimized(source, ALLSLOptimizer::Target::Mono); - ensure("Mono: a - b as a + -b: " + mono, in(mono, "g = a + -b;")); - ensure("Mono: -= as += -", in(mono, "g += -b;")); - ensure("Mono: of floats", in(mono, "h = f + -e;")); - ensure("Mono: a shift as a product", in(mono, "g = a * 8;")); - ensure("Mono: -1 - x as ~x", in(mono, "g = ~b;")); - ensure("Mono: a doubling as a sum", in(mono, "g = a + a;")); - ensure("Mono: a float by a power of two as a product", in(mono, "h = f * 0.25;")); - ensure("Mono: x * -1 as -x", in(mono, "g = -b;")); - ensure("Mono: && of truths as &", in(mono, "if (a < b & b > 2)")); - ensure("Mono: the bitwise stay", in(mono, "if (a & 1)") && in(mono, "g = c >> 2;") && in(mono, "g = c & 7;")); + UNSCOPED_INFO("Mono: a - b as a + -b: " + mono); + CHECK(in(mono, "g = a + -b;")); + UNSCOPED_INFO("Mono: -= as += -"); + CHECK(in(mono, "g += -b;")); + UNSCOPED_INFO("Mono: of floats"); + CHECK(in(mono, "h = f + -e;")); + UNSCOPED_INFO("Mono: a shift as a product"); + CHECK(in(mono, "g = a * 8;")); + UNSCOPED_INFO("Mono: -1 - x as ~x"); + CHECK(in(mono, "g = ~b;")); + UNSCOPED_INFO("Mono: a doubling as a sum"); + CHECK(in(mono, "g = a + a;")); + UNSCOPED_INFO("Mono: a float by a power of two as a product"); + CHECK(in(mono, "h = f * 0.25;")); + UNSCOPED_INFO("Mono: x * -1 as -x"); + CHECK(in(mono, "g = -b;")); + UNSCOPED_INFO("Mono: && of truths as &"); + CHECK(in(mono, "if (a < b & b > 2)")); + { + INFO("Mono: the bitwise stay"); + CHECK(in(mono, "if (a & 1)")); + CHECK(in(mono, "g = c >> 2;")); + CHECK(in(mono, "g = c & 7;")); + } const std::string lso = optimized(source, ALLSLOptimizer::Target::LSO); - ensure("LSO: a - b stays: " + lso, in(lso, "g = a - b;") && in(lso, "g -= b;") && in(lso, "h = f - e;")); - ensure("LSO: -1 - x as ~x", in(lso, "g = ~b;")); - ensure("LSO: x * -1 as -x", in(lso, "g = -b;")); - ensure("LSO: the rest stays", in(lso, "g = a << 3;") && in(lso, "g = a * 2;") && in(lso, "h = f / 4;") && in(lso, "if (a < b && b > 2)")); + { + INFO("LSO: a - b stays: " + lso); + CHECK(in(lso, "g = a - b;")); + CHECK(in(lso, "g -= b;")); + CHECK(in(lso, "h = f - e;")); + } + UNSCOPED_INFO("LSO: -1 - x as ~x"); + CHECK(in(lso, "g = ~b;")); + UNSCOPED_INFO("LSO: x * -1 as -x"); + CHECK(in(lso, "g = -b;")); + { + INFO("LSO: the rest stays"); + CHECK(in(lso, "g = a << 3;")); + CHECK(in(lso, "g = a * 2;")); + CHECK(in(lso, "h = f / 4;")); + CHECK(in(lso, "if (a < b && b > 2)")); + } const std::string luau = optimized(source, ALLSLOptimizer::Target::Luau); - ensure("Luau: a shift as a product: " + luau, in(luau, "g = a * 8;")); - ensure("Luau: a doubling as a sum", in(luau, "g = a + a;")); - ensure("Luau: -1 - x stays, ~ a call there", in(luau, "g = -1 - b;")); - ensure("Luau: an odd test as a remainder", in(luau, "if (a % 2)")); - ensure("Luau: == as the ! of a difference", in(luau, "g = !(a - b);")); - ensure("Luau: a shift right and a mask of a count as a quotient and a remainder", in(luau, "g = c / 4;") && in(luau, "g = c % 8;")); - ensure("Luau: not of what may be below nought", in(luau, "g = a >> 2;")); - ensure("Luau: a - b stays", in(luau, "g = a - b;")); + UNSCOPED_INFO("Luau: a shift as a product: " + luau); + CHECK(in(luau, "g = a * 8;")); + UNSCOPED_INFO("Luau: a doubling as a sum"); + CHECK(in(luau, "g = a + a;")); + UNSCOPED_INFO("Luau: -1 - x stays, ~ a call there"); + CHECK(in(luau, "g = -1 - b;")); + UNSCOPED_INFO("Luau: an odd test as a remainder"); + CHECK(in(luau, "if (a % 2)")); + UNSCOPED_INFO("Luau: == as the ! of a difference"); + CHECK(in(luau, "g = !(a - b);")); + { + INFO("Luau: a shift right and a mask of a count as a quotient and a remainder"); + CHECK(in(luau, "g = c / 4;")); + CHECK(in(luau, "g = c % 8;")); + } + UNSCOPED_INFO("Luau: not of what may be below nought"); + CHECK(in(luau, "g = a >> 2;")); + UNSCOPED_INFO("Luau: a - b stays"); + CHECK(in(luau, "g = a - b;")); } - template<> template<> - void allsloptimizer_object::test<60>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: branches are written with fewer jumps on every target", "[allsloptimizer]") { - set_test_name("branches in fewer jumps: a truth returned or set, a select as arithmetic, a value set before an if, ways the same once, an " - "else after a return dropped, a jump over statements as an else, an empty if gone, nested ifs merged"); const std::string source = "integer g;\nstring t;\n" "integer big(integer a)\n{\n if (a > 3)\n return TRUE;\n return FALSE;\n}\n" "integer none(integer a)\n{\n if (a)\n return FALSE;\n else\n return TRUE;\n}\n" @@ -2457,29 +3202,56 @@ namespace tut const std::string text = optimized(source, target); const bool lso = target == ALLSLOptimizer::Target::LSO; const bool mono = target == ALLSLOptimizer::Target::Mono; - ensure("a truth returned: " + text, in(text, "return a > 3;")); - ensure("its ! the other way", in(text, "return !a;")); - ensure("a truth set", in(text, "g = a > 2;")); - ensure("a select of a truth as arithmetic, on every target: " + text, in(text, "g = (b > 2) * ") && in(text, " + 7;")); - ensure("of anything else, on LSO and Mono", lso || mono ? in(text, "g = !!b * ") : !in(text, "g = !!b")); - ensure("a local's value set before the if: " + text, in(text, "u = \"none\";\n if (a > 4)")); - ensure("not a global's, which a call in the check could read", in(text, "t = \"global\";\n") && !in(text, "t = \"global\";\n if")); - ensure("ways the same written once", !in(text, "else\n llOwnerSay(\"same\")") && in(text, "llOwnerSay(\"same\");")); - ensure("what both ways end with once, after", in(text, "llOwnerSay(\"end\");") && text.find("llOwnerSay(\"end\")") == text.rfind("llOwnerSay(\"end\")")); - ensure("an else after a return dropped", !in(text, "else\n {\n llOwnerSay(\"other\")")); - ensure("a jump over statements as an else", !in(text, "jump past") && !in(text, "@past")); - ensure("an empty if gone", !in(text, "a < 3")); - ensure("nested ifs of truths merged on LSO and Mono: " + text, lso || mono ? in(text, "if ((a > 1) & (b < 4))") : in(text, "if (a > 1)\n if (b < 4)")); - ensure("nested ifs merged with && on LSO", lso ? in(text, "if (a && b)") : !in(text, "if (a && b)")); - ensure("not where the inner may stop the script", in(text, "100 / b")); - } - } - - template<> template<> - void allsloptimizer_object::test<61>() - { - set_test_name("loops: a for or a while whose first check passes run as a do on LSO and Mono, the step in its check; a counter read nowhere else " - "counted down; not where the check is not known, nor the counter read"); + UNSCOPED_INFO("a truth returned: " + text); + REQUIRE(in(text, "return a > 3;")); + UNSCOPED_INFO("its ! the other way"); + REQUIRE(in(text, "return !a;")); + UNSCOPED_INFO("a truth set"); + REQUIRE(in(text, "g = a > 2;")); + { + INFO("a select of a truth as arithmetic, on every target: " + text); + REQUIRE(in(text, "g = (b > 2) * ")); + REQUIRE(in(text, " + 7;")); + } + UNSCOPED_INFO("of anything else, on LSO and Mono"); + REQUIRE((lso || mono ? in(text, "g = !!b * ") : !in(text, "g = !!b"))); + UNSCOPED_INFO("a local's value set before the if: " + text); + REQUIRE(in(text, "u = \"none\";\n if (a > 4)")); + { + INFO("not a global's, which a call in the check could read"); + REQUIRE(in(text, "t = \"global\";\n")); + REQUIRE_FALSE(in(text, "t = \"global\";\n if")); + } + { + INFO("ways the same written once"); + REQUIRE_FALSE(in(text, "else\n llOwnerSay(\"same\")")); + REQUIRE(in(text, "llOwnerSay(\"same\");")); + } + { + INFO("what both ways end with once, after"); + REQUIRE(in(text, "llOwnerSay(\"end\");")); + REQUIRE(text.find("llOwnerSay(\"end\")") == text.rfind("llOwnerSay(\"end\")")); + } + UNSCOPED_INFO("an else after a return dropped"); + REQUIRE_FALSE(in(text, "else\n {\n llOwnerSay(\"other\")")); + { + INFO("a jump over statements as an else"); + REQUIRE_FALSE(in(text, "jump past")); + REQUIRE_FALSE(in(text, "@past")); + } + UNSCOPED_INFO("an empty if gone"); + REQUIRE_FALSE(in(text, "a < 3")); + UNSCOPED_INFO("nested ifs of truths merged on LSO and Mono: " + text); + REQUIRE((lso || mono ? in(text, "if ((a > 1) & (b < 4))") : in(text, "if (a > 1)\n if (b < 4)"))); + UNSCOPED_INFO("nested ifs merged with && on LSO"); + REQUIRE((lso ? in(text, "if (a && b)") : !in(text, "if (a && b)"))); + UNSCOPED_INFO("not where the inner may stop the script"); + REQUIRE(in(text, "100 / b")); + } + } + + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: loops known to run become dos and unread counters count down", "[allsloptimizer]") + { const std::string source = "default\n{\n state_entry()\n {\n" " list l = llParseString2List(llGetObjectName(), [\" \"], []);\n" " integer i;\n integer j;\n integer k;\n integer m;\n integer n = (integer)llFrand(9);\n" @@ -2495,20 +3267,32 @@ namespace tut { const std::string text = optimized(source, target); const bool luau = target == ALLSLOptimizer::Target::Luau; - ensure("a for known to run as a do, its step in the check: " + text, luau ? in(text, "for (i = 0; i < 10; ++i)") : in(text, "while(++i < 10);")); - ensure("a counter read nowhere else counted down", in(text, "j = 3;") && in(text, "while(--j);")); - ensure("to a count never below nought: " + text, (in(text, "integer k = llGetListLength(l);") || in(text, "integer k = (l != []);")) && in(text, "while (k--)")); - ensure("not where the first check is not known", in(text, "for (m = n; m < 10; ++m)")); - ensure("a while known to run as a do", luau ? in(text, "while (w < 5)") : in(text, "while(w < 5);")); - ensure("a loop known never to run gone, its first part kept: " + text, !in(text, "never") && in(text, "llOwnerSay(\"5\");")); + UNSCOPED_INFO("a for known to run as a do, its step in the check: " + text); + REQUIRE((luau ? in(text, "for (i = 0; i < 10; ++i)") : in(text, "while(++i < 10);"))); + { + INFO("a counter read nowhere else counted down"); + REQUIRE(in(text, "j = 3;")); + REQUIRE(in(text, "while(--j);")); + } + { + INFO("to a count never below nought: " + text); + REQUIRE((in(text, "integer k = llGetListLength(l);") || in(text, "integer k = (l != []);"))); + REQUIRE(in(text, "while (k--)")); + } + UNSCOPED_INFO("not where the first check is not known"); + REQUIRE(in(text, "for (m = n; m < 10; ++m)")); + UNSCOPED_INFO("a while known to run as a do"); + REQUIRE((luau ? in(text, "while (w < 5)") : in(text, "while(w < 5);"))); + { + INFO("a loop known never to run gone, its first part kept: " + text); + REQUIRE_FALSE(in(text, "never")); + REQUIRE(in(text, "llOwnerSay(\"5\");")); + } } } - template<> template<> - void allsloptimizer_object::test<62>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a key compared with NULL_KEY is tested as itself unless it may be any string", "[allsloptimizer]") { - set_test_name("a key that is a key found or NULL_KEY tested as itself: k != NULL_KEY as k, k == NULL_KEY as k with its ways turned round; " - "not of a key that may be any string"); const std::string source = "default\n{\n" " touch_start(integer d)\n {\n" " key owner = llGetOwner();\n" @@ -2527,21 +3311,26 @@ namespace tut for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { const std::string text = optimized(source, target); - ensure("a local set to the owner: " + text, in(text, "if (owner)")); - ensure("a detected key, its ways turned round", in(text, "if (llDetectedKey(0))\n llOwnerSay(\"somebody\");\n else\n llOwnerSay(\"nobody\");")); - ensure("a lone == turned round", in(text, "if (sitter)\n ;\n else\n llOwnerSay(\"empty\");")); - ensure("a listen's speaker: " + text, in(text, "if (id)\n llOwnerSay(m);")); - ensure("not a key cast from a string", in(text, "if (given != ")); - ensure("not a link message's key, which is any string", in(text, "if (id != ") && in(text, "llOwnerSay(str);")); + UNSCOPED_INFO("a local set to the owner: " + text); + REQUIRE(in(text, "if (owner)")); + UNSCOPED_INFO("a detected key, its ways turned round"); + REQUIRE(in(text, "if (llDetectedKey(0))\n llOwnerSay(\"somebody\");\n else\n llOwnerSay(\"nobody\");")); + UNSCOPED_INFO("a lone == turned round"); + REQUIRE(in(text, "if (sitter)\n ;\n else\n llOwnerSay(\"empty\");")); + UNSCOPED_INFO("a listen's speaker: " + text); + REQUIRE(in(text, "if (id)\n llOwnerSay(m);")); + UNSCOPED_INFO("not a key cast from a string"); + REQUIRE(in(text, "if (given != ")); + { + INFO("not a link message's key, which is any string"); + REQUIRE(in(text, "if (id != ")); + REQUIRE(in(text, "llOwnerSay(str);")); + } } } - template<> template<> - void allsloptimizer_object::test<63>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: unread parameters and shared constant arguments and unread returns go", "[allsloptimizer]") { - set_test_name("functions given and giving what their calls need: a parameter never read taken away with what each call gives it, one every " - "call gives the same constant made a local, a value no call reads no longer returned; not where a call's argument does " - "something, the calls differ, the parameter is set, or a call reads the value"); const std::string source = "integer got;\n" "say(integer channel, string message, integer unused)\n{\n llSay(channel, message);\n}\n" "tell(integer quiet, string message)\n{\n llOwnerSay(message);\n}\n" @@ -2559,22 +3348,37 @@ namespace tut for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { const std::string text = optimized(source, target); - ensure("a parameter never read gone, and its arguments: " + text, in(text, "say(string message)") && in(text, "say(\"a\");")); - ensure("one every call gives the same constant made its value", in(text, "llSay(0, message);")); - ensure("not where a call's argument does something", in(text, "tell(integer quiet, string message)") && in(text, "llListen(5")); - ensure("a value no call reads no longer returned", in(text, "\nnoisy(integer n)") && !in(text, "integer noisy")); - ensure("what it returned still worked out where it does something, or gone", !in(text, "return n * 2") || in(text, "n * 2;")); - ensure("not where a call reads it", in(text, "integer read(integer n)")); - ensure("not a parameter that is set", in(text, "set(integer n)") && in(text, "set(7);")); + { + INFO("a parameter never read gone, and its arguments: " + text); + REQUIRE(in(text, "say(string message)")); + REQUIRE(in(text, "say(\"a\");")); + } + UNSCOPED_INFO("one every call gives the same constant made its value"); + REQUIRE(in(text, "llSay(0, message);")); + { + INFO("not where a call's argument does something"); + REQUIRE(in(text, "tell(integer quiet, string message)")); + REQUIRE(in(text, "llListen(5")); + } + { + INFO("a value no call reads no longer returned"); + REQUIRE(in(text, "\nnoisy(integer n)")); + REQUIRE_FALSE(in(text, "integer noisy")); + } + UNSCOPED_INFO("what it returned still worked out where it does something, or gone"); + REQUIRE((!in(text, "return n * 2") || in(text, "n * 2;"))); + UNSCOPED_INFO("not where a call reads it"); + REQUIRE(in(text, "integer read(integer n)")); + { + INFO("not a parameter that is set"); + REQUIRE(in(text, "set(integer n)")); + REQUIRE(in(text, "set(7);")); + } } } - template<> template<> - void allsloptimizer_object::test<64>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: dead stores go and globals each handler sets first become its locals", "[allsloptimizer]") { - set_test_name("a value set again before it is read is not set; a global every handler sets before it reads made a local of each -- on " - "Mono, whose locals each cost a frame, only where that is less than the global; not one read before it is set, nor one a " - "function also sets; a || 1 and a && 0 settled"); const std::string source = "integer scratch;\n" "integer twice;\n" "integer kept = 3;\n" @@ -2596,24 +3400,33 @@ namespace tut { const std::string text = optimized(source, target); // A read of the clock changes nothing, and goes with it. - ensure("a value set again before it is read not set: " + text, in(text, "integer i = (integer)llFrand(9);")); - ensure("a for's first part sets again", !in(text, "integer j = 4;")); - ensure("a global one handler sets before it reads made a local", in(text, "integer scratch = llGetUnixTime();") && !in(text, "integer scratch;\n")); - ensure("one two handlers set before they read, a local of each but on Mono", - target == ALLSLOptimizer::Target::Mono ? in(text, "integer twice;\n") : in(text, "integer twice = llGetUnixTime();") && in(text, "llOwnerSay((string)t + ")); - ensure("not one read before it is set", in(text, "integer kept")); - ensure("not one a function sets too", in(text, "integer shared")); - ensure("a || 1 settled true", in(text, "llOwnerSay(\"always\")") && !in(text, "|| 1")); - ensure("a && 0 settled false", !in(text, "never")); + UNSCOPED_INFO("a value set again before it is read not set: " + text); + REQUIRE(in(text, "integer i = (integer)llFrand(9);")); + UNSCOPED_INFO("a for's first part sets again"); + REQUIRE_FALSE(in(text, "integer j = 4;")); + { + INFO("a global one handler sets before it reads made a local"); + REQUIRE(in(text, "integer scratch = llGetUnixTime();")); + REQUIRE_FALSE(in(text, "integer scratch;\n")); + } + UNSCOPED_INFO("one two handlers set before they read, a local of each but on Mono"); + REQUIRE((target == ALLSLOptimizer::Target::Mono ? in(text, "integer twice;\n") : in(text, "integer twice = llGetUnixTime();") && in(text, "llOwnerSay((string)t + "))); + UNSCOPED_INFO("not one read before it is set"); + REQUIRE(in(text, "integer kept")); + UNSCOPED_INFO("not one a function sets too"); + REQUIRE(in(text, "integer shared")); + { + INFO("a || 1 settled true"); + REQUIRE(in(text, "llOwnerSay(\"always\")")); + REQUIRE_FALSE(in(text, "|| 1")); + } + UNSCOPED_INFO("a && 0 settled false"); + REQUIRE_FALSE(in(text, "never")); } } - template<> template<> - void allsloptimizer_object::test<65>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: repeated global reads and repeated constants are held once where cheaper", "[allsloptimizer]") { - set_test_name("a global the script sets read again and again read once into a local, where nothing between sets it; a constant written at " - "many places kept once, in a global where the target holds one smaller so, else in a local; each on its target, Mono's " - "locals paying for their frames"); const std::string source = "integer channel;\n" "integer never = 7;\n" "default\n{\n" @@ -2628,28 +3441,40 @@ namespace tut " llSetPos(llGetPos() + <1.5, 2.5, 3.5>);\n llSetPos(llGetPos() + <1.5, 2.5, 3.5>);\n" " }\n}\n"; const std::string lso = optimized(source, ALLSLOptimizer::Target::LSO); - ensure("LSO: a long string at three places kept in a global: " + lso, in(lso, "string s = \"a sentence long enough to be worth holding\";") && - in(lso, "llOwnerSay(s);")); - ensure("LSO: a vector at four places, in a global", in(lso, "vector v = <1.5, 2.5, 3.5>;")); - ensure("LSO: a global's reads stay, as large as a local's there", in(lso, "llSay(channel, \"one\");")); + { + INFO("LSO: a long string at three places kept in a global: " + lso); + CHECK(in(lso, "string s = \"a sentence long enough to be worth holding\";")); + CHECK(in(lso, "llOwnerSay(s);")); + } + UNSCOPED_INFO("LSO: a vector at four places, in a global"); + CHECK(in(lso, "vector v = <1.5, 2.5, 3.5>;")); + UNSCOPED_INFO("LSO: a global's reads stay, as large as a local's there"); + CHECK(in(lso, "llSay(channel, \"one\");")); const std::string luau = optimized(source, ALLSLOptimizer::Target::Luau); - ensure("Luau: a global read three times read once: " + luau, in(luau, "integer localChannel = channel;") && in(luau, "llSay(localChannel, \"one\");")); - ensure("Luau: not across where it is set", in(luau, "channel = n;")); - ensure("Luau: never one the script never sets, which is the folder's", !in(luau, "localNever")); - ensure("Luau: constants held once in a function already", !in(luau, "string s = ")); + { + INFO("Luau: a global read three times read once: " + luau); + CHECK(in(luau, "integer localChannel = channel;")); + CHECK(in(luau, "llSay(localChannel, \"one\");")); + } + UNSCOPED_INFO("Luau: not across where it is set"); + CHECK(in(luau, "channel = n;")); + UNSCOPED_INFO("Luau: never one the script never sets, which is the folder's"); + CHECK_FALSE(in(luau, "localNever")); + UNSCOPED_INFO("Luau: constants held once in a function already"); + CHECK_FALSE(in(luau, "string s = ")); const std::string mono = optimized(source, ALLSLOptimizer::Target::Mono); - ensure("Mono: a vector at four places kept in a local, its frame paid for: " + mono, in(mono, "vector v = <1.5, 2.5, 3.5>;")); - ensure("Mono: three reads of a global not worth a local's frame", !in(mono, "localChannel")); - ensure("Mono: a string held once already", !in(mono, "string s = ")); + UNSCOPED_INFO("Mono: a vector at four places kept in a local, its frame paid for: " + mono); + CHECK(in(mono, "vector v = <1.5, 2.5, 3.5>;")); + UNSCOPED_INFO("Mono: three reads of a global not worth a local's frame"); + CHECK_FALSE(in(mono, "localChannel")); + UNSCOPED_INFO("Mono: a string held once already"); + CHECK_FALSE(in(mono, "string s = ")); } - template<> template<> - void allsloptimizer_object::test<66>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: string literals join on LSO or when asked and endless loops become jumps", "[allsloptimizer]") { - set_test_name("two string literals joined on LSO, which writes each where it is used, and where the option says elsewhere; a loop that " - "never ends run by a jump back on LSO and Mono"); const std::string source = "default\n{\n touch_start(integer n)\n {\n" " llOwnerSay(\"Hello, \" + \"world\");\n" " while (TRUE)\n {\n llOwnerSay((string)llFrand(1));\n" @@ -2660,21 +3485,25 @@ namespace tut const std::string text = optimized(source, target); const bool lso = target == ALLSLOptimizer::Target::LSO; const bool luau = target == ALLSLOptimizer::Target::Luau; - ensure("two literals joined on LSO alone, by default: " + text, lso ? in(text, "\"Hello, world\"") : in(text, "\"Hello, \" + \"world\"")); - ensure("a loop that never ends by a jump back, not on Luau", luau ? in(text, "while (TRUE)") : in(text, "@loop;") && in(text, "jump loop;")); + UNSCOPED_INFO("two literals joined on LSO alone, by default: " + text); + REQUIRE((lso ? in(text, "\"Hello, world\"") : in(text, "\"Hello, \" + \"world\""))); + UNSCOPED_INFO("a loop that never ends by a jump back, not on Luau"); + REQUIRE((luau ? in(text, "while (TRUE)") : in(text, "@loop;") && in(text, "jump loop;"))); // Its jump out and back made one jump back where the check fails. - ensure("what follows it still reached: " + text, in(text, "llOwnerSay(\"done\");") && (luau || in(text, "jump loop;\n llOwnerSay(\"done\");"))); + { + INFO("what follows it still reached: " + text); + REQUIRE(in(text, "llOwnerSay(\"done\");")); + REQUIRE((luau || in(text, "jump loop;\n llOwnerSay(\"done\");"))); + } } ALLSLOptimizer::Options o = options(); o.addstrings = true; - ensure("joined on Mono where the option asks", in(ALLSLOptimizer::run(source, o).text, "\"Hello, world\"")); + UNSCOPED_INFO("joined on Mono where the option asks"); + CHECK(in(ALLSLOptimizer::run(source, o).text, "\"Hello, world\"")); } - template<> template<> - void allsloptimizer_object::test<67>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: string to integer casts fold as 32-bit hosts do and big JSON numbers are left", "[allsloptimizer]") { - set_test_name("a string cast to an integer folds as the grid's 32-bit hosts cast one, past 0xFFFFFFFF -1; a JSON number past 32 bits " - "is left to the simulator"); const std::string source = "default\n{\n touch_start(integer n)\n {\n" " llOwnerSay((string)((integer)\"4294967296\"));\n" " llOwnerSay((string)((integer)\"-4294967296\"));\n" @@ -2694,21 +3523,28 @@ namespace tut { ++minus_ones; } - ensure("past 0xFFFFFFFF, -1, its minus or not: " + text, minus_ones == 4); - ensure("within it, wrapped below nought: " + text, in(text, "llOwnerSay(\"-1294967296\");")); - ensure("spaces and what follows the number, hexadecimal only from a leading 0x: " + text, - in(text, "llOwnerSay(\"12\");") && in(text, "llOwnerSay(\"16\");") && in(text, "llOwnerSay(\"0\");")); - ensure("a JSON number past 32 bits left: " + text, in(text, "llJson2List(\"[9999999999]\")")); - ensure("one within them folded: " + text, in(text, "2147483647") && !in(text, "llJson2List(\"[2147483647")); + UNSCOPED_INFO("past 0xFFFFFFFF, -1, its minus or not: " + text); + REQUIRE(minus_ones == 4); + UNSCOPED_INFO("within it, wrapped below nought: " + text); + REQUIRE(in(text, "llOwnerSay(\"-1294967296\");")); + { + INFO("spaces and what follows the number, hexadecimal only from a leading 0x: " + text); + REQUIRE(in(text, "llOwnerSay(\"12\");")); + REQUIRE(in(text, "llOwnerSay(\"16\");")); + REQUIRE(in(text, "llOwnerSay(\"0\");")); + } + UNSCOPED_INFO("a JSON number past 32 bits left: " + text); + REQUIRE(in(text, "llJson2List(\"[9999999999]\")")); + { + INFO("one within them folded: " + text); + REQUIRE(in(text, "2147483647")); + REQUIRE_FALSE(in(text, "llJson2List(\"[2147483647")); + } } } - template<> template<> - void allsloptimizer_object::test<68>() + TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: float library functions fold where double and single agree and -0 becomes 0", "[allsloptimizer]") { - set_test_name("a float function of the library folds where in double rounded once and in single precision it agrees, to a single on " - "every target, as each VM answers; a pow or an atan2 of -0 is 0; on Luau, sin of no single and a length no single " - "is are left"); const std::string source = "default\n{\n touch_start(integer n)\n {\n" " llOwnerSay((string)llPow(2.0, 10.0));\n" " llOwnerSay((string)llSqrt(2.0));\n" @@ -2722,16 +3558,35 @@ namespace tut for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono }) { const std::string text = optimized(source, target); - ensure("each folded as the VM's single: " + text, - in(text, "llOwnerSay(\"1024.000000\");") && in(text, "llOwnerSay(\"1.414214\");") && in(text, "llOwnerSay(\"0.841471\");") && - in(text, "llOwnerSay(\"0.295520\");") && in(text, "llOwnerSay(\"5.000000\");") && !in(text, "llSin") && !in(text, "llVecMag")); - ensure("-0 made 0, not -0: " + text, !in(text, "-0.000000")); + { + INFO("each folded as the VM's single: " + text); + REQUIRE(in(text, "llOwnerSay(\"1024.000000\");")); + REQUIRE(in(text, "llOwnerSay(\"1.414214\");")); + REQUIRE(in(text, "llOwnerSay(\"0.841471\");")); + REQUIRE(in(text, "llOwnerSay(\"0.295520\");")); + REQUIRE(in(text, "llOwnerSay(\"5.000000\");")); + REQUIRE_FALSE(in(text, "llSin")); + REQUIRE_FALSE(in(text, "llVecMag")); + } + UNSCOPED_INFO("-0 made 0, not -0: " + text); + REQUIRE_FALSE(in(text, "-0.000000")); } const std::string luau = optimized(source, ALLSLOptimizer::Target::Luau); - ensure("a single, not the double: " + luau, in(luau, "(string)0.8414709568023682") && in(luau, "(string)1.4142135381698608")); - ensure("exact, folded: " + luau, in(luau, "(string)1024.0") && in(luau, "(string)5.0")); - ensure("-0 made 0: " + luau, !in(luau, "-0.0")); - ensure("sin of what is no single, which SLua takes as it is, left: " + luau, in(luau, "llSin(0.30000000447034836)")); - ensure("a length no single is, which the simulator gives by a way not known, left: " + luau, in(luau, "llVecMag(")); - } -} // namespace tut + { + INFO("a single, not the double: " + luau); + CHECK(in(luau, "(string)0.8414709568023682")); + CHECK(in(luau, "(string)1.4142135381698608")); + } + { + INFO("exact, folded: " + luau); + CHECK(in(luau, "(string)1024.0")); + CHECK(in(luau, "(string)5.0")); + } + UNSCOPED_INFO("-0 made 0: " + luau); + CHECK_FALSE(in(luau, "-0.0")); + UNSCOPED_INFO("sin of what is no single, which SLua takes as it is, left: " + luau); + CHECK(in(luau, "llSin(0.30000000447034836)")); + UNSCOPED_INFO("a length no single is, which the simulator gives by a way not known, left: " + luau); + CHECK(in(luau, "llVecMag(")); + } +} // anonymous namespace diff --git a/indra/alscript/tests/allslservice_test.cpp b/indra/alscript/tests/allslservice_test.cpp index bfff29bfd8..1fc17cb456 100644 --- a/indra/alscript/tests/allslservice_test.cpp +++ b/indra/alscript/tests/allslservice_test.cpp @@ -27,7 +27,7 @@ #include "../lsl/allslservice.h" #include "../lint/alscriptfixes.h" -#include "../test/lltut.h" +#include #include "llsdserialize.h" #include "lluuid.h" @@ -35,7 +35,7 @@ #include #include -namespace tut +namespace { struct allslservice_data { @@ -81,23 +81,18 @@ namespace tut } }; - typedef test_group allslservice_group; - typedef allslservice_group::object allslservice_object; - allslservice_group allslservice_instance("allslservice"); - - template<> template<> - void allslservice_object::test<1>() + TEST_CASE_METHOD(allslservice_data, "allslservice: the grid's builtins load", "[allslservice]") { - set_test_name("the grid's builtins load"); - ensure("builtins load: " + error, loaded); - ensure("service says so", service.hasBuiltins()); + UNSCOPED_INFO("builtins load: " + error); + CHECK(loaded); + UNSCOPED_INFO("service says so"); + CHECK(service.hasBuiltins()); } - template<> template<> - void allslservice_object::test<2>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a script that is right checks clean", "[allslservice]") { - set_test_name("a script that is right checks clean"); - ensure("builtins load: " + error, loaded); + UNSCOPED_INFO("builtins load: " + error); + CHECK(loaded); ALScriptProblems problems = service.check( "default\n" "{\n" @@ -106,14 +101,14 @@ namespace tut " llSay(0, \"Hello, \" + (string)llGetOwner());\n" " }\n" "}\n"); - ensure_equals("nothing said: " + said(problems), problems.size(), 0); + UNSCOPED_INFO("nothing said: " + said(problems)); + REQUIRE(problems.size() == 0); } - template<> template<> - void allslservice_object::test<3>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a wrong argument type is an error on its line", "[allslservice]") { - set_test_name("a wrong argument type is an error on its line"); - ensure("builtins load: " + error, loaded); + UNSCOPED_INFO("builtins load: " + error); + CHECK(loaded); ALScriptProblems problems = service.check( "default\n" "{\n" @@ -124,25 +119,34 @@ namespace tut "}\n"); // E_ARGUMENT_WRONG_TYPE const ALScriptProblem* problem = coded(problems, "10011"); - ensure("the wrong-type error: " + said(problems), problem != nullptr); - ensure("an error", problem->severity == ALScriptProblem::Severity::Error); - ensure("from the checks", problem->source == ALScriptProblem::Source::Types); - ensure_equals("on the call's line, zero-based", problem->line, 4); + UNSCOPED_INFO("the wrong-type error: " + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("an error"); + CHECK(problem->severity == ALScriptProblem::Severity::Error); + UNSCOPED_INFO("from the checks"); + CHECK(problem->source == ALScriptProblem::Source::Types); + UNSCOPED_INFO("on the call's line, zero-based"); + CHECK(problem->line == 4); // Taken apart by its code, for another language to say: the // words in the order the marks have them. - ensure_equals("the key", problem->key, std::string("LSLArgumentWrongType")); - ensure_equals("five words", problem->args.size(), size_t(5)); - ensure_equals("the type passed", problem->args[0], std::string("string")); - ensure_equals("the argument's number", problem->args[1], std::string("1")); - ensure_equals("the function", problem->args[2], std::string("llSay")); - ensure_equals("put back, the same words", ALScriptProblem::fill("Passing [1] as argument [2] of `[3]' which is declared as `[4] [5]'.", problem->args), problem->message); + UNSCOPED_INFO("the key"); + CHECK(problem->key == std::string("LSLArgumentWrongType")); + UNSCOPED_INFO("five words"); + REQUIRE(problem->args.size() == size_t(5)); + UNSCOPED_INFO("the type passed"); + CHECK(problem->args[0] == std::string("string")); + UNSCOPED_INFO("the argument's number"); + CHECK(problem->args[1] == std::string("1")); + UNSCOPED_INFO("the function"); + CHECK(problem->args[2] == std::string("llSay")); + UNSCOPED_INFO("put back, the same words"); + CHECK(ALScriptProblem::fill("Passing [1] as argument [2] of `[3]' which is declared as `[4] [5]'.", problem->args) == problem->message); } - template<> template<> - void allslservice_object::test<4>() + TEST_CASE_METHOD(allslservice_data, "allslservice: an unused global is a warning", "[allslservice]") { - set_test_name("an unused global is a warning"); - ensure("builtins load: " + error, loaded); + UNSCOPED_INFO("builtins load: " + error); + CHECK(loaded); ALScriptProblems problems = service.check( "integer unused = 1;\n" "default\n" @@ -153,18 +157,22 @@ namespace tut "}\n"); // W_DECLARED_BUT_NOT_USED const ALScriptProblem* problem = coded(problems, "20009"); - ensure("the unused warning: " + said(problems), problem != nullptr); - ensure("a warning", problem->severity == ALScriptProblem::Severity::Warning); - ensure("from the lint", problem->source == ALScriptProblem::Source::Lint); - ensure_equals("on the declaration's line", problem->line, 0); - ensure_equals("no errors", errors(problems), 0); + UNSCOPED_INFO("the unused warning: " + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("a warning"); + CHECK(problem->severity == ALScriptProblem::Severity::Warning); + UNSCOPED_INFO("from the lint"); + CHECK(problem->source == ALScriptProblem::Source::Lint); + UNSCOPED_INFO("on the declaration's line"); + CHECK(problem->line == 0); + UNSCOPED_INFO("no errors"); + CHECK(errors(problems) == 0); } - template<> template<> - void allslservice_object::test<5>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a syntax error is the parser's", "[allslservice]") { - set_test_name("a syntax error is the parser's"); - ensure("builtins load: " + error, loaded); + UNSCOPED_INFO("builtins load: " + error); + CHECK(loaded); ALScriptProblems problems = service.check( "default\n" "{\n" @@ -172,27 +180,30 @@ namespace tut " {\n" " }\n" "}\n"); - ensure("an error: " + said(problems), errors(problems) > 0); - ensure("from the parser", problems.front().source == ALScriptProblem::Source::Parser); - ensure("on a line past the first", problems.front().line > 0); + UNSCOPED_INFO("an error: " + said(problems)); + CHECK(errors(problems) > 0); + UNSCOPED_INFO("from the parser"); + CHECK(problems.front().source == ALScriptProblem::Source::Parser); + UNSCOPED_INFO("on a line past the first"); + CHECK(problems.front().line > 0); } - template<> template<> - void allslservice_object::test<6>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a builtins file that cannot be opened is refused and is not fatal", "[allslservice]") { - set_test_name("a builtins file that cannot be opened is refused, not fatal"); ALLSLService bare; std::string why; - ensure("refused", !bare.loadBuiltins("/nonexistent/builtins.txt", why)); - ensure("with a reason", !why.empty()); - ensure("nothing loaded", !bare.hasBuiltins()); + UNSCOPED_INFO("refused"); + REQUIRE_FALSE(bare.loadBuiltins("/nonexistent/builtins.txt", why)); + UNSCOPED_INFO("with a reason"); + REQUIRE_FALSE(why.empty()); + UNSCOPED_INFO("nothing loaded"); + CHECK_FALSE(bare.hasBuiltins()); } - template<> template<> - void allslservice_object::test<7>() + TEST_CASE_METHOD(allslservice_data, "allslservice: symbols in scope are the script's own and only locals declared before", "[allslservice]") { - set_test_name("the script's symbols in scope: globals, functions, states, and locals declared before the position"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string script = "integer count = 0;\n" "float half(integer n) { return n / 2.0; }\n" @@ -218,26 +229,32 @@ namespace tut } return false; }; - ensure("the global", has("count", ALScriptSymbolKind::Variable, "integer count")); - ensure("the function", has("half", ALScriptSymbolKind::Function, "float half(integer n)")); - ensure("the state", has("other", ALScriptSymbolKind::State, "state other")); - ensure("the event's parameter", has("total", ALScriptSymbolKind::Parameter, "integer total")); - ensure("the local before", has("before", ALScriptSymbolKind::Variable, "string before")); + UNSCOPED_INFO("the global"); + CHECK(has("count", ALScriptSymbolKind::Variable, "integer count")); + UNSCOPED_INFO("the function"); + CHECK(has("half", ALScriptSymbolKind::Function, "float half(integer n)")); + UNSCOPED_INFO("the state"); + CHECK(has("other", ALScriptSymbolKind::State, "state other")); + UNSCOPED_INFO("the event's parameter"); + CHECK(has("total", ALScriptSymbolKind::Parameter, "integer total")); + UNSCOPED_INFO("the local before"); + CHECK(has("before", ALScriptSymbolKind::Variable, "string before")); bool after = false, builtin = false; for (const ALScriptCompletion& c : found) { after = after || c.text == "after"; builtin = builtin || c.text == "llSay"; } - ensure("not the local after", !after); - ensure("not the builtins", !builtin); + UNSCOPED_INFO("not the local after"); + CHECK_FALSE(after); + UNSCOPED_INFO("not the builtins"); + CHECK_FALSE(builtin); } - template<> template<> - void allslservice_object::test<8>() + TEST_CASE_METHOD(allslservice_data, "allslservice: hover shows a declaration and where it is; signature says which argument", "[allslservice]") { - set_test_name("hover reads a symbol's declaration and points at it; a signature says which argument"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string script = "float half(integer n) { return n / 2.0; }\n" "default\n" @@ -248,30 +265,48 @@ namespace tut " }\n" "}\n"; ALScriptHover hover = service.hover(script, 5, 26); // on `half` - ensure("found", hover.found); - ensure_equals("as declared", hover.label, std::string("float half(integer n)")); - ensure("declared on the first line", hover.hasDefinition && hover.definitionLine == 0); + UNSCOPED_INFO("found"); + REQUIRE(hover.found); + UNSCOPED_INFO("as declared"); + CHECK(hover.label == std::string("float half(integer n)")); + { + INFO("declared on the first line"); + CHECK(hover.hasDefinition); + CHECK(hover.definitionLine == 0); + } hover = service.hover(script, 5, 10); // on `llSay` - ensure("a builtin is found", hover.found); - ensure("as declared: " + hover.label, hover.label.find("llSay(") != std::string::npos); - ensure("but not in the script", !hover.hasDefinition); + UNSCOPED_INFO("a builtin is found"); + REQUIRE(hover.found); + UNSCOPED_INFO("as declared: " + hover.label); + REQUIRE(hover.label.find("llSay(") != std::string::npos); + UNSCOPED_INFO("but not in the script"); + CHECK_FALSE(hover.hasDefinition); ALScriptSignature sig = service.signature(script, 5, 15); // in llSay's first argument - ensure("in the call", sig.found); - ensure("the builtin's signature: " + sig.label, sig.label.find("llSay(") == 0 || sig.label.find(" llSay(") != std::string::npos); - ensure_equals("two parameters", sig.parameters.size(), size_t(2)); - ensure_equals("the first", sig.active, 0); + UNSCOPED_INFO("in the call"); + REQUIRE(sig.found); + UNSCOPED_INFO("the builtin's signature: " + sig.label); + REQUIRE((sig.label.find("llSay(") == 0 || sig.label.find(" llSay(") != std::string::npos)); + UNSCOPED_INFO("two parameters"); + REQUIRE(sig.parameters.size() == size_t(2)); + UNSCOPED_INFO("the first"); + CHECK(sig.active == 0); sig = service.signature(script, 5, 30); // just inside half( - ensure("the inner call", sig.found && sig.label == "float half(integer n)"); - ensure_equals("its only parameter", sig.active, 0); - ensure("none at the top", !service.signature(script, 0, 0).found); + { + INFO("the inner call"); + REQUIRE(sig.found); + CHECK(sig.label == "float half(integer n)"); + } + UNSCOPED_INFO("its only parameter"); + CHECK(sig.active == 0); + UNSCOPED_INFO("none at the top"); + REQUIRE_FALSE(service.signature(script, 0, 0).found); } - template<> template<> - void allslservice_object::test<9>() + TEST_CASE_METHOD(allslservice_data, "allslservice: references find a declaration and its uses; only the script's own rename", "[allslservice]") { - set_test_name("references find a symbol's declaration and every use; builtins, events and default are not the script's to rename"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string script = "integer count = 0;\n" "default\n" @@ -288,40 +323,87 @@ namespace tut " state_entry() { state default; }\n" "}\n"; ALScriptReferences refs = service.references(script, 5, 16); // on the second `count` - ensure("found", refs.found && refs.name == "count"); - ensure("a variable", refs.kind == ALScriptSymbolKind::Variable); - ensure(llformat("declared on the first line, not %d:%d-%d (%d)", refs.definition.line, refs.definition.column, refs.definition.endColumn, (int)refs.hasDefinition), - refs.hasDefinition && refs.definition.line == 0 && refs.definition.column == 8 && refs.definition.endColumn == 13); - ensure_equals("four places", refs.references.size(), size_t(4)); - ensure("the declaration first", refs.references[0] == refs.definition); - ensure("the cast's operand last", refs.references[3].line == 6 && refs.references[3].column == 25); - ensure("renamable", refs.renamable); + { + INFO("found"); + REQUIRE(refs.found); + CHECK(refs.name == "count"); + } + UNSCOPED_INFO("a variable"); + CHECK(refs.kind == ALScriptSymbolKind::Variable); + { + INFO(llformat("declared on the first line, not %d:%d-%d (%d)", refs.definition.line, refs.definition.column, refs.definition.endColumn, (int)refs.hasDefinition)); + CHECK(refs.hasDefinition); + CHECK(refs.definition.line == 0); + CHECK(refs.definition.column == 8); + CHECK(refs.definition.endColumn == 13); + } + UNSCOPED_INFO("four places"); + REQUIRE(refs.references.size() == size_t(4)); + UNSCOPED_INFO("the declaration first"); + CHECK(refs.references[0] == refs.definition); + { + INFO("the cast's operand last"); + CHECK(refs.references[3].line == 6); + CHECK(refs.references[3].column == 25); + } + UNSCOPED_INFO("renamable"); + CHECK(refs.renamable); refs = service.references(script, 6, 10); // on llSay - ensure("a builtin is found", refs.found && refs.name == "llSay"); - ensure("used once, declared nowhere here", refs.references.size() == 1 && !refs.hasDefinition && !refs.renamable); + { + INFO("a builtin is found"); + REQUIRE(refs.found); + CHECK(refs.name == "llSay"); + } + { + INFO("used once, declared nowhere here"); + REQUIRE(refs.references.size() == 1); + CHECK_FALSE(refs.hasDefinition); + CHECK_FALSE(refs.renamable); + } refs = service.references(script, 7, 15); // on `other` in `state other;` - ensure("a state is found", refs.found && refs.kind == ALScriptSymbolKind::State); - ensure("declared where it is", refs.hasDefinition && refs.definition.line == 10 && refs.definition.column == 6); - ensure_equals("the declaration and the change", refs.references.size(), size_t(2)); - ensure("renamable", refs.renamable); + { + INFO("a state is found"); + REQUIRE(refs.found); + CHECK(refs.kind == ALScriptSymbolKind::State); + } + { + INFO("declared where it is"); + CHECK(refs.hasDefinition); + CHECK(refs.definition.line == 10); + CHECK(refs.definition.column == 6); + } + UNSCOPED_INFO("the declaration and the change"); + REQUIRE(refs.references.size() == size_t(2)); + UNSCOPED_INFO("renamable"); + CHECK(refs.renamable); refs = service.references(script, 12, 26); // on `default` in `state default;` - ensure("default is found", refs.found && refs.name == "default"); - ensure("but is not the script's to rename", !refs.renamable); + { + INFO("default is found"); + REQUIRE(refs.found); + CHECK(refs.name == "default"); + } + UNSCOPED_INFO("but is not the script's to rename"); + CHECK_FALSE(refs.renamable); refs = service.references(script, 3, 6); // on state_entry - ensure("an event is found", refs.found && refs.kind == ALScriptSymbolKind::Event); - ensure(llformat("in both states, not %d places", (int)refs.references.size()), refs.references.size() == 2); - ensure("not renamable", !refs.renamable); + { + INFO("an event is found"); + REQUIRE(refs.found); + CHECK(refs.kind == ALScriptSymbolKind::Event); + } + UNSCOPED_INFO(llformat("in both states, not %d places", (int)refs.references.size())); + REQUIRE(refs.references.size() == 2); + UNSCOPED_INFO("not renamable"); + CHECK_FALSE(refs.renamable); } - template<> template<> - void allslservice_object::test<10>() + TEST_CASE_METHOD(allslservice_data, "allslservice: the outline lists globals and functions and states; events one deeper", "[allslservice]") { - set_test_name("the outline lists the globals, the functions and the states with their events one deeper"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string script = "integer count = 0;\n" "float half(integer n) { return n / 2.0; }\n" @@ -336,22 +418,62 @@ namespace tut std::vector outline = service.outline(script); std::string names; for (const ALScriptOutlineEntry& e : outline) names += " " + e.name + llformat("@%d", e.depth); - ensure_equals("six entries:" + names, outline.size(), size_t(6)); - ensure("the global", outline[0].name == "count" && outline[0].kind == ALScriptSymbolKind::Variable && outline[0].detail == "integer count" && outline[0].depth == 0); - ensure("the function, as declared", outline[1].name == "half" && outline[1].kind == ALScriptSymbolKind::Function && outline[1].detail == "float half(integer n)"); - ensure("its name where it is", outline[1].nameSpan.line == 1 && outline[1].nameSpan.column == 6 && outline[1].nameSpan.endColumn == 10); - ensure("default, a state", outline[2].name == "default" && outline[2].kind == ALScriptSymbolKind::State && outline[2].depth == 0); - ensure("spanning its lines", outline[2].span.line == 2 && outline[2].span.endLine == 8); - ensure("state_entry, one deeper", outline[3].name == "state_entry" && outline[3].kind == ALScriptSymbolKind::Event && outline[3].depth == 1); - ensure("touch_start with its parameter", outline[4].name == "touch_start" && outline[4].detail == "touch_start(integer total)" && outline[4].span.line == 5 && outline[4].span.endLine == 7); - ensure("the other state", outline[5].name == "other" && outline[5].kind == ALScriptSymbolKind::State); + UNSCOPED_INFO("six entries:" + names); + REQUIRE(outline.size() == size_t(6)); + { + INFO("the global"); + CHECK(outline[0].name == "count"); + CHECK(outline[0].kind == ALScriptSymbolKind::Variable); + CHECK(outline[0].detail == "integer count"); + CHECK(outline[0].depth == 0); + } + { + INFO("the function, as declared"); + CHECK(outline[1].name == "half"); + CHECK(outline[1].kind == ALScriptSymbolKind::Function); + CHECK(outline[1].detail == "float half(integer n)"); + } + { + INFO("its name where it is"); + CHECK(outline[1].nameSpan.line == 1); + CHECK(outline[1].nameSpan.column == 6); + CHECK(outline[1].nameSpan.endColumn == 10); + } + { + INFO("default, a state"); + CHECK(outline[2].name == "default"); + CHECK(outline[2].kind == ALScriptSymbolKind::State); + CHECK(outline[2].depth == 0); + } + { + INFO("spanning its lines"); + CHECK(outline[2].span.line == 2); + CHECK(outline[2].span.endLine == 8); + } + { + INFO("state_entry, one deeper"); + CHECK(outline[3].name == "state_entry"); + CHECK(outline[3].kind == ALScriptSymbolKind::Event); + CHECK(outline[3].depth == 1); + } + { + INFO("touch_start with its parameter"); + CHECK(outline[4].name == "touch_start"); + CHECK(outline[4].detail == "touch_start(integer total)"); + CHECK(outline[4].span.line == 5); + CHECK(outline[4].span.endLine == 7); + } + { + INFO("the other state"); + CHECK(outline[5].name == "other"); + CHECK(outline[5].kind == ALScriptSymbolKind::State); + } } - template<> template<> - void allslservice_object::test<11>() + TEST_CASE_METHOD(allslservice_data, "allslservice: semantic tokens tell every name by what it is", "[allslservice]") { - set_test_name("every name is told by what it is: global, parameter, local, function, builtin, state, event, label"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string script = "integer count = 0;\n" "float half(integer n) { return n / 2.0; }\n" @@ -378,38 +500,93 @@ namespace tut std::string listed; for (const ALScriptSemanticToken& t : tokens) listed += llformat(" %d:%d/%d+%d", t.span.line, t.span.column, (int)t.kind, (int)t.modifiers); const ALScriptSemanticToken* count = at(0, 8); - ensure("count, a global declared:" + listed, count && count->kind == ALScriptSymbolKind::Variable && (count->modifiers & ALScriptSemanticToken::Global) && (count->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("count, a global declared:" + listed); + REQUIRE(count); + REQUIRE(count->kind == ALScriptSymbolKind::Variable); + REQUIRE(count->modifiers & ALScriptSemanticToken::Global); + CHECK(count->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* half = at(1, 6); - ensure("half, a function declared:" + listed, half && half->kind == ALScriptSymbolKind::Function && (half->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("half, a function declared:" + listed); + REQUIRE(half); + REQUIRE(half->kind == ALScriptSymbolKind::Function); + CHECK(half->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* n = at(1, 19); - ensure("n, a parameter declared:" + listed, n && n->kind == ALScriptSymbolKind::Parameter && (n->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("n, a parameter declared:" + listed); + REQUIRE(n); + REQUIRE(n->kind == ALScriptSymbolKind::Parameter); + CHECK(n->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* touch = at(4, 4); - ensure("touch_start, an event:" + listed, touch && touch->kind == ALScriptSymbolKind::Event); + { + INFO("touch_start, an event:" + listed); + REQUIRE(touch); + CHECK(touch->kind == ALScriptSymbolKind::Event); + } const ALScriptSemanticToken* h = at(6, 14); - ensure("h, a local declared:" + listed, h && h->kind == ALScriptSymbolKind::Variable && !(h->modifiers & ALScriptSemanticToken::Global) && (h->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("h, a local declared:" + listed); + REQUIRE(h); + REQUIRE(h->kind == ALScriptSymbolKind::Variable); + REQUIRE_FALSE(h->modifiers & ALScriptSemanticToken::Global); + CHECK(h->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* total = at(6, 23); - ensure("total used, a parameter:" + listed, total && total->kind == ALScriptSymbolKind::Parameter && !(total->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("total used, a parameter:" + listed); + REQUIRE(total); + REQUIRE(total->kind == ALScriptSymbolKind::Parameter); + CHECK_FALSE(total->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* say = at(7, 8); - ensure("llSay, a builtin function:" + listed, say && say->kind == ALScriptSymbolKind::Function && (say->modifiers & ALScriptSemanticToken::Builtin)); + { + INFO("llSay, a builtin function:" + listed); + REQUIRE(say); + REQUIRE(say->kind == ALScriptSymbolKind::Function); + CHECK(say->modifiers & ALScriptSemanticToken::Builtin); + } const ALScriptSemanticToken* channel = at(7, 14); - ensure("PUBLIC_CHANNEL, a builtin constant:" + listed, channel && channel->kind == ALScriptSymbolKind::Constant && (channel->modifiers & ALScriptSemanticToken::ReadOnly)); + { + INFO("PUBLIC_CHANNEL, a builtin constant:" + listed); + REQUIRE(channel); + REQUIRE(channel->kind == ALScriptSymbolKind::Constant); + CHECK(channel->modifiers & ALScriptSemanticToken::ReadOnly); + } const ALScriptSemanticToken* label = at(8, 9); - ensure("again, a label declared:" + listed, label && label->kind == ALScriptSymbolKind::Label && (label->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("again, a label declared:" + listed); + REQUIRE(label); + REQUIRE(label->kind == ALScriptSymbolKind::Label); + CHECK(label->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* jump = at(9, 24); - ensure("jumped to, not declared:" + listed, jump && jump->kind == ALScriptSymbolKind::Label && !(jump->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("jumped to, not declared:" + listed); + REQUIRE(jump); + REQUIRE(jump->kind == ALScriptSymbolKind::Label); + CHECK_FALSE(jump->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* other = at(10, 14); - ensure("other, a state:" + listed, other && other->kind == ALScriptSymbolKind::State); + { + INFO("other, a state:" + listed); + REQUIRE(other); + CHECK(other->kind == ALScriptSymbolKind::State); + } for (size_t i = 1; i < tokens.size(); ++i) { - ensure("in order, each once", tokens[i - 1].span < tokens[i].span); + UNSCOPED_INFO("in order, each once"); + REQUIRE(tokens[i - 1].span < tokens[i].span); } } - template<> template<> - void allslservice_object::test<12>() + TEST_CASE_METHOD(allslservice_data, "allslservice: inlay hints name each argument's parameter in builtin and own calls", "[allslservice]") { - set_test_name("inlay hints name each argument's parameter, builtin or the script's own"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string script = "float half(integer n) { return n / 2.0; }\n" "default\n" @@ -431,18 +608,22 @@ namespace tut } return false; }; - ensure("the channel's name before the 0:" + listed, has(6, 14, "channel:")); - ensure("the message's before the cast:" + listed, has(6, 17, "msg:")); - ensure("nothing before n, which is the name:" + listed, !has(6, 30, "n:")); - ensure("n: before the 2:" + listed, has(7, 29, "n:")); - ensure("nothing when not asked", service.inlayHints(script, false).empty()); + UNSCOPED_INFO("the channel's name before the 0:" + listed); + CHECK(has(6, 14, "channel:")); + UNSCOPED_INFO("the message's before the cast:" + listed); + CHECK(has(6, 17, "msg:")); + UNSCOPED_INFO("nothing before n, which is the name:" + listed); + CHECK_FALSE(has(6, 30, "n:")); + UNSCOPED_INFO("n: before the 2:" + listed); + CHECK(has(7, 29, "n:")); + UNSCOPED_INFO("nothing when not asked"); + REQUIRE(service.inlayHints(script, false).empty()); } - template<> template<> - void allslservice_object::test<13>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a script mid-edit is answered about the rest of it", "[allslservice]") { - set_test_name("a script mid-edit is answered about the rest of it: the scope, the call being typed, what every name is"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string head = "integer count = 0;\n" "float half(integer n) { return n / 2.0; }\n" @@ -464,63 +645,99 @@ namespace tut // A call not yet closed, the caret after an argument half typed. std::string script = head + " llSay(0, be" + tail; ALScriptSignature sig = service.signature(script, 7, 19); - ensure("the call is found", sig.found && sig.label.find("llSay(") != std::string::npos); - ensure_equals("at its second parameter", sig.active, 1); - ensure("the text does not parse", !service.parsed()); - ensure("but was understood", service.understood()); + { + INFO("the call is found"); + REQUIRE(sig.found); + REQUIRE(sig.label.find("llSay(") != std::string::npos); + } + UNSCOPED_INFO("at its second parameter"); + CHECK(sig.active == 1); + UNSCOPED_INFO("the text does not parse"); + CHECK_FALSE(service.parsed()); + UNSCOPED_INFO("but was understood"); + CHECK(service.understood()); const std::string in_scope = names(script, 7, 17); - ensure("the local before, in scope:" + in_scope, in_scope.find(" before ") != std::string::npos); - ensure("the parameter:" + in_scope, in_scope.find(" total ") != std::string::npos); - ensure("the global:" + in_scope, in_scope.find(" count ") != std::string::npos); + UNSCOPED_INFO("the local before, in scope:" + in_scope); + REQUIRE(in_scope.find(" before ") != std::string::npos); + UNSCOPED_INFO("the parameter:" + in_scope); + REQUIRE(in_scope.find(" total ") != std::string::npos); + UNSCOPED_INFO("the global:" + in_scope); + REQUIRE(in_scope.find(" count ") != std::string::npos); // The bracket just typed, and a comma just typed. sig = service.signature(head + " llSay(" + tail, 7, 14); - ensure("just opened", sig.found); - ensure_equals("at the first parameter", sig.active, 0); + UNSCOPED_INFO("just opened"); + REQUIRE(sig.found); + UNSCOPED_INFO("at the first parameter"); + CHECK(sig.active == 0); sig = service.signature(head + " llSay(0," + tail, 7, 16); - ensure("after the comma", sig.found); - ensure_equals("at the second parameter", sig.active, 1); + UNSCOPED_INFO("after the comma"); + REQUIRE(sig.found); + UNSCOPED_INFO("at the second parameter"); + CHECK(sig.active == 1); sig = service.signature(head + " llSay(0, \"hel" + tail, 7, 20); - ensure("inside a string not yet closed", sig.found && sig.active == 1); + { + INFO("inside a string not yet closed"); + REQUIRE(sig.found); + CHECK(sig.active == 1); + } sig = service.signature(head + " half(" + tail, 7, 13); - ensure("the script's own function", sig.found && sig.label == "float half(integer n)"); + { + INFO("the script's own function"); + REQUIRE(sig.found); + CHECK(sig.label == "float half(integer n)"); + } // A name alone, and a statement without its semicolon. - ensure("a word alone", names(head + " bef" + tail, 7, 8).find(" before ") != std::string::npos); - ensure("no semicolon", names(head + " llSay(0, before)" + tail, 7, 17).find(" before ") != std::string::npos); + UNSCOPED_INFO("a word alone"); + REQUIRE(names(head + " bef" + tail, 7, 8).find(" before ") != std::string::npos); + UNSCOPED_INFO("no semicolon"); + REQUIRE(names(head + " llSay(0, before)" + tail, 7, 17).find(" before ") != std::string::npos); // What the check's other questions answer of a text that does not // parse: the rest of it, rather than nothing. script = head + " llSay(0, be" + tail; - ensure("the outline", service.outline(script).size() == 4); + UNSCOPED_INFO("the outline"); + REQUIRE(service.outline(script).size() == 4); const std::vector tokens = service.semanticTokens(script); bool before_named = false; for (const ALScriptSemanticToken& token : tokens) { before_named = before_named || (token.span.line == 6 && token.kind == ALScriptSymbolKind::Variable); } - ensure("the names before the break", before_named); - ensure("the hints", !service.inlayHints(head + " llSay(0, \"x\");\n llOwnerSay(" + tail, true).empty()); + UNSCOPED_INFO("the names before the break"); + CHECK(before_named); + UNSCOPED_INFO("the hints"); + REQUIRE_FALSE(service.inlayHints(head + " llSay(0, \"x\");\n llOwnerSay(" + tail, true).empty()); // A function being declared at the top, before the states. script = "integer count = 0;\nfoo(\ndefault\n{\n state_entry()\n {\n llSay(0, (string)count);\n }\n}\n"; const ALScriptHover hover = service.hover(script, 6, 26); - ensure("a name below a broken declaration", hover.found && hover.label == "integer count"); - ensure("its references", service.references(script, 6, 26).references.size() == 2); + { + INFO("a name below a broken declaration"); + REQUIRE(hover.found); + CHECK(hover.label == "integer count"); + } + UNSCOPED_INFO("its references"); + REQUIRE(service.references(script, 6, 26).references.size() == 2); // A block left open at the end. script = head + " llSay(0, before);\n"; - ensure("closed at the end", names(script, 7, 17).find(" before ") != std::string::npos); + UNSCOPED_INFO("closed at the end"); + REQUIRE(names(script, 7, 17).find(" before ") != std::string::npos); // Nothing to mend from. - ensure("nonsense is not understood", service.outline("}}} ((( ;;; \"").empty() && !service.understood()); + { + INFO("nonsense is not understood"); + REQUIRE(service.outline("}}} ((( ;;; \"").empty()); + CHECK_FALSE(service.understood()); + } } - template<> template<> - void allslservice_object::test<14>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a syntax error says what is missing and where or what was unexpected", "[allslservice]") { - set_test_name("a syntax error says what is missing where it is missing, or what was unexpected, keyed"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string head = "default\n{\n state_entry()\n {\n"; auto first_error = [&](const std::string& script) { for (const ALScriptProblem& problem : service.check(script)) @@ -534,44 +751,73 @@ namespace tut }; // A call not closed, the block closed on the next line. ALScriptProblem p = first_error(head + " llSay(0, \"hi\"\n }\n}\n"); - ensure_equals("missing the bracket: " + p.message, p.message, std::string("Missing ')'.")); - ensure("keyed", p.key == "LSLSyntaxMissing" && p.args.size() == 1 && p.args[0] == "')'"); - ensure("on the string's closing quote, not the next line's brace", p.line == 4 && p.column == 20 && p.endColumn == 21); + UNSCOPED_INFO("missing the bracket: " + p.message); + CHECK(p.message == std::string("Missing ')'.")); + { + INFO("keyed"); + CHECK(p.key == "LSLSyntaxMissing"); + REQUIRE(p.args.size() == 1); + CHECK(p.args[0] == "')'"); + } + { + INFO("on the string's closing quote, not the next line's brace"); + CHECK(p.line == 4); + CHECK(p.column == 20); + CHECK(p.endColumn == 21); + } // A statement not ended. p = first_error(head + " llSay(0, \"hi\")\n llOwnerSay(\"x\");\n }\n}\n"); - ensure_equals("missing the semicolon: " + p.message, p.message, std::string("Missing ';'.")); - ensure("after the call", p.line == 4 && p.column == 21); + UNSCOPED_INFO("missing the semicolon: " + p.message); + CHECK(p.message == std::string("Missing ';'.")); + { + INFO("after the call"); + CHECK(p.line == 4); + CHECK(p.column == 21); + } p = first_error(head + " integer x = 5 // five\n }\n}\n"); - ensure_equals("past a comment: " + p.message, p.message, std::string("Missing ';'.")); - ensure("on the number", p.line == 4 && p.column == 20); + UNSCOPED_INFO("past a comment: " + p.message); + CHECK(p.message == std::string("Missing ';'.")); + { + INFO("on the number"); + CHECK(p.line == 4); + CHECK(p.column == 20); + } // Something that is no statement's next word. p = first_error("integer x = 1;\nfoo(\ndefault\n{\n state_entry() { }\n}\n"); - ensure("unexpected, by what it is: " + p.message, p.key == "LSLSyntaxUnexpected" || p.key == "LSLSyntaxUnexpectedWanted"); - ensure("said as written: " + p.message, p.message.find("'default'") != std::string::npos); - ensure("no bison left in it: " + p.message, p.message.find("syntax error") == std::string::npos && p.message.find("STATE_DEFAULT") == std::string::npos); + UNSCOPED_INFO("unexpected, by what it is: " + p.message); + CHECK((p.key == "LSLSyntaxUnexpected" || p.key == "LSLSyntaxUnexpectedWanted")); + UNSCOPED_INFO("said as written: " + p.message); + REQUIRE(p.message.find("'default'") != std::string::npos); + { + INFO("no bison left in it: " + p.message); + REQUIRE(p.message.find("syntax error") == std::string::npos); + REQUIRE(p.message.find("STATE_DEFAULT") == std::string::npos); + } } - template<> template<> - void allslservice_object::test<15>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a text checked again says the same after other questions or targets", "[allslservice]") { - set_test_name("the same text checked again says what it says once, whatever was asked between, for either target"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string script = "integer unused;\nlist g = [1, 2];\ninteger h = g;\ndefault\n{\n state_entry()\n {\n integer never;\n }\n}\n"; const ALScriptProblems first = service.check(script, true); - ensure("something to say: " + said(first), !first.empty()); + UNSCOPED_INFO("something to say: " + said(first)); + REQUIRE_FALSE(first.empty()); service.outline(script); service.hover(script, 0, 9); const ALScriptProblems again = service.check(script, true); - ensure_equals("said once, the second time too", said(again), said(first)); + UNSCOPED_INFO("said once, the second time too"); + CHECK(said(again) == said(first)); const ALScriptProblems lso = service.check(script, false); const ALScriptProblems back = service.check(script, true); - ensure_equals("and after the other target, nothing of it left", said(back), said(first)); - ensure_equals("the other target, asked twice, the same", said(service.check(script, false)), said(lso)); + UNSCOPED_INFO("and after the other target, nothing of it left"); + CHECK(said(back) == said(first)); + UNSCOPED_INFO("the other target, asked twice, the same"); + CHECK(said(service.check(script, false)) == said(lso)); } - template<> template<> - void allslservice_object::test<16>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a parameter without its type is named; the unexpected is said plainly", "[allslservice]") { - set_test_name("a parameter written without its type says so by name, a function's or an event's, and what was not expected is said plainly"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); auto first_error = [&](const std::string& script) { for (const ALScriptProblem& problem : service.check(script)) { @@ -585,23 +831,48 @@ namespace tut const std::string tail = "default\n{\n state_entry() { }\n}\n"; // As Nexii's linkset library has it. ALScriptProblem p = first_error("list ObjectLinksetSittingAvatars(object) {\n return [];\n}\n" + tail); - ensure_equals("the first: " + p.message, p.message, + UNSCOPED_INFO("the first: " + p.message); + CHECK(p.message == std::string("The parameter 'object' needs its type before it: integer, float, string, key, vector, rotation or list.")); - ensure("keyed", p.key == "LSLParameterUntyped" && p.args.size() == 1 && p.args[0] == "object"); - ensure("on the name, whole", p.line == 0 && p.column == 33 && p.endLine == 0 && p.endColumn == 39); + { + INFO("keyed"); + CHECK(p.key == "LSLParameterUntyped"); + REQUIRE(p.args.size() == 1); + CHECK(p.args[0] == "object"); + } + { + INFO("on the name, whole"); + CHECK(p.line == 0); + CHECK(p.column == 33); + CHECK(p.endLine == 0); + CHECK(p.endColumn == 39); + } p = first_error("f(integer a, b) { }\n" + tail); - ensure("after a comma: " + p.message, p.key == "LSLParameterUntyped" && p.args[0] == "b" && p.column == 13); + { + INFO("after a comma: " + p.message); + CHECK(p.key == "LSLParameterUntyped"); + CHECK(p.args[0] == "b"); + CHECK(p.column == 13); + } p = first_error("default\n{\n touch_start(total)\n {\n }\n}\n"); - ensure("an event's: " + p.message, p.key == "LSLParameterUntyped" && p.args[0] == "total" && p.line == 2); + { + INFO("an event's: " + p.message); + CHECK(p.key == "LSLParameterUntyped"); + CHECK(p.args[0] == "total"); + CHECK(p.line == 2); + } // A name where none can go, not after a bracket. p = first_error("default\n{\n state_entry()\n {\n llSay(0, \"x\") y;\n }\n}\n"); - ensure("not a parameter: " + p.message, p.key != "LSLParameterUntyped"); - ensure("said plainly: " + p.message, p.message.find("Unexpected a") == std::string::npos && p.message.find("syntax error") == std::string::npos); + UNSCOPED_INFO("not a parameter: " + p.message); + CHECK(p.key != "LSLParameterUntyped"); + { + INFO("said plainly: " + p.message); + REQUIRE(p.message.find("Unexpected a") == std::string::npos); + REQUIRE(p.message.find("syntax error") == std::string::npos); + } } - template<> template<> - void allslservice_object::test<17>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a definitions file with lines the engine cannot read loads the rest", "[allslservice]") { - set_test_name("a definitions file with lines the engine cannot read loads the rest, and the process goes on"); // What a grid newer than this viewer may send: a type the engine does // not know, a constant it cannot read, blank lines of CR LF and of // blanks -- each of which ended the process before. @@ -622,26 +893,28 @@ namespace tut const bool read = service.loadBuiltins(path, why); std::error_code gone; std::filesystem::remove(fsyspath(path), gone); - ensure("loaded: " + why, read); + UNSCOPED_INFO("loaded: " + why); + CHECK(read); const std::string head = "default\n{\n state_entry()\n {\n"; const std::string tail = " }\n}\n"; const ALScriptProblems kept = service.check(head + " llOwnerSay((string)(llAlchemyProbe(1) + llAlchemyAfter(\"x\")));\n" + tail, true); - ensure_equals("the lines it could read are known: " + said(kept), errors(kept), size_t(0)); + UNSCOPED_INFO("the lines it could read are known: " + said(kept)); + CHECK(errors(kept) == size_t(0)); const ALScriptProblems left = service.check(head + " llAlchemyBadReturn();\n" + tail, true); - ensure("a line it could not read is not: " + said(left), errors(left) > 0); + UNSCOPED_INFO("a line it could not read is not: " + said(left)); + CHECK(errors(left) > 0); } - template<> template<> - void allslservice_object::test<18>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a definitions file under a folder named outside the ANSI code page loads", "[allslservice]") { - set_test_name("a definitions file under a folder named outside the ANSI code page loads"); // The grid's definitions are kept in the cache, under the user's // profile, whose name may be in any script: the path is UTF-8, and // on Windows the engine opened it as the ANSI code page read it. const fsyspath dir = std::filesystem::temp_directory_path() / fsyspath("al_builtins_\xc3\xa9\xe6\x97\xa5_" + LLUUID::generateNewID().asString()); std::error_code made; std::filesystem::create_directories(dir, made); - ensure("folder made: " + made.message(), !made); + UNSCOPED_INFO("folder made: " + made.message()); + REQUIRE_FALSE(made); const std::string path = fsyspath(dir / "builtins.txt").string(); { llofstream out(path, std::ios::binary); @@ -651,15 +924,15 @@ namespace tut const bool read = service.loadBuiltins(path, why); std::error_code gone; std::filesystem::remove_all(dir, gone); - ensure("loaded: " + why, read); + UNSCOPED_INFO("loaded: " + why); + CHECK(read); const ALScriptProblems kept = service.check("default\n{\n state_entry()\n {\n llOwnerSay((string)llAlchemyWide(1));\n }\n}\n", true); - ensure_equals("what it defines is known: " + said(kept), errors(kept), size_t(0)); + UNSCOPED_INFO("what it defines is known: " + said(kept)); + CHECK(errors(kept) == size_t(0)); } - template<> template<> - void allslservice_object::test<19>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a broken text is mended once whole and once at the caret and no more", "[allslservice]") { - set_test_name("a text that does not parse is mended once whole and once at the caret, however the questions take turns"); const std::string broken = "integer count;\ndefault {\n state_entry() {\n llSay(0, (string)count\n }\n}\n"; const size_t before = service.mendings(); // What the check asks after it, whole; a call's parameters, at the @@ -667,7 +940,8 @@ namespace tut service.outline(broken); service.signature(broken, 3, 20); const size_t twice = service.mendings(); - ensure_equals("one each", twice - before, size_t(2)); + UNSCOPED_INFO("one each"); + CHECK(twice - before == size_t(2)); for (int turn = 0; turn < 3; ++turn) { service.outline(broken); @@ -675,15 +949,15 @@ namespace tut service.hover(broken, 3, 20); service.symbols(broken, 3, 20); } - ensure_equals("and no more, turn and turn about", service.mendings(), twice); + UNSCOPED_INFO("and no more, turn and turn about"); + CHECK(service.mendings() == twice); service.signature(broken, 3, 12); - ensure_equals("another place is mended again", service.mendings(), twice + 1); + UNSCOPED_INFO("another place is mended again"); + CHECK(service.mendings() == twice + 1); } - template<> template<> - void allslservice_object::test<20>() + TEST_CASE_METHOD(allslservice_data, "allslservice: lines passed over get no semantic tokens and their problems no fixes", "[allslservice]") { - set_test_name("lines nobody reads -- an include's -- are passed over by the names and the hints, and their problems offered no fixes"); // An include's function, unused, then the script's own. const std::string text = "helper(integer count)\n{\n llOwnerSay((string)count);\n}\n" "default\n{\n state_entry()\n {\n integer unused = 1;\n llOwnerSay(\"x\");\n }\n}\n"; @@ -702,38 +976,43 @@ namespace tut own_fixed = !problem.fixes.empty(); } } - ensure("the include's unused function said", helper_said); - ensure("but offered nothing", !helper_fixed); - ensure("the script's own offered its fix", own_fixed); + UNSCOPED_INFO("the include's unused function said"); + CHECK(helper_said); + UNSCOPED_INFO("but offered nothing"); + CHECK_FALSE(helper_fixed); + UNSCOPED_INFO("the script's own offered its fix"); + CHECK(own_fixed); bool in_include = false; for (const ALScriptSemanticToken& token : service.semanticTokens(text)) { in_include |= token.span.line <= 3; } - ensure("no names coloured in the include", !in_include); + UNSCOPED_INFO("no names coloured in the include"); + CHECK_FALSE(in_include); service.setPassedOver({}); bool coloured = false; for (const ALScriptSemanticToken& token : service.semanticTokens(text)) { coloured |= token.span.line <= 3; } - ensure("with nothing passed over, coloured", coloured); + UNSCOPED_INFO("with nothing passed over, coloured"); + CHECK(coloured); } - template<> template<> - void allslservice_object::test<21>() + TEST_CASE_METHOD(allslservice_data, "allslservice: another region's builtins: new ones known after and none declared twice", "[allslservice]") { - set_test_name("another region's builtins: what it adds is known after, what both have stays as first loaded, and nothing is said twice"); const std::string base = std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt"; std::string error; - ensure("the first region's: " + error, service.loadBuiltins(base, error)); + UNSCOPED_INFO("the first region's: " + error); + REQUIRE(service.loadBuiltins(base, error)); 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)) { unknown |= problem.message.find("llBrandNewThing") != std::string::npos; } - ensure("not yet known", unknown); + UNSCOPED_INFO("not yet known"); + CHECK(unknown); // A region with one function more. std::string text; { @@ -751,42 +1030,53 @@ namespace tut } const bool loaded = service.loadBuiltins(path, error); LLFile::remove(path); - ensure("loaded: " + error, loaded); + UNSCOPED_INFO("loaded: " + error); + CHECK(loaded); unknown = false; for (const ALScriptProblem& problem : service.check(script)) { unknown |= problem.message.find("llBrandNewThing") != std::string::npos; } - ensure("known now", !unknown); + UNSCOPED_INFO("known now"); + CHECK_FALSE(unknown); // Nothing twice: an old one still one declaration. const ALScriptSignature said = service.signature("default { state_entry() { llAbs( } }\n", 0, 32); - ensure("an old function as it was", said.found && said.label.find("llAbs") != std::string::npos); + { + INFO("an old function as it was"); + REQUIRE(said.found); + REQUIRE(said.label.find("llAbs") != std::string::npos); + } } - template<> template<> - void allslservice_object::test<22>() + TEST_CASE_METHOD(allslservice_data, "allslservice: every template for a new LSL script checks without an error", "[allslservice]") { - set_test_name("every template Script Studio offers for a new LSL script checks without an error"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); llifstream in(std::string(AL_SCRIPT_TEMPLATES_DIR) + "/lsl.xml", std::ios::in | std::ios::binary); LLSD templates; - ensure("read", in.is_open() && LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE && templates.isArray()); - ensure("some", templates.size() > 0); + { + INFO("read"); + CHECK(in.is_open()); + CHECK(LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE); + REQUIRE(templates.isArray()); + } + UNSCOPED_INFO("some"); + REQUIRE(templates.size() > 0); for (LLSD::array_const_iterator it = templates.beginArray(); it != templates.endArray(); ++it) { const std::string name = (*it)["name"].asString(); const ALScriptProblems problems = service.check((*it)["body"].asString()); - ensure(name + " parses", service.parsed()); - ensure_equals(name + ": " + said(problems), errors(problems), size_t(0)); + UNSCOPED_INFO(name + " parses"); + REQUIRE(service.parsed()); + UNSCOPED_INFO(name + ": " + said(problems)); + REQUIRE(errors(problems) == size_t(0)); } } - template<> template<> - void allslservice_object::test<23>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a handler or state begun in an open block is a missing brace before it", "[allslservice]") { - set_test_name("a handler or a state begun inside a block left open is a missing brace, after the last code before it, not what the " - "parser wanted of the head it misread"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); auto first_error = [&](const std::string& script) { for (const ALScriptProblem& problem : service.check(script)) { @@ -800,27 +1090,51 @@ namespace tut const std::string open = "default\n{\n state_entry()\n {\n llSay(0, \"Hello, Avatar!\");\n\n"; // A handler with parameters, read as a call. ALScriptProblem p = first_error(open + " touch_start(integer total_number)\n {\n llSay(0, \"Touched.\");\n }\n}\n"); - ensure_equals("the brace: " + p.message, p.message, std::string("Missing '}'.")); - ensure("keyed", p.key == "LSLSyntaxMissing" && p.args.size() == 1 && p.args[0] == "'}'"); - ensure("on the semicolon before it", p.line == 4 && p.column == 34 && p.endColumn == 35); + UNSCOPED_INFO("the brace: " + p.message); + CHECK(p.message == std::string("Missing '}'.")); + { + INFO("keyed"); + CHECK(p.key == "LSLSyntaxMissing"); + REQUIRE(p.args.size() == 1); + CHECK(p.args[0] == "'}'"); + } + { + INFO("on the semicolon before it"); + CHECK(p.line == 4); + CHECK(p.column == 34); + CHECK(p.endColumn == 35); + } // One without, its brace on the next line. p = first_error(open + " touch_start()\n {\n }\n}\n"); - ensure_equals("the brace, a head without parameters: " + p.message, p.message, std::string("Missing '}'.")); - ensure("on the same semicolon", p.line == 4 && p.column == 34); + UNSCOPED_INFO("the brace, a head without parameters: " + p.message); + CHECK(p.message == std::string("Missing '}'.")); + { + INFO("on the same semicolon"); + CHECK(p.line == 4); + CHECK(p.column == 34); + } // A state after one not closed. p = first_error("default\n{\n state_entry()\n {\n }\n\nstate other\n{\n state_entry()\n {\n }\n}\n"); - ensure_equals("the state's brace: " + p.message, p.message, std::string("Missing '}'.")); - ensure("after the handler's", p.line == 4 && p.column == 4); + UNSCOPED_INFO("the state's brace: " + p.message); + CHECK(p.message == std::string("Missing '}'.")); + { + INFO("after the handler's"); + CHECK(p.line == 4); + CHECK(p.column == 4); + } // A handler's own head wrong where handlers go is not one. p = first_error("default\n{\n touch_start(integer)\n {\n }\n}\n"); - ensure("a head wrong in its state's braces: not a brace", p.severity == ALScriptProblem::Severity::Error && p.message != "Missing '}'."); + { + INFO("a head wrong in its state's braces: not a brace"); + CHECK(p.severity == ALScriptProblem::Severity::Error); + CHECK(p.message != "Missing '}'."); + } } - template<> template<> - void allslservice_object::test<24>() + TEST_CASE_METHOD(allslservice_data, "allslservice: SlLoopInvariantCall notes a loop check's invariant call; NOLINT by name", "[allslservice]") { - set_test_name("the studio's own LSL lints beside Tailslide's: a pure call in a loop's check whose arguments the loop leaves be, noted once, and answering a NOLINT by its name"); - ensure("builtins load: " + error, loaded); + UNSCOPED_INFO("builtins load: " + error); + CHECK(loaded); const auto found = [&](const std::string& text) { ALScriptProblems out; for (const ALScriptProblem& p : service.check(text)) @@ -839,14 +1153,23 @@ namespace tut " while (i < llStringLength(s)) ++i;\n" " do ++i; while (i < llGetListLength(llParseString2List(s, [\",\"], [])));\n" "} }\n"); - ensure_equals("three: each loop's once", said.size(), size_t(3)); - ensure("a note keyed as Tailslide's, by the function: " + said[0].message, - said[0].key == "LSLSlLoopInvariantCall" && said[0].severity == ALScriptProblem::Severity::Note && said[0].line == 3 && - said[0].args == std::vector{ "llGetListLength", "integer", "3", "4" } && - said[0].source == ALScriptProblem::Source::Lint); - ensure("the outer call of two: " + said[2].message, said[2].line == 5 && said[2].args[0] == "llGetListLength"); - ensure("a NOLINT answers to the rule's name", - ALScriptFixes::suppressed(said[0], " for (i = 0; i < llGetListLength(gL); ++i) // NOLINT(SlLoopInvariantCall)", "", false)); + UNSCOPED_INFO("three: each loop's once"); + REQUIRE(said.size() == size_t(3)); + { + INFO("a note keyed as Tailslide's, by the function: " + said[0].message); + CHECK(said[0].key == "LSLSlLoopInvariantCall"); + CHECK(said[0].severity == ALScriptProblem::Severity::Note); + CHECK(said[0].line == 3); + CHECK(said[0].args == std::vector{ "llGetListLength", "integer", "3", "4" }); + CHECK(said[0].source == ALScriptProblem::Source::Lint); + } + { + INFO("the outer call of two: " + said[2].message); + CHECK(said[2].line == 5); + CHECK(said[2].args[0] == "llGetListLength"); + } + UNSCOPED_INFO("a NOLINT answers to the rule's name"); + CHECK(ALScriptFixes::suppressed(said[0], " for (i = 0; i < llGetListLength(gL); ++i) // NOLINT(SlLoopInvariantCall)", "", false)); said = found("list gL = [1, 2];\n" "grow() { gL += [0]; }\n" @@ -856,14 +1179,14 @@ namespace tut " for (i = 0; i < llGetListLength(gL) && i < 10; ++i) grow();\n" " while (llGetUnixTime() < 5) ++i;\n" "} }\n"); - ensure("not where the loop changes what it is given, itself or by a function, nor for a call that reads the world", said.empty()); + UNSCOPED_INFO("not where the loop changes what it is given, itself or by a function, nor for a call that reads the world"); + REQUIRE(said.empty()); } - template<> template<> - void allslservice_object::test<25>() + TEST_CASE_METHOD(allslservice_data, "allslservice: a shape-only outline drops details and mends apart from the kept tree", "[allslservice]") { - set_test_name("the shape alone, from a parse: the outline but for the words of each declaration, mended where it must be, the tree kept left be"); - ensure("builtins loaded: " + error, loaded); + UNSCOPED_INFO("builtins loaded: " + error); + CHECK(loaded); const std::string script = "integer count = 0;\n" "float half(integer n) { return n / 2.0; }\n" @@ -875,19 +1198,35 @@ namespace tut "}\n"; const std::vector whole = service.outline(script); const std::vector shape = service.outline(script, false); - ensure_equals("as many", shape.size(), whole.size()); + UNSCOPED_INFO("as many"); + REQUIRE(shape.size() == whole.size()); for (size_t i = 0; i < shape.size(); ++i) { - ensure("the same " + whole[i].name, shape[i].name == whole[i].name && shape[i].kind == whole[i].kind && shape[i].depth == whole[i].depth && - shape[i].span == whole[i].span && shape[i].nameSpan == whole[i].nameSpan); - ensure("no words of its declaration: " + shape[i].detail, shape[i].detail.empty()); + { + INFO("the same " + whole[i].name); + REQUIRE(shape[i].name == whole[i].name); + REQUIRE(shape[i].kind == whole[i].kind); + REQUIRE(shape[i].depth == whole[i].depth); + REQUIRE(shape[i].span == whole[i].span); + REQUIRE(shape[i].nameSpan == whole[i].nameSpan); + } + UNSCOPED_INFO("no words of its declaration: " + shape[i].detail); + REQUIRE(shape[i].detail.empty()); } // Another text's shape, broken: mended apart from the tree kept. const std::string broken = "integer count;\ndefault {\n state_entry() {\n llSay(0, (string)count\n }\n}\n"; const size_t before = service.mendings(); const std::vector mended = service.outline(broken, false); - ensure("mended to its shape", mended.size() == 3 && mended[0].name == "count" && mended[2].name == "state_entry" && mended[2].depth == 1); - ensure_equals("the tree kept mended nothing", service.mendings(), before); - ensure("and still answers for its own text", service.parsed()); + { + INFO("mended to its shape"); + REQUIRE(mended.size() == 3); + CHECK(mended[0].name == "count"); + CHECK(mended[2].name == "state_entry"); + CHECK(mended[2].depth == 1); + } + UNSCOPED_INFO("the tree kept mended nothing"); + CHECK(service.mendings() == before); + UNSCOPED_INFO("and still answers for its own text"); + CHECK(service.parsed()); } } diff --git a/indra/alscript/tests/allsltoslua_test.cpp b/indra/alscript/tests/allsltoslua_test.cpp index 487713d896..4feae12592 100644 --- a/indra/alscript/tests/allsltoslua_test.cpp +++ b/indra/alscript/tests/allsltoslua_test.cpp @@ -31,7 +31,7 @@ #include "../lsl/allsltraits.h" #include "../luau/alluauservice.h" -#include "../test/lltut.h" +#include #include "Luau/Allocator.h" #include "Luau/Lexer.h" @@ -42,7 +42,7 @@ #include #include -namespace tut +namespace { struct allsltoslua_data { @@ -62,7 +62,7 @@ namespace tut loaded = lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error); if (!loaded) { - fail("the builtins did not load: " + error); + FAIL("the builtins did not load: " + error); } } llifstream in(std::string(AL_LSL_DEFINITIONS_DIR) + "/secondlife.d.luau", std::ios::binary); @@ -88,7 +88,8 @@ namespace tut { why += p.message + "\n"; } - ensure("converted: " + why, r.converted); + UNSCOPED_INFO("converted: " + why); + REQUIRE(r.converted); return r; } @@ -112,7 +113,8 @@ namespace tut // What SLua's check says of it: nothing unwanted. void checksClean(const ALLSLToSLua::Result& r) { - ensure("the definitions", definitions); + UNSCOPED_INFO("the definitions"); + REQUIRE(definitions); std::string said; for (const ALScriptProblem& p : service.check(r.text)) { @@ -121,7 +123,8 @@ namespace tut said += llformat("[%d:%d] %s\n", p.line + 1, p.column + 1, p.message.c_str()); } } - ensure("checks as SLua:\n" + said + "---\n" + r.text, said.empty()); + UNSCOPED_INFO("checks as SLua:\n" + said + "---\n" + r.text); + REQUIRE(said.empty()); } // The comment Luau's own lexer reads beginning at `at` in the text, @@ -159,7 +162,8 @@ namespace tut static std::string commentAt(const std::string& slua, const std::string& begins) { const size_t at = slua.find(begins); - ensure("written: " + begins + "\n" + slua, at != std::string::npos); + UNSCOPED_INFO("written: " + begins + "\n" + slua); + REQUIRE(at != std::string::npos); return commentAt(slua, at); } // A toggle turned the other way in SLua: a dash taken out of its @@ -168,7 +172,8 @@ namespace tut static std::string toggled(const std::string& slua, const std::string& opener, bool off = true) { const size_t at = slua.find(opener); - ensure("written: " + opener + "\n" + slua, at != std::string::npos); + UNSCOPED_INFO("written: " + opener + "\n" + slua); + REQUIRE(at != std::string::npos); std::string other = slua; if (off) { @@ -218,14 +223,8 @@ namespace tut } }; - typedef test_group allsltoslua_group; - typedef allsltoslua_group::object allsltoslua_object; - tut::allsltoslua_group allsltoslua_test("ALLSLToSLua"); - - template<> template<> - void allsltoslua_object::test<1>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a one-state script as locals and LLEvents handlers; state_entry last", "[ALLSLToSLua]") { - set_test_name("a script of one state: globals and functions as locals, handlers on LLEvents, the detected count from the table, state_entry run last"); const ALLSLToSLua::Result r = convert("integer gCount = 2;\n" "string greet(string who) { return \"Hello, \" + who; }\n" "default {\n" @@ -235,21 +234,39 @@ namespace tut " llSay(0, greet(llDetectedName(0)) + \" \" + (string)gCount);\n" " }\n" "}\n"); - ensure("the global: " + r.text, has(r, "local gCount = 2")); - ensure("the function: " + r.text, has(r, "local function greet(who)") && has(r, "return \"Hello, \" .. who")); - ensure("the handler: " + r.text, has(r, "LLEvents:on(\"touch_start\", function(detected)") && has(r, "local total_number = #detected")); - ensure("the step: " + r.text, has(r, "gCount += total_number")); - ensure("what was detected from the table: " + r.text, has(r, "detected[1]:getName()") && noted(r, "SluaDetectedTable")); - ensure("an integer joined to text, interpolated: " + r.text, has(r, "ll.Say(0, `{greet(detected[1]:getName())} {gCount}`)")); - ensure("state_entry last: " + r.text, r.text.find("-- state_entry") > r.text.find("LLEvents:on") && has(r, "ll.Say(0, greet(\"world\"))")); - ensure("the note over its line: " + r.text, has(r, "-- LSL: detected[n] is what LSL read")); + UNSCOPED_INFO("the global: " + r.text); + CHECK(has(r, "local gCount = 2")); + { + INFO("the function: " + r.text); + CHECK(has(r, "local function greet(who)")); + CHECK(has(r, "return \"Hello, \" .. who")); + } + { + INFO("the handler: " + r.text); + CHECK(has(r, "LLEvents:on(\"touch_start\", function(detected)")); + CHECK(has(r, "local total_number = #detected")); + } + UNSCOPED_INFO("the step: " + r.text); + CHECK(has(r, "gCount += total_number")); + { + INFO("what was detected from the table: " + r.text); + CHECK(has(r, "detected[1]:getName()")); + CHECK(noted(r, "SluaDetectedTable")); + } + UNSCOPED_INFO("an integer joined to text, interpolated: " + r.text); + CHECK(has(r, "ll.Say(0, `{greet(detected[1]:getName())} {gCount}`)")); + { + INFO("state_entry last: " + r.text); + CHECK(r.text.find("-- state_entry") > r.text.find("LLEvents:on")); + CHECK(has(r, "ll.Say(0, greet(\"world\"))")); + } + UNSCOPED_INFO("the note over its line: " + r.text); + CHECK(has(r, "-- LSL: detected[n] is what LSL read")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<2>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: expressions: truth tests; 1 or 0 comparisons; / and % toward nought; lists", "[ALLSLToSLua]") { - set_test_name("expressions: conditions as LSL reads them, comparisons as 1 or 0 where they are numbers, integer division and remainder toward nought, lists grown and measured"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " integer a = 7; integer b = 2; float f = 1.5; string s = \"x\"; list l = [1, 2];\n" " integer same = a == b;\n" @@ -265,31 +282,60 @@ namespace tut " vector v = <1, 2, 3>; v.x = 4; v.z += 1;\n" " llOwnerSay((string)v + (string)same + (string)q + (string)m + (string)t + (string)flags);\n" "} }\n"); - ensure("a comparison as a number: " + r.text, has(r, "local same = if a == b then 1 else 0")); - ensure("an integer's truth: " + r.text, has(r, "if a ~= 0 then")); - ensure("a string's: " + r.text, has(r, "if s ~= \"\" then")); - ensure("and: " + r.text, has(r, "if a > 1 and #l > 1 then")); - ensure("integer division: " + r.text, has(r, "local q = bit32.s32(a / b)")); - ensure("remainder: " + r.text, has(r, "local m = math.fmod(a, b)")); - ensure("a list no other holds grown in place: " + r.text, has(r, "table.insert(l, 3)") && has(r, "table.append(l, 4, \"five\")") && - !has(r, "table.extend")); - ensure("TRUE and FALSE: " + r.text, has(r, "local t = 1") && has(r, "if false then")); - ensure("a float as LSL writes it: " + r.text, has(r, "string.format(\"%.6f\", f)")); - ensure("a string as an integer, as a list's item converts it: " + r.text, - has(r, "lslInteger(\"12abc\")") && has(r, "return llcompat.List2Integer({ s }, 0)")); - ensure("lists compared by length: " + r.text, has(r, "#l == #{ 1 }") && noted(r, "SluaListCompare")); + UNSCOPED_INFO("a comparison as a number: " + r.text); + CHECK(has(r, "local same = if a == b then 1 else 0")); + UNSCOPED_INFO("an integer's truth: " + r.text); + CHECK(has(r, "if a ~= 0 then")); + UNSCOPED_INFO("a string's: " + r.text); + CHECK(has(r, "if s ~= \"\" then")); + UNSCOPED_INFO("and: " + r.text); + CHECK(has(r, "if a > 1 and #l > 1 then")); + UNSCOPED_INFO("integer division: " + r.text); + CHECK(has(r, "local q = bit32.s32(a / b)")); + UNSCOPED_INFO("remainder: " + r.text); + CHECK(has(r, "local m = math.fmod(a, b)")); + { + INFO("a list no other holds grown in place: " + r.text); + CHECK(has(r, "table.insert(l, 3)")); + CHECK(has(r, "table.append(l, 4, \"five\")")); + CHECK_FALSE(has(r, "table.extend")); + } + { + INFO("TRUE and FALSE: " + r.text); + CHECK(has(r, "local t = 1")); + CHECK(has(r, "if false then")); + } + UNSCOPED_INFO("a float as LSL writes it: " + r.text); + CHECK(has(r, "string.format(\"%.6f\", f)")); + { + INFO("a string as an integer, as a list's item converts it: " + r.text); + CHECK(has(r, "lslInteger(\"12abc\")")); + CHECK(has(r, "return llcompat.List2Integer({ s }, 0)")); + } + { + INFO("lists compared by length: " + r.text); + CHECK(has(r, "#l == #{ 1 }")); + CHECK(noted(r, "SluaListCompare")); + } // bit32 answers 0 to 4294967295: made LSL's signed integer, but where // bit32 takes it again. - ensure("bits: " + r.text, has(r, "bit32.s32(bit32.bor(bit32.band(a, b), 4))") && !has(r, "local function int32")); - ensure("a vector's part set: " + r.text, has(r, "v = vector(4, v.y, v.z)") && has(r, "v = vector(v.x, v.y, v.z + 1)")); - ensure("a vector as LSL writes it: " + r.text, has(r, "ll.DumpList2String({ v }, \"\")")); + { + INFO("bits: " + r.text); + CHECK(has(r, "bit32.s32(bit32.bor(bit32.band(a, b), 4))")); + CHECK_FALSE(has(r, "local function int32")); + } + { + INFO("a vector's part set: " + r.text); + CHECK(has(r, "v = vector(4, v.y, v.z)")); + CHECK(has(r, "v = vector(v.x, v.y, v.z + 1)")); + } + UNSCOPED_INFO("a vector as LSL writes it: " + r.text); + CHECK(has(r, "ll.DumpList2String({ v }, \"\")")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<3>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: loops: numeric for; break and continue jumps; repeat; other jumps noted", "[ALLSLToSLua]") { - set_test_name("loops: a counting for as Luau's numeric for, a jump out as break and to the next turn as continue, a do as repeat; another jump noted"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " integer i; integer n;\n" " for (i = 0; i < 10; ++i) {\n" @@ -306,20 +352,32 @@ namespace tut " @away;\n" " llOwnerSay((string)n);\n" "} }\n"); - ensure("a numeric for: " + r.text, has(r, "for i = 0, 9 do")); - ensure("its counter declared by it alone: " + r.text, !has(r, "local i = 0")); - ensure("continue, the step Luau's own: " + r.text, has(r, "then continue end")); - ensure("break: " + r.text, has(r, "break")); - ensure("repeat: " + r.text, has(r, "repeat") && has(r, "until not (n > 5)")); - ensure("a step: " + r.text, has(r, "n -= 1")); - ensure("another jump noted and kept as a mark: " + r.text, noted(r, "SluaJump") && has(r, "-- jump away") && has(r, "-- @away")); + UNSCOPED_INFO("a numeric for: " + r.text); + CHECK(has(r, "for i = 0, 9 do")); + UNSCOPED_INFO("its counter declared by it alone: " + r.text); + CHECK_FALSE(has(r, "local i = 0")); + UNSCOPED_INFO("continue, the step Luau's own: " + r.text); + CHECK(has(r, "then continue end")); + UNSCOPED_INFO("break: " + r.text); + CHECK(has(r, "break")); + { + INFO("repeat: " + r.text); + CHECK(has(r, "repeat")); + CHECK(has(r, "until not (n > 5)")); + } + UNSCOPED_INFO("a step: " + r.text); + CHECK(has(r, "n -= 1")); + { + INFO("another jump noted and kept as a mark: " + r.text); + CHECK(noted(r, "SluaJump")); + CHECK(has(r, "-- jump away")); + CHECK(has(r, "-- @away")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<4>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: states as handler tables entered by setState; the timer on LLTimers", "[ALLSLToSLua]") { - set_test_name("states: each a table of handlers, entered by setState, a state change ending the event; the timer on LLTimers, calling the state's"); const ALLSLToSLua::Result r = convert("default {\n" " state_entry() { llSetTimerEvent(1.0); }\n" " timer() { state running; }\n" @@ -329,28 +387,52 @@ namespace tut " state_entry() { llOwnerSay(\"running\"); }\n" " touch_start(integer n) { if (n > 1) { state default; } llOwnerSay(\"touched\"); }\n" "}\n"); - ensure("the tables: " + r.text, has(r, "states.default = {") && has(r, "states.running = {")); - ensure("setState: " + r.text, has(r, "local function setState(name: string)") && has(r, "setState(\"default\")")); - ensure("a change ends the event, where anything follows: " + r.text, - has(r, "setState(\"running\")\n end,") && has(r, "setState(\"default\")\n return\n end")); - ensure("the timer on LLTimers: " + r.text, has(r, "setTimer(1") && has(r, "LLTimers:every(seconds") && noted(r, "SluaTimers")); - ensure("calling the state's handler: " + r.text, has(r, "states[currentState].timer") && has(r, "event ~= \"timer\"")); - ensure("state_exit a handler of its state: " + r.text, has(r, "state_exit = function()")); - ensure("what is let go of noted: " + r.text, noted(r, "SluaStates")); + { + INFO("the tables: " + r.text); + CHECK(has(r, "states.default = {")); + CHECK(has(r, "states.running = {")); + } + { + INFO("setState: " + r.text); + CHECK(has(r, "local function setState(name: string)")); + CHECK(has(r, "setState(\"default\")")); + } + { + INFO("a change ends the event, where anything follows: " + r.text); + CHECK(has(r, "setState(\"running\")\n end,")); + CHECK(has(r, "setState(\"default\")\n return\n end")); + } + { + INFO("the timer on LLTimers: " + r.text); + CHECK(has(r, "setTimer(1")); + CHECK(has(r, "LLTimers:every(seconds")); + CHECK(noted(r, "SluaTimers")); + } + { + INFO("calling the state's handler: " + r.text); + CHECK(has(r, "states[currentState].timer")); + CHECK(has(r, "event ~= \"timer\"")); + } + UNSCOPED_INFO("state_exit a handler of its state: " + r.text); + CHECK(has(r, "state_exit = function()")); + UNSCOPED_INFO("what is let go of noted: " + r.text); + CHECK(noted(r, "SluaStates")); // Tapple's: a name with no state, which only an edit could write, // said so at the call, before the state it is in is left. const size_t looked = r.text.find(" local entering = states[name]\n if not entering then\n error(\"unknown state: \" .. name, 2)\n end\n"); - ensure("a state not there said: " + r.text, looked != std::string::npos && looked < r.text.find("local leaving")); + { + INFO("a state not there said: " + r.text); + REQUIRE(looked != std::string::npos); + CHECK(looked < r.text.find("local leaving")); + } checksClean(r); ALLSLToSLua::Options typed; typed.types = true; checksClean(ALLSLToSLua::convert("default { touch_start(integer n) { state two; } }\nstate two { state_entry() { state default; } }\n", typed)); } - template<> template<> - void allsltoslua_object::test<5>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: names: reserved ones renamed; forward declarations; inline assignments", "[ALLSLToSLua]") { - set_test_name("names: one Luau or SLua holds renamed, functions declared first where one calls another after it; assignments inside expressions made in place"); const ALLSLToSLua::Result r = convert("integer table = 1;\n" "integer first(integer end) { return second(end) + table; }\n" "integer second(integer x) { return x * 2; }\n" @@ -358,28 +440,41 @@ namespace tut " integer i; integer j = (i = 4) + i++;\n" " llOwnerSay((string)first(j));\n" "} }\n"); - ensure("renamed: " + r.text, has(r, "local table_ = 1") && has(r, "(end_)")); - ensure("only the one called before it is written declared first: " + r.text, - has(r, "local second\n") && has(r, "local function first(end_)") && has(r, "\nfunction second(x)")); - ensure("an assignment in place: " + r.text, has(r, "(function() i = 4 return i end)()") && noted(r, "SluaAssignInExpression")); - ensure("a step after, what it was: " + r.text, has(r, "(function() local was = i; i += 1 return was end)()")); + { + INFO("renamed: " + r.text); + CHECK(has(r, "local table_ = 1")); + CHECK(has(r, "(end_)")); + } + { + INFO("only the one called before it is written declared first: " + r.text); + CHECK(has(r, "local second\n")); + CHECK(has(r, "local function first(end_)")); + CHECK(has(r, "\nfunction second(x)")); + } + { + INFO("an assignment in place: " + r.text); + CHECK(has(r, "(function() i = 4 return i end)()")); + CHECK(noted(r, "SluaAssignInExpression")); + } + UNSCOPED_INFO("a step after, what it was: " + r.text); + CHECK(has(r, "(function() local was = i; i += 1 return was end)()")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<6>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: LSL that does not parse writes nothing and says why", "[ALLSLToSLua]") { - set_test_name("LSL that does not parse writes nothing, and says why"); const ALLSLToSLua::Result r = ALLSLToSLua::convert("default { state_entry() { integer = ; } }"); - ensure("not converted", !r.converted && r.text.empty()); - ensure("why", !r.problems.empty()); + { + INFO("not converted"); + CHECK_FALSE(r.converted); + REQUIRE(r.text.empty()); + } + UNSCOPED_INFO("why"); + REQUIRE_FALSE(r.problems.empty()); } - template<> template<> - void allsltoslua_object::test<7>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: close to LSL: llcompat's Detected and timer event noted; ll.OwnerSay kept", "[ALLSLToSLua]") { - set_test_name("close to LSL: llcompat's Detected functions and timer event, as LSL had them, noted with SLua's ways; ll's " - "OwnerSay, which only SLua's deprecating sets apart"); const ALLSLToSLua::Options close = ALLSLToSLua::Options::closeToLSL(); const ALLSLToSLua::Result r = ALLSLToSLua::convert("default {\n" " state_entry() { llSetTimerEvent(2.0); }\n" @@ -387,18 +482,30 @@ namespace tut " touch_start(integer n) { llOwnerSay(llDetectedName(0)); }\n" "}\n", close); - ensure("converted", r.converted); - ensure("llcompat's Detected: " + r.text, has(r, "llcompat.DetectedName(0)") && noted(r, "SluaDetected")); - ensure("the timer event: " + r.text, has(r, "llcompat.SetTimerEvent(2") && has(r, "LLEvents:on(\"timer\"") && noted(r, "SluaTimer")); - ensure("ll.OwnerSay, SLua deprecating it for print: " + r.text, - has(r, "ll.OwnerSay(\"tick\")") && !has(r, "llcompat.OwnerSay") && noted(r, "SluaDeprecatedFor", "OwnerSay")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + { + INFO("llcompat's Detected: " + r.text); + CHECK(has(r, "llcompat.DetectedName(0)")); + CHECK(noted(r, "SluaDetected")); + } + { + INFO("the timer event: " + r.text); + CHECK(has(r, "llcompat.SetTimerEvent(2")); + CHECK(has(r, "LLEvents:on(\"timer\"")); + CHECK(noted(r, "SluaTimer")); + } + { + INFO("ll.OwnerSay, SLua deprecating it for print: " + r.text); + CHECK(has(r, "ll.OwnerSay(\"tick\")")); + CHECK_FALSE(has(r, "llcompat.OwnerSay")); + CHECK(noted(r, "SluaDeprecatedFor", "OwnerSay")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<8>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: SLua's ll where it means the same and SLua's own forms for some calls", "[ALLSLToSLua]") { - set_test_name("SLua's ll where it means the same -- a boolean, a constant index moved on, a find against nil -- and what SLua has in a call's stead"); const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" " key k = llDetectedKey(0); string s = \"abcdef\"; list l = [\"a\", \"b\"]; integer i = 2;\n" " if (llSameGroup(k)) llOwnerSay(\"group\");\n" @@ -413,24 +520,43 @@ namespace tut " integer r = llRound(2.5) + llGetUnixTime();\n" " llOwnerSay(head + tail + (string)same + (string)at + (string)p + (string)r);\n" "} }\n"); - ensure("a boolean in a condition: " + r.text, has(r, "if ll.SameGroup(k) then")); - ensure("and as a number: " + r.text, has(r, "local same = if ll.SameGroup(k) then 1 else 0")); - ensure("a constant index moved on: " + r.text, has(r, "ll.GetSubString(s, 1, 3)")); - ensure("one that is not, through llcompat: " + r.text, has(r, "llcompat.GetSubString(s, i, -1)") && noted(r, "SluaIndex", "GetSubString")); - ensure("one thing found in a list: " + r.text, has(r, "if table.find(l, \"a\") ~= nil then")); - ensure("~ of a find: " + r.text, has(r, "if string.find(s, \"c\", 1, true) ~= nil then")); - ensure("< 0 of one: " + r.text, has(r, "if string.find(s, \"z\", 1, true) == nil then")); - ensure("a find's index as a number, through llcompat: " + r.text, has(r, "llcompat.ListFindList(l, { \"a\", \"b\" })")); - ensure("^, math and vector: " + r.text, has(r, "2.0 ^ 3.0") && has(r, "math.abs(-1.0)") && has(r, "vector.magnitude(vector(1, 2, 3) - ZERO_VECTOR)")); - ensure("half up, as LSL rounds, not math.round: " + r.text, has(r, "math.floor(2.5 + 0.5)")); - ensure("os.time and print: " + r.text, has(r, "os.time()") && has(r, "print(")); + UNSCOPED_INFO("a boolean in a condition: " + r.text); + CHECK(has(r, "if ll.SameGroup(k) then")); + UNSCOPED_INFO("and as a number: " + r.text); + CHECK(has(r, "local same = if ll.SameGroup(k) then 1 else 0")); + UNSCOPED_INFO("a constant index moved on: " + r.text); + CHECK(has(r, "ll.GetSubString(s, 1, 3)")); + { + INFO("one that is not, through llcompat: " + r.text); + CHECK(has(r, "llcompat.GetSubString(s, i, -1)")); + CHECK(noted(r, "SluaIndex", "GetSubString")); + } + UNSCOPED_INFO("one thing found in a list: " + r.text); + CHECK(has(r, "if table.find(l, \"a\") ~= nil then")); + UNSCOPED_INFO("~ of a find: " + r.text); + CHECK(has(r, "if string.find(s, \"c\", 1, true) ~= nil then")); + UNSCOPED_INFO("< 0 of one: " + r.text); + CHECK(has(r, "if string.find(s, \"z\", 1, true) == nil then")); + UNSCOPED_INFO("a find's index as a number, through llcompat: " + r.text); + CHECK(has(r, "llcompat.ListFindList(l, { \"a\", \"b\" })")); + { + INFO("^, math and vector: " + r.text); + CHECK(has(r, "2.0 ^ 3.0")); + CHECK(has(r, "math.abs(-1.0)")); + CHECK(has(r, "vector.magnitude(vector(1, 2, 3) - ZERO_VECTOR)")); + } + UNSCOPED_INFO("half up, as LSL rounds, not math.round: " + r.text); + CHECK(has(r, "math.floor(2.5 + 0.5)")); + { + INFO("os.time and print: " + r.text); + CHECK(has(r, "os.time()")); + CHECK(has(r, "print(")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<9>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: handlers assigned to LLEvents' fields and Luau types on what LSL typed", "[ALLSLToSLua]") { - set_test_name("handlers assigned to LLEvents' fields, and Luau types on what LSL typed"); ALLSLToSLua::Options options; options.handlers = ALLSLToSLua::Options::Handlers::Field; options.types = true; @@ -438,12 +564,22 @@ namespace tut "string greet(string who) { return \"Hi \" + who; }\n" "default { touch_start(integer n) { string s = greet(\"x\"); gCount += n; llOwnerSay(s); } }\n", options); - ensure("converted", r.converted); - ensure("a field, as SLua's own scripts write one: " + r.text, - has(r, "function LLEvents.touch_start(detected: { DetectedEvent })") && !has(r, "LLEvents:on") && !has(r, "= function(")); - ensure("the count typed: " + r.text, has(r, "local n: number = #detected")); - ensure("typed: " + r.text, has(r, "local gCount: number = 2") && has(r, "local function greet(who: string): string") && - has(r, "local s: string = greet(\"x\")")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + { + INFO("a field, as SLua's own scripts write one: " + r.text); + CHECK(has(r, "function LLEvents.touch_start(detected: { DetectedEvent })")); + CHECK_FALSE(has(r, "LLEvents:on")); + CHECK_FALSE(has(r, "= function(")); + } + UNSCOPED_INFO("the count typed: " + r.text); + CHECK(has(r, "local n: number = #detected")); + { + INFO("typed: " + r.text); + CHECK(has(r, "local gCount: number = 2")); + CHECK(has(r, "local function greet(who: string): string")); + CHECK(has(r, "local s: string = greet(\"x\")")); + } checksClean(r); // Each parameter as the definitions type SLua's handler: a key a @@ -456,11 +592,14 @@ namespace tut " money(key giver, integer amount) { giver = \"someone\"; llOwnerSay((string)giver); }\n" "}\n"; const ALLSLToSLua::Result typed = ALLSLToSLua::convert(handlers, options); - ensure("an integer: " + typed.text, has(typed, "function LLEvents.on_rez(param: number)")); - ensure("a key a uuid: " + typed.text, has(typed, "function LLEvents.listen(c: number, n: string, id: uuid, m: string)")); - ensure("link_message's id a string: " + typed.text, has(typed, "function LLEvents.link_message(s: number, num: number, str: string, id: string)")); - ensure("a key the body takes as text, under a name of its own: " + typed.text, - has(typed, "function LLEvents.money(giverKey: uuid, amount: number)\n local giver: string = tostring(giverKey)\n giver = \"someone\"\n")); + UNSCOPED_INFO("an integer: " + typed.text); + CHECK(has(typed, "function LLEvents.on_rez(param: number)")); + UNSCOPED_INFO("a key a uuid: " + typed.text); + CHECK(has(typed, "function LLEvents.listen(c: number, n: string, id: uuid, m: string)")); + UNSCOPED_INFO("link_message's id a string: " + typed.text); + CHECK(has(typed, "function LLEvents.link_message(s: number, num: number, str: string, id: string)")); + UNSCOPED_INFO("a key the body takes as text, under a name of its own: " + typed.text); + CHECK(has(typed, "function LLEvents.money(giverKey: uuid, amount: number)\n local giver: string = tostring(giverKey)\n giver = \"someone\"\n")); checksClean(typed); // Strict as well, under the grid's solver: each handler as the // definitions type it, the text key made text. @@ -471,41 +610,48 @@ namespace tut { said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); } - ensure("strict:\n" + said + "---\n" + typed.text, said.empty()); + UNSCOPED_INFO("strict:\n" + said + "---\n" + typed.text); + REQUIRE(said.empty()); } ALLSLToSLua::Options on = options; on.handlers = ALLSLToSLua::Options::Handlers::On; - ensure("typed with LLEvents:on too", has(ALLSLToSLua::convert(handlers, on), "LLEvents:on(\"on_rez\", function(param: number)")); + UNSCOPED_INFO("typed with LLEvents:on too"); + CHECK(has(ALLSLToSLua::convert(handlers, on), "LLEvents:on(\"on_rez\", function(param: number)")); on.types = false; - ensure("and none where types are not written", has(ALLSLToSLua::convert(handlers, on), "LLEvents:on(\"on_rez\", function(param)")); + UNSCOPED_INFO("and none where types are not written"); + CHECK(has(ALLSLToSLua::convert(handlers, on), "LLEvents:on(\"on_rez\", function(param)")); const ALLSLToSLua::Result many = ALLSLToSLua::convert("default { touch_start(integer n) { state other; } }\n" "state other { touch_start(integer n) { state default; } }\n", options); // SLua stops the script at a field assigned nil: taken off by // LLEvents:off, as LLEvents:on's are. - ensure("setState sets the fields: " + many.text, has(many, "(LLEvents :: any)[event] = handler") && - has(many, "LLEvents:off(event :: any, handler)") && !has(many, "[event] = nil")); - ensure("a state's handlers typed in its table: " + many.text, has(many, "touch_start = function(detected: { DetectedEvent })")); + { + INFO("setState sets the fields: " + many.text); + CHECK(has(many, "(LLEvents :: any)[event] = handler")); + CHECK(has(many, "LLEvents:off(event :: any, handler)")); + CHECK_FALSE(has(many, "[event] = nil")); + } + UNSCOPED_INFO("a state's handlers typed in its table: " + many.text); + CHECK(has(many, "touch_start = function(detected: { DetectedEvent })")); checksClean(many); } - template<> template<> - void allsltoslua_object::test<10>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: no comments where they are not wanted: the notes are still said", "[ALLSLToSLua]") { - set_test_name("no comments where they are not wanted: the notes are still said"); ALLSLToSLua::Options options; options.comments = false; const ALLSLToSLua::Result r = ALLSLToSLua::convert("default { state_entry() { list l = [7]; integer a = l == [2]; llOwnerSay((string)a); } }\n", options); - ensure("converted", r.converted); - ensure("no comment: " + r.text, !has(r, "-- LSL:")); - ensure("but noted", noted(r, "SluaListCompare")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("no comment: " + r.text); + CHECK_FALSE(has(r, "-- LSL:")); + UNSCOPED_INFO("but noted"); + CHECK(noted(r, "SluaListCompare")); } - template<> template<> - void allsltoslua_object::test<11>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: numeric for where it counts as LSL's did; while where it might not", "[ALLSLToSLua]") { - set_test_name("numeric for where it counts as LSL's did: up, down, by a step, to a length; a while where the counter or the limit could change, or the counter is read after"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " list l = [1, 2, 3]; integer n = 4; integer total;\n" " integer a; for (a = 0; a < llGetListLength(l); a++) total += llList2Integer(l, a);\n" @@ -516,19 +662,26 @@ namespace tut " integer f; for (f = 0; f < 3; ++f) total += f;\n" " llOwnerSay((string)(total + f));\n" "} }\n"); - ensure("up to a length, walking its items: " + r.text, has(r, "for _, item in l do\n total += item")); - ensure("down by a step: " + r.text, has(r, "for b = 10, 0, -2 do")); - ensure("up to and with: " + r.text, has(r, "for c = 1, n do")); - ensure("a counter set in its body keeps the while: " + r.text, has(r, "while d < n do")); - ensure("a limit set in its body keeps the while: " + r.text, has(r, "while e < n do")); - ensure("a counter read after keeps the while: " + r.text, has(r, "while f < 3 do") && has(r, "local f = 0")); + UNSCOPED_INFO("up to a length, walking its items: " + r.text); + CHECK(has(r, "for _, item in l do\n total += item")); + UNSCOPED_INFO("down by a step: " + r.text); + CHECK(has(r, "for b = 10, 0, -2 do")); + UNSCOPED_INFO("up to and with: " + r.text); + CHECK(has(r, "for c = 1, n do")); + UNSCOPED_INFO("a counter set in its body keeps the while: " + r.text); + CHECK(has(r, "while d < n do")); + UNSCOPED_INFO("a limit set in its body keeps the while: " + r.text); + CHECK(has(r, "while e < n do")); + { + INFO("a counter read after keeps the while: " + r.text); + CHECK(has(r, "while f < 3 do")); + CHECK(has(r, "local f = 0")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<12>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: integers only used as truths become booleans; number reads keep numbers", "[ALLSLToSLua]") { - set_test_name("integers only ever truth values are booleans, and functions that answer one; each use that reads a number keeps it one"); const ALLSLToSLua::Result r = convert("integer gOn = FALSE;\n" "integer gSeen;\n" "integer isOwner(key k) { return k == llGetOwner(); }\n" @@ -545,14 +698,40 @@ namespace tut " integer y; integer z = (y = 5); if (y) llOwnerSay((string)z);\n" " if (count) llOwnerSay((string)count);\n" "} }\n"); - ensure("a global toggled: " + r.text, has(r, "local gOn = false") && has(r, "gOn = not gOn") && has(r, "if gOn then")); - ensure("compared with FALSE: " + r.text, has(r, "local gSeen = false") && has(r, "if not gSeen and isOwner(") && has(r, "gSeen = true")); - ensure("a function that answers one: " + r.text, has(r, "return k == ll.GetOwner()")); - ensure("a length, as a truth: " + r.text, has(r, "return #l ~= 0") && has(r, "local busy = anyOf({ 1 })") && has(r, "while not busy do")); - ensure("stepped: " + r.text, has(r, "local count = 0") && has(r, "count += 1")); - ensure("printed: " + r.text, has(r, "local shown = 1")); - ensure("more than TRUE, compared with it: " + r.text, has(r, "local loose = 5") && has(r, "if loose == 1 then")); - ensure("set where the assignment is read: " + r.text, has(r, "local y = 0")); + { + INFO("a global toggled: " + r.text); + CHECK(has(r, "local gOn = false")); + CHECK(has(r, "gOn = not gOn")); + CHECK(has(r, "if gOn then")); + } + { + INFO("compared with FALSE: " + r.text); + CHECK(has(r, "local gSeen = false")); + CHECK(has(r, "if not gSeen and isOwner(")); + CHECK(has(r, "gSeen = true")); + } + UNSCOPED_INFO("a function that answers one: " + r.text); + CHECK(has(r, "return k == ll.GetOwner()")); + { + INFO("a length, as a truth: " + r.text); + CHECK(has(r, "return #l ~= 0")); + CHECK(has(r, "local busy = anyOf({ 1 })")); + CHECK(has(r, "while not busy do")); + } + { + INFO("stepped: " + r.text); + CHECK(has(r, "local count = 0")); + CHECK(has(r, "count += 1")); + } + UNSCOPED_INFO("printed: " + r.text); + CHECK(has(r, "local shown = 1")); + { + INFO("more than TRUE, compared with it: " + r.text); + CHECK(has(r, "local loose = 5")); + CHECK(has(r, "if loose == 1 then")); + } + UNSCOPED_INFO("set where the assignment is read: " + r.text); + CHECK(has(r, "local y = 0")); checksClean(r); ALLSLToSLua::Options typed; @@ -560,14 +739,16 @@ namespace tut const ALLSLToSLua::Result t = ALLSLToSLua::convert("integer ready(integer n) { return n > 2; }\n" "default { state_entry() { integer ok = ready(3); if (ok) llOwnerSay(\"yes\"); } }\n", typed); - ensure("typed boolean: " + t.text, has(t, "local function ready(n: number): boolean") && has(t, "local ok: boolean = ready(3)")); + { + INFO("typed boolean: " + t.text); + CHECK(has(t, "local function ready(n: number): boolean")); + CHECK(has(t, "local ok: boolean = ready(3)")); + } checksClean(t); } - template<> template<> - void allsltoslua_object::test<13>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: the right-first memory hack is an append; a visible order is noted", "[ALLSLToSLua]") { - set_test_name("LSL ran a binary's right side first: (l = []) + l is the append it meant, and where the order could show it is noted"); const ALLSLToSLua::Result r = convert("list gItems;\n" "string gLog = \"a\";\n" "default { touch_start(integer n) {\n" @@ -576,13 +757,25 @@ namespace tut " gLog = (gLog = \"\") + gLog + \"b\";\n" " llOwnerSay(gLog + (string)llGetListLength(gItems));\n" "} }\n"); - ensure("appended: " + r.text, has(r, "table.insert(gItems, \"x\")")); - ensure("prepended: " + r.text, has(r, "table.insert(gItems, 1, \"y\")")); - ensure("a string: " + r.text, has(r, "gLog ..= \"b\"")); - ensure("nothing cleared first: " + r.text, !has(r, "gItems = {} return") && !has(r, "gLog = \"\" return")); - ensure("noted once for each variable: " + r.text, noted(r, "SluaMemoryHack") && count(r, "-- LSL: (gItems = []) + gItems") == 1 && - count(r, "-- LSL: (gLog = \"\") + gLog") == 1); - ensure("not taken as an order that shows", !orderNoted(r)); + UNSCOPED_INFO("appended: " + r.text); + CHECK(has(r, "table.insert(gItems, \"x\")")); + UNSCOPED_INFO("prepended: " + r.text); + CHECK(has(r, "table.insert(gItems, 1, \"y\")")); + UNSCOPED_INFO("a string: " + r.text); + CHECK(has(r, "gLog ..= \"b\"")); + { + INFO("nothing cleared first: " + r.text); + CHECK_FALSE(has(r, "gItems = {} return")); + CHECK_FALSE(has(r, "gLog = \"\" return")); + } + { + INFO("noted once for each variable: " + r.text); + CHECK(noted(r, "SluaMemoryHack")); + CHECK(count(r, "-- LSL: (gItems = []) + gItems") == 1); + CHECK(count(r, "-- LSL: (gLog = \"\") + gLog") == 1); + } + UNSCOPED_INFO("not taken as an order that shows"); + CHECK_FALSE(orderNoted(r)); checksClean(r); const ALLSLToSLua::Result order = convert("integer gCount;\n" @@ -593,12 +786,16 @@ namespace tut " integer k = gCount - bump();\n" " llOwnerSay((string)(j + k));\n" "} }\n"); - ensure("an assignment the other side reads, named", noted(order, "SluaRightFirstSet", "i")); - ensure("a call that sets what the other side reads, named", noted(order, "SluaRightFirstCall", "bump()")); - ensure("said over its line, with what: " + order.text, has(order, "-- LSL: LSL ran the right side of this before the left, and Luau " + UNSCOPED_INFO("an assignment the other side reads, named"); + CHECK(noted(order, "SluaRightFirstSet", "i")); + UNSCOPED_INFO("a call that sets what the other side reads, named"); + CHECK(noted(order, "SluaRightFirstCall", "bump()")); + UNSCOPED_INFO("said over its line, with what: " + order.text); + CHECK(has(order, "-- LSL: LSL ran the right side of this before the left, and Luau " "runs the left first: bump() sets gCount, which the other side " "reads or sets too.")); - ensure("said over its line: " + order.text, has(order, "-- LSL: LSL ran the right side of this before the left")); + UNSCOPED_INFO("said over its line: " + order.text); + CHECK(has(order, "-- LSL: LSL ran the right side of this before the left")); checksClean(order); const ALLSLToSLua::Result quiet = convert("integer gCount;\n" @@ -609,14 +806,13 @@ namespace tut " string s = \"n: \" + (string)bump();\n" " llOwnerSay(s + (string)(j + (integer)llGetTime()));\n" "} }\n"); - ensure("nothing either side changes the other reads: " + quiet.text, !orderNoted(quiet)); + UNSCOPED_INFO("nothing either side changes the other reads: " + quiet.text); + CHECK_FALSE(orderNoted(quiet)); checksClean(quiet); } - template<> template<> - void allsltoslua_object::test<14>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: unshared lists grown in place; a list handed on copied and noted", "[ALLSLToSLua]") { - set_test_name("lists no other holds grown in place: appended, prepended, by a list's name, by a call, several at once, passed to a function that only reads it; a list handed on keeps LSL's copy, and says why"); const ALLSLToSLua::Result r = convert("list gAll;\n" "list gSeen;\n" "list gPassed;\n" @@ -646,28 +842,51 @@ namespace tut " show(llListInsertList(gGrown, [addOne()], 0));\n" " show(kept() + b + a + gAll + gStored);\n" "} }\n"); - ensure("a local returned, grown in place: " + r.text, has(r, "local out = ll.ParseString2List(s, { \",\" }, {})") && has(r, "table.insert(out, \"end\")")); - ensure("a call's list, put on the end: " + r.text, has(r, "gAll = parts(\"a,b\")") && has(r, "table.extend(gAll, parts(\"c\"))")); - ensure("prepended: " + r.text, has(r, "table.insert(gAll, 1, \"first\")")); - ensure("a list by its name: " + r.text, has(r, "table.extend(gAll, more)")); - ensure("several that do not run apart, each had before any is added: " + r.text, has(r, "table.append(gAll, ll.GetTime(), ll.Frand(1.0))")); - ensure("passed to a function that only reads it: " + r.text, has(r, "table.insert(gSeen, n)")); - ensure("passed to one that keeps it: " + r.text, - has(r, "gPassed = table.extend(table.clone(gPassed), { n })") && - has(r, "-- LSL: LSL's lists were values, and gPassed is passed to a function of the script's that keeps it")); - ensure("returned: " + r.text, has(r, "gKept = table.extend(table.clone(gKept), { 2 })") && has(r, "gKept is returned by a function")); - ensure("given to another: " + r.text, has(r, "a = table.extend(table.clone(a), { 2 })") && has(r, "a is given to another variable")); - ensure("read where a call in the statement grows it: " + r.text, - has(r, "gGrown = table.extend(table.clone(gGrown), { 1 })") && - has(r, "gGrown is read where a call of the script's in the same statement changes it")); - ensure("said once for each: " + r.text, count(r, "and gPassed is") == 1); - checksClean(r); - } - - template<> template<> - void allsltoslua_object::test<15>() - { - set_test_name("strings built in loops: their pieces in a table, joined once after the loop that builds them; noted where they cannot be"); + { + INFO("a local returned, grown in place: " + r.text); + CHECK(has(r, "local out = ll.ParseString2List(s, { \",\" }, {})")); + CHECK(has(r, "table.insert(out, \"end\")")); + } + { + INFO("a call's list, put on the end: " + r.text); + CHECK(has(r, "gAll = parts(\"a,b\")")); + CHECK(has(r, "table.extend(gAll, parts(\"c\"))")); + } + UNSCOPED_INFO("prepended: " + r.text); + CHECK(has(r, "table.insert(gAll, 1, \"first\")")); + UNSCOPED_INFO("a list by its name: " + r.text); + CHECK(has(r, "table.extend(gAll, more)")); + UNSCOPED_INFO("several that do not run apart, each had before any is added: " + r.text); + CHECK(has(r, "table.append(gAll, ll.GetTime(), ll.Frand(1.0))")); + UNSCOPED_INFO("passed to a function that only reads it: " + r.text); + CHECK(has(r, "table.insert(gSeen, n)")); + { + INFO("passed to one that keeps it: " + r.text); + CHECK(has(r, "gPassed = table.extend(table.clone(gPassed), { n })")); + CHECK(has(r, "-- LSL: LSL's lists were values, and gPassed is passed to a function of the script's that keeps it")); + } + { + INFO("returned: " + r.text); + CHECK(has(r, "gKept = table.extend(table.clone(gKept), { 2 })")); + CHECK(has(r, "gKept is returned by a function")); + } + { + INFO("given to another: " + r.text); + CHECK(has(r, "a = table.extend(table.clone(a), { 2 })")); + CHECK(has(r, "a is given to another variable")); + } + { + INFO("read where a call in the statement grows it: " + r.text); + CHECK(has(r, "gGrown = table.extend(table.clone(gGrown), { 1 })")); + CHECK(has(r, "gGrown is read where a call of the script's in the same statement changes it")); + } + UNSCOPED_INFO("said once for each: " + r.text); + CHECK(count(r, "and gPassed is") == 1); + checksClean(r); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: strings built in loops joined once from a table; noted where not", "[ALLSLToSLua]") + { const ALLSLToSLua::Result r = convert("string gReport;\n" "default { touch_start(integer n) {\n" " list names = [\"a\", \"b\", \"c\"];\n" @@ -686,34 +905,58 @@ namespace tut " for (k = 0; k < 2; ++k) gReport += \"y\";\n" " llOwnerSay(out + seen + gReport + (string)outParts);\n" "} }\n"); - ensure("the outer loop's table, a name of its own: " + r.text, has(r, "local outParts2 = {}\n for _, item in names do")); - ensure("the pieces put in: " + r.text, has(r, "table.insert(outParts2, item .. row)")); - ensure("joined after: " + r.text, has(r, " end\n out ..= table.concat(outParts2)")); - ensure("the inner loop's, declared in the outer: " + r.text, - has(r, " local rowParts = {}\n for j = 0, 2 do\n table.insert(rowParts, tostring(j))")); - ensure("declared empty just before it: declared where it is joined: " + r.text, - has(r, " end\n local row = table.concat(rowParts)") && !has(r, "local row = \"\"")); - ensure("declared with words of its own: added to: " + r.text, has(r, "local out = \"Names: \"") && has(r, "out ..= table.concat(outParts2)")); - ensure("read in its loop: noted: " + r.text, has(r, "seen ..= \"x\"") && has(r, "-- LSL: seen is built with .. in a loop")); - ensure("a global: noted: " + r.text, has(r, "gReport ..= \"y\"") && has(r, "-- LSL: gReport is built with .. in a loop")); + UNSCOPED_INFO("the outer loop's table, a name of its own: " + r.text); + CHECK(has(r, "local outParts2 = {}\n for _, item in names do")); + UNSCOPED_INFO("the pieces put in: " + r.text); + CHECK(has(r, "table.insert(outParts2, item .. row)")); + UNSCOPED_INFO("joined after: " + r.text); + CHECK(has(r, " end\n out ..= table.concat(outParts2)")); + UNSCOPED_INFO("the inner loop's, declared in the outer: " + r.text); + CHECK(has(r, " local rowParts = {}\n for j = 0, 2 do\n table.insert(rowParts, tostring(j))")); + { + INFO("declared empty just before it: declared where it is joined: " + r.text); + CHECK(has(r, " end\n local row = table.concat(rowParts)")); + CHECK_FALSE(has(r, "local row = \"\"")); + } + { + INFO("declared with words of its own: added to: " + r.text); + CHECK(has(r, "local out = \"Names: \"")); + CHECK(has(r, "out ..= table.concat(outParts2)")); + } + { + INFO("read in its loop: noted: " + r.text); + CHECK(has(r, "seen ..= \"x\"")); + CHECK(has(r, "-- LSL: seen is built with .. in a loop")); + } + { + INFO("a global: noted: " + r.text); + CHECK(has(r, "gReport ..= \"y\"")); + CHECK(has(r, "-- LSL: gReport is built with .. in a loop")); + } checksClean(r); ALLSLToSLua::Options typed; typed.types = true; const ALLSLToSLua::Result t = ALLSLToSLua::convert("default { state_entry() { string s; integer i; for (i = 0; i < 3; ++i) s += \"z\"; llOwnerSay(s); } }\n", typed); - ensure("typed: " + t.text, has(t, "local sParts: { string } = {}") && has(t, "local s: string = table.concat(sParts)")); + { + INFO("typed: " + t.text); + CHECK(has(t, "local sParts: { string } = {}")); + CHECK(has(t, "local s: string = table.concat(sParts)")); + } checksClean(t); const ALLSLToSLua::Result read = convert("default { state_entry() { string t = \"\"; llOwnerSay(t); integer i; for (i = 0; i < 3; ++i) t += \"q\"; llOwnerSay(t); } }\n"); - ensure("read between: declared where it was: " + read.text, has(read, "local t = \"\"") && has(read, "t ..= table.concat(tParts)")); + { + INFO("read between: declared where it was: " + read.text); + CHECK(has(read, "local t = \"\"")); + CHECK(has(read, "t ..= table.concat(tParts)")); + } checksClean(read); } - template<> template<> - void allsltoslua_object::test<16>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: compound assignments: x op= y; integer /= and %= whole; vector %= cross", "[ALLSLToSLua]") { - set_test_name("x = x op y as Luau's own compound assignments; an integer's /= and %= written whole, as / and % are; a vector's %= its cross product"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " integer a = 7; float f = 3.0; vector v = <1, 0, 0>;\n" " a /= 2; a %= 3; f /= 2.0; v %= <0, 1, 0>;\n" @@ -721,60 +964,99 @@ namespace tut " b = b + c; b = b - (c - 1); b = b - c - 1; c = c * 2 + b; s = s + \"y\" + (string)b;\n" " llOwnerSay((string)a + (string)f + (string)v + s + (string)(b + c));\n" "} }\n"); - ensure("integer division: " + r.text, has(r, "a = bit32.s32(a / 2)") && !has(r, "//=")); - ensure("remainder: " + r.text, has(r, "a = math.fmod(a, 3)") && !has(r, "%=")); - ensure("a float's: " + r.text, has(r, "f /= 2.0")); - ensure("a vector's cross product: " + r.text, has(r, "v = vector.cross(v, vector(0, 1, 0))")); - ensure("x = x op y: " + r.text, has(r, "b += c") && has(r, "b -= c - 1")); - ensure("not where the left of the operator is more than x: " + r.text, has(r, "b = b - c - 1") && has(r, "c = c * 2 + b")); - ensure("a string's pieces, joined: " + r.text, has(r, "s ..= `y{b}`")); - ensure("a float's and a vector's as LSL writes them, in one interpolated string: " + r.text, - has(r, "print(`{a}{string.format(\"%.6f\", f)}{ll.DumpList2String({ v }, \"\")}{s}{b + c}`)")); + { + INFO("integer division: " + r.text); + CHECK(has(r, "a = bit32.s32(a / 2)")); + CHECK_FALSE(has(r, "//=")); + } + { + INFO("remainder: " + r.text); + CHECK(has(r, "a = math.fmod(a, 3)")); + CHECK_FALSE(has(r, "%=")); + } + UNSCOPED_INFO("a float's: " + r.text); + CHECK(has(r, "f /= 2.0")); + UNSCOPED_INFO("a vector's cross product: " + r.text); + CHECK(has(r, "v = vector.cross(v, vector(0, 1, 0))")); + { + INFO("x = x op y: " + r.text); + CHECK(has(r, "b += c")); + CHECK(has(r, "b -= c - 1")); + } + { + INFO("not where the left of the operator is more than x: " + r.text); + CHECK(has(r, "b = b - c - 1")); + CHECK(has(r, "c = c * 2 + b")); + } + UNSCOPED_INFO("a string's pieces, joined: " + r.text); + CHECK(has(r, "s ..= `y{b}`")); + UNSCOPED_INFO("a float's and a vector's as LSL writes them, in one interpolated string: " + r.text); + CHECK(has(r, "print(`{a}{string.format(\"%.6f\", f)}{ll.DumpList2String({ v }, \"\")}{s}{b + c}`)")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<17>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a minus before a negative number is bracketed and not a comment", "[ALLSLToSLua]") { - set_test_name("a minus before a negative number is bracketed, not a comment"); const ALLSLToSLua::Result r = convert("default { state_entry() { integer a = -5; llOwnerSay((string)(-(-2147483648)) + (string)(-a) + (string)(- -a)); } }\n"); - ensure("bracketed: " + r.text, has(r, "-(-2147483648)") && !has(r, "--2147483648")); - ensure("a name as it was: " + r.text, has(r, "}{-a}{")); - ensure("twice: " + r.text, has(r, "-(-a)")); + { + INFO("bracketed: " + r.text); + CHECK(has(r, "-(-2147483648)")); + CHECK_FALSE(has(r, "--2147483648")); + } + UNSCOPED_INFO("a name as it was: " + r.text); + CHECK(has(r, "}{-a}{")); + UNSCOPED_INFO("twice: " + r.text); + CHECK(has(r, "-(-a)")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<18>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a one-state script's state_exit never ran: left out and said", "[ALLSLToSLua]") { - set_test_name("a script of one state: its state_exit, which never ran and SLua has no event for, left out and said"); const ALLSLToSLua::Result r = convert("default { state_entry() { llOwnerSay(\"in\"); } state_exit() { llOwnerSay(\"out\"); } touch_start(integer n) { } }\n"); - ensure("not an event: " + r.text, !has(r, "\"state_exit\"") && !has(r, "print(\"out\")")); - ensure("said where it was: " + r.text, noted(r, "SluaStateExit") && has(r, "-- LSL: state_exit runs as a script leaves a state") && - has(r, "-- state_exit, left out")); + { + INFO("not an event: " + r.text); + CHECK_FALSE(has(r, "\"state_exit\"")); + CHECK_FALSE(has(r, "print(\"out\")")); + } + { + INFO("said where it was: " + r.text); + CHECK(noted(r, "SluaStateExit")); + CHECK(has(r, "-- LSL: state_exit runs as a script leaves a state")); + CHECK(has(r, "-- state_exit, left out")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<19>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a function SLua lacks left out and said; boolean ll lists kept on llcompat", "[ALLSLToSLua]") { - set_test_name("a function SLua has nowhere left out and said; one whose ll list has booleans for LSL's 1 and 0 kept on llcompat"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " llPointAt(ZERO_VECTOR);\n" " list e = llGetExperienceList(NULL_KEY);\n" " if (llList2Integer(llGetPrimitiveParams([PRIM_FULLBRIGHT, 0]), 0)) llOwnerSay((string)llGetListLength(e));\n" "} }\n"); - ensure("left out: " + r.text, !has(r, "PointAt(") && has(r, "-- llPointAt, left out") && noted(r, "SluaAbsent", "llPointAt")); - ensure("its empty value where read: " + r.text, has(r, "local e = {}") && noted(r, "SluaAbsent", "llGetExperienceList")); - ensure("llcompat, said: " + r.text, has(r, "llcompat.GetPrimitiveParams({ PRIM_FULLBRIGHT, 0 })") && noted(r, "SluaBoolList", "GetPrimitiveParams")); - ensure("not a truth: " + r.text, !has(r, "if llcompat.GetPrimitiveParams")); + { + INFO("left out: " + r.text); + CHECK_FALSE(has(r, "PointAt(")); + CHECK(has(r, "-- llPointAt, left out")); + CHECK(noted(r, "SluaAbsent", "llPointAt")); + } + { + INFO("its empty value where read: " + r.text); + CHECK(has(r, "local e = {}")); + CHECK(noted(r, "SluaAbsent", "llGetExperienceList")); + } + { + INFO("llcompat, said: " + r.text); + CHECK(has(r, "llcompat.GetPrimitiveParams({ PRIM_FULLBRIGHT, 0 })")); + CHECK(noted(r, "SluaBoolList", "GetPrimitiveParams")); + } + UNSCOPED_INFO("not a truth: " + r.text); + CHECK_FALSE(has(r, "if llcompat.GetPrimitiveParams")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<20>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: NULL_KEY and such constants: a uuid as a key and a string as text", "[ALLSLToSLua]") { - set_test_name("NULL_KEY and the constants LSL types a string and SLua a uuid: a key where compared with one or given as one, LSL's string where read as one"); const ALLSLToSLua::Result r = convert("string gName = NULL_KEY;\n" "key gKey = NULL_KEY;\n" "string nothing() { return NULL_KEY; }\n" @@ -785,19 +1067,29 @@ namespace tut " list l = [NULL_KEY, TEXTURE_BLANK];\n" " if (NULL_KEY) llOwnerSay(gName + nothing() + (string)gKey + (string)llGetListLength(l));\n" "} }\n"); - ensure("a key against a key: " + r.text, has(r, "if k ~= NULL_KEY then")); - ensure("a string where LSL's string was: " + r.text, has(r, "tostring(NULL_KEY) ~= \"\"") && has(r, "ll.GetSubString(tostring(NULL_KEY), 1, 8)")); - ensure("in a list, as LSL had it: " + r.text, has(r, "{ tostring(NULL_KEY), tostring(TEXTURE_BLANK) }")); - ensure("a string's truth: " + r.text, has(r, "if tostring(NULL_KEY) ~= \"\" then print(")); - ensure("given to a string and a key: " + r.text, has(r, "local gName = tostring(NULL_KEY)") && has(r, "local gKey = NULL_KEY")); - ensure("returned as a string function's: " + r.text, has(r, "return tostring(NULL_KEY)")); + UNSCOPED_INFO("a key against a key: " + r.text); + CHECK(has(r, "if k ~= NULL_KEY then")); + { + INFO("a string where LSL's string was: " + r.text); + CHECK(has(r, "tostring(NULL_KEY) ~= \"\"")); + CHECK(has(r, "ll.GetSubString(tostring(NULL_KEY), 1, 8)")); + } + UNSCOPED_INFO("in a list, as LSL had it: " + r.text); + CHECK(has(r, "{ tostring(NULL_KEY), tostring(TEXTURE_BLANK) }")); + UNSCOPED_INFO("a string's truth: " + r.text); + CHECK(has(r, "if tostring(NULL_KEY) ~= \"\" then print(")); + { + INFO("given to a string and a key: " + r.text); + CHECK(has(r, "local gName = tostring(NULL_KEY)")); + CHECK(has(r, "local gKey = NULL_KEY")); + } + UNSCOPED_INFO("returned as a string function's: " + r.text); + CHECK(has(r, "return tostring(NULL_KEY)")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<21>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: Tailslide's test scripts convert and check clean or are refused", "[ALLSLToSLua]") { - set_test_name("Tailslide's own test scripts: every one it takes converts and checks as SLua, and each it refuses is refused"); // Those Tailslide says are wrong LSL, by design. static const std::set REFUSED = { "bad_globals.lsl", "bugs/0002.lsl", "bugs/0003.lsl", "bugs/0006.lsl", "bugs/0007.lsl", "bugs/0010.lsl", "bugs/0015.lsl", "bugs/0017.lsl", "bugs/0019.lsl", "bugs/bad-for-exprs.lsl", @@ -816,7 +1108,8 @@ namespace tut } } std::sort(files.begin(), files.end()); - ensure("the scripts: " + dir, files.size() > 100); + UNSCOPED_INFO("the scripts: " + dir); + REQUIRE(files.size() > 100); std::string wrong; size_t converted = 0; for (const std::string& name : files) @@ -845,31 +1138,34 @@ namespace tut } } } - ensure("every one as it should be:\n" + wrong, wrong.empty()); - ensure_equals("those taken, converted", converted + REFUSED.size(), files.size()); + UNSCOPED_INFO("every one as it should be:\n" + wrong); + REQUIRE(wrong.empty()); + UNSCOPED_INFO("those taken, converted"); + REQUIRE(converted + REFUSED.size() == files.size()); } - template<> template<> - void allsltoslua_object::test<22>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: deprecated ll: SLua's own way where it means the same or llcompat noted", "[ALLSLToSLua]") { - set_test_name("what SLua deprecates: its own way where it means the same -- math.floor(x + 0.5), utf8.len, an index read as LSL's -- and llcompat, with SLua's word, where not"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " string s = \"a:b\"; list l = [\"x\", 2];\n" " integer at = llSubStringIndex(s, \":\");\n" " integer n = llStringLength(s) + llRound(1.5);\n" " llOwnerSay(llList2String(l, at) + (string)(at + n));\n" "} }\n"); - ensure("an index read as LSL's: " + r.text, has(r, "local at = (ll.SubStringIndex(s, \":\") or 0) - 1")); - ensure("utf8.len and half up: " + r.text, has(r, "local n = (utf8.len(s) :: number) + math.floor(1.5 + 0.5)")); - ensure("llcompat, with SLua's word: " + r.text, - has(r, "llcompat.List2String(l, at)") && has(r, "-- LSL: SLua deprecates ll.List2String: Use '[]' and 'tostring' instead.")); + UNSCOPED_INFO("an index read as LSL's: " + r.text); + CHECK(has(r, "local at = (ll.SubStringIndex(s, \":\") or 0) - 1")); + UNSCOPED_INFO("utf8.len and half up: " + r.text); + CHECK(has(r, "local n = (utf8.len(s) :: number) + math.floor(1.5 + 0.5)")); + { + INFO("llcompat, with SLua's word: " + r.text); + CHECK(has(r, "llcompat.List2String(l, at)")); + CHECK(has(r, "-- LSL: SLua deprecates ll.List2String: Use '[]' and 'tostring' instead.")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<23>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: keys that hold text stay strings: link_message's id and non-UUID text", "[ALLSLToSLua]") { - set_test_name("keys that hold text: link_message's id as SLua passes it, a string; a key given text that is no UUID kept a string; text where SLua takes it, ll's key too"); const ALLSLToSLua::Result r = convert("key DOMAIN = \"MY CHANNEL\";\n" "key gOwner;\n" "send(key to) { llMessageLinked(LINK_SET, 0, \"\", to); }\n" @@ -887,21 +1183,36 @@ namespace tut " llOwnerSay((string)llGetOwnerKey(\"abc\") + (string)k);\n" " }\n" "}\n"); - ensure("a key given text, a string: " + r.text, has(r, "local DOMAIN = \"MY CHANNEL\"")); - ensure("text where SLua takes it: " + r.text, - has(r, "ll.MessageLinked(LINK_SET, 1, \"hi\", DOMAIN)") && has(r, "ll.MessageLinked(LINK_SET, 2, \"x\", gOwner)") && - has(r, "send(\"other text\")") && has(r, "ll.MessageLinked(LINK_SET, 0, \"\", to)")); - ensure("compared as text: " + r.text, has(r, "if id == DOMAIN then") && has(r, "if id == tostring(gOwner) then")); - ensure("a key given the id, text: " + r.text, has(r, "local k = id") && has(r, "{k}`)")); - ensure("ll's key given text as it is, said: " + r.text, - has(r, "ll.GetOwnerKey(\"abc\" :: any)") && noted(r, "SluaKeyAsText") && !noted(r, "SluaKeyText")); + UNSCOPED_INFO("a key given text, a string: " + r.text); + CHECK(has(r, "local DOMAIN = \"MY CHANNEL\"")); + { + INFO("text where SLua takes it: " + r.text); + CHECK(has(r, "ll.MessageLinked(LINK_SET, 1, \"hi\", DOMAIN)")); + CHECK(has(r, "ll.MessageLinked(LINK_SET, 2, \"x\", gOwner)")); + CHECK(has(r, "send(\"other text\")")); + CHECK(has(r, "ll.MessageLinked(LINK_SET, 0, \"\", to)")); + } + { + INFO("compared as text: " + r.text); + CHECK(has(r, "if id == DOMAIN then")); + CHECK(has(r, "if id == tostring(gOwner) then")); + } + { + INFO("a key given the id, text: " + r.text); + CHECK(has(r, "local k = id")); + CHECK(has(r, "{k}`)")); + } + { + INFO("ll's key given text as it is, said: " + r.text); + CHECK(has(r, "ll.GetOwnerKey(\"abc\" :: any)")); + CHECK(noted(r, "SluaKeyAsText")); + CHECK_FALSE(noted(r, "SluaKeyText")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<24>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: notes only where they say something: &&; deprecations; bit32's range", "[ALLSLToSLua]") { - set_test_name("a note only where it says something: && not over a read, a deprecated call's reason rather than its indexes; bit32's range made LSL's, not for an & with a flag nor a truth"); const ALLSLToSLua::Result r = convert("integer sayIt() { llOwnerSay(\"x\"); return 1; }\n" "default { changed(integer what) {\n" " if (what & CHANGED_LINK && llGetLinkNumber() == 0) llDie();\n" @@ -910,34 +1221,45 @@ namespace tut " llOwnerSay(llList2String(l, what) + (string)m);\n" " if ((what & 3) && sayIt()) llDie();\n" "} }\n"); - ensure("bit32's range made LSL's where it shows: " + r.text, has(r, "local m = bit32.s32(bit32.bor(what, ") && count(r, "-- LSL: bit32") == 1); - ensure("not for a flag's truth: " + r.text, has(r, "if bit32.btest(what, CHANGED_LINK)")); - ensure("&& said once, over a call that does something: " + r.text, - count(r, "leaves its right side unrun") == 1 && has(r, "LSL ran both sides, the right one first.\n if bit32.btest(what, 3) and sayIt()")); - ensure("the deprecated call's reason only: " + r.text, has(r, "-- LSL: SLua deprecates ll.List2String") && !has(r, "takes indexes from 0")); + { + INFO("bit32's range made LSL's where it shows: " + r.text); + CHECK(has(r, "local m = bit32.s32(bit32.bor(what, ")); + CHECK(count(r, "-- LSL: bit32") == 1); + } + UNSCOPED_INFO("not for a flag's truth: " + r.text); + CHECK(has(r, "if bit32.btest(what, CHANGED_LINK)")); + { + INFO("&& said once, over a call that does something: " + r.text); + CHECK(count(r, "leaves its right side unrun") == 1); + CHECK(has(r, "LSL ran both sides, the right one first.\n if bit32.btest(what, 3) and sayIt()")); + } + { + INFO("the deprecated call's reason only: " + r.text); + CHECK(has(r, "-- LSL: SLua deprecates ll.List2String")); + CHECK_FALSE(has(r, "takes indexes from 0")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<25>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: flatter expressions: joined text unbracketed; one bit32 call per chain", "[ALLSLToSLua]") { - set_test_name("flatter expressions: text joined in a chain without brackets, and an &, | or ^ of the same again one bit32 call"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " string b = \"b\"; integer n = 3; integer f = 5;\n" " llOwnerSay(\"a\" + b + \"c\" + (string)n);\n" " llSetTextureAnim(ANIM_ON | ROTATE | LOOP, ALL_SIDES, 0, 0, 0, TWO_PI, 1.0);\n" " llOwnerSay((string)((f | 2) & n));\n" "} }\n"); - ensure("a chain, one string: " + r.text, has(r, "print(`a{b}c{n}`)")); - ensure("one call: " + r.text, has(r, "ll.SetTextureAnim(bit32.bor(ANIM_ON, ROTATE, LOOP), ALL_SIDES")); - ensure("not across operators: " + r.text, has(r, "bit32.band(bit32.bor(f, 2), n)")); + UNSCOPED_INFO("a chain, one string: " + r.text); + CHECK(has(r, "print(`a{b}c{n}`)")); + UNSCOPED_INFO("one call: " + r.text); + CHECK(has(r, "ll.SetTextureAnim(bit32.bor(ANIM_ON, ROTATE, LOOP), ALL_SIDES")); + UNSCOPED_INFO("not across operators: " + r.text); + CHECK(has(r, "bit32.band(bit32.bor(f, 2), n)")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<26>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: steps as statements before or after theirs unless something reads x", "[ALLSLToSLua]") { - set_test_name("steps as statements: x++ after its statement, ++x and x = v before it, where nothing else in it reads x; kept in place where something could"); const ALLSLToSLua::Result r = convert("integer gLine;\n" "integer bump() { return ++gLine; }\n" "default { state_entry() {\n" @@ -948,19 +1270,23 @@ namespace tut " llOwnerSay((string)(gLine++));\n" " llOwnerSay((string)bump() + (string)(gLine++) + (string)(m + k));\n" "} }\n"); - ensure("after: " + r.text, has(r, "ll.Say(n, \"a\")\nn += 1\n")); - ensure("before: " + r.text, has(r, "\nn += 1\nlocal m = n\n")); - ensure("read again, kept: " + r.text, has(r, "local k = (function() local was = n; n += 1 return was end)() + n")); - ensure("a global with nothing of the script's called: " + r.text, has(r, "print(tostring(gLine))\ngLine += 1\n")); - ensure("a global beside a call of the script's, kept: " + r.text, has(r, "print(`{bump()}{(function() local was = gLine;")); - ensure("a return's step before it: " + r.text, has(r, " gLine += 1\n return gLine\n")); + UNSCOPED_INFO("after: " + r.text); + CHECK(has(r, "ll.Say(n, \"a\")\nn += 1\n")); + UNSCOPED_INFO("before: " + r.text); + CHECK(has(r, "\nn += 1\nlocal m = n\n")); + UNSCOPED_INFO("read again, kept: " + r.text); + CHECK(has(r, "local k = (function() local was = n; n += 1 return was end)() + n")); + UNSCOPED_INFO("a global with nothing of the script's called: " + r.text); + CHECK(has(r, "print(tostring(gLine))\ngLine += 1\n")); + UNSCOPED_INFO("a global beside a call of the script's, kept: " + r.text); + CHECK(has(r, "print(`{bump()}{(function() local was = gLine;")); + UNSCOPED_INFO("a return's step before it: " + r.text); + CHECK(has(r, " gLine += 1\n return gLine\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<27>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: simpler conditions: negated comparisons flipped; if-set booleans direct", "[ALLSLToSLua]") { - set_test_name("simpler conditions: !(a == b) as a ~= b, a boolean given TRUE or FALSE by an if as the check itself"); const ALLSLToSLua::Result r = convert("integer gOn;\n" "default { touch_start(integer n) {\n" " if (!(n == 2)) llOwnerSay(\"not two\");\n" @@ -969,24 +1295,34 @@ namespace tut " if (n > 5) { gOn = FALSE; } else { gOn = TRUE; }\n" " if (gOn) llOwnerSay(\"on\");\n" "} }\n"); - ensure("flipped: " + r.text, has(r, "if n ~= 2 then") && has(r, "if n == 3 then")); + { + INFO("flipped: " + r.text); + CHECK(has(r, "if n ~= 2 then")); + CHECK(has(r, "if n == 3 then")); + } const ALLSLToSLua::Result nots = convert("integer gOn;\n" "default { touch_start(integer n) {\n" " if (!n) llOwnerSay(\"none\"); if (!(n & 4)) llOwnerSay(\"no four\"); if (!(n > 1)) llDie();\n" " gOn = !gOn; if (gOn) llOwnerSay(\"on\");\n" "} }\n"); - ensure("not a number: the other way round: " + nots.text, - has(nots, "if n == 0 then") && has(nots, "if not bit32.btest(n, 4) then") && has(nots, "if not (n > 1) then") && - has(nots, "gOn = not gOn")); + { + INFO("not a number: the other way round: " + nots.text); + CHECK(has(nots, "if n == 0 then")); + CHECK(has(nots, "if not bit32.btest(n, 4) then")); + CHECK(has(nots, "if not (n > 1) then")); + CHECK(has(nots, "gOn = not gOn")); + } checksClean(nots); - ensure("the check itself: " + r.text, has(r, " gOn = n > 1\n") && has(r, " gOn = not (n > 5)\n")); + { + INFO("the check itself: " + r.text); + CHECK(has(r, " gOn = n > 1\n")); + CHECK(has(r, " gOn = not (n > 5)\n")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<28>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a non-negative counter moved on by one as an index; loops count from 1", "[ALLSLToSLua]") { - set_test_name("a counter never below nought is an index ll takes moved on by one, and a loop whose counter is only such an index counts from 1"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " integer n = llGetInventoryNumber(INVENTORY_SOUND);\n" " integer i; for (i = 0; i < n; ++i) llOwnerSay(llGetInventoryName(INVENTORY_SOUND, i));\n" @@ -995,19 +1331,21 @@ namespace tut " string s = \"abcdef\";\n" " integer c; for (c = 0; c <= 2; ++c) llOwnerSay(llGetSubString(s, c, c + 1));\n" "} }\n"); - ensure("counting from 1: " + r.text, has(r, "for i = 1, n do\n print(ll.GetInventoryName(INVENTORY_SOUND, i))")); - ensure("read as a number too, moved on by one: " + r.text, - has(r, "for j = 0, n - 1 do\n print(`{j}{ll.GetInventoryName(INVENTORY_SOUND, j + 1)}`)")); - ensure("down to nought: " + r.text, has(r, "for k = n - 1, 0, -1 do\n print(ll.GetInventoryName(INVENTORY_SOUND, k + 1))")); - ensure("to and with, a number added: " + r.text, has(r, "for c = 1, 3 do\n print(ll.GetSubString(s, c, c + 1))")); - ensure("no llcompat: " + r.text, !has(r, "llcompat.")); + UNSCOPED_INFO("counting from 1: " + r.text); + CHECK(has(r, "for i = 1, n do\n print(ll.GetInventoryName(INVENTORY_SOUND, i))")); + UNSCOPED_INFO("read as a number too, moved on by one: " + r.text); + CHECK(has(r, "for j = 0, n - 1 do\n print(`{j}{ll.GetInventoryName(INVENTORY_SOUND, j + 1)}`)")); + UNSCOPED_INFO("down to nought: " + r.text); + CHECK(has(r, "for k = n - 1, 0, -1 do\n print(ll.GetInventoryName(INVENTORY_SOUND, k + 1))")); + UNSCOPED_INFO("to and with, a number added: " + r.text); + CHECK(has(r, "for c = 1, 3 do\n print(ll.GetSubString(s, c, c + 1))")); + UNSCOPED_INFO("no llcompat: " + r.text); + CHECK_FALSE(has(r, "llcompat.")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<29>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: list items read by index directly or through tostring; else llcompat", "[ALLSLToSLua]") { - set_test_name("a list's items read by index where they are already what is asked for: within its length as they are, else LSL's empty value past the end; tostring where that makes them so; llcompat where nothing does"); const ALLSLToSLua::Result r = convert("list gNames = [\"a\", \"b\"];\n" "list parts(string s) { return llParseString2List(s, [\",\"], []); }\n" "default { state_entry() {\n" @@ -1019,20 +1357,26 @@ namespace tut " llOwnerSay(llList2String(mixed, 2) + (string)llList2Integer(mixed, 0) + llList2String(gNames, n));\n" " integer i; for (i = 0; i < llGetListLength(p); ++i) llOwnerSay(llList2String(p, i) + llGetSubString(\"abc\", i, i));\n" "} }\n"); - ensure("from the start, empty past the end: " + r.text, has(r, "(gNames[1] or \"\")")); - ensure("back from the end, through a function's list: " + r.text, has(r, "(p[#p] or \"\")")); - ensure("keys and text made text: " + r.text, has(r, "tostring(keys[2] or \"\")")); - ensure("a float's text is not tostring's, and an index not known: llcompat: " + r.text, - has(r, "llcompat.List2String(mixed, 2)") && has(r, "llcompat.List2String(gNames, n)")); - ensure("mixed items for a number: llcompat: " + r.text, has(r, "llcompat.List2Integer(mixed, 0)")); - ensure("within its length, as it is, counting from 1: " + r.text, has(r, "for i = 1, #p do\n print(p[i] .. ll.GetSubString(\"abc\", i, i))")); + UNSCOPED_INFO("from the start, empty past the end: " + r.text); + CHECK(has(r, "(gNames[1] or \"\")")); + UNSCOPED_INFO("back from the end, through a function's list: " + r.text); + CHECK(has(r, "(p[#p] or \"\")")); + UNSCOPED_INFO("keys and text made text: " + r.text); + CHECK(has(r, "tostring(keys[2] or \"\")")); + { + INFO("a float's text is not tostring's, and an index not known: llcompat: " + r.text); + CHECK(has(r, "llcompat.List2String(mixed, 2)")); + CHECK(has(r, "llcompat.List2String(gNames, n)")); + } + UNSCOPED_INFO("mixed items for a number: llcompat: " + r.text); + CHECK(has(r, "llcompat.List2Integer(mixed, 0)")); + UNSCOPED_INFO("within its length, as it is, counting from 1: " + r.text); + CHECK(has(r, "for i = 1, #p do\n print(p[i] .. ll.GetSubString(\"abc\", i, i))")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<30>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: unshared lists changed at either end in place; elsewhere a new list", "[ALLSLToSLua]") { - set_test_name("a list no other holds taken from or put to at its front or back in place: table.remove and table.insert; anywhere else, or one held elsewhere, as LSL made a new one"); const ALLSLToSLua::Result r = convert("list gQueue;\n" "list gShared;\n" "keep(list l) { gShared = l; }\n" @@ -1045,17 +1389,21 @@ namespace tut " keep(gShared);\n" " gShared = llDeleteSubList(gShared, 0, 0);\n" "} }\n"); - ensure("in place: " + r.text, has(r, "table.insert(gQueue, 1, n + 1)") && has(r, "table.remove(gQueue, 1)") && has(r, "table.remove(gQueue)\n")); - ensure("elsewhere in the list, as LSL did: " + r.text, has(r, "gQueue = llcompat.DeleteSubList(gQueue, 1, 1)")); - ensure("one held elsewhere, as LSL did: " + r.text, has(r, "gShared = llcompat.DeleteSubList(gShared, 0, 0)")); + { + INFO("in place: " + r.text); + CHECK(has(r, "table.insert(gQueue, 1, n + 1)")); + CHECK(has(r, "table.remove(gQueue, 1)")); + CHECK(has(r, "table.remove(gQueue)\n")); + } + UNSCOPED_INFO("elsewhere in the list, as LSL did: " + r.text); + CHECK(has(r, "gQueue = llcompat.DeleteSubList(gQueue, 1, 1)")); + UNSCOPED_INFO("one held elsewhere, as LSL did: " + r.text); + CHECK(has(r, "gShared = llcompat.DeleteSubList(gShared, 0, 0)")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<31>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: notes are keyed with their words and lint; the studio's words used", "[ALLSLToSLua]") { - set_test_name("each note is keyed, with the words it was said with, and carries the lint that finds the same in SLua where one " - "does; the studio's words for a note are its comment's, and the header's"); const std::string lsl = "default {\n" " touch_start(integer n) {\n" " string s = \"abc\";\n" @@ -1066,26 +1414,40 @@ namespace tut "}\n"; ALLSLToSLua::Options close = ALLSLToSLua::Options::closeToLSL(); const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, close); - ensure("converted", r.converted); + UNSCOPED_INFO("converted"); + CHECK(r.converted); std::string listed; for (const ALScriptProblem& p : r.notes) { listed += p.key + "(" + (p.args.empty() ? std::string() : p.args[0]) + ") " + p.code + "\n"; } // Once for each function, with the lint where there is one. - ensure("an index: " + listed, noted(r, "SluaIndex", "GetSubString")); + UNSCOPED_INFO("an index: " + listed); + CHECK(noted(r, "SluaIndex", "GetSubString")); size_t indexes = 0; for (const ALScriptProblem& p : r.notes) { indexes += p.key == "SluaIndex"; const bool compat = p.key == "SluaIndex" || p.key == "SluaIndexFound" || p.key == "SluaBool"; - ensure("the lint of " + p.key + ": " + listed, compat ? p.code == "SlCompatCall" : p.code.empty()); - ensure_equals("no mark left unfilled", p.message, ALScriptProblem::fill(p.message, p.args)); + UNSCOPED_INFO("the lint of " + p.key + ": " + listed); + REQUIRE((compat ? p.code == "SlCompatCall" : p.code.empty())); + UNSCOPED_INFO("no mark left unfilled"); + REQUIRE(p.message == ALScriptProblem::fill(p.message, p.args)); + } + UNSCOPED_INFO("one index note for one function"); + CHECK(indexes == size_t(1)); + { + INFO("a found index and a boolean: " + listed); + CHECK(noted(r, "SluaIndexFound", "SubStringIndex")); + CHECK(noted(r, "SluaBool", "SameGroup")); + } + UNSCOPED_INFO("words filled: " + listed); + CHECK(has(r, "-- LSL: llcompat.GetSubString takes indexes from 0, as LSL did; ll.GetSubString takes them from 1.")); + { + INFO("the lint by key"); + CHECK(std::string(ALLSLToSLua::lintOf("SluaIndex")) == "SlCompatCall"); + REQUIRE_FALSE(ALLSLToSLua::lintOf("SluaListCompare")); } - ensure_equals("one index note for one function", indexes, size_t(1)); - ensure("a found index and a boolean: " + listed, noted(r, "SluaIndexFound", "SubStringIndex") && noted(r, "SluaBool", "SameGroup")); - ensure("words filled: " + listed, has(r, "-- LSL: llcompat.GetSubString takes indexes from 0, as LSL did; ll.GetSubString takes them from 1.")); - ensure("the lint by key", std::string(ALLSLToSLua::lintOf("SluaIndex")) == "SlCompatCall" && !ALLSLToSLua::lintOf("SluaListCompare")); // The studio's words: each comment's, each note's, and the header's, // a line at a time. @@ -1093,9 +1455,14 @@ namespace tut return key == "SluaHeader" ? std::string("first\nsecond") : "<" + key + (args.empty() ? "" : " " + args[0]) + ">"; }; const ALLSLToSLua::Result said = ALLSLToSLua::convert(lsl, close); - ensure("the header's: " + said.text, said.text.rfind("-- first\n-- second\n\n", 0) == 0); - ensure("a comment's: " + said.text, has(said, "-- LSL: \n")); - ensure("a note's", noted(said, "SluaIndex", "GetSubString") && [&] { + UNSCOPED_INFO("the header's: " + said.text); + CHECK(said.text.rfind("-- first\n-- second\n\n", 0) == 0); + UNSCOPED_INFO("a comment's: " + said.text); + CHECK(has(said, "-- LSL: \n")); + { + INFO("a note's"); + CHECK(noted(said, "SluaIndex", "GetSubString")); + CHECK([&] { for (const ALScriptProblem& p : said.notes) { if (p.key == "SluaIndex") @@ -1105,40 +1472,67 @@ namespace tut } return false; }()); + } } - template<> template<> - void allsltoslua_object::test<32>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: LSL comments read back as notes with Done and the lint's fix", "[ALLSLToSLua]") { - set_test_name("each LSL comment read back as a note: Done takes it out; one a lint finds too offers the lint's fix with it, read in " - "the words it was written in"); - ensure("the definitions", definitions); + UNSCOPED_INFO("the definitions"); + CHECK(definitions); const std::string lsl = "default { state_entry() { string s = \"abc\"; llOwnerSay(llGetSubString(s, 0, 2)); } }"; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); - ensure("converted", r.converted); + UNSCOPED_INFO("converted"); + CHECK(r.converted); ALScriptProblems notes = ALLSLToSLua::notesIn(r.text, nullptr); - ensure("as many as the converter noted: " + r.text, notes.size() == r.notes.size() && !notes.empty()); + { + INFO("as many as the converter noted: " + r.text); + REQUIRE(notes.size() == r.notes.size()); + REQUIRE_FALSE(notes.empty()); + } ALScriptProblem* index = nullptr; for (ALScriptProblem& note : notes) { - ensure("a note's", note.key == "SluaNote" && note.severity == ALScriptProblem::Severity::Note && note.fixes.size() == 1 && - note.fixes[0].safe && note.fixes[0].removes && !note.fixes[0].preferred); + { + INFO("a note's"); + REQUIRE(note.key == "SluaNote"); + REQUIRE(note.severity == ALScriptProblem::Severity::Note); + REQUIRE(note.fixes.size() == 1); + REQUIRE(note.fixes[0].safe); + REQUIRE(note.fixes[0].removes); + REQUIRE_FALSE(note.fixes[0].preferred); + } index = note.message.rfind("llcompat.GetSubString takes indexes", 0) == 0 ? ¬e : index; } - ensure("the index's, the lint's: " + r.text, index && index->code == "SlCompatCall"); + { + INFO("the index's, the lint's: " + r.text); + REQUIRE(index); + CHECK(index->code == "SlCompatCall"); + } const size_t at = index - notes.data(); ALLSLToSLua::linkNotes(notes, service.check(r.text), r.text); const ALScriptProblem& linked = notes[at]; - ensure("the lint's fix first, then Done", linked.fixes.size() == 2 && linked.fixes[0].preferred && - linked.fixes[0].title == "Write it ll.GetSubString(s, 1, 3), and take the note out" && - linked.fixes[1].title == "Done: take the note out"); + { + INFO("the lint's fix first, then Done"); + REQUIRE(linked.fixes.size() == 2); + CHECK(linked.fixes[0].preferred); + CHECK(linked.fixes[0].title == "Write it ll.GetSubString(s, 1, 3), and take the note out"); + CHECK(linked.fixes[1].title == "Done: take the note out"); + } const std::optional made = ALScriptFixes::apply(r.text, linked.fixes[0]); - ensure("made: ll's, the note gone", made && made->find("ll.GetSubString(s, 1, 3)") != std::string::npos && - made->find("llcompat.GetSubString takes indexes") == std::string::npos); + { + INFO("made: ll's, the note gone"); + REQUIRE(made); + REQUIRE(made->find("ll.GetSubString(s, 1, 3)") != std::string::npos); + REQUIRE(made->find("llcompat.GetSubString takes indexes") == std::string::npos); + } const std::optional done = ALScriptFixes::apply(r.text, linked.fixes[1]); - ensure("done: the line gone, the call kept", done && done->find("llcompat.GetSubString takes indexes") == std::string::npos && - done->find("llcompat.GetSubString(s, 0, 2)") != std::string::npos && - std::count(done->begin(), done->end(), '\n') + 1 == std::count(r.text.begin(), r.text.end(), '\n')); + { + INFO("done: the line gone, the call kept"); + REQUIRE(done); + REQUIRE(done->find("llcompat.GetSubString takes indexes") == std::string::npos); + REQUIRE(done->find("llcompat.GetSubString(s, 0, 2)") != std::string::npos); + CHECK(std::count(done->begin(), done->end(), '\n') + 1 == std::count(r.text.begin(), r.text.end(), '\n')); + } // Written in the studio's words, read back in them. ALLSLToSLua::Options close = ALLSLToSLua::Options::closeToLSL(); @@ -1146,18 +1540,19 @@ namespace tut return ALScriptProblem::fill(key == "SluaIndex" ? "Index de [1]" : english, args); }; const ALLSLToSLua::Result said = ALLSLToSLua::convert(lsl, close); - ensure("written so", has(said, "-- LSL: Index de GetSubString\n")); + UNSCOPED_INFO("written so"); + CHECK(has(said, "-- LSL: Index de GetSubString\n")); const auto linted = [](const ALScriptProblems& read) { return std::count_if(read.begin(), read.end(), [](const ALScriptProblem& note) { return note.code == "SlCompatCall"; }); }; - ensure("read in the studio's words", linted(ALLSLToSLua::notesIn(said.text, close.words)) == 1); - ensure("not in English's", linted(ALLSLToSLua::notesIn(said.text, nullptr)) == 0); + UNSCOPED_INFO("read in the studio's words"); + CHECK(linted(ALLSLToSLua::notesIn(said.text, close.words)) == 1); + UNSCOPED_INFO("not in English's"); + CHECK(linted(ALLSLToSLua::notesIn(said.text, nullptr)) == 0); } - template<> template<> - void allsltoslua_object::test<33>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: parameters only read as truths are booleans; others and events numbers", "[ALLSLToSLua]") { - set_test_name("a parameter the script only reads as a truth is a boolean, each call giving one; one read as a number, or given more than TRUE where it is asked about TRUE, stays a number; an event's stay the grid's numbers"); const ALLSLToSLua::Result r = convert("say(integer loud, string text) { if (loud) llShout(0, text); else llSay(0, text); }\n" "glow(integer on) { if (!on) return; llOwnerSay(\"glow\"); }\n" "integer next(integer n) { return n + 1; }\n" @@ -1173,13 +1568,34 @@ namespace tut " }\n" " on_rez(integer p) { if (p) llOwnerSay(\"rezzed\"); }\n" "}\n"); - ensure("read only as a truth: a boolean: " + r.text, has(r, "local function say(loud, text)") && has(r, "if loud then")); - ensure("each call giving one, a number as its truth: " + r.text, - has(r, "say(true, \"hi\")") && has(r, "say(n > 1, \"many\")") && has(r, "say(n ~= 0, \"some\")")); - ensure("not, as a truth: " + r.text, has(r, "if not on then") && has(r, "glow(false)")); - ensure("read as a number: kept one: " + r.text, has(r, "return n + 1") && has(r, "next_(n)")); - ensure("given 5 where asked about TRUE: kept a number: " + r.text, has(r, "f == 1") && has(r, "exactly(5)")); - ensure("an event's: the grid's number: " + r.text, has(r, "if p ~= 0 then")); + { + INFO("read only as a truth: a boolean: " + r.text); + CHECK(has(r, "local function say(loud, text)")); + CHECK(has(r, "if loud then")); + } + { + INFO("each call giving one, a number as its truth: " + r.text); + CHECK(has(r, "say(true, \"hi\")")); + CHECK(has(r, "say(n > 1, \"many\")")); + CHECK(has(r, "say(n ~= 0, \"some\")")); + } + { + INFO("not, as a truth: " + r.text); + CHECK(has(r, "if not on then")); + CHECK(has(r, "glow(false)")); + } + { + INFO("read as a number: kept one: " + r.text); + CHECK(has(r, "return n + 1")); + CHECK(has(r, "next_(n)")); + } + { + INFO("given 5 where asked about TRUE: kept a number: " + r.text); + CHECK(has(r, "f == 1")); + CHECK(has(r, "exactly(5)")); + } + UNSCOPED_INFO("an event's: the grid's number: " + r.text); + CHECK(has(r, "if p ~= 0 then")); checksClean(r); ALLSLToSLua::Options typed; @@ -1187,14 +1603,13 @@ namespace tut const ALLSLToSLua::Result t = ALLSLToSLua::convert("say(integer loud, string text) { if (loud) llSay(0, text); }\n" "default { state_entry() { say(TRUE, \"hi\"); } }\n", typed); - ensure("typed: " + t.text, has(t, "local function say(loud: boolean, text: string)")); + UNSCOPED_INFO("typed: " + t.text); + CHECK(has(t, "local function say(loud: boolean, text: string)")); checksClean(t); } - template<> template<> - void allsltoslua_object::test<34>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a counting loop from 0 whose counter only indexes walks the items", "[ALLSLToSLua]") { - set_test_name("a counting loop over a list's items, its counter read as nothing else, walks the items; from another start, or read otherwise, it counts"); const ALLSLToSLua::Result r = convert("list gNames = [\"a\", \"b\"];\n" "default { state_entry() {\n" " list keys = [llGetOwner()];\n" @@ -1210,11 +1625,14 @@ namespace tut " integer n;\n" " for (n = 0; n < llGetListLength(gNames); ++n) llOwnerSay(llList2String(gNames, n) + (string)n);\n" "} }\n"); - ensure("walked: " + r.text, has(r, "for _, item in gNames do\n print(item)\n")); - ensure("one inside another, each its own name, a key's made text: " + r.text, - has(r, "for _, item2 in keys do\n print(item .. tostring(item2))")); - ensure("from the second: counted: " + r.text, has(r, "for m = 2, #gNames do\n print(gNames[m])")); - ensure("its counter read as a number too: counted: " + r.text, has(r, "for n = 0, #gNames - 1 do")); + UNSCOPED_INFO("walked: " + r.text); + CHECK(has(r, "for _, item in gNames do\n print(item)\n")); + UNSCOPED_INFO("one inside another, each its own name, a key's made text: " + r.text); + CHECK(has(r, "for _, item2 in keys do\n print(item .. tostring(item2))")); + UNSCOPED_INFO("from the second: counted: " + r.text); + CHECK(has(r, "for m = 2, #gNames do\n print(gNames[m])")); + UNSCOPED_INFO("its counter read as a number too: counted: " + r.text); + CHECK(has(r, "for n = 0, #gNames - 1 do")); checksClean(r); const ALLSLToSLua::Result named = convert("list gNames = [\"a\"];\n" @@ -1223,14 +1641,13 @@ namespace tut " integer i;\n" " for (i = 0; i < llGetListLength(gNames); ++i) llOwnerSay(llList2String(gNames, i) + (string)_);\n" "} }\n"); - ensure("an index that would hide the script's _ named afresh: " + named.text, has(named, "for _2, item in gNames do")); + UNSCOPED_INFO("an index that would hide the script's _ named afresh: " + named.text); + CHECK(has(named, "for _2, item in gNames do")); checksClean(named); } - template<> template<> - void allsltoslua_object::test<35>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: strings built in loops: two loops share one; one read in its loop kept", "[ALLSLToSLua]") { - set_test_name("strings built in loops: one built by two loops declared once, the second adding to it; one appended to another in the loop it builds read as it stands, not built"); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " string s;\n" " integer i;\n" @@ -1244,18 +1661,28 @@ namespace tut " for (k = 0; k < 3; ++k) { line += \"x\"; all += line; }\n" " llOwnerSay(all);\n" "} }\n"); - ensure("declared at the first loop's join: " + r.text, has(r, "local s = table.concat(sParts)")); - ensure("the second adds to it: " + r.text, has(r, "s ..= table.concat(sParts2)") && !has(r, "local s = table.concat(sParts2)")); - ensure("one read in its loop kept as it is: " + r.text, has(r, "local line = \"\"") && !has(r, "table.concat(lineParts)")); - ensure("the other built from it: " + r.text, has(r, "table.insert(allParts, line)") && has(r, "local all = table.concat(allParts)")); + UNSCOPED_INFO("declared at the first loop's join: " + r.text); + CHECK(has(r, "local s = table.concat(sParts)")); + { + INFO("the second adds to it: " + r.text); + CHECK(has(r, "s ..= table.concat(sParts2)")); + CHECK_FALSE(has(r, "local s = table.concat(sParts2)")); + } + { + INFO("one read in its loop kept as it is: " + r.text); + CHECK(has(r, "local line = \"\"")); + CHECK_FALSE(has(r, "table.concat(lineParts)")); + } + { + INFO("the other built from it: " + r.text); + CHECK(has(r, "table.insert(allParts, line)")); + CHECK(has(r, "local all = table.concat(allParts)")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<36>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: bit32 answers made signed where they can pass 2147483647 and it shows", "[ALLSLToSLua]") { - set_test_name("bit32's answers made LSL's signed integers where they can pass 2147483647 -- a message number with its top bit set, the mask " - "that finds it, ~, a shift -- and not where bit32 takes them again, only truth is asked, or an & is with a constant not below nought"); // A protocol of link messages whose numbers have their top bit set, // as a set of scripts may share: before, the mask's answer was never // the message's number, and no message was heard. @@ -1280,24 +1707,32 @@ namespace tut " llOwnerSay((string)(inverse + high + low + part));\n" " }\n" "}\n"); - ensure("the mask's answer signed: " + r.text, has(r, "if bit32.s32(bit32.band(num, MASK)) == MSG then")); - ensure("the number sent signed: " + r.text, has(r, "ll.MessageLinked(LINK_SET, bit32.s32(bit32.bor(MSG, FN_RESET)), \"\", \"\")")); - ensure("an & with a constant not below nought as it is: " + r.text, has(r, "local fn = bit32.band(num, FN_MASK)\n")); - ensure("~ signed: " + r.text, has(r, "local inverse = bit32.s32(bit32.bnot(num))")); - ensure("<< signed: " + r.text, has(r, "local high = bit32.s32(bit32.lshift(num, 4))")); - ensure(">> of what may be below nought signed: " + r.text, has(r, "local low = bit32.s32(bit32.arshift(num, 2))")); - ensure(">> of a constant not below nought as it is: " + r.text, has(r, "local part = bit32.arshift(FN_MASK, 8)\n")); - ensure("a truth, and what bit32 takes again, as they are: " + r.text, - has(r, "if bit32.btest(bit32.bnot(ll.GetPermissions()), PERMISSION_TRIGGER_ANIMATION) then")); - ensure("SLua's own bit32.s32, no helper, said once: " + r.text, !has(r, "local function int32") && count(r, "-- LSL: bit32 answers") == 1); + UNSCOPED_INFO("the mask's answer signed: " + r.text); + CHECK(has(r, "if bit32.s32(bit32.band(num, MASK)) == MSG then")); + UNSCOPED_INFO("the number sent signed: " + r.text); + CHECK(has(r, "ll.MessageLinked(LINK_SET, bit32.s32(bit32.bor(MSG, FN_RESET)), \"\", \"\")")); + UNSCOPED_INFO("an & with a constant not below nought as it is: " + r.text); + CHECK(has(r, "local fn = bit32.band(num, FN_MASK)\n")); + UNSCOPED_INFO("~ signed: " + r.text); + CHECK(has(r, "local inverse = bit32.s32(bit32.bnot(num))")); + UNSCOPED_INFO("<< signed: " + r.text); + CHECK(has(r, "local high = bit32.s32(bit32.lshift(num, 4))")); + UNSCOPED_INFO(">> of what may be below nought signed: " + r.text); + CHECK(has(r, "local low = bit32.s32(bit32.arshift(num, 2))")); + UNSCOPED_INFO(">> of a constant not below nought as it is: " + r.text); + CHECK(has(r, "local part = bit32.arshift(FN_MASK, 8)\n")); + UNSCOPED_INFO("a truth, and what bit32 takes again, as they are: " + r.text); + CHECK(has(r, "if bit32.btest(bit32.bnot(ll.GetPermissions()), PERMISSION_TRIGGER_ANIMATION) then")); + { + INFO("SLua's own bit32.s32, no helper, said once: " + r.text); + CHECK_FALSE(has(r, "local function int32")); + CHECK(count(r, "-- LSL: bit32 answers") == 1); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<37>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: comments carried over where they stood with blocks at a safe level", "[ALLSLToSLua]") { - set_test_name("the script's comments carried over, each over what it stood over: the script's own at the top, a global's, those inside a list on their lines, " - "a trailing one, a brace's after its opening line, between branches, at a block's end; // as --, /* */ as --[[ ]] at a level nothing in it closes"); const std::string lsl = "// Door script by Someone.\n" "// Do as you like with it.\n" "\n" @@ -1340,35 +1775,58 @@ namespace tut "}\n"; const ALLSLToSLua::Result r = convert(lsl); const auto at = [&r](const std::string& text) { return r.text.find(text); }; - ensure("the script's own at the top: " + r.text, has(r, "-- Door script by Someone.\n-- Do as you like with it.\n\n")); - ensure("over everything, once: " + r.text, at("-- Door script by Someone.") < at("local SWING") && count(r, "Door script") == 1); - ensure("a global's, what trailed it still after it: " + r.text, has(r, "-- How far it swings, in degrees\nlocal SWING = 90.0 -- a right angle\n")); - ensure("those inside a list, on their lines: " + r.text, - has(r, "local NAMES = {\n \"open\", -- when it is open\n \"closed\" -- when it is shut\n}\n")); - ensure("a block comment as Luau's: " + r.text, has(r, "--[[ old code:\ninteger unused() { return 1; }\n]]\n")); - ensure("over the function it stood over: " + r.text, at("--[[ old code:") > at("local NAMES") && at("--[[ old code:") < at("-- Turns the door.")); - ensure("a brace's, after the line that opens it: " + r.text, has(r, "-- Turns the door.\nlocal function swing(by) -- by degrees\n")); - ensure("over a statement: " + r.text, has(r, " -- the rotation it turns by\n local r = ")); - ensure("a trailing one, after its statement: " + r.text, has(r, " ll.SetLinkPrimitiveParamsFast(LINK_THIS, { PRIM_ROT_LOCAL, r * ll.GetLocalRot() }) -- turn\n")); - ensure("between branches, over the elseif: " + r.text, has(r, " -- when it shuts\n elseif by < 0 then\n")); - ensure("an empty branch's, in it: " + r.text, has(r, " if false then\n --[[ left off ]]\n elseif by == 0 then\n")); - ensure("at a block's end: " + r.text, has(r, " -- done\nend\n")); - ensure("a state's, over its handler: " + r.text, has(r, "-- on a touch\nLLEvents:on(\"touch_start\"")); - ensure("not a block of Luau's: " + r.text, has(r, " swing(SWING) -- [[ not a block of Luau's ]]\n")); - ensure("a level nothing in it closes: " + r.text, has(r, " --[=[ it ends ]] here ]=]\nend)")); + UNSCOPED_INFO("the script's own at the top: " + r.text); + CHECK(has(r, "-- Door script by Someone.\n-- Do as you like with it.\n\n")); + { + INFO("over everything, once: " + r.text); + CHECK(at("-- Door script by Someone.") < at("local SWING")); + CHECK(count(r, "Door script") == 1); + } + UNSCOPED_INFO("a global's, what trailed it still after it: " + r.text); + CHECK(has(r, "-- How far it swings, in degrees\nlocal SWING = 90.0 -- a right angle\n")); + UNSCOPED_INFO("those inside a list, on their lines: " + r.text); + CHECK(has(r, "local NAMES = {\n \"open\", -- when it is open\n \"closed\" -- when it is shut\n}\n")); + UNSCOPED_INFO("a block comment as Luau's: " + r.text); + CHECK(has(r, "--[[ old code:\ninteger unused() { return 1; }\n]]\n")); + { + INFO("over the function it stood over: " + r.text); + CHECK(at("--[[ old code:") > at("local NAMES")); + CHECK(at("--[[ old code:") < at("-- Turns the door.")); + } + UNSCOPED_INFO("a brace's, after the line that opens it: " + r.text); + CHECK(has(r, "-- Turns the door.\nlocal function swing(by) -- by degrees\n")); + UNSCOPED_INFO("over a statement: " + r.text); + CHECK(has(r, " -- the rotation it turns by\n local r = ")); + UNSCOPED_INFO("a trailing one, after its statement: " + r.text); + CHECK(has(r, " ll.SetLinkPrimitiveParamsFast(LINK_THIS, { PRIM_ROT_LOCAL, r * ll.GetLocalRot() }) -- turn\n")); + UNSCOPED_INFO("between branches, over the elseif: " + r.text); + CHECK(has(r, " -- when it shuts\n elseif by < 0 then\n")); + UNSCOPED_INFO("an empty branch's, in it: " + r.text); + CHECK(has(r, " if false then\n --[[ left off ]]\n elseif by == 0 then\n")); + UNSCOPED_INFO("at a block's end: " + r.text); + CHECK(has(r, " -- done\nend\n")); + UNSCOPED_INFO("a state's, over its handler: " + r.text); + CHECK(has(r, "-- on a touch\nLLEvents:on(\"touch_start\"")); + UNSCOPED_INFO("not a block of Luau's: " + r.text); + CHECK(has(r, " swing(SWING) -- [[ not a block of Luau's ]]\n")); + UNSCOPED_INFO("a level nothing in it closes: " + r.text); + CHECK(has(r, " --[=[ it ends ]] here ]=]\nend)")); checksClean(r); ALLSLToSLua::Options none; none.keepComments = false; const ALLSLToSLua::Result bare = ALLSLToSLua::convert(lsl, none); - ensure("none where asked: " + bare.text, bare.converted && bare.text.find("Door script") == std::string::npos && - bare.text.find("degrees") == std::string::npos && bare.text.find("--[[") == std::string::npos); + { + INFO("none where asked: " + bare.text); + CHECK(bare.converted); + REQUIRE(bare.text.find("Door script") == std::string::npos); + REQUIRE(bare.text.find("degrees") == std::string::npos); + REQUIRE(bare.text.find("--[[") == std::string::npos); + } } - template<> template<> - void allsltoslua_object::test<38>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: spans: each LSL line beside the SLua line made of it", "[ALLSLToSLua]") { - set_test_name("spans: each global, function, handler and statement's LSL line beside the line of SLua made of it, under the head, its notes and comments"); const std::string lsl = "integer count = 0;\n" // 0 "add(integer n)\n" // 1 "{\n" // 2 @@ -1393,9 +1851,15 @@ namespace tut } for (const ALLSLToSLua::Span& span : r.spans) { - ensure("within both: " + std::to_string(span.lslFirst) + " " + std::to_string(span.sluaFirst), - span.lslFirst >= 0 && span.lslLast < 14 && span.lslFirst <= span.lslLast && span.sluaFirst >= 0 && span.sluaFirst <= span.sluaLast && - span.sluaLast < static_cast(lines.size())); + { + INFO("within both: " + std::to_string(span.lslFirst) + " " + std::to_string(span.sluaFirst)); + REQUIRE(span.lslFirst >= 0); + REQUIRE(span.lslLast < 14); + REQUIRE(span.lslFirst <= span.lslLast); + REQUIRE(span.sluaFirst >= 0); + REQUIRE(span.sluaFirst <= span.sluaLast); + REQUIRE(span.sluaLast < static_cast(lines.size())); + } } const auto at = [&](S32 lsl_line) -> std::string { for (const ALLSLToSLua::Span& span : r.spans) @@ -1407,19 +1871,25 @@ namespace tut } return "(none)"; }; - ensure("the global: " + at(0) + "\n" + r.text, at(0).find("local count") != std::string::npos); - ensure("the function: " + at(1), at(1).find("function add(") != std::string::npos); - ensure("its statement: " + at(3), at(3).find("count") != std::string::npos && at(3).find("n") != std::string::npos); - ensure("the handler: " + at(7), at(7).find("touch_start") != std::string::npos); - ensure("a call under its comment, not the comment: " + at(10), at(10).find("add(1)") != std::string::npos); - ensure("the next: " + at(11), at(11).find("ll.Say(0") != std::string::npos); + UNSCOPED_INFO("the global: " + at(0) + "\n" + r.text); + REQUIRE(at(0).find("local count") != std::string::npos); + UNSCOPED_INFO("the function: " + at(1)); + REQUIRE(at(1).find("function add(") != std::string::npos); + { + INFO("its statement: " + at(3)); + REQUIRE(at(3).find("count") != std::string::npos); + REQUIRE(at(3).find("n") != std::string::npos); + } + UNSCOPED_INFO("the handler: " + at(7)); + REQUIRE(at(7).find("touch_start") != std::string::npos); + UNSCOPED_INFO("a call under its comment, not the comment: " + at(10)); + REQUIRE(at(10).find("add(1)") != std::string::npos); + UNSCOPED_INFO("the next: " + at(11)); + REQUIRE(at(11).find("ll.Say(0") != std::string::npos); } - template<> template<> - void allsltoslua_object::test<39>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: fewer calls: bit32.btest and table.append; hexadecimal kept where safe", "[ALLSLToSLua]") { - set_test_name("in fewer calls: an & asked whether it is nought by bit32.btest, values added together by one table.append, a key's default " - "the null key; and hexadecimal as written where SLua reads the same number"); const ALLSLToSLua::Result r = convert("integer MASK = 0x0000FF00;\n" "integer HIGH = 0xF0000000;\n" "integer S_MASK = 0xF0000000;\n" @@ -1443,42 +1913,79 @@ namespace tut " float f = (float)\"1.5\";\n" " llOwnerSay((string)(masked + HIGH + top + BIG + BIG_HEX + WRAPPED + sign) + (string)f + (string)gSitter);\n" "} }\n"); - ensure("an & as a condition: " + r.text, has(r, "if bit32.btest(flags, PARCEL_FLAG_ALLOW_CREATE_OBJECTS) then")); - ensure("not of one, an & of &s one call: " + r.text, has(r, "if not bit32.btest(flags, MASK, 0x300) then")); - ensure("against nought: " + r.text, has(r, "if not bit32.btest(flags, 4) then")); - ensure("an & as a number is still bit32.band: " + r.text, has(r, "local masked = bit32.band(flags, MASK)")); - ensure("one bit32 takes, as written, which it reads the same: " + r.text, has(r, "local top = bit32.s32(bit32.band(flags, 0x80000000))")); - ensure("values added together: " + r.text, - has(r, "table.append(params, PRIM_LINK_TARGET, LINK_ROOT, PRIM_POSITION, ll.GetPos())") && has(r, "table.insert(params, n)")); - ensure("the null key: " + r.text, has(r, "local gSitter = NULL_KEY") && !has(r, "uuid(\"\")")); - ensure("a float from a string: " + r.text, has(r, "lslFloat(\"1.5\")") && has(r, "return llcompat.List2Float({ s }, 0)")); - ensure("hexadecimal kept: " + r.text, has(r, "local MASK = 0x0000FF00")); - ensure("past 0x7FFFFFFF, which SLua reads as another number, as LSL had it: " + r.text, has(r, "local HIGH = -268435456")); + UNSCOPED_INFO("an & as a condition: " + r.text); + CHECK(has(r, "if bit32.btest(flags, PARCEL_FLAG_ALLOW_CREATE_OBJECTS) then")); + UNSCOPED_INFO("not of one, an & of &s one call: " + r.text); + CHECK(has(r, "if not bit32.btest(flags, MASK, 0x300) then")); + UNSCOPED_INFO("against nought: " + r.text); + CHECK(has(r, "if not bit32.btest(flags, 4) then")); + UNSCOPED_INFO("an & as a number is still bit32.band: " + r.text); + CHECK(has(r, "local masked = bit32.band(flags, MASK)")); + UNSCOPED_INFO("one bit32 takes, as written, which it reads the same: " + r.text); + CHECK(has(r, "local top = bit32.s32(bit32.band(flags, 0x80000000))")); + { + INFO("values added together: " + r.text); + CHECK(has(r, "table.append(params, PRIM_LINK_TARGET, LINK_ROOT, PRIM_POSITION, ll.GetPos())")); + CHECK(has(r, "table.insert(params, n)")); + } + { + INFO("the null key: " + r.text); + CHECK(has(r, "local gSitter = NULL_KEY")); + CHECK_FALSE(has(r, "uuid(\"\")")); + } + { + INFO("a float from a string: " + r.text); + CHECK(has(r, "lslFloat(\"1.5\")")); + CHECK(has(r, "return llcompat.List2Float({ s }, 0)")); + } + UNSCOPED_INFO("hexadecimal kept: " + r.text); + CHECK(has(r, "local MASK = 0x0000FF00")); + UNSCOPED_INFO("past 0x7FFFFFFF, which SLua reads as another number, as LSL had it: " + r.text); + CHECK(has(r, "local HIGH = -268435456")); // As the grid's 32-bit hosts read them: strtoul stops at 0xFFFFFFFF. - ensure("past 32 bits, -1: " + r.text, has(r, "local BIG = -1\n") && has(r, "local BIG_HEX = -1\n")); - ensure("and said: " + r.text, noted(r, "SluaIntegerPast32Bits", "4294967296") && noted(r, "SluaIntegerPast32Bits", "0x100000000")); - ensure("a global only bit32 reads keeps its hexadecimal: " + r.text, has(r, "local S_MASK = 0xF0000000\n") && - has(r, "bit32.band(flags, S_MASK)")); - ensure("one read as a number does not: " + r.text, has(r, "local HIGH = -268435456")); - ensure("nor a local, never set, only bit32 reads: " + r.text, has(r, "local low = 0xFFFF0000\n")); + { + INFO("past 32 bits, -1: " + r.text); + CHECK(has(r, "local BIG = -1\n")); + CHECK(has(r, "local BIG_HEX = -1\n")); + } + { + INFO("and said: " + r.text); + CHECK(noted(r, "SluaIntegerPast32Bits", "4294967296")); + CHECK(noted(r, "SluaIntegerPast32Bits", "0x100000000")); + } + { + INFO("a global only bit32 reads keeps its hexadecimal: " + r.text); + CHECK(has(r, "local S_MASK = 0xF0000000\n")); + CHECK(has(r, "bit32.band(flags, S_MASK)")); + } + UNSCOPED_INFO("one read as a number does not: " + r.text); + CHECK(has(r, "local HIGH = -268435456")); + UNSCOPED_INFO("nor a local, never set, only bit32 reads: " + r.text); + CHECK(has(r, "local low = 0xFFFF0000\n")); // Each said once, over the first place: what SLua reads such a // number as, and what bit32.s32 is for. const auto times = [&r](const char* key) { return std::count_if(r.notes.begin(), r.notes.end(), [key](const ALScriptProblem& p) { return p.key == key; }); }; - ensure("the hexadecimal's number said once, over the first: " + r.text, - times("SluaHexSign") == 1 && noted(r, "SluaHexSign", "0xF0000000") && - has(r, "-- LSL: SLua reads 0xF0000000 as 4026531840, where LSL wrapped it to -268435456: bit32 takes either the same, so it " + { + INFO("the hexadecimal's number said once, over the first: " + r.text); + CHECK(times("SluaHexSign") == 1); + CHECK(noted(r, "SluaHexSign", "0xF0000000")); + CHECK(has(r, "-- LSL: SLua reads 0xF0000000 as 4026531840, where LSL wrapped it to -268435456: bit32 takes either the same, so it " "stays as written where only bit32 reads it, and is -268435456 where it is read as a number.\nlocal HIGH = -268435456")); - ensure("bit32.s32 said once: " + r.text, times("SluaBit32Signed") == 1 && count(r, "-- LSL: bit32 answers 0 to 4294967295") == 1); - ensure("within them, wrapped: " + r.text, has(r, "local WRAPPED = -2147483648\n")); + } + { + INFO("bit32.s32 said once: " + r.text); + CHECK(times("SluaBit32Signed") == 1); + CHECK(count(r, "-- LSL: bit32 answers 0 to 4294967295") == 1); + } + UNSCOPED_INFO("within them, wrapped: " + r.text); + CHECK(has(r, "local WRAPPED = -2147483648\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<40>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: numeric for by a constant and a shared counter; one forward declaration", "[ALLSLToSLua]") { - set_test_name("numeric for by a constant's name, and a counter its fors share; each function local but one called before it is written"); const ALLSLToSLua::Result r = convert("integer ACTIVE_PROPS_STRIDE = 3;\n" "list activeProps = [\"a\", 1, 2, \"b\", 3, 4];\n" "setProps(list unattachedProps) {\n" @@ -1494,24 +2001,33 @@ namespace tut "report() { llOwnerSay(\"done\"); }\n" "integer twice(integer n) { return n * 2; }\n" "default { state_entry() { setProps([\"x\"]); llOwnerSay((string)twice(2)); } }\n"); - ensure("down by the constant's name: " + r.text, has(r, "for i = #activeProps - ACTIVE_PROPS_STRIDE, 0, -ACTIVE_PROPS_STRIDE do")); - ensure("the same counter's other for: " + r.text, has(r, "for i = 0, #unattachedProps - 1 do")); - ensure("the counter's declaration gone: " + r.text, !has(r, "local i = 0") && !has(r, "while i")); - ensure("only the one called first declared first: " + r.text, has(r, "local report\n") && has(r, "local function setProps(") && - has(r, "\nfunction report()") && has(r, "local function twice(")); + UNSCOPED_INFO("down by the constant's name: " + r.text); + CHECK(has(r, "for i = #activeProps - ACTIVE_PROPS_STRIDE, 0, -ACTIVE_PROPS_STRIDE do")); + UNSCOPED_INFO("the same counter's other for: " + r.text); + CHECK(has(r, "for i = 0, #unattachedProps - 1 do")); + { + INFO("the counter's declaration gone: " + r.text); + CHECK_FALSE(has(r, "local i = 0")); + CHECK_FALSE(has(r, "while i")); + } + { + INFO("only the one called first declared first: " + r.text); + CHECK(has(r, "local report\n")); + CHECK(has(r, "local function setProps(")); + CHECK(has(r, "\nfunction report()")); + CHECK(has(r, "local function twice(")); + } checksClean(r); const ALLSLToSLua::Result set = convert("integer gStep = 2;\n" "default { state_entry() { integer i; for (i = 0; i < 10; i += gStep) llOwnerSay((string)i); gStep = 3; } }\n"); - ensure("a step by a global something sets keeps the while: " + set.text, has(set, "while i < 10 do")); + UNSCOPED_INFO("a step by a global something sets keeps the while: " + set.text); + CHECK(has(set, "while i < 10 do")); checksClean(set); } - template<> template<> - void allsltoslua_object::test<41>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: comments as laid out: trailing; rules as dashes; the toggle; one-line ifs", "[ALLSLToSLua]") { - set_test_name("the script's comments as it laid them out: one after a statement still after it, rules of stars and slashes as dashes, " - "the //* toggle as ---[[, and an if on one line on one line"); ALLSLToSLua::Options options; options.types = true; const ALLSLToSLua::Result r = ALLSLToSLua::convert("/***************************************/\n" @@ -1538,18 +2054,31 @@ namespace tut "}\n" "default { state_entry() { llOwnerSay(get(allowed(S_MASK)) + (string)gDebug); } }\n", options); - ensure("converted", r.converted); - ensure("a rule of stars: " + r.text, has(r, "\n-----------------------------------------\n--------------- CONSTANTS ---------------\n")); - ensure("after its statement: " + r.text, has(r, "local S_MASK: number = -268435456 -- -268435456\n")); - ensure("a rule of slashes: " + r.text, has(r, "\n---------------------------------------------------------------\n") || - has(r, "------------------------- DEBUGGING ---------------------------\n")); - ensure("the toggle: " + r.text, has(r, "---[[\nlocal function get(") && has(r, "end\n--]]\n")); - ensure("the toggle the other way: " + r.text, has(r, "--[[\nstring old() { return \"\"; }\n--]]")); - ensure("an if on one line on one: " + r.text, - has(r, " if bit32.btest(parcelFlags, PARCEL_FLAG_ALLOW_CREATE_OBJECTS) then return 1 end -- may build\n")); - ensure("one on two lines on three: " + r.text, has(r, " if bit32.btest(parcelFlags, 4) then\n return 2\n end\n")); - ensure("after a declaration and a block comment after a step: " + r.text, - has(r, "local n: number = 3 -- three\n") && has(r, "n += 1 --[[ and one ]]\n")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("a rule of stars: " + r.text); + CHECK(has(r, "\n-----------------------------------------\n--------------- CONSTANTS ---------------\n")); + UNSCOPED_INFO("after its statement: " + r.text); + CHECK(has(r, "local S_MASK: number = -268435456 -- -268435456\n")); + UNSCOPED_INFO("a rule of slashes: " + r.text); + CHECK((has(r, "\n---------------------------------------------------------------\n") || + has(r, "------------------------- DEBUGGING ---------------------------\n"))); + { + INFO("the toggle: " + r.text); + CHECK(has(r, "---[[\nlocal function get(")); + CHECK(has(r, "end\n--]]\n")); + } + UNSCOPED_INFO("the toggle the other way: " + r.text); + CHECK(has(r, "--[[\nstring old() { return \"\"; }\n--]]")); + UNSCOPED_INFO("an if on one line on one: " + r.text); + CHECK(has(r, " if bit32.btest(parcelFlags, PARCEL_FLAG_ALLOW_CREATE_OBJECTS) then return 1 end -- may build\n")); + UNSCOPED_INFO("one on two lines on three: " + r.text); + CHECK(has(r, " if bit32.btest(parcelFlags, 4) then\n return 2\n end\n")); + { + INFO("after a declaration and a block comment after a step: " + r.text); + CHECK(has(r, "local n: number = 3 -- three\n")); + CHECK(has(r, "n += 1 --[[ and one ]]\n")); + } // The lines beside the LSL's past an if put on one line. std::vector lines; for (size_t from = 0; from <= r.text.size();) @@ -1568,16 +2097,20 @@ namespace tut } return "(none)"; }; - ensure("the if on one line beside its line: " + at(15), at(15).find("then return 1 end") != std::string::npos); - ensure("the next beside its own: " + at(16), at(16).find("if bit32.btest(parcelFlags, 4) then") != std::string::npos); - ensure("and those after: " + at(18) + " / " + at(19), at(18).find("local n: number = 3") != std::string::npos && at(19).find("n += 1") != std::string::npos); + UNSCOPED_INFO("the if on one line beside its line: " + at(15)); + REQUIRE(at(15).find("then return 1 end") != std::string::npos); + UNSCOPED_INFO("the next beside its own: " + at(16)); + REQUIRE(at(16).find("if bit32.btest(parcelFlags, 4) then") != std::string::npos); + { + INFO("and those after: " + at(18) + " / " + at(19)); + REQUIRE(at(18).find("local n: number = 3") != std::string::npos); + REQUIRE(at(19).find("n += 1") != std::string::npos); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<42>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: declarations the LSL lined up lined up again and others as written", "[ALLSLToSLua]") { - set_test_name("declarations the LSL lined up lined up again, their = and the comments after them; those it did not, as written"); ALLSLToSLua::Options options; options.types = true; const ALLSLToSLua::Result r = ALLSLToSLua::convert("integer S_MASK = 0xF0000000; // the sign\n" @@ -1592,19 +2125,21 @@ namespace tut " llOwnerSay(PREFIX + (string)(S_MASK + ACTIVE_STRIDE + a + x + yy) + bee);\n" "} }\n", options); - ensure("converted", r.converted); - ensure("globals lined up: " + r.text, has(r, "local S_MASK: number = -268435456 -- the sign\n" + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("globals lined up: " + r.text); + CHECK(has(r, "local S_MASK: number = -268435456 -- the sign\n" "local ACTIVE_STRIDE: number = 3 -- per prop\n" "local PREFIX: string = \"ARS#\" -- linkset data\n")); - ensure("not lined up, not lined up: " + r.text, has(r, "local a: number = 1\nlocal bee: string = \"b\"\n")); - ensure("locals lined up: " + r.text, has(r, "\nlocal x: number = 1\nlocal yy: number = 2.5\n")); + UNSCOPED_INFO("not lined up, not lined up: " + r.text); + CHECK(has(r, "local a: number = 1\nlocal bee: string = \"b\"\n")); + UNSCOPED_INFO("locals lined up: " + r.text); + CHECK(has(r, "\nlocal x: number = 1\nlocal yy: number = 2.5\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<43>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a vector or rotation cast from text bracketed only where needed", "[ALLSLToSLua]") { - set_test_name("text made a vector or a rotation bracketed only where an operator around it needs it"); const ALLSLToSLua::Result r = ALLSLToSLua::convert("vector offset;\n" "default { state_entry() {\n" " list command = [\"a\", \"<1,2,3>\"];\n" @@ -1613,36 +2148,40 @@ namespace tut " llOwnerSay((string)((vector)llList2String(command, 1) * 2.0));\n" " llSetPos((vector)llList2String(command, 1));\n" "} }\n"); - ensure("converted", r.converted); - ensure("assigned as it is: " + r.text, has(r, "offset = tovector(command[2] or \"\") or ZERO_VECTOR\n")); - ensure("a local's value too: " + r.text, has(r, "local r = toquaternion(command[2] or \"\") or ZERO_ROTATION\n")); - ensure("an argument too: " + r.text, has(r, "ll.SetPos(tovector(command[2] or \"\") or ZERO_VECTOR)")); - ensure("an operand bracketed: " + r.text, has(r, "(tovector(command[2] or \"\") or ZERO_VECTOR) * 2")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("assigned as it is: " + r.text); + CHECK(has(r, "offset = tovector(command[2] or \"\") or ZERO_VECTOR\n")); + UNSCOPED_INFO("a local's value too: " + r.text); + CHECK(has(r, "local r = toquaternion(command[2] or \"\") or ZERO_ROTATION\n")); + UNSCOPED_INFO("an argument too: " + r.text); + CHECK(has(r, "ll.SetPos(tovector(command[2] or \"\") or ZERO_VECTOR)")); + UNSCOPED_INFO("an operand bracketed: " + r.text); + CHECK(has(r, "(tovector(command[2] or \"\") or ZERO_VECTOR) * 2")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<44>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: an integer as text interpolated where joined and by tostring alone", "[ALLSLToSLua]") { - set_test_name("an integer made text where text is joined, interpolated; alone, or a lone piece of a table's, by tostring"); const ALLSLToSLua::Result r = convert("set(integer keey, string value) { llLinksetDataWrite(\"ARS#\" + (string)keey, value); }\n" "default { state_entry() {\n" " integer n = 4; float f = 1.5; key k = llGetOwner();\n" " set(n, (string)n);\n" " llOwnerSay((string)f + \";\" + (string)k + \";\" + (string)(n * 2) + (string)(-n));\n" "} }\n"); - ensure("converted", r.converted); - ensure("joined, interpolated: " + r.text, has(r, "ll.LinksetDataWrite(`ARS#{keey}`, value)")); - ensure("alone, still text: " + r.text, has(r, "set(n, tostring(n))")); - ensure("a float's six places, a key's text, a product bracketed, a negation bare: " + r.text, - has(r, "print(`{string.format(\"%.6f\", f)};{k};{n * 2}{-n}`)")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("joined, interpolated: " + r.text); + CHECK(has(r, "ll.LinksetDataWrite(`ARS#{keey}`, value)")); + UNSCOPED_INFO("alone, still text: " + r.text); + CHECK(has(r, "set(n, tostring(n))")); + UNSCOPED_INFO("a float's six places, a key's text, a product bracketed, a negation bare: " + r.text); + CHECK(has(r, "print(`{string.format(\"%.6f\", f)};{k};{n * 2}{-n}`)")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<45>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a vector as text with five places as LSL wrote it noted once", "[ALLSLToSLua]") { - set_test_name("a vector made text as LSL wrote it, five places a part, which tostring would not: noted, once"); const ALLSLToSLua::Result r = convert("integer SEAT_NUM = 0;\n" "vector offset = ZERO_VECTOR;\n" "set(integer which, string value) { llLinksetDataWrite((string)which, value); }\n" @@ -1650,18 +2189,23 @@ namespace tut " set(7, (string)SEAT_NUM + \";\" + (string)offset);\n" " llOwnerSay((string)ZERO_ROTATION);\n" "} }\n"); - ensure("converted", r.converted); - ensure("LSL's text kept: " + r.text, has(r, "set(7, `{SEAT_NUM};{ll.DumpList2String({ offset }, \"\")}`)")); - ensure("a rotation's too: " + r.text, has(r, "ll.DumpList2String({ ZERO_ROTATION }, \"\")")); - ensure("said why, once: " + r.text, noted(r, "SluaVectorText") && r.text.find("-- LSL: ll.DumpList2String writes") != std::string::npos && - r.text.find("-- LSL: ll.DumpList2String writes") == r.text.rfind("-- LSL: ll.DumpList2String writes")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("LSL's text kept: " + r.text); + CHECK(has(r, "set(7, `{SEAT_NUM};{ll.DumpList2String({ offset }, \"\")}`)")); + UNSCOPED_INFO("a rotation's too: " + r.text); + CHECK(has(r, "ll.DumpList2String({ ZERO_ROTATION }, \"\")")); + { + INFO("said why, once: " + r.text); + CHECK(noted(r, "SluaVectorText")); + REQUIRE(r.text.find("-- LSL: ll.DumpList2String writes") != std::string::npos); + CHECK(r.text.find("-- LSL: ll.DumpList2String writes") == r.text.rfind("-- LSL: ll.DumpList2String writes")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<46>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: text joined with non-strings interpolated and escaped; strings with ..", "[ALLSLToSLua]") { - set_test_name("text joined with a piece that is no string as one interpolated string, its own text escaped; strings alone with .."); const ALLSLToSLua::Result r = convert("default { state_entry() {\n" " key k = llGetOwner(); integer n = 3; string s = \"x\"; float f = 0.5;\n" " llOwnerSay(\"100% `{a}` \\\\ \\\"q\\\"\\n\" + (string)k);\n" @@ -1672,19 +2216,22 @@ namespace tut " integer i; for (i = 0; i < n; ++i) all += (string)k + \",\";\n" " llOwnerSay(all + s);\n" "} }\n"); - ensure("converted", r.converted); - ensure("its text escaped -- a backtick, a brace, a backslash, a line -- and % as it is: " + r.text, - has(r, "print(`100% \\`\\{a}\\` \\\\ \"q\"\\n{k}`)")); - ensure("a number joined too: " + r.text, has(r, "print(`n={n};{s}`)")); - ensure("strings alone, with ..: " + r.text, has(r, "print(all .. s)")); - ensure("one inside another's braces: " + r.text, has(r, "print(`a{ll.ToUpper(`b{k}`)}{string.format(\"%.6f\", f)}`)")); - ensure("joined onto a string: " + r.text, has(r, "s ..= `/{k}`")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("its text escaped -- a backtick, a brace, a backslash, a line -- and % as it is: " + r.text); + CHECK(has(r, "print(`100% \\`\\{a}\\` \\\\ \"q\"\\n{k}`)")); + UNSCOPED_INFO("a number joined too: " + r.text); + CHECK(has(r, "print(`n={n};{s}`)")); + UNSCOPED_INFO("strings alone, with ..: " + r.text); + CHECK(has(r, "print(all .. s)")); + UNSCOPED_INFO("one inside another's braces: " + r.text); + CHECK(has(r, "print(`a{ll.ToUpper(`b{k}`)}{string.format(\"%.6f\", f)}`)")); + UNSCOPED_INFO("joined onto a string: " + r.text); + CHECK(has(r, "s ..= `/{k}`")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<47>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: spans end where their SLua does: blocks at end and statements on a line", "[ALLSLToSLua]") { - set_test_name("spans end where the SLua made of them does: a block at its end, a statement at its own line, a comment over the next left to the next"); const std::string lsl = "default\n" // 0 "{\n" // 1 " touch_start(integer d)\n" // 2 @@ -1724,30 +2271,50 @@ namespace tut return first == std::string::npos ? std::string() : text.substr(first); }; const ALLSLToSLua::Span* block = span(4); - ensure("the if", block && block->lslLast == 7); - ensure("from its head: " + trimmed(block->sluaFirst), trimmed(block->sluaFirst).find("if d > 1 then") == 0); - ensure("to its end: " + trimmed(block->sluaLast) + "\n" + r.text, trimmed(block->sluaLast) == "end"); + { + INFO("the if"); + REQUIRE(block); + CHECK(block->lslLast == 7); + } + UNSCOPED_INFO("from its head: " + trimmed(block->sluaFirst)); + CHECK(trimmed(block->sluaFirst).find("if d > 1 then") == 0); + UNSCOPED_INFO("to its end: " + trimmed(block->sluaLast) + "\n" + r.text); + CHECK(trimmed(block->sluaLast) == "end"); const ALLSLToSLua::Span* inside = span(6); std::string all; for (const ALLSLToSLua::Span& each : r.spans) { all += std::to_string(each.lslFirst) + "-" + std::to_string(each.lslLast) + " : " + std::to_string(each.sluaFirst) + "-" + std::to_string(each.sluaLast) + "\n"; } - ensure("the statement in it, a line of its own\n" + all + r.text, inside && inside->lslLast == 6 && inside->sluaFirst == inside->sluaLast && - trimmed(inside->sluaFirst).find("ll.Say(0, \"many\")") == 0); + { + INFO("the statement in it, a line of its own\n" + all + r.text); + REQUIRE(inside); + REQUIRE(inside->lslLast == 6); + REQUIRE(inside->sluaFirst == inside->sluaLast); + CHECK(trimmed(inside->sluaFirst).find("ll.Say(0, \"many\")") == 0); + } const ALLSLToSLua::Span* next = span(9); - ensure("the next, under the comment: " + (next ? trimmed(next->sluaFirst) : std::string()), - next && next->sluaFirst == next->sluaLast && trimmed(next->sluaFirst).find("ll.Say(0, \"one\")") == 0 && - trimmed(next->sluaFirst - 1) == "-- after"); + { + INFO("the next, under the comment: " + (next ? trimmed(next->sluaFirst) : std::string())); + REQUIRE(next); + REQUIRE(next->sluaFirst == next->sluaLast); + REQUIRE(trimmed(next->sluaFirst).find("ll.Say(0, \"one\")") == 0); + CHECK(trimmed(next->sluaFirst - 1) == "-- after"); + } const ALLSLToSLua::Span* handler = span(2); - ensure("the handler, over all of it", handler && handler->lslLast == 10 && handler->sluaFirst < block->sluaFirst && handler->sluaLast >= next->sluaLast); - ensure("to its own end: " + trimmed(handler->sluaLast), trimmed(handler->sluaLast).find("end") == 0); + { + INFO("the handler, over all of it"); + REQUIRE(handler); + REQUIRE(handler->lslLast == 10); + REQUIRE(handler->sluaFirst < block->sluaFirst); + CHECK(handler->sluaLast >= next->sluaLast); + } + UNSCOPED_INFO("to its own end: " + trimmed(handler->sluaLast)); + CHECK(trimmed(handler->sluaLast).find("end") == 0); } - template<> template<> - void allsltoslua_object::test<48>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: what the SLua says otherwise: calls and names and syntax; no semicolons", "[ALLSLToSLua]") { - set_test_name("what the SLua says otherwise: each call's name as written, SLua's own way's too, a name SLua holds for its own, the syntax always; semicolons let go of"); const ALLSLToSLua::Result r = convert("integer end = 1;\n" "default\n" "{\n" @@ -1760,20 +2327,33 @@ namespace tut const auto has_pair = [&r](const std::string& lsl, const std::string& slua) { return std::find(r.same.begin(), r.same.end(), std::make_pair(lsl, slua)) != r.same.end(); }; - ensure("llSay as ll.Say: " + r.text, has_pair("llSay", "ll.Say")); - ensure("llOwnerSay as SLua's print: " + r.text, has_pair("llOwnerSay", "print")); - ensure("llAbs as SLua's own: " + r.text, has_pair("llAbs", "math.abs")); - ensure("a name Luau holds, marked", has_pair("end", "end_") && has(r, "local end_")); - ensure("the syntax", has_pair("!=", "~=") && has_pair("&&", "and") && has_pair("integer", "local") && has_pair("TRUE", "true") && - has_pair("(string)", "tostring")); - ensure("each once", std::count(r.same.begin(), r.same.end(), std::make_pair(std::string("llSay"), std::string("ll.Say"))) == 1); - ensure("semicolons let go of", r.dropped == std::vector{ ";" }); + UNSCOPED_INFO("llSay as ll.Say: " + r.text); + CHECK(has_pair("llSay", "ll.Say")); + UNSCOPED_INFO("llOwnerSay as SLua's print: " + r.text); + CHECK(has_pair("llOwnerSay", "print")); + UNSCOPED_INFO("llAbs as SLua's own: " + r.text); + CHECK(has_pair("llAbs", "math.abs")); + { + INFO("a name Luau holds, marked"); + CHECK(has_pair("end", "end_")); + CHECK(has(r, "local end_")); + } + { + INFO("the syntax"); + CHECK(has_pair("!=", "~=")); + CHECK(has_pair("&&", "and")); + CHECK(has_pair("integer", "local")); + CHECK(has_pair("TRUE", "true")); + CHECK(has_pair("(string)", "tostring")); + } + UNSCOPED_INFO("each once"); + CHECK(std::count(r.same.begin(), r.same.end(), std::make_pair(std::string("llSay"), std::string("ll.Say"))) == 1); + UNSCOPED_INFO("semicolons let go of"); + CHECK(r.dropped == std::vector{ ";" }); } - template<> template<> - void allsltoslua_object::test<49>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: an integer added to a string by += or s = s + joined by ..= alone", "[ALLSLToSLua]") { - set_test_name("an integer made text and added to a string with += joined on as s = s + it has it, by .. alone"); const ALLSLToSLua::Result r = convert("string gOut;\n" "string gTwo;\n" "default { state_entry() {\n" @@ -1782,16 +2362,17 @@ namespace tut " gTwo = gTwo + (string)n;\n" " llOwnerSay(gOut + gTwo);\n" "} }\n"); - ensure("converted", r.converted); - ensure("+=: " + r.text, has(r, "gOut ..= n\n")); - ensure("s = s +: " + r.text, has(r, "gTwo ..= n\n")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("+=: " + r.text); + CHECK(has(r, "gOut ..= n\n")); + UNSCOPED_INFO("s = s +: " + r.text); + CHECK(has(r, "gTwo ..= n\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<50>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: spans: statements sharing an LSL line side by side and not nested", "[ALLSLToSLua]") { - set_test_name("spans: statements sharing an LSL line side by side, not one inside the other"); const ALLSLToSLua::Result r = convert("integer x;\n" // 0 "integer y;\n" // 1 "default\n" // 2 @@ -1813,22 +2394,26 @@ namespace tut shared.push_back(&each); } } - ensure_equals("two statements on the line\n" + all + r.text, shared.size(), size_t(2)); - ensure("their SLua apart\n" + all + r.text, shared[0]->sluaLast < shared[1]->sluaFirst || shared[1]->sluaLast < shared[0]->sluaFirst); + UNSCOPED_INFO("two statements on the line\n" + all + r.text); + REQUIRE(shared.size() == size_t(2)); + UNSCOPED_INFO("their SLua apart\n" + all + r.text); + 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) { if (each.lslFirst == 4) { - ensure("the handler over both\n" + all, each.sluaFirst < shared[0]->sluaFirst && each.sluaLast >= shared[1]->sluaLast); + { + INFO("the handler over both\n" + all); + REQUIRE(each.sluaFirst < shared[0]->sluaFirst); + REQUIRE(each.sluaLast >= shared[1]->sluaLast); + } } } } - template<> template<> - void allsltoslua_object::test<51>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: what the SLua says otherwise: a call as written; marks only where written", "[ALLSLToSLua]") { - set_test_name("what the SLua says otherwise: a call by the name it was written with, not one inside it; marks only where written so"); const auto has_pair = [](const ALLSLToSLua::Result& r, const std::string& lsl, const std::string& slua) { return std::find(r.same.begin(), r.same.end(), std::make_pair(lsl, slua)) != r.same.end(); }; @@ -1836,27 +2421,39 @@ namespace tut " integer n = 2;\n" " if (n > 1) llSay(0, (string)llPow(llFrand(1.0), 2.0));\n" "} }\n"); - ensure("llFrand as ll.Frand: " + plain.text, has_pair(plain, "llFrand", "ll.Frand")); - ensure("llPow, written as ^, not as the call inside it: " + plain.text, - std::none_of(plain.same.begin(), plain.same.end(), [](const auto& p) { return p.first == "llPow"; })); - ensure("a block's braces: " + plain.text, has_pair(plain, "{", "then") && has_pair(plain, "}", "end")); - ensure("no vector, no string joined: no marks for them: " + plain.text, - !has_pair(plain, ">", ")") && !has_pair(plain, "<", "vector(") && !has_pair(plain, "+", "..") && !has_pair(plain, "+=", "..=")); + UNSCOPED_INFO("llFrand as ll.Frand: " + plain.text); + CHECK(has_pair(plain, "llFrand", "ll.Frand")); + UNSCOPED_INFO("llPow, written as ^, not as the call inside it: " + plain.text); + CHECK(std::none_of(plain.same.begin(), plain.same.end(), [](const auto& p) { return p.first == "llPow"; })); + { + INFO("a block's braces: " + plain.text); + CHECK(has_pair(plain, "{", "then")); + CHECK(has_pair(plain, "}", "end")); + } + { + INFO("no vector, no string joined: no marks for them: " + plain.text); + CHECK_FALSE(has_pair(plain, ">", ")")); + CHECK_FALSE(has_pair(plain, "<", "vector(")); + CHECK_FALSE(has_pair(plain, "+", "..")); + CHECK_FALSE(has_pair(plain, "+=", "..=")); + } const ALLSLToSLua::Result marks = convert("string gOut;\n" "default { state_entry() {\n" " vector v = <1, 2, llFrand(1.0)>;\n" " gOut += llGetObjectName();\n" " llSay(0, gOut + llGetObjectDesc() + (string)v.z);\n" "} }\n"); - ensure("a vector's brackets: " + marks.text, has_pair(marks, "<", "vector(") && has_pair(marks, ">", ")")); - ensure("a string added to: " + marks.text, has_pair(marks, "+=", "..=")); + { + INFO("a vector's brackets: " + marks.text); + CHECK(has_pair(marks, "<", "vector(")); + CHECK(has_pair(marks, ">", ")")); + } + UNSCOPED_INFO("a string added to: " + marks.text); + CHECK(has_pair(marks, "+=", "..=")); } - template<> template<> - void allsltoslua_object::test<52>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a comment after a block's brace stays after the line that opens it", "[ALLSLToSLua]") { - set_test_name("a comment after a block's brace stays after the line that opens the block: then, else, elseif, a loop's do, repeat, a function, a " - "handler; one over several lines too, running on; a one-line if with a block comment after its brace still on one line"); const std::string lsl = "vector offset;\n" "list adjustments;\n" "integer count() { // how many\n" @@ -1889,24 +2486,40 @@ namespace tut " }\n" "}\n"; const ALLSLToSLua::Result r = convert(lsl); - ensure("converted", r.converted); - ensure("then: " + r.text, has(r, " if i < 0 then -- no saved setting. use 0\n offset = ZERO_VECTOR\n")); - ensure("elseif: " + r.text, has(r, " elseif i > 100 then -- too far\n")); - ensure("else: " + r.text, has(r, " else -- restore saved setting\n")); - ensure("not over the if: " + r.text, !has(r, "-- no saved setting. use 0\n -- restore") && count(r, "restore saved setting") == 1); - ensure("a while's do: " + r.text, has(r, " do -- count down\n")); - ensure("repeat: " + r.text, has(r, "repeat --[[ at least once ]]\n")); - ensure("a for's do: " + r.text, has(r, " do -- each\n")); - ensure("a function: " + r.text, has(r, "local function count() -- how many\n")); - ensure("a handler: " + r.text, has(r, "LLEvents:on(\"touch_start\", function(detected) -- whoever touches\n")); - ensure("one over two lines, running on: " + r.text, has(r, " if i ~= 0 then --[[ over\n two lines ]]\n print(\"x\")\n")); - ensure("a one-line if on one line still: " + r.text, has(r, " if n ~= 0 then --[[ one ]] print(\"y\") end\n")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("then: " + r.text); + CHECK(has(r, " if i < 0 then -- no saved setting. use 0\n offset = ZERO_VECTOR\n")); + UNSCOPED_INFO("elseif: " + r.text); + CHECK(has(r, " elseif i > 100 then -- too far\n")); + UNSCOPED_INFO("else: " + r.text); + CHECK(has(r, " else -- restore saved setting\n")); + { + INFO("not over the if: " + r.text); + CHECK_FALSE(has(r, "-- no saved setting. use 0\n -- restore")); + CHECK(count(r, "restore saved setting") == 1); + } + UNSCOPED_INFO("a while's do: " + r.text); + CHECK(has(r, " do -- count down\n")); + UNSCOPED_INFO("repeat: " + r.text); + CHECK(has(r, "repeat --[[ at least once ]]\n")); + UNSCOPED_INFO("a for's do: " + r.text); + CHECK(has(r, " do -- each\n")); + UNSCOPED_INFO("a function: " + r.text); + CHECK(has(r, "local function count() -- how many\n")); + UNSCOPED_INFO("a handler: " + r.text); + CHECK(has(r, "LLEvents:on(\"touch_start\", function(detected) -- whoever touches\n")); + UNSCOPED_INFO("one over two lines, running on: " + r.text); + CHECK(has(r, " if i ~= 0 then --[[ over\n two lines ]]\n print(\"x\")\n")); + UNSCOPED_INFO("a one-line if on one line still: " + r.text); + CHECK(has(r, " if n ~= 0 then --[[ one ]] print(\"y\") end\n")); checksClean(r); // The same with the field style, and in a state's table. ALLSLToSLua::Options field; field.handlers = ALLSLToSLua::Options::Handlers::Field; - ensure("a field's handler: ", has(ALLSLToSLua::convert(lsl, field), "function LLEvents.touch_start(detected) -- whoever touches\n")); + UNSCOPED_INFO("a field's handler: "); + CHECK(has(ALLSLToSLua::convert(lsl, field), "function LLEvents.touch_start(detected) -- whoever touches\n")); const ALLSLToSLua::Result states = convert("default {\n" " touch_start(integer n) { // first state\n" " state other;\n" @@ -1917,20 +2530,24 @@ namespace tut " llOwnerSay(\"in\");\n" " }\n" "}\n"); - ensure("in a state's table: " + states.text, has(states, "touch_start = function(detected) -- first state\n") && - has(states, "state_entry = function() -- entered\n")); - checksClean(states); + { + INFO("in a state's table: " + states.text); + CHECK(has(states, "touch_start = function(detected) -- first state\n")); + CHECK(has(states, "state_entry = function() -- entered\n")); + } + checksClean(states); // A single state's state_entry runs at the top: its comment at the // top of what it ran. const ALLSLToSLua::Result entry = convert("default {\n state_entry() { // set up\n llOwnerSay(\"hi\");\n }\n}\n"); - ensure("state_entry's, kept: " + entry.text, has(entry, "-- set up\n") && entry.text.find("-- set up") < entry.text.find("ll.OwnerSay")); + { + INFO("state_entry's, kept: " + entry.text); + CHECK(has(entry, "-- set up\n")); + CHECK(entry.text.find("-- set up") < entry.text.find("ll.OwnerSay")); + } } - template<> template<> - void allsltoslua_object::test<53>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a comment after the brace of an unwritten block goes over its stand-in", "[ALLSLToSLua]") { - set_test_name("a comment after the brace of a block not written goes over what stands for it, not at the end of what holds it: a boolean's " - "if and else, a lone state's state_exit; an if with -- in a string in its check still on one line"); const ALLSLToSLua::Result r = convert("integer gMany;\n" "default {\n" " touch_start(integer n) {\n" @@ -1945,8 +2562,17 @@ namespace tut const size_t many = r.text.find(" -- many\n"); const size_t one = r.text.find(" -- one\n"); const size_t set = r.text.find(" gMany = n > 1\n"); - ensure("both over the line that stands for the if: " + r.text, set != std::string::npos && many < one && one < set); - ensure("once each: " + r.text, count(r, "-- many") == 1 && count(r, "-- one") == 1); + { + INFO("both over the line that stands for the if: " + r.text); + REQUIRE(set != std::string::npos); + CHECK(many < one); + CHECK(one < set); + } + { + INFO("once each: " + r.text); + CHECK(count(r, "-- many") == 1); + CHECK(count(r, "-- one") == 1); + } checksClean(r); const ALLSLToSLua::Result exit = convert("default {\n" @@ -1958,22 +2584,24 @@ namespace tut " }\n" "}\n"); const size_t left = exit.text.find("-- state_exit, left out"); - ensure("state_exit's, over the line said in its place: " + exit.text, - left != std::string::npos && exit.text.find("-- say goodbye\n") < left && count(exit, "-- say goodbye") == 1); + { + INFO("state_exit's, over the line said in its place: " + exit.text); + REQUIRE(left != std::string::npos); + CHECK(exit.text.find("-- say goodbye\n") < left); + CHECK(count(exit, "-- say goodbye") == 1); + } const ALLSLToSLua::Result dashes = convert("default {\n" " listen(integer c, string name, key id, string s) {\n" " if (s == \" -- x\") llOwnerSay(\"y\");\n" " }\n" "}\n"); - ensure("a string's dashes no comment: " + dashes.text, has(dashes, " == \" -- x\" then print(\"y\") end\n")); + UNSCOPED_INFO("a string's dashes no comment: " + dashes.text); + CHECK(has(dashes, " == \" -- x\" then print(\"y\") end\n")); } - template<> template<> - void allsltoslua_object::test<54>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: llVecNorm through one vecNorm that keeps zero zero unless known nonzero", "[ALLSLToSLua]") { - set_test_name("llVecNorm keeps a zero vector zero, as LSL does, where vector.normalize makes NaN of it: through vecNorm, written " - "once, but for a vector known not to be nought; no vecNorm where nothing normalizes"); const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" " vector d = llDetectedPos(0) - llGetPos();\n" " vector a = llVecNorm(d);\n" @@ -1982,20 +2610,204 @@ namespace tut " vector z = llVecNorm(ZERO_VECTOR);\n" " llOwnerSay((string)(a + b + c + z));\n" "} }\n"); - ensure("guarded: " + r.text, has(r, "local a = vecNorm(d)") && has(r, "vecNorm(ll.GetVel())")); - ensure("written once: " + r.text, count(r, "local function vecNorm(v: vector): vector") == 1); - ensure("a vector known: " + r.text, has(r, "vector.normalize(vector(0, 3, 4))")); - ensure("nought guarded: " + r.text, has(r, "vecNorm(ZERO_VECTOR)")); + { + INFO("guarded: " + r.text); + CHECK(has(r, "local a = vecNorm(d)")); + CHECK(has(r, "vecNorm(ll.GetVel())")); + } + UNSCOPED_INFO("written once: " + r.text); + CHECK(count(r, "local function vecNorm(v: vector): vector") == 1); + UNSCOPED_INFO("a vector known: " + r.text); + CHECK(has(r, "vector.normalize(vector(0, 3, 4))")); + UNSCOPED_INFO("nought guarded: " + r.text); + CHECK(has(r, "vecNorm(ZERO_VECTOR)")); checksClean(r); const ALLSLToSLua::Result none = convert("default { state_entry() { llOwnerSay((string)llVecMag(<1, 2, 3>)); } }\n"); - ensure("no helper unasked: " + none.text, !has(none, "vecNorm")); + UNSCOPED_INFO("no helper unasked: " + none.text); + CHECK_FALSE(has(none, "vecNorm")); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: lists appended with table.extend and + on a copy of the left; no joinLists", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result r = convert("list gKept = [1];\n" + "list kept() { return gKept; }\n" + "show(list l) { llOwnerSay(llDumpList2String(l, \",\")); }\n" + "default { touch_start(integer n) {\n" + " list all = llParseString2List(\"a,b\", [\",\"], []);\n" + " list more = [4, 5];\n" + " all += more;\n" + " all += all;\n" + " all += llList2List(more, 0, n);\n" + " all += [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, " + "26, 27, 28, 29, 30, 31, 32, \"x\"];\n" + " show(all);\n" + " show(kept() + more + all);\n" + " show(more + n);\n" + " show(n + more);\n" + " show([PRIM_NAME, \"x\"] + more);\n" + " show(llParseString2List(\"c\", [], []) + [1] + [<1, 2, 3>]);\n" + " llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_COLOR, ALL_SIDES, <1, 1, 1>, 1.0] + more);\n" + "} }\n"); + UNSCOPED_INFO("a list by its name: " + r.text); + CHECK(has(r, "table.extend(all, more)")); + UNSCOPED_INFO("itself: " + r.text); + CHECK(has(r, "table.extend(all, all)")); + { + INFO("a call's, run once: " + r.text); + CHECK(has(r, "table.extend(all, llcompat.List2List(more, 0, n))")); + CHECK_FALSE(has(r, "for _, item in")); + } + { + INFO("many values: " + r.text); + CHECK(has(r, "table.extend(all, { 0, 1, 2,")); + CHECK(has(r, "32, \"x\" })")); + } + UNSCOPED_INFO("one copy, of the variable a call answers: " + r.text); + CHECK(has(r, "show(table.extend(table.extend(table.clone(kept()), more), all))")); + UNSCOPED_INFO("a variable copied: " + r.text); + CHECK(has(r, "show(table.extend(table.clone(more), { n }))")); + UNSCOPED_INFO("a value: " + r.text); + CHECK(has(r, "show(table.extend({ n } :: { any }, more))")); + { + INFO("written out: " + r.text); + CHECK(has(r, "show(table.extend({ PRIM_NAME, \"x\" } :: { any }, more))")); + CHECK(has(r, "table.extend({ PRIM_COLOR, ALL_SIDES, vector(1, 1, 1), 1.0 } :: { any }, more)")); + } + UNSCOPED_INFO("a library call's: " + r.text); + CHECK(has(r, "show(table.extend(table.extend(ll.ParseString2List(\"c\", {}, {}) :: { any }, { 1 }), { vector(1, 2, 3) }))")); + { + INFO("no helper, nor a move: " + r.text); + CHECK_FALSE(has(r, "joinLists")); + CHECK_FALSE(has(r, "table.move(")); + } + checksClean(r); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: list functions through SLua's own where they do as LSL's; else llcompat", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result r = convert("list gNames = [\"a\", \"b\"];\n" + "list gKept;\n" + "list gL = [1, 2, 3];\n" + "integer bump() { gL += [0]; return 1; }\n" + "default { touch_start(integer n) {\n" + " list l = [1, 2, 3];\n" + " integer i;\n" + " for (i = 0; i < llGetListLength(l); ++i)\n" + " l = llListReplaceList(l, [llList2Integer(l, i) * 2], i, i);\n" + " list held = [1, 2];\n" + " gKept = held;\n" + " integer j;\n" + " for (j = 0; j < llGetListLength(held); ++j)\n" + " held = llListReplaceList(held, [j], j, j);\n" + " integer k;\n" + " for (k = 0; k < llGetListLength(gL); ++k)\n" + " gL = llListReplaceList(gL, [bump()], k, k);\n" + " l = llListReplaceList(l, [7], -1, -1);\n" + " l = llListInsertList(l, [9], llGetListLength(l));\n" + " list part = llList2List(l, 1, 2);\n" + " list all = llList2List(l, 0, -1);\n" + " list rest = llList2List(l, 2, -1);\n" + " list wrapped = llList2List(l, 3, 1);\n" + " llOwnerSay(llDumpList2String(l, \",\"));\n" + " llOwnerSay(llDumpList2String(gNames + [n], \", \"));\n" + " llOwnerSay(llDumpList2String(l + [1.5], \",\"));\n" + " llOwnerSay((string)l);\n" + " integer at = llListFindList(gNames, [\"b\"]);\n" + " integer none = llListFindList(gNames, [NULL_KEY]);\n" + " if (llListFindList(gNames, [NULL_KEY]) != -1) llOwnerSay(\"null\");\n" + " integer parsed = llListFindList(llParseString2List(\"a,b\", [\",\"], []), [\"b\"]);\n" + " llOwnerSay((string)(at + none + parsed) + llDumpList2String(part + all + rest + wrapped + held, \"\"));\n" + "} }\n"); + UNSCOPED_INFO("replaced in place, in Luau's numeric for: " + r.text); + CHECK(has(r, "for i = 0, #l - 1 do\n l[i + 1] = l[i + 1] * 2\n")); + { + INFO("a list held elsewhere, replaced as LSL's: " + r.text); + CHECK(has(r, "for j = 0, #held - 1 do\n")); + CHECK(has(r, " held = llcompat.ListReplaceList(held, { j }, j, j)\n")); + } + { + INFO("a global a call of the script's could change: " + r.text); + CHECK(has(r, "while k < #gL do")); + CHECK(has(r, "gL = llcompat.ListReplaceList(gL, { bump() }, k, k)")); + } + UNSCOPED_INFO("the last of a list that could be empty: " + r.text); + CHECK(has(r, "l = llcompat.ListReplaceList(l, { 7 }, -1, -1)")); + UNSCOPED_INFO("on the end: " + r.text); + CHECK(has(r, "table.insert(l, 9)")); + { + INFO("a part, and all of it: " + r.text); + CHECK(has(r, "local part = table.move(l, 2, 3, 1, {})")); + CHECK(has(r, "local all = table.clone(l)")); + } + { + INFO("to the end, and the part left out, as LSL's: " + r.text); + CHECK(has(r, "local rest = llcompat.List2List(l, 2, -1)")); + CHECK(has(r, "local wrapped = llcompat.List2List(l, 3, 1)")); + } + { + INFO("whole numbers joined: " + r.text); + CHECK(has(r, "print(table.concat(l, \",\"))")); + CHECK(has(r, "print(table.concat(l))")); + } + UNSCOPED_INFO("and text: " + r.text); + CHECK(has(r, "print(table.concat(table.extend(table.clone(gNames), { n }), \", \"))")); + UNSCOPED_INFO("a float as LSL writes one: " + r.text); + CHECK(has(r, "print(ll.DumpList2String(table.extend(table.clone(l), { 1.5 }), \",\"))")); + UNSCOPED_INFO("found, its place: " + r.text); + CHECK(has(r, "local at = (table.find(gNames, \"b\") or 0) - 1")); + { + INFO("NULL_KEY as LSL's string, as its place and found: " + r.text); + CHECK(has(r, "local none = (table.find(gNames, tostring(NULL_KEY)) or 0) - 1")); + CHECK(has(r, "if table.find(gNames, tostring(NULL_KEY)) ~= nil then")); + } + UNSCOPED_INFO("in a library call's list: " + r.text); + CHECK(has(r, "local parsed = (table.find(ll.ParseString2List(\"a,b\", { \",\" }, {}) :: { any }, \"b\") or 0) - 1")); + UNSCOPED_INFO("no llcompat but those: " + r.text); + CHECK(count(r, "llcompat.") == 5); + checksClean(r); } - template<> template<> - void allsltoslua_object::test<65>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: llSubStringIndex asked only whether it found is a plain string.find", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" + " string s = llGetObjectName(); key k = llGetOwner();\n" + " if (llSubStringIndex(s, \"a\") != -1) llOwnerSay(\"a\");\n" + " if (llSubStringIndex(s, \"b\") >= 0) llOwnerSay(\"b\");\n" + " if (llSubStringIndex(s, \"c\") > -1) llOwnerSay(\"c\");\n" + " if (llSubStringIndex(s, \"d\") == -1) llOwnerSay(\"no d\");\n" + " if (!~llSubStringIndex(s, \"e\")) llOwnerSay(\"no e\");\n" + " if (~llSubStringIndex(s, \".\")) llOwnerSay(\"dot\");\n" + " if (~llSubStringIndex(s, k)) llOwnerSay(\"owner\");\n" + " if (~llSubStringIndex(s, \"\")) llOwnerSay(\"empty\");\n" + " integer at = llSubStringIndex(s, \"x\");\n" + " llOwnerSay((string)at);\n" + "} }\n"); + { + INFO("found: " + r.text); + CHECK(has(r, "if string.find(s, \"a\", 1, true) ~= nil then")); + CHECK(has(r, "if string.find(s, \"b\", 1, true) ~= nil then")); + CHECK(has(r, "if string.find(s, \"c\", 1, true) ~= nil then")); + } + { + INFO("not: " + r.text); + CHECK(has(r, "if string.find(s, \"d\", 1, true) == nil then")); + CHECK(has(r, "if not (string.find(s, \"e\", 1, true) ~= nil) then")); + } + UNSCOPED_INFO("plain, not a pattern: " + r.text); + CHECK(has(r, "if string.find(s, \".\", 1, true) ~= nil then")); + UNSCOPED_INFO("a key as its text: " + r.text); + CHECK(has(r, "if string.find(s, tostring(k), 1, true) ~= nil then")); + UNSCOPED_INFO("the empty text: " + r.text); + CHECK(has(r, "if string.find(s, \"\", 1, true) ~= nil then")); + UNSCOPED_INFO("its index in characters, ll's: " + r.text); + CHECK(has(r, "local at = (ll.SubStringIndex(s, \"x\") or 0) - 1")); + UNSCOPED_INFO("found by string.find alone: " + r.text); + CHECK(count(r, "SubStringIndex") == 1); + checksClean(r); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a script that resets its time keeps its own timeBase clock; else ll's", "[ALLSLToSLua]") { - set_test_name("a script that resets its time keeps a clock of its own, script-wide as a state change leaves LSL's: ll.GetTime read " - "from timeBase, each reset setting it again; one that only reads keeps ll.GetTime; close to LSL, llcompat's three"); const ALLSLToSLua::Result r = convert("float timeBase;\n" "default {\n" " state_entry() { llResetTime(); state running; }\n" @@ -2008,37 +2820,60 @@ namespace tut " llOwnerSay((string)(lap + timeBase) + \" \" + (string)(llGetTime() * 2));\n" " }\n" "}\n"); - ensure("the clock, once, over everything: " + r.text, - count(r, "local timeBase = ll.GetTime()") == 1 && r.text.find("local timeBase = ll.GetTime()") < r.text.find("local states")); - ensure("the script's own name made another: " + r.text, has(r, "local timeBase_ = 0") && has(r, "timeBase_ = ll.GetTime() - timeBase")); - ensure("a reset: " + r.text, has(r, "state_entry = function()\n timeBase = ll.GetTime()\n setState(\"running\")")); - ensure("both, as a value, through the helper: " + r.text, - has(r, "local lap = getAndResetTime()") && count(r, "local function getAndResetTime(): number") == 1); - ensure("both where nothing reads the time, the reset alone: " + r.text, - has(r, "local lap = getAndResetTime()\n timeBase = ll.GetTime()\n")); - ensure("a read bracketed: " + r.text, has(r, "(ll.GetTime() - timeBase) * 2")); - ensure("nothing of llcompat's: " + r.text, !has(r, "llcompat.ResetTime") && !has(r, "llcompat.GetAndResetTime") && - !noted(r, "SluaCompatOnly", "ResetTime") && !noted(r, "SluaCompatOnly", "GetAndResetTime")); + { + INFO("the clock, once, over everything: " + r.text); + CHECK(count(r, "local timeBase = ll.GetTime()") == 1); + CHECK(r.text.find("local timeBase = ll.GetTime()") < r.text.find("local states")); + } + { + INFO("the script's own name made another: " + r.text); + CHECK(has(r, "local timeBase_ = 0")); + CHECK(has(r, "timeBase_ = ll.GetTime() - timeBase")); + } + UNSCOPED_INFO("a reset: " + r.text); + CHECK(has(r, "state_entry = function()\n timeBase = ll.GetTime()\n setState(\"running\")")); + { + INFO("both, as a value, through the helper: " + r.text); + CHECK(has(r, "local lap = getAndResetTime()")); + CHECK(count(r, "local function getAndResetTime(): number") == 1); + } + UNSCOPED_INFO("both where nothing reads the time, the reset alone: " + r.text); + CHECK(has(r, "local lap = getAndResetTime()\n timeBase = ll.GetTime()\n")); + UNSCOPED_INFO("a read bracketed: " + r.text); + CHECK(has(r, "(ll.GetTime() - timeBase) * 2")); + { + INFO("nothing of llcompat's: " + r.text); + CHECK_FALSE(has(r, "llcompat.ResetTime")); + CHECK_FALSE(has(r, "llcompat.GetAndResetTime")); + CHECK_FALSE(noted(r, "SluaCompatOnly", "ResetTime")); + CHECK_FALSE(noted(r, "SluaCompatOnly", "GetAndResetTime")); + } checksClean(r); const ALLSLToSLua::Result reads = convert("default { touch_start(integer n) { llOwnerSay((string)llGetTime()); } }\n"); - ensure("only read, ll's: " + reads.text, has(reads, "ll.GetTime()") && !has(reads, "timeBase")); + { + INFO("only read, ll's: " + reads.text); + CHECK(has(reads, "ll.GetTime()")); + CHECK_FALSE(has(reads, "timeBase")); + } const ALLSLToSLua::Result close = ALLSLToSLua::convert("default { touch_start(integer n) {\n" " llOwnerSay((string)llGetTime());\n" " llResetTime();\n" "} }\n", ALLSLToSLua::Options::closeToLSL()); - ensure("close to LSL, llcompat's together: " + close.text, - has(close, "llcompat.GetTime()") && has(close, "llcompat.ResetTime()") && !has(close, "ll.GetTime") && !has(close, "timeBase")); + { + INFO("close to LSL, llcompat's together: " + close.text); + CHECK(has(close, "llcompat.GetTime()")); + CHECK(has(close, "llcompat.ResetTime()")); + CHECK_FALSE(has(close, "ll.GetTime")); + CHECK_FALSE(has(close, "timeBase")); + } checksClean(close); } - template<> template<> - void allsltoslua_object::test<66>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: Base64 through llbase64 with decode noted once; ll's close to LSL", "[ALLSLToSLua]") { - set_test_name("Base64 through llbase64: encode as it is, the same bytes as LSL's; decode noted once, as it keeps a NUL and what is " - "not UTF-8; close to LSL, ll's, which only SLua's deprecating sets apart from llcompat's"); const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" " key k = llDetectedKey(0);\n" " string s = llStringToBase64(\"hello\");\n" @@ -2047,26 +2882,45 @@ namespace tut " string e = llBase64ToString(t + \"=\");\n" " llOwnerSay(s + t + d + e);\n" "} }\n"); - ensure("encode: " + r.text, has(r, "local s = llbase64.encode(\"hello\")") && has(r, "local t = llbase64.encode(tostring(k))")); - ensure("decode: " + r.text, has(r, "local d = llbase64.decode(s)") && has(r, "local e = llbase64.decode(t .. \"=\")")); - ensure("decode noted once: " + r.text, noted(r, "SluaBase64Decode") && count(r, "-- LSL: llbase64.decode keeps every byte") == 1); - ensure("nothing of llcompat's or ll's: " + r.text, !has(r, "llcompat.") && !has(r, "ll.StringToBase64") && !has(r, "ll.Base64ToString") && - !noted(r, "SluaDeprecatedFor", "StringToBase64") && - !noted(r, "SluaDeprecatedFor", "Base64ToString")); + { + INFO("encode: " + r.text); + CHECK(has(r, "local s = llbase64.encode(\"hello\")")); + CHECK(has(r, "local t = llbase64.encode(tostring(k))")); + } + { + INFO("decode: " + r.text); + CHECK(has(r, "local d = llbase64.decode(s)")); + CHECK(has(r, "local e = llbase64.decode(t .. \"=\")")); + } + { + INFO("decode noted once: " + r.text); + CHECK(noted(r, "SluaBase64Decode")); + CHECK(count(r, "-- LSL: llbase64.decode keeps every byte") == 1); + } + { + INFO("nothing of llcompat's or ll's: " + r.text); + CHECK_FALSE(has(r, "llcompat.")); + CHECK_FALSE(has(r, "ll.StringToBase64")); + CHECK_FALSE(has(r, "ll.Base64ToString")); + CHECK_FALSE(noted(r, "SluaDeprecatedFor", "StringToBase64")); + CHECK_FALSE(noted(r, "SluaDeprecatedFor", "Base64ToString")); + } checksClean(r); const ALLSLToSLua::Result close = ALLSLToSLua::convert("default { state_entry() { llOwnerSay(llBase64ToString(llStringToBase64(\"x\"))); } }\n", ALLSLToSLua::Options::closeToLSL()); - ensure("close to LSL: " + close.text, - has(close, "ll.Base64ToString(ll.StringToBase64(\"x\"))") && !has(close, "llcompat.") && !has(close, "llbase64.decode(") && - !has(close, "llbase64.encode(") && !noted(close, "SluaBase64Decode")); + { + INFO("close to LSL: " + close.text); + CHECK(has(close, "ll.Base64ToString(ll.StringToBase64(\"x\"))")); + CHECK_FALSE(has(close, "llcompat.")); + CHECK_FALSE(has(close, "llbase64.decode(")); + CHECK_FALSE(has(close, "llbase64.encode(")); + CHECK_FALSE(noted(close, "SluaBase64Decode")); + } } - template<> template<> - void allsltoslua_object::test<67>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: llAdjustDamage in on_damage as detected adjustDamage; llcompat elsewhere", "[ALLSLToSLua]") { - set_test_name("llAdjustDamage in on_damage as the detected table's adjustDamage, as the llDetected* are; llcompat's elsewhere, where " - "LSL's call only said an error and adjustDamage would stop the handler"); const ALLSLToSLua::Result r = convert("halve() { llAdjustDamage(0, 2.0); }\n" "default {\n" " on_damage(integer n) {\n" @@ -2077,22 +2931,29 @@ namespace tut " }\n" " touch_start(integer n) { llAdjustDamage(0, 1.0); }\n" "}\n"); - ensure("on_damage's own: " + r.text, has(r, "detected[1]:adjustDamage(0.5)") && has(r, "detected[i + 1]:adjustDamage(5)") && - noted(r, "SluaDetectedTable")); - ensure("a function's, llcompat's: " + r.text, has(r, "llcompat.AdjustDamage(0, 2.0)")); - ensure("another event's, llcompat's: " + r.text, has(r, "llcompat.AdjustDamage(0, 1.0)")); + { + INFO("on_damage's own: " + r.text); + CHECK(has(r, "detected[1]:adjustDamage(0.5)")); + CHECK(has(r, "detected[i + 1]:adjustDamage(5)")); + CHECK(noted(r, "SluaDetectedTable")); + } + UNSCOPED_INFO("a function's, llcompat's: " + r.text); + CHECK(has(r, "llcompat.AdjustDamage(0, 2.0)")); + UNSCOPED_INFO("another event's, llcompat's: " + r.text); + CHECK(has(r, "llcompat.AdjustDamage(0, 1.0)")); checksClean(r); const ALLSLToSLua::Result close = ALLSLToSLua::convert("default { on_damage(integer n) { llAdjustDamage(0, 0.5); } }\n", ALLSLToSLua::Options::closeToLSL()); - ensure("close to LSL, llcompat's: " + close.text, has(close, "llcompat.AdjustDamage(0, 0.5)") && !has(close, "adjustDamage(")); + { + INFO("close to LSL, llcompat's: " + close.text); + CHECK(has(close, "llcompat.AdjustDamage(0, 0.5)")); + CHECK_FALSE(has(close, "adjustDamage(")); + } } - template<> template<> - void allsltoslua_object::test<68>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a timer every handler turns off first is LLTimers:once; else :every", "[ALLSLToSLua]") { - set_test_name("a timer every state's handler of which turns it off first is a one-shot, on LLTimers:once, set again as often as " - "the script likes; on LLTimers:every where a state has no timer handler, or one that does anything first"); const ALLSLToSLua::Result r = convert("default {\n" " state_entry() { llSetTimerEvent(5.0); }\n" " timer() {\n" @@ -2102,19 +2963,31 @@ namespace tut " }\n" " touch_start(integer n) { llSetTimerEvent(1); }\n" "}\n"); - ensure("once: " + r.text, has(r, "timerHandle = LLTimers:once(seconds, function()") && !has(r, "LLTimers:every(seconds, function")); - ensure("said so: " + r.text, has(r, "-- time it is set, as its handler turns it off before anything else.")); - ensure("the handler's own off kept, and set again: " + r.text, - has(r, "function timerHandler()\n setTimer(0.0)\n") && has(r, "setTimer(2)") && has(r, "setTimer(1)")); + { + INFO("once: " + r.text); + CHECK(has(r, "timerHandle = LLTimers:once(seconds, function()")); + CHECK_FALSE(has(r, "LLTimers:every(seconds, function")); + } + UNSCOPED_INFO("said so: " + r.text); + CHECK(has(r, "-- time it is set, as its handler turns it off before anything else.")); + { + INFO("the handler's own off kept, and set again: " + r.text); + CHECK(has(r, "function timerHandler()\n setTimer(0.0)\n")); + CHECK(has(r, "setTimer(2)")); + CHECK(has(r, "setTimer(1)")); + } checksClean(r); // Tapple's: the local the preamble declared, assigned by a function // statement of its name, which types as it is declared. ALLSLToSLua::Options typed; typed.types = true; const ALLSLToSLua::Result types = ALLSLToSLua::convert("default { state_entry() { llSetTimerEvent(5); } timer() { llOwnerSay(\"t\"); } }\n", typed); - ensure("a function statement, typed: " + types.text, - has(types, "local timerHandler: (() -> ())? = nil\n") && has(types, "\nfunction timerHandler()\n print(\"t\")\nend\n") && - !has(types, "timerHandler = function")); + { + INFO("a function statement, typed: " + types.text); + CHECK(has(types, "local timerHandler: (() -> ())? = nil\n")); + CHECK(has(types, "\nfunction timerHandler()\n print(\"t\")\nend\n")); + CHECK_FALSE(has(types, "timerHandler = function")); + } checksClean(types); const ALLSLToSLua::Result states = convert("default {\n" @@ -2125,8 +2998,11 @@ namespace tut " timer() { llSetTimerEvent(FALSE); llOwnerSay(\"two\"); }\n" " touch_start(integer n) { llSetTimerEvent(3); }\n" "}\n"); - ensure("every state's, once: " + states.text, - has(states, "timerHandle = LLTimers:once(seconds, function") && has(states, "states[currentState].timer")); + { + INFO("every state's, once: " + states.text); + CHECK(has(states, "timerHandle = LLTimers:once(seconds, function")); + CHECK(has(states, "states[currentState].timer")); + } checksClean(states); const ALLSLToSLua::Result without = convert("default {\n" @@ -2136,15 +3012,21 @@ namespace tut "state two {\n" " touch_start(integer n) { state default; }\n" "}\n"); - ensure("a state with none, every: " + without.text, - has(without, "timerHandle = LLTimers:every(seconds, function") && !has(without, "LLTimers:once(seconds, function")); + { + INFO("a state with none, every: " + without.text); + CHECK(has(without, "timerHandle = LLTimers:every(seconds, function")); + CHECK_FALSE(has(without, "LLTimers:once(seconds, function")); + } const ALLSLToSLua::Result later = convert("default {\n" " state_entry() { llSetTimerEvent(1); }\n" " timer() { llOwnerSay(\"tick\"); llSetTimerEvent(0); }\n" "}\n"); - ensure("off after something else, every: " + later.text, - has(later, "timerHandle = LLTimers:every(seconds, function") && !has(later, "LLTimers:once(seconds, function")); + { + INFO("off after something else, every: " + later.text); + CHECK(has(later, "timerHandle = LLTimers:every(seconds, function")); + CHECK_FALSE(has(later, "LLTimers:once(seconds, function")); + } const ALLSLToSLua::Result again = convert("float gNext = 0.0;\n" "default {\n" @@ -2152,15 +3034,15 @@ namespace tut " timer() { llSetTimerEvent(gNext); }\n" " touch_start(integer n) { gNext = 2.0; }\n" "}\n"); - ensure("set to what is not written nought, every: " + again.text, - has(again, "timerHandle = LLTimers:every(seconds, function") && !has(again, "LLTimers:once(seconds, function")); + { + INFO("set to what is not written nought, every: " + again.text); + CHECK(has(again, "timerHandle = LLTimers:every(seconds, function")); + CHECK_FALSE(has(again, "LLTimers:once(seconds, function")); + } } - template<> template<> - void allsltoslua_object::test<75>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: integer / and % as LSL's via bit32.s32 and math.fmod; // and % if safe", "[ALLSLToSLua]") { - set_test_name("an integer's / and % as LSL's, toward nought and the remainder of the dividend's sign: bit32.s32 of the quotient and " - "math.fmod, unnoted; // and % where neither side is below nought; compound ones written whole; a float's / as it was"); const char* const script = "integer gSize = 16;\n" "default { touch_start(integer n) {\n" " integer a = n - 3; integer b = 1 - n; float f = 2.5;\n" @@ -2174,22 +3056,46 @@ namespace tut " llOwnerSay((string)(q + m + sum + back + cut + half + a + b) + (string)f);\n" "} }\n"; const ALLSLToSLua::Result r = convert(script); - ensure("toward nought: " + r.text, has(r, "local q = bit32.s32(a / b)") && has(r, "local m = math.fmod(a, b)")); - ensure("bracketed as / binds: " + r.text, has(r, "bit32.s32((a + 1) / (b - 2)) + math.fmod(a + 1, b - 2)")); - ensure("a side below nought: " + r.text, has(r, "bit32.s32(-7 / 2) + math.fmod(7, -2)")); - ensure("neither below nought: " + r.text, has(r, "gSize // 4 + gSize % 3 + bit32.band(n, 255) // 16 + bit32.band(n, 255) % gSize")); - ensure("one known, not the other: " + r.text, has(r, "local half = bit32.s32(a / gSize)")); - ensure("compound ones whole: " + r.text, has(r, "a = bit32.s32(a / b)") && has(r, "a = math.fmod(a, b)") && has(r, "b = bit32.s32(b / 2)") && - has(r, "b = math.fmod(b, 3)") && !has(r, "//=") && !has(r, "%=")); - ensure("a float's: " + r.text, has(r, "f /= 2")); - ensure("unnoted: " + r.text, !noted(r, "SluaIntegerDivision") && !noted(r, "SluaModulo")); + { + INFO("toward nought: " + r.text); + CHECK(has(r, "local q = bit32.s32(a / b)")); + CHECK(has(r, "local m = math.fmod(a, b)")); + } + UNSCOPED_INFO("bracketed as / binds: " + r.text); + CHECK(has(r, "bit32.s32((a + 1) / (b - 2)) + math.fmod(a + 1, b - 2)")); + UNSCOPED_INFO("a side below nought: " + r.text); + CHECK(has(r, "bit32.s32(-7 / 2) + math.fmod(7, -2)")); + UNSCOPED_INFO("neither below nought: " + r.text); + CHECK(has(r, "gSize // 4 + gSize % 3 + bit32.band(n, 255) // 16 + bit32.band(n, 255) % gSize")); + UNSCOPED_INFO("one known, not the other: " + r.text); + CHECK(has(r, "local half = bit32.s32(a / gSize)")); + { + INFO("compound ones whole: " + r.text); + CHECK(has(r, "a = bit32.s32(a / b)")); + CHECK(has(r, "a = math.fmod(a, b)")); + CHECK(has(r, "b = bit32.s32(b / 2)")); + CHECK(has(r, "b = math.fmod(b, 3)")); + CHECK_FALSE(has(r, "//=")); + CHECK_FALSE(has(r, "%=")); + } + UNSCOPED_INFO("a float's: " + r.text); + CHECK(has(r, "f /= 2")); + { + INFO("unnoted: " + r.text); + CHECK_FALSE(noted(r, "SluaIntegerDivision")); + CHECK_FALSE(noted(r, "SluaModulo")); + } checksClean(r); // Typed, each one a number to the checker, strict under the grid's // solver: math.fmod's one answer and bit32.s32's. ALLSLToSLua::Options options; options.types = true; const ALLSLToSLua::Result typed = ALLSLToSLua::convert(script, options); - ensure("typed: " + typed.text, has(typed, "local q: number = bit32.s32(a / b)") && has(typed, "local m: number = math.fmod(a, b)")); + { + INFO("typed: " + typed.text); + CHECK(has(typed, "local q: number = bit32.s32(a / b)")); + CHECK(has(typed, "local m: number = math.fmod(a, b)")); + } checksClean(typed); if (!newSolver) { @@ -2198,370 +3104,86 @@ namespace tut { said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); } - ensure("strict:\n" + said + "---\n" + typed.text, said.empty()); + UNSCOPED_INFO("strict:\n" + said + "---\n" + typed.text); + REQUIRE(said.empty()); } } - - template<> template<> - void allsltoslua_object::test<76>() - { - set_test_name("a key cast from no text said to be NULL_KEY, and one with capitals said to be written small, cast or given; a key " - "given no text kept text; other text as it was said; the two XorBase64Strings said to be what ll.XorBase64 is not, " - "LSL's kept"); - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " key a = (key)\"\"; key c = (key)\"A2E76FCD-9360-4F6D-A924-938F923DF11D\";\n" - " if ((key)\"\" == NULL_KEY) llOwnerSay(\"same\");\n" - " string s = llGetObjectDesc(); key d = (key)s;\n" - " llOwnerSay((string)a + (string)c + (string)d + llXorBase64(\"a\", \"b\") + llXorBase64Strings(\"a\", \"b\") +\n" - " llXorBase64StringsCorrect(\"a\", \"b\"));\n" - "} }\n"); - ensure("no text, NULL_KEY: " + r.text, has(r, "local a = uuid(\"\")") && noted(r, "SluaKeyEmpty") && - has(r, "-- LSL: SLua's uuid of no text is NULL_KEY")); - ensure("capitals, small: " + r.text, has(r, "uuid(\"A2E76FCD-9360-4F6D-A924-938F923DF11D\")") && noted(r, "SluaKeyCase")); - ensure("other text as it was: " + r.text, has(r, "local d = uuid(s)") && noted(r, "SluaUuid")); - ensure("ll.XorBase64 as it is: " + r.text, has(r, "ll.XorBase64(\"a\", \"b\")")); - ensure("the two, LSL's: " + r.text, - has(r, "ll.XorBase64Strings(\"a\", \"b\")") && has(r, "ll.XorBase64StringsCorrect(\"a\", \"b\")") && !has(r, "llcompat.")); - ensure("said what ll.XorBase64 is not: " + r.text, noted(r, "SluaXorBase64Wrong") && noted(r, "SluaXorBase64Nul") && - !noted(r, "SluaDeprecated", "XorBase64Strings") && - !noted(r, "SluaDeprecated", "XorBase64StringsCorrect")); - for (const ALScriptProblem& p : r.notes) - { - if (p.key == "SluaXorBase64Wrong" || p.key == "SluaXorBase64Nul") - { - ensure("named: " + p.message, p.message.find("for ll.XorBase64,") != std::string::npos); - } - } - checksClean(r); - - // Given rather than cast: no text kept as text, as other text that is - // no UUID is; capitals the same said, and a UUID written small - // nothing at all. - const ALLSLToSLua::Result given = convert("default { touch_start(integer n) {\n" - " key b = \"\"; key c = \"A2E76FCD-9360-4F6D-A924-938F923DF11D\";\n" - " key l = \"a2e76fcd-9360-4f6d-a924-938f923df11d\";\n" - " llOwnerSay((string)llGetOwnerKey(b) + (string)llGetOwnerKey(c) + (string)llGetOwnerKey(l));\n" - "} }\n"); - ensure("given no text, text: " + given.text, has(given, "local b = \"\"") && has(given, "ll.GetOwnerKey(b :: any)") && - !noted(given, "SluaKeyEmpty") && noted(given, "SluaKeyCase")); - ensure("nothing else said of them: " + given.text, !noted(given, "SluaKeyText") && !noted(given, "SluaUuidText")); - checksClean(given); - } - - template<> template<> - void allsltoslua_object::test<90>() - { - set_test_name("the //* toggle with a word after its star: ---[[ and the word over the code, --]] under it, which SLua reads as " - "two line comments, and a dash taken out of the opener as the block comment the closer closes"); - ALLSLToSLua::Options options; - options.types = true; - const ALLSLToSLua::Result r = ALLSLToSLua::convert("//* OVERRIDE\n" - "integer allowAttachedAO;\n" - "//*/\n" - "default { state_entry() { if (allowAttachedAO) llOwnerSay(\"on\"); } }\n", - options); - ensure("converted", r.converted); - ensure("the toggle: " + r.text, has(r, "\n---[[ OVERRIDE\nlocal allowAttachedAO: boolean = false\n--]]\n")); - ensure_equals("the opener a line comment", commentAt(r.text, "---[["), std::string("---[[ OVERRIDE")); - ensure_equals("the closer one", commentAt(r.text, "--]]"), std::string("--]]")); - ensure_equals("turned off", toggled(r.text, "---[["), std::string("--[[ OVERRIDE\nlocal allowAttachedAO: boolean = false\n--]]")); - checksClean(r); - } - - template<> template<> - void allsltoslua_object::test<91>() - { - set_test_name("what is half of a toggle: an opener only where a closer follows it, and no rule of stars nor a comment closed on " - "its line once a / is out; openers since the last closer share the next; a closer's own comment after it SLua's"); - const ALLSLToSLua::Result lone = convert("integer a;\n" - "//* nothing closes this\n" - "integer b;\n" - "default { state_entry() { llOwnerSay((string)(a + b)); } }\n"); - ensure("a lone opener a comment with a star: " + lone.text, has(lone, "\n--* nothing closes this\nlocal b = 0\n") && !has(lone, "---[")); - checksClean(lone); - - const ALLSLToSLua::Result r = convert("//* not one */\n" - "//**************\n" - "integer c = 1;\n" - "//* one\n" - "integer d = 2;\n" - "//* two\n" - "integer e = 3;\n" - "//*/ // both\n" - "//*/\n" - "default { state_entry() { llOwnerSay((string)(c + d + e)); } }\n"); - ensure("closed on its line: " + r.text, has(r, "\n--* not one */\n")); - ensure("a rule of stars: " + r.text, has(r, "\n--**************\n")); - ensure("two openers and a closer, then one alone: " + r.text, - has(r, "\n---[[ one\nlocal d = 2\n---[[ two\nlocal e = 3\n--]] -- both\n--]]\n")); - ensure_equals("the first off", toggled(r.text, "---[[ one"), std::string("--[[ one\nlocal d = 2\n---[[ two\nlocal e = 3\n--]]")); - ensure_equals("the second off", toggled(r.text, "---[[ two"), std::string("--[[ two\nlocal e = 3\n--]]")); - std::string off = r.text; - off.erase(off.find("---[[ one"), 1); - ensure_equals("the closer's own comment after it, off", commentAt(off, "-- both"), std::string("-- both")); - checksClean(r); - } - - template<> template<> - void allsltoslua_object::test<92>() - { - set_test_name("the toggle the other way, /* over the code and //*/ under it: --[[ and --]], which a dash put in the opener makes two " - "line comments again; and an opener over code before one, which shares its closer"); - const ALLSLToSLua::Result r = convert("/* OVERRIDE\n" - "integer allowAttachedAO;\n" - "//*/\n" - "//* A\n" - "integer x = 1;\n" - "/* B\n" - "integer y = 2;\n" - "//*/\n" - "default { state_entry() { llOwnerSay((string)x); } }\n"); - ensure_equals("one block comment", commentAt(r.text, "--[[ OVERRIDE"), std::string("--[[ OVERRIDE\ninteger allowAttachedAO;\n--]]")); - // What it holds stays LSL: nothing says it is code. - std::string on = r.text; - on.insert(on.find("--[[ OVERRIDE"), "-"); - ensure_equals("turned on, the opener a line comment", commentAt(on, "---[[ OVERRIDE"), std::string("---[[ OVERRIDE")); - ensure_equals("and the closer", commentAt(on, "--]]"), std::string("--]]")); - ensure_equals("the opener over code off, to the closer of the one after it", toggled(r.text, "---[[ A"), - std::string("--[[ A\nlocal x = 1\n\n--[[ B\ninteger y = 2;\n--]]")); - checksClean(r); - } - - template<> template<> - void allsltoslua_object::test<93>() - { - set_test_name("a toggle's level, the fewest =s nothing between its first opener and its closer closes once it is off: past a ]] " - "in a string or a comment in it, the opener and closer of one pair at one; nought where the script leaves no byte " - "to mark it with"); - const ALLSLToSLua::Result r = convert("//* OVERRIDE\n" - "string s = \"]]\";\n" - "//*/\n" - "default { state_entry() { llOwnerSay(s); } }\n"); - ensure("past a string's ]]: " + r.text, has(r, "\n---[=[ OVERRIDE\nlocal s = \"]]\"\n--]=]\n")); - ensure_equals("off", toggled(r.text, "---[=["), std::string("--[=[ OVERRIDE\nlocal s = \"]]\"\n--]=]")); - checksClean(r); - - const ALLSLToSLua::Result shared = convert("//* A\n" - "integer x = 1; /* one */\n" - "/* B\n" - "string s = \"]]\";\n" - "//*/\n" - "default { state_entry() { llOwnerSay((string)x); } }\n"); - ensure("one level for the pair: " + shared.text, has(shared, "\n---[=[ A\nlocal x = 1 --[[ one ]]\n") && - has(shared, "\n--[=[ B\nstring s = \"]]\";\n--]=]\n")); - ensure_equals("the opener off", toggled(shared.text, "---[=[ A"), - std::string("--[=[ A\nlocal x = 1 --[[ one ]]\n\n--[=[ B\nstring s = \"]]\";\n--]=]")); - checksClean(shared); - - std::string every = "// "; - for (char c = 1; c < ' '; ++c) - { - if (c != '\t' && c != '\n' && c != '\r') - { - every += c; - } - } - const ALLSLToSLua::Result unmarked = convert(every + "\ninteger y;\n//* A\ninteger x = 1;\n//*/\n" - "default { state_entry() { llOwnerSay((string)(x + y)); } }\n"); - ensure("paired still: " + unmarked.text, has(unmarked, "\n---[[ A\nlocal x = 1\n--]]\n")); - ensure_equals("no byte of its own left in", count(unmarked, std::string(1, '\x01')), size_t(1)); - checksClean(unmarked); - } - - template<> template<> - void allsltoslua_object::test<94>() - { - set_test_name("where a toggle sits: after code on its line, which stays when it is off, the closer after code it turns off; in a " - "handler; and from the script's first comments, kept off the helpers written over the code"); - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " integer a = 1; //* A\n" - " llOwnerSay((string)a); //*/\n" - " //* DEBUG\n" - " llOwnerSay(\"debug\");\n" - " //*/\n" - " llOwnerSay(\"done\");\n" - "} }\n"); - ensure("after code: " + r.text, has(r, "\nlocal a = 1 ---[[ A\nprint(tostring(a)) --]]\n")); - ensure_equals("off from it", toggled(r.text, "---[[ A"), std::string("--[[ A\nprint(tostring(a)) --]]")); - ensure("in a handler: " + r.text, has(r, "\n---[[ DEBUG\nprint(\"debug\")\n--]]\nprint(\"done\")\n")); - ensure_equals("off", toggled(r.text, "---[[ DEBUG"), std::string("--[[ DEBUG\nprint(\"debug\")\n--]]")); - checksClean(r); - - const ALLSLToSLua::Result top = convert("//* DEBUG\n" - "\n" - "vector v = <1, 2, 3>;\n" - "//*/\n" - "default { state_entry() { llOwnerSay((string)llVecNorm(v)); } }\n"); - ensure("the helper over it: " + top.text, has(top, "local function vecNorm(") && - top.text.find("local function vecNorm(") < top.text.find("---[[ DEBUG")); - ensure_equals("off", toggled(top.text, "---[[ DEBUG"), std::string("--[[ DEBUG\nlocal v = vector(1, 2, 3)\n--]]")); - checksClean(top); - } - - template<> template<> - void allsltoslua_object::test<85>() - { - set_test_name("a call the definitions deprecate, naming what to use where SLua's own word names nothing, is noted with it: " - "llMakeFire's ll.ParticleSystem, llSoundPreload's ll.PreloadSound; llXorBase64Strings' ll.XorBase64 in a note of " - "its own, which says how the two differ"); - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " llMakeFire(10, 1.0, 1.0, 1.0, PI, \"\", ZERO_VECTOR);\n" - " llSoundPreload(\"boom\");\n" - " llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 1.0]);\n" - " llOwnerSay(llXorBase64Strings(\"YQ==\", \"Yg==\"));\n" - " llTakeCamera(llGetOwner());\n" - "} }\n"); - const auto named = [&r](const std::string& bare, const std::string& use) { - return std::any_of(r.notes.begin(), r.notes.end(), [&](const ALScriptProblem& p) { - return p.key == "SluaDeprecatedFor" && p.args.size() == 2 && p.args[0] == bare && p.args[1] == use; - }); - }; - ensure("each, for what it names: " + r.text, - named("MakeFire", "ll.ParticleSystem") && named("SoundPreload", "ll.PreloadSound") && - named("SetPrimitiveParams", "ll.SetLinkPrimitiveParamsFast") && named("TakeCamera", "ll.SetCameraParams")); - ensure("XorBase64Strings' own: " + r.text, noted(r, "SluaXorBase64Wrong") && !named("XorBase64Strings", "ll.XorBase64")); - ensure("said so: " + r.text, has(r, "-- LSL: SLua deprecates ll.MakeFire, for ll.ParticleSystem.\n")); - ensure("none said bare: " + r.text, !noted(r, "SluaDeprecated")); - checksClean(r); - } - - template<> template<> - void allsltoslua_object::test<55>() - { - set_test_name("lists appended with table.extend, which runs what it is given once and answers the list: a list by its name, " - "itself, a call's, many values; + in an expression on a copy of the left, but for a new one, said to hold any " - "value, and another +'s answer; no joinLists"); - const ALLSLToSLua::Result r = convert("list gKept = [1];\n" - "list kept() { return gKept; }\n" - "show(list l) { llOwnerSay(llDumpList2String(l, \",\")); }\n" - "default { touch_start(integer n) {\n" - " list all = llParseString2List(\"a,b\", [\",\"], []);\n" - " list more = [4, 5];\n" - " all += more;\n" - " all += all;\n" - " all += llList2List(more, 0, n);\n" - " all += [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, " - "26, 27, 28, 29, 30, 31, 32, \"x\"];\n" - " show(all);\n" - " show(kept() + more + all);\n" - " show(more + n);\n" - " show(n + more);\n" - " show([PRIM_NAME, \"x\"] + more);\n" - " show(llParseString2List(\"c\", [], []) + [1] + [<1, 2, 3>]);\n" - " llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_COLOR, ALL_SIDES, <1, 1, 1>, 1.0] + more);\n" - "} }\n"); - ensure("a list by its name: " + r.text, has(r, "table.extend(all, more)")); - ensure("itself: " + r.text, has(r, "table.extend(all, all)")); - ensure("a call's, run once: " + r.text, has(r, "table.extend(all, llcompat.List2List(more, 0, n))") && !has(r, "for _, item in")); - ensure("many values: " + r.text, has(r, "table.extend(all, { 0, 1, 2,") && has(r, "32, \"x\" })")); - ensure("one copy, of the variable a call answers: " + r.text, - has(r, "show(table.extend(table.extend(table.clone(kept()), more), all))")); - ensure("a variable copied: " + r.text, has(r, "show(table.extend(table.clone(more), { n }))")); - ensure("a value: " + r.text, has(r, "show(table.extend({ n } :: { any }, more))")); - ensure("written out: " + r.text, has(r, "show(table.extend({ PRIM_NAME, \"x\" } :: { any }, more))") && - has(r, "table.extend({ PRIM_COLOR, ALL_SIDES, vector(1, 1, 1), 1.0 } :: { any }, more)")); - ensure("a library call's: " + r.text, - has(r, "show(table.extend(table.extend(ll.ParseString2List(\"c\", {}, {}) :: { any }, { 1 }), { vector(1, 2, 3) }))")); - ensure("no helper, nor a move: " + r.text, !has(r, "joinLists") && !has(r, "table.move(")); - checksClean(r); - } - - template<> template<> - void allsltoslua_object::test<56>() - { - set_test_name("list forms through SLua's own where they do what LSL's did: an item replaced at a counter within the list, in " - "place, its loop Luau's numeric for, the length kept; one put on the end; a part between two places written out; " - "text and whole numbers joined; one thing's place found; llcompat's where not"); - const ALLSLToSLua::Result r = convert("list gNames = [\"a\", \"b\"];\n" - "list gKept;\n" - "list gL = [1, 2, 3];\n" - "integer bump() { gL += [0]; return 1; }\n" - "default { touch_start(integer n) {\n" - " list l = [1, 2, 3];\n" - " integer i;\n" - " for (i = 0; i < llGetListLength(l); ++i)\n" - " l = llListReplaceList(l, [llList2Integer(l, i) * 2], i, i);\n" - " list held = [1, 2];\n" - " gKept = held;\n" - " integer j;\n" - " for (j = 0; j < llGetListLength(held); ++j)\n" - " held = llListReplaceList(held, [j], j, j);\n" - " integer k;\n" - " for (k = 0; k < llGetListLength(gL); ++k)\n" - " gL = llListReplaceList(gL, [bump()], k, k);\n" - " l = llListReplaceList(l, [7], -1, -1);\n" - " l = llListInsertList(l, [9], llGetListLength(l));\n" - " list part = llList2List(l, 1, 2);\n" - " list all = llList2List(l, 0, -1);\n" - " list rest = llList2List(l, 2, -1);\n" - " list wrapped = llList2List(l, 3, 1);\n" - " llOwnerSay(llDumpList2String(l, \",\"));\n" - " llOwnerSay(llDumpList2String(gNames + [n], \", \"));\n" - " llOwnerSay(llDumpList2String(l + [1.5], \",\"));\n" - " llOwnerSay((string)l);\n" - " integer at = llListFindList(gNames, [\"b\"]);\n" - " integer none = llListFindList(gNames, [NULL_KEY]);\n" - " if (llListFindList(gNames, [NULL_KEY]) != -1) llOwnerSay(\"null\");\n" - " integer parsed = llListFindList(llParseString2List(\"a,b\", [\",\"], []), [\"b\"]);\n" - " llOwnerSay((string)(at + none + parsed) + llDumpList2String(part + all + rest + wrapped + held, \"\"));\n" - "} }\n"); - ensure("replaced in place, in Luau's numeric for: " + r.text, has(r, "for i = 0, #l - 1 do\n l[i + 1] = l[i + 1] * 2\n")); - ensure("a list held elsewhere, replaced as LSL's: " + r.text, - has(r, "for j = 0, #held - 1 do\n") && has(r, " held = llcompat.ListReplaceList(held, { j }, j, j)\n")); - ensure("a global a call of the script's could change: " + r.text, has(r, "while k < #gL do") && - has(r, "gL = llcompat.ListReplaceList(gL, { bump() }, k, k)")); - ensure("the last of a list that could be empty: " + r.text, has(r, "l = llcompat.ListReplaceList(l, { 7 }, -1, -1)")); - ensure("on the end: " + r.text, has(r, "table.insert(l, 9)")); - ensure("a part, and all of it: " + r.text, has(r, "local part = table.move(l, 2, 3, 1, {})") && has(r, "local all = table.clone(l)")); - ensure("to the end, and the part left out, as LSL's: " + r.text, - has(r, "local rest = llcompat.List2List(l, 2, -1)") && has(r, "local wrapped = llcompat.List2List(l, 3, 1)")); - ensure("whole numbers joined: " + r.text, has(r, "print(table.concat(l, \",\"))") && has(r, "print(table.concat(l))")); - ensure("and text: " + r.text, has(r, "print(table.concat(table.extend(table.clone(gNames), { n }), \", \"))")); - ensure("a float as LSL writes one: " + r.text, has(r, "print(ll.DumpList2String(table.extend(table.clone(l), { 1.5 }), \",\"))")); - ensure("found, its place: " + r.text, has(r, "local at = (table.find(gNames, \"b\") or 0) - 1")); - ensure("NULL_KEY as LSL's string, as its place and found: " + r.text, - has(r, "local none = (table.find(gNames, tostring(NULL_KEY)) or 0) - 1") && - has(r, "if table.find(gNames, tostring(NULL_KEY)) ~= nil then")); - ensure("in a library call's list: " + r.text, has(r, "local parsed = (table.find(ll.ParseString2List(\"a,b\", { \",\" }, {}) :: { any }, \"b\") or 0) - 1")); - ensure("no llcompat but those: " + r.text, count(r, "llcompat.") == 5); - checksClean(r); - } - - template<> template<> - void allsltoslua_object::test<57>() - { - set_test_name("llSubStringIndex asked only whether it found: string.find's plain search, each way LSL asks it, a key as its " - "text, the empty text found; its index still ll's"); - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " string s = llGetObjectName(); key k = llGetOwner();\n" - " if (llSubStringIndex(s, \"a\") != -1) llOwnerSay(\"a\");\n" - " if (llSubStringIndex(s, \"b\") >= 0) llOwnerSay(\"b\");\n" - " if (llSubStringIndex(s, \"c\") > -1) llOwnerSay(\"c\");\n" - " if (llSubStringIndex(s, \"d\") == -1) llOwnerSay(\"no d\");\n" - " if (!~llSubStringIndex(s, \"e\")) llOwnerSay(\"no e\");\n" - " if (~llSubStringIndex(s, \".\")) llOwnerSay(\"dot\");\n" - " if (~llSubStringIndex(s, k)) llOwnerSay(\"owner\");\n" - " if (~llSubStringIndex(s, \"\")) llOwnerSay(\"empty\");\n" - " integer at = llSubStringIndex(s, \"x\");\n" - " llOwnerSay((string)at);\n" + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: keys from empty or capital text noted; the XorBase64Strings pair noted", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" + " key a = (key)\"\"; key c = (key)\"A2E76FCD-9360-4F6D-A924-938F923DF11D\";\n" + " if ((key)\"\" == NULL_KEY) llOwnerSay(\"same\");\n" + " string s = llGetObjectDesc(); key d = (key)s;\n" + " llOwnerSay((string)a + (string)c + (string)d + llXorBase64(\"a\", \"b\") + llXorBase64Strings(\"a\", \"b\") +\n" + " llXorBase64StringsCorrect(\"a\", \"b\"));\n" "} }\n"); - ensure("found: " + r.text, has(r, "if string.find(s, \"a\", 1, true) ~= nil then") && has(r, "if string.find(s, \"b\", 1, true) ~= nil then") && - has(r, "if string.find(s, \"c\", 1, true) ~= nil then")); - ensure("not: " + r.text, has(r, "if string.find(s, \"d\", 1, true) == nil then") && has(r, "if not (string.find(s, \"e\", 1, true) ~= nil) then")); - ensure("plain, not a pattern: " + r.text, has(r, "if string.find(s, \".\", 1, true) ~= nil then")); - ensure("a key as its text: " + r.text, has(r, "if string.find(s, tostring(k), 1, true) ~= nil then")); - ensure("the empty text: " + r.text, has(r, "if string.find(s, \"\", 1, true) ~= nil then")); - ensure("its index in characters, ll's: " + r.text, has(r, "local at = (ll.SubStringIndex(s, \"x\") or 0) - 1")); - ensure("found by string.find alone: " + r.text, count(r, "SubStringIndex") == 1); + { + INFO("no text, NULL_KEY: " + r.text); + CHECK(has(r, "local a = uuid(\"\")")); + CHECK(noted(r, "SluaKeyEmpty")); + CHECK(has(r, "-- LSL: SLua's uuid of no text is NULL_KEY")); + } + { + INFO("capitals, small: " + r.text); + CHECK(has(r, "uuid(\"A2E76FCD-9360-4F6D-A924-938F923DF11D\")")); + CHECK(noted(r, "SluaKeyCase")); + } + { + INFO("other text as it was: " + r.text); + CHECK(has(r, "local d = uuid(s)")); + CHECK(noted(r, "SluaUuid")); + } + UNSCOPED_INFO("ll.XorBase64 as it is: " + r.text); + CHECK(has(r, "ll.XorBase64(\"a\", \"b\")")); + { + INFO("the two, LSL's: " + r.text); + CHECK(has(r, "ll.XorBase64Strings(\"a\", \"b\")")); + CHECK(has(r, "ll.XorBase64StringsCorrect(\"a\", \"b\")")); + CHECK_FALSE(has(r, "llcompat.")); + } + { + INFO("said what ll.XorBase64 is not: " + r.text); + CHECK(noted(r, "SluaXorBase64Wrong")); + CHECK(noted(r, "SluaXorBase64Nul")); + CHECK_FALSE(noted(r, "SluaDeprecated", "XorBase64Strings")); + CHECK_FALSE(noted(r, "SluaDeprecated", "XorBase64StringsCorrect")); + } + for (const ALScriptProblem& p : r.notes) + { + if (p.key == "SluaXorBase64Wrong" || p.key == "SluaXorBase64Nul") + { + UNSCOPED_INFO("named: " + p.message); + REQUIRE(p.message.find("for ll.XorBase64,") != std::string::npos); + } + } checksClean(r); + + // Given rather than cast: no text kept as text, as other text that is + // no UUID is; capitals the same said, and a UUID written small + // nothing at all. + const ALLSLToSLua::Result given = convert("default { touch_start(integer n) {\n" + " key b = \"\"; key c = \"A2E76FCD-9360-4F6D-A924-938F923DF11D\";\n" + " key l = \"a2e76fcd-9360-4f6d-a924-938f923df11d\";\n" + " llOwnerSay((string)llGetOwnerKey(b) + (string)llGetOwnerKey(c) + (string)llGetOwnerKey(l));\n" + "} }\n"); + { + INFO("given no text, text: " + given.text); + CHECK(has(given, "local b = \"\"")); + CHECK(has(given, "ll.GetOwnerKey(b :: any)")); + CHECK_FALSE(noted(given, "SluaKeyEmpty")); + CHECK(noted(given, "SluaKeyCase")); + } + { + INFO("nothing else said of them: " + given.text); + CHECK_FALSE(noted(given, "SluaKeyText")); + CHECK_FALSE(noted(given, "SluaUuidText")); + } + checksClean(given); } - template<> template<> - void allsltoslua_object::test<77>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a particle rule list written out as a ParticleParams table", "[ALLSLToSLua]") { - set_test_name("a particle rule list written out as the ParticleParams table ll.ParticleSystem takes: each rule by the key the " - "definitions give it, its value as the list had it, the flags' masks each true, a key to a line where the list had " - "more than one"); const ALLSLToSLua::Result r = convert("integer gFlags = 3;\n" "default { touch_start(integer n) {\n" " llParticleSystem([\n" @@ -2574,7 +3196,8 @@ namespace tut " llLinkParticleSystem(2, [PSYS_PART_FLAGS, PSYS_PART_BOUNCE_MASK]);\n" " llParticleSystem([]);\n" "} }\n"); - ensure("a key to a line: " + r.text, has(r, " ll.ParticleSystem({\n" + UNSCOPED_INFO("a key to a line: " + r.text); + CHECK(has(r, " ll.ParticleSystem({\n" " emissive = true,\n" " color_interp = true,\n" " color_begin = vector(1, 0, 0),\n" @@ -2583,22 +3206,27 @@ namespace tut " texture = tostring(NULL_KEY),\n" " target_key = ll.GetOwner(),\n" " })\n")); - ensure("on one line as written; flags not of masks as they are: " + r.text, - has(r, "ll.LinkParticleSystem(LINK_SET, { flags = gFlags, burst_rate = 0.1 })") && has(r, "ll.LinkParticleSystem(2, { bounce = true })")); - ensure("no rules, as they were: " + r.text, has(r, "ll.ParticleSystem({})")); + { + INFO("on one line as written; flags not of masks as they are: " + r.text); + CHECK(has(r, "ll.LinkParticleSystem(LINK_SET, { flags = gFlags, burst_rate = 0.1 })")); + CHECK(has(r, "ll.LinkParticleSystem(2, { bounce = true })")); + } + UNSCOPED_INFO("no rules, as they were: " + r.text); + CHECK(has(r, "ll.ParticleSystem({})")); const auto same = [&r](const std::string& lsl, const std::string& slua) { return std::find(r.same.begin(), r.same.end(), std::make_pair(lsl, slua)) != r.same.end(); }; - ensure("each constant the same word as its key", same("PSYS_PART_START_COLOR", "color_begin") && same("PSYS_PART_EMISSIVE_MASK", "emissive") && - same("PSYS_SRC_BURST_RATE", "burst_rate")); + { + INFO("each constant the same word as its key"); + CHECK(same("PSYS_PART_START_COLOR", "color_begin")); + CHECK(same("PSYS_PART_EMISSIVE_MASK", "emissive")); + CHECK(same("PSYS_SRC_BURST_RATE", "burst_rate")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<78>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a media rule list as MediaParams with booleans and whitelist pieces", "[ALLSLToSLua]") { - set_test_name("a media rule list as MediaParams: TRUE and FALSE, 1 and 0, as booleans, a whitelist's text as its pieces where " - "the serializer joins them back the same; a list where it would not, or a truth is no 1 or 0 written out"); const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" " integer on = llGetLinkNumber();\n" " llSetPrimMediaParams(0, [PRIM_MEDIA_AUTO_PLAY, TRUE, PRIM_MEDIA_AUTO_LOOP, 0, PRIM_MEDIA_CURRENT_URL, \"http://a.com\",\n" @@ -2610,7 +3238,8 @@ namespace tut " llSetPrimMediaParams(4, [PRIM_MEDIA_AUTO_PLAY, on]);\n" " llSetPrimMediaParams(5, [PRIM_MEDIA_AUTO_ZOOM, 2]);\n" "} }\n"); - ensure("a table: " + r.text, has(r, " ll.SetPrimMediaParams(0, {\n" + UNSCOPED_INFO("a table: " + r.text); + CHECK(has(r, " ll.SetPrimMediaParams(0, {\n" " auto_play = true,\n" " auto_loop = false,\n" " current_url = \"http://a.com\",\n" @@ -2618,30 +3247,34 @@ namespace tut " whitelist_enable = true,\n" " whitelist = { \"a.com\", \"*.b.com\", \"\" },\n" " })\n")); - ensure("what it answers read: " + r.text, has(r, "if ll.SetLinkMedia(LINK_THIS, 1, { home_url = \"http://h\" }) == STATUS_OK then")); + UNSCOPED_INFO("what it answers read: " + r.text); + CHECK(has(r, "if ll.SetLinkMedia(LINK_THIS, 1, { home_url = \"http://h\" }) == STATUS_OK then")); // The join leaves a first piece of none out, escapes a \, and sends // no rule at all for no text. - ensure("a whitelist the join would change, a list: " + r.text, - has(r, "ll.SetPrimMediaParams(1, { PRIM_MEDIA_WHITELIST, \",a.com\" })") && has(r, "ll.SetPrimMediaParams(2, { PRIM_MEDIA_WHITELIST, \"a") && - has(r, "ll.SetPrimMediaParams(3, { PRIM_MEDIA_WHITELIST, \"\" })")); - ensure("a truth not written out as one, a list: " + r.text, - has(r, "ll.SetPrimMediaParams(4, { PRIM_MEDIA_AUTO_PLAY, on })") && has(r, "ll.SetPrimMediaParams(5, { PRIM_MEDIA_AUTO_ZOOM, 2 })")); + { + INFO("a whitelist the join would change, a list: " + r.text); + CHECK(has(r, "ll.SetPrimMediaParams(1, { PRIM_MEDIA_WHITELIST, \",a.com\" })")); + CHECK(has(r, "ll.SetPrimMediaParams(2, { PRIM_MEDIA_WHITELIST, \"a")); + CHECK(has(r, "ll.SetPrimMediaParams(3, { PRIM_MEDIA_WHITELIST, \"\" })")); + } + { + INFO("a truth not written out as one, a list: " + r.text); + CHECK(has(r, "ll.SetPrimMediaParams(4, { PRIM_MEDIA_AUTO_PLAY, on })")); + CHECK(has(r, "ll.SetPrimMediaParams(5, { PRIM_MEDIA_AUTO_ZOOM, 2 })")); + } checksClean(r); } - template<> template<> - void allsltoslua_object::test<79>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: an HTTP rule list as HttpRequestParams with headers by name", "[ALLSLToSLua]") { - set_test_name("an HTTP rule list as HttpRequestParams: headers of names each once by name, said where the serializer's order " - "by name is not the order written; the accepts in order; a list for a name twice, a name not written out, or a " - "value moved past one that changes something"); const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" " string token = llGetObjectDesc();\n" " llHTTPRequest(\"https://x\", [HTTP_METHOD, \"POST\", HTTP_MIMETYPE, \"application/json\", HTTP_VERIFY_CERT, FALSE,\n" " HTTP_CUSTOM_HEADER, \"X-B\", token, HTTP_BODY_MAXLENGTH, 16384, HTTP_CUSTOM_HEADER, \"X-A\", \"two\",\n" " HTTP_ACCEPT, \"application/json\", HTTP_ACCEPT, \"text/plain\"], \"{}\");\n" "} }\n"); - ensure("a table: " + r.text, has(r, " ll.HTTPRequest(\"https://x\", {\n" + UNSCOPED_INFO("a table: " + r.text); + CHECK(has(r, " ll.HTTPRequest(\"https://x\", {\n" " method = \"POST\",\n" " mimetype = \"application/json\",\n" " verify_cert = false,\n" @@ -2649,16 +3282,22 @@ namespace tut " max_body_length = 16384,\n" " accept = { \"application/json\", \"text/plain\" },\n" " }, \"{}\")\n")); - ensure("the headers' order said: " + r.text, noted(r, "SluaHeaderOrder") && has(r, "-- LSL: SLua passes ll.HTTPRequest a custom_header table's headers sorted by name")); + { + INFO("the headers' order said: " + r.text); + CHECK(noted(r, "SluaHeaderOrder")); + CHECK(has(r, "-- LSL: SLua passes ll.HTTPRequest a custom_header table's headers sorted by name")); + } checksClean(r); const ALLSLToSLua::Result sorted = convert("default { touch_start(integer n) {\n" " key id = llHTTPRequest(\"https://y\", [HTTP_CUSTOM_HEADER, \"A\", \"1\", HTTP_CUSTOM_HEADER, \"B\", \"2\"], \"\");\n" " llOwnerSay((string)id);\n" "} }\n"); - ensure("in order by name, nothing said: " + sorted.text, - has(sorted, "local id = ll.HTTPRequest(\"https://y\", { custom_header = { [\"A\"] = \"1\", [\"B\"] = \"2\" } }, \"\")") && - !noted(sorted, "SluaHeaderOrder")); + { + INFO("in order by name, nothing said: " + sorted.text); + CHECK(has(sorted, "local id = ll.HTTPRequest(\"https://y\", { custom_header = { [\"A\"] = \"1\", [\"B\"] = \"2\" } }, \"\")")); + CHECK_FALSE(noted(sorted, "SluaHeaderOrder")); + } checksClean(sorted); const ALLSLToSLua::Result lists = convert("integer gCount;\n" @@ -2670,19 +3309,19 @@ namespace tut " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_METHOD, \"GET\", HTTP_CUSTOM_HEADER, \"B\", counted()], \"\");\n" " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_CUSTOM_HEADER, \"B\", counted()], \"\");\n" "} }\n"); - ensure("a name twice, a list: " + lists.text, has(lists, "ll.HTTPRequest(\"https://z\", { HTTP_CUSTOM_HEADER, \"A\", \"1\", HTTP_CUSTOM_HEADER, \"A\", \"2\" }, \"\")")); - ensure("a name not written out, a list: " + lists.text, has(lists, "ll.HTTPRequest(\"https://z\", { HTTP_CUSTOM_HEADER, token, \"1\" }, \"\")")); - ensure("moved past another, a list: " + lists.text, has(lists, "{ HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_METHOD, \"GET\", HTTP_CUSTOM_HEADER, \"B\", counted() }")); - ensure("one after another, run in their order: " + lists.text, has(lists, "{ custom_header = { [\"A\"] = counted(), [\"B\"] = counted() } }")); + UNSCOPED_INFO("a name twice, a list: " + lists.text); + CHECK(has(lists, "ll.HTTPRequest(\"https://z\", { HTTP_CUSTOM_HEADER, \"A\", \"1\", HTTP_CUSTOM_HEADER, \"A\", \"2\" }, \"\")")); + UNSCOPED_INFO("a name not written out, a list: " + lists.text); + CHECK(has(lists, "ll.HTTPRequest(\"https://z\", { HTTP_CUSTOM_HEADER, token, \"1\" }, \"\")")); + UNSCOPED_INFO("moved past another, a list: " + lists.text); + CHECK(has(lists, "{ HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_METHOD, \"GET\", HTTP_CUSTOM_HEADER, \"B\", counted() }")); + UNSCOPED_INFO("one after another, run in their order: " + lists.text); + CHECK(has(lists, "{ custom_header = { [\"A\"] = counted(), [\"B\"] = counted() } }")); checksClean(lists); } - template<> template<> - void allsltoslua_object::test<80>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a rule list the table cannot say just so stays a list", "[ALLSLToSLua]") { - set_test_name("a rule list the table could not say just so stays a list: not written out, a key twice, a rule of another table " - "or a number, a value of another shape, a rule with no value; flags that are not an | of masks each once, as they " - "are; none at all without SLua's idioms"); const char* const script = "list gRules = [PSYS_PART_MAX_AGE, 2.0];\n" "default { touch_start(integer n) {\n" " llParticleSystem(gRules);\n" @@ -2696,26 +3335,37 @@ namespace tut " llLinkParticleSystem(3, [PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | (PSYS_PART_BOUNCE_MASK | PSYS_PART_RIBBON_MASK)]);\n" "} }\n"; const ALLSLToSLua::Result r = convert(script); - ensure("not written out: " + r.text, has(r, "ll.ParticleSystem(gRules)")); - ensure("a key twice: " + r.text, has(r, "ll.ParticleSystem({ PSYS_PART_MAX_AGE, 2.0, PSYS_PART_MAX_AGE, 3.0 })")); - ensure("another table's rule: " + r.text, has(r, "ll.ParticleSystem({ PRIM_MEDIA_AUTO_PLAY, 1 })")); - ensure("a number for a rule: " + r.text, has(r, "ll.ParticleSystem({ 7, 2.0 })")); - ensure("a value of another shape: " + r.text, has(r, "ll.ParticleSystem({ PSYS_PART_START_ALPHA, vector(1, 0, 0) })")); - ensure("no value: " + r.text, has(r, "ll.ParticleSystem({ PSYS_PART_MAX_AGE })")); - ensure("flags as they are: " + r.text, has(r, "ll.LinkParticleSystem(1, { flags = PSYS_PART_WIND_MASK + PSYS_PART_BOUNCE_MASK })") && - has(r, "ll.LinkParticleSystem(2, { flags = bit32.bor(PSYS_PART_WIND_MASK, PSYS_PART_WIND_MASK) })")); - ensure("masks bracketed, each true: " + r.text, has(r, "ll.LinkParticleSystem(3, { wind = true, bounce = true, ribbon = true })")); + UNSCOPED_INFO("not written out: " + r.text); + CHECK(has(r, "ll.ParticleSystem(gRules)")); + UNSCOPED_INFO("a key twice: " + r.text); + CHECK(has(r, "ll.ParticleSystem({ PSYS_PART_MAX_AGE, 2.0, PSYS_PART_MAX_AGE, 3.0 })")); + UNSCOPED_INFO("another table's rule: " + r.text); + CHECK(has(r, "ll.ParticleSystem({ PRIM_MEDIA_AUTO_PLAY, 1 })")); + UNSCOPED_INFO("a number for a rule: " + r.text); + CHECK(has(r, "ll.ParticleSystem({ 7, 2.0 })")); + UNSCOPED_INFO("a value of another shape: " + r.text); + CHECK(has(r, "ll.ParticleSystem({ PSYS_PART_START_ALPHA, vector(1, 0, 0) })")); + UNSCOPED_INFO("no value: " + r.text); + CHECK(has(r, "ll.ParticleSystem({ PSYS_PART_MAX_AGE })")); + { + INFO("flags as they are: " + r.text); + CHECK(has(r, "ll.LinkParticleSystem(1, { flags = PSYS_PART_WIND_MASK + PSYS_PART_BOUNCE_MASK })")); + CHECK(has(r, "ll.LinkParticleSystem(2, { flags = bit32.bor(PSYS_PART_WIND_MASK, PSYS_PART_WIND_MASK) })")); + } + UNSCOPED_INFO("masks bracketed, each true: " + r.text); + CHECK(has(r, "ll.LinkParticleSystem(3, { wind = true, bounce = true, ribbon = true })")); ALLSLToSLua::Options plain; plain.idioms = false; const ALLSLToSLua::Result lists = ALLSLToSLua::convert(script, plain); - ensure("no idioms, no tables: " + lists.text, has(lists, "ll.LinkParticleSystem(3, { PSYS_PART_FLAGS, ") && !has(lists, "wind = true")); + { + INFO("no idioms, no tables: " + lists.text); + CHECK(has(lists, "ll.LinkParticleSystem(3, { PSYS_PART_FLAGS, ")); + CHECK_FALSE(has(lists, "wind = true")); + } } - template<> template<> - void allsltoslua_object::test<81>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: every rule of the three tables matches the definitions' types", "[ALLSLToSLua]") { - set_test_name("every rule of the three tables at once: each key one the definitions' type has, every key the type has written, " - "and strict under the grid's solver"); const ALLSLToSLua::Result r = convert( "default { touch_start(integer n) {\n" " key k = llGetOwner();\n" @@ -2767,7 +3417,8 @@ namespace tut { const std::string text = defs.str(); const size_t begin = text.find(std::string("export type ") + type + " = {"); - ensure(std::string("the definitions have ") + type, begin != std::string::npos); + UNSCOPED_INFO(std::string("the definitions have ") + type); + REQUIRE(begin != std::string::npos); const std::string block = text.substr(begin, text.find("\n}", begin) - begin); std::set fields; std::istringstream lines(block); @@ -2791,8 +3442,10 @@ namespace tut { unknown += fields.contains(key) ? "" : " " + key; } - ensure(std::string(type) + " has every key written:" + unknown + "\n" + r.text, unknown.empty()); - ensure(std::string(type) + " written with every key it has:" + missing + "\n" + r.text, missing.empty()); + UNSCOPED_INFO(std::string(type) + " has every key written:" + unknown + "\n" + r.text); + REQUIRE(unknown.empty()); + UNSCOPED_INFO(std::string(type) + " written with every key it has:" + missing + "\n" + r.text); + REQUIRE(missing.empty()); } checksClean(r); if (!newSolver) @@ -2802,19 +3455,16 @@ namespace tut { said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); } - ensure("strict:\n" + said + "---\n" + r.text, said.empty()); + UNSCOPED_INFO("strict:\n" + said + "---\n" + r.text); + REQUIRE(said.empty()); } } } -namespace tut +namespace { - template<> template<> - void allsltoslua_object::test<82>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a list written over several lines keeps its lines and comments", "[ALLSLToSLua]") { - set_test_name("a list the LSL wrote over several lines is written over the same lines, nested ones a level further in, each " - "comment in it where it was; a comment after it on the line it ends trails that line; a list on one line, or in " - "a line made inside an expression, stays on one"); const ALLSLToSLua::Result r = convert("list gColours = [\n" " // warm\n" " \"red\", \"orange\",\n" @@ -2849,12 +3499,12 @@ namespace tut " PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP\n" " ]);\n" "} }\n"); - ensure("a global's, its comments where they were: " + r.text, - has(r, "local gColours = {\n -- warm\n \"red\", \"orange\",\n --[[ cool ]] \"blue\" -- last\n}\n")); - ensure("closed on the line it closed on, a comment after it trailing that: " + r.text, - has(r, "local gTwo = { 1, 2,\n 3 } -- three\n")); - ensure("Tapple's, as the LSL had it: " + r.text, - has(r, " local activeRequest = ll.List2Json(JSON_OBJECT, {\n" + UNSCOPED_INFO("a global's, its comments where they were: " + r.text); + CHECK(has(r, "local gColours = {\n -- warm\n \"red\", \"orange\",\n --[[ cool ]] \"blue\" -- last\n}\n")); + UNSCOPED_INFO("closed on the line it closed on, a comment after it trailing that: " + r.text); + CHECK(has(r, "local gTwo = { 1, 2,\n 3 } -- three\n")); + UNSCOPED_INFO("Tapple's, as the LSL had it: " + r.text); + CHECK(has(r, " local activeRequest = ll.List2Json(JSON_OBJECT, {\n" " \"fn\", \"SELECT\",\n" " \"radius\", 10,\n" " \"ima\", ll.List2Json(JSON_ARRAY, { \"Ungulate\" }),\n" @@ -2867,18 +3517,23 @@ namespace tut " }),\n" " \"responders\", \"[]\"\n" " }) -- sent\n")); - ensure("each comment once: " + r.text, count(r, "only the owner") == 1 && count(r, "-- sent") == 1 && count(r, "-- first") == 1); - ensure("in a line made inside an expression, one line: " + r.text, has(r, "(function() m = { 3, 4 } return m end)()")); - ensure("in a condition: " + r.text, has(r, " if table.find({ \"a\", -- first\n \"b\" }")); - ensure("one written another way, its comment over it: " + r.text, has(r, " -- none\n ll.ParticleSystem({\n")); + { + INFO("each comment once: " + r.text); + CHECK(count(r, "only the owner") == 1); + CHECK(count(r, "-- sent") == 1); + CHECK(count(r, "-- first") == 1); + } + UNSCOPED_INFO("in a line made inside an expression, one line: " + r.text); + CHECK(has(r, "(function() m = { 3, 4 } return m end)()")); + UNSCOPED_INFO("in a condition: " + r.text); + CHECK(has(r, " if table.find({ \"a\", -- first\n \"b\" }")); + UNSCOPED_INFO("one written another way, its comment over it: " + r.text); + CHECK(has(r, " -- none\n ll.ParticleSystem({\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<83>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a detected event's count only declared where the body names it", "[ALLSLToSLua]") { - set_test_name("a detected event's count made of the detected table only where the body names it: none that nothing reads, which " - "a lint would ask to take out"); const ALLSLToSLua::Result r = convert("selectHorse() {}\n" "default {\n" " touch_start (integer num) {\n" @@ -2889,25 +3544,28 @@ namespace tut " collision_start(integer n) { llOwnerSay((string)n); }\n" " sensor(integer found) { found = 0; }\n" "}\n"); - ensure("none that nothing reads: " + r.text, - has(r, "LLEvents:on(\"touch_start\", function(detected)\n -- LSL: detected[n]") && !has(r, "local num")); - ensure("one read: " + r.text, has(r, "function(detected)\n local n = #detected\n print(tostring(n))\n")); - ensure("one set, which needs it there: " + r.text, has(r, " local found = #detected\n found = 0\n")); + { + INFO("none that nothing reads: " + r.text); + CHECK(has(r, "LLEvents:on(\"touch_start\", function(detected)\n -- LSL: detected[n]")); + CHECK_FALSE(has(r, "local num")); + } + UNSCOPED_INFO("one read: " + r.text); + CHECK(has(r, "function(detected)\n local n = #detected\n print(tostring(n))\n")); + UNSCOPED_INFO("one set, which needs it there: " + r.text); + CHECK(has(r, " local found = #detected\n found = 0\n")); // Unused, of what Luau says: only what the LSL itself never read. std::string unused; for (const ALScriptProblem& p : service.check(r.text)) { unused += p.code == "LocalUnused" ? p.message + "\n" : std::string(); } - ensure_equals("unused", unused, std::string("Variable 'found' is never used; prefix with '_' to silence\n")); + UNSCOPED_INFO("unused"); + CHECK(unused == std::string("Variable 'found' is never used; prefix with '_' to silence\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<84>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: text where ll takes a key passed as it is with :: any and noted once", "[ALLSLToSLua]") { - set_test_name("text given where ll or llcompat takes a key is given as it is, as LSL's function took it, `:: any` for the type " - "checker, said once; a UUID written out still a uuid; the script's own function's key still uuid()"); const std::string lsl = "string gJson;\n" "integer mine(key who) { return who == llGetOwner(); }\n" "default { touch_start(integer n) {\n" @@ -2917,12 +3575,19 @@ namespace tut " if (mine(llJsonGetValue(gJson, [\"owner\"]))) llOwnerSay(\"mine\");\n" "} }\n"; const ALLSLToSLua::Result r = convert(lsl); - ensure("Tapple's, as it is: " + r.text, has(r, "ll.Key2Name(ll.JsonGetValue(gJson, { \"selected\" }) :: any)")); - ensure("bracketed where it must be: " + r.text, has(r, "ll.Key2Name((\"a2e76fcd-9360-4f6d-a924-\" .. \"938f923df11d\") :: any)")); - ensure("a UUID written out, a uuid: " + r.text, has(r, "ll.Key2Name(uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"))")); - ensure("the script's own function's key, uuid(): " + r.text, has(r, "mine(uuid(ll.JsonGetValue(gJson, { \"owner\" })))") && - noted(r, "SluaUuidText")); - ensure("said once: " + r.text, count(r, "-- LSL: SLua's ll takes a key as text") == 1); + UNSCOPED_INFO("Tapple's, as it is: " + r.text); + CHECK(has(r, "ll.Key2Name(ll.JsonGetValue(gJson, { \"selected\" }) :: any)")); + UNSCOPED_INFO("bracketed where it must be: " + r.text); + CHECK(has(r, "ll.Key2Name((\"a2e76fcd-9360-4f6d-a924-\" .. \"938f923df11d\") :: any)")); + UNSCOPED_INFO("a UUID written out, a uuid: " + r.text); + CHECK(has(r, "ll.Key2Name(uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"))")); + { + INFO("the script's own function's key, uuid(): " + r.text); + CHECK(has(r, "mine(uuid(ll.JsonGetValue(gJson, { \"owner\" })))")); + CHECK(noted(r, "SluaUuidText")); + } + UNSCOPED_INFO("said once: " + r.text); + CHECK(count(r, "-- LSL: SLua's ll takes a key as text") == 1); checksClean(r); if (!newSolver) { @@ -2936,19 +3601,57 @@ namespace tut { said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); } - ensure("strict:\n" + said + "---\n" + calls.text, said.empty() && calls.text.find(":: any") != std::string::npos); + { + INFO("strict:\n" + said + "---\n" + calls.text); + REQUIRE(said.empty()); + REQUIRE(calls.text.find(":: any") != std::string::npos); + } } const ALLSLToSLua::Result close = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); - ensure("llcompat's the same: " + close.text, close.converted && close.text.find(":: any)") != std::string::npos); + { + INFO("llcompat's the same: " + close.text); + CHECK(close.converted); + REQUIRE(close.text.find(":: any)") != std::string::npos); + } checksClean(close); } - template<> template<> - void allsltoslua_object::test<86>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: deprecated calls noted with what to use; XorBase64Strings its own note", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result r = convert("default { state_entry() {\n" + " llMakeFire(10, 1.0, 1.0, 1.0, PI, \"\", ZERO_VECTOR);\n" + " llSoundPreload(\"boom\");\n" + " llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 1.0]);\n" + " llOwnerSay(llXorBase64Strings(\"YQ==\", \"Yg==\"));\n" + " llTakeCamera(llGetOwner());\n" + "} }\n"); + const auto named = [&r](const std::string& bare, const std::string& use) { + return std::any_of(r.notes.begin(), r.notes.end(), [&](const ALScriptProblem& p) { + return p.key == "SluaDeprecatedFor" && p.args.size() == 2 && p.args[0] == bare && p.args[1] == use; + }); + }; + { + INFO("each, for what it names: " + r.text); + CHECK(named("MakeFire", "ll.ParticleSystem")); + CHECK(named("SoundPreload", "ll.PreloadSound")); + CHECK(named("SetPrimitiveParams", "ll.SetLinkPrimitiveParamsFast")); + CHECK(named("TakeCamera", "ll.SetCameraParams")); + } + { + INFO("XorBase64Strings' own: " + r.text); + CHECK(noted(r, "SluaXorBase64Wrong")); + CHECK_FALSE(named("XorBase64Strings", "ll.XorBase64")); + } + UNSCOPED_INFO("said so: " + r.text); + CHECK(has(r, "-- LSL: SLua deprecates ll.MakeFire, for ll.ParticleSystem.\n")); + UNSCOPED_INFO("none said bare: " + r.text); + CHECK_FALSE(noted(r, "SluaDeprecated")); + checksClean(r); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a find's place against a literal compared in SLua's count from 1", "[ALLSLToSLua]") { - set_test_name("a find's place compared with a whole number written out is compared in SLua's count, from 1 or nil, on either side: " - "x == 1 where LSL said == 0; a place below -1 left as written, and one read as a number kept LSL's"); const std::string lsl = "string P = \"cfg\";\n" "list L;\n" "check(string item) {\n" @@ -2965,15 +3668,24 @@ namespace tut "}\n" "default { touch_start(integer n) { check(\"x\"); } }\n"; const ALLSLToSLua::Result r = convert(lsl); - ensure("Tapple's: " + r.text, has(r, "if P ~= \"\" and ll.SubStringIndex(item, P) == 1 then")); - ensure("the number on the left: " + r.text, has(r, "if ll.SubStringIndex(item, P) == 1 then print(\"b\")")); - ensure("~=: " + r.text, has(r, "if ll.SubStringIndex(item, P) ~= 3 then")); - ensure("an order, nil read as LSL's -1: " + r.text, has(r, "if (ll.SubStringIndex(item, P) or 0) > 1 then")); - ensure("an order the other way round: " + r.text, has(r, "if (ll.SubStringIndex(item, P) or 0) > 4 then")); - ensure("below -1, as written: " + r.text, has(r, "if (ll.SubStringIndex(item, P) or 0) - 1 == -2 then")); - ensure("under a not: " + r.text, has(r, "if ll.SubStringIndex(item, P) ~= 1 then print(\"g\")")); - ensure("table.find's: " + r.text, has(r, "if table.find(L, item) == 1 then")); - ensure("a number, LSL's: " + r.text, has(r, "local at = (ll.SubStringIndex(item, P) or 0) - 1")); + UNSCOPED_INFO("Tapple's: " + r.text); + CHECK(has(r, "if P ~= \"\" and ll.SubStringIndex(item, P) == 1 then")); + UNSCOPED_INFO("the number on the left: " + r.text); + CHECK(has(r, "if ll.SubStringIndex(item, P) == 1 then print(\"b\")")); + UNSCOPED_INFO("~=: " + r.text); + CHECK(has(r, "if ll.SubStringIndex(item, P) ~= 3 then")); + UNSCOPED_INFO("an order, nil read as LSL's -1: " + r.text); + CHECK(has(r, "if (ll.SubStringIndex(item, P) or 0) > 1 then")); + UNSCOPED_INFO("an order the other way round: " + r.text); + CHECK(has(r, "if (ll.SubStringIndex(item, P) or 0) > 4 then")); + UNSCOPED_INFO("below -1, as written: " + r.text); + CHECK(has(r, "if (ll.SubStringIndex(item, P) or 0) - 1 == -2 then")); + UNSCOPED_INFO("under a not: " + r.text); + CHECK(has(r, "if ll.SubStringIndex(item, P) ~= 1 then print(\"g\")")); + UNSCOPED_INFO("table.find's: " + r.text); + CHECK(has(r, "if table.find(L, item) == 1 then")); + UNSCOPED_INFO("a number, LSL's: " + r.text); + CHECK(has(r, "local at = (ll.SubStringIndex(item, P) or 0) - 1")); checksClean(r); // table.find's alone, without SLua's ll: whether it found, against nil. @@ -2986,20 +3698,26 @@ namespace tut " if (llListFindList(L, [n]) == 2) llOwnerSay(\"third\");\n" "} }\n", idioms); - ensure("found, against nil: " + found.text, found.converted && has(found, "if table.find(L, n) == nil then") && - has(found, "if table.find(L, n) ~= nil then") && has(found, "if table.find(L, n) == 3 then")); + { + INFO("found, against nil: " + found.text); + CHECK(found.converted); + CHECK(has(found, "if table.find(L, n) == nil then")); + CHECK(has(found, "if table.find(L, n) ~= nil then")); + CHECK(has(found, "if table.find(L, n) == 3 then")); + } checksClean(found); const ALLSLToSLua::Result close = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); - ensure("llcompat's, LSL's count: " + close.text, close.converted && has(close, "llcompat.SubStringIndex(item, P) == 0")); + { + INFO("llcompat's, LSL's count: " + close.text); + CHECK(close.converted); + CHECK(has(close, "llcompat.SubStringIndex(item, P) == 0")); + } checksClean(close); } - template<> template<> - void allsltoslua_object::test<87>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: blank lines between statements kept singly and not at a block's ends", "[ALLSLToSLua]") { - set_test_name("a blank line between two statements or globals is kept, one for a run of them; none at a block's start or end, " - "nor a second where a comment over the statement keeps its own"); const ALLSLToSLua::Result r = convert("integer a = 1;\n" "integer b = 2;\n" "\n" @@ -3025,20 +3743,23 @@ namespace tut " llOwnerSay((string)c);\n" " }\n" "}\n"); - ensure("the globals: " + r.text, has(r, "local a = 1\nlocal b = 2\n\nlocal c = 3\n")); - ensure("Tapple's: " + r.text, has(r, " -- only allow my owner to request contents\n if avatar ~= ll.GetOwner() then return false end\n\n return true\nend")); - ensure("none at a block's start: " + r.text, has(r, "(avatar)\n -- only allow")); - ensure("in a block, none at its end: " + r.text, has(r, " print(tostring(a))\n\n print(tostring(b))\n end\n")); - ensure("one over a comment kept apart: " + r.text, has(r, " end\n\n -- said apart\n print(tostring(c))\n")); - ensure("never two: " + r.text, !has(r, "\n\n\n")); + UNSCOPED_INFO("the globals: " + r.text); + CHECK(has(r, "local a = 1\nlocal b = 2\n\nlocal c = 3\n")); + UNSCOPED_INFO("Tapple's: " + r.text); + CHECK(has(r, " -- only allow my owner to request contents\n if avatar ~= ll.GetOwner() then return false end\n\n return true\nend")); + UNSCOPED_INFO("none at a block's start: " + r.text); + CHECK(has(r, "(avatar)\n -- only allow")); + UNSCOPED_INFO("in a block, none at its end: " + r.text); + CHECK(has(r, " print(tostring(a))\n\n print(tostring(b))\n end\n")); + UNSCOPED_INFO("one over a comment kept apart: " + r.text); + CHECK(has(r, " end\n\n -- said apart\n print(tostring(c))\n")); + UNSCOPED_INFO("never two: " + r.text); + CHECK_FALSE(has(r, "\n\n\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<88>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: table braces spaced as asked as the formatter spaces them", "[ALLSLToSLua]") { - set_test_name("tables and table types spaced inside their braces as asked, as the formatter spaces them: StyLua's by default, " - "Tapple's line; none where asked; an interpolated string's braces and {} never"); const std::string lsl = "list gTwo = [1, 2,\n 3];\n" "default {\n" " state_entry() { state two; }\n" @@ -3053,19 +3774,32 @@ namespace tut ALLSLToSLua::Options typed; typed.types = true; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, typed); - ensure("Tapple's: " + r.text, r.converted && has(r, "local params: { any } = ll.ParseStringKeepNulls(msg, { \"\\n\" }, {})")); - ensure("a rule table: " + r.text, has(r, "ll.SetPrimMediaParams(0, { auto_play = true, whitelist = { \"a.com\", \"b.com\" } })")); - ensure("the states' type: " + r.text, has(r, "local states: { [string]: { [string]: (...any) -> () } } = {}")); - ensure("over lines: " + r.text, has(r, "local gTwo: { any } = { 1, 2,\n 3 }\n")); - ensure("an interpolated string's: " + r.text, has(r, "`n={#params} {ll.DumpList2String({ vector(1, 2, 3) }, \"\")}`")); + { + INFO("Tapple's: " + r.text); + CHECK(r.converted); + CHECK(has(r, "local params: { any } = ll.ParseStringKeepNulls(msg, { \"\\n\" }, {})")); + } + UNSCOPED_INFO("a rule table: " + r.text); + CHECK(has(r, "ll.SetPrimMediaParams(0, { auto_play = true, whitelist = { \"a.com\", \"b.com\" } })")); + UNSCOPED_INFO("the states' type: " + r.text); + CHECK(has(r, "local states: { [string]: { [string]: (...any) -> () } } = {}")); + UNSCOPED_INFO("over lines: " + r.text); + CHECK(has(r, "local gTwo: { any } = { 1, 2,\n 3 }\n")); + UNSCOPED_INFO("an interpolated string's: " + r.text); + CHECK(has(r, "`n={#params} {ll.DumpList2String({ vector(1, 2, 3) }, \"\")}`")); checksClean(r); typed.braceSpaces = false; const ALLSLToSLua::Result none = ALLSLToSLua::convert(lsl, typed); - ensure("none: " + none.text, none.converted && has(none, "local params: {any} = ll.ParseStringKeepNulls(msg, {\"\\n\"}, {})") && - has(none, "ll.SetPrimMediaParams(0, {auto_play = true, whitelist = {\"a.com\", \"b.com\"}})") && - has(none, "local states: {[string]: {[string]: (...any) -> ()}} = {}") && - has(none, "local gTwo: {any} = {1, 2,\n 3}\n") && !has(none, "{ ")); + { + INFO("none: " + none.text); + CHECK(none.converted); + CHECK(has(none, "local params: {any} = ll.ParseStringKeepNulls(msg, {\"\\n\"}, {})")); + CHECK(has(none, "ll.SetPrimMediaParams(0, {auto_play = true, whitelist = {\"a.com\", \"b.com\"}})")); + CHECK(has(none, "local states: {[string]: {[string]: (...any) -> ()}} = {}")); + CHECK(has(none, "local gTwo: {any} = {1, 2,\n 3}\n")); + CHECK_FALSE(has(none, "{ ")); + } checksClean(none); // As the formatter would leave them, either way. @@ -3075,15 +3809,13 @@ namespace tut formatter.lua = true; formatter.braceSpaces = spaces; const ALLSLToSLua::Result same = spaces ? r : none; - ensure_equals(spaces ? "formatted, spaced" : "formatted, none", ALScriptFormatter::format(same.text, formatter), same.text); + UNSCOPED_INFO((spaces ? "formatted, spaced" : "formatted, none")); + REQUIRE(ALScriptFormatter::format(same.text, formatter) == same.text); } } - template<> template<> - void allsltoslua_object::test<89>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: converted SLua is as format on save leaves it with either brace spacing", "[ALLSLToSLua]") { - set_test_name("converted SLua is as format on save leaves it: a comment after an unbraced body trails it, after the line that " - "opens it where the body is empty; what the LSL lined up, lined up; one line break at the end; either brace spacing"); const std::string lsl = "integer CHANNEL = 1; // channel\n" "integer OWNER_ONLY = 0; // owner\n" "float TIMEOUT = 2.5; // seconds\n" @@ -3117,27 +3849,207 @@ namespace tut ALLSLToSLua::Options options; options.braceSpaces = spaces; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, options); - ensure("converted", r.converted); - ensure("each after its own: " + r.text, has(r, " print(\"a\") -- after a\n elseif") && has(r, " print(\"b\") -- after b\n else") && - has(r, " print(\"c\") -- after c\n end")); - ensure("an empty body's, after the line that opens it: " + r.text, has(r, " == 2 then -- an empty body\n else\n print(\"f\") -- after f\n")); - ensure("a loop's body's: " + r.text, has(r, " n -= 1 -- after the step\n end")); - ensure("between a block and its else, over what the else runs: " + r.text, has(r, " else\n -- over the else\n print(\"e\")\n")); - ensure("lined up: " + r.text, has(r, "local CHANNEL = 1 -- channel\nlocal OWNER_ONLY = 0 -- owner\nlocal TIMEOUT = 2.5 -- seconds\n")); - ensure("one line break at the end: " + r.text, r.text.size() > 2 && r.text.compare(r.text.size() - 2, 2, "\n\n") != 0 && r.text.back() == '\n'); + UNSCOPED_INFO("converted"); + REQUIRE(r.converted); + { + INFO("each after its own: " + r.text); + REQUIRE(has(r, " print(\"a\") -- after a\n elseif")); + REQUIRE(has(r, " print(\"b\") -- after b\n else")); + REQUIRE(has(r, " print(\"c\") -- after c\n end")); + } + UNSCOPED_INFO("an empty body's, after the line that opens it: " + r.text); + REQUIRE(has(r, " == 2 then -- an empty body\n else\n print(\"f\") -- after f\n")); + UNSCOPED_INFO("a loop's body's: " + r.text); + REQUIRE(has(r, " n -= 1 -- after the step\n end")); + UNSCOPED_INFO("between a block and its else, over what the else runs: " + r.text); + REQUIRE(has(r, " else\n -- over the else\n print(\"e\")\n")); + UNSCOPED_INFO("lined up: " + r.text); + REQUIRE(has(r, "local CHANNEL = 1 -- channel\nlocal OWNER_ONLY = 0 -- owner\nlocal TIMEOUT = 2.5 -- seconds\n")); + { + INFO("one line break at the end: " + r.text); + REQUIRE(r.text.size() > 2); + REQUIRE(r.text.compare(r.text.size() - 2, 2, "\n\n") != 0); + REQUIRE(r.text.back() == '\n'); + } ALScriptFormatter::Options formatter; formatter.lua = true; formatter.braceSpaces = spaces; - ensure_equals(spaces ? "formatted, spaced" : "formatted, none", ALScriptFormatter::format(r.text, formatter), r.text); + UNSCOPED_INFO((spaces ? "formatted, spaced" : "formatted, none")); + REQUIRE(ALScriptFormatter::format(r.text, formatter) == r.text); checksClean(r); } } - template<> template<> - void allsltoslua_object::test<95>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: the //* toggle with a word after its star as two SLua line comments", "[ALLSLToSLua]") + { + ALLSLToSLua::Options options; + options.types = true; + const ALLSLToSLua::Result r = ALLSLToSLua::convert("//* OVERRIDE\n" + "integer allowAttachedAO;\n" + "//*/\n" + "default { state_entry() { if (allowAttachedAO) llOwnerSay(\"on\"); } }\n", + options); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + UNSCOPED_INFO("the toggle: " + r.text); + CHECK(has(r, "\n---[[ OVERRIDE\nlocal allowAttachedAO: boolean = false\n--]]\n")); + UNSCOPED_INFO("the opener a line comment"); + CHECK(commentAt(r.text, "---[[") == std::string("---[[ OVERRIDE")); + UNSCOPED_INFO("the closer one"); + CHECK(commentAt(r.text, "--]]") == std::string("--]]")); + UNSCOPED_INFO("turned off"); + CHECK(toggled(r.text, "---[[") == std::string("--[[ OVERRIDE\nlocal allowAttachedAO: boolean = false\n--]]")); + checksClean(r); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: half a toggle: an opener only before a closer and openers share it", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result lone = convert("integer a;\n" + "//* nothing closes this\n" + "integer b;\n" + "default { state_entry() { llOwnerSay((string)(a + b)); } }\n"); + { + INFO("a lone opener a comment with a star: " + lone.text); + CHECK(has(lone, "\n--* nothing closes this\nlocal b = 0\n")); + CHECK_FALSE(has(lone, "---[")); + } + checksClean(lone); + + const ALLSLToSLua::Result r = convert("//* not one */\n" + "//**************\n" + "integer c = 1;\n" + "//* one\n" + "integer d = 2;\n" + "//* two\n" + "integer e = 3;\n" + "//*/ // both\n" + "//*/\n" + "default { state_entry() { llOwnerSay((string)(c + d + e)); } }\n"); + UNSCOPED_INFO("closed on its line: " + r.text); + CHECK(has(r, "\n--* not one */\n")); + UNSCOPED_INFO("a rule of stars: " + r.text); + CHECK(has(r, "\n--**************\n")); + UNSCOPED_INFO("two openers and a closer, then one alone: " + r.text); + CHECK(has(r, "\n---[[ one\nlocal d = 2\n---[[ two\nlocal e = 3\n--]] -- both\n--]]\n")); + UNSCOPED_INFO("the first off"); + CHECK(toggled(r.text, "---[[ one") == std::string("--[[ one\nlocal d = 2\n---[[ two\nlocal e = 3\n--]]")); + UNSCOPED_INFO("the second off"); + CHECK(toggled(r.text, "---[[ two") == std::string("--[[ two\nlocal e = 3\n--]]")); + std::string off = r.text; + off.erase(off.find("---[[ one"), 1); + UNSCOPED_INFO("the closer's own comment after it, off"); + CHECK(commentAt(off, "-- both") == std::string("-- both")); + checksClean(r); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: the toggle the other way round as a block comment SLua can turn on", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result r = convert("/* OVERRIDE\n" + "integer allowAttachedAO;\n" + "//*/\n" + "//* A\n" + "integer x = 1;\n" + "/* B\n" + "integer y = 2;\n" + "//*/\n" + "default { state_entry() { llOwnerSay((string)x); } }\n"); + UNSCOPED_INFO("one block comment"); + CHECK(commentAt(r.text, "--[[ OVERRIDE") == std::string("--[[ OVERRIDE\ninteger allowAttachedAO;\n--]]")); + // What it holds stays LSL: nothing says it is code. + std::string on = r.text; + on.insert(on.find("--[[ OVERRIDE"), "-"); + UNSCOPED_INFO("turned on, the opener a line comment"); + CHECK(commentAt(on, "---[[ OVERRIDE") == std::string("---[[ OVERRIDE")); + UNSCOPED_INFO("and the closer"); + CHECK(commentAt(on, "--]]") == std::string("--]]")); + UNSCOPED_INFO("the opener over code off, to the closer of the one after it"); + CHECK(toggled(r.text, "---[[ A") == + std::string("--[[ A\nlocal x = 1\n\n--[[ B\ninteger y = 2;\n--]]")); + checksClean(r); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a toggle's level is the fewest =s that nothing inside closes", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result r = convert("//* OVERRIDE\n" + "string s = \"]]\";\n" + "//*/\n" + "default { state_entry() { llOwnerSay(s); } }\n"); + UNSCOPED_INFO("past a string's ]]: " + r.text); + CHECK(has(r, "\n---[=[ OVERRIDE\nlocal s = \"]]\"\n--]=]\n")); + UNSCOPED_INFO("off"); + CHECK(toggled(r.text, "---[=[") == std::string("--[=[ OVERRIDE\nlocal s = \"]]\"\n--]=]")); + checksClean(r); + + const ALLSLToSLua::Result shared = convert("//* A\n" + "integer x = 1; /* one */\n" + "/* B\n" + "string s = \"]]\";\n" + "//*/\n" + "default { state_entry() { llOwnerSay((string)x); } }\n"); + { + INFO("one level for the pair: " + shared.text); + CHECK(has(shared, "\n---[=[ A\nlocal x = 1 --[[ one ]]\n")); + CHECK(has(shared, "\n--[=[ B\nstring s = \"]]\";\n--]=]\n")); + } + UNSCOPED_INFO("the opener off"); + CHECK(toggled(shared.text, "---[=[ A") == + std::string("--[=[ A\nlocal x = 1 --[[ one ]]\n\n--[=[ B\nstring s = \"]]\";\n--]=]")); + checksClean(shared); + + std::string every = "// "; + for (char c = 1; c < ' '; ++c) + { + if (c != '\t' && c != '\n' && c != '\r') + { + every += c; + } + } + const ALLSLToSLua::Result unmarked = convert(every + "\ninteger y;\n//* A\ninteger x = 1;\n//*/\n" + "default { state_entry() { llOwnerSay((string)(x + y)); } }\n"); + UNSCOPED_INFO("paired still: " + unmarked.text); + CHECK(has(unmarked, "\n---[[ A\nlocal x = 1\n--]]\n")); + UNSCOPED_INFO("no byte of its own left in"); + CHECK(count(unmarked, std::string(1, '\x01')) == size_t(1)); + checksClean(unmarked); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a toggle after code or in a handler or at the top above the helpers", "[ALLSLToSLua]") + { + const ALLSLToSLua::Result r = convert("default { state_entry() {\n" + " integer a = 1; //* A\n" + " llOwnerSay((string)a); //*/\n" + " //* DEBUG\n" + " llOwnerSay(\"debug\");\n" + " //*/\n" + " llOwnerSay(\"done\");\n" + "} }\n"); + UNSCOPED_INFO("after code: " + r.text); + CHECK(has(r, "\nlocal a = 1 ---[[ A\nprint(tostring(a)) --]]\n")); + UNSCOPED_INFO("off from it"); + CHECK(toggled(r.text, "---[[ A") == std::string("--[[ A\nprint(tostring(a)) --]]")); + UNSCOPED_INFO("in a handler: " + r.text); + CHECK(has(r, "\n---[[ DEBUG\nprint(\"debug\")\n--]]\nprint(\"done\")\n")); + UNSCOPED_INFO("off"); + CHECK(toggled(r.text, "---[[ DEBUG") == std::string("--[[ DEBUG\nprint(\"debug\")\n--]]")); + checksClean(r); + + const ALLSLToSLua::Result top = convert("//* DEBUG\n" + "\n" + "vector v = <1, 2, 3>;\n" + "//*/\n" + "default { state_entry() { llOwnerSay((string)llVecNorm(v)); } }\n"); + { + INFO("the helper over it: " + top.text); + CHECK(has(top, "local function vecNorm(")); + CHECK(top.text.find("local function vecNorm(") < top.text.find("---[[ DEBUG")); + } + UNSCOPED_INFO("off"); + CHECK(toggled(top.text, "---[[ DEBUG") == std::string("--[[ DEBUG\nlocal v = vector(1, 2, 3)\n--]]")); + checksClean(top); + } + + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a comment after an unbraced body trails the statement ending its line", "[ALLSLToSLua]") { - set_test_name("a comment after an unbraced body, on the line it ends, trails the statement that ends that line: a then's, an " - "elseif's, an else's, a loop's; an empty body's the line that opens it; a one-line if's, the whole if"); const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" " integer a; integer b;\n" " if (n == 1)\n" @@ -3156,22 +4068,25 @@ namespace tut " while (a) ; // an empty loop\n" " if (n == 5) llOwnerSay(\"h\"); // after h\n" "} }\n"); - ensure("a then's, an elseif's, an else's: " + r.text, - has(r, " if n == 1 then\n print(\"a\") -- after a\n elseif n == 2 then\n print(\"b\") -- after b\n" + UNSCOPED_INFO("a then's, an elseif's, an else's: " + r.text); + CHECK(has(r, " if n == 1 then\n print(\"a\") -- after a\n elseif n == 2 then\n print(\"b\") -- after b\n" " else\n print(\"c\") -- after c\n end\n")); - ensure("an empty body's, the line that opens it: " + r.text, - has(r, " == 2 then -- empty\n elseif b == 2 then -- empty too\n else\n a = true -- after a = 2\n end\n")); - ensure("an if on one line inside another: " + r.text, has(r, " if b ~= 0 then print(\"x\") end -- inner, one line\n")); - ensure("a loop's body's: " + r.text, has(r, " n -= 1 -- after the step\n end\n") && has(r, " while a do -- an empty loop\n end\n")); - ensure("a one-line if, as before: " + r.text, has(r, " if n == 5 then print(\"h\") end -- after h\n")); + UNSCOPED_INFO("an empty body's, the line that opens it: " + r.text); + CHECK(has(r, " == 2 then -- empty\n elseif b == 2 then -- empty too\n else\n a = true -- after a = 2\n end\n")); + UNSCOPED_INFO("an if on one line inside another: " + r.text); + CHECK(has(r, " if b ~= 0 then print(\"x\") end -- inner, one line\n")); + { + INFO("a loop's body's: " + r.text); + CHECK(has(r, " n -= 1 -- after the step\n end\n")); + CHECK(has(r, " while a do -- an empty loop\n end\n")); + } + UNSCOPED_INFO("a one-line if, as before: " + r.text); + CHECK(has(r, " if n == 5 then print(\"h\") end -- after h\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<96>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a float cut to an integer is math.modf bracketed where its values spread", "[ALLSLToSLua]") { - set_test_name("a float cut to an integer is math.modf's first value: bare where only its first is read anyway -- an operand, all " - "of a value assigned -- and kept to it by brackets where its values would spread"); const ALLSLToSLua::Result r = convert("integer menuChannel;\n" "vector gV;\n" "integer cut(float f) { return (integer)f; }\n" @@ -3187,24 +4102,30 @@ namespace tut " gV.z = (integer)f;\n" " llOwnerSay((string)(cut(f) + x + menuChannel) + (string)gV);\n" "} }\n"); - ensure("Tapple's: " + r.text, has(r, "menuChannel = math.modf(ll.Frand(1000)) - 10000\n")); - ensure("a local: " + r.text, has(r, "local x = math.modf(f)\n")); - ensure("assigned: " + r.text, has(r, " x = math.modf(f)\n")); - ensure("added: " + r.text, has(r, " x += math.modf(f)\n")); - ensure("its negative: " + r.text, has(r, " x = -math.modf(f)\n")); - ensure("compared: " + r.text, has(r, "if math.modf(f) == 3 then")); - ensure("an argument: " + r.text, has(r, "print(tostring((math.modf(f))))")); - ensure("a list's item: " + r.text, has(r, "{ (math.modf(f)) }")); - ensure("returned: " + r.text, has(r, "return (math.modf(f))")); - ensure("a vector's part, an argument: " + r.text, has(r, "gV = vector(gV.x, gV.y, (math.modf(f)))")); - checksClean(r); - } - template<> template<> - void allsltoslua_object::test<97>() - { - set_test_name("a local whose Luau name would hide what its reach names, which LSL kept apart, is named apart: Tapple's parameter " - "f2 beside the function f2, a local beside a function the mark gave its name, a parameter named as its own " - "function; a global the mark gave another's name; nothing where nothing is hidden"); + UNSCOPED_INFO("Tapple's: " + r.text); + CHECK(has(r, "menuChannel = math.modf(ll.Frand(1000)) - 10000\n")); + UNSCOPED_INFO("a local: " + r.text); + CHECK(has(r, "local x = math.modf(f)\n")); + UNSCOPED_INFO("assigned: " + r.text); + CHECK(has(r, " x = math.modf(f)\n")); + UNSCOPED_INFO("added: " + r.text); + CHECK(has(r, " x += math.modf(f)\n")); + UNSCOPED_INFO("its negative: " + r.text); + CHECK(has(r, " x = -math.modf(f)\n")); + UNSCOPED_INFO("compared: " + r.text); + CHECK(has(r, "if math.modf(f) == 3 then")); + UNSCOPED_INFO("an argument: " + r.text); + CHECK(has(r, "print(tostring((math.modf(f))))")); + UNSCOPED_INFO("a list's item: " + r.text); + CHECK(has(r, "{ (math.modf(f)) }")); + UNSCOPED_INFO("returned: " + r.text); + CHECK(has(r, "return (math.modf(f))")); + UNSCOPED_INFO("a vector's part, an argument: " + r.text); + CHECK(has(r, "gV = vector(gV.x, gV.y, (math.modf(f)))")); + checksClean(r); + } + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a local whose Luau name would hide another is named apart", "[ALLSLToSLua]") + { const ALLSLToSLua::Result r = convert("f1(string f2) {\n" " llOwnerSay(f2);\n" " f2();\n" @@ -3217,8 +4138,13 @@ namespace tut "default { state_entry() {\n" " f1(\"hello\");\n" "} }\n"); - ensure("Tapple's, the parameter named apart: " + r.text, has(r, "local function f1(f2_)\n print(f2_)\n f2()\nend\n")); - ensure("the function as it was: " + r.text, has(r, "local f2\n") && has(r, "function f2()\n")); + UNSCOPED_INFO("Tapple's, the parameter named apart: " + r.text); + CHECK(has(r, "local function f1(f2_)\n print(f2_)\n f2()\nend\n")); + { + INFO("the function as it was: " + r.text); + CHECK(has(r, "local f2\n")); + CHECK(has(r, "function f2()\n")); + } checksClean(r); const ALLSLToSLua::Result detected = convert("integer detected() {\n" @@ -3236,9 +4162,15 @@ namespace tut " llOwnerSay((string)(detected() * detected()));\n" " }\n" "}\n"); - ensure("Tapple's detected, the function marked: " + detected.text, has(detected, "local function detected_()")); - ensure("the local apart from it: " + detected.text, has(detected, "local detected_2 = \"fritz\"") && has(detected, "print(detected_2)")); - ensure("the function called: " + detected.text, has(detected, "print(tostring(detected_() * detected_()))")); + UNSCOPED_INFO("Tapple's detected, the function marked: " + detected.text); + CHECK(has(detected, "local function detected_()")); + { + INFO("the local apart from it: " + detected.text); + CHECK(has(detected, "local detected_2 = \"fritz\"")); + CHECK(has(detected, "print(detected_2)")); + } + UNSCOPED_INFO("the function called: " + detected.text); + CHECK(has(detected, "print(tostring(detected_() * detected_()))")); checksClean(detected); const ALLSLToSLua::Result own = convert("integer fact(integer fact) {\n" @@ -3256,20 +4188,28 @@ namespace tut " llOwnerSay(table + table_ + (string)(fact(n) + k));\n" " f3(\"a\"); f4();\n" "} }\n"); - ensure("a parameter named as its function: " + own.text, has(own, "local function fact(fact_)") && has(own, "return fact_ * fact(fact_ - 1)")); - ensure("a global the mark gave another's name: " + own.text, - has(own, "local table_2 = \"t\"\n") && has(own, "local table_ = \"u\"\n") && has(own, "print(`{table_2}{table_}{fact(n) + k}`)")); - ensure("nothing hidden, nothing renamed: " + own.text, has(own, "local function f3(f2)\n print(f2)\n")); - ensure("a call before the local, not in its reach: " + own.text, has(own, " f2()\n local f2 = \"x\"\n print(f2)\n")); - ensure("what a step was, under a name the script does not have: " + own.text, - has(own, "(function() local was2 = was; was += 1 return was2 end)() + was")); + { + INFO("a parameter named as its function: " + own.text); + CHECK(has(own, "local function fact(fact_)")); + CHECK(has(own, "return fact_ * fact(fact_ - 1)")); + } + { + INFO("a global the mark gave another's name: " + own.text); + CHECK(has(own, "local table_2 = \"t\"\n")); + CHECK(has(own, "local table_ = \"u\"\n")); + CHECK(has(own, "print(`{table_2}{table_}{fact(n) + k}`)")); + } + UNSCOPED_INFO("nothing hidden, nothing renamed: " + own.text); + CHECK(has(own, "local function f3(f2)\n print(f2)\n")); + UNSCOPED_INFO("a call before the local, not in its reach: " + own.text); + CHECK(has(own, " f2()\n local f2 = \"x\"\n print(f2)\n")); + UNSCOPED_INFO("what a step was, under a name the script does not have: " + own.text); + CHECK(has(own, "(function() local was2 = was; was += 1 return was2 end)() + was")); checksClean(own); } - template<> template<> - void allsltoslua_object::test<98>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a state's handlers kept apart by blank lines only where the LSL had them", "[ALLSLToSLua]") { - set_test_name("a state's handlers in its table kept apart by a blank line where the LSL had them so, and together where not"); const ALLSLToSLua::Result r = convert("default { state_entry() { state collect; } }\n" "state collect {\n" " changed(integer change) {\n" @@ -3286,19 +4226,19 @@ namespace tut " // listening\n" " listen(integer c, string name, key id, string msg) { llOwnerSay(msg); }\n" "}\n"); - ensure("Tapple's, apart: " + r.text, has(r, " end\n end,\n\n on_rez = function(param)\n")); - ensure("together: " + r.text, has(r, " print(\"r\")\n end,\n touch_start = function(detected)\n")); - ensure("one blank line over a comment over it: " + r.text, has(r, " end,\n\n -- listening\n listen = function(")); - ensure("none at the table's top: " + r.text, has(r, "states.collect = {\n changed = function(change)\n")); + UNSCOPED_INFO("Tapple's, apart: " + r.text); + CHECK(has(r, " end\n end,\n\n on_rez = function(param)\n")); + UNSCOPED_INFO("together: " + r.text); + CHECK(has(r, " print(\"r\")\n end,\n touch_start = function(detected)\n")); + UNSCOPED_INFO("one blank line over a comment over it: " + r.text); + CHECK(has(r, " end,\n\n -- listening\n listen = function(")); + UNSCOPED_INFO("none at the table's top: " + r.text); + CHECK(has(r, "states.collect = {\n changed = function(change)\n")); checksClean(r); } - template<> template<> - void allsltoslua_object::test<99>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: LSL's right-side-first order noted only where it could show", "[ALLSLToSLua]") { - set_test_name("LSL's right side first noted only where the order could show, naming what: not two sides that only read the " - "world, through a function of the script's too (Tapple's); a call that changes the world beside one that reads it; " - "a call that sets what the other side's function reads; not one beside a function that does not read it"); const ALLSLToSLua::Result r = convert("key gSender;\n" "string gCache;\n" "string myInfo() { return (string)llGetKey(); }\n" @@ -3312,20 +4252,28 @@ namespace tut " llOwnerSay(cached() + fill());\n" " llOwnerSay(other() + fill());\n" "} }\n"); - ensure("Tapple's, two sides that only read: " + r.text, !noted(r, "SluaRightFirst") && !noted(r, "SluaRightFirstWorld", "myInfo()")); - ensure("a call that changes the world, named with the other: " + r.text, noted(r, "SluaRightFirstWorld", "say()") && - has(r, "-- LSL: LSL ran the right side of this before the left, and Luau runs the left first: say() may change what " + { + INFO("Tapple's, two sides that only read: " + r.text); + CHECK_FALSE(noted(r, "SluaRightFirst")); + CHECK_FALSE(noted(r, "SluaRightFirstWorld", "myInfo()")); + } + { + INFO("a call that changes the world, named with the other: " + r.text); + CHECK(noted(r, "SluaRightFirstWorld", "say()")); + CHECK(has(r, "-- LSL: LSL ran the right side of this before the left, and Luau runs the left first: say() may change what " "llKey2Name() does or finds outside the script.\n print(ll.Key2Name(gSender) .. say())")); - ensure("a call that sets what the other's reads: " + r.text, noted(r, "SluaRightFirstCall", "fill()") && - has(r, "fill() sets gCache, which the other side reads or sets too.\n print(cached() .. fill())")); - ensure("noted twice, no more: " + r.text, count(r, "LSL ran the right side") == 2); + } + { + INFO("a call that sets what the other's reads: " + r.text); + CHECK(noted(r, "SluaRightFirstCall", "fill()")); + CHECK(has(r, "fill() sets gCache, which the other side reads or sets too.\n print(cached() .. fill())")); + } + UNSCOPED_INFO("noted twice, no more: " + r.text); + CHECK(count(r, "LSL ran the right side") == 2); checksClean(r); } - template<> template<> - void allsltoslua_object::test<100>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: with keyTextAny off text for a key passes bare and the checker marks it", "[ALLSLToSLua]") { - set_test_name("text given where ll takes a key, with keyTextAny off, as it is with no :: any (Tapple's), the note giving the " - "ways to see to it; what the type checker then marks, where it knows the text for text"); const std::string lsl = "key inventorySender;\n" "string myInfo() { return (string)llGetKey(); }\n" "default { touch_start(integer n) {\n" @@ -3335,11 +4283,21 @@ namespace tut ALLSLToSLua::Options o; o.keyTextAny = false; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, o); - ensure("converted", r.converted); - ensure("as it is: " + r.text, has(r, "ll.Key2Name(myInfo()))") && !has(r, "myInfo() :: any")); - ensure("a UUID written out, still a uuid: " + r.text, has(r, "ll.Key2Name(uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"))")); - ensure("the note with the ways: " + r.text, noted(r, "SluaKeyAsTextBare") && !noted(r, "SluaKeyAsText") && - has(r, "`:: any` quiets it, uuid() stops the script on text that is no UUID")); + UNSCOPED_INFO("converted"); + CHECK(r.converted); + { + INFO("as it is: " + r.text); + CHECK(has(r, "ll.Key2Name(myInfo()))")); + CHECK_FALSE(has(r, "myInfo() :: any")); + } + UNSCOPED_INFO("a UUID written out, still a uuid: " + r.text); + CHECK(has(r, "ll.Key2Name(uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"))")); + { + INFO("the note with the ways: " + r.text); + CHECK(noted(r, "SluaKeyAsTextBare")); + CHECK_FALSE(noted(r, "SluaKeyAsText")); + CHECK(has(r, "`:: any` quiets it, uuid() stops the script on text that is no UUID")); + } // What the checker marks as an error, each by its line. const auto marked = [&](const std::string& slua) { std::string said; @@ -3352,7 +4310,8 @@ namespace tut // The line the text for a key is on, as marked says it. const auto lineOf = [](const std::string& slua) { const size_t at = slua.find("ll.Key2Name(myInfo())"); - ensure("written: " + slua, at != std::string::npos); + UNSCOPED_INFO("written: " + slua); + REQUIRE(at != std::string::npos); return llformat("[%d] ", static_cast(std::count(slua.begin(), slua.begin() + at, '\n')) + 1); }; // Nonstrict, as the grid has it, takes an untyped function's answer @@ -3364,19 +4323,22 @@ namespace tut o.types = true; const ALLSLToSLua::Result typed = ALLSLToSLua::convert(lsl, o); const std::string said = marked(typed.text); - ensure("typed, the text for a key marked, alone:\n" + said + "---\n" + typed.text, - said.rfind(lineOf(typed.text), 0) == 0 && std::count(said.begin(), said.end(), '\n') == 1); + { + INFO("typed, the text for a key marked, alone:\n" + said + "---\n" + typed.text); + CHECK(said.rfind(lineOf(typed.text), 0) == 0); + CHECK(std::count(said.begin(), said.end(), '\n') == 1); + } const std::string strict = "--!strict\n" + r.text; const std::string loud = marked(strict); - ensure("strict, the same:\n" + loud + "---\n" + strict, loud.rfind(lineOf(strict), 0) == 0 && std::count(loud.begin(), loud.end(), '\n') == 1); + { + INFO("strict, the same:\n" + loud + "---\n" + strict); + CHECK(loud.rfind(lineOf(strict), 0) == 0); + CHECK(std::count(loud.begin(), loud.end(), '\n') == 1); + } } } - template<> template<> - void allsltoslua_object::test<101>() + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a key given no text holds text as LSL's did; a uuid key compared as one", "[ALLSLToSLua]") { - set_test_name("a key given no text holds text, as LSL's did (Tapple's): compared with \"\", sent over a link message, made text " - "and asked its truth as LSL had it; a key that is a uuid compared with no text or a UUID written out as a uuid, " - "other text and NULL_KEY as text"); const ALLSLToSLua::Result r = convert("key gUser = \"\";\n" "default {\n" " state_entry() {\n" @@ -3403,26 +4365,48 @@ namespace tut " if (NULL_KEY != \"\") llOwnerSay(\"NULL_KEY has text\");\n" " }\n" "}\n"); - ensure("Tapple's, text: " + r.text, has(r, "local k = \"\"") && has(r, "print(tostring(if k == \"\" then 1 else 0))")); - ensure("a global given no text, text: " + r.text, has(r, "local gUser = \"\"") && has(r, "gUser = tostring(detected[1]:getKey())")); - ensure("sent as text, no text and all: " + r.text, count(r, "ll.MessageLinked(LINK_SET, 0, \"\", gUser)") == 2); - ensure("made text as it is: " + r.text, has(r, "print(\"user: \" .. gUser)")); - ensure("its truth as LSL's key's: " + r.text, has(r, "if (touuid(gUser) or NULL_KEY).istruthy then")); - ensure("the id, text: " + r.text, has(r, "if id == \"\" then") && has(r, "if (touuid(id) or NULL_KEY).istruthy then")); - ensure("keys held as text, in a list as text: " + r.text, has(r, "{ id, gUser }") && !has(r, "{ uuid(")); - ensure("a uuid against no text, a uuid: " + r.text, has(r, "local d = NULL_KEY") && has(r, "if d == uuid(\"\") then") && - has(r, "if uuid(\"\") ~= d then")); - ensure("a UUID written out: " + r.text, has(r, "if d == uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\") then")); - ensure("other text, as text: " + r.text, has(r, "if tostring(d) == \"none\" then")); - ensure("NULL_KEY, as LSL's text: " + r.text, has(r, "if tostring(NULL_KEY) ~= \"\" then")); - checksClean(r); - } - template<> template<> - void allsltoslua_object::test<102>() - { - set_test_name("with uuidKeys off, every key of the script's own held as text, as LSL held them (Tapple's preference): its " - "variables, its functions' and handlers' parameters, what its functions give, a cast to a key; what SLua gives as " - "a uuid made text, text given ll as it is; closeToLSL the same"); + { + INFO("Tapple's, text: " + r.text); + CHECK(has(r, "local k = \"\"")); + CHECK(has(r, "print(tostring(if k == \"\" then 1 else 0))")); + } + { + INFO("a global given no text, text: " + r.text); + CHECK(has(r, "local gUser = \"\"")); + CHECK(has(r, "gUser = tostring(detected[1]:getKey())")); + } + UNSCOPED_INFO("sent as text, no text and all: " + r.text); + CHECK(count(r, "ll.MessageLinked(LINK_SET, 0, \"\", gUser)") == 2); + UNSCOPED_INFO("made text as it is: " + r.text); + CHECK(has(r, "print(\"user: \" .. gUser)")); + UNSCOPED_INFO("its truth as LSL's key's: " + r.text); + CHECK(has(r, "if (touuid(gUser) or NULL_KEY).istruthy then")); + { + INFO("the id, text: " + r.text); + CHECK(has(r, "if id == \"\" then")); + CHECK(has(r, "if (touuid(id) or NULL_KEY).istruthy then")); + } + { + INFO("keys held as text, in a list as text: " + r.text); + CHECK(has(r, "{ id, gUser }")); + CHECK_FALSE(has(r, "{ uuid(")); + } + { + INFO("a uuid against no text, a uuid: " + r.text); + CHECK(has(r, "local d = NULL_KEY")); + CHECK(has(r, "if d == uuid(\"\") then")); + CHECK(has(r, "if uuid(\"\") ~= d then")); + } + UNSCOPED_INFO("a UUID written out: " + r.text); + CHECK(has(r, "if d == uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\") then")); + UNSCOPED_INFO("other text, as text: " + r.text); + CHECK(has(r, "if tostring(d) == \"none\" then")); + UNSCOPED_INFO("NULL_KEY, as LSL's text: " + r.text); + CHECK(has(r, "if tostring(NULL_KEY) ~= \"\" then")); + checksClean(r); + } + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: with uuidKeys off the script's own keys are held as text as in LSL", "[ALLSLToSLua]") + { const std::string lsl = "key gOwner;\n" "key pick() { return llGetOwner(); }\n" "integer mine(key who) { return who == gOwner; }\n" @@ -3447,26 +4431,67 @@ namespace tut o.uuidKeys = false; o.types = true; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, o); - ensure("converted: " + (r.problems.empty() ? std::string() : r.problems.front().message), r.converted); - ensure("Tapple's: " + r.text, has(r, "local k: string = \"\"\n") && has(r, "print(tostring(if k == \"\" then 1 else 0))")); - ensure("a global, LSL's default: " + r.text, has(r, "local gOwner: string = \"\"\n") && has(r, "gOwner = tostring(ll.GetOwner())")); - ensure("what a function gives: " + r.text, has(r, "local function pick(): string\n return tostring(ll.GetOwner())")); - ensure("a parameter: " + r.text, has(r, "local function mine(who: string): boolean") && has(r, "who == gOwner")); - ensure("a cast, the text as it is: " + r.text, has(r, "local cast: string = s\n") && has(r, "print(cast .. \"\")") && !has(r, "uuid(s)") && !has(r, "uuid(\"\")")); - ensure("its truth as LSL's: " + r.text, has(r, "if (touuid(gOwner) or NULL_KEY).istruthy then")); - ensure("given ll as it is: " + r.text, has(r, "ll.Dialog(gOwner :: any, \"hi\"")); - ensure("against NULL_KEY, as text: " + r.text, has(r, "gOwner ~= tostring(NULL_KEY)")); - ensure("a handler's key, made text as it begins: " + r.text, has(r, "function(c: number, n: string, idKey: uuid, m: string)") && - has(r, "local id: string = tostring(idKey)") && has(r, "if id == gOwner then")); - ensure("in a list, text as it is: " + r.text, has(r, "local values: { any } = { odd, k, tostring(NULL_KEY) }") && !has(r, "{ uuid(")); - ensure("nothing said of NULL_KEY: " + r.text, !noted(r, "SluaKeyEmpty")); + UNSCOPED_INFO("converted: " + (r.problems.empty() ? std::string() : r.problems.front().message)); + CHECK(r.converted); + { + INFO("Tapple's: " + r.text); + CHECK(has(r, "local k: string = \"\"\n")); + CHECK(has(r, "print(tostring(if k == \"\" then 1 else 0))")); + } + { + INFO("a global, LSL's default: " + r.text); + CHECK(has(r, "local gOwner: string = \"\"\n")); + CHECK(has(r, "gOwner = tostring(ll.GetOwner())")); + } + UNSCOPED_INFO("what a function gives: " + r.text); + CHECK(has(r, "local function pick(): string\n return tostring(ll.GetOwner())")); + { + INFO("a parameter: " + r.text); + CHECK(has(r, "local function mine(who: string): boolean")); + CHECK(has(r, "who == gOwner")); + } + { + INFO("a cast, the text as it is: " + r.text); + CHECK(has(r, "local cast: string = s\n")); + CHECK(has(r, "print(cast .. \"\")")); + CHECK_FALSE(has(r, "uuid(s)")); + CHECK_FALSE(has(r, "uuid(\"\")")); + } + UNSCOPED_INFO("its truth as LSL's: " + r.text); + CHECK(has(r, "if (touuid(gOwner) or NULL_KEY).istruthy then")); + UNSCOPED_INFO("given ll as it is: " + r.text); + CHECK(has(r, "ll.Dialog(gOwner :: any, \"hi\"")); + UNSCOPED_INFO("against NULL_KEY, as text: " + r.text); + CHECK(has(r, "gOwner ~= tostring(NULL_KEY)")); + { + INFO("a handler's key, made text as it begins: " + r.text); + CHECK(has(r, "function(c: number, n: string, idKey: uuid, m: string)")); + CHECK(has(r, "local id: string = tostring(idKey)")); + CHECK(has(r, "if id == gOwner then")); + } + { + INFO("in a list, text as it is: " + r.text); + CHECK(has(r, "local values: { any } = { odd, k, tostring(NULL_KEY) }")); + CHECK_FALSE(has(r, "{ uuid(")); + } + UNSCOPED_INFO("nothing said of NULL_KEY: " + r.text); + CHECK_FALSE(noted(r, "SluaKeyEmpty")); checksClean(r); const ALLSLToSLua::Result close = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); - ensure("closeToLSL the same: " + close.text, close.converted && has(close, "local gOwner = \"\"\n") && has(close, "local cast = s\n")); + { + INFO("closeToLSL the same: " + close.text); + CHECK(close.converted); + CHECK(has(close, "local gOwner = \"\"\n")); + CHECK(has(close, "local cast = s\n")); + } checksClean(close); const ALLSLToSLua::Result uuids = convert(lsl); - ensure("uuids by default: " + uuids.text, has(uuids, "local gOwner = NULL_KEY\n") && has(uuids, "local cast = uuid(s)")); + { + INFO("uuids by default: " + uuids.text); + CHECK(has(uuids, "local gOwner = NULL_KEY\n")); + CHECK(has(uuids, "local cast = uuid(s)")); + } } } diff --git a/indra/alscript/tests/alluauconfig_test.cpp b/indra/alscript/tests/alluauconfig_test.cpp index 3ba1ed2935..7260e603ed 100644 --- a/indra/alscript/tests/alluauconfig_test.cpp +++ b/indra/alscript/tests/alluauconfig_test.cpp @@ -30,92 +30,161 @@ #include "lluuid.h" -#include "../test/lltut.h" +#include #include -namespace tut +namespace { - struct alluauconfig_data + TEST_CASE("alluauconfig: aliases by lower-case name with their paths and the mode where it is said", "[alluauconfig]") { - }; - - typedef test_group alluauconfig_group; - typedef alluauconfig_group::object alluauconfig_object; - alluauconfig_group alluauconfig_instance("alluauconfig"); - - template<> template<> - void alluauconfig_object::test<1>() - { - set_test_name("aliases come out by lower-case name with their paths, and the mode where it is said"); ALLuauConfig config; std::string error; - ensure("parses: " + error, ALLuauConfig::parse("{\n \"languageMode\": \"strict\",\n \"aliases\": { \"Lib\": \"./lib\", \"shared\": \"../shared\", \"abs\": \"/opt/lua\" }\n}\n", config, error)); - ensure_equals("three aliases", config.aliases.size(), size_t(3)); - ensure_equals("by lower-case name", config.aliases["lib"], std::string("./lib")); - ensure_equals("a path up", config.aliases["shared"], std::string("../shared")); - ensure_equals("an absolute one", config.aliases["abs"], std::string("/opt/lua")); - ensure_equals("the mode", config.mode, std::string("strict")); - ensure("no mode where none is said", ALLuauConfig::parse("{ \"aliases\": {} }", config, error) && config.mode.empty()); - ensure("not a configuration", !ALLuauConfig::parse("{ \"aliases\": 3 }", config, error) && !error.empty()); - ensure("nor an alias Luau refuses", !ALLuauConfig::parse("{ \"aliases\": { \"bad name\": \"x\" } }", config, error)); + UNSCOPED_INFO("parses: " + error); + REQUIRE(ALLuauConfig::parse("{\n \"languageMode\": \"strict\",\n \"aliases\": { \"Lib\": \"./lib\", \"shared\": \"../shared\", \"abs\": \"/opt/lua\" }\n}\n", config, error)); + UNSCOPED_INFO("three aliases"); + REQUIRE(config.aliases.size() == size_t(3)); + UNSCOPED_INFO("by lower-case name"); + CHECK(config.aliases["lib"] == std::string("./lib")); + UNSCOPED_INFO("a path up"); + CHECK(config.aliases["shared"] == std::string("../shared")); + UNSCOPED_INFO("an absolute one"); + CHECK(config.aliases["abs"] == std::string("/opt/lua")); + UNSCOPED_INFO("the mode"); + CHECK(config.mode == std::string("strict")); + { + INFO("no mode where none is said"); + CHECK(ALLuauConfig::parse("{ \"aliases\": {} }", config, error)); + REQUIRE(config.mode.empty()); + } + { + INFO("not a configuration"); + CHECK_FALSE(ALLuauConfig::parse("{ \"aliases\": 3 }", config, error)); + REQUIRE_FALSE(error.empty()); + } + UNSCOPED_INFO("nor an alias Luau refuses"); + CHECK_FALSE(ALLuauConfig::parse("{ \"aliases\": { \"bad name\": \"x\" } }", config, error)); } - template<> template<> - void alluauconfig_object::test<2>() + TEST_CASE("alluauconfig: aliasOf reads the @ name up to the slash; absolute paths and studio aliases", "[alluauconfig]") { - set_test_name("a require name's alias is what follows the @ up to the slash, and absolute paths are known on both platforms"); std::string alias, rest; - ensure("found", ALLuauConfig::aliasOf("@Lib/util/strings", alias, rest)); - ensure_equals("lower case", alias, std::string("lib")); - ensure_equals("the rest", rest, std::string("util/strings")); - ensure("the alias alone", ALLuauConfig::aliasOf("@lib", alias, rest) && alias == "lib" && rest.empty()); - ensure("not without the @", !ALLuauConfig::aliasOf("lib/util", alias, rest)); - ensure("not an empty one", !ALLuauConfig::aliasOf("@/util", alias, rest)); - ensure("absolute on posix", ALLuauConfig::absolute("/opt/lua")); - ensure("absolute on windows", ALLuauConfig::absolute("C:\\lua") && ALLuauConfig::absolute("c:/lua")); - ensure("relative", !ALLuauConfig::absolute("./lib") && !ALLuauConfig::absolute("lib")); + UNSCOPED_INFO("found"); + REQUIRE(ALLuauConfig::aliasOf("@Lib/util/strings", alias, rest)); + UNSCOPED_INFO("lower case"); + CHECK(alias == std::string("lib")); + UNSCOPED_INFO("the rest"); + CHECK(rest == std::string("util/strings")); + { + INFO("the alias alone"); + CHECK(ALLuauConfig::aliasOf("@lib", alias, rest)); + CHECK(alias == "lib"); + REQUIRE(rest.empty()); + } + UNSCOPED_INFO("not without the @"); + CHECK_FALSE(ALLuauConfig::aliasOf("lib/util", alias, rest)); + UNSCOPED_INFO("not an empty one"); + CHECK_FALSE(ALLuauConfig::aliasOf("@/util", alias, rest)); + UNSCOPED_INFO("absolute on posix"); + CHECK(ALLuauConfig::absolute("/opt/lua")); + { + INFO("absolute on windows"); + CHECK(ALLuauConfig::absolute("C:\\lua")); + CHECK(ALLuauConfig::absolute("c:/lua")); + } + { + INFO("relative"); + CHECK_FALSE(ALLuauConfig::absolute("./lib")); + CHECK_FALSE(ALLuauConfig::absolute("lib")); + } // Script Studio's own aliases: what Luau takes, not self, not sl-*. - ensure("a studio alias's name", ALLuauConfig::studioAliasName("lib") && ALLuauConfig::studioAliasName("My_Lib-2.x")); - ensure("not self, in any case", !ALLuauConfig::studioAliasName("self") && !ALLuauConfig::studioAliasName("Self")); - ensure("not sl-*", !ALLuauConfig::studioAliasName("sl-std") && !ALLuauConfig::studioAliasName("SL-x")); - ensure("nothing Luau refuses", !ALLuauConfig::studioAliasName("") && !ALLuauConfig::studioAliasName("a/b") && - !ALLuauConfig::studioAliasName("a b") && !ALLuauConfig::studioAliasName("..")); - ensure_equals("a folder's name made one", ALLuauConfig::studioAliasFor("My Library", {}), std::string("my-library")); - ensure_equals("a number where it is taken, in any case", ALLuauConfig::studioAliasFor("lib", { "LIB", "lib2" }), std::string("lib3")); - ensure_equals("one that cannot be: lib", ALLuauConfig::studioAliasFor("self", {}), std::string("lib")); + { + INFO("a studio alias's name"); + CHECK(ALLuauConfig::studioAliasName("lib")); + CHECK(ALLuauConfig::studioAliasName("My_Lib-2.x")); + } + { + INFO("not self, in any case"); + CHECK_FALSE(ALLuauConfig::studioAliasName("self")); + CHECK_FALSE(ALLuauConfig::studioAliasName("Self")); + } + { + INFO("not sl-*"); + CHECK_FALSE(ALLuauConfig::studioAliasName("sl-std")); + CHECK_FALSE(ALLuauConfig::studioAliasName("SL-x")); + } + { + INFO("nothing Luau refuses"); + CHECK_FALSE(ALLuauConfig::studioAliasName("")); + CHECK_FALSE(ALLuauConfig::studioAliasName("a/b")); + CHECK_FALSE(ALLuauConfig::studioAliasName("a b")); + CHECK_FALSE(ALLuauConfig::studioAliasName("..")); + } + UNSCOPED_INFO("a folder's name made one"); + CHECK(ALLuauConfig::studioAliasFor("My Library", {}) == std::string("my-library")); + UNSCOPED_INFO("a number where it is taken, in any case"); + CHECK(ALLuauConfig::studioAliasFor("lib", { "LIB", "lib2" }) == std::string("lib3")); + UNSCOPED_INFO("one that cannot be: lib"); + CHECK(ALLuauConfig::studioAliasFor("self", {}) == std::string("lib")); } - template<> template<> - void alluauconfig_object::test<3>() + TEST_CASE("alluauconfig: lints and globals come out as Luau reads them with its default lints", "[alluauconfig]") { - set_test_name("the lints and the globals come out as Luau reads them, the lints as its defaults where the file says nothing"); ALLuauConfig config; std::string error; const ALLuauConfig defaults; - ensure("parses: " + error, ALLuauConfig::parse("{\n \"lint\": { \"LocalUnused\": false, \"LocalShadow\": true },\n \"globals\": [\"Alchemy\", \"grid\"]\n}\n", config, error)); - ensure_equals("two globals", config.globals.size(), size_t(2)); - ensure_equals("in order", config.globals[0], std::string("Alchemy")); + UNSCOPED_INFO("parses: " + error); + REQUIRE(ALLuauConfig::parse("{\n \"lint\": { \"LocalUnused\": false, \"LocalShadow\": true },\n \"globals\": [\"Alchemy\", \"grid\"]\n}\n", config, error)); + UNSCOPED_INFO("two globals"); + REQUIRE(config.globals.size() == size_t(2)); + UNSCOPED_INFO("in order"); + CHECK(config.globals[0] == std::string("Alchemy")); // LocalUnused is code 7 and LocalShadow code 4 in Luau's numbering; // the second is off by default. - ensure("LocalUnused off", (config.lints & (1ull << 7)) == 0); - ensure("LocalShadow on", (config.lints & (1ull << 4)) != 0); - ensure("the rest as they were", (config.lints & ~((1ull << 7) | (1ull << 4))) == (defaults.lints & ~((1ull << 7) | (1ull << 4)))); - ensure("none fatal, not every lint an error", config.fatalLints == 0 && !config.lintErrors); - ensure("said nothing: the defaults", ALLuauConfig::parse("{}", config, error) && config.lints == defaults.lints && config.fatalLints == 0 && config.globals.empty()); - ensure("every lint an error where asked", ALLuauConfig::parse("{ \"lintErrors\": true }", config, error) && config.lintErrors); + UNSCOPED_INFO("LocalUnused off"); + CHECK((config.lints & (1ull << 7)) == 0); + UNSCOPED_INFO("LocalShadow on"); + CHECK((config.lints & (1ull << 4)) != 0); + UNSCOPED_INFO("the rest as they were"); + CHECK((config.lints & ~((1ull << 7) | (1ull << 4))) == (defaults.lints & ~((1ull << 7) | (1ull << 4)))); + { + INFO("none fatal, not every lint an error"); + CHECK(config.fatalLints == 0); + CHECK_FALSE(config.lintErrors); + } + { + INFO("said nothing: the defaults"); + CHECK(ALLuauConfig::parse("{}", config, error)); + CHECK(config.lints == defaults.lints); + CHECK(config.fatalLints == 0); + REQUIRE(config.globals.empty()); + } + { + INFO("every lint an error where asked"); + CHECK(ALLuauConfig::parse("{ \"lintErrors\": true }", config, error)); + CHECK(config.lintErrors); + } } - template<> template<> - void alluauconfig_object::test<4>() + TEST_CASE("alluauconfig: a .luaurc over a scripter's own lints and mode overrides only what it says", "[alluauconfig]") { - set_test_name("a .luaurc over a scripter's own lints and mode overrides only what it says"); const uint64_t unused = ALLuauConfig::lintBit("LocalUnused"); const uint64_t function = ALLuauConfig::lintBit("FunctionUnused"); const uint64_t shadow = ALLuauConfig::lintBit("LocalShadow"); - ensure("the lints have bits", unused && function && shadow && unused != function); - ensure("an unknown lint has none", ALLuauConfig::lintBit("NoSuchLint") == 0); - ensure("every lint is named", ALLuauConfig::lintNames().size() > 20 && ALLuauConfig::lintNames().front() == "UnknownGlobal"); + { + INFO("the lints have bits"); + CHECK(unused); + CHECK(function); + CHECK(shadow); + CHECK(unused != function); + } + UNSCOPED_INFO("an unknown lint has none"); + CHECK(ALLuauConfig::lintBit("NoSuchLint") == 0); + { + INFO("every lint is named"); + REQUIRE(ALLuauConfig::lintNames().size() > 20); + CHECK(ALLuauConfig::lintNames().front() == "UnknownGlobal"); + } ALLuauConfig base; base.lints &= ~unused; // the scripter turned it off @@ -123,41 +192,61 @@ namespace tut base.mode = "strict"; ALLuauConfig config; std::string error; - ensure("an empty file", ALLuauConfig::parse("{}", config, error, &base)); - ensure("keeps the lints off", (config.lints & unused) == 0 && (config.lints & function) != 0); - ensure("and the errors", (config.fatalLints & shadow) != 0); - ensure_equals("and the mode", config.mode, std::string("strict")); + UNSCOPED_INFO("an empty file"); + REQUIRE(ALLuauConfig::parse("{}", config, error, &base)); + { + INFO("keeps the lints off"); + CHECK((config.lints & unused) == 0); + CHECK((config.lints & function) != 0); + } + UNSCOPED_INFO("and the errors"); + CHECK((config.fatalLints & shadow) != 0); + UNSCOPED_INFO("and the mode"); + CHECK(config.mode == std::string("strict")); - ensure("a file saying one lint", ALLuauConfig::parse("{ \"lint\": { \"LocalUnused\": true, \"FunctionUnused\": false }, \"languageMode\": \"nonstrict\" }", config, error, &base)); - ensure("turns that one on", (config.lints & unused) != 0); - ensure("and that one off", (config.lints & function) == 0); - ensure("the rest as the scripter had them", (config.fatalLints & shadow) != 0); - ensure_equals("its mode over theirs", config.mode, std::string("nonstrict")); + UNSCOPED_INFO("a file saying one lint"); + CHECK(ALLuauConfig::parse("{ \"lint\": { \"LocalUnused\": true, \"FunctionUnused\": false }, \"languageMode\": \"nonstrict\" }", config, error, &base)); + UNSCOPED_INFO("turns that one on"); + CHECK((config.lints & unused) != 0); + UNSCOPED_INFO("and that one off"); + CHECK((config.lints & function) == 0); + UNSCOPED_INFO("the rest as the scripter had them"); + CHECK((config.fatalLints & shadow) != 0); + UNSCOPED_INFO("its mode over theirs"); + CHECK(config.mode == std::string("nonstrict")); - ensure("without a base, Luau's defaults", ALLuauConfig::parse("{}", config, error) && (config.lints & unused) != 0 && config.mode.empty()); + { + INFO("without a base, Luau's defaults"); + CHECK(ALLuauConfig::parse("{}", config, error)); + CHECK((config.lints & unused) != 0); + REQUIRE(config.mode.empty()); + } } - template<> template<> - void alluauconfig_object::test<5>() + TEST_CASE("alluauconfig: a file read over another adds to the globals and overrides the aliases", "[alluauconfig]") { - set_test_name("a file read over another adds its globals to the other's, and its aliases over the other's, as Luau reads a chain of them"); ALLuauConfig outer; std::string error; - ensure("the outer file", ALLuauConfig::parse("{ \"globals\": [\"shared\"], \"aliases\": { \"lib\": \"./lib\", \"util\": \"./util\" } }", outer, error)); + UNSCOPED_INFO("the outer file"); + REQUIRE(ALLuauConfig::parse("{ \"globals\": [\"shared\"], \"aliases\": { \"lib\": \"./lib\", \"util\": \"./util\" } }", outer, error)); ALLuauConfig inner; - ensure("the inner over it", ALLuauConfig::parse("{ \"globals\": [\"mine\"], \"aliases\": { \"lib\": \"./mylib\" } }", inner, error, &outer)); - ensure("both globals", inner.globals == std::vector{ "shared", "mine" }); - ensure_equals("the nearer alias", inner.aliases["lib"], std::string("./mylib")); - ensure_equals("the outer's kept", inner.aliases["util"], std::string("./util")); + UNSCOPED_INFO("the inner over it"); + REQUIRE(ALLuauConfig::parse("{ \"globals\": [\"mine\"], \"aliases\": { \"lib\": \"./mylib\" } }", inner, error, &outer)); + UNSCOPED_INFO("both globals"); + CHECK(inner.globals == std::vector{ "shared", "mine" }); + UNSCOPED_INFO("the nearer alias"); + CHECK(inner.aliases["lib"] == std::string("./mylib")); + UNSCOPED_INFO("the outer's kept"); + CHECK(inner.aliases["util"] == std::string("./util")); ALLuauConfig quiet; - ensure("one saying nothing of them", ALLuauConfig::parse("{}", quiet, error, &outer)); - ensure("keeps them", quiet.globals == std::vector{ "shared" }); + UNSCOPED_INFO("one saying nothing of them"); + REQUIRE(ALLuauConfig::parse("{}", quiet, error, &outer)); + UNSCOPED_INFO("keeps them"); + CHECK(quiet.globals == std::vector{ "shared" }); } - template<> template<> - void alluauconfig_object::test<6>() + TEST_CASE("alluauconfig: parseChain: the nearest file to say a thing wins and globals add up", "[alluauconfig]") { - set_test_name("a chain of files, nearest first, read from the top down: the nearest to say a thing wins, and globals add up"); const std::string root = "{ \"languageMode\": \"strict\", \"globals\": [\"a\"], \"lint\": { \"LocalUnused\": false }, " "\"aliases\": { \"lib\": \"./rootlib\", \"root\": \"./r\" } }"; const std::string broken = "{ not a configuration"; @@ -166,64 +255,109 @@ namespace tut const std::vector chain{ nearby, middle, broken, root }; ALLuauConfig merged; - ensure("parsed", ALLuauConfig::parseChain(chain, merged)); - ensure_equals("the nearest mode", merged.mode, std::string("nonstrict")); - ensure("globals from every file, the furthest first", merged.globals == std::vector{ "a", "b", "c" }); + UNSCOPED_INFO("parsed"); + REQUIRE(ALLuauConfig::parseChain(chain, merged)); + UNSCOPED_INFO("the nearest mode"); + CHECK(merged.mode == std::string("nonstrict")); + UNSCOPED_INFO("globals from every file, the furthest first"); + CHECK(merged.globals == std::vector{ "a", "b", "c" }); const uint64_t local_unused = ALLuauConfig::lintBit("LocalUnused"); const uint64_t function_unused = ALLuauConfig::lintBit("FunctionUnused"); - ensure("a lint the root turned off and the nearest on", (merged.lints & local_unused) != 0); - ensure("one the middle turned off, off", (merged.lints & function_unused) == 0); - ensure_equals("an alias the middle says over the root's", merged.aliases["lib"], std::string("./midlib")); - ensure_equals("the root's own kept", merged.aliases["root"], std::string("./r")); + UNSCOPED_INFO("a lint the root turned off and the nearest on"); + CHECK((merged.lints & local_unused) != 0); + UNSCOPED_INFO("one the middle turned off, off"); + CHECK((merged.lints & function_unused) == 0); + UNSCOPED_INFO("an alias the middle says over the root's"); + CHECK(merged.aliases["lib"] == std::string("./midlib")); + UNSCOPED_INFO("the root's own kept"); + CHECK(merged.aliases["root"] == std::string("./r")); ALLuauConfig base; base.mode = "strict"; ALLuauConfig none; - ensure("none that parse is no configuration", !ALLuauConfig::parseChain({ broken }, none, &base)); - ensure_equals("and leaves the base", none.mode, std::string("strict")); + UNSCOPED_INFO("none that parse is no configuration"); + REQUIRE_FALSE(ALLuauConfig::parseChain({ broken }, none, &base)); + UNSCOPED_INFO("and leaves the base"); + CHECK(none.mode == std::string("strict")); } - template<> template<> - void alluauconfig_object::test<7>() + TEST_CASE("alluauconfig: the studio's lints in a .luaurc are read apart from Luau's and * covers them", "[alluauconfig]") { - set_test_name("the studio's own lints in a .luaurc: taken out for Luau and said for the studio, first, last or alone, with the rest read whole; * says for them too"); const uint64_t compound = ALScriptLintPass::bit("SlCompoundAssign"); - ensure("a rule of the table", compound != 0 && (ALScriptLintPass::defaults() & compound) != 0); + { + INFO("a rule of the table"); + CHECK(compound != 0); + CHECK((ALScriptLintPass::defaults() & compound) != 0); + } ALLuauConfig out; std::string error; - ensure("beside Luau's: " + error, - ALLuauConfig::parse("{ \"languageMode\": \"strict\", \"lint\": { \"LocalUnused\": false, \"SlCompoundAssign\": false } }", out, error)); - ensure("the rest read", out.mode == "strict" && (out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); - ensure("ours off", (out.slLints & compound) == 0); - ensure("alone: " + error, ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false } }", out, error) && (out.slLints & compound) == 0); - ensure("first: " + error, ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false, \"LocalUnused\": false }, \"globals\": [\"g\"] }", out, error) && - (out.slLints & compound) == 0 && out.globals == std::vector{ "g" } && - (out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); - ensure("with // comments, over lines: " + error, - ALLuauConfig::parse("{\n // c style\n \"lint\": {\n \"LocalUnused\": true,\n \"SlCompoundAssign\": false\n }\n}", out, error) && - (out.slLints & compound) == 0); - ensure("* for ours too: " + error, ALLuauConfig::parse("{ \"lint\": { \"*\": false } }", out, error) && out.slLints == 0 && - (out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); - ensure("then one on: " + error, - ALLuauConfig::parse("{ \"lint\": { \"*\": false, \"SlCompoundAssign\": true } }", out, error) && out.slLints == compound); - ensure("an Sl name that is none", !ALLuauConfig::parse("{ \"lint\": { \"SlNope\": true } }", out, error) && error == "Unknown lint SlNope"); - ensure("a value that is none", !ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": \"fatal\" } }", out, error) && - error == "Bad setting 'fatal'. Valid options are true and false"); - ensure("Luau's words on the rest keep their lines: " + error, - !ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false },\n \"globals\": 3 }", out, error) && error.find("line 2") != std::string::npos); + UNSCOPED_INFO("beside Luau's: " + error); + REQUIRE(ALLuauConfig::parse("{ \"languageMode\": \"strict\", \"lint\": { \"LocalUnused\": false, \"SlCompoundAssign\": false } }", out, error)); + { + INFO("the rest read"); + CHECK(out.mode == "strict"); + CHECK((out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); + } + UNSCOPED_INFO("ours off"); + CHECK((out.slLints & compound) == 0); + { + INFO("alone: " + error); + CHECK(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false } }", out, error)); + CHECK((out.slLints & compound) == 0); + } + { + INFO("first: " + error); + CHECK(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false, \"LocalUnused\": false }, \"globals\": [\"g\"] }", out, error)); + CHECK((out.slLints & compound) == 0); + CHECK(out.globals == std::vector{ "g" }); + CHECK((out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); + } + { + INFO("with // comments, over lines: " + error); + CHECK(ALLuauConfig::parse("{\n // c style\n \"lint\": {\n \"LocalUnused\": true,\n \"SlCompoundAssign\": false\n }\n}", out, error)); + CHECK((out.slLints & compound) == 0); + } + { + INFO("* for ours too: " + error); + CHECK(ALLuauConfig::parse("{ \"lint\": { \"*\": false } }", out, error)); + CHECK(out.slLints == 0); + CHECK((out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); + } + { + INFO("then one on: " + error); + CHECK(ALLuauConfig::parse("{ \"lint\": { \"*\": false, \"SlCompoundAssign\": true } }", out, error)); + CHECK(out.slLints == compound); + } + { + INFO("an Sl name that is none"); + CHECK_FALSE(ALLuauConfig::parse("{ \"lint\": { \"SlNope\": true } }", out, error)); + CHECK(error == "Unknown lint SlNope"); + } + { + INFO("a value that is none"); + CHECK_FALSE(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": \"fatal\" } }", out, error)); + CHECK(error == "Bad setting 'fatal'. Valid options are true and false"); + } + { + INFO("Luau's words on the rest keep their lines: " + error); + CHECK_FALSE(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false },\n \"globals\": 3 }", out, error)); + REQUIRE(error.find("line 2") != std::string::npos); + } ALLuauConfig base; base.slLints = 0; base.slFatalLints = compound; - ensure("the base's where the file does not say: " + error, - ALLuauConfig::parse("{ \"languageMode\": \"strict\" }", out, error, &base) && out.slLints == 0 && out.slFatalLints == compound); + { + INFO("the base's where the file does not say: " + error); + CHECK(ALLuauConfig::parse("{ \"languageMode\": \"strict\" }", out, error, &base)); + CHECK(out.slLints == 0); + CHECK(out.slFatalLints == compound); + } } - template<> template<> - void alluauconfig_object::test<8>() + TEST_CASE("alluauconfig: a .config.luau's luau table reads as the .luaurc that says the same", "[alluauconfig]") { - set_test_name("a .config.luau is run and its luau table read as the .luaurc that says the same: aliases, mode, lints and globals, the studio's lints among them"); const char* const source = "local root = './lib'\n" "local lints = { LocalUnused = false, SlCompoundAssign = false }\n" @@ -239,122 +373,227 @@ namespace tut " other = { tool = true },\n" "}\n"; std::string json, error; - ensure("ran: " + error, ALLuauConfigScript::asLuaurc(source, json, error)); + UNSCOPED_INFO("ran: " + error); + REQUIRE(ALLuauConfigScript::asLuaurc(source, json, error)); ALLuauConfig config; - ensure("and parses as a .luaurc: " + error + " in " + json, ALLuauConfig::parse(json, config, error)); - ensure_equals("its mode", config.mode, std::string("strict")); - ensure("its aliases, by name in lower case", config.aliases.size() == 2 && config.aliases["lib"] == "./lib" && config.aliases["shared"] == "./lib/shared"); - ensure("its globals in order", config.globals == std::vector{ "Alchemy", "grid" }); - ensure("Luau's lint off", (config.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); - ensure("and the studio's", (config.slLints & ALScriptLintPass::bit("SlCompoundAssign")) == 0); - ensure("every lint an error", config.lintErrors); + UNSCOPED_INFO("and parses as a .luaurc: " + error + " in " + json); + REQUIRE(ALLuauConfig::parse(json, config, error)); + UNSCOPED_INFO("its mode"); + CHECK(config.mode == std::string("strict")); + { + INFO("its aliases, by name in lower case"); + REQUIRE(config.aliases.size() == 2); + CHECK(config.aliases["lib"] == "./lib"); + CHECK(config.aliases["shared"] == "./lib/shared"); + } + UNSCOPED_INFO("its globals in order"); + CHECK(config.globals == std::vector{ "Alchemy", "grid" }); + UNSCOPED_INFO("Luau's lint off"); + CHECK((config.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); + UNSCOPED_INFO("and the studio's"); + CHECK((config.slLints & ALScriptLintPass::bit("SlCompoundAssign")) == 0); + UNSCOPED_INFO("every lint an error"); + CHECK(config.lintErrors); - ensure("no luau table: a configuration that says nothing", ALLuauConfigScript::asLuaurc("return { other = 1 }", json, error) && json == "{}"); - ensure("an empty one the same", ALLuauConfigScript::asLuaurc("return { luau = {} }", json, error) && - ALLuauConfig::parse(json, config, error) && config.aliases.empty()); - ensure("no globals: an empty list", ALLuauConfigScript::asLuaurc("return { luau = { globals = {} } }", json, error) && - ALLuauConfig::parse(json, config, error) && config.globals.empty()); + { + INFO("no luau table: a configuration that says nothing"); + CHECK(ALLuauConfigScript::asLuaurc("return { other = 1 }", json, error)); + CHECK(json == "{}"); + } + { + INFO("an empty one the same"); + CHECK(ALLuauConfigScript::asLuaurc("return { luau = {} }", json, error)); + CHECK(ALLuauConfig::parse(json, config, error)); + REQUIRE(config.aliases.empty()); + } + { + INFO("no globals: an empty list"); + CHECK(ALLuauConfigScript::asLuaurc("return { luau = { globals = {} } }", json, error)); + CHECK(ALLuauConfig::parse(json, config, error)); + REQUIRE(config.globals.empty()); + } // A .luaurc's strings are taken byte for byte: a backslash is kept // as it is, and what one cannot hold is refused. - ensure("a backslash kept: " + error, ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'C:\\\\lua\\\\lib' } } }", json, error) && - ALLuauConfig::parse(json, config, error) && config.aliases["q"] == "C:\\lua\\lib"); - ensure("not a quote", !ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'a\"b' } } }", json, error) && - error.find("may not hold a quote") != std::string::npos); - ensure("nor a break", !ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'a\\nb' } } }", json, error)); - ensure("nor a backslash at the end", !ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'C:\\\\lua\\\\' } } }", json, error)); - ensure("nor in a name", !ALLuauConfigScript::asLuaurc("return { luau = { aliases = { ['a\"'] = 'x' } } }", json, error)); + { + INFO("a backslash kept: " + error); + CHECK(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'C:\\\\lua\\\\lib' } } }", json, error)); + CHECK(ALLuauConfig::parse(json, config, error)); + CHECK(config.aliases["q"] == "C:\\lua\\lib"); + } + { + INFO("not a quote"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'a\"b' } } }", json, error)); + REQUIRE(error.find("may not hold a quote") != std::string::npos); + } + UNSCOPED_INFO("nor a break"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'a\\nb' } } }", json, error)); + UNSCOPED_INFO("nor a backslash at the end"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'C:\\\\lua\\\\' } } }", json, error)); + UNSCOPED_INFO("nor in a name"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { ['a\"'] = 'x' } } }", json, error)); // Luau's own judgement of what the values say. - ensure("a mode that is none: Luau says so", ALLuauConfigScript::asLuaurc("return { luau = { languagemode = 'loose' } }", json, error) && - !ALLuauConfig::parse(json, config, error)); + { + INFO("a mode that is none: Luau says so"); + CHECK(ALLuauConfigScript::asLuaurc("return { luau = { languagemode = 'loose' } }", json, error)); + CHECK_FALSE(ALLuauConfig::parse(json, config, error)); + } } - template<> template<> - void alluauconfig_object::test<9>() + TEST_CASE("alluauconfig: a .config.luau that fails says why; and it cannot reach Eris", "[alluauconfig]") { - set_test_name("a .config.luau that fails says why: not compiling, an error, a yield, the wrong return, a value no configuration holds, too long, too much memory, " - "too much moved at once; and it cannot reach Eris"); std::string json, error; - ensure("does not compile", !ALLuauConfigScript::asLuaurc("return {", json, error) && !error.empty()); - ensure("an error, in its words", !ALLuauConfigScript::asLuaurc("error('no config here')", json, error) && error.find("no config here") != std::string::npos); - ensure_equals("a yield", (ALLuauConfigScript::asLuaurc("coroutine.yield()", json, error), error), std::string("configuration execution cannot yield")); - ensure_equals("nothing returned", (ALLuauConfigScript::asLuaurc("local x = 1", json, error), error), std::string("configuration must return exactly one value")); - ensure_equals("two returned", (ALLuauConfigScript::asLuaurc("return {}, {}", json, error), error), std::string("configuration must return exactly one value")); - ensure_equals("not a table", (ALLuauConfigScript::asLuaurc("return 'x'", json, error), error), std::string("configuration did not return a table")); - ensure_equals("luau not a table", (ALLuauConfigScript::asLuaurc("return { luau = 3 }", json, error), error), + { + INFO("does not compile"); + REQUIRE_FALSE(ALLuauConfigScript::asLuaurc("return {", json, error)); + REQUIRE_FALSE(error.empty()); + } + { + INFO("an error, in its words"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("error('no config here')", json, error)); + REQUIRE(error.find("no config here") != std::string::npos); + } + UNSCOPED_INFO("a yield"); + CHECK((ALLuauConfigScript::asLuaurc("coroutine.yield()", json, error), error) == std::string("configuration execution cannot yield")); + UNSCOPED_INFO("nothing returned"); + CHECK((ALLuauConfigScript::asLuaurc("local x = 1", json, error), error) == std::string("configuration must return exactly one value")); + UNSCOPED_INFO("two returned"); + CHECK((ALLuauConfigScript::asLuaurc("return {}, {}", json, error), error) == std::string("configuration must return exactly one value")); + UNSCOPED_INFO("not a table"); + CHECK((ALLuauConfigScript::asLuaurc("return 'x'", json, error), error) == std::string("configuration did not return a table")); + UNSCOPED_INFO("luau not a table"); + CHECK((ALLuauConfigScript::asLuaurc("return { luau = 3 }", json, error), error) == std::string("configuration value for key \"luau\" must be a table")); - ensure("a function where a value goes", !ALLuauConfigScript::asLuaurc("return { luau = { aliases = { x = print } } }", json, error) && - error.find("must be strings, booleans") != std::string::npos); + { + INFO("a function where a value goes"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { x = print } } }", json, error)); + REQUIRE(error.find("must be strings, booleans") != std::string::npos); + } // A .luaurc holds no numbers, and Luau's reading of one would stop // at it saying nothing of where: refused here, by its key. - ensure_equals("a number, by its key", (ALLuauConfigScript::asLuaurc("return { luau = { lint = { LocalUnused = 0 } } }", json, error), error), + UNSCOPED_INFO("a number, by its key"); + CHECK((ALLuauConfigScript::asLuaurc("return { luau = { lint = { LocalUnused = 0 } } }", json, error), error) == std::string("configuration value for key \"LocalUnused\" must be a string or a boolean, not a number")); - ensure("in an array, by its place", !ALLuauConfigScript::asLuaurc("return { luau = { globals = { 'a', 2 } } }", json, error) && - error.find("\"globals[2]\"") != std::string::npos); - ensure("at the top", !ALLuauConfigScript::asLuaurc("return { luau = { linterrors = 1 } }", json, error) && - error.find("\"linterrors\"") != std::string::npos); - ensure("an array with a hole", !ALLuauConfigScript::asLuaurc("return { luau = { globals = { [1] = 'a', [3] = 'b' } } }", json, error) && - error.find("invalid numeric key") != std::string::npos); - ensure("a table in itself, nesting forever", !ALLuauConfigScript::asLuaurc("local t = {} t.t = t return { luau = { aliases = t } }", json, error) && - error.find("nest too deeply") != std::string::npos); + { + INFO("in an array, by its place"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { globals = { 'a', 2 } } }", json, error)); + REQUIRE(error.find("\"globals[2]\"") != std::string::npos); + } + { + INFO("at the top"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { linterrors = 1 } }", json, error)); + REQUIRE(error.find("\"linterrors\"") != std::string::npos); + } + { + INFO("an array with a hole"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { globals = { [1] = 'a', [3] = 'b' } } }", json, error)); + REQUIRE(error.find("invalid numeric key") != std::string::npos); + } + { + INFO("a table in itself, nesting forever"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("local t = {} t.t = t return { luau = { aliases = t } }", json, error)); + REQUIRE(error.find("nest too deeply") != std::string::npos); + } // No metamethod runs as what it returned is read. - ensure("an __index that would loop is never asked", - ALLuauConfigScript::asLuaurc("return setmetatable({}, { __index = function() while true do end end })", json, error) && json == "{}"); + { + INFO("an __index that would loop is never asked"); + CHECK(ALLuauConfigScript::asLuaurc("return setmetatable({}, { __index = function() while true do end end })", json, error)); + CHECK(json == "{}"); + } // A run stopped by the deadline is kept a while, by its text: a text // of this run's own is run, not answered from what another test, or // this one before, asked. const std::string forever = "while true do end -- " + LLUUID::generateNewID().asString(); const auto started = std::chrono::steady_clock::now(); - ensure_equals("a loop that never ends, stopped", (ALLuauConfigScript::asLuaurc(forever, json, error), error), + UNSCOPED_INFO("a loop that never ends, stopped"); + CHECK((ALLuauConfigScript::asLuaurc(forever, json, error), error) == std::string("configuration execution timed out")); const F64 took = std::chrono::duration(std::chrono::steady_clock::now() - started).count(); - ensure(llformat("stopped at its time, not long after: %.2f s", took), took >= ALLuauConfigScript::MOST_SECONDS && took < ALLuauConfigScript::MOST_SECONDS + 2.0); - ensure("asked again at once, kept for a while: no second wait", (ALLuauConfigScript::asLuaurc(forever, json, error), + { + INFO(llformat("stopped at its time, not long after: %.2f s", took)); + CHECK(took >= ALLuauConfigScript::MOST_SECONDS); + CHECK(took < ALLuauConfigScript::MOST_SECONDS + 2.0); + } + UNSCOPED_INFO("asked again at once, kept for a while: no second wait"); + CHECK((ALLuauConfigScript::asLuaurc(forever, json, error), std::chrono::duration(std::chrono::steady_clock::now() - started).count() < took + 0.1)); // table.move's copy is a loop in C the deadline does not reach, and // nils moved into a table that holds none take no memory: held to so // many elements, at once. const auto moving = std::chrono::steady_clock::now(); - ensure("two billion nils moved, refused at once: " + error, - !ALLuauConfigScript::asLuaurc("table.move({}, 1, 2147483646, 2) return {}", json, error) && error.find("table.move") != std::string::npos && - std::chrono::duration(std::chrono::steady_clock::now() - moving).count() < ALLuauConfigScript::MOST_SECONDS); - ensure("a move within it, as Luau's: " + error, - ALLuauConfigScript::asLuaurc("return { luau = { globals = table.move({ 'a', 'b' }, 1, 2, 2, { 'z' }) } }", json, error) && - json == "{\"globals\": [\"z\", \"a\", \"b\"]}"); - ensure("memory past the most, refused", - !ALLuauConfigScript::asLuaurc("local t = {} for i = 1, 1e7 do t[i] = string.rep('x', 1000) .. i end return t", json, error) && - error.find("memory") != std::string::npos); - ensure("one string past it at once, refused", !ALLuauConfigScript::asLuaurc("return { luau = { x = string.rep('x', 64 * 1024 * 1024) } }", json, error) && - error.find("memory") != std::string::npos); + { + INFO("two billion nils moved, refused at once: " + error); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("table.move({}, 1, 2147483646, 2) return {}", json, error)); + 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: " + error); + CHECK(ALLuauConfigScript::asLuaurc("return { luau = { globals = table.move({ 'a', 'b' }, 1, 2, 2, { 'z' }) } }", json, error)); + CHECK(json == "{\"globals\": [\"z\", \"a\", \"b\"]}"); + } + { + INFO("memory past the most, refused"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("local t = {} for i = 1, 1e7 do t[i] = string.rep('x', 1000) .. i end return t", json, error)); + REQUIRE(error.find("memory") != std::string::npos); + } + { + INFO("one string past it at once, refused"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { x = string.rep('x', 64 * 1024 * 1024) } }", json, error)); + REQUIRE(error.find("memory") != std::string::npos); + } // Second Life's VM opens Eris as `ares`; a configuration is run // without it. The rest of Luau's libraries are there. - ensure("no ares: " + error, ALLuauConfigScript::asLuaurc("return { luau = { languagemode = ares and 'strict' or 'nonstrict' } }", json, error) && - json.find("nonstrict") != std::string::npos); - ensure("Luau's libraries: " + error, - ALLuauConfigScript::asLuaurc("return { luau = { globals = { string.upper('a'), tostring(math.max(1, 2)), table.concat({ 'x', 'y' }), buffer and 'b' or 'none' } } }", - json, error) && - json == "{\"globals\": [\"A\", \"2\", \"xy\", \"b\"]}"); + { + INFO("no ares: " + error); + CHECK(ALLuauConfigScript::asLuaurc("return { luau = { languagemode = ares and 'strict' or 'nonstrict' } }", json, error)); + REQUIRE(json.find("nonstrict") != std::string::npos); + } + { + INFO("Luau's libraries: " + error); + 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)); + CHECK(json == "{\"globals\": [\"A\", \"2\", \"xy\", \"b\"]}"); + } } - template<> template<> - void alluauconfig_object::test<10>() + TEST_CASE("alluauconfig: a .config.luau's run leaves no JSON on failure and no error on success", "[alluauconfig]") { - set_test_name("a .config.luau's run says only what it came to: no JSON with a failure, begun or not, no error with a success, run or kept"); const char* const failing = "return { luau = { languagemode = 'strict', lint = { LocalUnused = 0 } } }"; std::string json = "from before", error; - ensure("a failure part way through the writing", !ALLuauConfigScript::asLuaurc(failing, json, error) && !error.empty()); - ensure_equals("leaves no JSON", json, std::string()); + { + INFO("a failure part way through the writing"); + REQUIRE_FALSE(ALLuauConfigScript::asLuaurc(failing, json, error)); + REQUIRE_FALSE(error.empty()); + } + UNSCOPED_INFO("leaves no JSON"); + CHECK(json == std::string()); json = "from before"; - ensure("asked again, kept", !ALLuauConfigScript::asLuaurc(failing, json, error) && !error.empty()); - ensure_equals("and no JSON still", json, std::string()); + { + INFO("asked again, kept"); + CHECK_FALSE(ALLuauConfigScript::asLuaurc(failing, json, error)); + REQUIRE_FALSE(error.empty()); + } + UNSCOPED_INFO("and no JSON still"); + CHECK(json == std::string()); const char* const working = "return { luau = { languagemode = 'strict' } }"; error = "from before"; - ensure("a success: " + json, ALLuauConfigScript::asLuaurc(working, json, error) && json == "{\"languageMode\": \"strict\"}"); - ensure_equals("leaves no error", error, std::string()); + { + INFO("a success: " + json); + CHECK(ALLuauConfigScript::asLuaurc(working, json, error)); + CHECK(json == "{\"languageMode\": \"strict\"}"); + } + UNSCOPED_INFO("leaves no error"); + CHECK(error == std::string()); error = "from before"; - ensure("asked again, kept", ALLuauConfigScript::asLuaurc(working, json, error) && json == "{\"languageMode\": \"strict\"}"); - ensure_equals("and no error still", error, std::string()); + { + INFO("asked again, kept"); + CHECK(ALLuauConfigScript::asLuaurc(working, json, error)); + CHECK(json == "{\"languageMode\": \"strict\"}"); + } + UNSCOPED_INFO("and no error still"); + CHECK(error == std::string()); } } diff --git a/indra/alscript/tests/alluaufragment_test.cpp b/indra/alscript/tests/alluaufragment_test.cpp index e20d55976d..819673e815 100644 --- a/indra/alscript/tests/alluaufragment_test.cpp +++ b/indra/alscript/tests/alluaufragment_test.cpp @@ -29,7 +29,7 @@ #include "../lsl/allsltoslua.h" #include "../preprocessor/alpreprocessor.h" -#include "../test/lltut.h" +#include #include "llsdserialize.h" #include "Luau/Allocator.h" @@ -55,7 +55,7 @@ // warm job (ALLuauService::warm). Where the fragment declines, the whole // script answers both, and the two agree by construction; what is compared // is where it answers. -namespace tut +namespace { struct alluaufragment_data { @@ -500,26 +500,28 @@ namespace tut std::printf("alluaufragment, %s solver, %s: %zu places, %zu answered over a fragment; told apart by a type beside a name %zu, " "by which fit %zu, by a few names %zu, by a signature's parameters %zu\n", newSolver ? "new" : "old", corpus, tally.asked, tally.answered, tally.types, tally.fits, tally.few, tally.generics); - ensure(std::string(corpus) + ": the fragment answers as the whole script does:\n" + tally.faults, tally.faults.empty()); + UNSCOPED_INFO(std::string(corpus) + ": the fragment answers as the whole script does:\n" + tally.faults); + REQUIRE(tally.faults.empty()); if (answers) { - ensure(std::string(corpus) + ": most places answered over a fragment", tally.answered * 2 > tally.asked); + UNSCOPED_INFO(std::string(corpus) + ": most places answered over a fragment"); + REQUIRE(tally.answered * 2 > tally.asked); } } }; - typedef test_group alluaufragment_group; - typedef alluaufragment_group::object alluaufragment_object; - alluaufragment_group alluaufragment_instance("alluaufragment"); - - template<> template<> - void alluaufragment_object::test<1>() + TEST_CASE_METHOD(alluaufragment_data, "alluaufragment: over the studio's templates the fragment answers as the whole script does", "[alluaufragment]") { - set_test_name("over the studio's templates, the fragment answers as the whole script does"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); llifstream in(std::string(AL_SCRIPT_TEMPLATES_DIR) + "/slua.xml", std::ios::in | std::ios::binary); LLSD templates; - ensure("read", in.is_open() && LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE && templates.isArray()); + { + INFO("read"); + CHECK(in.is_open()); + CHECK(LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE); + REQUIRE(templates.isArray()); + } Tally tally; for (LLSD::array_const_iterator it = templates.beginArray(); it != templates.endArray(); ++it) { @@ -528,11 +530,10 @@ namespace tut report("the templates", tally); } - template<> template<> - void alluaufragment_object::test<2>() + TEST_CASE_METHOD(alluaufragment_data, "alluaufragment: over Tailslide's scripts as SLua the fragment answers as the whole does", "[alluaufragment]") { - set_test_name("over Tailslide's test scripts written again as SLua, the fragment answers as the whole script does"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string dir = std::string(AL_ALSCRIPT_TEST_DIR) + "/tailslide/scripts"; std::vector files; for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) @@ -543,11 +544,13 @@ namespace tut } } std::sort(files.begin(), files.end()); - ensure("the scripts: " + dir, files.size() > 100); + UNSCOPED_INFO("the scripts: " + dir); + REQUIRE(files.size() > 100); // The converter reads Tailslide's table of LSL's builtins, which the // LSL service fills for the whole process. ALLSLService lsl; - ensure("builtins loaded: " + error, 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)); Tally tally; for (const std::string& name : files) { @@ -560,11 +563,10 @@ namespace tut report("Tailslide's scripts", tally); } - template<> template<> - void alluaufragment_object::test<3>() + TEST_CASE_METHOD(alluaufragment_data, "alluaufragment: over an expanded script with modules the fragment answers as the whole does", "[alluaufragment]") { - set_test_name("over a script the preprocessor expanded, its requires apart as the studio checks them, the fragment answers as the whole script does, a module changed since the last check or not"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); namespace fs = std::filesystem; const fs::path dir = fs::path(AL_ALSCRIPT_TEST_DIR) / "preprocessor"; ALPreprocessor::Options options; @@ -584,7 +586,11 @@ namespace tut return ALPreprocessor::Found::Yes; }; const ALPreprocessor::Result made = ALPreprocessor::run(readFile((dir / "test_require.luau").string()), options); - ensure("expanded", made.problems.empty() && !made.apart.script.text.empty()); + { + INFO("expanded"); + REQUIRE(made.problems.empty()); + REQUIRE_FALSE(made.apart.script.text.empty()); + } ALLuauService::Modules modules; for (const ALPreprocessor::Result::Piece& piece : made.apart.modules) { @@ -594,7 +600,8 @@ namespace tut { modules.reaches.push_back({ resolved.from, resolved.name, resolved.path }); } - ensure("its modules", modules.modules.size() == 2); + UNSCOPED_INFO("its modules"); + REQUIRE(modules.modules.size() == 2); fragment.setModules(modules); whole.setModules(modules); Tally tally; @@ -621,16 +628,16 @@ namespace tut ask("test_require, util changed", { probe }, changed, /*settled*/ true); } report("an expanded script, a module changed since", changed, false); - ensure_equals("a module changed since: every place asked of the whole script", changed.answered, size_t(0)); + UNSCOPED_INFO("a module changed since: every place asked of the whole script"); + CHECK(changed.answered == size_t(0)); fragment.setModules({}); whole.setModules({}); } - template<> template<> - void alluaufragment_object::test<4>() + TEST_CASE_METHOD(alluaufragment_data, "alluaufragment: a line opened above the statement typed makes the whole script answer", "[alluaufragment]") { - set_test_name("a line opened above the statement being typed since the last check: Luau would start the fragment where the text has no such place, and the whole script answers instead"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); // Return pressed at the start of `print(a)`, then `.` typed in it // before another check: the last check's statement began at the // fifth column of a line that is empty now. @@ -642,15 +649,16 @@ namespace tut fragment.warm(base); const size_t before = fragment.fragmentsChecked(); const Told told = judged(fragment.complete(text, 3, 12), whole.complete(text, 3, 12)); - ensure_equals("asked of the whole script", fragment.fragmentsChecked(), before); - ensure("and answered as it is:" + told.faults, told.faults.empty()); + UNSCOPED_INFO("asked of the whole script"); + CHECK(fragment.fragmentsChecked() == before); + UNSCOPED_INFO("and answered as it is:" + told.faults); + REQUIRE(told.faults.empty()); } - template<> template<> - void alluaufragment_object::test<5>() + TEST_CASE_METHOD(alluaufragment_data, "alluaufragment: a handler stub is offered at an overloaded callee's argument but not within it", "[alluaufragment]") { - set_test_name("an overloaded callee's handler is offered written out where its argument is being written, and not in a field of one, a table in one or a type asserted of one: over a fragment as over the whole script"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const auto stubbed = [](const std::vector& found) { return std::any_of(found.begin(), found.end(), [](const ALScriptCompletion& c) { return !c.snippet.empty(); }); }; @@ -677,16 +685,17 @@ namespace tut const std::string text = base + place.line + "\n"; fragment.check(base); fragment.warm(base); - ensure_equals(std::string(place.line) + ": over a fragment", stubbed(fragment.complete(text, 1, place.column)), place.offered); - ensure_equals(std::string(place.line) + ": over the whole script", stubbed(whole.complete(text, 1, place.column)), place.offered); + UNSCOPED_INFO(std::string(place.line) + ": over a fragment"); + REQUIRE(stubbed(fragment.complete(text, 1, place.column)) == place.offered); + UNSCOPED_INFO(std::string(place.line) + ": over the whole script"); + REQUIRE(stubbed(whole.complete(text, 1, place.column)) == place.offered); } } - template<> template<> - void alluaufragment_object::test<6>() + TEST_CASE_METHOD(alluaufragment_data, "alluaufragment: a local declared again in one scope is the one in effect over a fragment", "[alluaufragment]") { - set_test_name("a local declared again in its own scope is the declaration in effect, offered and called, over a fragment as over the whole script, wherever the parser put each"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); // `test` a function, then a number in the same scope, and a line // typed below since the last check: the name alone, and a third // `test` initialized with it. Luau keeps both declarations among the @@ -736,8 +745,11 @@ namespace tut ask(above + place.checked, place.typed, [&](ALLuauService& service, const std::string& text, S32 line, const char* over) { const std::vector found = service.complete(text, line, place.column); const auto test = std::find_if(found.begin(), found.end(), [](const ALScriptCompletion& c) { return c.text == "test"; }); - ensure(llformat("round %d, %s, over %s: the number", round, place.typed, over), - test != found.end() && test->kind == ALScriptSymbolKind::Variable); + { + INFO(llformat("round %d, %s, over %s: the number", round, place.typed, over)); + REQUIRE(test != found.end()); + REQUIRE(test->kind == ALScriptSymbolKind::Variable); + } }); } // A function of two strings declared over one of a number: a @@ -745,10 +757,14 @@ namespace tut ask(above + "local function call(n: number)\nend\nlocal call = function(s: string, t: string)\nend\n", "call(\"x\", \"y\")", [&](ALLuauService& service, const std::string& text, S32 line, const char* over) { const ALScriptSignature found = service.signature(text, line, 5); - ensure(llformat("round %d, a call, over %s: the two strings, not %s", round, over, found.label.c_str()), - found.found && found.parameters.size() == 2); + { + INFO(llformat("round %d, a call, over %s: the two strings, not %s", round, over, found.label.c_str())); + REQUIRE(found.found); + REQUIRE(found.parameters.size() == 2); + } }); } - ensure("answered over a fragment", answered * 2 > asked); + UNSCOPED_INFO("answered over a fragment"); + CHECK(answered * 2 > asked); } } diff --git a/indra/alscript/tests/alluaunavigation_test.cpp b/indra/alscript/tests/alluaunavigation_test.cpp index 2b79ceef10..0ebdf683b9 100644 --- a/indra/alscript/tests/alluaunavigation_test.cpp +++ b/indra/alscript/tests/alluaunavigation_test.cpp @@ -27,7 +27,7 @@ #include "../luau/alluauservice.h" #include "../luau/alluauconfig.h" -#include "../test/lltut.h" +#include #include @@ -94,7 +94,7 @@ namespace } } -namespace tut +namespace { struct alluaunavigation_data { @@ -133,122 +133,178 @@ namespace tut } }; - typedef test_group alluaunavigation_group; - typedef alluaunavigation_group::object alluaunavigation_object; - alluaunavigation_group alluaunavigation_instance("alluaunavigation"); - - template<> template<> - void alluaunavigation_object::test<1>() + TEST_CASE_METHOD(alluaunavigation_data, "alluaunavigation: hover and references place a module's field in the module's own lines", "[alluaunavigation]") { - set_test_name("a field a module declares is declared there: hover and references say the module's key and its place in the module's lines"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); open("a", true); ALScriptReferences refs = service.references(SCRIPT_A, 5, 12); // `twice` in `util.twice(2)` - ensure("found", refs.found && refs.name == "twice"); - ensure(llformat("a function, not kind %d", (int)refs.kind), refs.kind == ALScriptSymbolKind::Function); - ensure_equals("declared in the module", refs.definitionFile, std::string(UTIL)); - ensure_equals("at `function M.twice`", where(refs.definition), std::string("3:11")); - ensure("the module's to rename", refs.hasDefinition && refs.renamable); + { + INFO("found"); + REQUIRE(refs.found); + CHECK(refs.name == "twice"); + } + UNSCOPED_INFO(llformat("a function, not kind %d", (int)refs.kind)); + CHECK(refs.kind == ALScriptSymbolKind::Function); + UNSCOPED_INFO("declared in the module"); + CHECK(refs.definitionFile == std::string(UTIL)); + UNSCOPED_INFO("at `function M.twice`"); + CHECK(where(refs.definition) == std::string("3:11")); + { + INFO("the module's to rename"); + CHECK(refs.hasDefinition); + CHECK(refs.renamable); + } const ALScriptHover hover = service.hover(SCRIPT_A, 5, 12); - ensure("hover: the module's", hover.found && hover.hasDefinition && hover.definitionFile == UTIL); - ensure_equals("hover: the same place", llformat("%d:%d", hover.definitionLine, hover.definitionColumn), std::string("3:11")); + { + INFO("hover: the module's"); + REQUIRE(hover.found); + CHECK(hover.hasDefinition); + CHECK(hover.definitionFile == UTIL); + } + UNSCOPED_INFO("hover: the same place"); + CHECK(llformat("%d:%d", hover.definitionLine, hover.definitionColumn) == std::string("3:11")); refs = service.references(SCRIPT_A, 6, 12); // `limit`, assigned - ensure_equals("a field assigned: declared where it is assigned", refs.definitionFile + "@" + where(refs.definition), + UNSCOPED_INFO("a field assigned: declared where it is assigned"); + CHECK(refs.definitionFile + "@" + where(refs.definition) == std::string(UTIL) + "@6:2"); refs = service.references(SCRIPT_A, 5, 29); // `double`, a module that requires the other - ensure_equals("each module its own key", refs.definitionFile + "@" + where(refs.definition), std::string(SHAPES) + "@2:11"); + UNSCOPED_INFO("each module its own key"); + CHECK(refs.definitionFile + "@" + where(refs.definition) == std::string(SHAPES) + "@2:11"); const ALScriptHover local = service.hover(SCRIPT_A, 5, 7); // `util` itself - ensure("a local the script binds: the script's", local.hasDefinition && local.definitionFile.empty() && local.definitionLine == 1); + { + INFO("a local the script binds: the script's"); + CHECK(local.hasDefinition); + REQUIRE(local.definitionFile.empty()); + CHECK(local.definitionLine == 1); + } const ALScriptHover say = service.hover("ll.Say(0, \"x\")\n", 0, 4); - ensure("what the definitions declare: nowhere to go", say.found && !say.hasDefinition); + { + INFO("what the definitions declare: nowhere to go"); + REQUIRE(say.found); + CHECK_FALSE(say.hasDefinition); + } service.setDocument(""); } - template<> template<> - void alluaunavigation_object::test<2>() + TEST_CASE_METHOD(alluaunavigation_data, "alluaunavigation: a module's exported type is found through its local and declared there", "[alluaunavigation]") { - set_test_name("a type a module exports is declared there, found through the local the module was required as; its uses in the module too"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); open("a", true); const ALScriptReferences refs = service.references(SCRIPT_A, 4, 15); // `Point` in `util.Point` - ensure("found", refs.found && refs.name == "Point"); - ensure("a type", refs.kind == ALScriptSymbolKind::Type); - ensure_equals("declared in the module", refs.definitionFile + "@" + where(refs.definition), std::string(UTIL) + "@2:12"); - ensure_equals("each place: the script's, the module's", placesOf(refs), std::string("4:14 ") + UTIL + "@2:12 " + UTIL + "@7:21"); - ensure("renamable", refs.renamable); + { + INFO("found"); + REQUIRE(refs.found); + CHECK(refs.name == "Point"); + } + UNSCOPED_INFO("a type"); + CHECK(refs.kind == ALScriptSymbolKind::Type); + UNSCOPED_INFO("declared in the module"); + CHECK(refs.definitionFile + "@" + where(refs.definition) == std::string(UTIL) + "@2:12"); + UNSCOPED_INFO("each place: the script's, the module's"); + CHECK(placesOf(refs) == std::string("4:14 ") + UTIL + "@2:12 " + UTIL + "@7:21"); + UNSCOPED_INFO("renamable"); + CHECK(refs.renamable); // The local the module was required as is named in a type's prefix // too, which a rename of it renames. const ALScriptReferences local = service.references(SCRIPT_A, 1, 7); - ensure_equals("the local, the prefix among its places", placesOf(local), std::string("1:6 4:9 4:22 5:6 6:6")); + UNSCOPED_INFO("the local, the prefix among its places"); + CHECK(placesOf(local) == std::string("1:6 4:9 4:22 5:6 6:6")); service.setDocument(""); } - template<> template<> - void alluaunavigation_object::test<3>() + TEST_CASE_METHOD(alluaunavigation_data, "alluaunavigation: a module's field is found everywhere it stands but not another table's", "[alluaunavigation]") { - set_test_name("a module's field is found wherever it stands, in the script and every module it requires; a field of the same name on another table is left alone"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); open("a", true); ALScriptReferences refs = service.references(SCRIPT_A, 5, 12); - ensure_equals("the script's, the module's own, and the other module's", placesOf(refs), + UNSCOPED_INFO("the script's, the module's own, and the other module's"); + CHECK(placesOf(refs) == std::string("5:11 ") + UTIL + "@3:11 " + SHAPES + "@3:16"); refs = service.references(SCRIPT_A, 6, 12); - ensure_equals("an assigned one the same", placesOf(refs), std::string("6:11 ") + UTIL + "@6:2 " + SHAPES + "@3:32"); + UNSCOPED_INFO("an assigned one the same"); + CHECK(placesOf(refs) == std::string("6:11 ") + UTIL + "@6:2 " + SHAPES + "@3:32"); // The script's own table's field of the same name: the script's alone. refs = service.references(SCRIPT_A, 5, 46); - ensure("found", refs.found && refs.name == "twice" && refs.definitionFile.empty()); - ensure_equals("declared in the script's constructor", where(refs.definition), std::string("3:16")); - ensure_equals("and used once; none of the module's", placesOf(refs), std::string("3:16 5:45")); + { + INFO("found"); + REQUIRE(refs.found); + CHECK(refs.name == "twice"); + REQUIRE(refs.definitionFile.empty()); + } + UNSCOPED_INFO("declared in the script's constructor"); + CHECK(where(refs.definition) == std::string("3:16")); + UNSCOPED_INFO("and used once; none of the module's"); + CHECK(placesOf(refs) == std::string("3:16 5:45")); // Asked at the declaration in the constructor: the same. - ensure_equals("from the declaration", placesOf(service.references(SCRIPT_A, 3, 17)), std::string("3:16 5:45")); + UNSCOPED_INFO("from the declaration"); + CHECK(placesOf(service.references(SCRIPT_A, 3, 17)) == std::string("3:16 5:45")); service.setDocument(""); } - template<> template<> - void alluaunavigation_object::test<4>() + TEST_CASE_METHOD(alluaunavigation_data, "alluaunavigation: scripts sharing a module find their places and the module's from either end", "[alluaunavigation]") { - set_test_name("two scripts requiring one module: each finds its own places and the module's; and asked at the module's declaration, as a lookup through another script asks, the script's places through the require"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); open("a", true); const ALScriptReferences a = service.references(SCRIPT_A, 5, 12); open("b", false); const ALScriptReferences b = service.references(SCRIPT_B, 1, 12); - ensure_equals("the other script's: its own and the module's", placesOf(b), std::string("1:11 ") + UTIL + "@3:11"); - ensure_equals("the same declaration", b.definitionFile + "@" + where(b.definition), a.definitionFile + "@" + where(a.definition)); + UNSCOPED_INFO("the other script's: its own and the module's"); + CHECK(placesOf(b) == std::string("1:11 ") + UTIL + "@3:11"); + UNSCOPED_INFO("the same declaration"); + CHECK(b.definitionFile + "@" + where(b.definition) == a.definitionFile + "@" + where(a.definition)); // What the lookup across scripts asks of each: the script read // apart with its modules, as the checker reads it, asked at the // module's declaration in the module's own lines. open("lookup", false); const ALScriptReferences from_module = service.references(SCRIPT_B, 3, 12, UTIL); // `twice` in `function M.twice` - ensure("found from the module", from_module.found && from_module.name == "twice" && from_module.definitionFile == UTIL); - ensure_equals("the script's use, through the require, and the module's declaration", placesOf(from_module), + { + INFO("found from the module"); + REQUIRE(from_module.found); + CHECK(from_module.name == "twice"); + CHECK(from_module.definitionFile == UTIL); + } + UNSCOPED_INFO("the script's use, through the require, and the module's declaration"); + CHECK(placesOf(from_module) == std::string("1:11 ") + UTIL + "@3:11"); // A module's local is its own alone. const ALScriptReferences local = service.references(SCRIPT_B, 1, 6, UTIL); // `M` in `local M = {}` - ensure("found", local.found && local.name == "M"); - ensure_equals("the module's places alone", placesOf(local), + { + INFO("found"); + REQUIRE(local.found); + CHECK(local.name == "M"); + } + UNSCOPED_INFO("the module's places alone"); + CHECK(placesOf(local) == std::string(UTIL) + "@1:6 " + UTIL + "@3:9 " + UTIL + "@6:0 " + UTIL + "@7:9 " + UTIL + "@10:7"); service.setDocument(""); } - template<> template<> - void alluaunavigation_object::test<5>() + TEST_CASE_METHOD(alluaunavigation_data, "alluaunavigation: hover on a type's name says it as declared and where it was declared", "[alluaunavigation]") { - set_test_name("a hover on a type's name says the type as its declaration reads, and where it was declared: a module's through its local, the script's own, a generic one, a built-in one"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); open("a", true); ALScriptHover hover = service.hover(SCRIPT_A, 4, 15); // `Point` in `util.Point` - ensure("found", hover.found); - ensure("the module's, by its name and fields: " + hover.label, - hover.label.rfind("type util.Point = ", 0) == 0 && hover.label.find("x: number") != std::string::npos); - ensure_equals("declared in the module", hover.definitionFile + "@" + llformat("%d:%d", hover.definitionLine, hover.definitionColumn), + UNSCOPED_INFO("found"); + REQUIRE(hover.found); + { + INFO("the module's, by its name and fields: " + hover.label); + CHECK(hover.label.rfind("type util.Point = ", 0) == 0); + REQUIRE(hover.label.find("x: number") != std::string::npos); + } + UNSCOPED_INFO("declared in the module"); + CHECK(hover.definitionFile + "@" + llformat("%d:%d", hover.definitionLine, hover.definitionColumn) == std::string(UTIL) + "@2:12"); const std::string own = "--!strict\n" // 0 @@ -257,45 +313,68 @@ namespace tut "local n: number = p.first\n"; // 3 open("own", false); hover = service.hover(own, 2, 10); // `Pair` - ensure("the script's own, generic: " + hover.label, hover.found && hover.label.rfind("type Pair = ", 0) == 0 && - hover.label.find("first: T") != std::string::npos); - ensure("declared in the script", hover.hasDefinition && hover.definitionFile.empty() && hover.definitionLine == 1); + { + INFO("the script's own, generic: " + hover.label); + REQUIRE(hover.found); + CHECK(hover.label.rfind("type Pair = ", 0) == 0); + REQUIRE(hover.label.find("first: T") != std::string::npos); + } + { + INFO("declared in the script"); + CHECK(hover.hasDefinition); + REQUIRE(hover.definitionFile.empty()); + CHECK(hover.definitionLine == 1); + } hover = service.hover(own, 3, 10); // `number` - ensure_equals("a built-in one, only itself", hover.label, std::string("type number")); + UNSCOPED_INFO("a built-in one, only itself"); + CHECK(hover.label == std::string("type number")); hover = service.hover(own, 3, 6); // `n`, the local, as ever - ensure("a local still a local: " + hover.label, hover.label.rfind("local n", 0) == 0); + UNSCOPED_INFO("a local still a local: " + hover.label); + CHECK(hover.label.rfind("local n", 0) == 0); service.setDocument(""); } - template<> template<> - void alluaunavigation_object::test<6>() + TEST_CASE_METHOD(alluaunavigation_data, "alluaunavigation: a field is found by a string key in brackets too with its quotes left out", "[alluaunavigation]") { - set_test_name("a field is found by a string in brackets too -- an index, a constructor's key -- each place its name alone, quotes aside, which is what a rename replaces"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string text = "--!strict\n" // 0 "local t = { size = 1, [\"name\"] = \"a\" }\n" // 1 "t[\"size\"] = t.size + 1\n" // 2 "print(t.name, t[\"name\"], t['size'])\n"; // 3 open("keys", false); ALScriptReferences refs = service.references(text, 2, 4); // `size` in `t["size"]` - ensure("found", refs.found && refs.name == "size"); - ensure_equals("each place: the record's key, the strings' insides, the name", placesOf(refs), std::string("1:12 2:3 2:14 3:28")); - ensure_equals("declared by the record", where(refs.definition), std::string("1:12")); - ensure("renamable", refs.hasDefinition && refs.renamable); + { + INFO("found"); + REQUIRE(refs.found); + CHECK(refs.name == "size"); + } + UNSCOPED_INFO("each place: the record's key, the strings' insides, the name"); + CHECK(placesOf(refs) == std::string("1:12 2:3 2:14 3:28")); + UNSCOPED_INFO("declared by the record"); + CHECK(where(refs.definition) == std::string("1:12")); + { + INFO("renamable"); + CHECK(refs.hasDefinition); + CHECK(refs.renamable); + } refs = service.references(text, 3, 9); // `name` in `t.name` - ensure_equals("a key in brackets declares it", placesOf(refs), std::string("1:24 3:8 3:17")); - ensure_equals("there, inside its quotes", where(refs.definition), std::string("1:24")); - ensure("renamable", refs.renamable); - ensure_equals("asked at that key: the same", placesOf(service.references(text, 1, 25)), std::string("1:24 3:8 3:17")); + UNSCOPED_INFO("a key in brackets declares it"); + CHECK(placesOf(refs) == std::string("1:24 3:8 3:17")); + UNSCOPED_INFO("there, inside its quotes"); + CHECK(where(refs.definition) == std::string("1:24")); + UNSCOPED_INFO("renamable"); + CHECK(refs.renamable); + UNSCOPED_INFO("asked at that key: the same"); + CHECK(placesOf(service.references(text, 1, 25)) == std::string("1:24 3:8 3:17")); service.setDocument(""); } - template<> template<> - void alluaunavigation_object::test<7>() + TEST_CASE_METHOD(alluaunavigation_data, "alluaunavigation: a method is its class's through __index; a function __index stops a rename", "[alluaunavigation]") { - set_test_name("an object's method is its class's, through its metatable's __index, whichever end it is asked at; one reached some way not followed leaves the field not to rename"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string account = "--!strict\n" // 0 "local Account = {}\n" // 1 "Account.__index = Account\n" // 2 @@ -309,12 +388,23 @@ namespace tut "print(a:deposit(5))\n"; // 10 open("account", false); ALScriptReferences refs = service.references(account, 6, 18); // `deposit` in `function Account:deposit` - ensure("found", refs.found && refs.name == "deposit"); - ensure_equals("the declaration and the object's call", placesOf(refs), std::string("6:17 10:8")); - ensure("renamable", refs.hasDefinition && refs.renamable); + { + INFO("found"); + REQUIRE(refs.found); + CHECK(refs.name == "deposit"); + } + UNSCOPED_INFO("the declaration and the object's call"); + CHECK(placesOf(refs) == std::string("6:17 10:8")); + { + INFO("renamable"); + CHECK(refs.hasDefinition); + CHECK(refs.renamable); + } refs = service.references(account, 10, 9); // `deposit` in `a:deposit(5)` - ensure_equals("from the call: the same", placesOf(refs), std::string("6:17 10:8")); - ensure_equals("declared in the class", where(refs.definition), std::string("6:17")); + UNSCOPED_INFO("from the call: the same"); + CHECK(placesOf(refs) == std::string("6:17 10:8")); + UNSCOPED_INFO("declared in the class"); + CHECK(where(refs.definition) == std::string("6:17")); const std::string proxy = "--!strict\n" // 0 "local Base = {}\n" // 1 @@ -325,8 +415,13 @@ namespace tut "print(Base.greet(), o.greet)\n"; // 6 open("proxy", false); refs = service.references(proxy, 2, 15); // `greet` in `function Base.greet` - ensure("found, and declared", refs.found && refs.hasDefinition); - ensure("an object's field of the name, its __index a function: not to rename", !refs.renamable); + { + INFO("found, and declared"); + REQUIRE(refs.found); + CHECK(refs.hasDefinition); + } + UNSCOPED_INFO("an object's field of the name, its __index a function: not to rename"); + CHECK_FALSE(refs.renamable); service.setDocument(""); } } diff --git a/indra/alscript/tests/alluauservice_test.cpp b/indra/alscript/tests/alluauservice_test.cpp index f922823c77..0576b18be3 100644 --- a/indra/alscript/tests/alluauservice_test.cpp +++ b/indra/alscript/tests/alluauservice_test.cpp @@ -32,7 +32,7 @@ #include "../lint/alselenefilters.h" #include "../core/alscriptlexicon.h" -#include "../test/lltut.h" +#include #include "llsdserialize.h" #include @@ -40,7 +40,7 @@ #include #include -namespace tut +namespace { struct alluauservice_data { @@ -102,63 +102,64 @@ namespace tut } }; - typedef test_group alluauservice_group; - typedef alluauservice_group::object alluauservice_object; - alluauservice_group alluauservice_instance("alluauservice"); - - template<> template<> - void alluauservice_object::test<1>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the grid's definitions load", "[alluauservice]") { - set_test_name("the grid's definitions load"); - ensure("definitions file read", !definitions.empty()); - ensure("definitions load: " + error, loaded); - ensure("service says so", service.hasDefinitions()); + UNSCOPED_INFO("definitions file read"); + REQUIRE_FALSE(definitions.empty()); + UNSCOPED_INFO("definitions load: " + error); + CHECK(loaded); + UNSCOPED_INFO("service says so"); + CHECK(service.hasDefinitions()); } - template<> template<> - void alluauservice_object::test<2>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a script using ll and the SL types checks clean", "[alluauservice]") { - set_test_name("a script using ll and the SL types checks clean"); - ensure("definitions load: " + error, loaded); + UNSCOPED_INFO("definitions load: " + error); + CHECK(loaded); ALScriptProblems problems = service.check( "local owner: uuid = ll.GetOwner()\n" "ll.Say(0, tostring(owner))\n" "local here: vector = vector.create(1, 2, 3)\n" "ll.SetPos(here + vector.one)\n"); - ensure_equals("no errors: " + said(problems), errors(problems), 0); + UNSCOPED_INFO("no errors: " + said(problems)); + CHECK(errors(problems) == 0); } - template<> template<> - void alluauservice_object::test<3>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a wrong argument type is a type error", "[alluauservice]") { - set_test_name("a wrong argument type is a type error"); - ensure("definitions load: " + error, loaded); + UNSCOPED_INFO("definitions load: " + error); + CHECK(loaded); ALScriptProblems problems = service.check("ll.Say(\"zero\", 0)\n"); - ensure("an error: " + said(problems), errors(problems) > 0); - ensure("from the type checker", problems.front().source == ALScriptProblem::Source::Types); - ensure_equals("on the first line", problems.front().line, 0); + UNSCOPED_INFO("an error: " + said(problems)); + CHECK(errors(problems) > 0); + UNSCOPED_INFO("from the type checker"); + CHECK(problems.front().source == ALScriptProblem::Source::Types); + UNSCOPED_INFO("on the first line"); + CHECK(problems.front().line == 0); } - template<> template<> - void alluauservice_object::test<4>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: an unknown global is reported by name", "[alluauservice]") { - set_test_name("an unknown global is reported by name"); - ensure("definitions load: " + error, loaded); + UNSCOPED_INFO("definitions load: " + error); + CHECK(loaded); ALScriptProblems problems = service.check("frobnicate(1)\n"); - ensure("an error: " + said(problems), errors(problems) > 0); - ensure("naming it: " + said(problems), mentions(problems, "frobnicate")); + UNSCOPED_INFO("an error: " + said(problems)); + CHECK(errors(problems) > 0); + UNSCOPED_INFO("naming it: " + said(problems)); + CHECK(mentions(problems, "frobnicate")); } - template<> template<> - void alluauservice_object::test<5>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a deprecated function is a lint; lints and unknown globals come keyed", "[alluauservice]") { - set_test_name("a deprecated function is a lint"); - ensure("definitions load: " + error, loaded); + UNSCOPED_INFO("definitions load: " + error); + CHECK(loaded); // ll.Cloud is one of the few functions the ll table itself marks // deprecated; the legacy names live in llcompat. ALScriptProblems problems = service.check("local density = ll.Cloud(vector.zero)\n"); - ensure("something said: " + said(problems), !problems.empty()); - ensure("deprecated: " + said(problems), mentions(problems, "deprecated")); + UNSCOPED_INFO("something said: " + said(problems)); + REQUIRE_FALSE(problems.empty()); + UNSCOPED_INFO("deprecated: " + said(problems)); + CHECK(mentions(problems, "deprecated")); // A lint the map knows is taken apart by its name, an unknown // global by its shape, for another language to say. problems = service.check("local unused = 1\nnope()\n"); @@ -176,47 +177,51 @@ namespace tut keyed_error = true; } } - ensure("the unused local's lint, keyed with its name: " + said(problems), keyed_lint); - ensure("the unknown global, keyed with its name: " + said(problems), keyed_error); + UNSCOPED_INFO("the unused local's lint, keyed with its name: " + said(problems)); + CHECK(keyed_lint); + UNSCOPED_INFO("the unknown global, keyed with its name: " + said(problems)); + CHECK(keyed_error); } - template<> template<> - void alluauservice_object::test<6>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a syntax error is the parser's", "[alluauservice]") { - set_test_name("a syntax error is the parser's"); - ensure("definitions load: " + error, loaded); + UNSCOPED_INFO("definitions load: " + error); + CHECK(loaded); ALScriptProblems problems = service.check("local x = \n"); - ensure("an error: " + said(problems), errors(problems) > 0); - ensure("from the parser", problems.front().source == ALScriptProblem::Source::Parser); + UNSCOPED_INFO("an error: " + said(problems)); + CHECK(errors(problems) > 0); + UNSCOPED_INFO("from the parser"); + CHECK(problems.front().source == ALScriptProblem::Source::Parser); } - template<> template<> - void alluauservice_object::test<7>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: without the definitions ll is unknown", "[alluauservice]") { - set_test_name("without the definitions, ll is unknown"); ALLuauService bare; - ensure("nothing loaded", !bare.hasDefinitions()); + UNSCOPED_INFO("nothing loaded"); + CHECK_FALSE(bare.hasDefinitions()); ALScriptProblems problems = bare.check("ll.Say(0, \"x\")\n"); - ensure("an error: " + said(problems), errors(problems) > 0); - ensure("naming ll: " + said(problems), mentions(problems, "ll")); + UNSCOPED_INFO("an error: " + said(problems)); + CHECK(errors(problems) > 0); + UNSCOPED_INFO("naming ll: " + said(problems)); + CHECK(mentions(problems, "ll")); } - template<> template<> - void alluauservice_object::test<8>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: bad definitions are refused with a reason", "[alluauservice]") { - set_test_name("bad definitions are refused with a reason"); ALLuauService bare; std::string why; - ensure("refused", !bare.loadDefinitions("declare ll: {\n", why)); - ensure("with a reason", !why.empty()); - ensure("and nothing loaded", !bare.hasDefinitions()); + UNSCOPED_INFO("refused"); + REQUIRE_FALSE(bare.loadDefinitions("declare ll: {\n", why)); + UNSCOPED_INFO("with a reason"); + REQUIRE_FALSE(why.empty()); + UNSCOPED_INFO("and nothing loaded"); + CHECK_FALSE(bare.hasDefinitions()); } - template<> template<> - void alluauservice_object::test<9>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: completion offers the fields of ll and the locals in scope", "[alluauservice]") { - set_test_name("completion offers the fields of ll and the locals in scope"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local count = 1\nll.Sa\n"; // At the end of `ll.Sa`. std::vector found = service.complete(script, 1, 5); @@ -226,13 +231,16 @@ namespace tut if (c.text == "Say") { say = true; - ensure("a function", c.kind == ALScriptSymbolKind::Function); - ensure("with a signature: " + c.detail, c.detail.find("(") != std::string::npos); + UNSCOPED_INFO("a function"); + REQUIRE(c.kind == ALScriptSymbolKind::Function); + UNSCOPED_INFO("with a signature: " + c.detail); + REQUIRE(c.detail.find("(") != std::string::npos); } } std::string names = llformat("%d entries:", (int)found.size()); for (size_t i = 0; i < found.size() && i < 12; ++i) names += " " + found[i].text; - ensure("ll.Say is offered; " + names, say); + UNSCOPED_INFO("ll.Say is offered; " + names); + CHECK(say); // At the start of an empty statement: bindings and keywords. found = service.complete(script, 2, 0); bool local = false, keyword = false; @@ -241,91 +249,86 @@ namespace tut local = local || (c.text == "count" && c.kind == ALScriptSymbolKind::Variable && c.detail == "number"); keyword = keyword || (c.text == "local" && c.kind == ALScriptSymbolKind::Keyword); } - ensure("the local, typed", local); - ensure("a keyword", keyword); + UNSCOPED_INFO("the local, typed"); + CHECK(local); + UNSCOPED_INFO("a keyword"); + CHECK(keyword); } - template<> template<> - void alluauservice_object::test<10>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: hover says what a name is with its docs and where a local was bound", "[alluauservice]") { - set_test_name("hover says what a name is, with its documentation, and where a local was bound"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); llifstream in(std::string(AL_LSL_DEFINITIONS_DIR) + "/secondlife.docs.json", std::ios::binary); std::stringstream json; json << in.rdbuf(); std::string docs_error; - ensure("docs loaded: " + docs_error, service.loadDocs(json.str(), docs_error)); + UNSCOPED_INFO("docs loaded: " + docs_error); + CHECK(service.loadDocs(json.str(), docs_error)); const std::string script = "local count = 1\nll.Say(0, tostring(count))\n"; ALScriptHover hover = service.hover(script, 1, 4); // on `Say` - ensure("found", hover.found); - ensure("the name and its type: " + hover.label, hover.label.find("Say") != std::string::npos && hover.label.find("(") != std::string::npos); - ensure("documented: " + hover.documentation, !hover.documentation.empty()); - ensure("not the script's own", !hover.hasDefinition); + UNSCOPED_INFO("found"); + REQUIRE(hover.found); + { + INFO("the name and its type: " + hover.label); + REQUIRE(hover.label.find("Say") != std::string::npos); + REQUIRE(hover.label.find("(") != std::string::npos); + } + UNSCOPED_INFO("documented: " + hover.documentation); + REQUIRE_FALSE(hover.documentation.empty()); + UNSCOPED_INFO("not the script's own"); + CHECK_FALSE(hover.hasDefinition); hover = service.hover(script, 1, 20); // on `count` in the call - ensure("found the local", hover.found); - ensure("a number: " + hover.label, hover.label.find("number") != std::string::npos); - ensure("bound in the script", hover.hasDefinition && hover.definitionLine == 0); - ensure("nothing at nothing", !service.hover(script, 2, 0).found); + UNSCOPED_INFO("found the local"); + REQUIRE(hover.found); + UNSCOPED_INFO("a number: " + hover.label); + REQUIRE(hover.label.find("number") != std::string::npos); + { + INFO("bound in the script"); + CHECK(hover.hasDefinition); + CHECK(hover.definitionLine == 0); + } + UNSCOPED_INFO("nothing at nothing"); + REQUIRE_FALSE(service.hover(script, 2, 0).found); hover = service.hover(script, 1, 0); // on `ll` itself - ensure("the table is found", hover.found); - ensure(llformat("and said in a glance, not %d characters", (int)hover.label.size()), hover.label.size() < 1200); - ensure("with how many more there are: " + hover.label, hover.label.find("more") != std::string::npos); - ensure("and the whole of it apart, a field to a line", hover.typeDetail.size() > hover.label.size() && hover.typeDetail.find('\n') != std::string::npos); - } - - template<> template<> - void alluauservice_object::test<17>() - { - set_test_name("hover says what kind of name it is, a function by its signature, and what is wanted where a type is wrong"); - ensure("definitions loaded: " + error, loaded); - const std::string script = - "local function half(n: number): number\n" - " return n / 2\n" - "end\n" - "const limit = 3\n" - "local p = { a = 1 }\n" - "ll.Say(0, half(\"x\"))\n" - "print(limit, p.a)\n"; - ALScriptHover hover = service.hover(script, 0, 16); // half, declared - ensure("a function by its signature: " + hover.label, hover.label.rfind("function half(n: number): number", 0) == 0); - hover = service.hover(script, 0, 20); // n, the parameter - ensure("a parameter: " + hover.label, hover.label == "(parameter) n: number"); - hover = service.hover(script, 1, 11); // n, used - ensure("still a parameter where used: " + hover.label, hover.label == "(parameter) n: number"); - hover = service.hover(script, 3, 7); // limit - ensure("a const: " + hover.label, hover.label == "const limit: number"); - hover = service.hover(script, 4, 6); // p - ensure("a local: " + hover.label, hover.label.rfind("local p: ", 0) == 0); - hover = service.hover(script, 6, 15); // p.a - ensure("a field: " + hover.label, hover.label == "(field) p.a: number"); - hover = service.hover(script, 5, 16); // "x" where a number is wanted - ensure("what is wanted there: " + hover.expected, hover.expected == "number"); - hover = service.hover(script, 6, 0); // print - // The new solver gives print its generic pack: print. - ensure("a builtin by its signature: " + hover.label, hover.label.rfind(newSolver ? "function print<" : "function print(", 0) == 0); + UNSCOPED_INFO("the table is found"); + REQUIRE(hover.found); + UNSCOPED_INFO(llformat("and said in a glance, not %d characters", (int)hover.label.size())); + REQUIRE(hover.label.size() < 1200); + UNSCOPED_INFO("with how many more there are: " + hover.label); + REQUIRE(hover.label.find("more") != std::string::npos); + { + INFO("and the whole of it apart, a field to a line"); + REQUIRE(hover.typeDetail.size() > hover.label.size()); + REQUIRE(hover.typeDetail.find('\n') != std::string::npos); + } } - template<> template<> - void alluauservice_object::test<11>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a signature names the call the position is in and which parameter it is at", "[alluauservice]") { - set_test_name("a signature names the call the position is in and which parameter it is at"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "ll.Say(0, \"hi\")\n"; ALScriptSignature sig = service.signature(script, 0, 7); // in the first argument - ensure("found", sig.found); - ensure("the call: " + sig.label, sig.label.find("Say") != std::string::npos); - ensure_equals("two parameters", sig.parameters.size(), size_t(2)); - ensure_equals("at the first", sig.active, 0); + UNSCOPED_INFO("found"); + REQUIRE(sig.found); + UNSCOPED_INFO("the call: " + sig.label); + REQUIRE(sig.label.find("Say") != std::string::npos); + UNSCOPED_INFO("two parameters"); + REQUIRE(sig.parameters.size() == size_t(2)); + UNSCOPED_INFO("at the first"); + CHECK(sig.active == 0); sig = service.signature(script, 0, 11); // in the second - ensure_equals("at the second", sig.active, 1); - ensure("none outside a call", !service.signature(script, 0, 0).found); + UNSCOPED_INFO("at the second"); + CHECK(sig.active == 1); + UNSCOPED_INFO("none outside a call"); + REQUIRE_FALSE(service.signature(script, 0, 0).found); } - template<> template<> - void alluauservice_object::test<12>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: references find a name's binding and uses and whether it can be renamed", "[alluauservice]") { - set_test_name("references find where a name is bound and every place it stands, and know what is the script's to rename"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local count = 1\n" "ll.Say(0, tostring(count))\n" @@ -334,38 +337,78 @@ namespace tut "function M.f(n: number) return n end\n" "M.f(count)\n"; ALScriptReferences refs = service.references(script, 1, 20); // on `count` in the call - ensure("the local is found", refs.found); - ensure_equals("by name", refs.name, std::string("count")); - ensure(llformat("a variable, not kind %d", (int)refs.kind), refs.kind == ALScriptSymbolKind::Variable); - ensure(llformat("bound on the first line, not %d:%d-%d", refs.definition.line, refs.definition.column, refs.definition.endColumn), - refs.hasDefinition && refs.definition.line == 0 && refs.definition.column == 6 && refs.definition.endColumn == 11); - ensure_equals("five places", refs.references.size(), size_t(5)); - ensure("the first is the binding", refs.references[0] == refs.definition); - ensure("the last is the argument", refs.references[4].line == 5 && refs.references[4].column == 4); - ensure("the script's to rename", refs.renamable); + UNSCOPED_INFO("the local is found"); + REQUIRE(refs.found); + UNSCOPED_INFO("by name"); + CHECK(refs.name == std::string("count")); + UNSCOPED_INFO(llformat("a variable, not kind %d", (int)refs.kind)); + CHECK(refs.kind == ALScriptSymbolKind::Variable); + { + INFO(llformat("bound on the first line, not %d:%d-%d", refs.definition.line, refs.definition.column, refs.definition.endColumn)); + CHECK(refs.hasDefinition); + CHECK(refs.definition.line == 0); + CHECK(refs.definition.column == 6); + CHECK(refs.definition.endColumn == 11); + } + UNSCOPED_INFO("five places"); + REQUIRE(refs.references.size() == size_t(5)); + UNSCOPED_INFO("the first is the binding"); + CHECK(refs.references[0] == refs.definition); + { + INFO("the last is the argument"); + CHECK(refs.references[4].line == 5); + CHECK(refs.references[4].column == 4); + } + UNSCOPED_INFO("the script's to rename"); + CHECK(refs.renamable); refs = service.references(script, 1, 4); // on `Say` - ensure("a field of ll is found", refs.found && refs.name == "Say"); - ensure("a function", refs.kind == ALScriptSymbolKind::Function); - ensure("not bound in the script", !refs.hasDefinition && !refs.renamable); - ensure_equals("used once", refs.references.size(), size_t(1)); + { + INFO("a field of ll is found"); + REQUIRE(refs.found); + CHECK(refs.name == "Say"); + } + UNSCOPED_INFO("a function"); + CHECK(refs.kind == ALScriptSymbolKind::Function); + { + INFO("not bound in the script"); + CHECK_FALSE(refs.hasDefinition); + CHECK_FALSE(refs.renamable); + } + UNSCOPED_INFO("used once"); + REQUIRE(refs.references.size() == size_t(1)); refs = service.references(script, 5, 2); // on `f` in `M.f(count)` - ensure("the script's own field is found", refs.found && refs.name == "f"); - ensure("bound by the function statement", refs.hasDefinition && refs.definition.line == 4 && refs.definition.column == 11); - ensure_equals("declared and called", refs.references.size(), size_t(2)); - ensure("and renamable", refs.renamable); + { + INFO("the script's own field is found"); + REQUIRE(refs.found); + CHECK(refs.name == "f"); + } + { + INFO("bound by the function statement"); + CHECK(refs.hasDefinition); + CHECK(refs.definition.line == 4); + CHECK(refs.definition.column == 11); + } + UNSCOPED_INFO("declared and called"); + REQUIRE(refs.references.size() == size_t(2)); + UNSCOPED_INFO("and renamable"); + CHECK(refs.renamable); refs = service.references(script, 0, 6); // on the binding itself - ensure("asked at the binding, the same answer", refs.found && refs.references.size() == 5); - ensure("nothing at nothing", !service.references(script, 3, 0).found); + { + INFO("asked at the binding, the same answer"); + REQUIRE(refs.found); + REQUIRE(refs.references.size() == 5); + } + UNSCOPED_INFO("nothing at nothing"); + REQUIRE_FALSE(service.references(script, 3, 0).found); } - template<> template<> - void alluauservice_object::test<13>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the outline lists what the top binds and functions with inner ones deeper", "[alluauservice]") { - set_test_name("the outline lists what the top binds and every function, each function's own one deeper"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local count = 1\n" "local function half(n: number)\n" @@ -378,33 +421,67 @@ namespace tut std::vector outline = service.outline(script); std::string names; for (const ALScriptOutlineEntry& e : outline) names += " " + e.name + llformat("@%d", e.depth); - ensure_equals("five entries:" + names, outline.size(), size_t(5)); - ensure("count first, a number", outline[0].name == "count" && outline[0].kind == ALScriptSymbolKind::Variable && outline[0].detail == "number" && outline[0].depth == 0); - ensure("half, a function spanning its lines", outline[1].name == "half" && outline[1].kind == ALScriptSymbolKind::Function && outline[1].span.line == 1 && outline[1].span.endLine == 5); - ensure("its name where it is", outline[1].nameSpan.line == 1 && outline[1].nameSpan.column == 15); - ensure("quarter inside it, one deeper", outline[2].name == "quarter" && outline[2].depth == 1); - ensure("the event handler, as an event", outline[3].name == "LLEvents.touch_start" && outline[3].kind == ALScriptSymbolKind::Event); - ensure("the global", outline[4].name == "handlers" && outline[4].kind == ALScriptSymbolKind::Variable); + UNSCOPED_INFO("five entries:" + names); + REQUIRE(outline.size() == size_t(5)); + { + INFO("count first, a number"); + CHECK(outline[0].name == "count"); + CHECK(outline[0].kind == ALScriptSymbolKind::Variable); + CHECK(outline[0].detail == "number"); + CHECK(outline[0].depth == 0); + } + { + INFO("half, a function spanning its lines"); + CHECK(outline[1].name == "half"); + CHECK(outline[1].kind == ALScriptSymbolKind::Function); + CHECK(outline[1].span.line == 1); + CHECK(outline[1].span.endLine == 5); + } + { + INFO("its name where it is"); + CHECK(outline[1].nameSpan.line == 1); + CHECK(outline[1].nameSpan.column == 15); + } + { + INFO("quarter inside it, one deeper"); + CHECK(outline[2].name == "quarter"); + CHECK(outline[2].depth == 1); + } + { + INFO("the event handler, as an event"); + CHECK(outline[3].name == "LLEvents.touch_start"); + CHECK(outline[3].kind == ALScriptSymbolKind::Event); + } + { + INFO("the global"); + CHECK(outline[4].name == "handlers"); + CHECK(outline[4].kind == ALScriptSymbolKind::Variable); + } } - template<> template<> - void alluauservice_object::test<14>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a missing key is named as written with the nearest key there is", "[alluauservice]") { - set_test_name("a key that is not there is named by what was written, with the nearest key there is"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); ALScriptProblems problems = service.check("ll.ay(0, \"hi\")\n"); - ensure("one problem", !problems.empty()); + UNSCOPED_INFO("one problem"); + REQUIRE_FALSE(problems.empty()); const std::string& message = problems.front().message; - ensure("names ll, not its fields: " + message, message.find("not found in ll") != std::string::npos && message.find("Abs") == std::string::npos); - ensure("and suggests Say: " + message, message.find("'Say'") != std::string::npos); - ensure(llformat("in a glance, not %d characters", (int)message.size()), message.size() < 100); + { + INFO("names ll, not its fields: " + message); + REQUIRE(message.find("not found in ll") != std::string::npos); + REQUIRE(message.find("Abs") == std::string::npos); + } + UNSCOPED_INFO("and suggests Say: " + message); + REQUIRE(message.find("'Say'") != std::string::npos); + UNSCOPED_INFO(llformat("in a glance, not %d characters", (int)message.size())); + REQUIRE(message.size() < 100); } - template<> template<> - void alluauservice_object::test<15>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: every name is told by what it is: its kind and modifiers in order", "[alluauservice]") { - set_test_name("every name is told by what it is: parameter, local, global, field, function, type, builtin, deprecated"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local count = 1\n" "local function half(n: number): number\n" @@ -425,40 +502,96 @@ namespace tut std::string listed; for (const ALScriptSemanticToken& t : tokens) listed += llformat(" %d:%d/%d+%d", t.span.line, t.span.column, (int)t.kind, (int)t.modifiers); const ALScriptSemanticToken* count_decl = at(0, 6); - ensure("count declared, a variable:" + listed, count_decl && count_decl->kind == ALScriptSymbolKind::Variable && (count_decl->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("count declared, a variable:" + listed); + REQUIRE(count_decl); + REQUIRE(count_decl->kind == ALScriptSymbolKind::Variable); + CHECK(count_decl->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* half_decl = at(1, 15); - ensure("half declared, a function:" + listed, half_decl && half_decl->kind == ALScriptSymbolKind::Function && (half_decl->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("half declared, a function:" + listed); + REQUIRE(half_decl); + REQUIRE(half_decl->kind == ALScriptSymbolKind::Function); + CHECK(half_decl->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* n_param = at(1, 20); - ensure("n, a parameter:" + listed, n_param && n_param->kind == ALScriptSymbolKind::Parameter); + { + INFO("n, a parameter:" + listed); + REQUIRE(n_param); + CHECK(n_param->kind == ALScriptSymbolKind::Parameter); + } const ALScriptSemanticToken* n_type = at(1, 23); - ensure("number, a type:" + listed, n_type && n_type->kind == ALScriptSymbolKind::Type); + { + INFO("number, a type:" + listed); + REQUIRE(n_type); + CHECK(n_type->kind == ALScriptSymbolKind::Type); + } const ALScriptSemanticToken* n_use = at(2, 11); - ensure("n used, still a parameter:" + listed, n_use && n_use->kind == ALScriptSymbolKind::Parameter && !(n_use->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("n used, still a parameter:" + listed); + REQUIRE(n_use); + REQUIRE(n_use->kind == ALScriptSymbolKind::Parameter); + CHECK_FALSE(n_use->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* pair = at(4, 5); - ensure("Pair declared, a type:" + listed, pair && pair->kind == ALScriptSymbolKind::Type && (pair->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("Pair declared, a type:" + listed); + REQUIRE(pair); + REQUIRE(pair->kind == ALScriptSymbolKind::Type); + CHECK(pair->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* a_key = at(5, 18); - ensure("a in the table, a field declared:" + listed, a_key && a_key->kind == ALScriptSymbolKind::Field && (a_key->modifiers & ALScriptSemanticToken::Declaration)); + { + INFO("a in the table, a field declared:" + listed); + REQUIRE(a_key); + REQUIRE(a_key->kind == ALScriptSymbolKind::Field); + CHECK(a_key->modifiers & ALScriptSemanticToken::Declaration); + } const ALScriptSemanticToken* ll = at(6, 0); - ensure("ll, a builtin global:" + listed, ll && (ll->modifiers & ALScriptSemanticToken::Builtin) && (ll->modifiers & ALScriptSemanticToken::Global)); + { + INFO("ll, a builtin global:" + listed); + REQUIRE(ll); + REQUIRE(ll->modifiers & ALScriptSemanticToken::Builtin); + CHECK(ll->modifiers & ALScriptSemanticToken::Global); + } const ALScriptSemanticToken* say = at(6, 3); - ensure("Say, a builtin function:" + listed, say && say->kind == ALScriptSymbolKind::Function && (say->modifiers & ALScriptSemanticToken::Builtin)); + { + INFO("Say, a builtin function:" + listed); + REQUIRE(say); + REQUIRE(say->kind == ALScriptSymbolKind::Function); + CHECK(say->modifiers & ALScriptSemanticToken::Builtin); + } const ALScriptSemanticToken* p_a = at(6, 21); - ensure("p.a, a field:" + listed, p_a && p_a->kind == ALScriptSymbolKind::Field); + { + INFO("p.a, a field:" + listed); + REQUIRE(p_a); + CHECK(p_a->kind == ALScriptSymbolKind::Field); + } const ALScriptSemanticToken* half_call = at(6, 25); - ensure("half called, a function:" + listed, half_call && half_call->kind == ALScriptSymbolKind::Function); + { + INFO("half called, a function:" + listed); + REQUIRE(half_call); + CHECK(half_call->kind == ALScriptSymbolKind::Function); + } const ALScriptSemanticToken* handlers = at(7, 0); - ensure("handlers, a global the script binds:" + listed, handlers && (handlers->modifiers & ALScriptSemanticToken::Global) && !(handlers->modifiers & ALScriptSemanticToken::Builtin)); + { + INFO("handlers, a global the script binds:" + listed); + REQUIRE(handlers); + REQUIRE(handlers->modifiers & ALScriptSemanticToken::Global); + CHECK_FALSE(handlers->modifiers & ALScriptSemanticToken::Builtin); + } for (size_t i = 1; i < tokens.size(); ++i) { - ensure("in order, each once", tokens[i - 1].span < tokens[i].span); + UNSCOPED_INFO("in order, each once"); + REQUIRE(tokens[i - 1].span < tokens[i].span); } } - template<> template<> - void alluauservice_object::test<16>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: inlay hints name each argument's parameter and each untyped local's type", "[alluauservice]") { - set_test_name("inlay hints name each argument's parameter and the type a local was given without saying"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local function greet(name: string, times: number) end\n" "local times = 3\n" @@ -475,49 +608,105 @@ namespace tut } return false; }; - ensure("the local's type after its name:" + listed, has(1, 11, ": number")); + UNSCOPED_INFO("the local's type after its name:" + listed); + CHECK(has(1, 11, ": number")); for (const ALScriptInlayHint& h : hints) { - ensure("a type may be written in, a parameter's name never: " + h.text, h.writable == (h.kind == ALScriptInlayHint::Kind::Type)); + UNSCOPED_INFO("a type may be written in, a parameter's name never: " + h.text); + REQUIRE(h.writable == (h.kind == ALScriptInlayHint::Kind::Type)); } // A type cut short says less than the text would have to. const std::vector wide = service.inlayHints("local t = { a = 1, b = 2, c = 3, d = 4, e = 5 }\n", false, true); - ensure("not a table cut short", wide.empty() || !wide.front().writable || wide.front().text.find("...") == std::string::npos); - ensure("not one that says its type:" + listed, !has(2, 11, ": number")); - ensure("name: before the string:" + listed, has(3, 6, "name:")); - ensure("nothing before an argument that is the name:" + listed, !has(3, 12, "times:")); - ensure("the channel's name before the 0:" + listed, has(4, 7, "channel:")); - ensure("and the message's:" + listed, has(4, 10, "msg:")); + UNSCOPED_INFO("not a table cut short"); + REQUIRE((wide.empty() || !wide.front().writable || wide.front().text.find("...") == std::string::npos)); + UNSCOPED_INFO("not one that says its type:" + listed); + CHECK_FALSE(has(2, 11, ": number")); + UNSCOPED_INFO("name: before the string:" + listed); + CHECK(has(3, 6, "name:")); + UNSCOPED_INFO("nothing before an argument that is the name:" + listed); + CHECK_FALSE(has(3, 12, "times:")); + UNSCOPED_INFO("the channel's name before the 0:" + listed); + CHECK(has(4, 7, "channel:")); + UNSCOPED_INFO("and the message's:" + listed); + CHECK(has(4, 10, "msg:")); std::vector only_types = service.inlayHints(script, false, true); - ensure("types alone, when asked", only_types.size() == 1 && only_types.front().kind == ALScriptInlayHint::Kind::Type); - ensure("nothing when neither is asked", service.inlayHints(script, false, false).empty()); + { + INFO("types alone, when asked"); + REQUIRE(only_types.size() == 1); + CHECK(only_types.front().kind == ALScriptInlayHint::Kind::Type); + } + UNSCOPED_INFO("nothing when neither is asked"); + REQUIRE(service.inlayHints(script, false, false).empty()); + } + + TEST_CASE_METHOD(alluauservice_data, "alluauservice: hover tells each kind of name and the type wanted where a type is wrong", "[alluauservice]") + { + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); + const std::string script = + "local function half(n: number): number\n" + " return n / 2\n" + "end\n" + "const limit = 3\n" + "local p = { a = 1 }\n" + "ll.Say(0, half(\"x\"))\n" + "print(limit, p.a)\n"; + ALScriptHover hover = service.hover(script, 0, 16); // half, declared + UNSCOPED_INFO("a function by its signature: " + hover.label); + CHECK(hover.label.rfind("function half(n: number): number", 0) == 0); + hover = service.hover(script, 0, 20); // n, the parameter + UNSCOPED_INFO("a parameter: " + hover.label); + CHECK(hover.label == "(parameter) n: number"); + hover = service.hover(script, 1, 11); // n, used + UNSCOPED_INFO("still a parameter where used: " + hover.label); + CHECK(hover.label == "(parameter) n: number"); + hover = service.hover(script, 3, 7); // limit + UNSCOPED_INFO("a const: " + hover.label); + CHECK(hover.label == "const limit: number"); + hover = service.hover(script, 4, 6); // p + UNSCOPED_INFO("a local: " + hover.label); + CHECK(hover.label.rfind("local p: ", 0) == 0); + hover = service.hover(script, 6, 15); // p.a + UNSCOPED_INFO("a field: " + hover.label); + CHECK(hover.label == "(field) p.a: number"); + hover = service.hover(script, 5, 16); // "x" where a number is wanted + UNSCOPED_INFO("what is wanted there: " + hover.expected); + CHECK(hover.expected == "number"); + hover = service.hover(script, 6, 0); // print + // The new solver gives print its generic pack: print. + UNSCOPED_INFO("a builtin by its signature: " + hover.label); + CHECK(hover.label.rfind(newSolver ? "function print<" : "function print(", 0) == 0); } - template<> template<> - void alluauservice_object::test<18>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the configuration adds globals and turns lints off until it is replaced", "[alluauservice]") { - set_test_name("the configuration names the globals a script may use and turns lints off, until it is replaced"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local unused = 1\nAlchemy.log(\"hi\")\n"; ALScriptProblems plain = service.check(script); - ensure("Alchemy unknown without a configuration: " + said(plain), mentions(plain, "Alchemy")); - ensure("the unused local warned of: " + said(plain), mentions(plain, "unused")); + UNSCOPED_INFO("Alchemy unknown without a configuration: " + said(plain)); + CHECK(mentions(plain, "Alchemy")); + UNSCOPED_INFO("the unused local warned of: " + said(plain)); + CHECK(mentions(plain, "unused")); ALLuauConfig config; std::string bad; - ensure("parses: " + bad, ALLuauConfig::parse("{ \"globals\": [\"Alchemy\"], \"lint\": { \"LocalUnused\": false } }", config, bad)); + UNSCOPED_INFO("parses: " + bad); + REQUIRE(ALLuauConfig::parse("{ \"globals\": [\"Alchemy\"], \"lint\": { \"LocalUnused\": false } }", config, bad)); service.setConfig(config); ALScriptProblems configured = service.check(script); - ensure("Alchemy known: " + said(configured), !mentions(configured, "Alchemy")); - ensure("the unused local let be: " + said(configured), !mentions(configured, "unused")); + UNSCOPED_INFO("Alchemy known: " + said(configured)); + CHECK_FALSE(mentions(configured, "Alchemy")); + UNSCOPED_INFO("the unused local let be: " + said(configured)); + CHECK_FALSE(mentions(configured, "unused")); service.setConfig(ALLuauConfig()); ALScriptProblems again = service.check(script); - ensure("unknown again: " + said(again), mentions(again, "Alchemy")); + UNSCOPED_INFO("unknown again: " + said(again)); + CHECK(mentions(again, "Alchemy")); } - template<> template<> - void alluauservice_object::test<19>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the engine's commonest messages come back keyed", "[alluauservice]") { - set_test_name("the engine's commonest messages come back keyed, which an upgrade that rewords them would end"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); // Each script says one thing the map has a row for; a Luau whose // wording moved on gives the message with no key, and this is // where that shows -- run check_script_strings.py then. @@ -545,14 +734,14 @@ namespace tut { keyed |= p.key.compare(0, strlen(c.key), c.key) == 0; } - ensure(std::string("keyed ") + c.key + " for " + c.script + ": " + said(problems), keyed); + UNSCOPED_INFO(std::string("keyed ") + c.key + " for " + c.script + ": " + said(problems)); + REQUIRE(keyed); } } - template<> template<> - void alluauservice_object::test<20>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: questions reuse a text's check and hints are the same whatever came first", "[alluauservice]") { - set_test_name("a question asked again of the same text is answered from the check made, and what goes beside the text is the same whatever came first"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local function f(a) return a + 1 end\nlocal y = f(2)\nlocal z = y * 2\nprint(z)\n"; ALLuauConfig config; service.setConfig(config); @@ -567,17 +756,23 @@ namespace tut service.references(script, 1, 6); service.outline(script); service.semanticTokens(script); - ensure_equals("the queries share the one check", service.typeChecks(), checked); + UNSCOPED_INFO("the queries share the one check"); + CHECK(service.typeChecks() == checked); service.setConfig(config); service.hover(script, 1, 6); - ensure_equals("the same configuration again changes nothing", service.typeChecks(), checked); + UNSCOPED_INFO("the same configuration again changes nothing"); + CHECK(service.typeChecks() == checked); // A text asked about before a check has what a hover does. const std::vector after_hover = service.inlayHints(script, true, true); - ensure_equals("as many hints", after_hover.size(), after_check.size()); + UNSCOPED_INFO("as many hints"); + REQUIRE(after_hover.size() == after_check.size()); for (size_t i = 0; i < after_hover.size(); ++i) { - ensure("the same hint: " + after_hover[i].text + " / " + after_check[i].text, - after_hover[i].text == after_check[i].text && after_hover[i].line == after_check[i].line); + { + INFO("the same hint: " + after_hover[i].text + " / " + after_check[i].text); + REQUIRE(after_hover[i].text == after_check[i].text); + REQUIRE(after_hover[i].line == after_check[i].line); + } } // Another configuration, or another text, is checked again: one // unlike either solver's default. @@ -585,45 +780,58 @@ namespace tut strict.mode = "nocheck"; service.setConfig(strict); service.hover(script, 1, 6); - ensure("another configuration, checked again", service.typeChecks() > checked); + UNSCOPED_INFO("another configuration, checked again"); + CHECK(service.typeChecks() > checked); const size_t now = service.typeChecks(); service.hover(script + "print(y)\n", 1, 6); - ensure("another text, checked again", service.typeChecks() > now); + UNSCOPED_INFO("another text, checked again"); + CHECK(service.typeChecks() > now); service.setConfig(config); } - template<> template<> - void alluauservice_object::test<21>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a dotted method call takes its object first and the signature counts it", "[alluauservice]") { - set_test_name("a method called with a dot is given its object as its first argument, and the signature counts it"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local T = {}\nfunction T:m(a: number, b: string) end\nT.m(T, 1, \"x\")\nT:m(1, \"x\")\n"; ALScriptSignature dotted = service.signature(script, 2, 12); // in "x" - ensure("found", dotted.found); - ensure_equals("self, a and b: " + dotted.label, dotted.parameters.size(), size_t(3)); - ensure_equals("at the third", dotted.active, 2); - ensure("which is b: " + dotted.parameters[2], dotted.parameters[2].find("b") == 0); + UNSCOPED_INFO("found"); + REQUIRE(dotted.found); + UNSCOPED_INFO("self, a and b: " + dotted.label); + REQUIRE(dotted.parameters.size() == size_t(3)); + UNSCOPED_INFO("at the third"); + CHECK(dotted.active == 2); + UNSCOPED_INFO("which is b: " + dotted.parameters[2]); + CHECK(dotted.parameters[2].find("b") == 0); ALScriptSignature colon = service.signature(script, 3, 9); // in "x" - ensure_equals("with a colon, a and b", colon.parameters.size(), size_t(2)); - ensure("at b: " + colon.parameters[static_cast(std::max(0, colon.active))], colon.active == 1 && colon.parameters[1].find("b") == 0); + 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))]); + CHECK(colon.active == 1); + CHECK(colon.parameters[1].find("b") == 0); + } } - template<> template<> - void alluauservice_object::test<22>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: both solvers use the definitions; the new nonstrict says only sure failures", "[alluauservice]") { - set_test_name("either solver checks with the definitions, the new one in nonstrict mode saying only what is sure to fail as the script runs"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); for (const bool use : { true, false, true }) { std::string why; - ensure("switched: " + why, service.setNewSolver(use, why)); - ensure_equals("the one asked for", service.newSolver(), use); - ensure("the definitions with it", service.hasDefinitions()); + UNSCOPED_INFO("switched: " + why); + REQUIRE(service.setNewSolver(use, why)); + UNSCOPED_INFO("the one asked for"); + REQUIRE(service.newSolver() == use); + UNSCOPED_INFO("the definitions with it"); + REQUIRE(service.hasDefinitions()); ALLuauConfig config; config.mode = "strict"; service.setConfig(config); ALScriptProblems problems = service.check("ll.Say(\"zero\", 0)\n"); - ensure(std::string(use ? "new" : "old") + ", strict, a wrong argument: " + said(problems), errors(problems) > 0); + UNSCOPED_INFO(std::string(use ? "new" : "old") + ", strict, a wrong argument: " + said(problems)); + REQUIRE(errors(problems) > 0); // A method called with a dot is a missing self, whichever says it. problems = service.check("local T = {}\nfunction T:m(a: number) end\nT.m(1)\n"); bool missing_self = false; @@ -631,25 +839,30 @@ namespace tut { missing_self |= problem.key == "LuauRequiresSelf" && problem.line == 2 && problem.column == 0; } - ensure(std::string(use ? "new" : "old") + ": a missing self, at the call: " + said(problems), missing_self); + UNSCOPED_INFO(std::string(use ? "new" : "old") + ": a missing self, at the call: " + said(problems)); + REQUIRE(missing_self); } // Nonstrict, as the grid compiles: the new solver says nothing of // ll.Say's argument, and what it does say comes keyed. ALLuauConfig config; config.mode = "nonstrict"; service.setConfig(config); - ensure("nothing of ll.Say: " + said(service.check("ll.Say(\"zero\", 0)\n")), errors(service.check("ll.Say(\"zero\", 0)\n")) == 0); + UNSCOPED_INFO("nothing of ll.Say: " + said(service.check("ll.Say(\"zero\", 0)\n"))); + CHECK(errors(service.check("ll.Say(\"zero\", 0)\n")) == 0); const ALScriptProblems problems = service.check("print(string.len(5))\n"); - ensure("a checked function given the wrong type: " + said(problems), !problems.empty() && problems.front().key == "LuauCheckedCall"); + { + INFO("a checked function given the wrong type: " + said(problems)); + REQUIRE_FALSE(problems.empty()); + CHECK(problems.front().key == "LuauCheckedCall"); + } std::string why; service.setNewSolver(false, why); } - template<> template<> - void alluauservice_object::test<23>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a check past its time limit says so; a stopped one answers nothing", "[alluauservice]") { - set_test_name("a check past its time limit stops, says so, and answers what it found by then; a stopped one answers nothing"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); std::string script; for (int i = 0; i < 50; ++i) { @@ -663,42 +876,66 @@ namespace tut // Far less than any check takes. service.setTimeLimit(1e-9); ALScriptProblems problems = service.check(script); - ensure(which + "said to have stopped: " + said(problems), !problems.empty() && problems.front().key == "LuauCheckTimedOut" && - problems.front().severity == ALScriptProblem::Severity::Warning); + { + INFO(which + "said to have stopped: " + said(problems)); + REQUIRE_FALSE(problems.empty()); + REQUIRE(problems.front().key == "LuauCheckTimedOut"); + REQUIRE(problems.front().severity == ALScriptProblem::Severity::Warning); + } service.setTimeLimit(0.0); problems = service.check(script); - ensure(which + "unlimited, it runs its course: " + said(problems), problems.empty()); + UNSCOPED_INFO(which + "unlimited, it runs its course: " + said(problems)); + REQUIRE(problems.empty()); // Stopped before it began: nothing, and said to be stopped. ALLuauService::Stop stop = ALLuauService::newStop(); ALLuauService::cancel(stop); service.setStop(stop); problems = service.check("local n: number = \"s\"\n"); - ensure(which + "stopped, nothing: " + said(problems), problems.empty() && service.stopped()); + { + INFO(which + "stopped, nothing: " + said(problems)); + REQUIRE(problems.empty()); + REQUIRE(service.stopped()); + } // And a question after it is answered from a check of its own, // not the part of one the stop left. service.setStop(nullptr); const ALScriptHover hover = service.hover("local n: number = \"s\"\n", 0, 6); - ensure(which + "not stopped now", !service.stopped()); - ensure(which + "answered: " + hover.label, hover.label.find("n: number") != std::string::npos); + UNSCOPED_INFO(which + "not stopped now"); + REQUIRE_FALSE(service.stopped()); + UNSCOPED_INFO(which + "answered: " + hover.label); + REQUIRE(hover.label.find("n: number") != std::string::npos); problems = service.check("local n: number = \"s\"\n"); - ensure(which + "and checked in full: " + said(problems), errors(problems) > 0 || use); + UNSCOPED_INFO(which + "and checked in full: " + said(problems)); + REQUIRE((errors(problems) > 0 || use)); } std::string why; service.setNewSolver(newSolver, why); } - template<> template<> - void alluauservice_object::test<24>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: selene's comments allow or deny Luau's lints for the file or one statement", "[alluauservice]") { - set_test_name("selene's comments: its lints named as Luau's, allowed or denied for the whole file before any code, or for the statement beside"); - ensure("definitions load: " + error, loaded); + UNSCOPED_INFO("definitions load: " + error); + CHECK(loaded); const auto read = ALSeleneFilters::read("-- # selene: allow(unused_variable, multiple_statements))"); - ensure("read leniently, for the whole file", read && read->file && read->action == ALSeleneFilters::Action::Allow && read->lints.size() == 2); - ensure("a Luau name stands for itself, selene's for Luau's", - ALSeleneFilters::luauLints("LocalUnused") == ALLuauConfig::lintBit("LocalUnused") && - (ALSeleneFilters::luauLints("unused_variable") & ALLuauConfig::lintBit("FunctionUnused")) != 0); - ensure("a check Luau does not make stands for none", ALSeleneFilters::luauLints("almost_swapped") == 0); - ensure("not a directive", !ALSeleneFilters::read("-- seleneous: allow(x)") && !ALSeleneFilters::read("-- selene: permit(x)")); + { + INFO("read leniently, for the whole file"); + REQUIRE(read); + REQUIRE(read->file); + REQUIRE(read->action == ALSeleneFilters::Action::Allow); + REQUIRE(read->lints.size() == 2); + } + { + INFO("a Luau name stands for itself, selene's for Luau's"); + CHECK(ALSeleneFilters::luauLints("LocalUnused") == ALLuauConfig::lintBit("LocalUnused")); + CHECK((ALSeleneFilters::luauLints("unused_variable") & ALLuauConfig::lintBit("FunctionUnused")) != 0); + } + UNSCOPED_INFO("a check Luau does not make stands for none"); + CHECK(ALSeleneFilters::luauLints("almost_swapped") == 0); + { + INFO("not a directive"); + REQUIRE_FALSE(ALSeleneFilters::read("-- seleneous: allow(x)")); + REQUIRE_FALSE(ALSeleneFilters::read("-- selene: permit(x)")); + } const auto unused = [&](const std::string& script) { size_t count = 0; @@ -708,41 +945,54 @@ namespace tut } return count; }; - ensure_equals("without, both said", unused("local a = 1\nlocal b = 2\n"), size_t(2)); - ensure_equals("the whole file", unused("-- @file header\n-- # selene: allow(unused_variable)\nlocal a = 1\nlocal b = 2\n"), size_t(0)); - ensure_equals("after code, nothing: selene refuses a whole file's there", unused("local a = 1\n--# selene: allow(unused_variable)\nlocal b = 2\n"), size_t(2)); - ensure_equals("the statement beside, not the next", unused("-- selene: allow(unused_variable)\nlocal a = 1\nlocal b = 2\n"), size_t(1)); - ensure_equals("a whole function beside it", - unused("-- selene: allow(unused_variable)\nlocal function f()\n local a = 1\nend\nf()\nlocal b = 2\n"), size_t(1)); - ensure_equals("at a line's end, its statement", unused("local a = 1 -- selene: allow(unused_variable)\nlocal b = 2\n"), size_t(1)); + UNSCOPED_INFO("without, both said"); + CHECK(unused("local a = 1\nlocal b = 2\n") == size_t(2)); + UNSCOPED_INFO("the whole file"); + CHECK(unused("-- @file header\n-- # selene: allow(unused_variable)\nlocal a = 1\nlocal b = 2\n") == size_t(0)); + UNSCOPED_INFO("after code, nothing: selene refuses a whole file's there"); + CHECK(unused("local a = 1\n--# selene: allow(unused_variable)\nlocal b = 2\n") == size_t(2)); + UNSCOPED_INFO("the statement beside, not the next"); + CHECK(unused("-- selene: allow(unused_variable)\nlocal a = 1\nlocal b = 2\n") == size_t(1)); + UNSCOPED_INFO("a whole function beside it"); + CHECK(unused("-- selene: allow(unused_variable)\nlocal function f()\n local a = 1\nend\nf()\nlocal b = 2\n") == size_t(1)); + UNSCOPED_INFO("at a line's end, its statement"); + CHECK(unused("local a = 1 -- selene: allow(unused_variable)\nlocal b = 2\n") == size_t(1)); bool denied = false; for (const ALScriptProblem& p : service.check("-- selene: deny(unused_variable)\nlocal a = 1\n")) { denied |= p.key.compare(0, 19, "LuauLintLocalUnused") == 0 && p.severity == ALScriptProblem::Severity::Error; } - ensure("denied: an error", denied); + UNSCOPED_INFO("denied: an error"); + CHECK(denied); } - template<> template<> - void alluauservice_object::test<25>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: an overloaded function's signature shows every form and the one that fits", "[alluauservice]") { - set_test_name("an overloaded function's signature: every form, the one the arguments fit first shown"); - ensure("definitions load: " + error, loaded); + UNSCOPED_INFO("definitions load: " + error); + CHECK(loaded); const std::string script = "local f: ((n: number) -> ()) & ((a: string, b: string) -> ()) = nil :: any\nf(\"x\", \"y\")\n"; const ALScriptSignature sig = service.signature(script, 1, 7); - ensure("found", sig.found); - ensure_equals("both forms", sig.overloads.size(), size_t(2)); - ensure_equals("the second fits two arguments", sig.overload, 1); - ensure("shown", sig.label == sig.overloads[1].label && sig.parameters.size() == 2); - ensure_equals("at the second argument", sig.active, 1); - ensure("one form: none listed", service.signature("local function g(a: number) end\ng(1)\n", 1, 2).overloads.empty()); + UNSCOPED_INFO("found"); + REQUIRE(sig.found); + UNSCOPED_INFO("both forms"); + REQUIRE(sig.overloads.size() == size_t(2)); + UNSCOPED_INFO("the second fits two arguments"); + CHECK(sig.overload == 1); + { + INFO("shown"); + CHECK(sig.label == sig.overloads[1].label); + REQUIRE(sig.parameters.size() == 2); + } + UNSCOPED_INFO("at the second argument"); + CHECK(sig.active == 1); + UNSCOPED_INFO("one form: none listed"); + REQUIRE(service.signature("local function g(a: number) end\ng(1)\n", 1, 2).overloads.empty()); } - template<> template<> - void alluauservice_object::test<26>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: type checked once for problems and once for questions; once if strict or new", "[alluauservice]") { - set_test_name("a text is type checked once for its check and once for the questions, in whatever order they come; once for both where it is strict or the solver is the new one"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); ALLuauConfig config; service.setConfig(config); const bool one_slot = service.newSolver(); @@ -759,9 +1009,12 @@ namespace tut service.complete(loose, 2, 2); service.signature(loose, 1, 12); service.references(loose, 1, 6); - ensure_equals("nonstrict: the check, and the questions' own", service.typeChecks() - start, size_t(one_slot ? 1 : 2)); - ensure_equals("the check again, from what it found", said(service.check(loose)), first); - ensure_equals("and not checked again", service.typeChecks() - start, size_t(one_slot ? 1 : 2)); + UNSCOPED_INFO("nonstrict: the check, and the questions' own"); + CHECK(service.typeChecks() - start == size_t(one_slot ? 1 : 2)); + UNSCOPED_INFO("the check again, from what it found"); + CHECK(said(service.check(loose)) == first); + UNSCOPED_INFO("and not checked again"); + CHECK(service.typeChecks() - start == size_t(one_slot ? 1 : 2)); // A script strict by its own comment: its check serves the questions. const std::string strict = "--!strict\n" + loose; @@ -770,51 +1023,77 @@ namespace tut service.check(strict); service.outline(strict); service.inlayHints(strict, true, true); - ensure_equals("strict: one check, the question first", service.typeChecks() - before, size_t(1)); + UNSCOPED_INFO("strict: one check, the question first"); + CHECK(service.typeChecks() - before == size_t(1)); service.complete(strict, 3, 2); - ensure_equals("a completion reads autocomplete's own", service.typeChecks() - before, size_t(one_slot ? 1 : 2)); + UNSCOPED_INFO("a completion reads autocomplete's own"); + CHECK(service.typeChecks() - before == size_t(one_slot ? 1 : 2)); const std::string strict_first = said(service.check(strict)); service.hover(strict, 2, 6); - ensure_equals("and nothing checked again", service.typeChecks() - before, size_t(one_slot ? 1 : 2)); + 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"; const auto loose_hints = service.inlayHints(typed, false, true); const auto strict_hints = service.inlayHints("--!strict\n" + typed, false, true); - ensure_equals("hints read nonstrict", loose_hints.size(), size_t(2)); - ensure_equals("and strict", strict_hints.size(), size_t(2)); - ensure("the same", loose_hints[0].text == strict_hints[0].text && loose_hints[1].text == strict_hints[1].text); - ensure("the problems of each told apart", said(service.check(loose)) == first && said(service.check(strict)) == strict_first); + UNSCOPED_INFO("hints read nonstrict"); + REQUIRE(loose_hints.size() == size_t(2)); + UNSCOPED_INFO("and strict"); + REQUIRE(strict_hints.size() == size_t(2)); + { + INFO("the same"); + CHECK(loose_hints[0].text == strict_hints[0].text); + CHECK(loose_hints[1].text == strict_hints[1].text); + } + { + INFO("the problems of each told apart"); + CHECK(said(service.check(loose)) == first); + CHECK(said(service.check(strict)) == strict_first); + } } - template<> template<> - void alluauservice_object::test<27>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the same definitions and docs again load nothing and keep what was checked", "[alluauservice]") { - set_test_name("the same definitions and docs again are the ones in hand: nothing loaded over, and what was checked stands"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local n: number = 1\nprint(n)\n"; service.check(script); const size_t checked = service.typeChecks(); std::string why; - ensure("the same again: loaded", service.loadDefinitions(definitions, why) && why.empty()); + { + INFO("the same again: loaded"); + REQUIRE(service.loadDefinitions(definitions, why)); + REQUIRE(why.empty()); + } service.check(script); - ensure_equals("what was checked stands", service.typeChecks(), checked); + UNSCOPED_INFO("what was checked stands"); + CHECK(service.typeChecks() == checked); const std::string docs = "{\"@sl-slua/global/ll.Say\": {\"documentation\": \"Says it.\", \"learn_more_link\": \"x\"}}"; - ensure("docs loaded", service.loadDocs(docs, why) && service.hasDocs()); - ensure("and the same again", service.loadDocs(docs, why) && service.hasDocs()); + { + INFO("docs loaded"); + CHECK(service.loadDocs(docs, why)); + CHECK(service.hasDocs()); + } + { + INFO("and the same again"); + CHECK(service.loadDocs(docs, why)); + CHECK(service.hasDocs()); + } // Other definitions are loaded over, and the script checked again. - ensure("others loaded", service.loadDefinitions(definitions + "\ndeclare function extraThing(): number\n", why)); + UNSCOPED_INFO("others loaded"); + CHECK(service.loadDefinitions(definitions + "\ndeclare function extraThing(): number\n", why)); service.check(script); - ensure("checked again against them", service.typeChecks() > checked); + UNSCOPED_INFO("checked again against them"); + CHECK(service.typeChecks() > checked); service.loadDefinitions(definitions, why); } - template<> template<> - void alluauservice_object::test<28>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a local's type is hinted at any depth and offered as a refactor at its line", "[alluauservice]") { - set_test_name("a local's type is found however deep its scope, and the refactor at a line reads that line's hint as the whole text's has it"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local a = 1\n" "local function f()\n" " local b = \"x\"\n" @@ -830,7 +1109,8 @@ namespace tut { all += std::to_string(hint.line) + hint.text + ";"; } - ensure_equals("every local's, at every depth", all, std::string("0: number;2: string;4: number;")); + UNSCOPED_INFO("every local's, at every depth"); + CHECK(all == std::string("0: number;2: string;4: number;")); for (const auto& [line, column, type] : { std::tuple{ 0, 6, "number" }, std::tuple{ 2, 10, "string" }, std::tuple{ 4, 14, "number" } }) { bool offered = false; @@ -838,15 +1118,15 @@ namespace tut { offered |= fix.title == std::string("Declare it as '") + type + "'"; } - ensure("line " + std::to_string(line) + ": declared as " + type, offered); + UNSCOPED_INFO("line " + std::to_string(line) + ": declared as " + type); + REQUIRE(offered); } } - template<> template<> - void alluauservice_object::test<29>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: each script its own module and configuration; the oldest of many let go", "[alluauservice]") { - set_test_name("each script its own module: moving between a few finds each checked as it was left, each with its own configuration"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string a = "local a: number = 1\nprint(a)\n"; const std::string b = "local b: string = \"x\"\nprint(b)\n"; ALLuauConfig loose; @@ -868,7 +1148,8 @@ namespace tut service.setConfig(loose); service.check(b); } - ensure_equals("turn and turn about, each as it was left", service.typeChecks(), both); + UNSCOPED_INFO("turn and turn about, each as it was left"); + CHECK(service.typeChecks() == both); // Past a few, the one asked of longest ago is let go of. for (const char* other : { "c", "d", "e" }) { @@ -880,23 +1161,25 @@ namespace tut service.setDocument("b"); service.setConfig(loose); service.check(b); - ensure_equals("one of the last few kept", service.typeChecks(), now); + UNSCOPED_INFO("one of the last few kept"); + CHECK(service.typeChecks() == now); service.setDocument("a"); service.setConfig(strict); service.check(a); - ensure_equals("the one asked of longest ago let go of", service.typeChecks(), now + 1); + UNSCOPED_INFO("the one asked of longest ago let go of"); + CHECK(service.typeChecks() == now + 1); // The module of no name is apart from them all. service.setDocument(""); service.setConfig(loose); service.check(a); - ensure_equals("unnamed: its own", service.typeChecks(), now + 2); + UNSCOPED_INFO("unnamed: its own"); + CHECK(service.typeChecks() == now + 2); } - template<> template<> - void alluauservice_object::test<30>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a module put ahead of the script is passed over by the names and the hints", "[alluauservice]") { - set_test_name("lines nobody reads -- a module put ahead of the script -- are passed over by the names and the hints"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string text = "local __modules = {}\n" "__modules[\"m\"] = (function()\nlocal inner = 1\nreturn inner\nend)()\n" "local own = __modules[\"m\"]\nprint(own)\n"; @@ -906,29 +1189,32 @@ namespace tut { in_module |= token.span.line >= 1 && token.span.line <= 4; } - ensure("no names coloured in the module", !in_module); + UNSCOPED_INFO("no names coloured in the module"); + CHECK_FALSE(in_module); bool hinted_module = false, hinted_own = false; for (const ALScriptInlayHint& hint : service.inlayHints(text, false, true)) { hinted_module |= hint.line >= 1 && hint.line <= 4; hinted_own |= hint.line == 5; } - ensure("no hints in the module", !hinted_module); - ensure("the script's own hinted", hinted_own); + UNSCOPED_INFO("no hints in the module"); + CHECK_FALSE(hinted_module); + UNSCOPED_INFO("the script's own hinted"); + CHECK(hinted_own); service.setPassedOver({}); in_module = false; for (const ALScriptSemanticToken& token : service.semanticTokens(text)) { in_module |= token.span.line >= 1 && token.span.line <= 4; } - ensure("with nothing passed over, coloured", in_module); + UNSCOPED_INFO("with nothing passed over, coloured"); + CHECK(in_module); } - template<> template<> - void alluauservice_object::test<31>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: required modules are checked on their own and not again for a script edit", "[alluauservice]") { - set_test_name("a script's requires kept as calls reach its modules, each checked on its own and not again for an edit of the script alone"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); ALLuauConfig config; service.setDocument("uses"); service.setConfig(config); @@ -945,32 +1231,36 @@ namespace tut typed |= problem.file.empty() && problem.line == 2; module_said |= problem.file == "disk:/lib/util.luau" && problem.line == 5; } - ensure("the module's types reach the script: " + said(problems), typed); - ensure("the module's own problem is the module's, in its lines: " + said(problems), module_said); + UNSCOPED_INFO("the module's types reach the script: " + said(problems)); + CHECK(typed); + UNSCOPED_INFO("the module's own problem is the module's, in its lines: " + said(problems)); + CHECK(module_said); // The script typed in: only the script is checked again. const size_t modules_before = service.modulesChecked(); service.check(script + "print(s)\n"); - ensure_equals("the script alone", service.modulesChecked() - modules_before, size_t(1)); + UNSCOPED_INFO("the script alone"); + CHECK(service.modulesChecked() - modules_before == size_t(1)); // The module changed: it, and the script that requires it. modules.modules[0].text = "local M = {}\nfunction M.twice(n: number): string\n return tostring(n * 2)\nend\nreturn M\n"; service.setModules(modules); const size_t again = service.modulesChecked(); const ALScriptProblems now = service.check(script + "print(s)\n"); - ensure_equals("both", service.modulesChecked() - again, size_t(2)); + UNSCOPED_INFO("both"); + CHECK(service.modulesChecked() - again == size_t(2)); bool still = false; for (const ALScriptProblem& problem : now) { still |= problem.line == 2 && problem.file.empty() && problem.severity == ALScriptProblem::Severity::Error; } - ensure("and what it gives now fits: " + said(now), !still); + UNSCOPED_INFO("and what it gives now fits: " + said(now)); + CHECK_FALSE(still); service.setDocument(""); } - template<> template<> - void alluauservice_object::test<32>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the outline lists handlers as their events and timers with what is inside", "[alluauservice]") { - set_test_name("the outline lists a handler put on an event as the event, a timer's as a timer, each with what is inside it"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "LLEvents:on(\"touch_start\", function(events)\n" " local who = events[1]\n" @@ -983,21 +1273,44 @@ namespace tut std::vector outline = service.outline(script); std::string names; for (const ALScriptOutlineEntry& e : outline) names += " " + e.name + llformat("@%d", e.depth); - ensure_equals("five entries:" + names, outline.size(), size_t(5)); - ensure("the touch as an event", outline[0].name == "touch_start" && outline[0].kind == ALScriptSymbolKind::Event && outline[0].depth == 0); - ensure("named where its event is", outline[0].nameSpan.line == 0 && outline[0].nameSpan.column == 12); - ensure("spanning the call", outline[0].span.line == 0 && outline[0].span.endLine == 3); - ensure("what it holds, one deeper", outline[1].name == "greet" && outline[1].depth == 1); - ensure("once, too", outline[2].name == "listen" && outline[2].kind == ALScriptSymbolKind::Event); - ensure_equals("a timer, how often", outline[3].name, std::string("timer every 2.5")); - ensure_equals("a timer whose delay the script works out", outline[4].name, std::string("timer once")); + UNSCOPED_INFO("five entries:" + names); + REQUIRE(outline.size() == size_t(5)); + { + INFO("the touch as an event"); + CHECK(outline[0].name == "touch_start"); + CHECK(outline[0].kind == ALScriptSymbolKind::Event); + CHECK(outline[0].depth == 0); + } + { + INFO("named where its event is"); + CHECK(outline[0].nameSpan.line == 0); + CHECK(outline[0].nameSpan.column == 12); + } + { + INFO("spanning the call"); + CHECK(outline[0].span.line == 0); + CHECK(outline[0].span.endLine == 3); + } + { + INFO("what it holds, one deeper"); + CHECK(outline[1].name == "greet"); + CHECK(outline[1].depth == 1); + } + { + INFO("once, too"); + CHECK(outline[2].name == "listen"); + CHECK(outline[2].kind == ALScriptSymbolKind::Event); + } + UNSCOPED_INFO("a timer, how often"); + CHECK(outline[3].name == std::string("timer every 2.5")); + UNSCOPED_INFO("a timer whose delay the script works out"); + CHECK(outline[4].name == std::string("timer once")); } - template<> template<> - void alluauservice_object::test<33>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: every SLua template Script Studio offers checks without an error", "[alluauservice]") { - set_test_name("every template Script Studio offers for a new SLua script checks without an error"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); // Nonstrict, as the grid compiles: the new solver checking strict // cannot yet push an overloaded function's parameter types into a // function given to it, so every LLEvents:on there is an error it @@ -1007,8 +1320,14 @@ namespace tut service.setConfig(grid); llifstream in(std::string(AL_SCRIPT_TEMPLATES_DIR) + "/slua.xml", std::ios::in | std::ios::binary); LLSD templates; - ensure("read", in.is_open() && LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE && templates.isArray()); - ensure("some", templates.size() > 0); + { + INFO("read"); + CHECK(in.is_open()); + CHECK(LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE); + REQUIRE(templates.isArray()); + } + UNSCOPED_INFO("some"); + REQUIRE(templates.size() > 0); for (LLSD::array_const_iterator it = templates.beginArray(); it != templates.endArray(); ++it) { const std::string name = (*it)["name"].asString(); @@ -1021,15 +1340,15 @@ namespace tut wrong += llformat(" [%d:%d] %s", problem.line, problem.column, problem.message.c_str()); } } - ensure(name + ":" + wrong, wrong.empty()); + UNSCOPED_INFO(name + ":" + wrong); + REQUIRE(wrong.empty()); } } - template<> template<> - void alluauservice_object::test<34>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the studio's own lints answer to config and --!nolint and selene and NOLINT", "[alluauservice]") { - set_test_name("the studio's own lints beside Luau's: found, off or an error as configured, turned off by --!nolint and selene's comments, and a NOLINT answers to its name"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const auto found = [&](const std::string& text) { ALScriptProblems out; for (const ALScriptProblem& p : service.check(text)) @@ -1043,49 +1362,84 @@ namespace tut }; const std::string script = "local x = 1\nx = x + 1\nlocal t = { n = \"a\" }\nt.n = t.n .. \"b\"\nx = 1 + x\nprint(x, t.n)\n"; ALScriptProblems said = found(script); - ensure_equals("two: x and t.n", said.size(), size_t(2)); - ensure("the first, a note in its words: " + said[0].message, - said[0].key == "LuauLintSlCompoundAssign" && said[0].severity == ALScriptProblem::Severity::Note && said[0].line == 1 && - said[0].args == std::vector{ "x", "+" } && said[0].source == ALScriptProblem::Source::Lint); - ensure("a field, joined: " + said[1].message, said[1].line == 3 && said[1].args == std::vector{ "t.n", ".." }); - ensure("a NOLINT answers to its name", ALScriptFixes::suppressed(said[0], "x = x + 1 -- NOLINT(SlCompoundAssign)", "", true) && - !ALScriptFixes::suppressed(said[0], "x = x + 1 -- NOLINT(LocalUnused)", "", true)); + UNSCOPED_INFO("two: x and t.n"); + REQUIRE(said.size() == size_t(2)); + { + INFO("the first, a note in its words: " + said[0].message); + CHECK(said[0].key == "LuauLintSlCompoundAssign"); + CHECK(said[0].severity == ALScriptProblem::Severity::Note); + CHECK(said[0].line == 1); + CHECK(said[0].args == std::vector{ "x", "+" }); + CHECK(said[0].source == ALScriptProblem::Source::Lint); + } + { + INFO("a field, joined: " + said[1].message); + CHECK(said[1].line == 3); + CHECK(said[1].args == std::vector{ "t.n", ".." }); + } + { + INFO("a NOLINT answers to its name"); + CHECK(ALScriptFixes::suppressed(said[0], "x = x + 1 -- NOLINT(SlCompoundAssign)", "", true)); + CHECK_FALSE(ALScriptFixes::suppressed(said[0], "x = x + 1 -- NOLINT(LocalUnused)", "", true)); + } ALLuauConfig config; config.slLints = 0; service.setConfig(config); - ensure("off", found(script).empty()); + UNSCOPED_INFO("off"); + REQUIRE(found(script).empty()); config.slLints = ALScriptLintPass::defaults(); config.slFatalLints = ALScriptLintPass::bit("SlCompoundAssign"); service.setConfig(config); said = found(script); - ensure("an error", said.size() == 2 && said[0].severity == ALScriptProblem::Severity::Error); + { + INFO("an error"); + REQUIRE(said.size() == 2); + CHECK(said[0].severity == ALScriptProblem::Severity::Error); + } config.slFatalLints = 0; config.lintErrors = true; service.setConfig(config); said = found(script); - ensure("every lint an error", said.size() == 2 && said[0].severity == ALScriptProblem::Severity::Error); + { + INFO("every lint an error"); + REQUIRE(said.size() == 2); + CHECK(said[0].severity == ALScriptProblem::Severity::Error); + } service.setConfig(ALLuauConfig()); said = found("--!nolint SlCompoundAssign\n" + script); - ensure("--!nolint by its name, and no word that Luau does not know it", said.empty()); - ensure("--!nolint alone", found("--!nolint\n" + script).empty()); + UNSCOPED_INFO("--!nolint by its name, and no word that Luau does not know it"); + REQUIRE(said.empty()); + UNSCOPED_INFO("--!nolint alone"); + REQUIRE(found("--!nolint\n" + script).empty()); said = found("--!nolint SlNope\n" + script); - ensure("a name that is none is still said", said.size() == 3 && said[0].key.rfind("LuauLintDirective", 0) == 0); + { + INFO("a name that is none is still said"); + REQUIRE(said.size() == 3); + CHECK(said[0].key.rfind("LuauLintDirective", 0) == 0); + } said = found("local x = 1\nx = x + 1 -- selene: allow(SlCompoundAssign)\nprint(x)\n"); - ensure("selene's comment beside it", said.empty()); + UNSCOPED_INFO("selene's comment beside it"); + REQUIRE(said.empty()); said = found("--# selene: deny(SlCompoundAssign)\nlocal x = 1\nx = x + 1\nprint(x)\n"); - ensure("selene's for the whole file, an error", said.size() == 1 && said[0].severity == ALScriptProblem::Severity::Error); - ensure("selene's words for the studio's own", ALSeleneFilters::slLints("SlCompoundAssign") == ALScriptLintPass::bit("SlCompoundAssign") && - ALSeleneFilters::slLints("unused_variable") == 0); + { + INFO("selene's for the whole file, an error"); + REQUIRE(said.size() == 1); + CHECK(said[0].severity == ALScriptProblem::Severity::Error); + } + { + INFO("selene's words for the studio's own"); + CHECK(ALSeleneFilters::slLints("SlCompoundAssign") == ALScriptLintPass::bit("SlCompoundAssign")); + CHECK(ALSeleneFilters::slLints("unused_variable") == 0); + } } - template<> template<> - void alluauservice_object::test<35>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: SlNumberTruth: a number used as a condition; not a number? or a comparison", "[alluauservice]") { - set_test_name("SlNumberTruth: a number read as a condition, through and, or and brackets, and under not; not number?, nor a comparison"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const auto found = [&](const std::string& text) { std::vector out; for (const ALScriptProblem& p : service.check(text)) @@ -1105,10 +1459,15 @@ namespace tut "local m: number? = nil\n" "if m then print(m) end\n" "if n ~= 0 or not (n > 1) then print(b, t) end\n"); - ensure_equals("four: " + llformat("%zu", said.size()), said.size(), size_t(4)); - ensure("each: " + said[0] + "|" + said[1] + "|" + said[2] + "|" + said[3], - said[0] == "LuauLintSlNumberTruth n 1" && said[1] == "LuauLintSlNumberTruth n 2" && said[2] == "LuauLintSlNumberTruthNot n 3" && - said[3] == "LuauLintSlNumberTruth n 4"); + UNSCOPED_INFO("four: " + llformat("%zu", said.size())); + REQUIRE(said.size() == size_t(4)); + { + INFO("each: " + said[0] + "|" + said[1] + "|" + said[2] + "|" + said[3]); + CHECK(said[0] == "LuauLintSlNumberTruth n 1"); + CHECK(said[1] == "LuauLintSlNumberTruth n 2"); + CHECK(said[2] == "LuauLintSlNumberTruthNot n 3"); + CHECK(said[3] == "LuauLintSlNumberTruth n 4"); + } // A local by what it is given, where the old solver's nonstrict // mode says any: a copy of a number is one; one given a string, or // joined to one, or a parameter, is not. @@ -1128,15 +1487,18 @@ namespace tut { all += one + "|"; } - ensure("a copy only: " + all, given.size() == (newSolver ? 3u : 1u) && given[0] == "LuauLintSlNumberTruth copy 7" && - (!newSolver || (given[1] == "LuauLintSlStringTruth s 7" && given[2] == "LuauLintSlNumberTruth j 7"))); + { + INFO("a copy only: " + all); + REQUIRE(given.size() == (newSolver ? 3u : 1u)); + CHECK(given[0] == "LuauLintSlNumberTruth copy 7"); + CHECK((!newSolver || (given[1] == "LuauLintSlStringTruth s 7" && given[2] == "LuauLintSlNumberTruth j 7"))); + } } - template<> template<> - void alluauservice_object::test<36>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: no mode means the solver's own default; nonstrict means nonstrict in either", "[alluauservice]") { - set_test_name("a configuration that says no mode is checked in the solver's own: the new one's strict, as strict as the old one's nonstrict; one that says nonstrict is nonstrict under either"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local function f(): number? return nil end\nlocal x = f() + 1\nprint(x)\n"; const auto nil_said = [&] { for (const ALScriptProblem& p : service.check(script)) @@ -1156,21 +1518,23 @@ namespace tut const bool asked_nonstrict = nil_said(); if (newSolver) { - ensure("strict where nothing says", by_default); - ensure("nonstrict where asked", !asked_nonstrict); + UNSCOPED_INFO("strict where nothing says"); + CHECK(by_default); + UNSCOPED_INFO("nonstrict where asked"); + CHECK_FALSE(asked_nonstrict); } else { - ensure_equals("nonstrict, whether asked or not", by_default, asked_nonstrict); + UNSCOPED_INFO("nonstrict, whether asked or not"); + CHECK(by_default == asked_nonstrict); } service.setConfig(ALLuauConfig()); } - template<> template<> - void alluauservice_object::test<37>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: SlNilSentinel: finds against -1 or 0 and llcompat's against nil only", "[alluauservice]") { - set_test_name("SlNilSentinel: a find against -1, or ordered against 0 or -1, on either side, a local given one, table's and string's; llcompat's against nil; not what can hold"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const auto found = [&](const std::string& text) { std::vector out; for (const ALScriptProblem& p : service.check(text)) @@ -1210,7 +1574,8 @@ namespace tut { listed += each + "\n"; } - ensure("each, an error:\n" + listed, said == expected); + UNSCOPED_INFO("each, an error:\n" + listed); + CHECK(said == expected); // What can hold: nil against SLua's, -1 and order against // llcompat's, an order that asks where, a local given a number // too, stepped, or nil before it is given llcompat's, a @@ -1226,14 +1591,14 @@ namespace tut "m = llcompat.SubStringIndex(\"a\", \"b\")\n" "local function f(p) return p == -1 end\n" "if j == -1 or n == -1 or m == nil or ll.GetInventoryType(\"x\") == -1 then print(f(3)) end\n"); - ensure("none: " + (none.empty() ? std::string() : none[0]), none.empty()); + UNSCOPED_INFO("none: " + (none.empty() ? std::string() : none[0])); + REQUIRE(none.empty()); } - template<> template<> - void alluauservice_object::test<38>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a shape is the untyped outline from a parse alone even of a broken text", "[alluauservice]") { - set_test_name("a shape is the outline from a parse alone: the same symbols and spans, no types; a text broken in places as far as it parses; the service as it was"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "local count = 1\n" "local function half(n: number)\n" @@ -1243,23 +1608,36 @@ namespace tut "function LLEvents.touch_start(n: number) end\n"; const std::vector outline = service.outline(script); const std::vector shape = ALLuauService::shape(script); - ensure_equals("as many", shape.size(), outline.size()); + UNSCOPED_INFO("as many"); + REQUIRE(shape.size() == outline.size()); for (size_t i = 0; i < shape.size(); ++i) { - ensure("the same symbol, where it was", shape[i].name == outline[i].name && shape[i].kind == outline[i].kind && shape[i].span == outline[i].span && - shape[i].depth == outline[i].depth); - ensure("no type", shape[i].detail.empty()); + { + INFO("the same symbol, where it was"); + REQUIRE(shape[i].name == outline[i].name); + REQUIRE(shape[i].kind == outline[i].kind); + REQUIRE(shape[i].span == outline[i].span); + REQUIRE(shape[i].depth == outline[i].depth); + } + UNSCOPED_INFO("no type"); + REQUIRE(shape[i].detail.empty()); } - ensure("the outline still typed", service.outline(script)[0].detail == "number"); + UNSCOPED_INFO("the outline still typed"); + CHECK(service.outline(script)[0].detail == "number"); const std::vector broken = ALLuauService::shape("local function a()\n x = \nend\nlocal function b()\nend\n"); - ensure("broken in a: both still there", broken.size() == 2 && broken[0].name == "a" && broken[1].name == "b" && broken[1].span.line == 3); + { + INFO("broken in a: both still there"); + REQUIRE(broken.size() == 2); + CHECK(broken[0].name == "a"); + CHECK(broken[1].name == "b"); + CHECK(broken[1].span.line == 3); + } } - template<> template<> - void alluauservice_object::test<39>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: completion says what fits and where brackets go; methods only after a colon", "[alluauservice]") { - set_test_name("completion says what fits where it goes and where a call's brackets go, and offers a method only after a colon"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const auto entry = [](const std::vector& found, const std::string& text) -> const ALScriptCompletion* { for (const ALScriptCompletion& c : found) { @@ -1276,10 +1654,17 @@ namespace tut std::vector found = service.complete(typed, 2, 18); const ALScriptCompletion* count = entry(found, "count"); const ALScriptCompletion* name = entry(found, "name"); - ensure("both offered", count && name); - ensure("the number fits", count->fits); - ensure("the string does not", !name->fits); - ensure("said where an expression goes", count->context == ALScriptCompletion::Context::Expression); + { + INFO("both offered"); + REQUIRE(count); + REQUIRE(name); + } + UNSCOPED_INFO("the number fits"); + CHECK(count->fits); + UNSCOPED_INFO("the string does not"); + CHECK_FALSE(name->fits); + UNSCOPED_INFO("said where an expression goes"); + CHECK(count->context == ALScriptCompletion::Context::Expression); // Where a call's brackets go: between them for what takes // something, after them for what takes nothing, none for what is no @@ -1292,30 +1677,57 @@ namespace tut const ALScriptCompletion* say = entry(found, "print"); const ALScriptCompletion* now = entry(found, "now"); const ALScriptCompletion* lib = entry(found, "ll"); - ensure("print, between its brackets", say && say->brackets == ALScriptCompletion::Brackets::Inside); - ensure("now, after an empty pair", now && now->brackets == ALScriptCompletion::Brackets::After); - ensure("ll, no call to say anything of", lib && lib->brackets == ALScriptCompletion::Brackets::Guess); + { + INFO("print, between its brackets"); + REQUIRE(say); + CHECK(say->brackets == ALScriptCompletion::Brackets::Inside); + } + { + INFO("now, after an empty pair"); + REQUIRE(now); + CHECK(now->brackets == ALScriptCompletion::Brackets::After); + } + { + INFO("ll, no call to say anything of"); + REQUIRE(lib); + CHECK(lib->brackets == ALScriptCompletion::Brackets::Guess); + } found = service.complete(calls, 3, 30); const ALScriptCompletion* twice = entry(found, "twice"); - ensure("twice, wanted as it is", twice && twice->fits); - ensure("so no brackets", twice->brackets == ALScriptCompletion::Brackets::None); + { + INFO("twice, wanted as it is"); + REQUIRE(twice); + CHECK(twice->fits); + } + UNSCOPED_INFO("so no brackets"); + CHECK(twice->brackets == ALScriptCompletion::Brackets::None); // A method is called with a colon, and after a dot is no use. The // grid's own declare theirs as fields taking self, which are called // with a colon all the same. const std::string events = "local a = LLEvents.\nlocal b = LLEvents:\nlocal t = {}\nfunction t:greet(): number return 1 end\nlocal d = t:\n"; found = service.complete(events, 0, 19); - ensure("none of LLEvents' methods after a dot", !entry(found, "on") && !entry(found, "off") && !entry(found, "handlers")); + { + INFO("none of LLEvents' methods after a dot"); + REQUIRE_FALSE(entry(found, "on")); + REQUIRE_FALSE(entry(found, "off")); + REQUIRE_FALSE(entry(found, "handlers")); + } found = service.complete(events, 1, 19); - ensure("all of them after a colon", entry(found, "on") && entry(found, "off") && entry(found, "handlers")); - ensure("a script's own method after a colon", entry(service.complete(events, 4, 12), "greet") != nullptr); + { + INFO("all of them after a colon"); + REQUIRE(entry(found, "on")); + REQUIRE(entry(found, "off")); + REQUIRE(entry(found, "handlers")); + } + UNSCOPED_INFO("a script's own method after a colon"); + REQUIRE(entry(service.complete(events, 4, 12), "greet") != nullptr); } - template<> template<> - void alluauservice_object::test<40>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a function an argument wants is offered as a stub to fill in", "[alluauservice]") { - set_test_name("a function an argument wants is offered written out, to fill in: Luau's own stub, and an overloaded callee's from the overload chosen"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const auto stub = [](const std::vector& found) -> const ALScriptCompletion* { for (const ALScriptCompletion& c : found) { @@ -1330,41 +1742,50 @@ namespace tut const std::string each = "local function each(n: number, f: (i: number) -> ()) end\neach(3, f)\n"; const std::vector for_each = service.complete(each, 1, 8); const ALScriptCompletion* own = stub(for_each); - ensure("Luau's stub offered", own != nullptr); - ensure_equals("its head", own->text, std::string("function(i: number)")); - ensure_equals("its body to fill, then its end", own->snippet, std::string("function(i: number)\n $0\nend")); - ensure("of the type wanted", own->fits); + UNSCOPED_INFO("Luau's stub offered"); + REQUIRE(own != nullptr); + UNSCOPED_INFO("its head"); + CHECK(own->text == std::string("function(i: number)")); + UNSCOPED_INFO("its body to fill, then its end"); + CHECK(own->snippet == std::string("function(i: number)\n $0\nend")); + UNSCOPED_INFO("of the type wanted"); + CHECK(own->fits); const std::string events = "LLEvents:on(\"touch_start\", f)\n"; const std::vector for_event = service.complete(events, 0, 27); const ALScriptCompletion* handler = stub(for_event); - ensure("the event's handler offered", handler != nullptr); - ensure_equals("taking what the event gives", handler->text, std::string("function(detected: {DetectedEvent})")); - ensure("a body to fill: " + handler->snippet, handler->snippet.find("\n $0\nend") != std::string::npos); + UNSCOPED_INFO("the event's handler offered"); + REQUIRE(handler != nullptr); + UNSCOPED_INFO("taking what the event gives"); + CHECK(handler->text == std::string("function(detected: {DetectedEvent})")); + UNSCOPED_INFO("a body to fill: " + handler->snippet); + REQUIRE(handler->snippet.find("\n $0\nend") != std::string::npos); } - template<> template<> - void alluauservice_object::test<41>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: after --! only the hot comments the studio knows are offered", "[alluauservice]") { - set_test_name("after --! the comments that say how a script is checked are offered, those the studio offers alone"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::vector found = service.complete("--!st\nlocal x = 1\n", 0, 3); std::string said; for (const ALScriptCompletion& c : found) { said += " " + c.text; - ensure("a word of the language: " + c.text, c.kind == ALScriptSymbolKind::Keyword); - ensure("in a hot comment: " + c.text, c.context == ALScriptCompletion::Context::HotComment); - ensure("one the studio offers: " + c.text, ALScriptLexicon::isLuauHotComment(c.text)); + UNSCOPED_INFO("a word of the language: " + c.text); + REQUIRE(c.kind == ALScriptSymbolKind::Keyword); + UNSCOPED_INFO("in a hot comment: " + c.text); + REQUIRE(c.context == ALScriptCompletion::Context::HotComment); + UNSCOPED_INFO("one the studio offers: " + c.text); + REQUIRE(ALScriptLexicon::isLuauHotComment(c.text)); } - ensure_equals("each of them:" + said, found.size(), std::size(ALScriptLexicon::LUAU_HOT_COMMENTS)); + UNSCOPED_INFO("each of them:" + said); + REQUIRE(found.size() == std::size(ALScriptLexicon::LUAU_HOT_COMMENTS)); } - template<> template<> - void alluauservice_object::test<42>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: fragments answer completion and signature help over the statement typed", "[alluauservice]") { - set_test_name("as a script is typed, a completion and signature help are answered over the statement typed, and nothing is checked whole"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const auto offered = [](const std::vector& found, const std::string& text) -> const ALScriptCompletion* { for (const ALScriptCompletion& c : found) { @@ -1376,7 +1797,8 @@ namespace tut return nullptr; }; std::string why; - ensure("docs loaded", service.loadDocs("{\"@sl-slua/global/ll.Say\": {\"documentation\": \"Says it.\", \"learn_more_link\": \"x\"}," + UNSCOPED_INFO("docs loaded"); + CHECK(service.loadDocs("{\"@sl-slua/global/ll.Say\": {\"documentation\": \"Says it.\", \"learn_more_link\": \"x\"}," " \"@sl-slua/global/ll\": {\"documentation\": \"The library.\", \"learn_more_link\": \"x\"}," " \"@sl-slua/global/vector\": {\"documentation\": \"Makes one.\", \"learn_more_link\": \"x\"}," " \"@sl-slua/global/vector.magnitude\": {\"documentation\": \"Its length.\", \"learn_more_link\": \"x\"}}", @@ -1389,71 +1811,107 @@ namespace tut service.complete(base, 0, 0); size_t checks = service.typeChecks(); service.complete(base + "local n: number = co\n", 3, 18); - ensure("off: checked whole", service.typeChecks() > checks); - ensure_equals("a call of a field documented as the field, not what holds it", service.signature(base + "ll.Say(0, )\n", 3, 10).documentation, + UNSCOPED_INFO("off: checked whole"); + CHECK(service.typeChecks() > checks); + UNSCOPED_INFO("a call of a field documented as the field, not what holds it"); + CHECK(service.signature(base + "ll.Say(0, )\n", 3, 10).documentation == std::string("Says it.")); // `vector` is a table that is also called, whose fields Luau gives // no symbol of their own. - ensure_equals("and of a callable table's field", service.signature(base + "local m = vector.magnitude()\n", 3, 27).documentation, + UNSCOPED_INFO("and of a callable table's field"); + CHECK(service.signature(base + "local m = vector.magnitude()\n", 3, 27).documentation == std::string("Its length.")); service.setFragments(true); - ensure("on", service.fragments()); + UNSCOPED_INFO("on"); + CHECK(service.fragments()); service.check(base); service.complete(base, 0, 0); checks = service.typeChecks(); const size_t parts = service.fragmentsChecked(); std::vector found = service.complete(base + "local n: number = co\n", 3, 18); const ALScriptCompletion* count = offered(found, "count"); - ensure("a local above offered", count != nullptr); - ensure("and fitting where a number is wanted", count->fits); - ensure("a function in scope offered", offered(found, "twice") != nullptr); - ensure("a table's fields", offered(service.complete(base + "local y = t.\n", 3, 12), "b") != nullptr); + UNSCOPED_INFO("a local above offered"); + REQUIRE(count != nullptr); + UNSCOPED_INFO("and fitting where a number is wanted"); + CHECK(count->fits); + UNSCOPED_INFO("a function in scope offered"); + REQUIRE(offered(found, "twice") != nullptr); + UNSCOPED_INFO("a table's fields"); + REQUIRE(offered(service.complete(base + "local y = t.\n", 3, 12), "b") != nullptr); found = service.complete(base + "LLEvents:\n", 3, 9); - ensure("an object's methods", offered(found, "on") && offered(found, "off")); + { + INFO("an object's methods"); + REQUIRE(offered(found, "on")); + REQUIRE(offered(found, "off")); + } ALScriptSignature said = service.signature(base + "ll.Say(0, )\n", 3, 10); - ensure("a call's signature", said.found); - ensure_equals("its label", said.label, std::string("ll.Say(channel: number, msg: string): ()")); - ensure_equals("at its second argument", said.active, 1); - ensure_equals("documented as the field it calls", said.documentation, std::string("Says it.")); + UNSCOPED_INFO("a call's signature"); + REQUIRE(said.found); + UNSCOPED_INFO("its label"); + CHECK(said.label == std::string("ll.Say(channel: number, msg: string): ()")); + UNSCOPED_INFO("at its second argument"); + CHECK(said.active == 1); + UNSCOPED_INFO("documented as the field it calls"); + CHECK(said.documentation == std::string("Says it.")); said = service.signature(base + "if twice() then end\n", 3, 9); - ensure("a call in a condition", said.found && said.label.find("twice(n: number)") == 0); + { + INFO("a call in a condition"); + REQUIRE(said.found); + CHECK(said.label.find("twice(n: number)") == 0); + } - ensure("nothing offered in a comment", service.complete(base + "-- tw\n", 3, 5).empty()); - ensure("no signature outside a call", !service.signature(base + "local q = 1\n", 3, 8).found); - ensure_equals("a callable table's field over the fragment too", service.signature(base + "local m = vector.magnitude()\n", 3, 27).documentation, + UNSCOPED_INFO("nothing offered in a comment"); + REQUIRE(service.complete(base + "-- tw\n", 3, 5).empty()); + UNSCOPED_INFO("no signature outside a call"); + REQUIRE_FALSE(service.signature(base + "local q = 1\n", 3, 8).found); + UNSCOPED_INFO("a callable table's field over the fragment too"); + CHECK(service.signature(base + "local m = vector.magnitude()\n", 3, 27).documentation == std::string("Its length.")); - ensure_equals("none of it checked whole", service.typeChecks(), checks); - ensure_equals("each answered over its statement", service.fragmentsChecked() - parts, size_t(6)); + UNSCOPED_INFO("none of it checked whole"); + CHECK(service.typeChecks() == checks); + UNSCOPED_INFO("each answered over its statement"); + CHECK(service.fragmentsChecked() - parts == size_t(6)); service.setFragments(false); } - template<> template<> - void alluauservice_object::test<43>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the whole script answers where a fragment cannot and requires survive one", "[alluauservice]") { - set_test_name("where a fragment cannot answer the whole script does; one stopped answers nothing; and the script's requires survive one"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); service.setFragments(true); // Nothing checked yet to patch: the whole script. service.setDocument("unchecked"); size_t checks = service.typeChecks(); size_t parts = service.fragmentsChecked(); const std::string base = "local count: number = 1\n"; - ensure("answered", !service.complete(base + "local x = co\n", 1, 10).empty()); - ensure("checked whole", service.typeChecks() > checks && service.fragmentsChecked() == parts); + UNSCOPED_INFO("answered"); + REQUIRE_FALSE(service.complete(base + "local x = co\n", 1, 10).empty()); + { + INFO("checked whole"); + CHECK(service.typeChecks() > checks); + CHECK(service.fragmentsChecked() == parts); + } // Stopped part way: nothing, and said so; the next, unstopped, // answers over its statement. ALLuauService::Stop stop = ALLuauService::newStop(); ALLuauService::cancel(stop); service.setStop(stop); - ensure("nothing", service.complete(base + "local y = co\n", 1, 10).empty()); - ensure("stopped", service.stopped()); + UNSCOPED_INFO("nothing"); + REQUIRE(service.complete(base + "local y = co\n", 1, 10).empty()); + UNSCOPED_INFO("stopped"); + CHECK(service.stopped()); service.setStop(nullptr); checks = service.typeChecks(); - ensure("answered again", !service.complete(base + "local y = co\n", 1, 10).empty() && !service.stopped()); - ensure_equals("over its statement", service.typeChecks(), checks); + { + INFO("answered again"); + REQUIRE_FALSE(service.complete(base + "local y = co\n", 1, 10).empty()); + CHECK_FALSE(service.stopped()); + } + UNSCOPED_INFO("over its statement"); + CHECK(service.typeChecks() == checks); // Something typed above since the last check, in another block // than the line typed: Luau sets beside the last check only the @@ -1468,14 +1926,20 @@ namespace tut parts = service.fragmentsChecked(); const std::string changed = "local function twice(n: string): string return n end\nlocal function main()\n local count = 1\n local x = tw\nend\n"; const std::vector found = service.complete(changed, 3, 14); - ensure("answered", offers(found, "twice")); - ensure("whole", service.typeChecks() > checks && service.fragmentsChecked() == parts); + UNSCOPED_INFO("answered"); + CHECK(offers(found, "twice")); + { + INFO("whole"); + CHECK(service.typeChecks() > checks); + CHECK(service.fragmentsChecked() == parts); + } bool string_now = false; for (const ALScriptCompletion& c : found) { string_now |= c.text == "twice" && c.detail.find("string") != std::string::npos; } - ensure("as it is now", string_now); + UNSCOPED_INFO("as it is now"); + CHECK(string_now); // A module the script requires, its requires traced when the text // was parsed: a fragment between that parse and the next check @@ -1493,18 +1957,19 @@ namespace tut service.complete(script, 1, 0); const std::string typed = script + "local n = util.\n"; service.hover(typed, 1, 8); - ensure("the module's function, over the fragment", offers(service.complete(typed, 2, 15), "twice")); + UNSCOPED_INFO("the module's function, over the fragment"); + CHECK(offers(service.complete(typed, 2, 15), "twice")); service.setFragments(false); - ensure("and the whole check after it finds it too", offers(service.complete(typed, 2, 15), "twice")); + UNSCOPED_INFO("and the whole check after it finds it too"); + CHECK(offers(service.complete(typed, 2, 15), "twice")); service.setModules({}); service.setDocument(""); } - template<> template<> - void alluauservice_object::test<44>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: warming after a check lets the next keystroke check nothing whole", "[alluauservice]") { - set_test_name("warming after a check makes what a fragment is checked against the text's, so the next keystroke checks nothing whole"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); service.setFragments(true); service.setDocument("warmed"); const std::string base = "local count: number = 1\n"; @@ -1514,29 +1979,34 @@ namespace tut service.check(base); size_t checks = service.typeChecks(); service.complete(typed, 1, 18); - ensure_equals("unwarmed", service.typeChecks() - checks, size_t(newSolver ? 0 : 1)); + UNSCOPED_INFO("unwarmed"); + CHECK(service.typeChecks() - checks == size_t(newSolver ? 0 : 1)); service.check(base); checks = service.typeChecks(); service.warm(base); - ensure_equals("the old solver's autocomplete module checked; the new solver's check is it", service.typeChecks() - checks, + UNSCOPED_INFO("the old solver's autocomplete module checked; the new solver's check is it"); + CHECK(service.typeChecks() - checks == size_t(newSolver ? 0 : 1)); service.warm(base); - ensure_equals("and not again for the same text", service.typeChecks() - checks, size_t(newSolver ? 0 : 1)); + UNSCOPED_INFO("and not again for the same text"); + CHECK(service.typeChecks() - checks == size_t(newSolver ? 0 : 1)); checks = service.typeChecks(); const size_t parts = service.fragmentsChecked(); - ensure("answered", offers(service.complete(typed, 1, 18), "count")); - ensure_equals("over its statement", service.fragmentsChecked() - parts, size_t(1)); - ensure_equals("nothing whole", service.typeChecks(), checks); + UNSCOPED_INFO("answered"); + CHECK(offers(service.complete(typed, 1, 18), "count")); + UNSCOPED_INFO("over its statement"); + CHECK(service.fragmentsChecked() - parts == size_t(1)); + UNSCOPED_INFO("nothing whole"); + CHECK(service.typeChecks() == checks); service.setFragments(false); service.setDocument(""); } - template<> template<> - void alluauservice_object::test<45>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: several modules required are checked in parallel once each", "[alluauservice]") { - set_test_name("a script requiring several modules has them checked together, each on a thread of its own as what it requires is checked: the same problems, each module checked once, and not again for an edit of the script alone"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); service.setDocument("several"); service.setConfig(ALLuauConfig()); ALLuauService::Modules modules; @@ -1555,7 +2025,8 @@ namespace tut script += "local s: string = m1.f(1) + m2.f(2) + m3.f(3) + m4.f(4)\n"; const size_t before = service.modulesChecked(); const ALScriptProblems problems = service.check(script); - ensure_equals("each module and the script checked once", service.modulesChecked() - before, size_t(5)); + UNSCOPED_INFO("each module and the script checked once"); + CHECK(service.modulesChecked() - before == size_t(5)); S32 in_modules = 0; bool own = false; for (const ALScriptProblem& problem : problems) @@ -1563,24 +2034,27 @@ namespace tut in_modules += problem.file.rfind("disk:/lib/m", 0) == 0 && problem.line == 2 && problem.source == ALScriptProblem::Source::Types ? 1 : 0; own |= problem.file.empty() && problem.line == 5 && problem.severity == ALScriptProblem::Severity::Error; } - ensure_equals("each module's own type error, in its lines: " + said(problems), in_modules, 4); - ensure("and the script's: " + said(problems), own); + UNSCOPED_INFO("each module's own type error, in its lines: " + said(problems)); + CHECK(in_modules == 4); + UNSCOPED_INFO("and the script's: " + said(problems)); + CHECK(own); // The script typed in: only it, on its own. const size_t again = service.modulesChecked(); const ALScriptProblems edited = service.check(script + "print(s)\n"); - ensure_equals("the script alone", service.modulesChecked() - again, size_t(1)); - ensure("its own problem still: " + said(edited), std::any_of(edited.begin(), edited.end(), [](const ALScriptProblem& problem) { + UNSCOPED_INFO("the script alone"); + CHECK(service.modulesChecked() - again == size_t(1)); + UNSCOPED_INFO("its own problem still: " + said(edited)); + REQUIRE(std::any_of(edited.begin(), edited.end(), [](const ALScriptProblem& problem) { return problem.file.empty() && problem.line == 5; })); service.setDocument(""); } - template<> template<> - void alluauservice_object::test<46>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a required module's lints are told in its lines without fixes and only once", "[alluauservice]") { - set_test_name("a module checked with the script has its lints told too, Luau's and the studio's own, in its lines and with no fix, while the script's keep theirs; not again for an edit of the script alone"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); service.setDocument("linted"); service.setConfig(ALLuauConfig()); ALLuauService::Modules modules; @@ -1608,23 +2082,33 @@ namespace tut return nullptr; }; const ALScriptProblem* unused = in(problems, "disk:/lib/util.luau", "LocalUnused", 0); - ensure("Luau's lint of the module, in its lines: " + said(problems), unused != nullptr); + UNSCOPED_INFO("Luau's lint of the module, in its lines: " + said(problems)); + REQUIRE(unused != nullptr); const ALScriptProblem* compound = in(problems, "disk:/lib/util.luau", "SlCompoundAssign", 2); - ensure("the studio's own of the module: " + said(problems), compound != nullptr); - ensure("neither with a fix, being another file's", unused->fixes.empty() && compound->fixes.empty()); + UNSCOPED_INFO("the studio's own of the module: " + said(problems)); + REQUIRE(compound != nullptr); + { + INFO("neither with a fix, being another file's"); + REQUIRE(unused->fixes.empty()); + REQUIRE(compound->fixes.empty()); + } const ALScriptProblem* own = in(problems, "", "SlCompoundAssign", 2); - ensure("the script's own, with its fix: " + said(problems), own && !own->fixes.empty()); + { + INFO("the script's own, with its fix: " + said(problems)); + REQUIRE(own); + REQUIRE_FALSE(own->fixes.empty()); + } const ALScriptProblems edited = service.check(script + "print(util)\n"); - ensure("an edit of the script alone: not the module's again", !in(edited, "disk:/lib/util.luau", "LocalUnused", 0)); + UNSCOPED_INFO("an edit of the script alone: not the module's again"); + REQUIRE_FALSE(in(edited, "disk:/lib/util.luau", "LocalUnused", 0)); service.setDocument(""); } - template<> template<> - void alluauservice_object::test<47>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a required module's exports are kept from its type in declaration order", "[alluauservice]") { - set_test_name("a module checked with the script has what it exports kept as its type says, in the order its fields were declared, for what is offered from it"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); service.setDocument("exports"); service.setConfig(ALLuauConfig()); const std::string made = "--!strict\n" @@ -1641,14 +2125,17 @@ namespace tut service.setModules(modules); service.check("local made = require(\"made\")\nprint(made.alpha)\n"); const std::optional> kept = ALLuauExports::checkedOf("disk:/lib/made.luau", made); - ensure("kept as its type says", kept.has_value()); + UNSCOPED_INFO("kept as its type says"); + REQUIRE(kept.has_value()); std::string listed; for (const std::string& name : *kept) { listed += " " + name; } - ensure("each field, in order:" + listed, *kept == std::vector({ "alpha", "beta" })); - ensure("not for another text", !ALLuauExports::checkedOf("disk:/lib/made.luau", made + "\n")); + UNSCOPED_INFO("each field, in order:" + listed); + CHECK(*kept == std::vector({ "alpha", "beta" })); + UNSCOPED_INFO("not for another text"); + REQUIRE_FALSE(ALLuauExports::checkedOf("disk:/lib/made.luau", made + "\n")); // Its type no table, as a nonstrict check may leave one: nothing // kept, and its text alone says what it exports. @@ -1657,15 +2144,15 @@ namespace tut modules.reaches.push_back({ "", "loose", "disk:/lib/loose.luau" }); service.setModules(modules); service.check("local made = require(\"made\")\nlocal loose = require(\"loose\")\nprint(made.alpha, loose)\n"); - ensure("no table, nothing kept", !ALLuauExports::checkedOf("disk:/lib/loose.luau", loose)); + UNSCOPED_INFO("no table, nothing kept"); + REQUIRE_FALSE(ALLuauExports::checkedOf("disk:/lib/loose.luau", loose)); service.setDocument(""); } - template<> template<> - void alluauservice_object::test<48>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a module returning other than one value is an error at the require of it", "[alluauservice]") { - set_test_name("a module that returns other than one value is said at the require that asks for it, as Luau's require would stop there: what the bundle need not check as it runs"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); for (const char* mode : { "", "nonstrict", "strict" }) { ALLuauConfig config; @@ -1697,21 +2184,24 @@ namespace tut in_mid |= problem.file == "disk:/lib/mid.luau" && problem.line == 0 && problem.key == "LuauModuleNotOneValue"; } const std::string where = std::string(mode) + ": " + said(problems); - ensure("two values: said at its require, " + where, two); - ensure("no value: said at its require, " + where, none); - ensure("one: nothing, " + where, !one); + UNSCOPED_INFO("two values: said at its require, " + where); + REQUIRE(two); + UNSCOPED_INFO("no value: said at its require, " + where); + REQUIRE(none); + UNSCOPED_INFO("one: nothing, " + where); + REQUIRE_FALSE(one); // Told as the module is checked, as all of a module's are: the // first time, its text unchanged after. - ensure("a module's require of it: said in that module, " + where, in_mid || *mode); + UNSCOPED_INFO("a module's require of it: said in that module, " + where); + REQUIRE((in_mid || *mode)); } service.setDocument(""); } - template<> template<> - void alluauservice_object::test<49>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a module a question checked first is told at the next check once", "[alluauservice]") { - set_test_name("a module a question checked before the script's check is told at that check all the same -- its lints, a require in it of what returns two values, what it exports -- and once"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); service.setDocument("asked first"); service.setConfig(ALLuauConfig()); const std::string util = "--!strict\n" // 0 @@ -1738,22 +2228,29 @@ namespace tut }); }; const ALScriptProblems problems = service.check(script); - ensure_equals("the check found the script checked", service.typeChecks(), checks); - ensure("the module's lint: " + said(problems), told(problems, "LocalUnused", 1)); - ensure("its require of what returns two values: " + said(problems), told(problems, "LuauModuleNotOneValue", 2)); + UNSCOPED_INFO("the check found the script checked"); + CHECK(service.typeChecks() == checks); + UNSCOPED_INFO("the module's lint: " + said(problems)); + CHECK(told(problems, "LocalUnused", 1)); + UNSCOPED_INFO("its require of what returns two values: " + said(problems)); + CHECK(told(problems, "LuauModuleNotOneValue", 2)); const std::optional> kept = ALLuauExports::checkedOf("disk:/lib/asked.luau", util); - ensure("what it exports, as its type says", kept && std::find(kept->begin(), kept->end(), "n") != kept->end()); + { + INFO("what it exports, as its type says"); + REQUIRE(kept); + REQUIRE(std::find(kept->begin(), kept->end(), "n") != kept->end()); + } // Told, and so not told again for the same text. const ALScriptProblems again = service.check(script); - ensure("once: " + said(again), !told(again, "LocalUnused", 1)); + UNSCOPED_INFO("once: " + said(again)); + CHECK_FALSE(told(again, "LocalUnused", 1)); service.setDocument(""); } - template<> template<> - void alluauservice_object::test<50>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: hover on a type says it; only the script's own has a declaration to go to", "[alluauservice]") { - set_test_name("a hover on the name of a type the definitions declare says it, with nowhere in the script to go -- its place is a line of their file -- while the script's own goes where it declares it"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string script = "--!strict\n" // 0 "local id: uuid = ll.GetOwner()\n" // 1 "local r: rotation = ll.GetRot()\n" // 2 @@ -1761,20 +2258,35 @@ namespace tut "local p: Pair = { a = 1 }\n" // 4 "print(id, r, p)\n"; // 5 ALScriptHover hover = service.hover(script, 1, 11); // `uuid`, an extern type - ensure("uuid, said: " + hover.label, hover.found && hover.label.rfind("type uuid", 0) == 0); - ensure("nowhere to go", !hover.hasDefinition); + { + INFO("uuid, said: " + hover.label); + REQUIRE(hover.found); + CHECK(hover.label.rfind("type uuid", 0) == 0); + } + UNSCOPED_INFO("nowhere to go"); + CHECK_FALSE(hover.hasDefinition); hover = service.hover(script, 2, 10); // `rotation`, an alias - ensure("rotation, said: " + hover.label, hover.found && hover.label.rfind("type rotation", 0) == 0); - ensure("nowhere to go either", !hover.hasDefinition); + { + INFO("rotation, said: " + hover.label); + REQUIRE(hover.found); + CHECK(hover.label.rfind("type rotation", 0) == 0); + } + UNSCOPED_INFO("nowhere to go either"); + CHECK_FALSE(hover.hasDefinition); hover = service.hover(script, 4, 10); // `Pair` - ensure("the script's own, where it declares it", hover.found && hover.hasDefinition && hover.definitionFile.empty() && hover.definitionLine == 3); + { + INFO("the script's own, where it declares it"); + REQUIRE(hover.found); + CHECK(hover.hasDefinition); + REQUIRE(hover.definitionFile.empty()); + CHECK(hover.definitionLine == 3); + } } - template<> template<> - void alluauservice_object::test<51>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a module's exports leave out keyword-named fields by type as by text", "[alluauservice]") { - set_test_name("what a module checked exports, as its type says, leaves out a field no script could write after a dot, as what its text says does"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); service.setDocument("keywords"); service.setConfig(ALLuauConfig()); const std::string util = "--!strict\nreturn { [\"end\"] = 1, ok = 2, [\"not\"] = 3, export = 4 }\n"; @@ -1784,17 +2296,19 @@ namespace tut service.setModules(modules); service.check("local util = require(\"util\")\nprint(util.ok)\n"); const std::optional> kept = ALLuauExports::checkedOf("disk:/lib/keywords.luau", util); - ensure("kept as its type says", kept.has_value()); - ensure("the names, a word only a statement begins with among them, and no keyword", *kept == std::vector({ "ok", "export" })); - ensure("as its text alone says", ALLuauExports::of(util) == std::vector({ "ok", "export" })); + UNSCOPED_INFO("kept as its type says"); + REQUIRE(kept.has_value()); + UNSCOPED_INFO("the names, a word only a statement begins with among them, and no keyword"); + CHECK(*kept == std::vector({ "ok", "export" })); + UNSCOPED_INFO("as its text alone says"); + CHECK(ALLuauExports::of(util) == std::vector({ "ok", "export" })); service.setDocument(""); } - template<> template<> - void alluauservice_object::test<52>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: a module's lints follow the configuration of the script requiring it", "[alluauservice]") { - set_test_name("a module's lints are told as the configuration of the script that requires it says, a module having none of its own: one it turns off not told, one it makes an error an error, every one where all are, Luau's and the studio's own alike"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const std::string module = "local unusedThing = 1\n" // 0 "local function helper() end\n" // 1 "local n = 1\n" // 2 @@ -1828,19 +2342,36 @@ namespace tut some.lints &= ~ALLuauConfig::lintBit("LocalUnused"); some.fatalLints |= ALLuauConfig::lintBit("FunctionUnused"); const ALScriptProblems problems = checked("some", "disk:/lib/some.luau", some); - ensure("one it turns off, not told: " + said(problems), !told(problems, "LocalUnused")); + UNSCOPED_INFO("one it turns off, not told: " + said(problems)); + REQUIRE_FALSE(told(problems, "LocalUnused")); const ALScriptProblem* fatal = told(problems, "FunctionUnused"); - ensure("one it makes an error, an error: " + said(problems), fatal && fatal->severity == ALScriptProblem::Severity::Error); + { + INFO("one it makes an error, an error: " + said(problems)); + REQUIRE(fatal); + CHECK(fatal->severity == ALScriptProblem::Severity::Error); + } const ALScriptProblem* compound = told(problems, "SlCompoundAssign"); - ensure("the studio's own as it says, no error: " + said(problems), compound && compound->severity != ALScriptProblem::Severity::Error); + { + INFO("the studio's own as it says, no error: " + said(problems)); + REQUIRE(compound); + CHECK(compound->severity != ALScriptProblem::Severity::Error); + } ALLuauConfig all; all.lintErrors = true; const ALScriptProblems errors = checked("all", "disk:/lib/all.luau", all); const ALScriptProblem* unused = told(errors, "LocalUnused"); - ensure("every one an error, Luau's: " + said(errors), unused && unused->severity == ALScriptProblem::Severity::Error); + { + INFO("every one an error, Luau's: " + said(errors)); + REQUIRE(unused); + CHECK(unused->severity == ALScriptProblem::Severity::Error); + } compound = told(errors, "SlCompoundAssign"); - ensure("and the studio's own: " + said(errors), compound && compound->severity == ALScriptProblem::Severity::Error); + { + INFO("and the studio's own: " + said(errors)); + REQUIRE(compound); + CHECK(compound->severity == ALScriptProblem::Severity::Error); + } service.setDocument(""); } @@ -1871,11 +2402,44 @@ namespace tut return false; } - template<> template<> - void alluauservice_object::test<71>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: LLEvents:on shows the form its event name fits and the first with no name", "[alluauservice]") { - set_test_name("the constants the grid's VM sets and the definitions leave out are known, as numbers: none an unknown global, in either mode"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); + const std::string script = "LLEvents:on(\"listen\", function(channel, name, id, msg) end)\n" // 0 + "LLEvents:on(\"touch_start\", function(detected) end)\n" // 1 + "LLEvents:once(\"timer\", function() end)\n" // 2 + "LLEvents:on(\n"; // 3 + const auto shown = [&](S32 line, S32 column) { + const ALScriptSignature sig = service.signature(script, line, column); + return sig.found && sig.overloads.size() == 2 ? sig.overload : -1; + }; + const ALScriptSignature listen = service.signature(script, 0, 24); + { + INFO("found, both forms"); + REQUIRE(listen.found); + REQUIRE(listen.overloads.size() == 2); + } + { + INFO("listen, not a detected event: " + listen.label); + CHECK(listen.overload == 1); + REQUIRE(listen.parameters.size() == 2); + REQUIRE(listen.parameters[1].find("Detected") == std::string::npos); + } + UNSCOPED_INFO("touch_start, a detected event"); + CHECK(shown(1, 29) == 0); + UNSCOPED_INFO("once too"); + CHECK(shown(2, 25) == 1); + UNSCOPED_INFO("in the name, as it is written"); + CHECK(shown(0, 14) == 1); + UNSCOPED_INFO("no name yet"); + CHECK(shown(3, 12) == 0); + } + + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the grid's undocumented constants are known as numbers in either mode", "[alluauservice]") + { + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); std::string read = "print("; std::string summed = "--!strict\nlocal total: number = 0"; for (const char* name : UNDOCUMENTED_CONSTANTS) @@ -1888,91 +2452,83 @@ namespace tut ALScriptProblems problems = service.check(read); for (const char* name : UNDOCUMENTED_CONSTANTS) { - ensure(std::string("known: ") + name + "\n" + said(problems), !unknownGlobal(problems, name)); + UNSCOPED_INFO(std::string("known: ") + name + "\n" + said(problems)); + REQUIRE_FALSE(unknownGlobal(problems, name)); } - ensure_equals("no errors: " + said(problems), alluauservice_data::errors(problems), size_t(0)); + UNSCOPED_INFO("no errors: " + said(problems)); + CHECK(alluauservice_data::errors(problems) == size_t(0)); problems = service.check(summed); - ensure_equals("each a number, strictly: " + said(problems), alluauservice_data::errors(problems), size_t(0)); + UNSCOPED_INFO("each a number, strictly: " + said(problems)); + CHECK(alluauservice_data::errors(problems) == size_t(0)); } - template<> template<> - void alluauservice_object::test<72>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: TRUE and FALSE are still unknown globals", "[alluauservice]") { - set_test_name("TRUE and FALSE are still unknown globals: the VM sets every builtins.txt constant but those two, which SLua says as true and false"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); const ALScriptProblems problems = service.check("print(PRIM_MATERIAL_DENSITY, TRUE, FALSE)\n"); - ensure("TRUE unknown: " + said(problems), unknownGlobal(problems, "TRUE")); - ensure("FALSE unknown: " + said(problems), unknownGlobal(problems, "FALSE")); - ensure("the constant beside them known: " + said(problems), !unknownGlobal(problems, "PRIM_MATERIAL_DENSITY")); + UNSCOPED_INFO("TRUE unknown: " + said(problems)); + CHECK(unknownGlobal(problems, "TRUE")); + UNSCOPED_INFO("FALSE unknown: " + said(problems)); + CHECK(unknownGlobal(problems, "FALSE")); + UNSCOPED_INFO("the constant beside them known: " + said(problems)); + CHECK_FALSE(unknownGlobal(problems, "PRIM_MATERIAL_DENSITY")); } - template<> template<> - void alluauservice_object::test<73>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: completion leaves out the undocumented constants but not a script's own", "[alluauservice]") { - set_test_name("completion does not offer the undocumented constants, as the grid's own lists do not, but offers the documented ones beside them and a script's own of the same name"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); std::string script = "print(PRIM_MATERIAL_)\n"; std::vector found = service.complete(script, 0, 20); - ensure("a documented one offered", alluauservice_data::offers(found, "PRIM_MATERIAL_FLESH")); - ensure("an undocumented one not", !alluauservice_data::offers(found, "PRIM_MATERIAL_DENSITY")); + UNSCOPED_INFO("a documented one offered"); + CHECK(alluauservice_data::offers(found, "PRIM_MATERIAL_FLESH")); + UNSCOPED_INFO("an undocumented one not"); + CHECK_FALSE(alluauservice_data::offers(found, "PRIM_MATERIAL_DENSITY")); script = "print(SKY_)\n"; found = service.complete(script, 0, 10); - ensure("nor its kin", !alluauservice_data::offers(found, "SKY_MIE_CONFIG") && !alluauservice_data::offers(found, "SKY_RAYLEIGH_CONFIG")); + { + INFO("nor its kin"); + CHECK_FALSE(alluauservice_data::offers(found, "SKY_MIE_CONFIG")); + CHECK_FALSE(alluauservice_data::offers(found, "SKY_RAYLEIGH_CONFIG")); + } script = "local REZ_TORQUE = 1\nprint(REZ_)\n"; found = service.complete(script, 1, 10); - ensure("a script's own of the name offered", alluauservice_data::offers(found, "REZ_TORQUE")); + UNSCOPED_INFO("a script's own of the name offered"); + CHECK(alluauservice_data::offers(found, "REZ_TORQUE")); } - template<> template<> - void alluauservice_object::test<74>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: definitions declaring an undocumented constant win and the rest stay known", "[alluauservice]") { - set_test_name("where the definitions declare one of the undocumented constants themselves, theirs stands, and the rest are still known"); ALLuauService own; std::string why; own.setNewSolver(newSolver, why); - ensure("loads: " + why, own.loadDefinitions(definitions + "\ndeclare REZ_TORQUE: string\n", why)); + UNSCOPED_INFO("loads: " + why); + CHECK(own.loadDefinitions(definitions + "\ndeclare REZ_TORQUE: string\n", why)); const ALScriptProblems problems = own.check("--!strict\nlocal s: string = REZ_TORQUE\nlocal n: number = SKY_MIE_CONFIG\nprint(s, n)\n"); - ensure_equals("theirs, a string; ours, a number: " + said(problems), alluauservice_data::errors(problems), size_t(0)); + UNSCOPED_INFO("theirs, a string; ours, a number: " + said(problems)); + CHECK(alluauservice_data::errors(problems) == size_t(0)); } - template<> template<> - void alluauservice_object::test<75>() + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the undocumented constants survive a solver change and hover as numbers", "[alluauservice]") { - set_test_name("the undocumented constants come back with the definitions when the solver changes, and hover says a number"); - ensure("definitions loaded: " + error, loaded); + UNSCOPED_INFO("definitions loaded: " + error); + CHECK(loaded); std::string why; - ensure("the other solver: " + why, service.setNewSolver(!newSolver, why)); + UNSCOPED_INFO("the other solver: " + why); + REQUIRE(service.setNewSolver(!newSolver, why)); ALScriptProblems problems = service.check("print(REZ_TORQUE)\n"); - ensure("known there: " + said(problems), !unknownGlobal(problems, "REZ_TORQUE")); - ensure("and back: " + why, service.setNewSolver(newSolver, why)); + UNSCOPED_INFO("known there: " + said(problems)); + CHECK_FALSE(unknownGlobal(problems, "REZ_TORQUE")); + UNSCOPED_INFO("and back: " + why); + CHECK(service.setNewSolver(newSolver, why)); problems = service.check("print(REZ_TORQUE)\n"); - ensure("known again: " + said(problems), !unknownGlobal(problems, "REZ_TORQUE")); + UNSCOPED_INFO("known again: " + said(problems)); + CHECK_FALSE(unknownGlobal(problems, "REZ_TORQUE")); const ALScriptHover hover = service.hover("print(REZ_TORQUE)\n", 0, 8); - ensure("hover finds it", hover.found); - ensure("a number: " + hover.label, hover.label.find("number") != std::string::npos); - } - - template<> template<> - void alluauservice_object::test<53>() - { - set_test_name("LLEvents:on's signature is the form its event's name fits: a detected event's handler given the detected table, another's " - "its own arguments; the first form where no name is written yet"); - ensure("definitions loaded: " + error, loaded); - const std::string script = "LLEvents:on(\"listen\", function(channel, name, id, msg) end)\n" // 0 - "LLEvents:on(\"touch_start\", function(detected) end)\n" // 1 - "LLEvents:once(\"timer\", function() end)\n" // 2 - "LLEvents:on(\n"; // 3 - const auto shown = [&](S32 line, S32 column) { - const ALScriptSignature sig = service.signature(script, line, column); - return sig.found && sig.overloads.size() == 2 ? sig.overload : -1; - }; - const ALScriptSignature listen = service.signature(script, 0, 24); - ensure("found, both forms", listen.found && listen.overloads.size() == 2); - ensure("listen, not a detected event: " + listen.label, listen.overload == 1 && listen.parameters.size() == 2 && - listen.parameters[1].find("Detected") == std::string::npos); - ensure_equals("touch_start, a detected event", shown(1, 29), 0); - ensure_equals("once too", shown(2, 25), 1); - ensure_equals("in the name, as it is written", shown(0, 14), 1); - ensure_equals("no name yet", shown(3, 12), 0); + UNSCOPED_INFO("hover finds it"); + REQUIRE(hover.found); + UNSCOPED_INFO("a number: " + hover.label); + REQUIRE(hover.label.find("number") != std::string::npos); } } diff --git a/indra/alscript/tests/almasterburst_test.cpp b/indra/alscript/tests/almasterburst_test.cpp index c82b2e2d27..8a0e2901d2 100644 --- a/indra/alscript/tests/almasterburst_test.cpp +++ b/indra/alscript/tests/almasterburst_test.cpp @@ -26,7 +26,7 @@ #include "../masters/almasterburst.h" -#include "../test/lltut.h" +#include #include #include @@ -34,7 +34,7 @@ #include #include -namespace tut +namespace { struct almasterburst_data { @@ -52,39 +52,54 @@ namespace tut static F64 before(F64 time) { return std::nextafter(time, -std::numeric_limits::infinity()); } }; - typedef test_group almasterburst_group; - typedef almasterburst_group::object almasterburst_object; - almasterburst_group almasterburst_instance("almasterburst"); - - template<> template<> - void almasterburst_object::test<1>() + TEST_CASE_METHOD(almasterburst_data, "almasterburst: a single file goes once only after the quiet time with nothing changed", "[almasterburst]") { - set_test_name("a single file goes once nothing has changed for the quiet time, and once only"); ALMasterBurst burst; - ensure("nothing waits at first", burst.empty() && !burst.dueAt()); - ensure("and nothing goes", burst.release(100.0).empty()); + { + INFO("nothing waits at first"); + REQUIRE(burst.empty()); + REQUIRE_FALSE(burst.dueAt()); + } + UNSCOPED_INFO("and nothing goes"); + REQUIRE(burst.release(100.0).empty()); burst.changed("/s/door.lsl", 100.0); - ensure("it waits", burst.waiting("/s/door.lsl") && burst.size() == 1); - ensure_equals("due a second on", *burst.dueAt(), 101.0); - ensure("not before", burst.release(100.5).empty() && burst.release(almasterburst_data::before(101.0)).empty()); - ensure_equals("then", almasterburst_data::joined(burst.release(101.0)), std::string("/s/door.lsl")); - ensure("and is gone", burst.empty() && !burst.dueAt()); - ensure("not given twice", burst.release(200.0).empty()); + { + INFO("it waits"); + CHECK(burst.waiting("/s/door.lsl")); + REQUIRE(burst.size() == 1); + } + UNSCOPED_INFO("due a second on"); + CHECK(*burst.dueAt() == 101.0); + { + INFO("not before"); + REQUIRE(burst.release(100.5).empty()); + REQUIRE(burst.release(almasterburst_data::before(101.0)).empty()); + } + UNSCOPED_INFO("then"); + CHECK(almasterburst_data::joined(burst.release(101.0)) == std::string("/s/door.lsl")); + { + INFO("and is gone"); + REQUIRE(burst.empty()); + REQUIRE_FALSE(burst.dueAt()); + } + UNSCOPED_INFO("not given twice"); + REQUIRE(burst.release(200.0).empty()); // A quiet time set shorter, and one less than nothing. burst.setQuiet(0.25); burst.changed("/s/door.lsl", 300.0); - ensure_equals("the quiet time set", *burst.dueAt(), 300.25); + UNSCOPED_INFO("the quiet time set"); + CHECK(*burst.dueAt() == 300.25); burst.setQuiet(-1.0); - ensure_equals("none at all is no wait", *burst.dueAt(), 300.0); - ensure_equals("and goes at once", almasterburst_data::joined(burst.release(300.0)), std::string("/s/door.lsl")); + UNSCOPED_INFO("none at all is no wait"); + CHECK(*burst.dueAt() == 300.0); + UNSCOPED_INFO("and goes at once"); + CHECK(almasterburst_data::joined(burst.release(300.0)) == std::string("/s/door.lsl")); } - template<> template<> - void almasterburst_object::test<2>() + TEST_CASE_METHOD(almasterburst_data, "almasterburst: a checkout of thirty files over 0.8 s is let go together once", "[almasterburst]") { - set_test_name("a checkout of thirty files over 0.8 s is let go together, once"); ALMasterBurst burst(1.0, 10.0); std::string expected; F64 last = 0.0; @@ -95,19 +110,26 @@ namespace tut burst.changed(path, last); expected += (expected.empty() ? "" : " ") + path; // Looked at as each is heard: the burst goes on. - ensure("none while it goes on", burst.release(last).empty()); + UNSCOPED_INFO("none while it goes on"); + REQUIRE(burst.release(last).empty()); + } + UNSCOPED_INFO("all thirty wait"); + REQUIRE(burst.size() == size_t(30)); + UNSCOPED_INFO("due a second after the last"); + CHECK(*burst.dueAt() == 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"); + CHECK(almasterburst_data::joined(burst.release(last + 1.0)) == expected); + { + INFO("nothing left"); + REQUIRE(burst.empty()); + REQUIRE(burst.release(last + 60.0).empty()); } - ensure_equals("all thirty wait", burst.size(), size_t(30)); - ensure_equals("due a second after the last", *burst.dueAt(), last + 1.0); - ensure("not a moment before", burst.release(almasterburst_data::before(last + 1.0)).empty()); - ensure_equals("all of them, in the order heard", almasterburst_data::joined(burst.release(last + 1.0)), expected); - ensure("nothing left", burst.empty() && burst.release(last + 60.0).empty()); } - template<> template<> - void almasterburst_object::test<3>() + TEST_CASE_METHOD(almasterburst_data, "almasterburst: a file rewritten every half-second still goes at the burst's longest", "[almasterburst]") { - set_test_name("a file rewritten every half-second still goes, once the burst has gone on its longest"); ALMasterBurst burst(1.0, 10.0); std::vector gone; for (int n = 0; n <= 50; ++n) @@ -116,73 +138,93 @@ namespace tut burst.changed("/s/build/out.lsl", now); if (n == 6) { - ensure_equals("due when quiet, while that is sooner", *burst.dueAt(), 4.0); + UNSCOPED_INFO("due when quiet, while that is sooner"); + REQUIRE(*burst.dueAt() == 4.0); } if (!burst.release(now).empty()) { gone.push_back(now); } } - ensure_equals("let go twice in 25 s", gone.size(), size_t(2)); - ensure_equals("first at ten seconds", gone[0], 10.0); + UNSCOPED_INFO("let go twice in 25 s"); + REQUIRE(gone.size() == size_t(2)); + UNSCOPED_INFO("first at ten seconds"); + CHECK(gone[0] == 10.0); // The next change began a burst of its own. - ensure_equals("then ten seconds after the change that followed", gone[1], 20.5); - ensure_equals("and once more due when that one is over", *burst.dueAt(), 25.0 + 1.0); + UNSCOPED_INFO("then ten seconds after the change that followed"); + CHECK(gone[1] == 20.5); + UNSCOPED_INFO("and once more due when that one is over"); + CHECK(*burst.dueAt() == 25.0 + 1.0); } - template<> template<> - void almasterburst_object::test<4>() + TEST_CASE_METHOD(almasterburst_data, "almasterburst: a file held past the quiet time stays and the others go first", "[almasterburst]") { - set_test_name("a file held past the quiet time stays, and the others go first"); ALMasterBurst burst(1.0, 10.0); burst.changed("/s/a.lsl", 0.0); // Found emptied: held a second and a half. burst.changed("/s/emptied.lsl", 0.25, 1.5); burst.changed("/s/b.lsl", 0.5); - ensure_equals("due when quiet", *burst.dueAt(), 1.5); - ensure_equals("the others go", almasterburst_data::joined(burst.release(1.5)), std::string("/s/a.lsl /s/b.lsl")); - ensure("the held one waits", burst.waiting("/s/emptied.lsl") && burst.size() == 1); - ensure_equals("due when its hold is over", *burst.dueAt(), 1.75); - ensure("not before", burst.release(almasterburst_data::before(1.75)).empty()); - ensure_equals("then it goes", almasterburst_data::joined(burst.release(1.75)), std::string("/s/emptied.lsl")); + UNSCOPED_INFO("due when quiet"); + CHECK(*burst.dueAt() == 1.5); + UNSCOPED_INFO("the others go"); + CHECK(almasterburst_data::joined(burst.release(1.5)) == std::string("/s/a.lsl /s/b.lsl")); + { + INFO("the held one waits"); + CHECK(burst.waiting("/s/emptied.lsl")); + REQUIRE(burst.size() == 1); + } + UNSCOPED_INFO("due when its hold is over"); + CHECK(*burst.dueAt() == 1.75); + UNSCOPED_INFO("not before"); + REQUIRE(burst.release(almasterburst_data::before(1.75)).empty()); + UNSCOPED_INFO("then it goes"); + CHECK(almasterburst_data::joined(burst.release(1.75)) == std::string("/s/emptied.lsl")); // 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); - ensure_equals("past its hold, the burst not over", *burst.dueAt(), 12.0); - ensure_equals("together then", almasterburst_data::joined(burst.release(12.0)), std::string("/s/emptied.lsl /s/c.lsl")); + UNSCOPED_INFO("past its hold, the burst not over"); + CHECK(*burst.dueAt() == 12.0); + UNSCOPED_INFO("together then"); + CHECK(almasterburst_data::joined(burst.release(12.0)) == std::string("/s/emptied.lsl /s/c.lsl")); } - template<> template<> - void almasterburst_object::test<5>() + TEST_CASE_METHOD(almasterburst_data, "almasterburst: a refilled file loses its hold and keeps its place; the last hold counts", "[almasterburst]") { - set_test_name("an emptied file filled again is held no longer, and keeps its place; the hold given last counts"); ALMasterBurst burst(1.0, 10.0); burst.changed("/s/a.lsl", 0.0, 1.5); burst.changed("/s/b.lsl", 0.125); burst.changed("/s/a.lsl", 0.25); - ensure_equals("due when quiet, the hold gone", *burst.dueAt(), 1.25); - ensure_equals("both, the refilled one in its first place", almasterburst_data::joined(burst.release(1.25)), + UNSCOPED_INFO("due when quiet, the hold gone"); + CHECK(*burst.dueAt() == 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); - ensure_equals("emptied later, held from then", *burst.dueAt(), 6.75); - ensure("not when quiet", burst.release(6.25).empty()); - ensure_equals("when the later hold is over", almasterburst_data::joined(burst.release(6.75)), std::string("/s/a.lsl")); + UNSCOPED_INFO("emptied later, held from then"); + CHECK(*burst.dueAt() == 6.75); + UNSCOPED_INFO("not when quiet"); + REQUIRE(burst.release(6.25).empty()); + UNSCOPED_INFO("when the later hold is over"); + CHECK(almasterburst_data::joined(burst.release(6.75)) == std::string("/s/a.lsl")); } - template<> template<> - void almasterburst_object::test<6>() + TEST_CASE_METHOD(almasterburst_data, "almasterburst: a file forgotten is not let go and the burst is of what is left", "[almasterburst]") { - set_test_name("a file forgotten is not let go, and the burst is of what is left"); ALMasterBurst burst(1.0, 10.0); burst.changed("/s/a.lsl", 0.0); burst.changed("/s/b.lsl", 0.25); burst.forget("/s/a.lsl"); burst.forget("/s/never.lsl"); - ensure("forgotten", !burst.waiting("/s/a.lsl") && burst.size() == 1); - ensure_equals("the rest goes", almasterburst_data::joined(burst.release(1.25)), std::string("/s/b.lsl")); + { + INFO("forgotten"); + CHECK_FALSE(burst.waiting("/s/a.lsl")); + REQUIRE(burst.size() == 1); + } + UNSCOPED_INFO("the rest goes"); + CHECK(almasterburst_data::joined(burst.release(1.25)) == std::string("/s/b.lsl")); // The first heard forgotten: the burst is as long as the rest's. burst.changed("/s/a.lsl", 100.0); @@ -194,41 +236,50 @@ namespace tut { burst.forget("/s/a.lsl"); } - ensure("not at the forgotten one's longest, nor before the rest's", burst.release(now).empty()); + UNSCOPED_INFO("not at the forgotten one's longest, nor before the rest's"); + REQUIRE(burst.release(now).empty()); } burst.changed("/s/busy.lsl", 115.0); - ensure_equals("due at the rest's longest", *burst.dueAt(), 115.0); - ensure_equals("which it goes at", almasterburst_data::joined(burst.release(115.0)), std::string("/s/busy.lsl")); + UNSCOPED_INFO("due at the rest's longest"); + CHECK(*burst.dueAt() == 115.0); + UNSCOPED_INFO("which it goes at"); + CHECK(almasterburst_data::joined(burst.release(115.0)) == std::string("/s/busy.lsl")); burst.changed("/s/a.lsl", 200.0); burst.changed("/s/b.lsl", 200.0, 1.5); burst.clear(); - ensure("cleared", burst.empty() && !burst.dueAt() && burst.release(300.0).empty()); + { + INFO("cleared"); + REQUIRE(burst.empty()); + REQUIRE_FALSE(burst.dueAt()); + REQUIRE(burst.release(300.0).empty()); + } } - template<> template<> - void almasterburst_object::test<7>() + TEST_CASE_METHOD(almasterburst_data, "almasterburst: Windows paths match in any case and separator and go as last spelled", "[almasterburst]") { - set_test_name("Windows's paths are one file in any case and with either separator; the spelling last given goes"); ALMasterBurst burst(1.0, 10.0); burst.changed("C:\\Scripts\\Door.lsl", 0.0); burst.changed("c:/scripts/door.LSL", 0.5); burst.changed("/s/Door.lsl", 0.5); burst.changed("/s/door.lsl", 0.5); - ensure_equals("one Windows file, and two others", burst.size(), size_t(3)); - ensure("found in any case", burst.waiting("C:/SCRIPTS/DOOR.LSL")); - ensure("but not another's", !burst.waiting("/S/door.lsl")); - ensure_equals("each once, as last spelled", almasterburst_data::joined(burst.release(1.5)), + UNSCOPED_INFO("one Windows file, and two others"); + REQUIRE(burst.size() == size_t(3)); + UNSCOPED_INFO("found in any case"); + CHECK(burst.waiting("C:/SCRIPTS/DOOR.LSL")); + UNSCOPED_INFO("but not another's"); + CHECK_FALSE(burst.waiting("/S/door.lsl")); + UNSCOPED_INFO("each once, as last spelled"); + CHECK(almasterburst_data::joined(burst.release(1.5)) == std::string("c:/scripts/door.LSL /s/Door.lsl /s/door.lsl")); burst.changed("C:\\Scripts\\Door.lsl", 2.0); burst.forget("c:\\SCRIPTS\\door.lsl"); - ensure("forgotten in any case", burst.empty()); + UNSCOPED_INFO("forgotten in any case"); + REQUIRE(burst.empty()); } - template<> template<> - void almasterburst_object::test<8>() + TEST_CASE_METHOD(almasterburst_data, "almasterburst: dueAt and release agree: nothing goes before it and something goes at it", "[almasterburst]") { - set_test_name("dueAt and release agree: nothing goes before it, and something goes at it"); ALMasterBurst burst(1.0, 4.0); // Changes to twenty files at times a step of 1/8 s apart, some of // them emptied, as a small generator picks them. @@ -251,15 +302,20 @@ namespace tut { break; } - ensure("nothing before it is due", burst.release(almasterburst_data::before(*due)).empty()); + UNSCOPED_INFO("nothing before it is due"); + REQUIRE(burst.release(almasterburst_data::before(*due)).empty()); const std::vector gone = burst.release(*due); - ensure("something when it is", !gone.empty()); + UNSCOPED_INFO("something when it is"); + REQUIRE_FALSE(gone.empty()); const std::set once(gone.begin(), gone.end()); - ensure_equals("each once", once.size(), gone.size()); + UNSCOPED_INFO("each once"); + REQUIRE(once.size() == gone.size()); for (const std::string& path : gone) { - ensure("only what was heard", heard.count(path) == 1); - ensure("and is waited on no longer", !burst.waiting(path)); + UNSCOPED_INFO("only what was heard"); + REQUIRE(heard.count(path) == 1); + UNSCOPED_INFO("and is waited on no longer"); + REQUIRE_FALSE(burst.waiting(path)); } given += gone.size(); } @@ -269,9 +325,12 @@ namespace tut } while (const std::optional due = burst.dueAt()) { - ensure("the last of them too", !burst.release(*due).empty()); + UNSCOPED_INFO("the last of them too"); + REQUIRE_FALSE(burst.release(*due).empty()); } - ensure("all let go in the end", burst.empty()); - ensure("many times over", given > 100); + UNSCOPED_INFO("all let go in the end"); + REQUIRE(burst.empty()); + UNSCOPED_INFO("many times over"); + CHECK(given > 100); } } diff --git a/indra/alscript/tests/almasterindex_test.cpp b/indra/alscript/tests/almasterindex_test.cpp index 6b826abe9d..6327e399c2 100644 --- a/indra/alscript/tests/almasterindex_test.cpp +++ b/indra/alscript/tests/almasterindex_test.cpp @@ -33,7 +33,7 @@ #include "llfile.h" #include "llsdserialize.h" -#include "../test/lltut.h" +#include #include #include @@ -43,7 +43,7 @@ #include #include -namespace tut +namespace { struct almasterindex_data { @@ -166,16 +166,11 @@ namespace tut } }; - typedef test_group almasterindex_group; - typedef almasterindex_group::object almasterindex_object; - almasterindex_group almasterindex_instance("almasterindex"); - - template<> template<> - void almasterindex_object::test<1>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: links go through the file and come back as they went", "[almasterindex]") { - set_test_name("links go through the file and come back as they went"); ALMasterIndex& made = open(); - ensure("none at first", made.empty()); + UNSCOPED_INFO("none at first"); + REQUIRE(made.empty()); ALMasterLink door = almasterindex_data::link(1, 2, "/s/door.lsl"); door.uses = { "disk:/s/util.lsl" }; door.hash = "abc"; @@ -186,51 +181,66 @@ namespace tut card.notecard = true; made.link({ door, card }); made.flush(); - ensure("written", !made.dirty()); + UNSCOPED_INFO("written"); + CHECK_FALSE(made.dirty()); ALMasterIndex& back = open(); - ensure_equals("both back", back.size(), size_t(2)); + UNSCOPED_INFO("both back"); + REQUIRE(back.size() == size_t(2)); const std::optional read = back.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); - ensure("the door's", read.has_value()); - ensure_equals("its master", read->master, std::string("/s/door.lsl")); - ensure_equals("its hash", read->hash, std::string("abc")); - ensure_equals("its stamp", read->stamp, S64(1234)); - ensure_equals("its base", read->base, almasterindex_data::key(9)); - ensure_equals("its uses", read->uses.size(), size_t(1)); - ensure_equals("its name", read->itemName, std::string("Door")); - ensure("the card's, a notecard's", back.linkOf(LLUUID::null, almasterindex_data::key(3))->notecard); - ensure_equals("found by its master", back.mastering("/s/door.lsl").size(), size_t(1)); - ensure_equals("and by what it read", back.affectedBy("disk:/s/util.lsl").size(), size_t(1)); - ensure_equals("in its object", back.linksIn(almasterindex_data::key(1)).size(), size_t(1)); - ensure_equals("in the inventory", back.linksIn(LLUUID::null).size(), size_t(1)); - ensure("read, nothing changed", !back.dirty()); + UNSCOPED_INFO("the door's"); + REQUIRE(read.has_value()); + UNSCOPED_INFO("its master"); + CHECK(read->master == std::string("/s/door.lsl")); + UNSCOPED_INFO("its hash"); + CHECK(read->hash == std::string("abc")); + UNSCOPED_INFO("its stamp"); + CHECK(read->stamp == S64(1234)); + UNSCOPED_INFO("its base"); + CHECK(read->base == almasterindex_data::key(9)); + UNSCOPED_INFO("its uses"); + REQUIRE(read->uses.size() == size_t(1)); + UNSCOPED_INFO("its name"); + CHECK(read->itemName == std::string("Door")); + UNSCOPED_INFO("the card's, a notecard's"); + CHECK(back.linkOf(LLUUID::null, almasterindex_data::key(3))->notecard); + UNSCOPED_INFO("found by its master"); + REQUIRE(back.mastering("/s/door.lsl").size() == size_t(1)); + UNSCOPED_INFO("and by what it read"); + REQUIRE(back.affectedBy("disk:/s/util.lsl").size() == size_t(1)); + UNSCOPED_INFO("in its object"); + REQUIRE(back.linksIn(almasterindex_data::key(1)).size() == size_t(1)); + UNSCOPED_INFO("in the inventory"); + REQUIRE(back.linksIn(LLUUID::null).size() == size_t(1)); + UNSCOPED_INFO("read, nothing changed"); + CHECK_FALSE(back.dirty()); } - template<> template<> - void almasterindex_object::test<2>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: an unreadable file holds no links and is kept until a link changes", "[almasterindex]") { - set_test_name("a file that will not read is no links, and is not written over until a link changes"); const std::string garbage = "this is not at all"; almasterindex_data::put(file, garbage); { ALMasterIndex& looked = open(); - ensure("no links", looked.empty()); - ensure("nothing to write", !looked.dirty()); + UNSCOPED_INFO("no links"); + REQUIRE(looked.empty()); + UNSCOPED_INFO("nothing to write"); + CHECK_FALSE(looked.dirty()); pass(10.0); index.reset(); } - ensure_equals("left as it was", text(), garbage); + UNSCOPED_INFO("left as it was"); + CHECK(text() == garbage); ALMasterIndex& changed = open(); changed.link(almasterindex_data::link(1, 2, "/s/door.lsl")); index.reset(); - ensure_equals("a link written over it", onDisk().size(), size_t(1)); + UNSCOPED_INFO("a link written over it"); + REQUIRE(onDisk().size() == size_t(1)); } - template<> template<> - void almasterindex_object::test<3>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: links orphaned long ago are let go of on reading and that is written", "[almasterindex]") { - set_test_name("links orphaned long ago are let go of as the file is read, and that is written"); ALMasterLinks links; ALMasterLink old = almasterindex_data::link(1, 1, "/s/old.lsl"); old.state = ALMasterLink::State::Orphaned; @@ -246,60 +256,77 @@ namespace tut almasterindex_data::put(file, out.str()); ALMasterIndex& read = open(); - ensure_equals("the old one gone", read.size(), size_t(2)); - ensure("not the recent one", read.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)).has_value()); - ensure("to be written", read.dirty()); - ensure_equals("nobody told: nobody listened yet", told, 0); + UNSCOPED_INFO("the old one gone"); + REQUIRE(read.size() == size_t(2)); + UNSCOPED_INFO("not the recent one"); + REQUIRE(read.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)).has_value()); + UNSCOPED_INFO("to be written"); + CHECK(read.dirty()); + UNSCOPED_INFO("nobody told: nobody listened yet"); + CHECK(told == 0); index.reset(); - ensure_equals("written without it", onDisk().size(), size_t(2)); + UNSCOPED_INFO("written without it"); + REQUIRE(onDisk().size() == size_t(2)); } - template<> template<> - void almasterindex_object::test<4>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: links made together are told once and one alone once", "[almasterindex]") { - set_test_name("links made together are told once, and one alone once"); ALMasterIndex& made = open(); made.link({ almasterindex_data::link(1, 1, "/s/a.lsl"), almasterindex_data::link(1, 2, "/s/b.lsl"), almasterindex_data::link(2, 3, "/s/c.lsl") }); - ensure_equals("three", made.size(), size_t(3)); - ensure_equals("told once", told, 1); + UNSCOPED_INFO("three"); + REQUIRE(made.size() == size_t(3)); + UNSCOPED_INFO("told once"); + CHECK(told == 1); made.link(almasterindex_data::link(2, 4, "/s/d.lsl")); - ensure_equals("and once more", told, 2); + UNSCOPED_INFO("and once more"); + CHECK(told == 2); made.link(std::vector()); - ensure_equals("none, not told", told, 2); + UNSCOPED_INFO("none, not told"); + CHECK(told == 2); made.link(almasterindex_data::link(1, 1, "/s/a2.lsl")); - ensure_equals("put over, not beside", made.size(), size_t(4)); - ensure_equals("the new master", made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))->master, std::string("/s/a2.lsl")); + UNSCOPED_INFO("put over, not beside"); + REQUIRE(made.size() == size_t(4)); + UNSCOPED_INFO("the new master"); + CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))->master == std::string("/s/a2.lsl")); } - template<> template<> - void almasterindex_object::test<5>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: a link let go of and links marked pending only where linked", "[almasterindex]") { - set_test_name("a link let go of, and links marked pending, those linked only"); ALMasterIndex& made = open(); made.link({ almasterindex_data::link(1, 1, "/s/a.lsl"), almasterindex_data::link(1, 2, "/s/b.lsl") }); told = 0; - ensure("let go of", made.unlink(almasterindex_data::key(1), almasterindex_data::key(1))); - ensure_equals("told", told, 1); - ensure("gone", !made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))); - ensure("one not linked is not", !made.unlink(almasterindex_data::key(1), almasterindex_data::key(1))); - ensure_equals("and not told", told, 1); + UNSCOPED_INFO("let go of"); + CHECK(made.unlink(almasterindex_data::key(1), almasterindex_data::key(1))); + UNSCOPED_INFO("told"); + CHECK(told == 1); + UNSCOPED_INFO("gone"); + REQUIRE_FALSE(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))); + UNSCOPED_INFO("one not linked is not"); + CHECK_FALSE(made.unlink(almasterindex_data::key(1), almasterindex_data::key(1))); + UNSCOPED_INFO("and not told"); + CHECK(told == 1); const size_t marked = made.markPending({ { almasterindex_data::key(1), almasterindex_data::key(1) }, { almasterindex_data::key(1), almasterindex_data::key(2) } }); - ensure_equals("the one linked", marked, size_t(1)); - ensure("pending", made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->state == ALMasterLink::State::Pending); - ensure_equals("told", told, 2); - ensure_equals("none linked, none marked", made.markPending({ { almasterindex_data::key(5), almasterindex_data::key(5) } }), size_t(0)); - ensure_equals("and not told", told, 2); + UNSCOPED_INFO("the one linked"); + CHECK(marked == size_t(1)); + UNSCOPED_INFO("pending"); + CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->state == ALMasterLink::State::Pending); + UNSCOPED_INFO("told"); + CHECK(told == 2); + UNSCOPED_INFO("none linked, none marked"); + CHECK(made.markPending({ { almasterindex_data::key(5), almasterindex_data::key(5) } }) == size_t(0)); + UNSCOPED_INFO("and not told"); + CHECK(told == 2); index.reset(); const ALMasterLinks disk = onDisk(); - ensure_equals("one on disk", disk.size(), size_t(1)); - ensure("pending there", disk.of(almasterindex_data::key(1), almasterindex_data::key(2))->state == ALMasterLink::State::Pending); + UNSCOPED_INFO("one on disk"); + REQUIRE(disk.size() == size_t(1)); + UNSCOPED_INFO("pending there"); + CHECK(disk.of(almasterindex_data::key(1), almasterindex_data::key(2))->state == ALMasterLink::State::Pending); } - template<> template<> - void almasterindex_object::test<6>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: a save heard elsewhere marks the link differing unless the master sent it", "[almasterindex]") { - set_test_name("a save heard from elsewhere marks the link differing, unless it is what the master last sent"); ALMasterIndex& made = open(); const std::string ours = "default { state_entry() {} }"; ALMasterLink door = almasterindex_data::link(1, 2, "/s/door.lsl"); @@ -313,100 +340,137 @@ namespace tut made.flush(); told = 0; - ensure("what it is of already: nothing", made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(2), almasterindex_data::key(100), ours) == ALMasterIndex::Heard::Nothing); - ensure("no asset: nothing", made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(2), LLUUID::null, "x") == ALMasterIndex::Heard::Nothing); - ensure("not linked: nothing", made.heardSaved(almasterindex_data::key(7), almasterindex_data::key(7), almasterindex_data::key(101), ours) == ALMasterIndex::Heard::Nothing); - ensure("nothing changed", !made.dirty()); + UNSCOPED_INFO("what it is of already: nothing"); + CHECK(made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(2), almasterindex_data::key(100), ours) == ALMasterIndex::Heard::Nothing); + UNSCOPED_INFO("no asset: nothing"); + CHECK(made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(2), LLUUID::null, "x") == ALMasterIndex::Heard::Nothing); + UNSCOPED_INFO("not linked: nothing"); + CHECK(made.heardSaved(almasterindex_data::key(7), almasterindex_data::key(7), almasterindex_data::key(101), ours) == ALMasterIndex::Heard::Nothing); + UNSCOPED_INFO("nothing changed"); + CHECK_FALSE(made.dirty()); // A recompile: the same text, a new asset. - ensure("the same", made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(2), almasterindex_data::key(101), ours) == ALMasterIndex::Heard::Same); + UNSCOPED_INFO("the same"); + 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)); - ensure_equals("the base moved", same->base, almasterindex_data::key(101)); - ensure("still active", same->state == ALMasterLink::State::Active); - ensure_equals("nobody told", told, 0); - ensure("to be written", made.dirty()); - ensure("a notecard's by its text", made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(3), almasterindex_data::key(201), "hello") == ALMasterIndex::Heard::Same); - - ensure("something else", made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(2), almasterindex_data::key(102), "default {}") == ALMasterIndex::Heard::Differing); + UNSCOPED_INFO("the base moved"); + CHECK(same->base == almasterindex_data::key(101)); + UNSCOPED_INFO("still active"); + CHECK(same->state == ALMasterLink::State::Active); + UNSCOPED_INFO("nobody told"); + CHECK(told == 0); + UNSCOPED_INFO("to be written"); + CHECK(made.dirty()); + UNSCOPED_INFO("a notecard's by its text"); + CHECK(made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(3), almasterindex_data::key(201), "hello") == ALMasterIndex::Heard::Same); + + UNSCOPED_INFO("something else"); + 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)); - ensure("differing", other->state == ALMasterLink::State::Differing); - ensure_equals("its base as it was", other->base, almasterindex_data::key(101)); - ensure_equals("told", told, 1); + UNSCOPED_INFO("differing"); + CHECK(other->state == ALMasterLink::State::Differing); + UNSCOPED_INFO("its base as it was"); + CHECK(other->base == almasterindex_data::key(101)); + UNSCOPED_INFO("told"); + CHECK(told == 1); // Nothing sent through it yet: no hash to be the same as. made.link(almasterindex_data::link(2, 4, "/s/new.lsl")); - ensure("never sent: differing", made.heardSaved(almasterindex_data::key(2), almasterindex_data::key(4), almasterindex_data::key(300), ours) == ALMasterIndex::Heard::Differing); + UNSCOPED_INFO("never sent: differing"); + CHECK(made.heardSaved(almasterindex_data::key(2), almasterindex_data::key(4), almasterindex_data::key(300), ours) == ALMasterIndex::Heard::Differing); } - template<> template<> - void almasterindex_object::test<7>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: a send's link is put back if still linked and moves to the item it saved as", "[almasterindex]") { - set_test_name("a send's link is put back where the item is linked still, and moves to a new item it was saved as"); 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)); sent.hash = "h1"; sent.base = almasterindex_data::key(50); - ensure("put back", made.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent)); - ensure_equals("with what the send left", made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->hash, std::string("h1")); - ensure_equals("told", told, 1); + UNSCOPED_INFO("put back"); + CHECK(made.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent)); + UNSCOPED_INFO("with what the send left"); + CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->hash == std::string("h1")); + UNSCOPED_INFO("told"); + CHECK(told == 1); // Saved as another item: the link is that one's. ALMasterLink rekeyed = sent; rekeyed.item = almasterindex_data::key(3); rekeyed.hash = "h2"; - ensure("put", made.finished(almasterindex_data::key(1), almasterindex_data::key(2), rekeyed)); - ensure("not the old item's", !made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))); - ensure_equals("the new one's", made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3))->hash, std::string("h2")); - ensure_equals("one link", made.size(), size_t(1)); - ensure_equals("found by its master", made.mastering("/s/door.lsl").size(), size_t(1)); + UNSCOPED_INFO("put"); + 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))); + UNSCOPED_INFO("the new one's"); + CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3))->hash == std::string("h2")); + UNSCOPED_INFO("one link"); + REQUIRE(made.size() == size_t(1)); + UNSCOPED_INFO("found by its master"); + REQUIRE(made.mastering("/s/door.lsl").size() == size_t(1)); // Let go of while it was on its way: stays let go of. made.unlink(almasterindex_data::key(1), almasterindex_data::key(3)); - ensure("not put", !made.finished(almasterindex_data::key(1), almasterindex_data::key(3), rekeyed)); - ensure("none", made.empty()); + UNSCOPED_INFO("not put"); + CHECK_FALSE(made.finished(almasterindex_data::key(1), almasterindex_data::key(3), rekeyed)); + UNSCOPED_INFO("none"); + REQUIRE(made.empty()); } - template<> template<> - void almasterindex_object::test<8>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: what a probe found is kept only by a link of its master knowing nothing yet", "[almasterindex]") { - set_test_name("what a probe found is kept only by a link that knows nothing still, of the same master, and told a moment later"); 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") }); - ensure("a new link knows nothing", ALMasterIndex::knowsNothing(*made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)))); + UNSCOPED_INFO("a new link knows nothing"); + CHECK(ALMasterIndex::knowsNothing(*made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)))); told = 0; - ensure("kept", made.adopted(almasterindex_data::key(1), almasterindex_data::key(1), "/s/a.lsl", { "disk:/s/util.lsl" }, false, "ha", 77)); + UNSCOPED_INFO("kept"); + 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)); - ensure_equals("what it read", a->uses.size(), size_t(1)); - ensure_equals("what went up, as the world holds it", a->hash, std::string("ha")); - ensure_equals("the stamp with it", a->stamp, S64(77)); - ensure("found by what it read", !made.affectedBy("disk:/s/util.lsl").empty()); + UNSCOPED_INFO("what it read"); + REQUIRE(a->uses.size() == size_t(1)); + UNSCOPED_INFO("what went up, as the world holds it"); + CHECK(a->hash == std::string("ha")); + UNSCOPED_INFO("the stamp with it"); + CHECK(a->stamp == S64(77)); + UNSCOPED_INFO("found by what it read"); + REQUIRE_FALSE(made.affectedBy("disk:/s/util.lsl").empty()); // The world holding something else: what it read, and no hash. - ensure("kept too", made.adopted(almasterindex_data::key(1), almasterindex_data::key(2), "/s/b.lsl", {}, true, std::string(), 0)); - ensure("missed", made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->missed); - ensure("no hash", made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->hash.empty()); - ensure_equals("not told yet", told, 0); + UNSCOPED_INFO("kept too"); + CHECK(made.adopted(almasterindex_data::key(1), almasterindex_data::key(2), "/s/b.lsl", {}, true, std::string(), 0)); + UNSCOPED_INFO("missed"); + CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->missed); + UNSCOPED_INFO("no hash"); + REQUIRE(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->hash.empty()); + UNSCOPED_INFO("not told yet"); + CHECK(told == 0); pass(ALMasterIndex::TELL_SOON + 0.01); - ensure_equals("told once for the run", told, 1); + UNSCOPED_INFO("told once for the run"); + CHECK(told == 1); - ensure("knowing something now: not again", !made.adopted(almasterindex_data::key(1), almasterindex_data::key(1), "/s/a.lsl", {}, false, "other", 1)); - ensure_equals("as it was", made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))->hash, std::string("ha")); + 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)); + UNSCOPED_INFO("as it was"); + CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))->hash == std::string("ha")); made.link(almasterindex_data::link(1, 3, "/s/elsewhere.lsl")); - ensure("linked to another master: not", !made.adopted(almasterindex_data::key(1), almasterindex_data::key(3), "/s/c.lsl", { "disk:/s/x.lsl" }, false, "hc", 1)); - ensure("nothing learned: nothing kept", !made.adopted(almasterindex_data::key(1), almasterindex_data::key(3), "/s/elsewhere.lsl", {}, false, std::string(), 0)); - ensure("not linked: not", !made.adopted(almasterindex_data::key(9), almasterindex_data::key(9), "/s/a.lsl", { "disk:/s/x.lsl" }, false, "h", 1)); - ensure("still knowing nothing", ALMasterIndex::knowsNothing(*made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3)))); + 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)); + UNSCOPED_INFO("nothing learned: nothing kept"); + 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)); + UNSCOPED_INFO("still knowing nothing"); + CHECK(ALMasterIndex::knowsNothing(*made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3)))); const S32 before = told; pass(1.0); - ensure_equals("nothing more told", told, before); + UNSCOPED_INFO("nothing more told"); + CHECK(told == before); } - template<> template<> - void almasterindex_object::test<9>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: a send's bookkeeping is written after a quiet second or five after the first", "[almasterindex]") { - set_test_name("a send's bookkeeping is written once nothing has changed for a second, and no later than five after the first"); ALMasterIndex& made = open(); made.link(almasterindex_data::link(1, 2, "/s/door.lsl")); made.flush(); @@ -414,18 +478,23 @@ namespace tut sent.hash = "h1"; made.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent); - ensure("waiting", made.dirty()); + UNSCOPED_INFO("waiting"); + CHECK(made.dirty()); pass(0.5); sent.hash = "h2"; made.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent); pass(0.6); - ensure("a second after the first, not after the last: waiting still", made.dirty()); + UNSCOPED_INFO("a second after the first, not after the last: waiting still"); + CHECK(made.dirty()); made.awaitWrites(); - ensure("the file as it was", onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash.empty()); + UNSCOPED_INFO("the file as it was"); + REQUIRE(onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash.empty()); pass(0.5); - ensure("a second after the last: handed over", !made.dirty()); + UNSCOPED_INFO("a second after the last: handed over"); + CHECK_FALSE(made.dirty()); made.awaitWrites(); - ensure_equals("written, the newest", onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash, std::string("h2")); + UNSCOPED_INFO("written, the newest"); + CHECK(onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash == std::string("h2")); // A change every 0.8 s: written five seconds after the first all the // same. @@ -435,43 +504,47 @@ namespace tut made.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent); pass(0.8); } - ensure("4.8 s on: waiting", made.dirty()); + UNSCOPED_INFO("4.8 s on: waiting"); + CHECK(made.dirty()); pass(0.3); - ensure("5.1 s on: handed over", !made.dirty()); + UNSCOPED_INFO("5.1 s on: handed over"); + CHECK_FALSE(made.dirty()); made.awaitWrites(); - ensure_equals("written", onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash, std::string("run5")); + UNSCOPED_INFO("written"); + CHECK(onDisk().of(almasterindex_data::key(1), almasterindex_data::key(2))->hash == std::string("run5")); } - template<> template<> - void almasterindex_object::test<10>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: whatever waits is written to its own file as the index goes", "[almasterindex]") { - set_test_name("whatever waits is written as the index goes, to its own file, as for another account"); 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)); sent.hash = "last"; first.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent); - ensure("waiting", first.dirty()); + UNSCOPED_INFO("waiting"); + CHECK(first.dirty()); // Another account's: this one's let go of, and written as it goes, // with no time passed. ALMasterIndex& second = open(other); - ensure("the other account's has none", second.empty()); - ensure_equals("the first's written to its own file", almasterindex_data::onDisk(file).of(almasterindex_data::key(1), almasterindex_data::key(2))->hash, + 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")); second.link(almasterindex_data::link(5, 6, "/s/lamp.lsl")); index.reset(); - ensure_equals("the second's to its own", almasterindex_data::onDisk(other).size(), size_t(1)); - ensure("and not to the first's", !almasterindex_data::onDisk(file).of(almasterindex_data::key(5), almasterindex_data::key(6))); + UNSCOPED_INFO("the second's to its own"); + REQUIRE(almasterindex_data::onDisk(other).size() == size_t(1)); + UNSCOPED_INFO("and not to the first's"); + REQUIRE_FALSE(almasterindex_data::onDisk(file).of(almasterindex_data::key(5), almasterindex_data::key(6))); // What waits for a time gone by finds nobody. pass(10.0); } - template<> template<> - void almasterindex_object::test<11>() + TEST_CASE_METHOD(almasterindex_data, "almasterindex: what a person did is written at once and what a send learned later", "[almasterindex]") { - set_test_name("what a person did, or what could not be found again, is written at once; what a send learned a moment later"); ALMasterIndex& made = open(); // Each read as a crash would leave the file: what was handed over // written, nothing more, and no time passed. @@ -483,8 +556,10 @@ namespace tut }; made.link({ almasterindex_data::link(1, 1, "/s/a.lsl"), almasterindex_data::link(1, 2, "/s/b.lsl") }); - ensure("a link: handed over at once", !made.dirty()); - ensure("on disk", on_disk(1).has_value()); + UNSCOPED_INFO("a link: handed over at once"); + CHECK_FALSE(made.dirty()); + UNSCOPED_INFO("on disk"); + 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)); @@ -492,31 +567,47 @@ namespace tut sent.base = almasterindex_data::key(10); sent.uses = { "disk:/s/util.lsl" }; made.finished(almasterindex_data::key(1), almasterindex_data::key(1), sent); - ensure("a send's bookkeeping waits", made.dirty()); - ensure("not on disk yet", on_disk(1)->hash.empty()); + UNSCOPED_INFO("a send's bookkeeping waits"); + CHECK(made.dirty()); + UNSCOPED_INFO("not on disk yet"); + REQUIRE(on_disk(1)->hash.empty()); // Something a person did takes it with it. made.markPending({ { almasterindex_data::key(1), almasterindex_data::key(2) } }); - ensure("pending: at once", !made.dirty()); - ensure("pending on disk", on_disk(2)->state == ALMasterLink::State::Pending); - ensure_equals("and what waited with it", on_disk(1)->hash, std::string("h1")); + UNSCOPED_INFO("pending: at once"); + CHECK_FALSE(made.dirty()); + UNSCOPED_INFO("pending on disk"); + CHECK(on_disk(2)->state == ALMasterLink::State::Pending); + UNSCOPED_INFO("and what waited with it"); + CHECK(on_disk(1)->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)); sent.state = ALMasterLink::State::Suspended; made.finished(almasterindex_data::key(1), almasterindex_data::key(1), sent); - ensure("suspended: at once", !made.dirty()); - ensure("suspended on disk", on_disk(1)->state == ALMasterLink::State::Suspended); + UNSCOPED_INFO("suspended: at once"); + CHECK_FALSE(made.dirty()); + UNSCOPED_INFO("suspended on disk"); + CHECK(on_disk(1)->state == ALMasterLink::State::Suspended); sent.state = ALMasterLink::State::Active; made.finished(almasterindex_data::key(1), almasterindex_data::key(1), sent); - ensure("active again: at once", !made.dirty() && on_disk(1)->state == ALMasterLink::State::Active); + { + INFO("active again: at once"); + CHECK_FALSE(made.dirty()); + CHECK(on_disk(1)->state == ALMasterLink::State::Active); + } // A send saved as another item: at once, or the link is lost with it. ALMasterLink moved = sent; moved.item = almasterindex_data::key(3); made.finished(almasterindex_data::key(1), almasterindex_data::key(1), moved); - ensure("moved: at once", !made.dirty()); - ensure("under the new item on disk", on_disk(3).has_value() && !on_disk(1).has_value()); + UNSCOPED_INFO("moved: at once"); + CHECK_FALSE(made.dirty()); + { + INFO("under the new item on disk"); + REQUIRE(on_disk(3).has_value()); + REQUIRE_FALSE(on_disk(1).has_value()); + } // A save heard from elsewhere: differing at once, the same later. sent = *made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3)); @@ -524,20 +615,33 @@ namespace tut made.finished(almasterindex_data::key(1), almasterindex_data::key(3), sent); made.flush(); made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(3), almasterindex_data::key(11), "default {}"); - ensure("the same: later", made.dirty()); + UNSCOPED_INFO("the same: later"); + CHECK(made.dirty()); made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(3), almasterindex_data::key(12), "default { touch_start(integer n) {} }"); - ensure("differing: at once", !made.dirty() && on_disk(3)->state == ALMasterLink::State::Differing); + { + INFO("differing: at once"); + CHECK_FALSE(made.dirty()); + CHECK(on_disk(3)->state == ALMasterLink::State::Differing); + } // A probe's answer, and a link let go of. made.link(almasterindex_data::link(1, 4, "/s/d.lsl")); made.adopted(almasterindex_data::key(1), almasterindex_data::key(4), "/s/d.lsl", { "disk:/s/util.lsl" }, false, std::string(), 0); - ensure("a probe's answer: later", made.dirty()); - ensure("not on disk yet", on_disk(4)->uses.empty()); + UNSCOPED_INFO("a probe's answer: later"); + CHECK(made.dirty()); + UNSCOPED_INFO("not on disk yet"); + REQUIRE(on_disk(4)->uses.empty()); made.unlink(almasterindex_data::key(1), almasterindex_data::key(2)); - ensure("let go of: at once", !made.dirty() && !on_disk(2).has_value()); - ensure_equals("the probe's answer with it", on_disk(4)->uses.size(), size_t(1)); + { + INFO("let go of: at once"); + CHECK_FALSE(made.dirty()); + REQUIRE_FALSE(on_disk(2).has_value()); + } + UNSCOPED_INFO("the probe's answer with it"); + REQUIRE(on_disk(4)->uses.size() == size_t(1)); // Nothing left for the time to write. pass(10.0); - ensure("nothing waiting", !made.dirty()); + UNSCOPED_INFO("nothing waiting"); + CHECK_FALSE(made.dirty()); } } diff --git a/indra/alscript/tests/almasterlinks_test.cpp b/indra/alscript/tests/almasterlinks_test.cpp index ef1cc38810..2de4c09535 100644 --- a/indra/alscript/tests/almasterlinks_test.cpp +++ b/indra/alscript/tests/almasterlinks_test.cpp @@ -28,13 +28,13 @@ #include "llsdserialize.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct almasterlinks_data { @@ -88,52 +88,64 @@ namespace tut } }; - typedef test_group almasterlinks_group; - typedef almasterlinks_group::object almasterlinks_object; - almasterlinks_group almasterlinks_instance("almasterlinks"); - - template<> template<> - void almasterlinks_object::test<1>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: a link is found by object and item then replaced in place and removed", "[almasterlinks]") { - set_test_name("a link is found by its object and item, put over, and removed"); ALMasterLinks links; - ensure("none at first", links.empty() && !links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))); + { + INFO("none at first"); + REQUIRE(links.empty()); + REQUIRE_FALSE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))); + } links.put(almasterlinks_data::link(1, 2, "/s/door.lsl")); links.put(almasterlinks_data::link(0, 2, "/s/inventory.lsl")); links.put(almasterlinks_data::link(3, 4, "/s/lamp.lsl")); - ensure_equals("three", links.size(), size_t(3)); - ensure_equals("by its object and item", links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))->master, std::string("/s/door.lsl")); - ensure_equals("the same item in the inventory is another", links.of(LLUUID::null, almasterlinks_data::key(2))->master, + UNSCOPED_INFO("three"); + REQUIRE(links.size() == size_t(3)); + UNSCOPED_INFO("by its object and item"); + CHECK(links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))->master == std::string("/s/door.lsl")); + 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")); - ensure("not by the item in another object", !links.of(almasterlinks_data::key(3), almasterlinks_data::key(2))); + UNSCOPED_INFO("not by the item in another object"); + REQUIRE_FALSE(links.of(almasterlinks_data::key(3), almasterlinks_data::key(2))); ALMasterLink again = almasterlinks_data::link(1, 2, "/s/door2.lsl"); again.made = ALMasterLink::Made::Hint; ALMasterLink& put = links.put(again); - ensure_equals("put over, not beside", links.size(), size_t(3)); - ensure_equals("what put answers is the one kept", put.master, std::string("/s/door2.lsl")); - ensure("and found so", links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))->made == ALMasterLink::Made::Hint); - ensure_equals("in its place", links.all()[0].master, std::string("/s/door2.lsl")); + UNSCOPED_INFO("put over, not beside"); + REQUIRE(links.size() == size_t(3)); + UNSCOPED_INFO("what put answers is the one kept"); + CHECK(put.master == std::string("/s/door2.lsl")); + UNSCOPED_INFO("and found so"); + CHECK(links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))->made == ALMasterLink::Made::Hint); + UNSCOPED_INFO("in its place"); + CHECK(links.all()[0].master == std::string("/s/door2.lsl")); ALMasterLink* found = links.find(almasterlinks_data::key(3), almasterlinks_data::key(4)); - ensure("found to change", found != nullptr); + UNSCOPED_INFO("found to change"); + REQUIRE(found != nullptr); found->hash = "xxh128:00"; - ensure_equals("changed where it is kept", links.of(almasterlinks_data::key(3), almasterlinks_data::key(4))->hash, std::string("xxh128:00")); - ensure("nothing to change of an item not linked", !links.find(almasterlinks_data::key(9), almasterlinks_data::key(9))); - - ensure("removed", links.remove(almasterlinks_data::key(1), almasterlinks_data::key(2))); - ensure("and gone", !links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))); - ensure("not twice", !links.remove(almasterlinks_data::key(1), almasterlinks_data::key(2))); - ensure_equals("the rest kept, in order", links.all()[0].master, std::string("/s/inventory.lsl")); - ensure_equals("and found where they now are", links.of(almasterlinks_data::key(3), almasterlinks_data::key(4))->master, + UNSCOPED_INFO("changed where it is kept"); + CHECK(links.of(almasterlinks_data::key(3), almasterlinks_data::key(4))->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))); + + UNSCOPED_INFO("removed"); + CHECK(links.remove(almasterlinks_data::key(1), almasterlinks_data::key(2))); + UNSCOPED_INFO("and gone"); + 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")); + 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")); } - template<> template<> - void almasterlinks_object::test<2>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: mastering lists a file's items comparing paths as their platform does", "[almasterlinks]") { - set_test_name("every item a master file masters, a path compared as its platform compares it"); ALMasterLinks links; links.put(almasterlinks_data::link(1, 10, "/s/net/door.lsl")); links.put(almasterlinks_data::link(2, 11, "/s/net/door.lsl")); @@ -141,33 +153,41 @@ namespace tut links.put(almasterlinks_data::link(4, 13, "C:\\Scripts\\Door.lsl")); links.put(almasterlinks_data::link(5, 14, "\\\\host\\share\\door.lsl")); - ensure_equals("one file masters several items, in the order put", almasterlinks_data::items(links.mastering("/s/net/door.lsl")), + UNSCOPED_INFO("one file masters several items, in the order put"); + CHECK(almasterlinks_data::items(links.mastering("/s/net/door.lsl")) == std::string("10 11")); - ensure("a path from a root of /, as written: the disk's own case", links.mastering("/s/net/Door.lsl").empty()); - ensure("nor another file", links.mastering("/s/net/window.lsl").empty()); - ensure_equals("Windows's, in any case and with either separator", - almasterlinks_data::items(links.mastering("c:/scripts/door.LSL")), std::string("13")); - ensure_equals("a share's the same", almasterlinks_data::items(links.mastering("//HOST/share/door.lsl")), + UNSCOPED_INFO("a path from a root of /, as written: the disk's own case"); + REQUIRE(links.mastering("/s/net/Door.lsl").empty()); + UNSCOPED_INFO("nor another file"); + REQUIRE(links.mastering("/s/net/window.lsl").empty()); + UNSCOPED_INFO("Windows's, in any case and with either separator"); + CHECK(almasterlinks_data::items(links.mastering("c:/scripts/door.LSL")) == std::string("13")); + UNSCOPED_INFO("a share's the same"); + CHECK(almasterlinks_data::items(links.mastering("//HOST/share/door.lsl")) == std::string("14")); - ensure_equals("the key of Windows's", ALMasterLinks::keyOf("C:/Scripts/Door.lsl"), std::string("c:\\scripts\\door.lsl")); - ensure_equals("and of any other", ALMasterLinks::keyOf("/S/Door.lsl"), std::string("/S/Door.lsl")); + UNSCOPED_INFO("the key of Windows's"); + CHECK(ALMasterLinks::keyOf("C:/Scripts/Door.lsl") == std::string("c:\\scripts\\door.lsl")); + UNSCOPED_INFO("and of any other"); + 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"; - ensure_equals("a master changed through find", almasterlinks_data::items(links.mastering("/s/net/door.lsl")), + UNSCOPED_INFO("a master changed through find"); + CHECK(almasterlinks_data::items(links.mastering("/s/net/door.lsl")) == std::string("10")); - ensure_equals("is found under the new", almasterlinks_data::items(links.mastering("/s/net/lamp.lsl")), + UNSCOPED_INFO("is found under the new"); + CHECK(almasterlinks_data::items(links.mastering("/s/net/lamp.lsl")) == std::string("11 12")); links.remove(almasterlinks_data::key(1), almasterlinks_data::key(10)); - ensure("and one removed is not", links.mastering("/s/net/door.lsl").empty()); - ensure_equals("nor are the others lost by it", almasterlinks_data::items(links.mastering("/s/net/lamp.lsl")), + UNSCOPED_INFO("and one removed is not"); + REQUIRE(links.mastering("/s/net/door.lsl").empty()); + UNSCOPED_INFO("nor are the others lost by it"); + CHECK(almasterlinks_data::items(links.mastering("/s/net/lamp.lsl")) == std::string("11 12")); } - template<> template<> - void almasterlinks_object::test<3>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: usersOf finds links that last read a file by its identity or its path", "[almasterlinks]") { - set_test_name("every link whose last expansion read a file, by the file's identity or its path"); ALMasterLinks links; ALMasterLink door = almasterlinks_data::link(1, 10, "/s/door.lsl"); door.uses = { "disk:/s/lib/util.lsl", "disk:/s/lib/net.lsl", "disk:/s/lib/util.lsl" }; @@ -179,28 +199,32 @@ namespace tut links.put(lamp); links.put(win); - ensure_equals("by its identity, each user once", almasterlinks_data::items(links.usersOf("disk:/s/lib/util.lsl")), + UNSCOPED_INFO("by its identity, each user once"); + CHECK(almasterlinks_data::items(links.usersOf("disk:/s/lib/util.lsl")) == std::string("10 11")); - ensure_equals("or by its path alone", almasterlinks_data::items(links.usersOf("/s/lib/net.lsl")), + UNSCOPED_INFO("or by its path alone"); + CHECK(almasterlinks_data::items(links.usersOf("/s/lib/net.lsl")) == std::string("10")); - ensure("a master is not a use of itself", links.usersOf("/s/door.lsl").empty()); - ensure_equals("Windows's in any case", almasterlinks_data::items(links.usersOf("disk:c:/s/lib/util.lsl")), + UNSCOPED_INFO("a master is not a use of itself"); + REQUIRE(links.usersOf("/s/door.lsl").empty()); + UNSCOPED_INFO("Windows's in any case"); + CHECK(almasterlinks_data::items(links.usersOf("disk:c:/s/lib/util.lsl")) == 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" }; - ensure_equals("uses changed through find", almasterlinks_data::items(links.usersOf("disk:/s/lib/util.lsl")), + UNSCOPED_INFO("uses changed through find"); + CHECK(almasterlinks_data::items(links.usersOf("disk:/s/lib/util.lsl")) == std::string("11")); ALMasterLink again = lamp; again.uses.clear(); links.put(again); - ensure("and through put", links.usersOf("disk:/s/lib/util.lsl").empty()); + UNSCOPED_INFO("and through put"); + REQUIRE(links.usersOf("disk:/s/lib/util.lsl").empty()); } - template<> template<> - void almasterlinks_object::test<4>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: orphaned links are kept ninety days and then pruned; no other is", "[almasterlinks]") { - set_test_name("orphaned links are kept ninety days and then pruned; no other is"); const LLDate now(1800000000.0); const F64 day = 24.0 * 60.0 * 60.0; ALMasterLinks links; @@ -223,26 +247,37 @@ namespace tut links.put(one); } - ensure_equals("one pruned", links.prune(now), size_t(1)); - ensure("the old orphan", !links.of(almasterlinks_data::key(1), almasterlinks_data::key(10))); - ensure("not the young one", links.of(almasterlinks_data::key(1), almasterlinks_data::key(11)) != nullptr); - ensure("nor any not orphaned, however old", - links.of(almasterlinks_data::key(1), almasterlinks_data::key(12)) && links.of(almasterlinks_data::key(1), almasterlinks_data::key(13))); + UNSCOPED_INFO("one pruned"); + CHECK(links.prune(now) == size_t(1)); + UNSCOPED_INFO("the old orphan"); + REQUIRE_FALSE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(10))); + UNSCOPED_INFO("not the young one"); + REQUIRE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(11)) != nullptr); + { + INFO("nor any not orphaned, however old"); + REQUIRE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(12))); + REQUIRE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(13))); + } const ALMasterLink* dated = links.of(almasterlinks_data::key(1), almasterlinks_data::key(14)); - ensure("one with no date is kept", dated != nullptr); - ensure("and dated now", dated->orphanedSince == now); - ensure_equals("found by its master still", links.mastering("/s/b.lsl").size(), size_t(1)); - - ensure_equals("fewer days where asked", links.prune(now, 30.0), size_t(1)); - ensure("the young one now", !links.of(almasterlinks_data::key(1), almasterlinks_data::key(11))); - ensure_equals("the one dated now goes in its time", links.prune(LLDate(now.secondsSinceEpoch() + 91 * day)), size_t(1)); - ensure_equals("and nothing else", links.size(), size_t(2)); + UNSCOPED_INFO("one with no date is kept"); + REQUIRE(dated != nullptr); + UNSCOPED_INFO("and dated now"); + CHECK(dated->orphanedSince == now); + UNSCOPED_INFO("found by its master still"); + REQUIRE(links.mastering("/s/b.lsl").size() == size_t(1)); + + UNSCOPED_INFO("fewer days where asked"); + CHECK(links.prune(now, 30.0) == size_t(1)); + UNSCOPED_INFO("the young one now"); + REQUIRE_FALSE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(11))); + UNSCOPED_INFO("the one dated now goes in its time"); + CHECK(links.prune(LLDate(now.secondsSinceEpoch() + 91 * day)) == size_t(1)); + UNSCOPED_INFO("and nothing else"); + REQUIRE(links.size() == size_t(2)); } - template<> template<> - void almasterlinks_object::test<5>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: the LLSD is versioned and every field round-trips through notation", "[almasterlinks]") { - set_test_name("the LLSD is versioned, and every field comes back, through notation too"); ALMasterLinks links; ALMasterLink full; full.object = almasterlinks_data::key(1); @@ -275,54 +310,90 @@ namespace tut } const LLSD llsd = links.toLLSD(); - ensure_equals("version 1", llsd["version"].asInteger(), 1); - ensure_equals("every link", llsd["links"].size(), 5); + UNSCOPED_INFO("version 1"); + CHECK(llsd["version"].asInteger() == 1); + UNSCOPED_INFO("every link"); + REQUIRE(llsd["links"].size() == 5); std::ostringstream out; LLSDSerialize::toNotation(llsd, out); std::istringstream in(out.str()); LLSD back; - ensure("as notation", LLSDSerialize::fromNotation(back, in, static_cast(out.str().size())) > 0); + UNSCOPED_INFO("as notation"); + REQUIRE(LLSDSerialize::fromNotation(back, in, static_cast(out.str().size())) > 0); const ALMasterLinks read = ALMasterLinks::fromLLSD(back); - ensure_equals("all read", read.size(), size_t(5)); + UNSCOPED_INFO("all read"); + REQUIRE(read.size() == size_t(5)); const ALMasterLink* got = read.of(full.object, full.item); - ensure("the full one", got != nullptr); - ensure_equals("master", got->master, full.master); - ensure("made", got->made == full.made); - ensure("lua", got->lua); - ensure_equals("target", got->target, full.target); - ensure_equals("base", got->base, full.base); - ensure_equals("hash", got->hash, full.hash); - ensure_equals("stamp, to the nanosecond", got->stamp, full.stamp); - ensure_equals("uses", almasterlinks_data::joined(got->uses), almasterlinks_data::joined(full.uses)); - ensure_equals("object name", got->objectName, full.objectName); - ensure_equals("item name", got->itemName, full.itemName); - ensure_equals("region name", got->regionName, full.regionName); - ensure("linked", got->linked == full.linked); - ensure("state", got->state == full.state); - ensure("orphaned since", got->orphanedSince == full.orphanedSince); + UNSCOPED_INFO("the full one"); + REQUIRE(got != nullptr); + UNSCOPED_INFO("master"); + CHECK(got->master == full.master); + UNSCOPED_INFO("made"); + CHECK(got->made == full.made); + UNSCOPED_INFO("lua"); + CHECK(got->lua); + UNSCOPED_INFO("target"); + CHECK(got->target == full.target); + UNSCOPED_INFO("base"); + CHECK(got->base == full.base); + UNSCOPED_INFO("hash"); + CHECK(got->hash == full.hash); + UNSCOPED_INFO("stamp, to the nanosecond"); + CHECK(got->stamp == full.stamp); + UNSCOPED_INFO("uses"); + CHECK(almasterlinks_data::joined(got->uses) == almasterlinks_data::joined(full.uses)); + UNSCOPED_INFO("object name"); + CHECK(got->objectName == full.objectName); + UNSCOPED_INFO("item name"); + CHECK(got->itemName == full.itemName); + UNSCOPED_INFO("region name"); + CHECK(got->regionName == full.regionName); + UNSCOPED_INFO("linked"); + CHECK(got->linked == full.linked); + UNSCOPED_INFO("state"); + CHECK(got->state == full.state); + UNSCOPED_INFO("orphaned since"); + CHECK(got->orphanedSince == full.orphanedSince); const ALMasterLink* plain = read.of(LLUUID::null, inventory.item); - ensure("the inventory's, with no object", plain && plain->object.isNull() && plain->state == ALMasterLink::State::Differing); - ensure("no date where none was", plain->orphanedSince.isNull() && !back["links"][1].has("orphaned_since")); + { + INFO("the inventory's, with no object"); + REQUIRE(plain); + REQUIRE(plain->object.isNull()); + CHECK(plain->state == ALMasterLink::State::Differing); + } + { + INFO("no date where none was"); + CHECK(plain->orphanedSince.isNull()); + REQUIRE_FALSE(back["links"][1].has("orphaned_since")); + } for (const ALMasterLink::State state : { ALMasterLink::State::Active, ALMasterLink::State::Pending, ALMasterLink::State::Suspended }) { const ALMasterLink* one = read.of(almasterlinks_data::key(5), almasterlinks_data::key(10 + static_cast(state))); - ensure("each state", one && one->state == state && one->made == ALMasterLink::Made::Hint); + { + INFO("each state"); + REQUIRE(one); + REQUIRE(one->state == state); + REQUIRE(one->made == ALMasterLink::Made::Hint); + } } - ensure_equals("found by their masters", read.mastering("/s/x.lsl").size(), size_t(3)); - ensure_equals("and their uses", read.usersOf("disk:/s/lib/b.luau").size(), size_t(1)); + UNSCOPED_INFO("found by their masters"); + REQUIRE(read.mastering("/s/x.lsl").size() == size_t(3)); + UNSCOPED_INFO("and their uses"); + REQUIRE(read.usersOf("disk:/s/lib/b.luau").size() == size_t(1)); } - template<> template<> - void almasterlinks_object::test<6>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: malformed records are skipped and unknown values are read carefully", "[almasterlinks]") { - set_test_name("a malformed record is passed over and the rest read; what is not known is read carefully"); - ensure("not a map", ALMasterLinks::fromLLSD(LLSD("links")).empty()); - ensure("no links", ALMasterLinks::fromLLSD(LLSD::emptyMap()).empty()); + UNSCOPED_INFO("not a map"); + REQUIRE(ALMasterLinks::fromLLSD(LLSD("links")).empty()); + UNSCOPED_INFO("no links"); + REQUIRE(ALMasterLinks::fromLLSD(LLSD::emptyMap()).empty()); LLSD wrong; wrong["links"] = "none"; - ensure("links not a list", ALMasterLinks::fromLLSD(wrong).empty()); + UNSCOPED_INFO("links not a list"); + REQUIRE(ALMasterLinks::fromLLSD(wrong).empty()); const auto record = [](const std::string& object, const std::string& item, const LLSD& master) { LLSD out; @@ -362,33 +433,50 @@ namespace tut links.append(record(object, almasterlinks_data::key(5).asString(), "/s/later.lsl")); const ALMasterLinks read = ALMasterLinks::fromLLSD(llsd); - ensure_equals("the two whole records", read.size(), size_t(2)); - ensure("not one whose object is no key, read as the inventory's", !read.of(LLUUID::null, almasterlinks_data::key(4))); + UNSCOPED_INFO("the two whole records"); + REQUIRE(read.size() == size_t(2)); + UNSCOPED_INFO("not one whose object is no key, read as the inventory's"); + REQUIRE_FALSE(read.of(LLUUID::null, almasterlinks_data::key(4))); const ALMasterLink* later = read.of(almasterlinks_data::key(1), almasterlinks_data::key(5)); - ensure("an item twice is read once, the later", later && later->master == "/s/later.lsl"); + { + INFO("an item twice is read once, the later"); + REQUIRE(later); + CHECK(later->master == "/s/later.lsl"); + } const ALMasterLink* plain = read.of(LLUUID::null, almasterlinks_data::key(6)); - ensure("one with no object is the inventory's", plain != nullptr); - ensure("what it does not say is as a new link has it", plain->made == ALMasterLink::Made::Picked && plain->state == ALMasterLink::State::Active); - ensure_equals("a stamp written as an integer", plain->stamp, S64(12345)); + UNSCOPED_INFO("one with no object is the inventory's"); + REQUIRE(plain != nullptr); + { + INFO("what it does not say is as a new link has it"); + REQUIRE(plain->made == ALMasterLink::Made::Picked); + CHECK(plain->state == ALMasterLink::State::Active); + } + UNSCOPED_INFO("a stamp written as an integer"); + CHECK(plain->stamp == S64(12345)); // The earlier of the item twice, alone. LLSD once; once["links"].append(good); const ALMasterLinks careful = ALMasterLinks::fromLLSD(once); const ALMasterLink* odd = careful.of(almasterlinks_data::key(1), almasterlinks_data::key(5)); - ensure("read", odd != nullptr); - ensure("a way of making not known is a hint's", odd->made == ALMasterLink::Made::Hint); - ensure("a state not known is suspended", odd->state == ALMasterLink::State::Suspended); - ensure("a stamp not a number is none", odd->stamp == 0); - ensure("a base not a key is none", odd->base.isNull()); - ensure_equals("only the uses that are names", almasterlinks_data::joined(odd->uses), std::string("disk:/s/lib/a.lsl")); - ensure("a version not given is read all the same", careful.size() == 1); + UNSCOPED_INFO("read"); + REQUIRE(odd != nullptr); + UNSCOPED_INFO("a way of making not known is a hint's"); + CHECK(odd->made == ALMasterLink::Made::Hint); + UNSCOPED_INFO("a state not known is suspended"); + CHECK(odd->state == ALMasterLink::State::Suspended); + UNSCOPED_INFO("a stamp not a number is none"); + CHECK(odd->stamp == 0); + UNSCOPED_INFO("a base not a key is none"); + CHECK(odd->base.isNull()); + UNSCOPED_INFO("only the uses that are names"); + CHECK(almasterlinks_data::joined(odd->uses) == std::string("disk:/s/lib/a.lsl")); + UNSCOPED_INFO("a version not given is read all the same"); + REQUIRE(careful.size() == 1); } - template<> template<> - void almasterlinks_object::test<7>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: thousands of links over a few masters and includes are found by each", "[almasterlinks]") { - set_test_name("thousands of links over a few masters and includes are found by each"); ALMasterLinks links; constexpr int LINKS = 5000; constexpr int MASTERS = 50; @@ -398,29 +486,36 @@ namespace tut one.uses = { "disk:/s/lib/common.lsl", "disk:/s/lib/part" + std::to_string(n % 7) + ".lsl" }; links.put(one); } - ensure_equals("all kept", links.size(), size_t(LINKS)); + UNSCOPED_INFO("all kept"); + REQUIRE(links.size() == size_t(LINKS)); for (int m = 0; m < MASTERS; ++m) { const std::vector found = links.mastering("/s/master" + std::to_string(m) + ".lsl"); - ensure_equals("each master's", found.size(), size_t(LINKS / MASTERS)); - ensure("each the master's", found.front()->master == "/s/master" + std::to_string(m) + ".lsl"); + UNSCOPED_INFO("each master's"); + REQUIRE(found.size() == size_t(LINKS / MASTERS)); + UNSCOPED_INFO("each the master's"); + REQUIRE(found.front()->master == "/s/master" + std::to_string(m) + ".lsl"); } - ensure_equals("every user of the include they share", links.usersOf("disk:/s/lib/common.lsl").size(), size_t(LINKS)); - ensure_equals("and of one a seventh read", links.usersOf("/s/lib/part3.lsl").size(), size_t((LINKS - 3 + 6) / 7)); + UNSCOPED_INFO("every user of the include they share"); + REQUIRE(links.usersOf("disk:/s/lib/common.lsl").size() == size_t(LINKS)); + UNSCOPED_INFO("and of one a seventh read"); + REQUIRE(links.usersOf("/s/lib/part3.lsl").size() == size_t((LINKS - 3 + 6) / 7)); for (int n = 0; n < LINKS; n += 2) { links.remove(almasterlinks_data::key(1 + n / 100), almasterlinks_data::key(100000 + n)); } - ensure_equals("half removed", links.size(), size_t(LINKS / 2)); - ensure_equals("the rest found by their master", links.mastering("/s/master1.lsl").size(), size_t(LINKS / MASTERS)); - ensure("none removed found", links.mastering("/s/master0.lsl").empty()); - ensure("each found by its item", links.of(almasterlinks_data::key(1 + 4999 / 100), almasterlinks_data::key(104999)) != nullptr); + UNSCOPED_INFO("half removed"); + REQUIRE(links.size() == size_t(LINKS / 2)); + UNSCOPED_INFO("the rest found by their master"); + REQUIRE(links.mastering("/s/master1.lsl").size() == size_t(LINKS / MASTERS)); + UNSCOPED_INFO("none removed found"); + REQUIRE(links.mastering("/s/master0.lsl").empty()); + UNSCOPED_INFO("each found by its item"); + REQUIRE(links.of(almasterlinks_data::key(1 + 4999 / 100), almasterlinks_data::key(104999)) != nullptr); } - template<> template<> - void almasterlinks_object::test<8>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: the missed flag round-trips through the LLSD and is written only where set", "[almasterlinks]") { - set_test_name("whether the last expansion missed something comes back through the LLSD, and is written only where set"); ALMasterLinks links; ALMasterLink missing = almasterlinks_data::link(1, 10, "/s/door.lsl"); missing.missed = true; @@ -428,18 +523,24 @@ namespace tut links.put(almasterlinks_data::link(1, 11, "/s/lamp.lsl")); const LLSD llsd = links.toLLSD(); - ensure_equals("still version 1", llsd["version"].asInteger(), 1); - ensure("written where set", llsd["links"][0]["missed"].asBoolean()); - ensure("and not where not", !llsd["links"][1].has("missed")); + UNSCOPED_INFO("still version 1"); + CHECK(llsd["version"].asInteger() == 1); + UNSCOPED_INFO("written where set"); + CHECK(llsd["links"][0]["missed"].asBoolean()); + UNSCOPED_INFO("and not where not"); + REQUIRE_FALSE(llsd["links"][1].has("missed")); std::ostringstream out; LLSDSerialize::toNotation(llsd, out); std::istringstream in(out.str()); LLSD back; - ensure("as notation", LLSDSerialize::fromNotation(back, in, static_cast(out.str().size())) > 0); + UNSCOPED_INFO("as notation"); + REQUIRE(LLSDSerialize::fromNotation(back, in, static_cast(out.str().size())) > 0); const ALMasterLinks read = ALMasterLinks::fromLLSD(back); - ensure("read back set", read.of(almasterlinks_data::key(1), almasterlinks_data::key(10))->missed); - ensure("and not", !read.of(almasterlinks_data::key(1), almasterlinks_data::key(11))->missed); + UNSCOPED_INFO("read back set"); + CHECK(read.of(almasterlinks_data::key(1), almasterlinks_data::key(10))->missed); + UNSCOPED_INFO("and not"); + CHECK_FALSE(read.of(almasterlinks_data::key(1), almasterlinks_data::key(11))->missed); // As an older version wrote it, with no such field, and as it might // be written otherwise. @@ -455,16 +556,18 @@ namespace tut record["missed"] = LLSD::emptyMap(); older["links"].append(record); const ALMasterLinks old = ALMasterLinks::fromLLSD(older); - ensure_equals("all read", old.size(), size_t(3)); - ensure("none said is none missed", !old.of(LLUUID::null, almasterlinks_data::key(12))->missed); - ensure("a number is read as one", old.of(LLUUID::null, almasterlinks_data::key(13))->missed); - ensure("what is no value is none", !old.of(LLUUID::null, almasterlinks_data::key(14))->missed); + UNSCOPED_INFO("all read"); + REQUIRE(old.size() == size_t(3)); + UNSCOPED_INFO("none said is none missed"); + CHECK_FALSE(old.of(LLUUID::null, almasterlinks_data::key(12))->missed); + UNSCOPED_INFO("a number is read as one"); + CHECK(old.of(LLUUID::null, almasterlinks_data::key(13))->missed); + UNSCOPED_INFO("what is no value is none"); + CHECK_FALSE(old.of(LLUUID::null, almasterlinks_data::key(14))->missed); } - template<> template<> - void almasterlinks_object::test<9>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: affectedBy lists users and links that missed but none suspended or orphaned", "[almasterlinks]") { - set_test_name("an include's change may send its users and every link that missed something, each once, none suspended or orphaned"); ALMasterLinks links; const auto with = [&links](int item, const std::vector& uses, bool missed, ALMasterLink::State state) { ALMasterLink one = almasterlinks_data::link(1, item, "/s/master" + std::to_string(item) + ".lsl"); @@ -483,25 +586,28 @@ namespace tut with(16, { "disk:/s/lib/util.lsl" }, false, State::Differing); with(17, {}, true, State::Suspended); - ensure_equals("its users and those that missed, in the order put", - almasterlinks_data::items(links.affectedBy("disk:/s/lib/util.lsl")), std::string("10 11 14 16")); - ensure_equals("by its path alone the same", almasterlinks_data::items(links.affectedBy("/s/lib/util.lsl")), + UNSCOPED_INFO("its users and those that missed, in the order put"); + CHECK(almasterlinks_data::items(links.affectedBy("disk:/s/lib/util.lsl")) == std::string("10 11 14 16")); + UNSCOPED_INFO("by its path alone the same"); + CHECK(almasterlinks_data::items(links.affectedBy("/s/lib/util.lsl")) == std::string("10 11 14 16")); - ensure_equals("a file nobody read: those that missed", almasterlinks_data::items(links.affectedBy("/s/lib/new.lsl")), + UNSCOPED_INFO("a file nobody read: those that missed"); + CHECK(almasterlinks_data::items(links.affectedBy("/s/lib/new.lsl")) == std::string("11 14")); - ensure_equals("one both a user and missing is one", almasterlinks_data::items(links.affectedBy("/s/lib/net.lsl")), + UNSCOPED_INFO("one both a user and missing is one"); + CHECK(almasterlinks_data::items(links.affectedBy("/s/lib/net.lsl")) == std::string("11 14 15")); // Expanded again, and nothing missed now. links.find(almasterlinks_data::key(1), almasterlinks_data::key(14))->missed = false; - ensure_equals("no longer", almasterlinks_data::items(links.affectedBy("/s/lib/new.lsl")), std::string("11")); - ensure("nothing at all with nothing linked", ALMasterLinks().affectedBy("/s/lib/util.lsl").empty()); + 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"); + REQUIRE(ALMasterLinks().affectedBy("/s/lib/util.lsl").empty()); } - template<> template<> - void almasterlinks_object::test<10>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: watched lists live and suspended masters and live disk includes once each", "[almasterlinks]") { - set_test_name("the files to watch: live and suspended links' masters, live ones' disk includes, each once, a master as a master"); ALMasterLinks links; const auto with = [&links](int item, const std::string& master, const std::vector& uses, ALMasterLink::State state) { ALMasterLink one = almasterlinks_data::link(1, item, master); @@ -524,21 +630,22 @@ namespace tut with(16, "C:\\S\\G.lsl", { "disk:/s/a.lsl" }, State::Active); with(17, "c:/s/g.lsl", {}, State::Active); - ensure_equals("each once, in the order met", almasterlinks_data::watched(links.watched()), + UNSCOPED_INFO("each once, in the order met"); + 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* /s/e.lsl* C:\\S\\G.lsl*")); - ensure("nothing with nothing linked", ALMasterLinks().watched().empty()); + UNSCOPED_INFO("nothing with nothing linked"); + 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; - ensure_equals("an include again where it no longer masters", almasterlinks_data::watched(links.watched()), + 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*")); } - template<> template<> - void almasterlinks_object::test<11>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: orphansNamed lists an item name's orphans by object name then latest", "[almasterlinks]") { - set_test_name("the orphaned links of an item's name, those of the object's name first, then the latest"); ALMasterLinks links; const auto orphan = [&links](int item, const std::string& item_name, const std::string& object_name, F64 since) { ALMasterLink one = almasterlinks_data::link(1, item, "/s/" + std::to_string(item) + ".lsl"); @@ -561,29 +668,34 @@ namespace tut links.put(live); orphan(17, "door", "Shed", 3000.0); - ensure_equals("the object's first, then the latest, as put among equals", - almasterlinks_data::items(links.orphansNamed("door", "Door")), std::string("12 11 13 17 10")); - ensure_equals("an object of no such name: the latest first", - almasterlinks_data::items(links.orphansNamed("door", "Window")), std::string("13 17 12 10 11")); - ensure_equals("names compared as written", almasterlinks_data::items(links.orphansNamed("Door", "door")), std::string("15")); - ensure("an item of no such name has none", links.orphansNamed("window", "Door").empty()); - ensure("nor an item with no name", links.orphansNamed("", "Door").empty()); + UNSCOPED_INFO("the object's first, then the latest, as put among equals"); + CHECK(almasterlinks_data::items(links.orphansNamed("door", "Door")) == std::string("12 11 13 17 10")); + UNSCOPED_INFO("an object of no such name: the latest first"); + CHECK(almasterlinks_data::items(links.orphansNamed("door", "Window")) == std::string("13 17 12 10 11")); + UNSCOPED_INFO("names compared as written"); + CHECK(almasterlinks_data::items(links.orphansNamed("Door", "door")) == std::string("15")); + UNSCOPED_INFO("an item of no such name has none"); + REQUIRE(links.orphansNamed("window", "Door").empty()); + UNSCOPED_INFO("nor an item with no name"); + REQUIRE(links.orphansNamed("", "Door").empty()); } - template<> template<> - void almasterlinks_object::test<12>() + TEST_CASE_METHOD(almasterlinks_data, "almasterlinks: a notecard link round-trips through the LLSD and a script's omits the flag", "[almasterlinks]") { - set_test_name("a notecard's link comes back through the LLSD as one, and a script's says nothing of it"); ALMasterLinks links; ALMasterLink note = almasterlinks_data::link(1, 10, "/s/readme.txt"); note.notecard = true; links.put(note); links.put(almasterlinks_data::link(1, 11, "/s/lamp.lsl")); const LLSD llsd = links.toLLSD(); - ensure("written where set", llsd["links"][0]["notecard"].asBoolean()); - ensure("and not where not", !llsd["links"][1].has("notecard")); + UNSCOPED_INFO("written where set"); + 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); - ensure("read back a notecard's", read.of(almasterlinks_data::key(1), almasterlinks_data::key(10))->notecard); - ensure("and a script's", !read.of(almasterlinks_data::key(1), almasterlinks_data::key(11))->notecard); + UNSCOPED_INFO("read back a notecard's"); + CHECK(read.of(almasterlinks_data::key(1), almasterlinks_data::key(10))->notecard); + UNSCOPED_INFO("and a script's"); + CHECK_FALSE(read.of(almasterlinks_data::key(1), almasterlinks_data::key(11))->notecard); } } diff --git a/indra/alscript/tests/almastermatch_test.cpp b/indra/alscript/tests/almastermatch_test.cpp index 074b278a47..d847e3a9d1 100644 --- a/indra/alscript/tests/almastermatch_test.cpp +++ b/indra/alscript/tests/almastermatch_test.cpp @@ -29,7 +29,7 @@ #include "fsyspath.h" #include "llfile.h" -#include "../test/lltut.h" +#include #include #include @@ -76,7 +76,7 @@ namespace using Aliases = std::vector>; } -namespace tut +namespace { struct almastermatch_data { @@ -103,21 +103,19 @@ namespace tut } }; - typedef test_group almastermatch_group; - typedef almastermatch_group::object almastermatch_object; - almastermatch_group almastermatch_instance("almastermatch"); - - template<> template<> - void almastermatch_object::test<1>() + TEST_CASE_METHOD(almastermatch_data, "almastermatch: a hint is an @file comment in the first ten lines or an upload header's", "[almastermatch]") { - set_test_name("a hint is an @file comment among the first ten lines of the source, else an upload header's"); - ensure_equals("LSL's", ALMasterMatch::hintOf("// @file net/door.lsl\ndefault {}\n", "", false).value_or("-"), std::string("net/door.lsl")); - ensure_equals("SLua's", ALMasterMatch::hintOf("-- @file net/door.luau\nprint(1)\n", "", true).value_or("-"), std::string("net/door.luau")); - ensure_equals("indented, close up, in any case, the line's end trimmed", - ALMasterMatch::hintOf(" //@FILE\tnet/door.lsl \r\n", "", false).value_or("-"), std::string("net/door.lsl")); - ensure_equals("quoted for its spaces", ALMasterMatch::hintOf("// @file \"my scripts/door.lsl\"\n", "", false).value_or("-"), + UNSCOPED_INFO("LSL's"); + CHECK(ALMasterMatch::hintOf("// @file net/door.lsl\ndefault {}\n", "", false).value_or("-") == std::string("net/door.lsl")); + UNSCOPED_INFO("SLua's"); + CHECK(ALMasterMatch::hintOf("-- @file net/door.luau\nprint(1)\n", "", true).value_or("-") == std::string("net/door.luau")); + UNSCOPED_INFO("indented, close up, in any case, the line's end trimmed"); + CHECK(ALMasterMatch::hintOf(" //@FILE\tnet/door.lsl \r\n", "", false).value_or("-") == std::string("net/door.lsl")); + UNSCOPED_INFO("quoted for its spaces"); + CHECK(ALMasterMatch::hintOf("// @file \"my scripts/door.lsl\"\n", "", false).value_or("-") == std::string("my scripts/door.lsl")); - ensure_equals("past a byte order mark", ALMasterMatch::hintOf("\xEF\xBB\xBF// @file door.lsl\n", "", false).value_or("-"), + UNSCOPED_INFO("past a byte order mark"); + CHECK(ALMasterMatch::hintOf("\xEF\xBB\xBF// @file door.lsl\n", "", false).value_or("-") == std::string("door.lsl")); std::string ninth; @@ -125,15 +123,22 @@ namespace tut { ninth += "integer x" + std::to_string(line) + ";\n"; } - ensure_equals("the tenth line", ALMasterMatch::hintOf(ninth + "// @file late.lsl\n", "", false).value_or("-"), std::string("late.lsl")); - ensure("not the eleventh", !ALMasterMatch::hintOf(ninth + "\n// @file late.lsl\n", "", false)); - ensure("not the other language's comment", !ALMasterMatch::hintOf("// @file door.luau\n", "", true)); - ensure("nor code", !ALMasterMatch::hintOf("string f = \"// @file door.lsl\";\n", "", false)); - ensure("nor a word that only begins so", !ALMasterMatch::hintOf("// @filename door.lsl\n", "", false)); - ensure("nor a doc comment's third dash", !ALMasterMatch::hintOf("--- @file door.luau\n", "", true)); - ensure("nor one naming nothing", !ALMasterMatch::hintOf("// @file \n", "", false)); - ensure_equals("passing one naming nothing for the next", - ALMasterMatch::hintOf("// @file\t\n// @file door.lsl\n", "", false).value_or("-"), std::string("door.lsl")); + UNSCOPED_INFO("the tenth line"); + CHECK(ALMasterMatch::hintOf(ninth + "// @file late.lsl\n", "", false).value_or("-") == std::string("late.lsl")); + UNSCOPED_INFO("not the eleventh"); + REQUIRE_FALSE(ALMasterMatch::hintOf(ninth + "\n// @file late.lsl\n", "", false)); + UNSCOPED_INFO("not the other language's comment"); + REQUIRE_FALSE(ALMasterMatch::hintOf("// @file door.luau\n", "", true)); + UNSCOPED_INFO("nor code"); + REQUIRE_FALSE(ALMasterMatch::hintOf("string f = \"// @file door.lsl\";\n", "", false)); + UNSCOPED_INFO("nor a word that only begins so"); + REQUIRE_FALSE(ALMasterMatch::hintOf("// @filename door.lsl\n", "", false)); + UNSCOPED_INFO("nor a doc comment's third dash"); + REQUIRE_FALSE(ALMasterMatch::hintOf("--- @file door.luau\n", "", true)); + UNSCOPED_INFO("nor one naming nothing"); + REQUIRE_FALSE(ALMasterMatch::hintOf("// @file \n", "", false)); + UNSCOPED_INFO("passing one naming nothing for the next"); + CHECK(ALMasterMatch::hintOf("// @file\t\n// @file door.lsl\n", "", false).value_or("-") == std::string("door.lsl")); const std::string ours = "// ================ alchemy meta ================\n" "// @file net/door.lsl\n" @@ -143,23 +148,27 @@ namespace tut "-- @file src/door.luau\n" "-- @hash 00\n" "-- ==================================================\n"; - ensure_equals("our header in the compiled half", ALMasterMatch::hintOf("default {}\n", ours + "default {}\n", false).value_or("-"), + UNSCOPED_INFO("our header in the compiled half"); + CHECK(ALMasterMatch::hintOf("default {}\n", ours + "default {}\n", false).value_or("-") == std::string("net/door.lsl")); - ensure_equals("past the target line", ALMasterMatch::hintOf("default {}\n", "//mono\n" + ours + "default {}\n", false).value_or("-"), + UNSCOPED_INFO("past the target line"); + CHECK(ALMasterMatch::hintOf("default {}\n", "//mono\n" + ours + "default {}\n", false).value_or("-") == std::string("net/door.lsl")); - ensure_equals("the plugin's", ALMasterMatch::hintOf("print(1)\n", "--luau\n" + theirs + "print(1)\n", true).value_or("-"), + UNSCOPED_INFO("the plugin's"); + CHECK(ALMasterMatch::hintOf("print(1)\n", "--luau\n" + theirs + "print(1)\n", true).value_or("-") == std::string("src/door.luau")); - ensure_equals("the source's own first", ALMasterMatch::hintOf("// @file mine.lsl\n", ours, false).value_or("-"), std::string("mine.lsl")); - ensure("not a header further down", !ALMasterMatch::hintOf("default {}\n", "default {}\n" + ours, false)); - ensure("nor one with no @file", - !ALMasterMatch::hintOf("x\n", "// ================ alchemy meta ================\n// @hash xxh128:00\n// ====\n", false)); - ensure("nothing where nothing says", !ALMasterMatch::hintOf("default {}\n", "default {}\n", false)); + UNSCOPED_INFO("the source's own first"); + CHECK(ALMasterMatch::hintOf("// @file mine.lsl\n", ours, false).value_or("-") == std::string("mine.lsl")); + UNSCOPED_INFO("not a header further down"); + REQUIRE_FALSE(ALMasterMatch::hintOf("default {}\n", "default {}\n" + ours, false)); + UNSCOPED_INFO("nor one with no @file"); + REQUIRE_FALSE(ALMasterMatch::hintOf("x\n", "// ================ alchemy meta ================\n// @hash xxh128:00\n// ====\n", false)); + UNSCOPED_INFO("nothing where nothing says"); + REQUIRE_FALSE(ALMasterMatch::hintOf("default {}\n", "default {}\n", false)); } - template<> template<> - void almastermatch_object::test<2>() + TEST_CASE_METHOD(almastermatch_data, "almastermatch: a relative hint resolves in the first blessed folder that has it", "[almastermatch]") { - set_test_name("a relative hint is looked for in each blessed folder in turn, and the first found wins"); Scratch s; const std::string first = s.write("a/net/door.lsl"); const std::string only = s.write("b/only.lsl"); @@ -168,26 +177,32 @@ namespace tut blessed.bless(s.at("a")); blessed.bless(s.at("b")); - ensure_equals("in both, the first folder's", almastermatch_data::resolved("net/door.lsl", blessed, false), first); - ensure_equals("with Windows's separators", almastermatch_data::resolved("net\\door.lsl", blessed, false), first); - ensure_equals("in the second only, the second's", almastermatch_data::resolved("only.lsl", blessed, false), only); - ensure_equals("a `.` on the way", almastermatch_data::resolved("./net/./door.lsl", blessed, false), first); - ensure_equals("in neither", almastermatch_data::resolved("missing.lsl", blessed, false), + UNSCOPED_INFO("in both, the first folder's"); + CHECK(almastermatch_data::resolved("net/door.lsl", blessed, false) == first); + UNSCOPED_INFO("with Windows's separators"); + CHECK(almastermatch_data::resolved("net\\door.lsl", blessed, false) == first); + UNSCOPED_INFO("in the second only, the second's"); + CHECK(almastermatch_data::resolved("only.lsl", blessed, false) == only); + UNSCOPED_INFO("a `.` on the way"); + CHECK(almastermatch_data::resolved("./net/./door.lsl", blessed, false) == first); + UNSCOPED_INFO("in neither"); + CHECK(almastermatch_data::resolved("missing.lsl", blessed, false) == std::string("- 'missing.lsl' is not a file in the include folders, or not an ordinary file of at most 4 MB")); fs::create_directories(s.under("a/folder.lsl")); - ensure_equals("a folder is no file, whatever it is called", almastermatch_data::resolved("folder.lsl", blessed, false).substr(0, 1), + UNSCOPED_INFO("a folder is no file, whatever it is called"); + CHECK(almastermatch_data::resolved("folder.lsl", blessed, false).substr(0, 1) == std::string("-")); ALDiskIncludes none; - ensure_equals("no folder blessed, looked for nowhere", almastermatch_data::resolved("only.lsl", none, false), + UNSCOPED_INFO("no folder blessed, looked for nowhere"); + CHECK(almastermatch_data::resolved("only.lsl", none, false) == std::string("- no include folders are set, so 'only.lsl' is looked for nowhere")); - ensure_equals("a hint naming nothing", almastermatch_data::resolved(" ", blessed, false), std::string("- the @file hint names no file")); + UNSCOPED_INFO("a hint naming nothing"); + CHECK(almastermatch_data::resolved(" ", blessed, false) == std::string("- the @file hint names no file")); } - template<> template<> - void almastermatch_object::test<3>() + TEST_CASE_METHOD(almastermatch_data, "almastermatch: no climb or root or link or share reaches outside the blessed folders", "[almastermatch]") { - set_test_name("nothing is reached outside the blessed folders: not by climbing, from a root, through a link, nor a share"); Scratch s; const std::string secret = s.write("private/secret.lsl", "// the author's own\n"); const std::string inside = s.write("a/net/door.lsl"); @@ -196,51 +211,60 @@ namespace tut blessed.bless(s.at("a")); blessed.bless(s.at("b")); - ensure_equals("climbing out of every blessed folder", almastermatch_data::resolved("../private/secret.lsl", blessed, false), + UNSCOPED_INFO("climbing out of every blessed folder"); + CHECK(almastermatch_data::resolved("../private/secret.lsl", blessed, false) == std::string("- '../private/secret.lsl' is outside the include folders")); - ensure_equals("however deep it starts", almastermatch_data::resolved("net/../../private/secret.lsl", blessed, false), + UNSCOPED_INFO("however deep it starts"); + CHECK(almastermatch_data::resolved("net/../../private/secret.lsl", blessed, false) == std::string("- 'net/../../private/secret.lsl' is outside the include folders")); - ensure_equals("climbing within one is no climb", almastermatch_data::resolved("net/../net/door.lsl", blessed, false), inside); - ensure_equals("nor into another blessed folder, as an include could read it", - almastermatch_data::resolved("../b/other.lsl", blessed, false), other); - - ensure_equals("from a root, under a blessed folder", almastermatch_data::resolved(inside, blessed, false), inside); - ensure_equals("from a root, outside", almastermatch_data::resolved(secret, blessed, false), + UNSCOPED_INFO("climbing within one is no climb"); + CHECK(almastermatch_data::resolved("net/../net/door.lsl", blessed, false) == inside); + UNSCOPED_INFO("nor into another blessed folder, as an include could read it"); + CHECK(almastermatch_data::resolved("../b/other.lsl", blessed, false) == other); + + UNSCOPED_INFO("from a root, under a blessed folder"); + CHECK(almastermatch_data::resolved(inside, blessed, false) == inside); + UNSCOPED_INFO("from a root, outside"); + CHECK(almastermatch_data::resolved(secret, blessed, false) == "- '" + secret + "' is outside the include folders"); - ensure_equals("from a root, climbing out", almastermatch_data::resolved(s.at("a/../private/secret.lsl"), blessed, false).substr(0, 1), + UNSCOPED_INFO("from a root, climbing out"); + CHECK(almastermatch_data::resolved(s.at("a/../private/secret.lsl"), blessed, false).substr(0, 1) == std::string("-")); for (const char* elsewhere : { "\\\\host\\share\\door.lsl", "//host/share/door.lsl", "\\\\?\\UNC\\host\\share\\door.lsl", "C:\\door.lsl", "c:door.lsl" }) { - ensure_equals(std::string("never asked of: ") + elsewhere, almastermatch_data::resolved(elsewhere, blessed, false), + UNSCOPED_INFO(std::string("never asked of: ") + elsewhere); + REQUIRE(almastermatch_data::resolved(elsewhere, blessed, false) == "- '" + std::string(elsewhere) + "' is outside the include folders"); } - ensure_equals("nor a home folder", almastermatch_data::resolved("~/door.lsl", blessed, false), + UNSCOPED_INFO("nor a home folder"); + CHECK(almastermatch_data::resolved("~/door.lsl", blessed, false) == std::string("- '~/door.lsl' is from a home folder, which a hint is not followed to")); std::error_code ec; fs::create_symlink(fsyspath(secret), s.root / "a" / "innocent.lsl", ec); if (!ec) { - ensure_equals("a link out of the folder", almastermatch_data::resolved("innocent.lsl", blessed, false).substr(0, 1), std::string("-")); + UNSCOPED_INFO("a link out of the folder"); + CHECK(almastermatch_data::resolved("innocent.lsl", blessed, false).substr(0, 1) == std::string("-")); } fs::create_directory_symlink(s.root / "private", s.root / "a" / "door", ec); if (!ec) { - ensure_equals("nor a file through a linked folder", almastermatch_data::resolved("door/secret.lsl", blessed, false).substr(0, 1), + UNSCOPED_INFO("nor a file through a linked folder"); + CHECK(almastermatch_data::resolved("door/secret.lsl", blessed, false).substr(0, 1) == std::string("-")); } fs::create_symlink(fsyspath(other), s.root / "a" / "inward.lsl", ec); if (!ec) { - ensure_equals("a link to another blessed folder is followed", almastermatch_data::resolved("inward.lsl", blessed, false), other); + UNSCOPED_INFO("a link to another blessed folder is followed"); + CHECK(almastermatch_data::resolved("inward.lsl", blessed, false) == other); } } - template<> template<> - void almastermatch_object::test<4>() + TEST_CASE_METHOD(almastermatch_data, "almastermatch: only a script of the item's language and a sensible size", "[almastermatch]") { - set_test_name("only a script of the item's language, of a sensible size"); Scratch s; const std::string notes = s.write("a/notes.txt", "keys\n"); const std::string door = s.write("a/door.lsl"); @@ -251,37 +275,46 @@ namespace tut ALDiskIncludes blessed; blessed.bless(s.at("a")); - ensure_equals("not another kind of file", almastermatch_data::resolved("notes.txt", blessed, false), + UNSCOPED_INFO("not another kind of file"); + CHECK(almastermatch_data::resolved("notes.txt", blessed, false) == std::string("- 'notes.txt' is not an LSL script (.lsl, .lslh or .lsli)")); - ensure_equals("nor a name with no extension", almastermatch_data::resolved("door", blessed, false).substr(0, 1), std::string("-")); - ensure_equals("LSL's own", almastermatch_data::resolved("door.lsl", blessed, false), door); + UNSCOPED_INFO("nor a name with no extension"); + CHECK(almastermatch_data::resolved("door", blessed, false).substr(0, 1) == std::string("-")); + UNSCOPED_INFO("LSL's own"); + CHECK(almastermatch_data::resolved("door.lsl", blessed, false) == door); // As the disk finds it: where it is the same file in any case, by // the case the disk keeps it in. const std::string upper = almastermatch_data::resolved("door.LSL", blessed, false); - ensure("an extension in any case", fs::exists(s.under("a/door.LSL")) ? upper == door : upper.substr(0, 1) == "-"); - ensure_equals("and its includes'", almastermatch_data::resolved("lib.lslh", blessed, false), header); - ensure_equals("not SLua's for LSL", almastermatch_data::resolved("util.luau", blessed, false), + UNSCOPED_INFO("an extension in any case"); + CHECK((fs::exists(s.under("a/door.LSL")) ? upper == door : upper.substr(0, 1) == "-")); + UNSCOPED_INFO("and its includes'"); + CHECK(almastermatch_data::resolved("lib.lslh", blessed, false) == header); + UNSCOPED_INFO("not SLua's for LSL"); + CHECK(almastermatch_data::resolved("util.luau", blessed, false) == std::string("- 'util.luau' is not an LSL script (.lsl, .lslh or .lsli)")); - ensure_equals("nor LSL's for SLua", almastermatch_data::resolved("door.lsl", blessed, true), + UNSCOPED_INFO("nor LSL's for SLua"); + CHECK(almastermatch_data::resolved("door.lsl", blessed, true) == std::string("- 'door.lsl' is not an SLua script (.luau or .lua)")); - ensure_equals("SLua's own", almastermatch_data::resolved("util.luau", blessed, true), util); - ensure_equals("and its older", almastermatch_data::resolved("old.lua", blessed, true), old); - ensure_equals("not one too big", almastermatch_data::resolved("big.lsl", blessed, false), + UNSCOPED_INFO("SLua's own"); + CHECK(almastermatch_data::resolved("util.luau", blessed, true) == util); + UNSCOPED_INFO("and its older"); + CHECK(almastermatch_data::resolved("old.lua", blessed, true) == old); + UNSCOPED_INFO("not one too big"); + CHECK(almastermatch_data::resolved("big.lsl", blessed, false) == std::string("- 'big.lsl' is not a file in the include folders, or not an ordinary file of at most 4 MB")); std::error_code ec; fs::create_symlink(fsyspath(notes), s.root / "a" / "fake.lsl", ec); if (!ec) { - ensure_equals("a script's name for another kind of file", almastermatch_data::resolved("fake.lsl", blessed, false), + UNSCOPED_INFO("a script's name for another kind of file"); + CHECK(almastermatch_data::resolved("fake.lsl", blessed, false) == std::string("- 'fake.lsl' leads to a file that is not an LSL script (.lsl, .lslh or .lsli)")); } } - template<> template<> - void almastermatch_object::test<5>() + TEST_CASE_METHOD(almastermatch_data, "almastermatch: an alias names a folder but is held to the blessed folders all the same", "[almastermatch]") { - set_test_name("an alias names a folder of the studio's or a configuration's, and is held to the blessed folders all the same"); Scratch s; const std::string util = s.write("a/lib/util.luau", "return {}\n"); s.write("private/x.luau", "return {}\n"); @@ -289,23 +322,28 @@ namespace tut blessed.bless(s.at("a")); const Aliases aliases{ { "lib", s.at("a/lib") }, { "@out", s.at("private") }, { "loose", "lib" } }; - ensure_equals("through the alias", almastermatch_data::resolved("@lib/util.luau", blessed, true, aliases), util); - ensure_equals("named in any case", almastermatch_data::resolved("@LIB/util.luau", blessed, true, aliases), util); - ensure_equals("one not set", almastermatch_data::resolved("@nope/util.luau", blessed, true, aliases), + UNSCOPED_INFO("through the alias"); + CHECK(almastermatch_data::resolved("@lib/util.luau", blessed, true, aliases) == util); + UNSCOPED_INFO("named in any case"); + CHECK(almastermatch_data::resolved("@LIB/util.luau", blessed, true, aliases) == util); + UNSCOPED_INFO("one not set"); + CHECK(almastermatch_data::resolved("@nope/util.luau", blessed, true, aliases) == std::string("- '@nope/util.luau' names the alias @nope, which is not set")); - ensure_equals("one whose folder is not blessed", almastermatch_data::resolved("@out/x.luau", blessed, true, aliases), + UNSCOPED_INFO("one whose folder is not blessed"); + CHECK(almastermatch_data::resolved("@out/x.luau", blessed, true, aliases) == std::string("- '@out/x.luau' is outside the include folders")); - ensure_equals("one climbed out of", almastermatch_data::resolved("@lib/../../private/x.luau", blessed, true, aliases), + UNSCOPED_INFO("one climbed out of"); + CHECK(almastermatch_data::resolved("@lib/../../private/x.luau", blessed, true, aliases) == std::string("- '@lib/../../private/x.luau' is outside the include folders")); - ensure_equals("one that is no folder on disk", almastermatch_data::resolved("@loose/util.luau", blessed, true, aliases), + UNSCOPED_INFO("one that is no folder on disk"); + CHECK(almastermatch_data::resolved("@loose/util.luau", blessed, true, aliases) == std::string("- the alias @loose is no folder on disk")); - ensure_equals("the alias alone is no file", almastermatch_data::resolved("@lib", blessed, true, aliases).substr(0, 1), std::string("-")); + UNSCOPED_INFO("the alias alone is no file"); + CHECK(almastermatch_data::resolved("@lib", blessed, true, aliases).substr(0, 1) == std::string("-")); } - template<> template<> - void almastermatch_object::test<6>() + TEST_CASE_METHOD(almastermatch_data, "almastermatch: byName lists . then the plugin's loose forms best first", "[almastermatch]") { - set_test_name("by name: . anywhere under a blessed folder, then the plugin's loose forms, every one best first"); Scratch s; const std::string door = s.write("a/door.lsl"); const std::string net_door = s.write("a/net/door.lsl"); @@ -322,34 +360,42 @@ namespace tut blessed.bless(s.at("a")); blessed.bless(s.at("b")); - ensure_equals("the folder blessed first, then the nearer its top; none hidden, none not blessed", - almastermatch_data::joined(ALMasterMatch::byName("door", blessed, false)), + UNSCOPED_INFO("the folder blessed first, then the nearer its top; none hidden, none not blessed"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("door", blessed, false)) == almastermatch_data::joined({ door, net_door, b_door })); - ensure_equals("a name with a folder in it, by its last parts", almastermatch_data::joined(ALMasterMatch::byName("net/door", blessed, false)), + UNSCOPED_INFO("a name with a folder in it, by its last parts"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("net/door", blessed, false)) == net_door); - ensure_equals("a name with its extension already", almastermatch_data::joined(ALMasterMatch::byName("door.lsl", blessed, false)), + UNSCOPED_INFO("a name with its extension already"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("door.lsl", blessed, false)) == almastermatch_data::joined({ door, net_door, b_door })); - ensure_equals("a name, then its folders run into it with _", almastermatch_data::joined(ALMasterMatch::byName("net_gate", blessed, false)), + UNSCOPED_INFO("a name, then its folders run into it with _"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("net_gate", blessed, false)) == almastermatch_data::joined({ flat, gate })); - ensure_equals("or with nothing", almastermatch_data::joined(ALMasterMatch::byName("netgate", blessed, false)), gate); - ensure_equals("or with a space", almastermatch_data::joined(ALMasterMatch::byName("net gate", blessed, false)), gate); - ensure_equals("its own case first, then any", almastermatch_data::joined(ALMasterMatch::byName("lamp", blessed, false)), + UNSCOPED_INFO("or with nothing"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("netgate", blessed, false)) == gate); + UNSCOPED_INFO("or with a space"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("net gate", blessed, false)) == gate); + UNSCOPED_INFO("its own case first, then any"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("lamp", blessed, false)) == almastermatch_data::joined({ lamp, big_lamp })); - ensure("not an include's header", ALMasterMatch::byName("util", blessed, false).empty()); - ensure("nor for no name", ALMasterMatch::byName("", blessed, false).empty()); - ensure("nor in no folder", ALMasterMatch::byName("door", ALDiskIncludes(), false).empty()); + UNSCOPED_INFO("not an include's header"); + REQUIRE(ALMasterMatch::byName("util", blessed, false).empty()); + UNSCOPED_INFO("nor for no name"); + REQUIRE(ALMasterMatch::byName("", blessed, false).empty()); + UNSCOPED_INFO("nor in no folder"); + REQUIRE(ALMasterMatch::byName("door", ALDiskIncludes(), false).empty()); const std::vector listed = ALMasterMatch::listing(blessed, false); - ensure_equals("listed once for many names, the same", - almastermatch_data::joined(ALMasterMatch::byName("door", listed, false)), + UNSCOPED_INFO("listed once for many names, the same"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("door", listed, false)) == almastermatch_data::joined(ALMasterMatch::byName("door", blessed, false))); - ensure("and given up where asked", ALMasterMatch::listing(blessed, false, [] { return true; }).empty()); + UNSCOPED_INFO("and given up where asked"); + REQUIRE(ALMasterMatch::listing(blessed, false, [] { return true; }).empty()); } - template<> template<> - void almastermatch_object::test<7>() + TEST_CASE_METHOD(almastermatch_data, "almastermatch: by name in SLua: .luau then .lua", "[almastermatch]") { - set_test_name("by name in SLua: .luau, then .lua"); Scratch s; const std::string luau = s.write("a/util.luau", "return {}\n"); const std::string lua = s.write("a/util.lua", "return {}\n"); @@ -357,20 +403,24 @@ namespace tut s.write("a/util.lsl"); ALDiskIncludes blessed; blessed.bless(s.at("a")); - ensure_equals("the modern first, nearer the top first among those", - almastermatch_data::joined(ALMasterMatch::byName("util", blessed, true)), almastermatch_data::joined({ luau, deep, lua })); - ensure_equals("and LSL's for LSL", ALMasterMatch::byName("util", blessed, false).size(), size_t(1)); + UNSCOPED_INFO("the modern first, nearer the top first among those"); + CHECK(almastermatch_data::joined(ALMasterMatch::byName("util", blessed, true)) == almastermatch_data::joined({ luau, deep, lua })); + UNSCOPED_INFO("and LSL's for LSL"); + REQUIRE(ALMasterMatch::byName("util", blessed, false).size() == size_t(1)); } - template<> template<> - void almastermatch_object::test<8>() + TEST_CASE_METHOD(almastermatch_data, "almastermatch: a notecard's master is text or notecard or JSON in any case; never a script", "[almastermatch]") { - set_test_name("a notecard's master is text, a notecard's own, or JSON, in any case, and never a script"); const std::vector& notecards = ALMasterMatch::notecardExtensions(); - ensure("text", ALDiskIncludes::extensionOf("readme.txt", notecards) != 0); - ensure("a notecard", ALDiskIncludes::extensionOf("Config.NOTECARD", notecards) != 0); - ensure("JSON", ALDiskIncludes::extensionOf("data/settings.json", notecards) != 0); - ensure("not a script", ALDiskIncludes::extensionOf("door.lsl", notecards) == 0); - ensure("nor SLua", ALDiskIncludes::extensionOf("door.luau", notecards) == 0); + UNSCOPED_INFO("text"); + CHECK(ALDiskIncludes::extensionOf("readme.txt", notecards) != 0); + UNSCOPED_INFO("a notecard"); + CHECK(ALDiskIncludes::extensionOf("Config.NOTECARD", notecards) != 0); + UNSCOPED_INFO("JSON"); + CHECK(ALDiskIncludes::extensionOf("data/settings.json", notecards) != 0); + UNSCOPED_INFO("not a script"); + CHECK(ALDiskIncludes::extensionOf("door.lsl", notecards) == 0); + UNSCOPED_INFO("nor SLua"); + CHECK(ALDiskIncludes::extensionOf("door.luau", notecards) == 0); } } diff --git a/indra/alscript/tests/almasterplan_test.cpp b/indra/alscript/tests/almasterplan_test.cpp index 5dde281d26..2ad87a8c3c 100644 --- a/indra/alscript/tests/almasterplan_test.cpp +++ b/indra/alscript/tests/almasterplan_test.cpp @@ -26,12 +26,12 @@ #include "../masters/almasterplan.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct almasterplan_data { @@ -55,14 +55,8 @@ namespace tut } }; - typedef test_group almasterplan_group; - typedef almasterplan_group::object almasterplan_object; - almasterplan_group almasterplan_instance("almasterplan"); - - template<> template<> - void almasterplan_object::test<1>() + TEST_CASE_METHOD(almasterplan_data, "almasterplan: every send for every state of the world and the file and either setting", "[almasterplan]") { - set_test_name("every send, every state of the world and the file, and the setting"); using Send = almasterplan_data::Send; using Do = almasterplan_data::Do; struct Row @@ -114,49 +108,64 @@ namespace tut { Send::Derived, true, true, true, false, Do::Skip }, { Send::Derived, true, true, true, true, Do::Skip }, }; - ensure_equals("every row", sizeof(table) / sizeof(table[0]), size_t(32)); + UNSCOPED_INFO("every row"); + CHECK(sizeof(table) / sizeof(table[0]) == size_t(32)); for (const Row& row : table) { const std::string name = std::string(row.kind == Send::Direct ? "direct" : "derived") + (row.moved ? ", moved" : ", not moved") + (row.same ? ", the same" : ", not the same") + (row.unchanged ? ", unchanged" : ", changed") + (row.skip ? ", skipping" : ", not skipping"); - ensure_equals(name, almasterplan_data::nameOf(ALMasterPlan::decide(row.kind, row.moved, row.same, row.unchanged, row.skip)), + UNSCOPED_INFO(name); + REQUIRE(almasterplan_data::nameOf(ALMasterPlan::decide(row.kind, row.moved, row.same, row.unchanged, row.skip)) == almasterplan_data::nameOf(row.expected)); } } - template<> template<> - void almasterplan_object::test<2>() + TEST_CASE_METHOD(almasterplan_data, "almasterplan: askFirst: past the count or across two objects; never with none changing", "[almasterplan]") { - set_test_name("an include's users are asked about over the count, or across two objects; never with none changing"); - ensure("none changing, nothing to ask", !ALMasterPlan::askFirst(0, 0, 8)); - ensure("one in one object", !ALMasterPlan::askFirst(1, 1, 8)); - ensure("exactly the count is not over it", !ALMasterPlan::askFirst(8, 1, 8)); - ensure("one past it is", ALMasterPlan::askFirst(9, 1, 8)); - ensure("many in one object", ALMasterPlan::askFirst(40, 1, 8)); - ensure("two in two objects", ALMasterPlan::askFirst(2, 2, 8)); - ensure("a count of none asks for one", ALMasterPlan::askFirst(1, 1, 0)); - ensure("and not for none", !ALMasterPlan::askFirst(0, 1, 0)); - ensure("objects counted for nothing changing ask nothing", !ALMasterPlan::askFirst(0, 3, 8)); + UNSCOPED_INFO("none changing, nothing to ask"); + CHECK_FALSE(ALMasterPlan::askFirst(0, 0, 8)); + UNSCOPED_INFO("one in one object"); + CHECK_FALSE(ALMasterPlan::askFirst(1, 1, 8)); + UNSCOPED_INFO("exactly the count is not over it"); + CHECK_FALSE(ALMasterPlan::askFirst(8, 1, 8)); + UNSCOPED_INFO("one past it is"); + CHECK(ALMasterPlan::askFirst(9, 1, 8)); + UNSCOPED_INFO("many in one object"); + CHECK(ALMasterPlan::askFirst(40, 1, 8)); + UNSCOPED_INFO("two in two objects"); + CHECK(ALMasterPlan::askFirst(2, 2, 8)); + UNSCOPED_INFO("a count of none asks for one"); + CHECK(ALMasterPlan::askFirst(1, 1, 0)); + UNSCOPED_INFO("and not for none"); + CHECK_FALSE(ALMasterPlan::askFirst(0, 1, 0)); + UNSCOPED_INFO("objects counted for nothing changing ask nothing"); + CHECK_FALSE(ALMasterPlan::askFirst(0, 3, 8)); } - template<> template<> - void almasterplan_object::test<3>() + TEST_CASE_METHOD(almasterplan_data, "almasterplan: objectsOf counts each object once and the inventory as one", "[almasterplan]") { - set_test_name("the objects scripts are in, each once, the inventory one of them"); const LLUUID door("00000001-0000-4000-8000-000000000000"); const LLUUID lamp("00000002-0000-4000-8000-000000000000"); - ensure_equals("none", ALMasterPlan::objectsOf({}), size_t(0)); - ensure_equals("one script", ALMasterPlan::objectsOf({ door }), size_t(1)); - ensure_equals("many scripts in one object", ALMasterPlan::objectsOf({ door, door, door, door, door }), size_t(1)); - ensure_equals("the inventory once", ALMasterPlan::objectsOf({ LLUUID::null, LLUUID::null }), size_t(1)); - ensure_equals("and as one beside the others", ALMasterPlan::objectsOf({ door, LLUUID::null, lamp, door, LLUUID::null }), size_t(3)); + UNSCOPED_INFO("none"); + CHECK(ALMasterPlan::objectsOf({}) == size_t(0)); + UNSCOPED_INFO("one script"); + CHECK(ALMasterPlan::objectsOf({ door }) == size_t(1)); + UNSCOPED_INFO("many scripts in one object"); + CHECK(ALMasterPlan::objectsOf({ door, door, door, door, door }) == size_t(1)); + UNSCOPED_INFO("the inventory once"); + CHECK(ALMasterPlan::objectsOf({ LLUUID::null, LLUUID::null }) == size_t(1)); + UNSCOPED_INFO("and as one beside the others"); + CHECK(ALMasterPlan::objectsOf({ door, LLUUID::null, lamp, door, LLUUID::null }) == size_t(3)); // Twenty scripts in one object do not ask until past the count; // two in two objects do at once. std::vector one(20, door); - ensure("twenty in one object, over eight", ALMasterPlan::askFirst(one.size(), ALMasterPlan::objectsOf(one), 8)); - ensure("eight in one object, not", !ALMasterPlan::askFirst(8, ALMasterPlan::objectsOf(std::vector(8, door)), 8)); - ensure("two in the inventory and an object", ALMasterPlan::askFirst(2, ALMasterPlan::objectsOf({ LLUUID::null, lamp }), 8)); + UNSCOPED_INFO("twenty in one object, over eight"); + REQUIRE(ALMasterPlan::askFirst(one.size(), ALMasterPlan::objectsOf(one), 8)); + UNSCOPED_INFO("eight in one object, not"); + CHECK_FALSE(ALMasterPlan::askFirst(8, ALMasterPlan::objectsOf(std::vector(8, door)), 8)); + UNSCOPED_INFO("two in the inventory and an object"); + CHECK(ALMasterPlan::askFirst(2, ALMasterPlan::objectsOf({ LLUUID::null, lamp }), 8)); } } diff --git a/indra/alscript/tests/almasterqueue_test.cpp b/indra/alscript/tests/almasterqueue_test.cpp index 84a5c83f65..6b31007e33 100644 --- a/indra/alscript/tests/almasterqueue_test.cpp +++ b/indra/alscript/tests/almasterqueue_test.cpp @@ -26,13 +26,13 @@ #include "../masters/almasterqueue.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct almasterqueue_data { @@ -52,87 +52,132 @@ namespace tut } }; - typedef test_group almasterqueue_group; - typedef almasterqueue_group::object almasterqueue_object; - almasterqueue_group almasterqueue_instance("almasterqueue"); - - template<> template<> - void almasterqueue_object::test<1>() + TEST_CASE_METHOD(almasterqueue_data, "almasterqueue: four go at once and the rest wait their turn in the order asked", "[almasterqueue]") { - set_test_name("four go at once, the rest wait their turn, first come, first served"); using Send = almasterqueue_data::Send; ALMasterQueue queue; - ensure_equals("four by default", queue.limit(), size_t(4)); + UNSCOPED_INFO("four by default"); + CHECK(queue.limit() == size_t(4)); for (const char* key : { "a", "b", "c", "d" }) { - ensure(std::string("starts: ") + key, queue.ask(key, Send::Derived)); - ensure(std::string("is underway: ") + key, queue.underway(key) && !queue.waiting(key)); + UNSCOPED_INFO(std::string("starts: ") + key); + REQUIRE(queue.ask(key, Send::Derived)); + { + INFO(std::string("is underway: ") + key); + REQUIRE(queue.underway(key)); + REQUIRE_FALSE(queue.waiting(key)); + } } - ensure_equals("four busy", queue.busy(), size_t(4)); - ensure("a fifth waits", !queue.ask("e", Send::Derived)); - ensure("a sixth too", !queue.ask("f", Send::Direct)); - ensure("waiting, not underway", queue.waiting("e") && !queue.underway("e")); - ensure_equals("still four busy", queue.busy(), size_t(4)); - - ensure_equals("one ends, the first waiting starts", almasterqueue_data::started(queue.finished("c")), std::string("e*")); - ensure("the one ended is not underway", !queue.underway("c") && !queue.waiting("c")); - ensure("the one started is", queue.underway("e") && !queue.waiting("e")); - ensure_equals("then the next", almasterqueue_data::started(queue.finished("a")), std::string("f")); - ensure_equals("then nothing waits", almasterqueue_data::started(queue.finished("b")), std::string()); - ensure_equals("three busy", queue.busy(), size_t(3)); - ensure("one asked now starts at once", queue.ask("g", Send::Derived)); - ensure_equals("one ended that was never asked starts nothing", almasterqueue_data::started(queue.finished("zz")), std::string()); - ensure_equals("and frees nothing", queue.busy(), size_t(4)); + UNSCOPED_INFO("four busy"); + CHECK(queue.busy() == size_t(4)); + UNSCOPED_INFO("a fifth waits"); + CHECK_FALSE(queue.ask("e", Send::Derived)); + UNSCOPED_INFO("a sixth too"); + CHECK_FALSE(queue.ask("f", Send::Direct)); + { + INFO("waiting, not underway"); + CHECK(queue.waiting("e")); + CHECK_FALSE(queue.underway("e")); + } + UNSCOPED_INFO("still four busy"); + CHECK(queue.busy() == size_t(4)); + + UNSCOPED_INFO("one ends, the first waiting starts"); + CHECK(almasterqueue_data::started(queue.finished("c")) == std::string("e*")); + { + INFO("the one ended is not underway"); + CHECK_FALSE(queue.underway("c")); + CHECK_FALSE(queue.waiting("c")); + } + { + INFO("the one started is"); + CHECK(queue.underway("e")); + CHECK_FALSE(queue.waiting("e")); + } + UNSCOPED_INFO("then the next"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("f")); + UNSCOPED_INFO("then nothing waits"); + CHECK(almasterqueue_data::started(queue.finished("b")) == std::string()); + UNSCOPED_INFO("three busy"); + CHECK(queue.busy() == size_t(3)); + UNSCOPED_INFO("one asked now starts at once"); + CHECK(queue.ask("g", Send::Derived)); + UNSCOPED_INFO("one ended that was never asked starts nothing"); + CHECK(almasterqueue_data::started(queue.finished("zz")) == std::string()); + UNSCOPED_INFO("and frees nothing"); + CHECK(queue.busy() == size_t(4)); } - template<> template<> - void almasterqueue_object::test<2>() + TEST_CASE_METHOD(almasterqueue_data, "almasterqueue: a send asked while on its way goes again once after it", "[almasterqueue]") { - set_test_name("asked while on its way, a send goes again after it, once"); using Send = almasterqueue_data::Send; ALMasterQueue queue; - ensure("starts", queue.ask("a", Send::Direct)); - ensure("asked again, waits", !queue.ask("a", Send::Direct)); - ensure("and again", !queue.ask("a", Send::Direct)); - ensure("underway, and to go again", queue.underway("a") && queue.waiting("a")); - ensure_equals("one busy, not three", queue.busy(), size_t(1)); - ensure_equals("ended: goes again at once", almasterqueue_data::started(queue.finished("a")), std::string("a")); - ensure("underway again, not to go a third time", queue.underway("a") && !queue.waiting("a")); - ensure_equals("ended: nothing more", almasterqueue_data::started(queue.finished("a")), std::string()); - ensure("and gone", !queue.underway("a") && queue.busy() == 0); + UNSCOPED_INFO("starts"); + REQUIRE(queue.ask("a", Send::Direct)); + UNSCOPED_INFO("asked again, waits"); + CHECK_FALSE(queue.ask("a", Send::Direct)); + UNSCOPED_INFO("and again"); + CHECK_FALSE(queue.ask("a", Send::Direct)); + { + INFO("underway, and to go again"); + CHECK(queue.underway("a")); + CHECK(queue.waiting("a")); + } + UNSCOPED_INFO("one busy, not three"); + CHECK(queue.busy() == size_t(1)); + UNSCOPED_INFO("ended: goes again at once"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("a")); + { + INFO("underway again, not to go a third time"); + CHECK(queue.underway("a")); + CHECK_FALSE(queue.waiting("a")); + } + UNSCOPED_INFO("ended: nothing more"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string()); + { + INFO("and gone"); + CHECK_FALSE(queue.underway("a")); + CHECK(queue.busy() == 0); + } } - template<> template<> - void almasterqueue_object::test<3>() + TEST_CASE_METHOD(almasterqueue_data, "almasterqueue: a send asked while it waits stays in its place", "[almasterqueue]") { - set_test_name("asked while it waits, a send stays in its place"); using Send = almasterqueue_data::Send; ALMasterQueue queue(1); - ensure("one place", queue.ask("a", Send::Derived)); - ensure("b waits", !queue.ask("b", Send::Derived)); - ensure("c waits", !queue.ask("c", Send::Derived)); - ensure("b again, in its place", !queue.ask("b", Send::Derived)); - ensure_equals("b first", almasterqueue_data::started(queue.finished("a")), std::string("b*")); - ensure_equals("then c", almasterqueue_data::started(queue.finished("b")), std::string("c*")); - ensure_equals("and b went once", almasterqueue_data::started(queue.finished("c")), std::string()); + UNSCOPED_INFO("one place"); + CHECK(queue.ask("a", Send::Derived)); + UNSCOPED_INFO("b waits"); + CHECK_FALSE(queue.ask("b", Send::Derived)); + UNSCOPED_INFO("c waits"); + CHECK_FALSE(queue.ask("c", Send::Derived)); + UNSCOPED_INFO("b again, in its place"); + CHECK_FALSE(queue.ask("b", Send::Derived)); + UNSCOPED_INFO("b first"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("b*")); + UNSCOPED_INFO("then c"); + CHECK(almasterqueue_data::started(queue.finished("b")) == std::string("c*")); + UNSCOPED_INFO("and b went once"); + CHECK(almasterqueue_data::started(queue.finished("c")) == std::string()); } - template<> template<> - void almasterqueue_object::test<4>() + TEST_CASE_METHOD(almasterqueue_data, "almasterqueue: a save of the master outranks the studio's own send whichever came first", "[almasterqueue]") { - set_test_name("a save of the master outranks the studio's own send, whichever was asked first"); using Send = almasterqueue_data::Send; ALMasterQueue queue(1); queue.ask("a", Send::Derived); // On its way: to go again as a save, asked either way round. queue.ask("a", Send::Derived); queue.ask("a", Send::Direct); - ensure_equals("a save after the studio's", almasterqueue_data::started(queue.finished("a")), std::string("a")); + UNSCOPED_INFO("a save after the studio's"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("a")); queue.ask("a", Send::Direct); queue.ask("a", Send::Derived); - ensure_equals("the studio's after a save", almasterqueue_data::started(queue.finished("a")), std::string("a")); + UNSCOPED_INFO("the studio's after a save"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("a")); queue.ask("a", Send::Derived); - ensure_equals("the studio's alone stays its own", almasterqueue_data::started(queue.finished("a")), std::string("a*")); + UNSCOPED_INFO("the studio's alone stays its own"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("a*")); // Waiting: the same. queue.ask("b", Send::Derived); @@ -141,15 +186,16 @@ namespace tut queue.ask("c", Send::Derived); queue.ask("d", Send::Derived); queue.ask("d", Send::Derived); - ensure_equals("b as a save", almasterqueue_data::started(queue.finished("a")), std::string("b")); - ensure_equals("c as a save", almasterqueue_data::started(queue.finished("b")), std::string("c")); - ensure_equals("d as the studio's", almasterqueue_data::started(queue.finished("c")), std::string("d*")); + UNSCOPED_INFO("b as a save"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("b")); + UNSCOPED_INFO("c as a save"); + CHECK(almasterqueue_data::started(queue.finished("b")) == std::string("c")); + UNSCOPED_INFO("d as the studio's"); + CHECK(almasterqueue_data::started(queue.finished("c")) == std::string("d*")); } - template<> template<> - void almasterqueue_object::test<5>() + TEST_CASE_METHOD(almasterqueue_data, "almasterqueue: a send asked again on its way goes behind all that wait with its newest text", "[almasterqueue]") { - set_test_name("a send asked again while on its way goes behind all that wait, its newest text last"); using Send = almasterqueue_data::Send; ALMasterQueue queue; for (const char* key : { "a", "b", "c", "d" }) @@ -159,18 +205,25 @@ namespace tut queue.ask("e", Send::Derived); queue.ask("f", Send::Derived); queue.ask("a", Send::Direct); - ensure_equals("a ends, the first waiting goes, not a", almasterqueue_data::started(queue.finished("a")), std::string("e*")); - ensure("a waits", queue.waiting("a") && !queue.underway("a")); - ensure_equals("then f", almasterqueue_data::started(queue.finished("b")), std::string("f*")); - ensure_equals("then a, as it was asked again", almasterqueue_data::started(queue.finished("c")), std::string("a")); - ensure_equals("four busy throughout", queue.busy(), size_t(4)); - ensure_equals("then nothing", almasterqueue_data::started(queue.finished("d")), std::string()); + UNSCOPED_INFO("a ends, the first waiting goes, not a"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("e*")); + { + INFO("a waits"); + CHECK(queue.waiting("a")); + CHECK_FALSE(queue.underway("a")); + } + UNSCOPED_INFO("then f"); + CHECK(almasterqueue_data::started(queue.finished("b")) == std::string("f*")); + UNSCOPED_INFO("then a, as it was asked again"); + CHECK(almasterqueue_data::started(queue.finished("c")) == std::string("a")); + UNSCOPED_INFO("four busy throughout"); + CHECK(queue.busy() == size_t(4)); + UNSCOPED_INFO("then nothing"); + CHECK(almasterqueue_data::started(queue.finished("d")) == std::string()); } - template<> template<> - void almasterqueue_object::test<6>() + TEST_CASE_METHOD(almasterqueue_data, "almasterqueue: a dropped script waiting or due again is not sent; one on its way goes on", "[almasterqueue]") { - set_test_name("a script dropped is not sent after all, waiting or to go again; one on its way goes on"); using Send = almasterqueue_data::Send; ALMasterQueue queue(2); queue.ask("a", Send::Direct); @@ -181,17 +234,26 @@ namespace tut queue.drop("c"); queue.drop("a"); queue.drop("never"); - ensure("c waits no longer", !queue.waiting("c") && !queue.underway("c")); - ensure("a goes on, but not again", queue.underway("a") && !queue.waiting("a")); - ensure_equals("a ends: d, not c, and not a", almasterqueue_data::started(queue.finished("a")), std::string("d*")); - ensure("c asked again goes in at the back", !queue.ask("c", Send::Direct)); - ensure_equals("and goes in its turn", almasterqueue_data::started(queue.finished("b")), std::string("c")); + { + INFO("c waits no longer"); + CHECK_FALSE(queue.waiting("c")); + CHECK_FALSE(queue.underway("c")); + } + { + INFO("a goes on, but not again"); + CHECK(queue.underway("a")); + CHECK_FALSE(queue.waiting("a")); + } + UNSCOPED_INFO("a ends: d, not c, and not a"); + CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("d*")); + UNSCOPED_INFO("c asked again goes in at the back"); + CHECK_FALSE(queue.ask("c", Send::Direct)); + UNSCOPED_INFO("and goes in its turn"); + CHECK(almasterqueue_data::started(queue.finished("b")) == std::string("c")); } - template<> template<> - void almasterqueue_object::test<7>() + TEST_CASE_METHOD(almasterqueue_data, "almasterqueue: the limit holds and each is sent once in order; a limit is at least one", "[almasterqueue]") { - set_test_name("never more on their way than the limit, every one sent once, in order; at least one"); using Send = almasterqueue_data::Send; ALMasterQueue queue; std::string order; @@ -206,9 +268,11 @@ namespace tut underway.push_back(key); order += (order.empty() ? "" : " ") + key; } - ensure("never more than four", queue.busy() <= 4); + UNSCOPED_INFO("never more than four"); + REQUIRE(queue.busy() <= 4); } - ensure_equals("four started", underway.size(), size_t(4)); + UNSCOPED_INFO("four started"); + REQUIRE(underway.size() == size_t(4)); while (!underway.empty()) { const std::string done = underway.front(); @@ -218,18 +282,33 @@ namespace tut underway.push_back(key); order += " " + key; } - ensure("never more than four", queue.busy() <= 4 && queue.busy() == underway.size()); + { + INFO("never more than four"); + REQUIRE(queue.busy() <= 4); + REQUIRE(queue.busy() == underway.size()); + } } - ensure_equals("all, once, in the order asked", order, expected); + UNSCOPED_INFO("all, once, in the order asked"); + CHECK(order == expected); ALMasterQueue none(0); - ensure_equals("a limit of none is one", none.limit(), size_t(1)); - ensure("which starts", none.ask("a", Send::Derived)); - ensure("one only", !none.ask("b", Send::Derived)); + UNSCOPED_INFO("a limit of none is one"); + CHECK(none.limit() == size_t(1)); + UNSCOPED_INFO("which starts"); + CHECK(none.ask("a", Send::Derived)); + UNSCOPED_INFO("one only"); + CHECK_FALSE(none.ask("b", Send::Derived)); none.clear(); - ensure("cleared", !none.underway("a") && !none.waiting("b") && none.busy() == 0); - ensure("and starts afresh", none.ask("b", Send::Derived)); - ensure_equals("with nothing behind it", almasterqueue_data::started(none.finished("b")), std::string()); + { + INFO("cleared"); + CHECK_FALSE(none.underway("a")); + CHECK_FALSE(none.waiting("b")); + CHECK(none.busy() == 0); + } + UNSCOPED_INFO("and starts afresh"); + CHECK(none.ask("b", Send::Derived)); + UNSCOPED_INFO("with nothing behind it"); + CHECK(almasterqueue_data::started(none.finished("b")) == std::string()); } } diff --git a/indra/alscript/tests/almasterwatch_test.cpp b/indra/alscript/tests/almasterwatch_test.cpp index 5533b46c28..2ef10a4b73 100644 --- a/indra/alscript/tests/almasterwatch_test.cpp +++ b/indra/alscript/tests/almasterwatch_test.cpp @@ -30,7 +30,7 @@ #include "llfile.h" #include "lluuid.h" -#include "../test/lltut.h" +#include #include #include @@ -40,7 +40,7 @@ #include #include -namespace tut +namespace { namespace fs = std::filesystem; @@ -206,42 +206,42 @@ namespace tut } }; - typedef test_group almasterwatch_group; - typedef almasterwatch_group::object almasterwatch_object; - almasterwatch_group almasterwatch_instance("almasterwatch"); - - template<> template<> - void almasterwatch_object::test<1>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: a master saved outside is released once the burst has been quiet a second", "[almasterwatch]") { - set_test_name("a master saved outside is released once the burst has been quiet a second"); const std::string door = in("door.lsl"); put(door, "default {}\n", 100); ALMasterWatch& watched = make(); watched.watch({ almasterwatch_data::master(door) }); - ensure("watched at once, its look answered here", watched.watching(door)); + UNSCOPED_INFO("watched at once, its look answered here"); + CHECK(watched.watching(door)); looked(); pass(5.0); - ensure("what was there already is no change", released.empty()); + UNSCOPED_INFO("what was there already is no change"); + REQUIRE(released.empty()); put(door, "default { state_entry() {} }\n", 200); watched.lookNow(); - ensure("moving: not heard yet", waiting.empty()); + UNSCOPED_INFO("moving: not heard yet"); + REQUIRE(waiting.empty()); watched.lookNow(); pass(0.9); - ensure("not before the quiet", released.empty()); + UNSCOPED_INFO("not before the quiet"); + REQUIRE(released.empty()); pass(0.2); - ensure_equals("released once", released.size(), size_t(1)); - ensure_equals("the master", almasterwatch_data::joined(released[0].masters), std::string("door.lsl")); - ensure("no include", released[0].includes.empty()); + UNSCOPED_INFO("released once"); + REQUIRE(released.size() == size_t(1)); + UNSCOPED_INFO("the master"); + CHECK(almasterwatch_data::joined(released[0].masters) == std::string("door.lsl")); + UNSCOPED_INFO("no include"); + REQUIRE(released[0].includes.empty()); looked(); pass(5.0); - ensure_equals("and not again", released.size(), size_t(1)); + UNSCOPED_INFO("and not again"); + REQUIRE(released.size() == size_t(1)); } - template<> template<> - void almasterwatch_object::test<2>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: a checkout of many files is released once and together after the last", "[almasterwatch]") { - set_test_name("a checkout of many files is released once, all together, after the last of them"); std::vector files; for (int i = 0; i < 10; ++i) { @@ -263,16 +263,17 @@ namespace tut } looked(); pass(0.9); - ensure("the burst goes on: nothing yet", released.empty()); + UNSCOPED_INFO("the burst goes on: nothing yet"); + REQUIRE(released.empty()); pass(0.2); - ensure_equals("released once", released.size(), size_t(1)); - ensure_equals("all ten together", released[0].masters.size(), size_t(10)); + UNSCOPED_INFO("released once"); + REQUIRE(released.size() == size_t(1)); + UNSCOPED_INFO("all ten together"); + REQUIRE(released[0].masters.size() == size_t(10)); } - template<> template<> - void almasterwatch_object::test<3>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: masters and includes are told apart as they are released", "[almasterwatch]") { - set_test_name("masters and includes are told apart as they are released"); const std::string door = in("door.lsl"); const std::string util = in("util.lsl"); put(door, "a", 100); @@ -283,15 +284,16 @@ namespace tut put(util, "b2", 200); looked(); pass(1.1); - ensure_equals("released once", released.size(), size_t(1)); - ensure_equals("the master", almasterwatch_data::joined(released[0].masters), std::string("door.lsl")); - ensure_equals("the include", almasterwatch_data::joined(released[0].includes), std::string("util.lsl")); + UNSCOPED_INFO("released once"); + REQUIRE(released.size() == size_t(1)); + UNSCOPED_INFO("the master"); + CHECK(almasterwatch_data::joined(released[0].masters) == std::string("door.lsl")); + UNSCOPED_INFO("the include"); + CHECK(almasterwatch_data::joined(released[0].includes) == std::string("util.lsl")); } - template<> template<> - void almasterwatch_object::test<4>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: an emptied file is held longer than the quiet unless filled again meanwhile", "[almasterwatch]") { - set_test_name("an emptied file is held a moment and a half, and one filled again meanwhile no longer than the quiet"); const std::string door = in("door.lsl"); put(door, "default {}\n", 100); ALMasterWatch& watched = make(); @@ -300,31 +302,35 @@ namespace tut put(door, "", 200); looked(); pass(1.1); - ensure("quiet, but emptied: held", released.empty()); + UNSCOPED_INFO("quiet, but emptied: held"); + REQUIRE(released.empty()); pass(0.5); - ensure_equals("taken as empty after the hold", released.size(), size_t(1)); + UNSCOPED_INFO("taken as empty after the hold"); + REQUIRE(released.size() == size_t(1)); // An editor saving in two steps: emptied, then filled. put(door, "default { touch_start(integer n) {} }\n", 300); looked(); pass(1.1); - ensure_equals("filled again: released", released.size(), size_t(2)); + UNSCOPED_INFO("filled again: released"); + REQUIRE(released.size() == size_t(2)); put(door, "", 400); looked(); pass(0.3); put(door, "default { state_entry() {} }\n", 500); looked(); pass(0.9); - ensure("a second after it was filled: not yet", released.size() == 2); + UNSCOPED_INFO("a second after it was filled: not yet"); + REQUIRE(released.size() == 2); pass(0.2); - ensure_equals("then, before the emptied hold would have ended", released.size(), size_t(3)); - ensure_equals("the file, once", released[2].masters.size(), size_t(1)); + UNSCOPED_INFO("then, before the emptied hold would have ended"); + REQUIRE(released.size() == size_t(3)); + UNSCOPED_INFO("the file, once"); + REQUIRE(released[2].masters.size() == size_t(1)); } - template<> template<> - void almasterwatch_object::test<5>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: what the studio writes is no change and takes a waiting change with it", "[almasterwatch]") { - set_test_name("what the studio writes itself is no change, and takes with it a change waiting in the burst"); const std::string door = in("door.lsl"); put(door, "a", 100); ALMasterWatch& watched = make(); @@ -334,7 +340,8 @@ namespace tut watched.seen(door); looked(); pass(5.0); - ensure("the studio's own write: nothing", released.empty()); + UNSCOPED_INFO("the studio's own write: nothing"); + REQUIRE(released.empty()); // Saved outside, and heard; then written by the studio before the // burst ends, which sends it itself. @@ -343,22 +350,27 @@ namespace tut put(door, "studio again", 400); watched.seen(door); pass(5.0); - ensure("nothing waiting on it from before", released.empty()); + UNSCOPED_INFO("nothing waiting on it from before"); + REQUIRE(released.empty()); looked(); pass(5.0); - ensure("and nothing after", released.empty()); + UNSCOPED_INFO("and nothing after"); + REQUIRE(released.empty()); } - template<> template<> - void almasterwatch_object::test<6>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: a save after a studio write during the first look is heard", "[almasterwatch]") { - set_test_name("a file the studio writes while its first look is out is watched from that write, and a save after it is heard"); const std::string door = in("door.lsl"); put(door, "first", 100); ALMasterWatch& watched = make(/*look_at_once*/ false); watched.watch({ almasterwatch_data::master(door) }); - ensure("waiting on its first look", watched.waiting(door) && !watched.watching(door)); - ensure_equals("asked once", asked.size(), size_t(1)); + { + INFO("waiting on its first look"); + CHECK(watched.waiting(door)); + CHECK_FALSE(watched.watching(door)); + } + UNSCOPED_INFO("asked once"); + REQUIRE(asked.size() == size_t(1)); // The studio writes it, then somebody else saves it, all before the // look answers: the save is heard, the studio's write is not. @@ -366,16 +378,16 @@ namespace tut watched.seen(door); put(door, "outside", 300); answer(0); - ensure("watched now", watched.watching(door)); + UNSCOPED_INFO("watched now"); + CHECK(watched.watching(door)); looked(); pass(1.1); - ensure_equals("the save after the studio's write heard", released.size(), size_t(1)); + UNSCOPED_INFO("the save after the studio's write heard"); + REQUIRE(released.size() == size_t(1)); } - template<> template<> - void almasterwatch_object::test<7>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: a studio write during the first look is no change however old the look", "[almasterwatch]") { - set_test_name("a file the studio writes while its first look is out is no change, whenever the look was made"); const std::string door = in("door.lsl"); const std::string lamp = in("lamp.lsl"); put(door, "first", 100); @@ -387,35 +399,40 @@ namespace tut put(door, "studio", 200); watched.seen(door); answer(0, before); - ensure("both watched", watched.watching(door) && watched.watching(lamp)); + { + INFO("both watched"); + CHECK(watched.watching(door)); + CHECK(watched.watching(lamp)); + } looked(); pass(5.0); - ensure("the studio's write is no change", released.empty()); + UNSCOPED_INFO("the studio's write is no change"); + REQUIRE(released.empty()); } - template<> template<> - void almasterwatch_object::test<8>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: a first look answering for a file no longer wanted makes no watch of it", "[almasterwatch]") { - set_test_name("a first look answering for a file no longer wanted makes no watch of it"); const std::string door = in("door.lsl"); put(door, "first", 100); ALMasterWatch& watched = make(/*look_at_once*/ false); watched.watch({ almasterwatch_data::master(door) }); watched.watch({}); - ensure("not waiting now", !watched.waiting(door)); + UNSCOPED_INFO("not waiting now"); + CHECK_FALSE(watched.waiting(door)); answer(0); - ensure("not watched", !watched.watching(door)); - ensure_equals("nothing at all", watched.size(), size_t(0)); + UNSCOPED_INFO("not watched"); + CHECK_FALSE(watched.watching(door)); + UNSCOPED_INFO("nothing at all"); + REQUIRE(watched.size() == size_t(0)); put(door, "changed", 200); looked(); pass(5.0); - ensure("nothing heard", released.empty()); + UNSCOPED_INFO("nothing heard"); + REQUIRE(released.empty()); } - template<> template<> - void almasterwatch_object::test<9>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: a file wanted again waits on the newer look and one wanted still on its own", "[almasterwatch]") { - set_test_name("a file let go of and wanted again waits on the newer look; one wanted still keeps the look it waits on"); const std::string door = in("door.lsl"); const std::string lamp = in("lamp.lsl"); put(door, "first", 100); @@ -424,30 +441,42 @@ namespace tut watched.watch({ almasterwatch_data::master(door) }); watched.watch({}); watched.watch({ almasterwatch_data::master(door) }); - ensure_equals("asked again", asked.size(), size_t(2)); + UNSCOPED_INFO("asked again"); + REQUIRE(asked.size() == size_t(2)); answer(0); - ensure("the older look makes no watch", !watched.watching(door) && watched.waiting(door)); + { + INFO("the older look makes no watch"); + CHECK_FALSE(watched.watching(door)); + CHECK(watched.waiting(door)); + } answer(1); - ensure("the newer one does", watched.watching(door)); + UNSCOPED_INFO("the newer one does"); + CHECK(watched.watching(door)); // Wanted still while another is added: the look it waits on answers // it, and only the new file is asked after. watched.watch({ almasterwatch_data::master(door), almasterwatch_data::master(lamp) }); - ensure("door watched already: lamp alone asked", asked.size() == 3 && asked[2].paths.size() == 1); + { + INFO("door watched already: lamp alone asked"); + REQUIRE(asked.size() == 3); + REQUIRE(asked[2].paths.size() == 1); + } const std::string other = in("other.lsl"); watched.watch({ almasterwatch_data::master(door), almasterwatch_data::master(lamp), almasterwatch_data::master(other) }); - ensure_equals("lamp waits on its look still: other alone asked", asked[3].paths.size(), size_t(1)); + UNSCOPED_INFO("lamp waits on its look still: other alone asked"); + REQUIRE(asked[3].paths.size() == size_t(1)); answer(3); - ensure("other watched", watched.watching(other)); - ensure("lamp waits on its own", watched.waiting(lamp)); + UNSCOPED_INFO("other watched"); + CHECK(watched.watching(other)); + UNSCOPED_INFO("lamp waits on its own"); + CHECK(watched.waiting(lamp)); answer(2); - ensure("lamp watched", watched.watching(lamp)); + UNSCOPED_INFO("lamp watched"); + CHECK(watched.watching(lamp)); } - template<> template<> - void almasterwatch_object::test<10>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: masters are looked at twice a second and includes once; past 256 every 2 s", "[almasterwatch]") { - set_test_name("a master is looked at twice a second, an include once, and everything every two seconds past 256 files"); const std::string door = in("door.lsl"); const std::string util = in("util.lsl"); size_t looks = 0; @@ -459,9 +488,12 @@ namespace tut return true; }); watch->watch({ almasterwatch_data::master(door), almasterwatch_data::include(util) }); - ensure_equals("a master", *watch->periodOf(door), ALMasterWatch::MASTER_PERIOD); - ensure_equals("an include", *watch->periodOf(util), ALMasterWatch::INCLUDE_PERIOD); - ensure_equals("both looked at first", looks, size_t(2)); + UNSCOPED_INFO("a master"); + CHECK(*watch->periodOf(door) == ALMasterWatch::MASTER_PERIOD); + UNSCOPED_INFO("an include"); + CHECK(*watch->periodOf(util) == ALMasterWatch::INCLUDE_PERIOD); + UNSCOPED_INFO("both looked at first"); + CHECK(looks == size_t(2)); std::vector many = { almasterwatch_data::master(door), almasterwatch_data::include(util) }; for (size_t i = 0; i < ALMasterWatch::MANY - 1; ++i) @@ -469,22 +501,28 @@ namespace tut many.push_back(almasterwatch_data::master(in("m" + std::to_string(i) + ".lsl"))); } watch->watch(many); - ensure_equals("257 watched", watch->size(), size_t(257)); - ensure_equals("past 256: a master every two seconds", *watch->periodOf(door), ALMasterWatch::MANY_PERIOD); - ensure_equals("and an include", *watch->periodOf(util), ALMasterWatch::MANY_PERIOD); - ensure_equals("only the new ones looked at", looks, size_t(257)); + UNSCOPED_INFO("257 watched"); + REQUIRE(watch->size() == size_t(257)); + UNSCOPED_INFO("past 256: a master every two seconds"); + CHECK(*watch->periodOf(door) == ALMasterWatch::MANY_PERIOD); + UNSCOPED_INFO("and an include"); + CHECK(*watch->periodOf(util) == 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) }); - ensure_equals("fewer again: a master's own", *watch->periodOf(door), ALMasterWatch::MASTER_PERIOD); - ensure_equals("an include's own", *watch->periodOf(util), ALMasterWatch::INCLUDE_PERIOD); - ensure_equals("nothing looked at again", looks, size_t(257)); - ensure("one not watched has no period", !watch->periodOf(in("m0.lsl"))); + UNSCOPED_INFO("fewer again: a master's own"); + CHECK(*watch->periodOf(door) == ALMasterWatch::MASTER_PERIOD); + UNSCOPED_INFO("an include's own"); + CHECK(*watch->periodOf(util) == ALMasterWatch::INCLUDE_PERIOD); + UNSCOPED_INFO("nothing looked at again"); + CHECK(looks == size_t(257)); + UNSCOPED_INFO("one not watched has no period"); + REQUIRE_FALSE(watch->periodOf(in("m0.lsl"))); } - template<> template<> - void almasterwatch_object::test<11>() + TEST_CASE_METHOD(almasterwatch_data, "almasterwatch: with no main loop to answer through the first looks are made at once", "[almasterwatch]") { - set_test_name("with no main loop to answer through, the first looks are made at once, here"); const std::string door = in("door.lsl"); put(door, "first", 100); watch = std::make_unique( @@ -492,10 +530,12 @@ namespace tut clock()); watch->setQuiet(0.5); watch->watch({ almasterwatch_data::master(door) }); - ensure("watched at once", watch->watching(door)); + UNSCOPED_INFO("watched at once"); + CHECK(watch->watching(door)); put(door, "second", 200); looked(); pass(0.6); - ensure_equals("released after the quiet set", released.size(), size_t(1)); + UNSCOPED_INFO("released after the quiet set"); + REQUIRE(released.size() == size_t(1)); } } diff --git a/indra/alscript/tests/almessagemap_test.cpp b/indra/alscript/tests/almessagemap_test.cpp index 3b09089a5a..f07eec58a7 100644 --- a/indra/alscript/tests/almessagemap_test.cpp +++ b/indra/alscript/tests/almessagemap_test.cpp @@ -27,9 +27,9 @@ #include "../core/almessagemap.h" #include "../core/alscriptproblem.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct almessagemap_data { @@ -43,169 +43,269 @@ namespace tut return out; } }; - typedef test_group almessagemap_group; - typedef almessagemap_group::object almessagemap_object; - tut::almessagemap_group almessagemap_instance("almessagemap"); - template<> template<> - void almessagemap_object::test<1>() + TEST_CASE_METHOD(almessagemap_data, "almessagemap: match gives the words between the literals in the marks' order", "[almessagemap]") { - set_test_name("the words between the literals, in the marks' order"); std::vector args; - ensure("matches", ALMessageMap::match("Passing [1] as argument [2] of `[3]' which is declared as `[4] [5]'.", + UNSCOPED_INFO("matches"); + REQUIRE(ALMessageMap::match("Passing [1] as argument [2] of `[3]' which is declared as `[4] [5]'.", "Passing string as argument 2 of `llSay' which is declared as `integer channel'.", args)); - ensure_equals("the words", joined(args), std::string("<2>")); - ensure("the first literal must begin it", !ALMessageMap::match("Too many arguments to function `[1]'.", "Warning: Too many arguments to function `f'.", args)); - ensure("the last must end it", !ALMessageMap::match("Too many arguments to function `[1]'.", "Too many arguments to function `f'. Really.", args)); - ensure("a template without marks is the message itself", ALMessageMap::match("Functions cannot change state.", "Functions cannot change state.", args)); - ensure("and no other", !ALMessageMap::match("Functions cannot change state.", "Functions cannot change state", args)); - ensure_equals("no words then", args.size(), size_t(0)); + UNSCOPED_INFO("the words"); + CHECK(joined(args) == std::string("<2>")); + UNSCOPED_INFO("the first literal must begin it"); + CHECK_FALSE(ALMessageMap::match("Too many arguments to function `[1]'.", "Warning: Too many arguments to function `f'.", args)); + UNSCOPED_INFO("the last must end it"); + CHECK_FALSE(ALMessageMap::match("Too many arguments to function `[1]'.", "Too many arguments to function `f'. Really.", args)); + UNSCOPED_INFO("a template without marks is the message itself"); + CHECK(ALMessageMap::match("Functions cannot change state.", "Functions cannot change state.", args)); + UNSCOPED_INFO("and no other"); + CHECK_FALSE(ALMessageMap::match("Functions cannot change state.", "Functions cannot change state", args)); + UNSCOPED_INFO("no words then"); + REQUIRE(args.size() == size_t(0)); } - template<> template<> - void almessagemap_object::test<2>() + TEST_CASE_METHOD(almessagemap_data, "almessagemap: a mark used twice stands for one word", "[almessagemap]") { - set_test_name("a mark used twice stands for one word"); std::vector args; - ensure("the same word twice", ALMessageMap::match("Trying to access `[1].[2]', but `[1]' is a [3]", "Trying to access `v.x', but `v' is a integer", args)); - ensure_equals("named once", joined(args), std::string("")); - ensure("a different word is no match", !ALMessageMap::match("Trying to access `[1].[2]', but `[1]' is a [3]", "Trying to access `v.x', but `w' is a integer", args)); + UNSCOPED_INFO("the same word twice"); + REQUIRE(ALMessageMap::match("Trying to access `[1].[2]', but `[1]' is a [3]", "Trying to access `v.x', but `v' is a integer", args)); + UNSCOPED_INFO("named once"); + CHECK(joined(args) == std::string("")); + UNSCOPED_INFO("a different word is no match"); + CHECK_FALSE(ALMessageMap::match("Trying to access `[1].[2]', but `[1]' is a [3]", "Trying to access `v.x', but `w' is a integer", args)); } - template<> template<> - void almessagemap_object::test<3>() + TEST_CASE_METHOD(almessagemap_data, "almessagemap: a literal is found at its first occurrence so a word holding it misleads", "[almessagemap]") { - set_test_name("a literal is found at its first occurrence after the last, which a word containing it can mislead"); std::vector args; // The engines' templates keep their words in quotes, so the // literal after a mark begins with the closing quote and a name // cannot contain it; this pins what a bare literal does. - ensure("a name with the literal in it", ALMessageMap::match("[1] is [2] now", "x is y is z now", args)); - ensure_equals("cut at the first: the rest is the second word", joined(args), std::string("")); - ensure("quoted, a name is whole", ALMessageMap::match("Variable '[1]' is never used; prefix with '_' to silence", "Variable 'is' is never used; prefix with '_' to silence", args)); - ensure_equals("the name", joined(args), std::string("")); + UNSCOPED_INFO("a name with the literal in it"); + REQUIRE(ALMessageMap::match("[1] is [2] now", "x is y is z now", args)); + UNSCOPED_INFO("cut at the first: the rest is the second word"); + CHECK(joined(args) == std::string("")); + UNSCOPED_INFO("quoted, a name is whole"); + CHECK(ALMessageMap::match("Variable '[1]' is never used; prefix with '_' to silence", "Variable 'is' is never used; prefix with '_' to silence", args)); + UNSCOPED_INFO("the name"); + CHECK(joined(args) == std::string("")); // The last literal is anchored at the end, so a word that holds // it is not cut there. - ensure("the end holds", ALMessageMap::match("Unknown require: [1]", "Unknown require: a: b", args)); - ensure_equals("the whole rest", joined(args), std::string("")); + UNSCOPED_INFO("the end holds"); + CHECK(ALMessageMap::match("Unknown require: [1]", "Unknown require: a: b", args)); + UNSCOPED_INFO("the whole rest"); + CHECK(joined(args) == std::string("")); } - template<> template<> - void almessagemap_object::test<4>() + TEST_CASE_METHOD(almessagemap_data, "almessagemap: Tailslide's messages are keyed by code and Luau's lints by name", "[almessagemap]") { - set_test_name("Tailslide's messages by code, Luau's lints by name"); ALMessageMap::Match m; - ensure("10007", ALMessageMap::lsl(10007, "`llSay' is undeclared; did you mean llSay?", m)); - ensure_equals("its key", m.key, std::string("LSLUndeclaredWithSuggestion")); - ensure_equals("its words", joined(m.args), std::string("")); - ensure("a code with another message is not taken", !ALMessageMap::lsl(10007, "`llSay' is undeclared.", m)); - ensure("a code the table lacks", !ALMessageMap::lsl(10020, "syntax error, unexpected ';'", m)); - ensure("a lint by name", ALMessageMap::luauLint("LocalUnused", "Variable 'x' is never used; prefix with '_' to silence", m)); - ensure_equals("its key", m.key, std::string("LuauLintLocalUnused")); - ensure("the name is part of the match", !ALMessageMap::luauLint("FunctionUnused", "Variable 'x' is never used; prefix with '_' to silence", m)); - ensure("the comparison lints have a mark for each operator", ALMessageMap::luauLint("ComparisonPrecedence", "X == Y == Z is equivalent to (X == Y) == Z; add parentheses to silence", m)); - ensure_equals("four of them", m.args.size(), size_t(4)); + UNSCOPED_INFO("10007"); + REQUIRE(ALMessageMap::lsl(10007, "`llSay' is undeclared; did you mean llSay?", m)); + UNSCOPED_INFO("its key"); + CHECK(m.key == std::string("LSLUndeclaredWithSuggestion")); + UNSCOPED_INFO("its words"); + CHECK(joined(m.args) == std::string("")); + UNSCOPED_INFO("a code with another message is not taken"); + CHECK_FALSE(ALMessageMap::lsl(10007, "`llSay' is undeclared.", m)); + UNSCOPED_INFO("a code the table lacks"); + CHECK_FALSE(ALMessageMap::lsl(10020, "syntax error, unexpected ';'", m)); + UNSCOPED_INFO("a lint by name"); + CHECK(ALMessageMap::luauLint("LocalUnused", "Variable 'x' is never used; prefix with '_' to silence", m)); + UNSCOPED_INFO("its key"); + CHECK(m.key == std::string("LuauLintLocalUnused")); + UNSCOPED_INFO("the name is part of the match"); + CHECK_FALSE(ALMessageMap::luauLint("FunctionUnused", "Variable 'x' is never used; prefix with '_' to silence", m)); + UNSCOPED_INFO("the comparison lints have a mark for each operator"); + CHECK(ALMessageMap::luauLint("ComparisonPrecedence", "X == Y == Z is equivalent to (X == Y) == Z; add parentheses to silence", m)); + UNSCOPED_INFO("four of them"); + REQUIRE(m.args.size() == size_t(4)); } - template<> template<> - void almessagemap_object::test<5>() + TEST_CASE_METHOD(almessagemap_data, "almessagemap: a lint whose template has clauses matches its shapes fuller first", "[almessagemap]") { - set_test_name("a lint whose template stands for clauses is matched by the shapes it takes, the fuller first"); ALMessageMap::Match m; - ensure("plain", ALMessageMap::luauLint("DeprecatedApi", "Member 'table.getn' is deprecated", m)); - ensure_equals("plain key", m.key, std::string("LuauLintDeprecatedMember")); - ensure_equals("the qualified name whole", joined(m.args), std::string("")); - ensure("with a replacement", ALMessageMap::luauLint("DeprecatedApi", "Member 'table.getn' is deprecated, use '#' instead", m)); - ensure_equals("its key", m.key, std::string("LuauLintDeprecatedMemberUse")); - ensure_equals("its words", joined(m.args), std::string("<#>")); - ensure("with a replacement and a reason", ALMessageMap::luauLint("DeprecatedApi", "Member 'a.b' is deprecated, use 'c' instead. It was slow", m)); - ensure_equals("the fuller key", m.key, std::string("LuauLintDeprecatedMemberUseReason")); - ensure_equals("three words", joined(m.args), std::string("")); - ensure("a function", ALMessageMap::luauLint("DeprecatedApi", "Function 'f' is deprecated. Gone", m)); - ensure_equals("its key", m.key, std::string("LuauLintDeprecatedFunctionReason")); + UNSCOPED_INFO("plain"); + REQUIRE(ALMessageMap::luauLint("DeprecatedApi", "Member 'table.getn' is deprecated", m)); + UNSCOPED_INFO("plain key"); + CHECK(m.key == std::string("LuauLintDeprecatedMember")); + UNSCOPED_INFO("the qualified name whole"); + CHECK(joined(m.args) == std::string("")); + UNSCOPED_INFO("with a replacement"); + CHECK(ALMessageMap::luauLint("DeprecatedApi", "Member 'table.getn' is deprecated, use '#' instead", m)); + UNSCOPED_INFO("its key"); + CHECK(m.key == std::string("LuauLintDeprecatedMemberUse")); + UNSCOPED_INFO("its words"); + CHECK(joined(m.args) == std::string("<#>")); + UNSCOPED_INFO("with a replacement and a reason"); + CHECK(ALMessageMap::luauLint("DeprecatedApi", "Member 'a.b' is deprecated, use 'c' instead. It was slow", m)); + UNSCOPED_INFO("the fuller key"); + CHECK(m.key == std::string("LuauLintDeprecatedMemberUseReason")); + UNSCOPED_INFO("three words"); + CHECK(joined(m.args) == std::string("")); + UNSCOPED_INFO("a function"); + CHECK(ALMessageMap::luauLint("DeprecatedApi", "Function 'f' is deprecated. Gone", m)); + UNSCOPED_INFO("its key"); + CHECK(m.key == std::string("LuauLintDeprecatedFunctionReason")); } - template<> template<> - void almessagemap_object::test<6>() + TEST_CASE_METHOD(almessagemap_data, "almessagemap: Luau's type errors by shape: the fuller shape before the one it begins with", "[almessagemap]") { - set_test_name("Luau's type errors by shape: the fuller shape before the one it begins with"); ALMessageMap::Match m; - ensure("a mismatch", ALMessageMap::luauError("Expected this to be 'number', but got 'string'", m)); - ensure_equals("its key", m.key, std::string("LuauTypeMismatch")); - ensure_equals("wanted then given, quotes and all", joined(m.args), std::string("<'number'><'string'>")); - ensure("with a reason", ALMessageMap::luauError("Expected this to be 'number', but got 'string'; a table is not a number", m)); - ensure_equals("the reason's key", m.key, std::string("LuauTypeMismatchReason")); - ensure_equals("three words", joined(m.args), std::string("<'number'><'string'>")); - ensure("exactly", ALMessageMap::luauError("Expected this to be exactly 'number', but got 'string'", m)); - ensure_equals("not the plain one with 'exactly' in the word", m.key, std::string("LuauTypeMismatchExactly")); - ensure("unreachable", ALMessageMap::luauError("Expected this to be unreachable, but got 'string'", m)); - ensure_equals("its key", m.key, std::string("LuauTypeMismatchUnreachable")); - ensure("a count", ALMessageMap::luauError("Argument count mismatch. Function 'll.Say' expects 2 arguments, but only 1 is specified", m)); - ensure_equals("only one", m.key, std::string("LuauArgumentCountOnlyOne")); - ensure_equals("the name and the count", joined(m.args), std::string("<2>")); - ensure("at least", ALMessageMap::luauError("Argument count mismatch. Function 'f' expects at least 2 arguments, but only 1 is specified", m)); - ensure_equals("not the plain one with 'at least' in the count", m.key, std::string("LuauArgumentCountAtLeastOnlyOne")); - ensure("a range", ALMessageMap::luauError("Argument count mismatch. Function 'f' expects 1 to 2 arguments, but none are specified", m)); - ensure_equals("range, none", m.key, std::string("LuauArgumentCountRangeNone")); - ensure("one wanted", ALMessageMap::luauError("Argument count mismatch. Function 'f' expects 1 argument, but 3 are specified", m)); - ensure_equals("its key", m.key, std::string("LuauArgumentCountOne")); - ensure("a key not found", ALMessageMap::luauError("Key 'b' not found in table '{ a: number }'. Did you mean 'a'?", m)); - ensure_equals("did you mean", m.key, std::string("LuauMissingPropertyDidYouMean")); - ensure_equals("its words", joined(m.args), std::string("<{ a: number }>")); - ensure("nothing the table knows", !ALMessageMap::luauError("Some words nobody wrote", m)); + UNSCOPED_INFO("a mismatch"); + REQUIRE(ALMessageMap::luauError("Expected this to be 'number', but got 'string'", m)); + UNSCOPED_INFO("its key"); + CHECK(m.key == std::string("LuauTypeMismatch")); + UNSCOPED_INFO("wanted then given, quotes and all"); + CHECK(joined(m.args) == std::string("<'number'><'string'>")); + UNSCOPED_INFO("with a reason"); + CHECK(ALMessageMap::luauError("Expected this to be 'number', but got 'string'; a table is not a number", m)); + UNSCOPED_INFO("the reason's key"); + CHECK(m.key == std::string("LuauTypeMismatchReason")); + UNSCOPED_INFO("three words"); + CHECK(joined(m.args) == std::string("<'number'><'string'>")); + UNSCOPED_INFO("exactly"); + CHECK(ALMessageMap::luauError("Expected this to be exactly 'number', but got 'string'", m)); + UNSCOPED_INFO("not the plain one with 'exactly' in the word"); + CHECK(m.key == std::string("LuauTypeMismatchExactly")); + UNSCOPED_INFO("unreachable"); + CHECK(ALMessageMap::luauError("Expected this to be unreachable, but got 'string'", m)); + UNSCOPED_INFO("its key"); + CHECK(m.key == std::string("LuauTypeMismatchUnreachable")); + UNSCOPED_INFO("a count"); + CHECK(ALMessageMap::luauError("Argument count mismatch. Function 'll.Say' expects 2 arguments, but only 1 is specified", m)); + UNSCOPED_INFO("only one"); + CHECK(m.key == std::string("LuauArgumentCountOnlyOne")); + UNSCOPED_INFO("the name and the count"); + CHECK(joined(m.args) == std::string("<2>")); + UNSCOPED_INFO("at least"); + CHECK(ALMessageMap::luauError("Argument count mismatch. Function 'f' expects at least 2 arguments, but only 1 is specified", m)); + UNSCOPED_INFO("not the plain one with 'at least' in the count"); + CHECK(m.key == std::string("LuauArgumentCountAtLeastOnlyOne")); + UNSCOPED_INFO("a range"); + CHECK(ALMessageMap::luauError("Argument count mismatch. Function 'f' expects 1 to 2 arguments, but none are specified", m)); + UNSCOPED_INFO("range, none"); + CHECK(m.key == std::string("LuauArgumentCountRangeNone")); + UNSCOPED_INFO("one wanted"); + CHECK(ALMessageMap::luauError("Argument count mismatch. Function 'f' expects 1 argument, but 3 are specified", m)); + UNSCOPED_INFO("its key"); + CHECK(m.key == std::string("LuauArgumentCountOne")); + UNSCOPED_INFO("a key not found"); + CHECK(ALMessageMap::luauError("Key 'b' not found in table '{ a: number }'. Did you mean 'a'?", m)); + UNSCOPED_INFO("did you mean"); + CHECK(m.key == std::string("LuauMissingPropertyDidYouMean")); + UNSCOPED_INFO("its words"); + CHECK(joined(m.args) == std::string("<{ a: number }>")); + UNSCOPED_INFO("nothing the table knows"); + CHECK_FALSE(ALMessageMap::luauError("Some words nobody wrote", m)); // The new solver's nonstrict mode. - ensure("a checked call", ALMessageMap::luauError("the function 'string.len' expects to get a string as its 1st argument, but is being given a number", m)); - ensure_equals("its key", m.key, std::string("LuauCheckedCall")); - ensure_equals("and words", joined(m.args), std::string("<1st>")); - ensure("an argument that fails, in a function", - ALMessageMap::luauError("in the function 'f', 'the argument 'x' is used in a way that will error at runtime", m)); - ensure_equals("in a function", m.key + joined(m.args), std::string("LuauFailsAtRuntimeIn")); - ensure("and in none", ALMessageMap::luauError("the argument 'x' is used in a way that will error at runtime", m)); - ensure_equals("in none", m.key + joined(m.args), std::string("LuauFailsAtRuntime")); + UNSCOPED_INFO("a checked call"); + CHECK(ALMessageMap::luauError("the function 'string.len' expects to get a string as its 1st argument, but is being given a number", m)); + UNSCOPED_INFO("its key"); + CHECK(m.key == std::string("LuauCheckedCall")); + UNSCOPED_INFO("and words"); + CHECK(joined(m.args) == std::string("<1st>")); + UNSCOPED_INFO("an argument that fails, in a function"); + CHECK(ALMessageMap::luauError("in the function 'f', 'the argument 'x' is used in a way that will error at runtime", m)); + UNSCOPED_INFO("in a function"); + CHECK(m.key + joined(m.args) == std::string("LuauFailsAtRuntimeIn")); + UNSCOPED_INFO("and in none"); + CHECK(ALMessageMap::luauError("the argument 'x' is used in a way that will error at runtime", m)); + UNSCOPED_INFO("in none"); + CHECK(m.key + joined(m.args) == std::string("LuauFailsAtRuntime")); } - template<> template<> - void almessagemap_object::test<7>() + TEST_CASE_METHOD(almessagemap_data, "almessagemap: unmarked templates and brackets are literal and fill puts words in as is", "[almessagemap]") { - set_test_name("a template without marks is its message whole; a bracket that is no mark is part of a literal; and a word is put in as it is"); std::vector args; - ensure("not the start of a longer one", !ALMessageMap::match("Functions cannot change state.", "Functions cannot change state. And more.", args)); - ensure("a bracket before a mark stays in the literal", ALMessageMap::match("Index [a] of [1] is out", "Index [a] of list is out", args)); - ensure_equals("and the word is found after it", joined(args), std::string("")); - ensure("the literal still has to begin the message", !ALMessageMap::match("Index [a] of [1] is out", "a] of list is out", args)); - ensure("a bracket after the last mark too", ALMessageMap::match("[1] is [10] long", "list is [10] long", args) && joined(args) == ""); - ensure_equals("a word with a mark in it is not filled again", ALScriptProblem::fill("[1] and [2]", { "a[2]", "b" }), std::string("a[2] and b")); - ensure_equals("a mark with no word stays", ALScriptProblem::fill("[1] and [3]", { "a" }), std::string("a and [3]")); - ensure_equals("each mark as often as it is used", ALScriptProblem::fill("[1][1]", { "x" }), std::string("xx")); + UNSCOPED_INFO("not the start of a longer one"); + REQUIRE_FALSE(ALMessageMap::match("Functions cannot change state.", "Functions cannot change state. And more.", args)); + UNSCOPED_INFO("a bracket before a mark stays in the literal"); + CHECK(ALMessageMap::match("Index [a] of [1] is out", "Index [a] of list is out", args)); + UNSCOPED_INFO("and the word is found after it"); + CHECK(joined(args) == std::string("")); + UNSCOPED_INFO("the literal still has to begin the message"); + CHECK_FALSE(ALMessageMap::match("Index [a] of [1] is out", "a] of list is out", args)); + { + INFO("a bracket after the last mark too"); + CHECK(ALMessageMap::match("[1] is [10] long", "list is [10] long", args)); + CHECK(joined(args) == ""); + } + UNSCOPED_INFO("a word with a mark in it is not filled again"); + CHECK(ALScriptProblem::fill("[1] and [2]", { "a[2]", "b" }) == std::string("a[2] and b")); + UNSCOPED_INFO("a mark with no word stays"); + CHECK(ALScriptProblem::fill("[1] and [3]", { "a" }) == std::string("a and [3]")); + UNSCOPED_INFO("each mark as often as it is used"); + CHECK(ALScriptProblem::fill("[1][1]", { "x" }) == std::string("xx")); } - template<> template<> - void almessagemap_object::test<8>() + TEST_CASE_METHOD(almessagemap_data, "almessagemap: why a SLua require found nothing is keyed with the module name left blank", "[almessagemap]") { - set_test_name("why a SLua require found nothing, keyed: Luau's navigator's words and the studio's own, the module's name left for [1]"); ALMessageMap::Match m; - ensure("a child", ALMessageMap::luauRequire("could not resolve child component \"util\"", m) && m.key == "PreprocRequireNoChild" && - m.args == std::vector({ "", "util" })); - ensure("ambiguous, the fuller shape", ALMessageMap::luauRequire("could not resolve child component \"util\" (ambiguous)", m) && - m.key == "PreprocRequireNoChildAmbiguous"); - ensure("an alias", ALMessageMap::luauRequire("@lib is not a valid alias", m) && m.key == "PreprocRequireNotAlias" && m.args[1] == "lib"); - ensure("a cycle", ALMessageMap::luauRequire("detected alias cycle (@a -> @b -> @a)", m) && m.key == "PreprocRequireAliasCycle" && - m.args[1] == "@a -> @b -> @a"); - ensure("the studio's own", ALMessageMap::luauRequire("the alias '@sl-std' is reserved: aliases starting @sl- are Second Life's", m) && - m.key == "PreprocRequireReserved" && m.args[1] == "sl-std"); - ensure("a folder with both configurations", ALMessageMap::luauRequire("could not resolve alias \"lib\" (ambiguous configuration file)", m) && - m.key == "PreprocRequireConfigAmbiguous" && m.args[1] == "lib"); - ensure("a file nothing lets in", ALMessageMap::luauRequire("'C:/lib/x.luau' is there, but no include folder or alias lets a require read it", m) && - m.key == "PreprocRequireNotAdmitted" && m.args[1] == "C:/lib/x.luau"); - ensure("an alias past its configuration's folder", - ALMessageMap::luauRequire("the configuration in 'C:/lib' names 'D:/out', which is outside it and outside the include folders", m) && - m.key == "PreprocRequireAliasRefused" && m.args == std::vector({ "", "C:/lib", "D:/out" })); - ensure("a configuration that could not be read", - ALMessageMap::luauRequire("the configuration 'C:/lib/.luaurc' could not be read: expected value", m) && - m.key == "PreprocRequireConfigBroken" && m.args == std::vector({ "", "C:/lib/.luaurc", "expected value" })); - ensure("a world alias naming the disk", - ALMessageMap::luauRequire("the alias stands for '/x', a path on disk, which a configuration in the world may not name", m) && - m.key == "PreprocRequireAliasNamesDisk" && m.args[1] == "/x"); - ensure("a reason of no known shape", !ALMessageMap::luauRequire("something else went wrong", m)); - ensure("its keys known as such", ALMessageMap::requireReason("PreprocRequireNoChild") && !ALMessageMap::requireReason("PreprocRequireTooDeep")); + { + INFO("a child"); + 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"); + CHECK(ALMessageMap::luauRequire("could not resolve child component \"util\" (ambiguous)", m)); + CHECK(m.key == "PreprocRequireNoChildAmbiguous"); + } + { + INFO("an alias"); + CHECK(ALMessageMap::luauRequire("@lib is not a valid alias", m)); + CHECK(m.key == "PreprocRequireNotAlias"); + CHECK(m.args[1] == "lib"); + } + { + INFO("a cycle"); + CHECK(ALMessageMap::luauRequire("detected alias cycle (@a -> @b -> @a)", m)); + CHECK(m.key == "PreprocRequireAliasCycle"); + CHECK(m.args[1] == "@a -> @b -> @a"); + } + { + INFO("the studio's own"); + 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"); + } + { + INFO("a folder with both configurations"); + CHECK(ALMessageMap::luauRequire("could not resolve alias \"lib\" (ambiguous configuration file)", m)); + CHECK(m.key == "PreprocRequireConfigAmbiguous"); + CHECK(m.args[1] == "lib"); + } + { + INFO("a file nothing lets in"); + 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"); + } + { + INFO("an alias past its configuration's folder"); + 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"); + 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"); + 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"); + } + UNSCOPED_INFO("a reason of no known shape"); + CHECK_FALSE(ALMessageMap::luauRequire("something else went wrong", m)); + { + INFO("its keys known as such"); + CHECK(ALMessageMap::requireReason("PreprocRequireNoChild")); + CHECK_FALSE(ALMessageMap::requireReason("PreprocRequireTooDeep")); + } } } diff --git a/indra/alscript/tests/almodulelook_test.cpp b/indra/alscript/tests/almodulelook_test.cpp index 7621d1f60f..00dd0b8ea2 100644 --- a/indra/alscript/tests/almodulelook_test.cpp +++ b/indra/alscript/tests/almodulelook_test.cpp @@ -30,7 +30,7 @@ #include "fsyspath.h" #include "llfile.h" -#include "../test/lltut.h" +#include #include #include @@ -79,21 +79,15 @@ namespace } } -namespace tut +namespace { struct almodulelook_data { ALModuleLook look; }; - typedef test_group almodulelook_group; - typedef almodulelook_group::object almodulelook_object; - almodulelook_group almodulelook_instance("almodulelook"); - - template<> template<> - void almodulelook_object::test<1>() + TEST_CASE_METHOD(almodulelook_data, "almodulelook: LSL: open texts first then folder files by path; never the script itself", "[almodulelook]") { - set_test_name("LSL: a folder's files by their path from it, what each declares; an open text first, the script itself never"); Scratch s; const std::string util = s.write("inc/lib/util.lsl", "integer helper() { return 1; }\n#define CHANNEL -42\n"); s.write("inc/notes.txt", "not a module\n"); @@ -103,19 +97,32 @@ namespace tut input.texts.push_back({ "inventory:open", "open.lsl", 3, std::make_shared("integer opened;\n") }); input.texts.push_back({ "object:self", "self.lsl", 1, std::make_shared("integer mine;\n") }); const std::vector found = look.look(input); - ensure_equals("the open one and the file, not the script itself nor another kind", found.size(), size_t(2)); - ensure("the open one first", found[0].path == "inventory:open" && found[0].exports == std::vector{ "opened" }); + UNSCOPED_INFO("the open one and the file, not the script itself nor another kind"); + REQUIRE(found.size() == size_t(2)); + { + INFO("the open one first"); + CHECK(found[0].path == "inventory:open"); + CHECK(found[0].exports == std::vector{ "opened" }); + } const ALModuleLook::Candidate* file = named(found, "util"); - ensure("the file, as it stands", file && file->path == ALIncludeIdentity::ofFile(util)); - ensure("by its path from the folder, then its own name", file->names.size() >= 2 && file->names[0] == "lib/util.lsl" && file->names[1] == "util.lsl"); - ensure("what it declares", file->exports == std::vector({ "CHANNEL", "helper" }) || - file->exports == std::vector({ "helper", "CHANNEL" })); + { + INFO("the file, as it stands"); + REQUIRE(file); + CHECK(file->path == ALIncludeIdentity::ofFile(util)); + } + { + INFO("by its path from the folder, then its own name"); + REQUIRE(file->names.size() >= 2); + REQUIRE(file->names[0] == "lib/util.lsl"); + CHECK(file->names[1] == "util.lsl"); + } + UNSCOPED_INFO("what it declares"); + CHECK((file->exports == std::vector({ "CHANNEL", "helper" }) || + file->exports == std::vector({ "helper", "CHANNEL" }))); } - template<> template<> - void almodulelook_object::test<2>() + TEST_CASE_METHOD(almodulelook_data, "almodulelook: SLua: a module by stem under prefix and alias; reread when its version moves", "[almodulelook]") { - set_test_name("SLua: a module by its stem, under a folder's prefix and each alias; an open text read again only as its version moves"); Scratch s; s.write("mods/net/init.luau", "return { get = function() end }\n"); s.write("mods/util.luau", "local M = {}\nfunction M.clamp() end\nreturn M\n"); @@ -128,24 +135,38 @@ namespace tut input.texts.push_back({ "inventory:open", "open.luau", 1, text }); std::vector found = look.look(input); const ALModuleLook::Candidate* util = named(found, "util"); - ensure("by its stem, under the prefix", util && util->names[0] == "@lib/util" && util->exports == std::vector{ "clamp" }); + { + INFO("by its stem, under the prefix"); + REQUIRE(util); + REQUIRE(util->names[0] == "@lib/util"); + CHECK(util->exports == std::vector{ "clamp" }); + } const ALModuleLook::Candidate* folder = named(found, "init"); - ensure("a folder's init by the folder's name first", folder && folder->names[0] == "@lib/net"); + { + INFO("a folder's init by the folder's name first"); + REQUIRE(folder); + CHECK(folder->names[0] == "@lib/net"); + } const ALModuleLook::Candidate* open = named(found, "open"); - ensure("an open one under the alias too", open && std::find(open->names.begin(), open->names.end(), "@lib/open") != open->names.end()); - ensure("what it exports", open->exports == std::vector{ "one" }); + { + INFO("an open one under the alias too"); + REQUIRE(open); + REQUIRE(std::find(open->names.begin(), open->names.end(), "@lib/open") != open->names.end()); + } + UNSCOPED_INFO("what it exports"); + CHECK(open->exports == std::vector{ "one" }); // 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"); - ensure("the same version: as read", named(look.look(input), "open")->exports == std::vector{ "one" }); + UNSCOPED_INFO("the same version: as read"); + CHECK(named(look.look(input), "open")->exports == std::vector{ "one" }); input.texts[0].version = 2; - ensure("a new one: read again", named(look.look(input), "open")->exports == std::vector{ "two" }); + UNSCOPED_INFO("a new one: read again"); + CHECK(named(look.look(input), "open")->exports == std::vector{ "two" }); } - template<> template<> - void almodulelook_object::test<3>() + TEST_CASE_METHOD(almodulelook_data, "almodulelook: SLua: checked exports of the same text win for files and open texts alike", "[almodulelook]") { - set_test_name("SLua: what the analysis found a module exports, where it checked that very text, over what the text alone says; a file and an open text alike"); Scratch s; const std::string made_text = "local function make()\n local t = {}\n t.alpha = 1\n t.beta = function() end\n return t\nend\nreturn make()\n"; const std::string made = s.write("mods/made.luau", made_text); @@ -153,23 +174,25 @@ namespace tut input.lua = true; input.self = "object:self"; input.folders = { { std::string(), s.at("mods") } }; - ensure("the text alone says nothing of a table made by a call", named(look.look(input), "made")->exports.empty()); + UNSCOPED_INFO("the text alone says nothing of a table made by a call"); + REQUIRE(named(look.look(input), "made")->exports.empty()); ALLuauExports::checked(ALIncludeIdentity::ofFile(made), made_text, { "alpha", "beta" }); - ensure("checked: what its type says", named(look.look(input), "made")->exports == std::vector({ "alpha", "beta" })); + UNSCOPED_INFO("checked: what its type says"); + CHECK(named(look.look(input), "made")->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" }); - ensure("an open text checked", named(look.look(input), "open")->exports == std::vector{ "gamma" }); + UNSCOPED_INFO("an open text checked"); + CHECK(named(look.look(input), "open")->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; - ensure("its text moved on since: read as it says", named(look.look(input), "open")->exports == std::vector{ "delta" }); + UNSCOPED_INFO("its text moved on since: read as it says"); + CHECK(named(look.look(input), "open")->exports == std::vector{ "delta" }); } - template<> template<> - void almodulelook_object::test<4>() + TEST_CASE_METHOD(almodulelook_data, "almodulelook: extensions match in any case and a later check of the same text is taken", "[almodulelook]") { - set_test_name("a file's extension in any case, as a require finds it; what the analysis found of a file asked each look, a later check of the same text taken"); Scratch s; const std::string text = "local M = {}\nfunction M.one() end\nreturn M\n"; const std::string upper = s.write("mods/Upper.LUAU", text); @@ -180,16 +203,27 @@ namespace tut input.folders = { { "@lib/", s.at("mods") } }; std::vector found = look.look(input); const ALModuleLook::Candidate* one = named(found, "Upper"); - ensure("listed, by its stem", one && one->names[0] == "@lib/Upper" && one->exports == std::vector{ "one" }); + { + INFO("listed, by its stem"); + REQUIRE(one); + REQUIRE(one->names[0] == "@lib/Upper"); + CHECK(one->exports == std::vector{ "one" }); + } ALLuauExports::checked(ALIncludeIdentity::ofFile(upper), text, { "first" }); - ensure("checked", named(look.look(input), "Upper")->exports == std::vector{ "first" }); + UNSCOPED_INFO("checked"); + CHECK(named(look.look(input), "Upper")->exports == std::vector{ "first" }); ALLuauExports::checked(ALIncludeIdentity::ofFile(upper), text, { "second" }); - ensure("checked again, the text the same: taken", named(look.look(input), "Upper")->exports == std::vector{ "second" }); + UNSCOPED_INFO("checked again, the text the same: taken"); + CHECK(named(look.look(input), "Upper")->exports == std::vector{ "second" }); input.lua = false; found = look.look(input); one = named(found, "defs.LSLH"); - ensure("an LSL include's in any case, by its name whole", one && one->exports == std::vector{ "shouted" }); + { + INFO("an LSL include's in any case, by its name whole"); + REQUIRE(one); + CHECK(one->exports == std::vector{ "shouted" }); + } } } diff --git a/indra/alscript/tests/alnotecardformat_test.cpp b/indra/alscript/tests/alnotecardformat_test.cpp index 9342240362..7fc65e2ab2 100644 --- a/indra/alscript/tests/alnotecardformat_test.cpp +++ b/indra/alscript/tests/alnotecardformat_test.cpp @@ -27,12 +27,12 @@ #include "../core/alnotecardformat.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct alnotecardformat_data { @@ -48,44 +48,43 @@ namespace tut return out; } }; - typedef test_group alnotecardformat_group; - typedef alnotecardformat_group::object alnotecardformat_object; - alnotecardformat_group alnotecardformat_instance("ALNotecardFormat"); - template<> template<> - void alnotecardformat_object::test<1>() + TEST_CASE_METHOD(alnotecardformat_data, "ALNotecardFormat: guess: JSON by brace or bracket; settings by mostly keys; text otherwise", "[ALNotecardFormat]") { - set_test_name("guessed: JSON by its brace or bracket; settings where most lines are keys or sections, two at least; plain text otherwise"); - ensure_equals("an object", ALNotecardFormat::guess(" \n{ \"a\": 1 }\n"), std::string("json")); - ensure_equals("an array", ALNotecardFormat::guess("[1, 2]"), std::string("json")); - ensure_equals("settings", ALNotecardFormat::guess("# the door\n[Door]\nspeed = 2\nsound: creak\n\nlocked=false\n"), std::string("config")); - ensure_equals("one key is not enough", ALNotecardFormat::guess("title: A Story\nOnce upon a time.\nThe end.\n"), std::string("text")); - ensure_equals("prose with a colon or two", ALNotecardFormat::guess("Note: read this.\nWelcome to the store\nAsk for help\nHave fun\n"), + UNSCOPED_INFO("an object"); + CHECK(ALNotecardFormat::guess(" \n{ \"a\": 1 }\n") == std::string("json")); + UNSCOPED_INFO("an array"); + CHECK(ALNotecardFormat::guess("[1, 2]") == std::string("json")); + UNSCOPED_INFO("settings"); + CHECK(ALNotecardFormat::guess("# the door\n[Door]\nspeed = 2\nsound: creak\n\nlocked=false\n") == std::string("config")); + UNSCOPED_INFO("one key is not enough"); + CHECK(ALNotecardFormat::guess("title: A Story\nOnce upon a time.\nThe end.\n") == std::string("text")); + UNSCOPED_INFO("prose with a colon or two"); + CHECK(ALNotecardFormat::guess("Note: read this.\nWelcome to the store\nAsk for help\nHave fun\n") == std::string("text")); - ensure_equals("nothing", ALNotecardFormat::guess(""), std::string("text")); + UNSCOPED_INFO("nothing"); + CHECK(ALNotecardFormat::guess("") == std::string("text")); } - template<> template<> - void alnotecardformat_object::test<2>() + TEST_CASE_METHOD(alnotecardformat_data, "ALNotecardFormat: linesPast finds lines longer than llGetNotecardLine returns by bytes", "[ALNotecardFormat]") { - set_test_name("the lines, counted from 0, longer than llGetNotecardLine returns: by bytes"); const std::string one_line_too_long(ALNotecardFormat::READ_LINE_BYTES + 1, 'x'); const std::string exactly(ALNotecardFormat::READ_LINE_BYTES, 'y'); const std::vector past = ALNotecardFormat::linesPast("short\n" + one_line_too_long + "\n" + exactly + "\n" + one_line_too_long); - ensure("the second and the fourth", past == std::vector({ 1, 3 })); + UNSCOPED_INFO("the second and the fourth"); + CHECK(past == std::vector({ 1, 3 })); // 513 characters of two bytes each: 1026 bytes. std::string wide; for (int i = 0; i < 513; ++i) { wide += "\xC3\xA9"; } - ensure("by bytes, not characters", ALNotecardFormat::linesPast(wide) == std::vector({ 0 })); + UNSCOPED_INFO("by bytes, not characters"); + CHECK(ALNotecardFormat::linesPast(wide) == std::vector({ 0 })); } - template<> template<> - void alnotecardformat_object::test<3>() + TEST_CASE_METHOD(alnotecardformat_data, "ALNotecardFormat: a JSON outline nests keys and places array objects and spans each value", "[ALNotecardFormat]") { - set_test_name("a JSON outline: every key nested under the one it is in, an array's objects by their places, each spanning its value"); const std::string text = "{\n" " \"name\": \"Door\",\n" " \"speed\": 2.5,\n" @@ -94,61 +93,107 @@ namespace tut " \"on\": true\n" "}\n"; const std::vector entries = ALNotecardFormat::outline(text); - ensure_equals("the keys", said(entries), + UNSCOPED_INFO("the keys"); + CHECK(said(entries) == std::string("name=\"Door\" speed=2.5 sounds={...} .open=\"creak\" .shut=\"bang\" stops=[...] .[2]={...} ..at=3 on=true")); const ALScriptOutlineEntry& sounds = entries[2]; - ensure("sounds named where it is written", sounds.nameSpan.line == 3 && sounds.nameSpan.column == 2 && sounds.nameSpan.endColumn == 10); - ensure("and spanning its object", sounds.span.line == 3 && sounds.span.endLine == 3 && sounds.span.endColumn == 47); - ensure("a scalar spans its value", entries[1].span.line == 2 && entries[1].span.endColumn == 14); + { + INFO("sounds named where it is written"); + CHECK(sounds.nameSpan.line == 3); + CHECK(sounds.nameSpan.column == 2); + CHECK(sounds.nameSpan.endColumn == 10); + } + { + INFO("and spanning its object"); + CHECK(sounds.span.line == 3); + CHECK(sounds.span.endLine == 3); + CHECK(sounds.span.endColumn == 47); + } + { + INFO("a scalar spans its value"); + CHECK(entries[1].span.line == 2); + CHECK(entries[1].span.endColumn == 14); + } } - template<> template<> - void alnotecardformat_object::test<4>() + TEST_CASE_METHOD(alnotecardformat_data, "ALNotecardFormat: a JSON outline of a top array or broken text or too many entries", "[ALNotecardFormat]") { - set_test_name("a JSON outline of an array at the top, of text that is not JSON all through, and of more entries than asked for"); - ensure_equals("an array's objects", said(ALNotecardFormat::outline("[{\"a\": 1}, {\"b\": [ {\"c\": null} ]}]")), + UNSCOPED_INFO("an array's objects"); + CHECK(said(ALNotecardFormat::outline("[{\"a\": 1}, {\"b\": [ {\"c\": null} ]}]")) == std::string("[0]={...} .a=1 [1]={...} .b=[...] ..[0]={...} ...c=null")); - ensure_equals("broken off: as far as it goes", said(ALNotecardFormat::outline("{\"a\": {\"b\": 1, \"c\"")), std::string("a={...} .b=1")); - ensure_equals("not JSON at all: nothing", said(ALNotecardFormat::outline("just words: here")), std::string()); - ensure_equals("no more than asked", ALNotecardFormat::outline("{\"a\":1,\"b\":2,\"c\":3}", 2).size(), size_t(2)); + UNSCOPED_INFO("broken off: as far as it goes"); + CHECK(said(ALNotecardFormat::outline("{\"a\": {\"b\": 1, \"c\"")) == std::string("a={...} .b=1")); + UNSCOPED_INFO("not JSON at all: nothing"); + CHECK(said(ALNotecardFormat::outline("just words: here")) == std::string()); + UNSCOPED_INFO("no more than asked"); + REQUIRE(ALNotecardFormat::outline("{\"a\":1,\"b\":2,\"c\":3}", 2).size() == size_t(2)); } - template<> template<> - void alnotecardformat_object::test<5>() + TEST_CASE_METHOD(alnotecardformat_data, "ALNotecardFormat: namedAt finds the notecard named first in llGetNotecardLine and its kin", "[ALNotecardFormat]") { - set_test_name("a notecard a script names where it reads one: the string first in llGetNotecardLine and its kin, LSL's or SLua's, in either quote"); const std::string lsl = " key q = llGetNotecardLine(\"config\", line);"; std::optional named = ALNotecardFormat::namedAt(lsl, 34); - ensure("inside the string", named && named->name == "config" && named->begin == 30 && named->end == 38); - ensure("on its quote", ALNotecardFormat::namedAt(lsl, 30).has_value()); - ensure("not on the call's name", !ALNotecardFormat::namedAt(lsl, 16)); - ensure("SLua's, single quotes, blanks about the bracket", ALNotecardFormat::namedAt("ll.GetNumberOfNotecardLines ( 'dialog' )", 32)->name == "dialog"); - ensure("an escape taken out", ALNotecardFormat::namedAt("llGetNotecardLineSync(\"say \\\"hi\\\"\", 0)", 25)->name == "say \"hi\""); - ensure("a string another call is given, not", !ALNotecardFormat::namedAt("llSay(0, \"config\");", 12)); - ensure("the second argument, not", !ALNotecardFormat::namedAt("llGetNotecardLine(name, \"x\")", 25)); + { + INFO("inside the string"); + REQUIRE(named); + REQUIRE(named->name == "config"); + REQUIRE(named->begin == 30); + CHECK(named->end == 38); + } + UNSCOPED_INFO("on its quote"); + 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\""); + UNSCOPED_INFO("a string another call is given, not"); + REQUIRE_FALSE(ALNotecardFormat::namedAt("llSay(0, \"config\");", 12)); + UNSCOPED_INFO("the second argument, not"); + REQUIRE_FALSE(ALNotecardFormat::namedAt("llGetNotecardLine(name, \"x\")", 25)); const std::string script = "default {\n" " state_entry() { llGetNotecardLine(\"config\", 0); llSay(0, \"config\"); }\n" " touch_start(integer n) { llGetNumberOfNotecardLines(\"config\"); llGetNotecardLine(\"other\", 0); }\n" "}\n"; const std::vector read = ALNotecardFormat::readersOf(script, "config"); - ensure_equals("two readers of it", read.size(), size_t(2)); - ensure("the first", read[0].line == 1 && read[0].column == 36 && read[0].endColumn == 44); - ensure("the second", read[1].line == 2 && read[1].column == 54); - ensure("none of another", ALNotecardFormat::readersOf(script, "missing").empty()); + UNSCOPED_INFO("two readers of it"); + REQUIRE(read.size() == size_t(2)); + { + INFO("the first"); + CHECK(read[0].line == 1); + CHECK(read[0].column == 36); + CHECK(read[0].endColumn == 44); + } + { + INFO("the second"); + CHECK(read[1].line == 2); + CHECK(read[1].column == 54); + } + UNSCOPED_INFO("none of another"); + REQUIRE(ALNotecardFormat::readersOf(script, "missing").empty()); } - template<> template<> - void alnotecardformat_object::test<6>() + TEST_CASE_METHOD(alnotecardformat_data, "ALNotecardFormat: readersOf counts a lone CR as a line break as LF is and CRLF as one", "[ALNotecardFormat]") { - set_test_name("a notecard's readers by the lines a document makes of the script: a lone CR a break as LF is, CRLF one"); const std::string script = "default {\r" " state_entry() { llGetNotecardLine(\"config\", 0); }\r\n" " touch_start(integer n) { llGetNumberOfNotecardLines(\"config\"); }\n" "}\r"; const std::vector read = ALNotecardFormat::readersOf(script, "config"); - ensure_equals("both", read.size(), size_t(2)); - ensure("the first on the second line", read[0].line == 1 && read[0].column == 36 && read[0].endColumn == 44); - ensure("the second on the third", read[1].line == 2 && read[1].column == 54); + UNSCOPED_INFO("both"); + REQUIRE(read.size() == size_t(2)); + { + INFO("the first on the second line"); + CHECK(read[0].line == 1); + CHECK(read[0].column == 36); + CHECK(read[0].endColumn == 44); + } + { + INFO("the second on the third"); + CHECK(read[1].line == 2); + CHECK(read[1].column == 54); + } } } diff --git a/indra/alscript/tests/alpreprocessor_test.cpp b/indra/alscript/tests/alpreprocessor_test.cpp index b19d3d871e..3cbdc2bf43 100644 --- a/indra/alscript/tests/alpreprocessor_test.cpp +++ b/indra/alscript/tests/alpreprocessor_test.cpp @@ -28,7 +28,7 @@ #include "../lsl/allslservice.h" #include "../preprocessor/alpreprocessor.h" -#include "../test/lltut.h" +#include #include "Luau/Compiler.h" #include "lua.h" @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -70,7 +71,7 @@ namespace } } -namespace tut +namespace { struct alpreprocessor_data { @@ -201,7 +202,8 @@ namespace tut { ALLSLService service; std::string error; - ensure("builtins: " + error, service.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error)); + UNSCOPED_INFO("builtins: " + error); + REQUIRE(service.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error)); } // The same but the notes of a require nothing reads, which a script @@ -234,40 +236,39 @@ namespace tut } }; - typedef test_group alpreprocessor_group; - typedef alpreprocessor_group::object alpreprocessor_object; - alpreprocessor_group alpreprocessor_instance("alpreprocessor"); - - template<> template<> - void alpreprocessor_object::test<1>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: object-like and function-like macros expand in code and nowhere else", "[alpreprocessor]") { - set_test_name("object-like and function-like macros expand in code and nowhere else"); const std::string source = "#define GREETING \"hello\"\n" "#define TWICE(x) x + x\n" "default { state_entry() { llSay(0, GREETING); integer y = TWICE(2); string s = \"GREETING\"; /* GREETING */ } }"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("text", r.text, + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("text"); + CHECK(r.text == std::string("\n\ndefault { state_entry() { llSay(0, \"hello\"); integer y = 2 + 2; string s = \"GREETING\"; /* GREETING */ } }")); - ensure("not disabled", !r.disabled); + UNSCOPED_INFO("not disabled"); + CHECK_FALSE(r.disabled); // A function-like macro's name on its own is only a name, and its // arguments may run over lines. r = ALPreprocessor::run("#define M(a, b) a+b\nM + 1\nM(1,\n 2)\n", options()); - ensure_equals("name alone", r.text, std::string("\nM + 1\n1+2\n")); - ensure_equals("no problems", messages(r), std::string()); + UNSCOPED_INFO("name alone"); + CHECK(r.text == std::string("\nM + 1\n1+2\n")); + UNSCOPED_INFO("no problems"); + CHECK(messages(r) == std::string()); // `#define` needs a name; an unknown directive is an error; a // redefinition is a warning; `#undef` takes it away. r = ALPreprocessor::run("#define\n#foo\n#define A 1\n#define A 2\nA\n#undef A\nA\n", options()); - ensure_equals("errors", messages(r), std::string("E 0: macro name missing in #define\nE 1: ill formed preprocessor directive '#foo'\nW 3: macro 'A' redefined\n")); - ensure_equals("undef", squeeze(r.text), std::string("2\nA\n")); + UNSCOPED_INFO("errors"); + CHECK(messages(r) == std::string("E 0: macro name missing in #define\nE 1: ill formed preprocessor directive '#foo'\nW 3: macro 'A' redefined\n")); + UNSCOPED_INFO("undef"); + CHECK(squeeze(r.text) == std::string("2\nA\n")); } - template<> template<> - void alpreprocessor_object::test<2>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: variadic macros with stringizing and pasting", "[alpreprocessor]") { - set_test_name("variadics, stringizing and pasting"); const std::string source = "#define STR(x) #x\n" "#define CAT(a, b) a ## b\n" "#define LOG(...) llOwnerSay(#__VA_ARGS__)\n" @@ -280,8 +281,10 @@ namespace tut "STR(__LINE__)\n" "TAIL(1) TAIL(1, 2, 3)\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("text", squeeze(r.text), + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("text"); + CHECK(squeeze(r.text) == std::string("\"hello world\"\n" "\"\\\"q\\\\n\\\"\"\n" "llSay(0, \"x\")\n" @@ -291,22 +294,24 @@ namespace tut "1 1 2, 3\n")); r = ALPreprocessor::run("#define P(a) ## a\n#define Q(a) # b\n#define R(a, a) a\nP(1)\n", options()); - ensure_equals("refused", messages(r), + UNSCOPED_INFO("refused"); + CHECK(messages(r) == std::string("E 0: '##' cannot be at either end of a macro body\n" "E 1: '#' is not followed by a macro parameter\n" "E 2: duplicate macro parameter 'a'\n")); - ensure_equals("nothing defined", squeeze(r.text), std::string("P(1)\n")); + UNSCOPED_INFO("nothing defined"); + CHECK(squeeze(r.text) == std::string("P(1)\n")); r = ALPreprocessor::run("#define M(a, b) a b\nM(1)\nM(1, 2, 3)\nM(1\n", options()); - ensure_equals("arity", messages(r), + UNSCOPED_INFO("arity"); + CHECK(messages(r) == std::string("E 1: too few arguments for macro 'M'\nE 2: too many arguments for macro 'M'\nE 3: unterminated argument list invoking macro 'M'\n")); - ensure_equals("left as written", squeeze(r.text), std::string("M(1)\nM(1, 2, 3)\nM(1\n")); + UNSCOPED_INFO("left as written"); + CHECK(squeeze(r.text) == std::string("M(1)\nM(1, 2, 3)\nM(1\n")); } - template<> template<> - void alpreprocessor_object::test<3>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: rescanning stops where the standard says", "[alpreprocessor]") { - set_test_name("rescanning stops where the standard says"); const std::string source = "#define f(a) a*g\n" "#define g(a) f(a)\n" "#define foo foo\n" @@ -318,14 +323,14 @@ namespace tut "AB\n" "I(I)(3)\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("text", squeeze(r.text), std::string("2*9*g\nfoo\na b AB\nI(3)\n")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("text"); + CHECK(squeeze(r.text) == std::string("2*9*g\nfoo\na b AB\nI(3)\n")); } - template<> template<> - void alpreprocessor_object::test<4>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: conditionals over C integer expressions keep their lines", "[alpreprocessor]") { - set_test_name("conditionals over C integer expressions keep their lines"); const std::string source = "#define A 1\n" "#define B 0\n" "#if A && !B\n" @@ -364,8 +369,10 @@ namespace tut "#endif\n" "end\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("text", squeeze(r.text), std::string("yes\nyes2\nyes3\nyes4\nyes5\nyes6\nend\n")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("text"); + 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; @@ -373,13 +380,17 @@ namespace tut { ++n; } - ensure_equals("one line each", n, 37); - ensure_equals("yes4 on its line", r.text.find("yes4"), r.text.find("yes4")); + UNSCOPED_INFO("one line each"); + CHECK(n == 37); + UNSCOPED_INFO("yes4 on its line"); + CHECK(r.text.find("yes4") == r.text.find("yes4")); ALSourceMap::Loc loc = r.map.toSource(19, 0); - ensure_equals("yes4 from line 19", loc.line, 19); + UNSCOPED_INFO("yes4 from line 19"); + CHECK(loc.line == 19); r = ALPreprocessor::run("#if 1\n#else\n#else\n#endif\n#endif\n#elif 1\n#if 1/0\n#endif\n#if 1 +\n#endif\n#if (1\n#endif\n#if 1\n", options()); - ensure_equals("errors", messages(r), + UNSCOPED_INFO("errors"); + CHECK(messages(r) == std::string("E 2: #else after #else\n" "E 4: #endif without #if\n" "E 5: #elif without #if\n" @@ -389,10 +400,8 @@ namespace tut "E 12: #if without #endif at the end of the file\n")); } - template<> template<> - void alpreprocessor_object::test<5>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: includes come from the resolver once if asked and name their own file", "[alpreprocessor]") { - set_test_name("includes come from the resolver, once if they ask, and say which file they are"); add("a.lsl", "integer a = __LINE__; string f = __SHORTFILE__; string id = __ASSETID__;\n", "aaaa"); add("once.lsl", "#pragma once\nonce\n"); add("deep.lsl", "#include \"deep.lsl\"\n"); @@ -409,49 +418,66 @@ namespace tut "#include NAME\n" "FROM_INC\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("problems", messages(r), std::string("E 5: could not find include file 'missing.lsl'\n")); - ensure_equals("text", r.text, + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string("E 5: could not find include file 'missing.lsl'\n")); + UNSCOPED_INFO("text"); + CHECK(r.text == std::string("\ninteger a = 1; string f = \"a.lsl\"; string id = \"aaaa\";\n" "string top = \"main.lsl\"; string tid = \"NOT_IN_WORLD\";\n" "\n\nonce\n" "\n\n\n\n\n\n7\n")); - ensure_equals("includes", r.includes.size(), 3u); - ensure_equals("first include", r.includes[0], std::string("a.lsl")); - ensure_equals("pending", r.pending.size(), 1u); - ensure_equals("pending name", r.pending[0], std::string("pending.lsl")); - ensure_equals("files", r.map.files().size(), 4u); - ensure_equals("main file", r.map.files()[0].name, std::string("main.lsl")); - ensure_equals("an include by its identity", r.map.fileOf("once.lsl"), 2); + UNSCOPED_INFO("includes"); + REQUIRE(r.includes.size() == 3u); + UNSCOPED_INFO("first include"); + CHECK(r.includes[0] == std::string("a.lsl")); + UNSCOPED_INFO("pending"); + REQUIRE(r.pending.size() == 1u); + UNSCOPED_INFO("pending name"); + CHECK(r.pending[0] == std::string("pending.lsl")); + UNSCOPED_INFO("files"); + REQUIRE(r.map.files().size() == 4u); + UNSCOPED_INFO("main file"); + CHECK(r.map.files()[0].name == std::string("main.lsl")); + UNSCOPED_INFO("an include by its identity"); + CHECK(r.map.fileOf("once.lsl") == 2); r = ALPreprocessor::run("#include \"deep.lsl\"\n", options()); - ensure("too deep", messages(r).find("nested too deeply") != std::string::npos); - ensure("in the include", r.problems.front().file == "deep.lsl"); + UNSCOPED_INFO("too deep"); + REQUIRE(messages(r).find("nested too deeply") != std::string::npos); + UNSCOPED_INFO("in the include"); + CHECK(r.problems.front().file == "deep.lsl"); r = ALPreprocessor::run("#include \"err.lsl\"\nafter\n", options()); - ensure_equals("error in an include", messages(r), std::string("E err.lsl:0: bad\n")); - ensure_equals("goes on", squeeze(r.text), std::string("after\n")); + UNSCOPED_INFO("error in an include"); + CHECK(messages(r) == std::string("E err.lsl:0: bad\n")); + UNSCOPED_INFO("goes on"); + CHECK(squeeze(r.text) == std::string("after\n")); ALPreprocessor::Options none = options(); none.resolve = nullptr; r = ALPreprocessor::run("#include \"a.lsl\"\n", none); - ensure_equals("no resolver", messages(r), std::string("E 0: could not find include file 'a.lsl'\n")); + UNSCOPED_INFO("no resolver"); + CHECK(messages(r) == std::string("E 0: could not find include file 'a.lsl'\n")); } - template<> template<> - void alpreprocessor_object::test<6>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: Firestorm quirks: multiline strings; off switch; #line; predefines", "[alpreprocessor]") { - set_test_name("Firestorm's LSL quirks: strings over lines, the off switch, #line, and the predefined macros"); ALPreprocessor::Result r = ALPreprocessor::run("string s = \"a\nb\";\nx", options()); - ensure_equals("a newline in a string", r.text, std::string("string s = \"a\\nb\"\n;\nx")); + UNSCOPED_INFO("a newline in a string"); + CHECK(r.text == std::string("string s = \"a\\nb\"\n;\nx")); const std::string off = "//fspreprocessor off\n#define X 1\nX\n"; r = ALPreprocessor::run(off, options()); - ensure("disabled", r.disabled); - ensure_equals("as it was", r.text, off); - ensure_equals("maps to itself", r.map.toSource(2, 0).line, 2); + UNSCOPED_INFO("disabled"); + CHECK(r.disabled); + UNSCOPED_INFO("as it was"); + CHECK(r.text == off); + UNSCOPED_INFO("maps to itself"); + CHECK(r.map.toSource(2, 0).line == 2); r = ALPreprocessor::run("#line 5 \"x\"\ny\n", options()); - ensure_equals("line passed through", r.text, std::string("//#line 5 \"x\"\ny\n")); + UNSCOPED_INFO("line passed through"); + CHECK(r.text == std::string("//#line 5 \"x\"\ny\n")); ALPreprocessor::Options o = options(); o.agentId = "1234"; @@ -460,29 +486,42 @@ namespace tut r = ALPreprocessor::run("__LINE__ __FILE__ __SHORTFILE__ __ASSETID__\n__AGENTKEY__ __AGENTID__ __AGENTIDRAW__ __AGENTNAME__ __UNIXTIME__\n" "integer(1.5) list(1, 2) key(x)\n__DATE__ __TIME__\n", o); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("line one", r.text.substr(0, r.text.find('\n')), std::string("1 \"main.lsl\" \"main.lsl\" \"asset-1\"")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("line one"); + CHECK(r.text.substr(0, r.text.find('\n')) == std::string("1 \"main.lsl\" \"main.lsl\" \"asset-1\"")); std::istringstream in(r.text); std::string line; std::getline(in, line); std::getline(in, line); - ensure_equals("line two", line, std::string("\"1234\" \"1234\" 1234 \"Some Body\" 1234567890")); + UNSCOPED_INFO("line two"); + CHECK(line == std::string("\"1234\" \"1234\" 1234 \"Some Body\" 1234567890")); std::getline(in, line); - ensure_equals("line three", line, std::string("((integer)(1.5)) ((list)(1, 2)) ((key)(x))")); + UNSCOPED_INFO("line three"); + CHECK(line == std::string("((integer)(1.5)) ((list)(1, 2)) ((key)(x))")); std::getline(in, line); - ensure_equals("date and time", line.size(), 13u + 1u + 10u); - ensure("date quoted", line[0] == '"' && line[12] == '"'); - ensure("time quoted", line[14] == '"' && line[23] == '"'); - ensure_equals("time colons", line.substr(17, 1) + line.substr(20, 1), std::string("::")); + UNSCOPED_INFO("date and time"); + REQUIRE(line.size() == 13u + 1u + 10u); + { + INFO("date quoted"); + CHECK(line[0] == '"'); + CHECK(line[12] == '"'); + } + { + INFO("time quoted"); + CHECK(line[14] == '"'); + CHECK(line[23] == '"'); + } + UNSCOPED_INFO("time colons"); + CHECK(line.substr(17, 1) + line.substr(20, 1) == std::string("::")); r = ALPreprocessor::run("#define BS 1 \\\n + 2\nBS\n", options()); - ensure_equals("continuation", squeeze(r.text), std::string("1 + 2\n")); + UNSCOPED_INFO("continuation"); + CHECK(squeeze(r.text) == std::string("1 + 2\n")); } - template<> template<> - void alpreprocessor_object::test<7>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: switch becomes the jump table Firestorm emits", "[alpreprocessor]") { - set_test_name("switch becomes the jump table Firestorm emits"); const std::string source = "#define USE_SWITCHES\n" "switch (x) {\n" "case 1: llSay(0, \"one\"); break;\n" @@ -490,9 +529,12 @@ namespace tut "default: llSay(0, \"other\");\n" "}\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("problems", messages(r), std::string()); - ensure("used", r.usedSwitches); - ensure_equals("text", r.text, + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("used"); + CHECK(r.usedSwitches); + UNSCOPED_INFO("text"); + CHECK(r.text == std::string("\n{if((x) == (1))jump _sw1_1;\n" "if((x) == (2))jump _sw1_2;\n" "jump _sw1_default;\n" @@ -505,63 +547,74 @@ namespace tut ALPreprocessor::Options o = options(); o.switches = true; r = ALPreprocessor::run("switch (a) { case 1: switch (b) { case 2: break; } break; }\n", o); - ensure_equals("nested", r.text, + UNSCOPED_INFO("nested"); + CHECK(r.text == std::string("{if((a) == (1))jump _sw2_1;\njump _sw2_end;\n" " @_sw2_1; {if((b) == (2))jump _sw1_1;\njump _sw1_end;\n" " @_sw1_1; jump _sw1_end; \n@_sw1_end;\n} jump _sw2_end; \n@_sw2_end;\n}\n")); - ensure_equals("not used when not asked", ALPreprocessor::run("switch (a) { }\n", options()).text, std::string("switch (a) { }\n")); + UNSCOPED_INFO("not used when not asked"); + CHECK(ALPreprocessor::run("switch (a) { }\n", options()).text == std::string("switch (a) { }\n")); r = ALPreprocessor::run("switch (a) { case 1 }\n", o); - ensure_equals("a case with no end", messages(r), std::string("E 0: cannot find ':' or '{' after case\n")); + UNSCOPED_INFO("a case with no end"); + CHECK(messages(r) == std::string("E 0: cannot find ':' or '{' after case\n")); // A value worked out again for each case, said where that is more // than a cost: a call, or one that changes something. const std::string warned = "W 0: the switch's value is worked out again for each case: put it in a local first\n"; - ensure_equals("a call", messages(ALPreprocessor::run("switch ((integer)llFrand(3)) { case 1: break; }\n", o)), warned); - ensure_equals("an increment", messages(ALPreprocessor::run("switch (i++) { case 1: break; }\n", o)), warned); - ensure_equals("an assignment", messages(ALPreprocessor::run("switch (i = 2) { case 1: break; }\n", o)), warned); - ensure_equals("not a name", messages(ALPreprocessor::run("switch (i) { case 1: break; }\n", o)), std::string()); - ensure_equals("nor arithmetic or a field", messages(ALPreprocessor::run("switch ((i + 1) * v.x) { case 1: break; }\n", o)), std::string()); - ensure_equals("nor a comparison", messages(ALPreprocessor::run("switch (i == 2) { case 1: break; }\n", o)), std::string()); + UNSCOPED_INFO("a call"); + CHECK(messages(ALPreprocessor::run("switch ((integer)llFrand(3)) { case 1: break; }\n", o)) == warned); + UNSCOPED_INFO("an increment"); + CHECK(messages(ALPreprocessor::run("switch (i++) { case 1: break; }\n", o)) == warned); + UNSCOPED_INFO("an assignment"); + CHECK(messages(ALPreprocessor::run("switch (i = 2) { case 1: break; }\n", o)) == warned); + UNSCOPED_INFO("not a name"); + CHECK(messages(ALPreprocessor::run("switch (i) { case 1: break; }\n", o)) == std::string()); + UNSCOPED_INFO("nor arithmetic or a field"); + CHECK(messages(ALPreprocessor::run("switch ((i + 1) * v.x) { case 1: break; }\n", o)) == std::string()); + UNSCOPED_INFO("nor a comparison"); + CHECK(messages(ALPreprocessor::run("switch (i == 2) { case 1: break; }\n", o)) == std::string()); } - template<> template<> - void alpreprocessor_object::test<8>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: lazy lists read and write through the helper", "[alpreprocessor]") { - set_test_name("lazy lists read and write through the helper"); const std::string source = "#define USE_LAZY_LISTS\n" "list l; integer i = (integer)l[2]; l[0] = 5; string s = (string)(l[(integer)l[1]]); rotation r = (rotation)l[0];\n" "x = (integer)(l[0]; f((float)l[0], l[1] = 2);\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("problems", messages(r), std::string()); - ensure("used", r.usedLazyLists); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("used"); + CHECK(r.usedLazyLists); const std::string helper = "list lazy_list_set(list L, integer i, list v)\n{\n while (llGetListLength(L) < i)\n L = L + 0;\n return llListReplaceList(L, v, i, i);\n}\n\n"; - ensure_equals("text", r.text, + UNSCOPED_INFO("text"); + CHECK(r.text == helper + "\nlist l; integer i = llList2Integer(l, 2); l = lazy_list_set(l,0,[5]); string s = llList2String(l, llList2Integer(l, 1)); rotation r = llList2Rot(l, 0);\n" "x = (integer)(l[0]; f(llList2Float(l, 0), l = lazy_list_set(l,1,[2]));\n"); ALPreprocessor::Options o = options(); o.lazyLists = true; r = ALPreprocessor::run("integer a = b == c;\n", o); - ensure("nothing to do", !r.usedLazyLists); - ensure_equals("untouched", r.text, std::string("integer a = b == c;\n")); + UNSCOPED_INFO("nothing to do"); + CHECK_FALSE(r.usedLazyLists); + UNSCOPED_INFO("untouched"); + CHECK(r.text == std::string("integer a = b == c;\n")); } - template<> template<> - void alpreprocessor_object::test<9>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: compression drops comments and every blank that is not needed", "[alpreprocessor]") { - set_test_name("compression drops comments and every blank that is not needed"); ALPreprocessor::Options o = options(); o.compress = true; ALPreprocessor::Result r = ALPreprocessor::run("// comment\ndefault { state_entry ( ) { integer a = - -1 ; /* x */ llSay( 0 , \"a b\" ) ; a ++ ; float f = 1 . 5; } }\n", o); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("text", r.text, std::string("default{state_entry(){integer a=- -1;llSay(0,\"a b\");a++;float f=1 . 5;}}\n")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("text"); + CHECK(r.text == std::string("default{state_entry(){integer a=- -1;llSay(0,\"a b\");a++;float f=1 . 5;}}\n")); r = ALPreprocessor::run("a\n\n\nb / /* c */ d\n", o); - ensure_equals("lines kept, not multiplied", r.text, std::string("a\nb/d\n")); + UNSCOPED_INFO("lines kept, not multiplied"); + CHECK(r.text == std::string("a\nb/d\n")); } - template<> template<> - void alpreprocessor_object::test<10>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: SLua: strings and comments and # kept; require gathered into modules", "[alpreprocessor]") { - set_test_name("SLua: its own strings and comments, # in a line, and require gathered into modules"); const std::string source = "--#define N 3\n" "local s = [[N]] -- N\n" "local t = \"N\" .. #N_list .. 'N' .. `N{N}`\n" @@ -569,21 +622,27 @@ namespace tut "print(N)\n" "--#line 3\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options(true)); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("text", r.text, + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("text"); + CHECK(r.text == std::string("\nlocal s = [[N]] -- N\nlocal t = \"N\" .. #N_list .. 'N' .. `N{N}`\n--[==[ N\nN ]==]\nprint(3)\n--#line 3\n")); add("a", "local b = require(\"b\")\nreturn { b = b }\n"); add("b", "return 42\n"); add("c", "return require(\"c\")\n"); r = ALPreprocessor::run("local a = require(\"a\")\nlocal b = require('b')\nlocal x = require(name)\n", options(true)); - ensure_equals("problems", faults(r), std::string()); + UNSCOPED_INFO("problems"); + CHECK(faults(r) == std::string()); // Numbered as met: a, then what a requires. const std::string bundled = bundleOf({ "local b = require(2)\nreturn { b = b }\n", "return 42\n" }, "local a = require(1)\nlocal b = require(2)\nlocal x = require(name)\n"); - ensure_equals("modules", r.text, bundled); - ensure_equals("included", r.includes.size(), 2u); - ensure_equals("b mapped to its file", r.map.files()[r.map.toSource(7, 0).file].name, std::string("b")); + UNSCOPED_INFO("modules"); + CHECK(r.text == bundled); + UNSCOPED_INFO("included"); + 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")); // 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; @@ -591,8 +650,10 @@ namespace tut { others += std::to_string(first) + "-" + std::to_string(last) + " "; } - ensure_equals("the modules' lines", others, std::string("3-4 7-7 ")); - ensure("not apart unless asked", !r.apart.valid); + UNSCOPED_INFO("the modules' lines"); + CHECK(others == std::string("3-4 7-7 ")); + UNSCOPED_INFO("not apart unless asked"); + CHECK_FALSE(r.apart.valid); // Apart, for the analyzers: the script with its requires as calls, // and each module as the run made it, each mapped to its file; the // text the bundle still. @@ -600,108 +661,113 @@ namespace tut apart.apart = true; const ALPreprocessor::Result split = ALPreprocessor::run("local a = require(\"a\")\nlocal b = require('b')\nlocal x = require(name)\n", apart); - ensure_equals("the bundle as ever", split.text, bundled); - ensure("apart", split.apart.valid && split.apart.modules.size() == 2); - ensure_equals("the script, its requires calls", split.apart.script.text, + UNSCOPED_INFO("the bundle as ever"); + CHECK(split.text == bundled); + { + INFO("apart"); + CHECK(split.apart.valid); + REQUIRE(split.apart.modules.size() == 2); + } + UNSCOPED_INFO("the script, its requires calls"); + CHECK(split.apart.script.text == std::string("local a = require(\"a\")\nlocal b = require('b')\nlocal x = require(name)\n")); - ensure_equals("the first reached", split.apart.modules[0].key, std::string("a")); - ensure_equals("as made, its own require a call", split.apart.modules[0].text, std::string("local b = require(\"b\")\nreturn { b = b }\n")); - ensure_equals("then what it reached", split.apart.modules[1].key, std::string("b")); - ensure_equals("b", split.apart.modules[1].text, std::string("return 42\n")); + UNSCOPED_INFO("the first reached"); + CHECK(split.apart.modules[0].key == std::string("a")); + UNSCOPED_INFO("as made, its own require a call"); + CHECK(split.apart.modules[0].text == std::string("local b = require(\"b\")\nreturn { b = b }\n")); + UNSCOPED_INFO("then what it reached"); + CHECK(split.apart.modules[1].key == std::string("b")); + UNSCOPED_INFO("b"); + CHECK(split.apart.modules[1].text == std::string("return 42\n")); const ALSourceMap::Loc in_a = split.apart.modules[0].map.toSource(1, 0); - ensure("mapped to its file", in_a.found() && split.apart.modules[0].map.files()[in_a.file].name == "a" && in_a.line == 1); - ensure("the script to its own", split.apart.script.map.toSource(2, 6).file == 0); + { + INFO("mapped to its file"); + REQUIRE(in_a.found()); + CHECK(split.apart.modules[0].map.files()[in_a.file].name == "a"); + CHECK(in_a.line == 1); + } + UNSCOPED_INFO("the script to its own"); + CHECK(split.apart.script.map.toSource(2, 6).file == 0); r = ALPreprocessor::run("local c = require(\"c\")\n", options(true)); - ensure_equals("a cycle", messages(r), std::string("E c:0: 'c' requires itself\n")); + UNSCOPED_INFO("a cycle"); + CHECK(messages(r) == std::string("E c:0: 'c' requires itself\n")); r = ALPreprocessor::run("local d = require(\"d\")\n", options(true)); - ensure_equals("missing", messages(r), std::string("E 0: could not find module 'd'\n")); - ensure_equals("left alone", r.text, std::string("local d = require(\"d\")\n")); + UNSCOPED_INFO("missing"); + CHECK(messages(r) == std::string("E 0: could not find module 'd'\n")); + UNSCOPED_INFO("left alone"); + CHECK(r.text == std::string("local d = require(\"d\")\n")); r = ALPreprocessor::run("--fspreprocessor off\n--#define X\n", options(true)); - ensure("off in Lua", r.disabled); + UNSCOPED_INFO("off in Lua"); + CHECK(r.disabled); } - template<> template<> - void alpreprocessor_object::test<11>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: the source map goes both ways", "[alpreprocessor]") { - set_test_name("the source map goes both ways"); add("inc", "x;\n"); ALPreprocessor::Result r = ALPreprocessor::run("#define X 10\ninteger a = X + 1;\n#include \"inc\"\ny;\n", options()); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("text", r.text, std::string("\ninteger a = 10 + 1;\n\nx;\ny;\n")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("text"); + CHECK(r.text == std::string("\ninteger a = 10 + 1;\n\nx;\ny;\n")); ALSourceMap::Loc loc = r.map.toSource(1, 0); - ensure("found", loc.found()); - ensure_equals("integer", loc.line * 1000 + loc.column, 1000); + UNSCOPED_INFO("found"); + REQUIRE(loc.found()); + UNSCOPED_INFO("integer"); + CHECK(loc.line * 1000 + loc.column == 1000); loc = r.map.toSource(1, 5); - ensure_equals("within integer", loc.column, 5); + UNSCOPED_INFO("within integer"); + CHECK(loc.column == 5); loc = r.map.toSource(1, 12); - ensure_equals("the 10 is the X", loc.column, 12); + UNSCOPED_INFO("the 10 is the X"); + CHECK(loc.column == 12); loc = r.map.toSource(1, 13); - ensure_equals("still the X", loc.column, 12); + UNSCOPED_INFO("still the X"); + CHECK(loc.column == 12); loc = r.map.toSource(1, 15); - ensure_equals("the + past the X", loc.column, 14); + UNSCOPED_INFO("the + past the X"); + CHECK(loc.column == 14); loc = r.map.toSource(1, 40); - ensure_equals("past the end", loc.column, 18); + UNSCOPED_INFO("past the end"); + CHECK(loc.column == 18); loc = r.map.toSource(0, 0); - ensure("a directive line has no origin", !loc.found()); + UNSCOPED_INFO("a directive line has no origin"); + REQUIRE_FALSE(loc.found()); loc = r.map.toSource(3, 0); - ensure_equals("from the include", loc.file, 1); - ensure_equals("its line", loc.line, 0); + UNSCOPED_INFO("from the include"); + CHECK(loc.file == 1); + UNSCOPED_INFO("its line"); + CHECK(loc.line == 0); loc = r.map.toSource(4, 0); - ensure_equals("after the include", loc.file * 1000 + loc.line, 3); + UNSCOPED_INFO("after the include"); + CHECK(loc.file * 1000 + loc.line == 3); loc = r.map.toExpanded(0, 1, 12); - ensure("forward found", loc.found()); - ensure_equals("X went to line 1", loc.line, 1); - ensure_equals("X went to column 12", loc.column, 12); + UNSCOPED_INFO("forward found"); + REQUIRE(loc.found()); + UNSCOPED_INFO("X went to line 1"); + CHECK(loc.line == 1); + UNSCOPED_INFO("X went to column 12"); + CHECK(loc.column == 12); loc = r.map.toExpanded(0, 1, 3); - ensure_equals("within integer forward", loc.column, 3); + UNSCOPED_INFO("within integer forward"); + CHECK(loc.column == 3); loc = r.map.toExpanded(0, 0, 3); - ensure("a directive made nothing", !loc.found()); + UNSCOPED_INFO("a directive made nothing"); + REQUIRE_FALSE(loc.found()); loc = r.map.toExpanded(0, 3, 0); - ensure_equals("y went past the include", loc.line, 4); + UNSCOPED_INFO("y went past the include"); + CHECK(loc.line == 4); loc = r.map.toExpanded(1, 0, 1); - ensure_equals("the include's x", loc.line, 3); + UNSCOPED_INFO("the include's x"); + CHECK(loc.line == 3); loc = r.map.toExpanded(0, 1, 100); - ensure_equals("past the end forward", loc.column, 19); - } - - template<> template<> - void alpreprocessor_object::test<13>() - { - set_test_name("the optimizer runs over the expanded text, and its notes come back to the source"); - ensureGridBuiltins(); - add("consts.lsl", "#define CHANNEL 7\ninteger unused = 1;\n"); - ALPreprocessor::Options o = options(); - o.optimize = true; - o.compress = true; - const std::string source = "#include \"consts.lsl\"\n#define TWICE(x) ((x) * 2)\ndefault\n{\n state_entry()\n {\n llSay(CHANNEL, (string)TWICE(21));\n }\n}\n"; - ALPreprocessor::Result r = ALPreprocessor::run(source, o); - ensure("optimized", r.optimized); - ensure_equals("text", r.text, std::string("default\n{\nstate_entry()\n{\nllSay(7,\"42\");\n}\n}\n")); - // The unused global was in the include, and the note says so. - bool noted = false; - for (const ALScriptProblem& p : r.problems) - { - if (p.message.find("removed the unused global unused") != std::string::npos) - { - noted = true; - ensure_equals("in the include", p.file, std::string("consts.lsl")); - ensure_equals("on its line there", p.line, 1); - } - } - ensure("noted", noted); - // The llSay is on output line 4, which came from source line 6. - const ALSourceMap::Loc loc = r.map.toSource(4, 0); - ensure("found", loc.found()); - ensure_equals("main file", loc.file, 0); - ensure_equals("line", loc.line, 6); + UNSCOPED_INFO("past the end forward"); + CHECK(loc.column == 19); } - template<> template<> - void alpreprocessor_object::test<12>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: every golden file comes out as its expected text", "[alpreprocessor]") { - set_test_name("every golden file comes out as its expected text"); namespace fs = std::filesystem; const fs::path dir = fsyspath(AL_ALSCRIPT_TEST_DIR) / "preprocessor"; S32 checked = 0; @@ -715,7 +781,8 @@ namespace tut const std::string stem = entry.path().stem().string(); const fs::path expected = dir / (stem + "_expected" + entry.path().extension().string()); ALPreprocessor::Result r = runGolden(entry.path(), "\n"); - ensure_equals("problems in " + name, messages(r), std::string()); + UNSCOPED_INFO("problems in " + name); + REQUIRE(messages(r) == std::string()); // With AL_PREPROCESSOR_WRITE_EXPECTED set, the run writes the // expected files instead of checking them: for a new golden // file, or a change that was meant, each to be read over @@ -726,91 +793,163 @@ namespace tut out << r.text; continue; } - ensure("expected file for " + name, fs::exists(expected)); - ensure_equals("output of " + name, r.text, readFile(expected)); + UNSCOPED_INFO("expected file for " + name); + REQUIRE(fs::exists(expected)); + UNSCOPED_INFO("output of " + name); + REQUIRE(r.text == readFile(expected)); ++checked; } - ensure("some golden files", checked > 0 || std::getenv("AL_PREPROCESSOR_WRITE_EXPECTED")); + UNSCOPED_INFO("some golden files"); + REQUIRE((checked > 0 || std::getenv("AL_PREPROCESSOR_WRITE_EXPECTED"))); } - template<> template<> - void alpreprocessor_object::test<14>() + + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: the optimizer runs on expanded text and its notes map to the source", "[alpreprocessor]") + { + ensureGridBuiltins(); + add("consts.lsl", "#define CHANNEL 7\ninteger unused = 1;\n"); + ALPreprocessor::Options o = options(); + o.optimize = true; + o.compress = true; + const std::string source = "#include \"consts.lsl\"\n#define TWICE(x) ((x) * 2)\ndefault\n{\n state_entry()\n {\n llSay(CHANNEL, (string)TWICE(21));\n }\n}\n"; + ALPreprocessor::Result r = ALPreprocessor::run(source, o); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + UNSCOPED_INFO("text"); + CHECK(r.text == std::string("default\n{\nstate_entry()\n{\nllSay(7,\"42\");\n}\n}\n")); + // The unused global was in the include, and the note says so. + bool noted = false; + for (const ALScriptProblem& p : r.problems) + { + if (p.message.find("removed the unused global unused") != std::string::npos) + { + noted = true; + UNSCOPED_INFO("in the include"); + REQUIRE(p.file == std::string("consts.lsl")); + UNSCOPED_INFO("on its line there"); + REQUIRE(p.line == 1); + } + } + UNSCOPED_INFO("noted"); + CHECK(noted); + // The llSay is on output line 4, which came from source line 6. + const ALSourceMap::Loc loc = r.map.toSource(4, 0); + UNSCOPED_INFO("found"); + REQUIRE(loc.found()); + UNSCOPED_INFO("main file"); + CHECK(loc.file == 0); + UNSCOPED_INFO("line"); + CHECK(loc.line == 6); + } + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: break and continue become jumps; &= |= ^= <<= >>= become assignments", "[alpreprocessor]") { - set_test_name("break and continue become jumps to labels after the loop and at the body's end, and &= |= ^= <<= >>= the assignments they are"); ALPreprocessor::Options o = options(); o.extensions = true; ALPreprocessor::Result r = ALPreprocessor::run("while (a) { if (b) break; if (c) continue; d++; }\n", o); - ensure_equals("problems", messages(r), std::string()); - ensure("used", r.usedExtensions); - ensure_equals("a while", r.text, std::string("while (a) { if (b) jump _brk1; if (c) jump _cnt1; d++; @_cnt1;}@_brk1;\n")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("used"); + CHECK(r.usedExtensions); + UNSCOPED_INFO("a while"); + CHECK(r.text == std::string("while (a) { if (b) jump _brk1; if (c) jump _cnt1; d++; @_cnt1;}@_brk1;\n")); r = ALPreprocessor::run("for (i = 0; i < 3; i++) if (i == 1) continue; else x++;\n", o); - ensure_equals("a for with one statement for a body gets braces", r.text, std::string("for (i = 0; i < 3; i++) { if (i == 1) jump _cnt1; else x++;@_cnt1;}\n")); + UNSCOPED_INFO("a for with one statement for a body gets braces"); + CHECK(r.text == std::string("for (i = 0; i < 3; i++) { if (i == 1) jump _cnt1; else x++;@_cnt1;}\n")); r = ALPreprocessor::run("do { if (x) break 2; } while (y);\n", o); - ensure_equals("break 2 with one loop is an error", messages(r), std::string("E 0: break outside a loop\n")); + UNSCOPED_INFO("break 2 with one loop is an error"); + CHECK(messages(r) == std::string("E 0: break outside a loop\n")); r = ALPreprocessor::run("while (a) { do { if (x) break 2; if (y) break; } while (b); c++; }\n", o); - ensure_equals("break 2 goes past the outer loop, break past the inner", r.text, + UNSCOPED_INFO("break 2 goes past the outer loop, break past the inner"); + CHECK(r.text == std::string("while (a) { do { if (x) jump _brk1; if (y) jump _brk2; } while (b);@_brk2; c++; }@_brk1;\n")); r = ALPreprocessor::run("continue;\n", o); - ensure_equals("outside a loop", messages(r), std::string("E 0: continue outside a loop\n")); + UNSCOPED_INFO("outside a loop"); + CHECK(messages(r) == std::string("E 0: continue outside a loop\n")); o.switches = true; r = ALPreprocessor::run("while (a) { switch (b) { case 1: break; } while (c) { break; } }\n", o); - ensure_equals("a break in a switch is the switch's, one in a loop inside it the loop's", r.text, + UNSCOPED_INFO("a break in a switch is the switch's, one in a loop inside it the loop's"); + CHECK(r.text == std::string("while (a) { {if((b) == (1))jump _sw1_1;\njump _sw1_end;\n @_sw1_1; jump _sw1_end; \n@_sw1_end;\n} while (c) { jump _brk2; }@_brk2; }\n")); r = ALPreprocessor::run("x &= 6; v.y |= 1 << n; z ^= (a | b); f(k <<= 2, 3); m >>= 1 + p;\n", o); - ensure_equals("the assignments", r.text, std::string("x = x & (6); v.y = v.y | (1 << n); z = z ^ ((a | b)); f(k = k << (2), 3); m = m >> (1 + p);\n")); - ensure_equals("not used when not asked", ALPreprocessor::run("while (a) break;\n", options()).text, std::string("while (a) break;\n")); + UNSCOPED_INFO("the assignments"); + CHECK(r.text == std::string("x = x & (6); v.y = v.y | (1 << n); z = z ^ ((a | b)); f(k = k << (2), 3); m = m >> (1 + p);\n")); + UNSCOPED_INFO("not used when not asked"); + CHECK(ALPreprocessor::run("while (a) break;\n", options()).text == std::string("while (a) break;\n")); r = ALPreprocessor::run("#define USE_EXTENSIONS\nwhile (a) break;\n", options()); - ensure_equals("USE_EXTENSIONS turns it on", r.text, std::string("\nwhile (a) jump _brk1;@_brk1;\n")); + UNSCOPED_INFO("USE_EXTENSIONS turns it on"); + CHECK(r.text == std::string("\nwhile (a) jump _brk1;@_brk1;\n")); r = ALPreprocessor::run("inline f() { }\ninline integer g(integer x) { return x; }\ninline = 3;\n", o); - ensure_equals("inline before a function is taken off and the name kept", r.text, std::string("f() { }\ninteger g(integer x) { return x; }\ninline = 3;\n")); - ensure("the names", r.inlined.size() == 2 && r.inlined[0] == "f" && r.inlined[1] == "g"); + UNSCOPED_INFO("inline before a function is taken off and the name kept"); + CHECK(r.text == std::string("f() { }\ninteger g(integer x) { return x; }\ninline = 3;\n")); + { + INFO("the names"); + REQUIRE(r.inlined.size() == 2); + CHECK(r.inlined[0] == "f"); + CHECK(r.inlined[1] == "g"); + } r = ALPreprocessor::run("integer break; while (a) { break = 1; x = continue; }\n", o); - ensure_equals("the words as names are errors, not rewrites", messages(r), + UNSCOPED_INFO("the words as names are errors, not rewrites"); + CHECK(messages(r) == std::string("E 0: 'break' is a name here, which break and continue reserve\n" "E 0: 'break' is a name here, which break and continue reserve\n" "E 0: 'continue' is a name here, which break and continue reserve\n")); - ensure_equals("and left as they were", r.text, std::string("integer break; while (a) { break = 1; x = continue; }\n")); + UNSCOPED_INFO("and left as they were"); + CHECK(r.text == std::string("integer break; while (a) { break = 1; x = continue; }\n")); } - template<> template<> - void alpreprocessor_object::test<15>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a label the script has where a loop's would go is the loop's", "[alpreprocessor]") { - set_test_name("a label the script has where a loop's would go is the loop's"); ALPreprocessor::Options o = options(); o.extensions = true; ALPreprocessor::Result r = ALPreprocessor::run("while (a) { if (b) break; c++; }\n@out;\n", o); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("a label after the loop is the break's", r.text, std::string("while (a) { if (b) jump out; c++; }\n@out;\n")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("a label after the loop is the break's"); + CHECK(r.text == std::string("while (a) { if (b) jump out; c++; }\n@out;\n")); r = ALPreprocessor::run("while (a) { if (b) continue; c++; @next; }\n", o); - ensure_equals("a label ending the body is the continue's", r.text, std::string("while (a) { if (b) jump next; c++; @next; }\n")); + UNSCOPED_INFO("a label ending the body is the continue's"); + CHECK(r.text == std::string("while (a) { if (b) jump next; c++; @next; }\n")); r = ALPreprocessor::run("do { if (b) break 2; } while (a); @x;\n", o); - ensure_equals("break 2 with one loop is still an error", messages(r), std::string("E 0: break outside a loop\n")); + UNSCOPED_INFO("break 2 with one loop is still an error"); + CHECK(messages(r) == std::string("E 0: break outside a loop\n")); r = ALPreprocessor::run("for (;;) { while (a) { if (b) break 2; } @in; }\n@after;\n", o); - ensure_equals("each loop's own: break 2 to the outer's following label, the inner's ending label unused", r.text, + UNSCOPED_INFO("each loop's own: break 2 to the outer's following label, the inner's ending label unused"); + CHECK(r.text == std::string("for (;;) { while (a) { if (b) jump after; } @in; }\n@after;\n")); r = ALPreprocessor::run("while (a) { break; } x = 1; @late;\n", o); - ensure_equals("a label that is not right after the loop is not the loop's", r.text, std::string("while (a) { jump _brk1; }@_brk1; x = 1; @late;\n")); + UNSCOPED_INFO("a label that is not right after the loop is not the loop's"); + CHECK(r.text == std::string("while (a) { jump _brk1; }@_brk1; x = 1; @late;\n")); } - template<> template<> - void alpreprocessor_object::test<16>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a preprocessor problem carries a key and its words for translation", "[alpreprocessor]") { - set_test_name("a problem of the preprocessor's own carries a key and its words, for a translation to be built the same way"); ALPreprocessor::Options o = options(); o.extensions = true; ALPreprocessor::Result r = ALPreprocessor::run("continue;\n#define f(a, a) a\n", o); // The directives go first, the extensions after: the macro's // problem is said before the loop's. - ensure_equals("two problems", r.problems.size(), size_t(2)); - ensure_equals("the words", r.problems[1].message, std::string("continue outside a loop")); - ensure_equals("the key", r.problems[1].key, std::string("PreprocOutsideLoop")); - ensure("the word it was built with", r.problems[1].args.size() == 1 && r.problems[1].args[0] == "continue"); - ensure_equals("the macro's key", r.problems[0].key, std::string("PreprocDuplicateParameter")); - ensure("and its word", r.problems[0].args.size() == 1 && r.problems[0].args[0] == "a"); - ensure_equals("a translation is the text with the words put in", ALScriptProblem::fill("[1] hors d'une boucle ([1], [2])", { "continue", "x" }), std::string("continue hors d'une boucle (continue, x)")); - ensure_equals("a word with a mark in it is not read again", ALScriptProblem::fill("[1]", { "[2]" }), std::string("[2]")); + UNSCOPED_INFO("two problems"); + REQUIRE(r.problems.size() == size_t(2)); + UNSCOPED_INFO("the words"); + CHECK(r.problems[1].message == std::string("continue outside a loop")); + UNSCOPED_INFO("the key"); + CHECK(r.problems[1].key == std::string("PreprocOutsideLoop")); + { + INFO("the word it was built with"); + REQUIRE(r.problems[1].args.size() == 1); + CHECK(r.problems[1].args[0] == "continue"); + } + UNSCOPED_INFO("the macro's key"); + CHECK(r.problems[0].key == std::string("PreprocDuplicateParameter")); + { + INFO("and its word"); + REQUIRE(r.problems[0].args.size() == 1); + CHECK(r.problems[0].args[0] == "a"); + } + UNSCOPED_INFO("a translation is the text with the words put in"); + CHECK(ALScriptProblem::fill("[1] hors d'une boucle ([1], [2])", { "continue", "x" }) == std::string("continue hors d'une boucle (continue, x)")); + UNSCOPED_INFO("a word with a mark in it is not read again"); + CHECK(ALScriptProblem::fill("[1]", { "[2]" }) == std::string("[2]")); } - template<> template<> - void alpreprocessor_object::test<17>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a run is bounded: doubling macros stopped; deep expressions refused", "[alpreprocessor]") { - set_test_name("a run is bounded: macros that double are stopped, a deep expression is refused, and a deep include is"); // Hide sets stop a macro expanding as itself, not one that // doubles: this is 2^24 tokens and would otherwise eat the // machine, on the thread that opened the script. @@ -823,14 +962,23 @@ namespace tut ALPreprocessor::Options o = options(); o.tokenBudget = 100000; ALPreprocessor::Result r = ALPreprocessor::run(bomb, o); - ensure("the run said it ran away", r.overran); - ensure("and gave the source back as it was", r.text == bomb); - ensure("with the reason", !r.problems.empty() && r.problems.back().key == std::string("PreprocTooMuch")); - ensure("an error", r.problems.back().severity == ALScriptProblem::Severity::Error); + UNSCOPED_INFO("the run said it ran away"); + CHECK(r.overran); + UNSCOPED_INFO("and gave the source back as it was"); + CHECK(r.text == bomb); + { + INFO("with the reason"); + REQUIRE_FALSE(r.problems.empty()); + CHECK(r.problems.back().key == std::string("PreprocTooMuch")); + } + UNSCOPED_INFO("an error"); + CHECK(r.problems.back().severity == ALScriptProblem::Severity::Error); // A run within the budget is untouched by it. ALPreprocessor::Result fine = ALPreprocessor::run("#define A 1\ninteger n = A;\n", o); - ensure("nothing said", !fine.overran); - ensure("and the macro put in", fine.text.find("integer n = 1;") != std::string::npos); + UNSCOPED_INFO("nothing said"); + CHECK_FALSE(fine.overran); + UNSCOPED_INFO("and the macro put in"); + REQUIRE(fine.text.find("integer n = 1;") != std::string::npos); // An expression nested past the depth is refused rather than // taking the C++ stack down with it. std::string deep = "#if "; @@ -847,18 +995,20 @@ namespace tut ALPreprocessor::Options d = options(); d.expressionDepth = 16; ALPreprocessor::Result nested = ALPreprocessor::run(deep, d); - ensure("said", !nested.problems.empty()); - ensure_equals("with its key", nested.problems.front().key, std::string("PreprocExpressionTooDeep")); + UNSCOPED_INFO("said"); + REQUIRE_FALSE(nested.problems.empty()); + UNSCOPED_INFO("with its key"); + CHECK(nested.problems.front().key == std::string("PreprocExpressionTooDeep")); // And one within the depth is answered. ALPreprocessor::Result shallow = ALPreprocessor::run("#if ((((1))))\ninteger n;\n#endif\n", d); - ensure("nothing said", shallow.problems.empty()); - ensure("the group taken", shallow.text.find("integer n;") != std::string::npos); + UNSCOPED_INFO("nothing said"); + REQUIRE(shallow.problems.empty()); + UNSCOPED_INFO("the group taken"); + REQUIRE(shallow.text.find("integer n;") != std::string::npos); } - template<> template<> - void alpreprocessor_object::test<18>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: an include that includes itself stops at the depth and is listed once", "[alpreprocessor]") { - set_test_name("an include that includes itself is stopped by the depth, and each include is listed once"); add("a.lsl", "#include \"b.lsl\"\n"); add("b.lsl", "#include \"a.lsl\"\n"); ALPreprocessor::Options o = options(); @@ -869,20 +1019,23 @@ namespace tut { deep = deep || p.key == "PreprocIncludeTooDeep"; } - ensure("said so", deep); - ensure_equals("each listed once", r.includes.size(), size_t(2)); + UNSCOPED_INFO("said so"); + CHECK(deep); + UNSCOPED_INFO("each listed once"); + REQUIRE(r.includes.size() == size_t(2)); // With `#pragma once` it settles rather than going deep at all. files.clear(); add("c.lsl", "#pragma once\ninteger c;\n"); ALPreprocessor::Result twice = ALPreprocessor::run("#include \"c.lsl\"\n#include \"c.lsl\"\n", o); - ensure("nothing said", twice.problems.empty()); - ensure_equals("listed once", twice.includes.size(), size_t(1)); - ensure_equals("and put in once", twice.text.find("integer c;"), twice.text.rfind("integer c;")); + UNSCOPED_INFO("nothing said"); + REQUIRE(twice.problems.empty()); + UNSCOPED_INFO("listed once"); + REQUIRE(twice.includes.size() == size_t(1)); + UNSCOPED_INFO("and put in once"); + CHECK(twice.text.find("integer c;") == twice.text.rfind("integer c;")); } - template<> template<> - void alpreprocessor_object::test<19>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: finish after an expansion alone gives what one whole run gives", "[alpreprocessor]") { - set_test_name("what a run does after the expansion it will do on its own, and to the same end"); // A caller that expands in rounds, waiting on includes from the // world, leaves the compression off until its last round and // calls `finish` itself. What it gets must be what one run @@ -896,28 +1049,31 @@ namespace tut ALPreprocessor::Options expanding = options(); expanding.compress = false; ALPreprocessor::Result in_two = ALPreprocessor::run(source, expanding); - ensure("the expansion alone leaves it as it was", in_two.text.find("// comment") != std::string::npos); + UNSCOPED_INFO("the expansion alone leaves it as it was"); + REQUIRE(in_two.text.find("// comment") != std::string::npos); ALPreprocessor::finish(in_two, whole); - ensure_equals("the same text", in_two.text, at_once.text); - ensure_equals("and the same way back", in_two.map.toSource(0, 0).line, at_once.map.toSource(0, 0).line); + UNSCOPED_INFO("the same text"); + CHECK(in_two.text == at_once.text); + UNSCOPED_INFO("and the same way back"); + CHECK(in_two.map.toSource(0, 0).line == at_once.map.toSource(0, 0).line); } - template<> template<> - void alpreprocessor_object::test<20>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: the scripter's defines reach every script and #undef has the last word", "[alpreprocessor]") { - set_test_name("a scripter's own macros are defined for every script, and a script's #undef has the last word"); ALPreprocessor::Options opts = options(); opts.defines = { "DEBUG", "LEVEL=3", " SPACED = 7 ", "2BAD", "" }; ALPreprocessor::Result r = ALPreprocessor::run("#if DEBUG\nx = LEVEL;\n#endif\ny = SPACED;\n#undef LEVEL\nz = LEVEL;\n", opts); - ensure_equals("no problems", messages(r), std::string()); - ensure("DEBUG is 1, so the block is in: " + r.text, r.text.find("x = 3;") != std::string::npos); - ensure("a value around blanks: " + r.text, r.text.find("y = 7;") != std::string::npos); - ensure("undefined by the script: " + r.text, r.text.find("z = LEVEL;") != std::string::npos); + UNSCOPED_INFO("no problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("DEBUG is 1, so the block is in: " + r.text); + REQUIRE(r.text.find("x = 3;") != std::string::npos); + UNSCOPED_INFO("a value around blanks: " + r.text); + REQUIRE(r.text.find("y = 7;") != std::string::npos); + UNSCOPED_INFO("undefined by the script: " + r.text); + REQUIRE(r.text.find("z = LEVEL;") != std::string::npos); } - template<> template<> - void alpreprocessor_object::test<21>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a run is bounded through chained ?: and nested calls and self includes", "[alpreprocessor]") { - set_test_name("a run is bounded however the script nests: chained ?:, macros in macros' arguments, files that include themselves"); // A chain of ?: is a level each, as a bracket is: said, and not // followed down the machine's stack. std::string chain = "#if "; @@ -932,7 +1088,8 @@ namespace tut } chain += "\nyes\n#endif\n"; ALPreprocessor::Result r = ALPreprocessor::run(chain, options()); - ensure("the chain is too deep: " + messages(r).substr(0, 200), messages(r).find("nests too deeply") != std::string::npos); + UNSCOPED_INFO("the chain is too deep: " + messages(r).substr(0, 200)); + REQUIRE(messages(r).find("nests too deeply") != std::string::npos); // A macro invoked in its own argument, deeper than the bound. std::string nested = "#define F(x) x\n"; @@ -947,13 +1104,20 @@ namespace tut } nested += ";\n"; r = ALPreprocessor::run(nested, options()); - ensure("overran", r.overran); - ensure("and said why: " + messages(r).substr(0, 200), messages(r).find("more deeply than this preprocessor follows") != std::string::npos); - ensure_equals("the text is the source as it was", r.text, nested); + UNSCOPED_INFO("overran"); + CHECK(r.overran); + UNSCOPED_INFO("and said why: " + messages(r).substr(0, 200)); + REQUIRE(messages(r).find("more deeply than this preprocessor follows") != std::string::npos); + UNSCOPED_INFO("the text is the source as it was"); + CHECK(r.text == nested); // Within the bound, as deep as a person writes, it expands. r = ALPreprocessor::run("#define F(x) x\nF(F(F(F(F(1)))));\n", options()); - ensure("five deep is nothing: " + messages(r), !r.overran && r.text.find("1;") != std::string::npos); + { + INFO("five deep is nothing: " + messages(r)); + CHECK_FALSE(r.overran); + REQUIRE(r.text.find("1;") != std::string::npos); + } // A file that includes itself twice: each level doubles, and the // files it opens are counted against the budget like any tokens. @@ -961,17 +1125,17 @@ namespace tut ALPreprocessor::Options opts = options(); opts.tokenBudget = 200000; r = ALPreprocessor::run("#include \"twice.lsl\"\n", opts); - ensure("overran, rather than opening two to the thirty-second files", r.overran); + UNSCOPED_INFO("overran, rather than opening two to the thirty-second files"); + CHECK(r.overran); } - template<> template<> - void alpreprocessor_object::test<22>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a name in a string literal is escaped; no module path reaches a save", "[alpreprocessor]") { - set_test_name("a name put in a string literal is escaped; a module's path is put nowhere in what a save sends"); ALPreprocessor::Options opts = options(); opts.fileName = "Say \"hi\" \\ there"; ALPreprocessor::Result r = ALPreprocessor::run("llSay(0, __FILE__);\nllSay(0, __SHORTFILE__);\n", opts); - ensure("the quote and the backslash escaped: " + r.text, r.text.find("\"Say \\\"hi\\\" \\\\ there\"") != std::string::npos); + UNSCOPED_INFO("the quote and the backslash escaped: " + r.text); + REQUIRE(r.text.find("\"Say \\\"hi\\\" \\\\ there\"") != std::string::npos); // A module found on a Windows disk: required by its number, and // the folders it is in -- the scripter's own -- nowhere in the text. @@ -981,15 +1145,19 @@ namespace tut module.path = "disk:C:\\Users\\me\\lib\\util.luau"; files["./util"] = module; r = ALPreprocessor::run("local util = require(\"./util\")\n", options(true)); - ensure_equals("nothing wrong", faults(r), std::string()); - ensure("required by its number: " + r.text, r.text.find("local util = require(1)") != std::string::npos); - ensure("no path: " + r.text, r.text.find("disk:") == std::string::npos && r.text.find("Users") == std::string::npos); + UNSCOPED_INFO("nothing wrong"); + CHECK(faults(r) == std::string()); + UNSCOPED_INFO("required by its number: " + r.text); + REQUIRE(r.text.find("local util = require(1)") != std::string::npos); + { + INFO("no path: " + r.text); + REQUIRE(r.text.find("disk:") == std::string::npos); + REQUIRE(r.text.find("Users") == std::string::npos); + } } - template<> template<> - void alpreprocessor_object::test<23>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: an interpolated SLua string with a brace or backtick ends where it ends", "[alpreprocessor]") { - set_test_name("an interpolated SLua string whose expression holds a string with a brace or a backtick ends where it ends"); const std::vector tokens = ALPreprocessor::tokenize("local s = `a{ \"{\" .. `{'}'}` }b` .. X\n", true); std::vector kinds; for (const ALPreprocessor::Token& t : tokens) @@ -999,18 +1167,22 @@ namespace tut kinds.push_back(t.text); } } - ensure("the string whole, then what follows it: " + std::to_string(kinds.size()), - kinds.size() >= 6 && kinds[3] == "`a{ \"{\" .. `{'}'}` }b`" && kinds[4] == ".." && kinds[5] == "X"); + { + INFO("the string whole, then what follows it: " + std::to_string(kinds.size())); + REQUIRE(kinds.size() >= 6); + CHECK(kinds[3] == "`a{ \"{\" .. `{'}'}` }b`"); + CHECK(kinds[4] == ".."); + CHECK(kinds[5] == "X"); + } // And the macro after it is expanded. ALPreprocessor::Result r = ALPreprocessor::run("--#define X 42\nlocal s = `{\"{\"}` .. X\n", options(true)); - ensure("the macro after the string expanded: " + r.text, r.text.find(".. 42") != std::string::npos); + UNSCOPED_INFO("the macro after the string expanded: " + r.text); + REQUIRE(r.text.find(".. 42") != std::string::npos); } - template<> template<> - void alpreprocessor_object::test<24>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: switch and loop transforms bounded; a long else-if chain no deeper", "[alpreprocessor]") { - set_test_name("the switch and loop transforms are bounded however the script nests, and a long run of else-ifs is no deeper for its length"); ALPreprocessor::Options opts = options(); opts.switches = true; opts.extensions = true; @@ -1028,9 +1200,12 @@ namespace tut } blocks += "\n} }\n"; ALPreprocessor::Result r = ALPreprocessor::run(blocks, opts); - ensure("overran", r.overran); - ensure("and said why: " + messages(r).substr(0, 200), messages(r).find("blocks nest more deeply") != std::string::npos); - ensure_equals("the text is the source as it was", r.text, blocks); + UNSCOPED_INFO("overran"); + CHECK(r.overran); + UNSCOPED_INFO("and said why: " + messages(r).substr(0, 200)); + REQUIRE(messages(r).find("blocks nest more deeply") != std::string::npos); + UNSCOPED_INFO("the text is the source as it was"); + CHECK(r.text == blocks); // Switches in switches, likewise. std::string switches = "default { state_entry() { integer x;\n"; @@ -1044,8 +1219,10 @@ namespace tut } switches += "\n} }\n"; r = ALPreprocessor::run(switches, opts); - ensure("switches overran", r.overran); - ensure("and said why: " + messages(r).substr(0, 200), messages(r).find("blocks nest more deeply") != std::string::npos); + UNSCOPED_INFO("switches overran"); + CHECK(r.overran); + UNSCOPED_INFO("and said why: " + messages(r).substr(0, 200)); + REQUIRE(messages(r).find("blocks nest more deeply") != std::string::npos); // As deep as anybody writes, done. std::string loops = "default { state_entry() { integer i;\n"; @@ -1060,8 +1237,13 @@ namespace tut } loops += "\n} }\n"; r = ALPreprocessor::run(loops, opts); - ensure("thirty deep is nothing: " + messages(r).substr(0, 200), !r.overran && !r.hasErrors()); - ensure("and the loops were lowered: " + r.text.substr(0, 200), r.text.find("jump _brk") != std::string::npos); + { + INFO("thirty deep is nothing: " + messages(r).substr(0, 200)); + CHECK_FALSE(r.overran); + CHECK_FALSE(r.hasErrors()); + } + UNSCOPED_INFO("and the loops were lowered: " + r.text.substr(0, 200)); + REQUIRE(r.text.find("jump _brk") != std::string::npos); // A loop governing a long chain of else-ifs, on a thread with what // the platform gives one -- half a megabyte on a Mac: the chain is @@ -1074,42 +1256,67 @@ namespace tut chain += "} }\n"; std::thread worker([&]() { r = ALPreprocessor::run(chain, opts); }); worker.join(); - ensure("the chain is no deeper for its length: " + messages(r).substr(0, 200), !r.overran && !r.hasErrors()); - ensure("and all of it is there", r.text.find("else if (i == 19999) i += 1;") != std::string::npos); + { + INFO("the chain is no deeper for its length: " + messages(r).substr(0, 200)); + CHECK_FALSE(r.overran); + CHECK_FALSE(r.hasErrors()); + } + UNSCOPED_INFO("and all of it is there"); + REQUIRE(r.text.find("else if (i == 19999) i += 1;") != std::string::npos); } - template<> template<> - void alpreprocessor_object::test<25>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: each line of a multiline comment or long string maps back to its own", "[alpreprocessor]") { - set_test_name("every line of a token over several lines maps back to its own: a block comment, a long string"); const std::string source = "#define X 1\n/* one\n two\n three */ integer x = X;\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("nothing wrong", messages(r), std::string()); - ensure("the comment on the lines it was on: " + r.text, r.text.find("\n/* one\n two\n three */ integer x = 1;") != std::string::npos); + UNSCOPED_INFO("nothing wrong"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("the comment on the lines it was on: " + r.text); + REQUIRE(r.text.find("\n/* one\n two\n three */ integer x = 1;") != std::string::npos); ALSourceMap::Loc loc = r.map.toSource(2, 4); - ensure("the comment's middle line maps", loc.found()); - ensure_equals("to its own line", loc.line, 2); - ensure_equals("and column", loc.column, 4); + UNSCOPED_INFO("the comment's middle line maps"); + REQUIRE(loc.found()); + UNSCOPED_INFO("to its own line"); + CHECK(loc.line == 2); + UNSCOPED_INFO("and column"); + CHECK(loc.column == 4); loc = r.map.toSource(3, 4); - ensure_equals("its last line, before what follows it on the line", loc.line, 3); - ensure_equals("at its own column", loc.column, 4); + UNSCOPED_INFO("its last line, before what follows it on the line"); + CHECK(loc.line == 3); + UNSCOPED_INFO("at its own column"); + CHECK(loc.column == 4); loc = r.map.toSource(3, 12); - ensure_equals("and what follows it where it is", loc.column, 12); + UNSCOPED_INFO("and what follows it where it is"); + CHECK(loc.column == 12); loc = r.map.toExpanded(0, 2, 4); - ensure("and back", loc.found() && loc.line == 2 && loc.column == 4); + { + INFO("and back"); + REQUIRE(loc.found()); + CHECK(loc.line == 2); + CHECK(loc.column == 4); + } const std::string lua = "--#define X 2\nlocal s = [[one\ntwo\nthree]] .. X\n"; r = ALPreprocessor::run(lua, options(true)); - ensure_equals("nothing wrong in SLua", messages(r), std::string()); + UNSCOPED_INFO("nothing wrong in SLua"); + CHECK(messages(r) == std::string()); loc = r.map.toSource(2, 1); - ensure("a long string's middle line maps to its own", loc.found() && loc.line == 2 && loc.column == 1); + { + INFO("a long string's middle line maps to its own"); + REQUIRE(loc.found()); + CHECK(loc.line == 2); + CHECK(loc.column == 1); + } loc = r.map.toSource(3, 11); - ensure("and the macro after it to where it was invoked", loc.found() && loc.line == 3 && loc.column == 11); + { + INFO("and the macro after it to where it was invoked"); + REQUIRE(loc.found()); + CHECK(loc.line == 3); + CHECK(loc.column == 11); + } } - template<> template<> - void alpreprocessor_object::test<26>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: transformAt reads a line's transform; a keyword as a name is none", "[alpreprocessor]") { - set_test_name("a line is read as the transform it is written for, and a name that is one of the words is no transform"); using T = ALPreprocessor::Transform; const auto of = [](std::vector lines, S32 at, std::string* said = nullptr) { std::string word; @@ -1121,49 +1328,81 @@ namespace tut return t; }; std::string word; - ensure("switch", of({ " switch (x)" }, 0, &word) == T::Switch); - ensure_equals("its word", word, std::string("switch")); - ensure("switch not called", of({ "switch = 2;" }, 0) == T::None); - ensure("a case", of({ " case 1:" }, 0, &word) == T::Switch); - ensure_equals("the case's word", word, std::string("case")); - ensure("case assigned", of({ "case = 1;" }, 0) == T::None); - ensure("case called", of({ "case(1);" }, 0) == T::None); - ensure("case with no colon", of({ "case 1" }, 0) == T::None); - ensure("break", of({ "break;" }, 0) == T::Extensions); - ensure("break out of two", of({ "break 2;" }, 0) == T::Extensions); - ensure("continue", of({ "\tcontinue;" }, 0) == T::Extensions); - ensure("break assigned", of({ "break = 3;" }, 0) == T::None); - ensure("an inline function", of({ "inline f(integer a)" }, 0, &word) == T::Extensions); - ensure_equals("inline's word", word, std::string("inline")); - ensure("an inline function with its type", of({ "inline integer f(integer a)" }, 0) == T::Extensions); - ensure("inline after the parameters", of({ "integer f(integer a) inline {" }, 0, &word) == T::Extensions); - ensure_equals("its word", word, std::string("inline")); - ensure("inline after them, the brace on the next line", of({ "f() inline" }, 0) == T::Extensions); - ensure("a name that ends in inline is not", of({ "integer f(integer a) noinline {" }, 0) == T::None); - ensure("inline assigned", of({ "inline = 2;" }, 0) == T::None); - ensure("const before a type", of({ "const integer X = 1;" }, 0, &word) == T::Extensions); - ensure_equals("const's word", word, std::string("const")); - ensure("a const parameter", of({ "f(integer a, const vector v)" }, 0) == T::Extensions); - ensure("a variable named const", of({ "integer const = 1;" }, 0) == T::None); - ensure("a word that starts with const", of({ "constant = 1;" }, 0) == T::None); - ensure("after a brace and a statement's end", of({ "} ; switch (x)" }, 0) == T::Switch); - ensure("a plain statement", of({ "integer x = 1;" }, 0) == T::None); - ensure("a blank line", of({ " " }, 0) == T::None); + UNSCOPED_INFO("switch"); + CHECK(of({ " switch (x)" }, 0, &word) == T::Switch); + UNSCOPED_INFO("its word"); + CHECK(word == std::string("switch")); + UNSCOPED_INFO("switch not called"); + CHECK(of({ "switch = 2;" }, 0) == T::None); + UNSCOPED_INFO("a case"); + CHECK(of({ " case 1:" }, 0, &word) == T::Switch); + UNSCOPED_INFO("the case's word"); + CHECK(word == std::string("case")); + UNSCOPED_INFO("case assigned"); + CHECK(of({ "case = 1;" }, 0) == T::None); + UNSCOPED_INFO("case called"); + CHECK(of({ "case(1);" }, 0) == T::None); + UNSCOPED_INFO("case with no colon"); + CHECK(of({ "case 1" }, 0) == T::None); + UNSCOPED_INFO("break"); + CHECK(of({ "break;" }, 0) == T::Extensions); + UNSCOPED_INFO("break out of two"); + CHECK(of({ "break 2;" }, 0) == T::Extensions); + UNSCOPED_INFO("continue"); + CHECK(of({ "\tcontinue;" }, 0) == T::Extensions); + UNSCOPED_INFO("break assigned"); + CHECK(of({ "break = 3;" }, 0) == T::None); + UNSCOPED_INFO("an inline function"); + CHECK(of({ "inline f(integer a)" }, 0, &word) == T::Extensions); + UNSCOPED_INFO("inline's word"); + CHECK(word == std::string("inline")); + UNSCOPED_INFO("an inline function with its type"); + CHECK(of({ "inline integer f(integer a)" }, 0) == T::Extensions); + UNSCOPED_INFO("inline after the parameters"); + CHECK(of({ "integer f(integer a) inline {" }, 0, &word) == T::Extensions); + UNSCOPED_INFO("its word"); + CHECK(word == std::string("inline")); + UNSCOPED_INFO("inline after them, the brace on the next line"); + CHECK(of({ "f() inline" }, 0) == T::Extensions); + UNSCOPED_INFO("a name that ends in inline is not"); + CHECK(of({ "integer f(integer a) noinline {" }, 0) == T::None); + UNSCOPED_INFO("inline assigned"); + CHECK(of({ "inline = 2;" }, 0) == T::None); + UNSCOPED_INFO("const before a type"); + CHECK(of({ "const integer X = 1;" }, 0, &word) == T::Extensions); + UNSCOPED_INFO("const's word"); + CHECK(word == std::string("const")); + UNSCOPED_INFO("a const parameter"); + CHECK(of({ "f(integer a, const vector v)" }, 0) == T::Extensions); + UNSCOPED_INFO("a variable named const"); + CHECK(of({ "integer const = 1;" }, 0) == T::None); + UNSCOPED_INFO("a word that starts with const"); + CHECK(of({ "constant = 1;" }, 0) == T::None); + UNSCOPED_INFO("after a brace and a statement's end"); + CHECK(of({ "} ; switch (x)" }, 0) == T::Switch); + UNSCOPED_INFO("a plain statement"); + CHECK(of({ "integer x = 1;" }, 0) == T::None); + UNSCOPED_INFO("a blank line"); + CHECK(of({ " " }, 0) == T::None); // A brace of its own is the switch's where the line before opens one. - ensure("the switch's brace", of({ "switch (x)", "", " {" }, 2) == T::Switch); - ensure("a brace after anything else", of({ "if (x)", "{" }, 1) == T::None); - ensure("a brace after a break is not the switch's", of({ "break;", "{" }, 1) == T::None); - ensure("a brace with nothing before it", of({ "", "{" }, 1) == T::None); - ensure("a line past the end", of({ "switch (x)" }, 1) == T::None); + UNSCOPED_INFO("the switch's brace"); + CHECK(of({ "switch (x)", "", " {" }, 2) == T::Switch); + UNSCOPED_INFO("a brace after anything else"); + CHECK(of({ "if (x)", "{" }, 1) == T::None); + UNSCOPED_INFO("a brace after a break is not the switch's"); + CHECK(of({ "break;", "{" }, 1) == T::None); + UNSCOPED_INFO("a brace with nothing before it"); + CHECK(of({ "", "{" }, 1) == T::None); + UNSCOPED_INFO("a line past the end"); + CHECK(of({ "switch (x)" }, 1) == T::None); } - template<> template<> - void alpreprocessor_object::test<27>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: verbatimSpan maps copied source back whole but not a macro's output", "[alpreprocessor]") { - set_test_name("a stretch of the output copied from the source maps back whole, and one a macro made does not"); const ALPreprocessor::Result r = ALPreprocessor::run("#define GREET llOwnerSay(\"hi\")\nGREET;\n llSay(0, \"x\");\n", options()); - ensure("no problems: " + messages(r), r.problems.empty()); + UNSCOPED_INFO("no problems: " + messages(r)); + REQUIRE(r.problems.empty()); // Where each call landed in the output. S32 greet_line = -1, greet_column = -1, say_line = -1, say_column = -1; std::istringstream in(r.text); @@ -1181,23 +1420,37 @@ namespace tut say_column = static_cast(at); } } - ensure("both in the output: " + r.text, greet_line >= 0 && say_line >= 0); + { + INFO("both in the output: " + r.text); + CHECK(greet_line >= 0); + CHECK(say_line >= 0); + } ALSourceMap::Loc begin, end; - ensure("llSay is the source's own", r.map.verbatimSpan(say_line, say_column, say_column + 5, begin, end)); - ensure_equals("from its line", begin.line, 2); - ensure_equals("its column", begin.column, 4); - ensure_equals("to its end", end.column, 9); - ensure_equals("in the script", begin.file, 0); - ensure("an insertion after it too", r.map.verbatimSpan(say_line, say_column + 5, say_column + 5, begin, end) && begin.column == 9); - ensure("a stretch of several tokens, the blanks between them the source's", r.map.verbatimSpan(say_line, say_column, say_column + 13, begin, end)); - ensure_equals("to the call's end", end.column, 17); - ensure("what the macro made is not", !r.map.verbatimSpan(greet_line, greet_column, greet_column + 10, begin, end)); + UNSCOPED_INFO("llSay is the source's own"); + REQUIRE(r.map.verbatimSpan(say_line, say_column, say_column + 5, begin, end)); + UNSCOPED_INFO("from its line"); + CHECK(begin.line == 2); + UNSCOPED_INFO("its column"); + CHECK(begin.column == 4); + UNSCOPED_INFO("to its end"); + CHECK(end.column == 9); + UNSCOPED_INFO("in the script"); + CHECK(begin.file == 0); + { + INFO("an insertion after it too"); + CHECK(r.map.verbatimSpan(say_line, say_column + 5, say_column + 5, begin, end)); + CHECK(begin.column == 9); + } + UNSCOPED_INFO("a stretch of several tokens, the blanks between them the source's"); + CHECK(r.map.verbatimSpan(say_line, say_column, say_column + 13, begin, end)); + UNSCOPED_INFO("to the call's end"); + CHECK(end.column == 17); + UNSCOPED_INFO("what the macro made is not"); + CHECK_FALSE(r.map.verbatimSpan(greet_line, greet_column, greet_column + 10, begin, end)); } - template<> template<> - void alpreprocessor_object::test<28>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a weighed optimizer run says what it saved in all and per note", "[alpreprocessor]") { - set_test_name("the optimizer's run, weighed, says what it saved in code: the whole, and each note what its lines came to less"); ensureGridBuiltins(); const std::string source = "integer unused(integer n)\n" "{\n" @@ -1215,8 +1468,13 @@ namespace tut o.optimizer.target = ALLSLOptimizer::Target::LSO; o.weigh = true; const ALPreprocessor::Result r = ALPreprocessor::run(source, o); - ensure("optimized", r.optimized); - ensure("weighed, and lighter: " + std::to_string(r.codeBefore) + " to " + std::to_string(r.codeAfter), r.codeAfter > 0 && r.codeAfter < r.codeBefore); + UNSCOPED_INFO("optimized"); + CHECK(r.optimized); + { + INFO("weighed, and lighter: " + std::to_string(r.codeBefore) + " to " + std::to_string(r.codeAfter)); + CHECK(r.codeAfter > 0); + CHECK(r.codeAfter < r.codeBefore); + } const ALScriptProblem* removed = nullptr; const ALScriptProblem* folded = nullptr; const ALScriptProblem* sizes = nullptr; @@ -1226,14 +1484,31 @@ namespace tut folded = p.line == 8 && p.source == ALScriptProblem::Source::Optimizer && p.key != "OptimizerRemovedFunction" ? &p : folded; sizes = p.key == "OptimizerSizesWeighed" ? &p : sizes; } - ensure("the function's removal noted", removed && removed->savedBytes.has_value()); - ensure("its lines' code all saved: " + std::to_string(removed->savedBytes.value_or(0)), *removed->savedBytes > 0); - ensure("the fold noted, with what its line saved", folded && folded->savedBytes && *folded->savedBytes > 0); - ensure("the whole said", sizes != nullptr); - ensure_equals("in code", sizes->args[2], std::to_string(r.codeBefore)); - ensure_equals("on its target", sizes->args[4], std::string("LSL (LSO)")); + { + INFO("the function's removal noted"); + REQUIRE(removed); + REQUIRE(removed->savedBytes.has_value()); + } + UNSCOPED_INFO("its lines' code all saved: " + std::to_string(removed->savedBytes.value_or(0))); + CHECK(*removed->savedBytes > 0); + { + INFO("the fold noted, with what its line saved"); + REQUIRE(folded); + REQUIRE(folded->savedBytes); + CHECK(*folded->savedBytes > 0); + } + UNSCOPED_INFO("the whole said"); + REQUIRE(sizes != nullptr); + UNSCOPED_INFO("in code"); + CHECK(sizes->args[2] == std::to_string(r.codeBefore)); + UNSCOPED_INFO("on its target"); + CHECK(sizes->args[4] == std::string("LSL (LSO)")); const S64 lost = S64(r.codeBefore) - S64(r.codeAfter); - ensure("no note says more than the whole lost", *removed->savedBytes <= lost && *folded->savedBytes <= lost); + { + INFO("no note says more than the whole lost"); + REQUIRE(*removed->savedBytes <= lost); + CHECK(*folded->savedBytes <= lost); + } // Not weighed unless asked: no bytes, and the words as they were. o.weigh = false; @@ -1241,16 +1516,19 @@ namespace tut bool said = false; for (const ALScriptProblem& p : bare.problems) { - ensure("nothing weighed", !p.savedBytes); + UNSCOPED_INFO("nothing weighed"); + REQUIRE_FALSE(p.savedBytes); said = said || p.key == "OptimizerSizes"; } - ensure("the size in source only", said && bare.codeBefore == 0); + { + INFO("the size in source only"); + CHECK(said); + CHECK(bare.codeBefore == 0); + } } - template<> template<> - void alpreprocessor_object::test<29>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a script that does not compile: the optimizer stands aside with a note", "[alpreprocessor]") { - set_test_name("a script that does not compile is not the preprocessor's error: the optimizer stands aside with a note where the first error is"); ensureGridBuiltins(); // Nexii's library, whose ObjectLinksetSittingAvatars has a parameter // without its type, included as a save would include it. @@ -1263,35 +1541,42 @@ namespace tut o.optimize = true; o.weigh = true; ALPreprocessor::Result r = ALPreprocessor::run(source, o); - ensure("no error of the preprocessor's: " + messages(r), !r.hasErrors()); - ensure_equals("one note, on the library's line", messages(r), + UNSCOPED_INFO("no error of the preprocessor's: " + messages(r)); + CHECK_FALSE(r.hasErrors()); + UNSCOPED_INFO("one note, on the library's line"); + CHECK(messages(r) == std::string("N linkset.lsl:110: not optimized: the script does not compile as it stands, and compiling it says why\n")); - ensure("keyed", r.problems.size() == 1 && r.problems[0].key == "OptimizerUncompiled" && r.problems[0].column == 33); - ensure("not optimized", !r.optimized); + { + INFO("keyed"); + REQUIRE(r.problems.size() == 1); + CHECK(r.problems[0].key == "OptimizerUncompiled"); + CHECK(r.problems[0].column == 33); + } + UNSCOPED_INFO("not optimized"); + CHECK_FALSE(r.optimized); ALPreprocessor::Options plain = options(); - ensure_equals("the text as expanded, for the compiler to read", r.text, ALPreprocessor::run(source, plain).text); + UNSCOPED_INFO("the text as expanded, for the compiler to read"); + CHECK(r.text == ALPreprocessor::run(source, plain).text); } - template<> template<> - void alpreprocessor_object::test<30>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: SLua: only the global require finds a module and not a field or method", "[alpreprocessor]") { - set_test_name("SLua: a field or a method named require is the table's, and only the global finds a module"); add("x", "return 1\n"); const ALPreprocessor::Result r = ALPreprocessor::run("local a = t.require(\"x\")\n" "local b = t : require(\"x\")\n" "local c = t .. require(\"x\")\n", options(true)); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("only the concatenated call is a module's", r.text, + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("only the concatenated call is a module's"); + CHECK(r.text == bundleOf({ "return 1\n" }, "local a = t.require(\"x\")\n" "local b = t : require(\"x\")\n" "local c = t .. require(1)\n")); } - template<> template<> - void alpreprocessor_object::test<31>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: SLua: requiresIn finds just the calls a run puts a module in place of", "[alpreprocessor]") { - set_test_name("SLua: the calls a run puts a module in place of are found where they stand, and only those"); const std::string source = "local a = require(\"a\")\n" // 0 "local b = require 'b'\n" // 1: no parentheses "local c = require( -- the next\n" @@ -1304,13 +1589,33 @@ namespace tut "local h = require([[h]])\n" "local i = x .. require(\"i\")\n"; // 10 const std::vector found = ALPreprocessor::requiresIn(source); - ensure_equals("three", found.size(), size_t(3)); - ensure_equals("a", found[0].name, std::string("a")); - ensure("a's stretch", found[0].line == 0 && found[0].column == 10 && found[0].endLine == 0 && found[0].endColumn == 22); - ensure_equals("c", found[1].name, std::string("c")); - ensure("c's stretch, across lines", found[1].line == 2 && found[1].column == 10 && found[1].endLine == 4 && found[1].endColumn == 1); - ensure_equals("i", found[2].name, std::string("i")); - ensure("i's line", found[2].line == 10 && found[2].column == 15); + UNSCOPED_INFO("three"); + REQUIRE(found.size() == size_t(3)); + UNSCOPED_INFO("a"); + CHECK(found[0].name == std::string("a")); + { + INFO("a's stretch"); + CHECK(found[0].line == 0); + CHECK(found[0].column == 10); + CHECK(found[0].endLine == 0); + CHECK(found[0].endColumn == 22); + } + UNSCOPED_INFO("c"); + CHECK(found[1].name == std::string("c")); + { + INFO("c's stretch, across lines"); + CHECK(found[1].line == 2); + CHECK(found[1].column == 10); + CHECK(found[1].endLine == 4); + CHECK(found[1].endColumn == 1); + } + UNSCOPED_INFO("i"); + CHECK(found[2].name == std::string("i")); + { + INFO("i's line"); + CHECK(found[2].line == 10); + CHECK(found[2].column == 15); + } // Every one of them is one a run puts a module in place of, and // no other call is. @@ -1318,7 +1623,8 @@ namespace tut add("c", "return 3\n"); add("i", "return 9\n"); const ALPreprocessor::Result r = ALPreprocessor::run(source, options(true)); - ensure_equals("problems", faults(r), std::string()); + UNSCOPED_INFO("problems"); + CHECK(faults(r) == std::string()); // Each module's function is put at the top once, and asked for by // its number where it was called for. size_t numbered = 0; @@ -1326,13 +1632,12 @@ namespace tut { numbered += at + 8 < r.text.size() && std::isdigit(static_cast(r.text[at + 8])); } - ensure_equals("as many requires as calls found", numbered, found.size()); + UNSCOPED_INFO("as many requires as calls found"); + REQUIRE(numbered == found.size()); } - template<> template<> - void alpreprocessor_object::test<32>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a run says what each include or module resolved to as written", "[alpreprocessor]") { - set_test_name("a run says what each include and module was found as, by the name the file asking wrote"); add("lib/util.lsl", "#include \"inner.lsl\"\nint util() { return 1; }\n"); files["lib/util.lsl"].path = "disk:/scripts/lib/util.lsl"; add("inner.lsl", "int inner() { return 2; }\n"); @@ -1341,33 +1646,55 @@ namespace tut const ALPreprocessor::Result r = ALPreprocessor::run("#include \"lib/util.lsl\"\n#include \"lib/util.lsl\"\ndefault {}\n", options()); // Included twice, with no #pragma once: its own include asked for // twice as well. - ensure_equals("four found", r.resolved.size(), size_t(4)); - ensure("the script's, by its name, as the identity found", - r.resolved[0].from.empty() && r.resolved[0].name == "lib/util.lsl" && r.resolved[0].path == "disk:/scripts/lib/util.lsl" && - !r.resolved[0].require); - ensure("the include's own, from it", r.resolved[1].from == "disk:/scripts/lib/util.lsl" && r.resolved[1].name == "inner.lsl" && - r.resolved[1].path == "inner.lsl"); - ensure("the same asked twice, twice", r.resolved[2].name == "lib/util.lsl" && r.resolved[2].from.empty() && - r.resolved[3].name == "inner.lsl"); + UNSCOPED_INFO("four found"); + REQUIRE(r.resolved.size() == size_t(4)); + { + INFO("the script's, by its name, as the identity found"); + REQUIRE(r.resolved[0].from.empty()); + CHECK(r.resolved[0].name == "lib/util.lsl"); + CHECK(r.resolved[0].path == "disk:/scripts/lib/util.lsl"); + CHECK_FALSE(r.resolved[0].require); + } + { + INFO("the include's own, from it"); + CHECK(r.resolved[1].from == "disk:/scripts/lib/util.lsl"); + CHECK(r.resolved[1].name == "inner.lsl"); + CHECK(r.resolved[1].path == "inner.lsl"); + } + { + INFO("the same asked twice, twice"); + CHECK(r.resolved[2].name == "lib/util.lsl"); + REQUIRE(r.resolved[2].from.empty()); + CHECK(r.resolved[3].name == "inner.lsl"); + } add("mod", "return 1\n"); files["mod"].path = "disk:/scripts/mod.luau"; const ALPreprocessor::Result lua = ALPreprocessor::run("local m = require(\"mod\")\nlocal n = require(\"none\")\n", options(true)); - ensure_equals("a module found, one not", lua.resolved.size(), size_t(1)); - ensure("as a require", lua.resolved[0].require && lua.resolved[0].name == "mod" && lua.resolved[0].path == "disk:/scripts/mod.luau"); + UNSCOPED_INFO("a module found, one not"); + REQUIRE(lua.resolved.size() == size_t(1)); + { + INFO("as a require"); + CHECK(lua.resolved[0].require); + CHECK(lua.resolved[0].name == "mod"); + CHECK(lua.resolved[0].path == "disk:/scripts/mod.luau"); + } } - template<> template<> - void alpreprocessor_object::test<33>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: repeated parameters and self pasting or stringizing stop within budget", "[alpreprocessor]") { - set_test_name("a parameter used over and over is spent as it goes in, and a token pasted or stringized to itself stops before it outgrows a script"); // A parameter used sixteen times, through arguments that each // expand the last: sixteen to the seventh tokens by the end, which // were made whole before they were counted. const std::string many = "#define D(x) x x x x x x x x x x x x x x x x\nD(D(D(D(D(D(D(1)))))))\n"; ALPreprocessor::Result r = ALPreprocessor::run(many, options()); - ensure("ran away: " + messages(r), r.overran && r.problems.back().key == std::string("PreprocTooMuch")); - ensure("and gave the source back", r.text == many); + { + INFO("ran away: " + messages(r)); + CHECK(r.overran); + CHECK(r.problems.back().key == std::string("PreprocTooMuch")); + } + UNSCOPED_INFO("and gave the source back"); + CHECK(r.text == many); // A token pasted to itself through an indirection doubles its text // and not the tokens: two to the fortieth bytes by the end. @@ -1377,7 +1704,11 @@ namespace tut call = "D(" + call + ")"; } r = ALPreprocessor::run("#define CAT(a) a##a\n#define D(a) CAT(a)\n" + call + ";\n", options()); - ensure("pasting stopped: " + messages(r), r.overran && r.problems.back().key == std::string("PreprocTokenTooLong")); + { + INFO("pasting stopped: " + messages(r)); + CHECK(r.overran); + CHECK(r.problems.back().key == std::string("PreprocTokenTooLong")); + } // A string stringized over and over, each level escaping the last. call = "\"a\""; @@ -1386,30 +1717,52 @@ namespace tut call = "X(" + call + ")"; } r = ALPreprocessor::run("#define S(x) #x\n#define X(x) S(x)\n" + call + ";\n", options()); - ensure("stringizing stopped: " + messages(r), r.overran && r.problems.back().key == std::string("PreprocTokenTooLong")); + { + INFO("stringizing stopped: " + messages(r)); + CHECK(r.overran); + CHECK(r.problems.back().key == std::string("PreprocTokenTooLong")); + } // The bytes stop what the tokens would not, and what fits is // untouched. ALPreprocessor::Options small = options(); small.byteBudget = 1000; r = ALPreprocessor::run("#define L \"" + std::string(600, 'a') + "\"\nL L\n", small); - ensure("the bytes: " + messages(r), r.overran && r.problems.back().key == std::string("PreprocTooMuch")); + { + INFO("the bytes: " + messages(r)); + CHECK(r.overran); + CHECK(r.problems.back().key == std::string("PreprocTooMuch")); + } r = ALPreprocessor::run("#define L \"" + std::string(100, 'a') + "\"\nL L\n", small); - ensure("within them: " + messages(r), !r.overran && r.text.find(std::string(100, 'a')) != std::string::npos); + { + INFO("within them: " + messages(r)); + CHECK_FALSE(r.overran); + REQUIRE(r.text.find(std::string(100, 'a')) != std::string::npos); + } // What a job answers when a run throws: as a run that ran away. const ALPreprocessor::Result failed = ALPreprocessor::failed("integer x;\n", options(), "std::bad_alloc"); - ensure("overran, with the source", failed.overran && failed.text == "integer x;\n"); - ensure("and why", failed.problems.size() == 1 && failed.problems[0].key == std::string("PreprocFailed") && - failed.problems[0].message.find("std::bad_alloc") != std::string::npos && - failed.problems[0].severity == ALScriptProblem::Severity::Error); - ensure("mapped to itself", failed.map.toSource(0, 3).found() && failed.map.toSource(0, 3).line == 0); + { + INFO("overran, with the source"); + CHECK(failed.overran); + CHECK(failed.text == "integer x;\n"); + } + { + INFO("and why"); + REQUIRE(failed.problems.size() == 1); + CHECK(failed.problems[0].key == std::string("PreprocFailed")); + REQUIRE(failed.problems[0].message.find("std::bad_alloc") != std::string::npos); + CHECK(failed.problems[0].severity == ALScriptProblem::Severity::Error); + } + { + INFO("mapped to itself"); + REQUIRE(failed.map.toSource(0, 3).found()); + CHECK(failed.map.toSource(0, 3).line == 0); + } } - template<> template<> - void alpreprocessor_object::test<34>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: nested lazy list reads stop at the block depth or each become a call", "[alpreprocessor]") { - set_test_name("lazy lists' reads nested by macros stop at the depth blocks may nest, and within it each is made a call"); // A read inside a read's brackets, a thousand deep, made by macros // each expanding the last -- rescanned, so the expansion itself is // flat, and only the rewrite descends. @@ -1422,81 +1775,117 @@ namespace tut ALPreprocessor::Options o = options(); o.lazyLists = true; ALPreprocessor::Result r = ALPreprocessor::run(deep, o); - ensure("stopped at the depth: " + messages(r), r.overran && r.problems.back().key == std::string("PreprocNestsTooDeep")); + { + INFO("stopped at the depth: " + messages(r)); + CHECK(r.overran); + CHECK(r.problems.back().key == std::string("PreprocNestsTooDeep")); + } // Within it, each read is a call, the inner in the outer's place. r = ALPreprocessor::run("#define N0 0\n#define N1 (integer)l[N0]\n#define N2 (integer)l[N1]\n#define N3 (integer)l[N2]\nlist l;\ninteger x = N3;\n", o); - ensure("nested reads each a call: " + r.text, - !r.overran && r.text.find("integer x = llList2Integer(l, llList2Integer(l, llList2Integer(l, 0)));") != std::string::npos); + { + INFO("nested reads each a call: " + r.text); + CHECK_FALSE(r.overran); + REQUIRE(r.text.find("integer x = llList2Integer(l, llList2Integer(l, llList2Integer(l, 0)));") != std::string::npos); + } } - template<> template<> - void alpreprocessor_object::test<35>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: SLua: a comment header stays above the unmapped module table", "[alpreprocessor]") { - set_test_name("SLua: a header of plain comments and hot ones stays ahead of the module table, which maps to no line of the script"); add("m", "return 1\n"); const ALPreprocessor::Result r = ALPreprocessor::run("-- @file vehicle/hovertext.luau\n--!strict\n--!nolint LocalUnused\nlocal m = require(\"m\")\n", options(true)); - ensure_equals("problems", faults(r), std::string()); - ensure_equals("the header first, whole", r.text.substr(0, r.text.find("do\n local modules")), + UNSCOPED_INFO("problems"); + CHECK(faults(r) == std::string()); + UNSCOPED_INFO("the header first, whole"); + CHECK(r.text.substr(0, r.text.find("do\n local modules")) == std::string("-- @file vehicle/hovertext.luau\n--!strict\n--!nolint LocalUnused\n")); - ensure("each header line its own", r.map.toSource(1, 0).found() && r.map.toSource(1, 0).file == 0 && r.map.toSource(1, 0).line == 1); - ensure("the table's lines and the require's map nowhere", !r.map.toSource(3, 0).found() && !r.map.toSource(9, 0).found()); - ensure("nor its module's wrapping", !r.map.toSource(5, 0).found()); - ensure("the module's own line maps to it", r.map.toSource(6, 0).found() && r.map.files()[r.map.toSource(6, 0).file].name == "m"); - ensure("the script's code after it maps back", r.map.toSource(19, 0).file == 0 && r.map.toSource(19, 0).line == 3); + { + INFO("each header line its own"); + REQUIRE(r.map.toSource(1, 0).found()); + CHECK(r.map.toSource(1, 0).file == 0); + CHECK(r.map.toSource(1, 0).line == 1); + } + { + INFO("the table's lines and the require's map nowhere"); + REQUIRE_FALSE(r.map.toSource(3, 0).found()); + REQUIRE_FALSE(r.map.toSource(9, 0).found()); + } + UNSCOPED_INFO("nor its module's wrapping"); + REQUIRE_FALSE(r.map.toSource(5, 0).found()); + { + INFO("the module's own line maps to it"); + REQUIRE(r.map.toSource(6, 0).found()); + CHECK(r.map.files()[r.map.toSource(6, 0).file].name == "m"); + } + { + INFO("the script's code after it maps back"); + CHECK(r.map.toSource(19, 0).file == 0); + CHECK(r.map.toSource(19, 0).line == 3); + } } - template<> template<> - void alpreprocessor_object::test<36>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a superseded run stops and gives the source back; a wanted one runs on", "[alpreprocessor]") { - set_test_name("a run no longer wanted stops where it stands and says so, the source as it was; one still wanted is not touched"); std::atomic later{ false }; ALPreprocessor::Options wanted = options(false); wanted.superseded = &later; wanted.switches = true; const std::string source = "#define TWICE(x) ((x) * 2)\ndefault { state_entry() { integer n = TWICE(3); switch (n) { case 6: break; } } }\n"; const ALPreprocessor::Result made = ALPreprocessor::run(source, wanted); - ensure("still wanted: made", !made.superseded && !made.overran && made.text.find("((3) * 2)") != std::string::npos); + { + INFO("still wanted: made"); + CHECK_FALSE(made.superseded); + CHECK_FALSE(made.overran); + REQUIRE(made.text.find("((3) * 2)") != std::string::npos); + } later = true; const ALPreprocessor::Result stopped = ALPreprocessor::run(source, wanted); - ensure("not wanted: stopped", stopped.superseded && stopped.overran); - ensure_equals("the source as it was", stopped.text, source); - ensure("nothing said of it", std::none_of(stopped.problems.begin(), stopped.problems.end(), + { + INFO("not wanted: stopped"); + CHECK(stopped.superseded); + CHECK(stopped.overran); + } + UNSCOPED_INFO("the source as it was"); + CHECK(stopped.text == source); + UNSCOPED_INFO("nothing said of it"); + CHECK(std::none_of(stopped.problems.begin(), stopped.problems.end(), [](const ALScriptProblem& p) { return p.severity == ALScriptProblem::Severity::Error; })); } - template<> template<> - void alpreprocessor_object::test<37>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: include guards: once until #undef; no guard with #else or code after", "[alpreprocessor]") { - set_test_name("a header behind a classic include guard: expanded once however often included, listed once in the map; again once undefined; an #else or code after it no guard"); add("guarded.lsl", "// the helpers\n#ifndef GUARDED\n#define GUARDED\ninteger helper() { return 1; }\n#endif\n"); const ALPreprocessor::Result r = ALPreprocessor::run( "#include \"guarded.lsl\"\n#include \"guarded.lsl\"\n#include \"guarded.lsl\"\ndefault { state_entry() { helper(); } }\n", options()); - ensure_equals("problems", messages(r), std::string()); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); size_t count = 0; for (size_t at = r.text.find("integer helper()"); at != std::string::npos; at = r.text.find("integer helper()", at + 1)) { ++count; } - ensure_equals("once", count, size_t(1)); - ensure_equals("listed once", r.map.files().size(), size_t(2)); + UNSCOPED_INFO("once"); + CHECK(count == size_t(1)); + UNSCOPED_INFO("listed once"); + REQUIRE(r.map.files().size() == size_t(2)); const ALPreprocessor::Result undone = ALPreprocessor::run( "#include \"guarded.lsl\"\n#undef GUARDED\n#include \"guarded.lsl\"\ndefault { state_entry() { } }\n", options()); - ensure("undefined: again", undone.text.find("integer helper()") != undone.text.rfind("integer helper()")); + UNSCOPED_INFO("undefined: again"); + CHECK(undone.text.find("integer helper()") != undone.text.rfind("integer helper()")); add("elsed.lsl", "#ifndef ELSED\n#define ELSED\ninteger a;\n#else\ninteger b;\n#endif\n"); const ALPreprocessor::Result elsed = ALPreprocessor::run("#include \"elsed.lsl\"\n#include \"elsed.lsl\"\n", options()); - ensure("an #else: no guard, the other branch made", elsed.text.find("integer b;") != std::string::npos); + UNSCOPED_INFO("an #else: no guard, the other branch made"); + REQUIRE(elsed.text.find("integer b;") != std::string::npos); add("after.lsl", "#ifndef AFTER\n#define AFTER\n#endif\ninteger after;\n"); const ALPreprocessor::Result after = ALPreprocessor::run("#include \"after.lsl\"\n#include \"after.lsl\"\n", options()); - ensure("code after the #endif: no guard, made twice", after.text.find("integer after;") != after.text.rfind("integer after;")); + UNSCOPED_INFO("code after the #endif: no guard, made twice"); + CHECK(after.text.find("integer after;") != after.text.rfind("integer after;")); } - template<> template<> - void alpreprocessor_object::test<38>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: macro arguments: none for no parameters; lines join; blanks kept", "[alpreprocessor]") { - set_test_name("a macro's arguments: none for one with no parameters, a line an argument runs over a space, blanks with no ( after them given back as they were"); const std::string source = "#define Z() zero\n" "#define I(x) [x]\n" "#define TWO(a, b) a+b\n" @@ -1505,13 +1894,13 @@ namespace tut "TWO(1,\n2) TWO( (1, 2) ,\t3 )\n" "I\n(3)\n"; const ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("text", squeeze(r.text), std::string("zero zero Z\n[1] I x I\n1+2 (1, 2)+3\n[3]\n")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("text"); + CHECK(squeeze(r.text) == std::string("zero zero Z\n[1] I x I\n1+2 (1, 2)+3\n[3]\n")); } - template<> template<> - void alpreprocessor_object::test<39>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a function marked inline in any form is listed and the mark taken off", "[alpreprocessor]") { - set_test_name("a function marked inline in any of the ways a mark is written, at the top of the script, and the marks taken off with the extensions off"); ensureGridBuiltins(); ALPreprocessor::Options o = options(); o.extensions = true; @@ -1520,24 +1909,36 @@ namespace tut "h() //pragma inline\n{\n}\n" "k() /* pragma inline */ { }\n", o); - ensure_equals("problems", messages(r), std::string()); - ensure("the four marked, in order", r.inlined == std::vector{ "f", "g", "h", "k" }); - ensure("the word after the parameters taken off: " + r.text, r.text.find("integer f(integer a) { return a; }") != std::string::npos); - ensure("a comment left as it is: " + r.text, r.text.find("g(string s) /*pragma inline*/ { llOwnerSay(s); }") != std::string::npos && - r.text.find("h() //pragma inline\n{") != std::string::npos); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("the four marked, in order"); + CHECK(r.inlined == std::vector{ "f", "g", "h", "k" }); + UNSCOPED_INFO("the word after the parameters taken off: " + r.text); + REQUIRE(r.text.find("integer f(integer a) { return a; }") != std::string::npos); + { + INFO("a comment left as it is: " + r.text); + REQUIRE(r.text.find("g(string s) /*pragma inline*/ { llOwnerSay(s); }") != std::string::npos); + REQUIRE(r.text.find("h() //pragma inline\n{") != std::string::npos); + } r = ALPreprocessor::run("integer inline = 1;\nf() { integer x = inline; }\ng() /* not pragma inline */ { }\ninteger v = (1) /*pragma inline*/;\n", o); - ensure("a variable named inline, another comment, a parenthesis that closes no parameters: none marked", r.inlined.empty()); - ensure_equals("and nothing taken off", r.text, std::string("integer inline = 1;\nf() { integer x = inline; }\ng() /* not pragma inline */ { }\ninteger v = (1) /*pragma inline*/;\n")); + UNSCOPED_INFO("a variable named inline, another comment, a parenthesis that closes no parameters: none marked"); + REQUIRE(r.inlined.empty()); + UNSCOPED_INFO("and nothing taken off"); + CHECK(r.text == std::string("integer inline = 1;\nf() { integer x = inline; }\ng() /* not pragma inline */ { }\ninteger v = (1) /*pragma inline*/;\n")); r = ALPreprocessor::run("default { state_entry() inline { } }\n", o); - ensure("an event is not a function to put in place", r.inlined.empty()); + UNSCOPED_INFO("an event is not a function to put in place"); + REQUIRE(r.inlined.empty()); // With the extensions off the marks come off all the same. const ALPreprocessor::Result plain = ALPreprocessor::run("inline f() { }\ninteger g(integer a) inline { return a; }\n", options()); - ensure_equals("taken off", plain.text, std::string("f() { }\ninteger g(integer a) { return a; }\n")); - ensure("and the names kept", plain.inlined == std::vector{ "f", "g" }); - ensure("the extensions not said to be used", !plain.usedExtensions); + UNSCOPED_INFO("taken off"); + CHECK(plain.text == std::string("f() { }\ninteger g(integer a) { return a; }\n")); + UNSCOPED_INFO("and the names kept"); + CHECK(plain.inlined == std::vector{ "f", "g" }); + UNSCOPED_INFO("the extensions not said to be used"); + CHECK_FALSE(plain.usedExtensions); // And the optimizer puts the marked function in place. ALPreprocessor::Options optimizing = options(); @@ -1545,14 +1946,16 @@ namespace tut const ALPreprocessor::Result put = ALPreprocessor::run("say(string s) inline\n{\n llOwnerSay(s);\n llOwnerSay(s + \"!\");\n}\n" "default\n{\n state_entry()\n {\n say(\"a\");\n say(\"b\");\n }\n}\n", optimizing); - ensure("both calls put in place: " + put.text, put.text.find("say(") == std::string::npos && put.text.find("llOwnerSay(\"a\")") != std::string::npos && - put.text.find("llOwnerSay(\"b\" + \"!\")") != std::string::npos); + { + INFO("both calls put in place: " + put.text); + REQUIRE(put.text.find("say(") == std::string::npos); + REQUIRE(put.text.find("llOwnerSay(\"a\")") != std::string::npos); + REQUIRE(put.text.find("llOwnerSay(\"b\" + \"!\")") != std::string::npos); + } } - template<> template<> - void alpreprocessor_object::test<40>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: const before a type is taken off and each name said where it is", "[alpreprocessor]") { - set_test_name("const before a type is taken off, and each name it declares said where it is, a function's as a function"); ensureGridBuiltins(); const auto at = [](const ALPreprocessor::Result& r, size_t i, const std::string& name, S32 line, S32 column, bool function) { return i < r.consts.size() && r.consts[i].name == name && r.consts[i].line == line && r.consts[i].column == column && @@ -1563,48 +1966,65 @@ namespace tut "const integer sq(const integer x) { return x * x; }\n" "default { state_entry() { const vector v = <1, 2, 3>; llOwnerSay((string)v); } }\n", options()); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("taken off", r.text, + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("taken off"); + CHECK(r.text == std::string("integer X = 5;\n" "float Y = 10.0;\n" "integer sq(integer x) { return x * x; }\n" "default { state_entry() { vector v = <1, 2, 3>; llOwnerSay((string)v); } }\n")); - ensure_equals("five", r.consts.size(), size_t(5)); - ensure("a global", at(r, 0, "X", 0, 8, false)); - ensure("the spaces up to the type gone too", at(r, 1, "Y", 1, 6, false)); - ensure("a function", at(r, 2, "sq", 2, 8, true)); - ensure("its parameter", at(r, 3, "x", 2, 19, false)); - ensure("a local", at(r, 4, "v", 3, 33, false)); - ensure("the extensions not said to be used", !r.usedExtensions); + UNSCOPED_INFO("five"); + REQUIRE(r.consts.size() == size_t(5)); + UNSCOPED_INFO("a global"); + CHECK(at(r, 0, "X", 0, 8, false)); + UNSCOPED_INFO("the spaces up to the type gone too"); + CHECK(at(r, 1, "Y", 1, 6, false)); + UNSCOPED_INFO("a function"); + CHECK(at(r, 2, "sq", 2, 8, true)); + UNSCOPED_INFO("its parameter"); + CHECK(at(r, 3, "x", 2, 19, false)); + UNSCOPED_INFO("a local"); + CHECK(at(r, 4, "v", 3, 33, false)); + UNSCOPED_INFO("the extensions not said to be used"); + CHECK_FALSE(r.usedExtensions); // A name, an assignment, a word with no type after it: no const. const std::string plain = "integer const = 1;\nf() { const = 2; integer constant = const; }\n"; r = ALPreprocessor::run(plain, options()); - ensure_equals("nothing taken off", r.text, plain); - ensure("nothing said", r.consts.empty()); + UNSCOPED_INFO("nothing taken off"); + CHECK(r.text == plain); + UNSCOPED_INFO("nothing said"); + REQUIRE(r.consts.empty()); // Made by a macro, and with the extensions on. ALPreprocessor::Options o = options(); o.extensions = true; r = ALPreprocessor::run("#define FIXED const integer\nFIXED Z = 3;\n", o); - ensure_equals("problems", messages(r), std::string()); - ensure_equals("a macro's", squeeze(r.text), std::string("integer Z = 3;\n")); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("a macro's"); + CHECK(squeeze(r.text) == std::string("integer Z = 3;\n")); const size_t z = r.text.find('Z'); const size_t newline = r.text.rfind('\n', z); const S32 line = static_cast(std::count(r.text.begin(), r.text.begin() + z, '\n')); const S32 column = static_cast(newline == std::string::npos ? z : z - newline - 1); - ensure("where the name is in the text", at(r, 0, "Z", line, column, false) && r.consts.size() == 1); + { + INFO("where the name is in the text"); + CHECK(at(r, 0, "Z", line, column, false)); + REQUIRE(r.consts.size() == 1); + } } - template<> template<> - void alpreprocessor_object::test<41>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: SLua: directives are --# first on a line and # is always Luau's length", "[alpreprocessor]") { - set_test_name("SLua: a directive is --# and its name first on a line, # is Luau's length wherever it stands, and a macro's # is length too"); // A line that starts with # goes on an expression: never a // directive, and nothing said of it. ALPreprocessor::Result r = ALPreprocessor::run("local n =\n #items\n#define N 3\nprint(N)\n", options(true)); - ensure_equals("nothing wrong", messages(r), std::string()); - ensure_equals("# lines are Luau's", r.text, std::string("local n =\n #items\n#define N 3\nprint(N)\n")); + UNSCOPED_INFO("nothing wrong"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("# lines are Luau's"); + CHECK(r.text == std::string("local n =\n #items\n#define N 3\nprint(N)\n")); // --# with a directive's name at once, indented or not; the // conditionals, the macros, #line. @@ -1622,8 +2042,10 @@ namespace tut "--#endif\n" "--#line 40\n", options(true)); - ensure_equals("nothing wrong", messages(r), std::string()); - ensure_equals("directives read, # as length", r.text, + UNSCOPED_INFO("nothing wrong"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("directives read, # as length"); + CHECK(r.text == std::string("\n\n\n\n\n\n\n\n\n\nprint(#xs, xs[#xs], ##xs, 2)\n\n--#line 40\n")); // Comments that only look like directives stay comments: selene's @@ -1637,48 +2059,70 @@ namespace tut "local z = 1 --#define Z 3\n" "print(X, Y, Z)\n"; r = ALPreprocessor::run(comments, options(true)); - ensure_equals("nothing wrong", messages(r), std::string()); - ensure_equals("all as written", r.text, comments); + UNSCOPED_INFO("nothing wrong"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("all as written"); + CHECK(r.text == comments); // An include, as LSL has one. add("lib.luau", "--#define GREETING \"hi\"\nlocal lib = {}\n"); r = ALPreprocessor::run("--#include \"lib.luau\"\nprint(GREETING)\n", options(true)); - ensure_equals("nothing wrong", messages(r), std::string()); - ensure("the include's text in, and its macro", r.text.find("local lib = {}") != std::string::npos && - r.text.find("print(\"hi\")") != std::string::npos); + UNSCOPED_INFO("nothing wrong"); + CHECK(messages(r) == std::string()); + { + INFO("the include's text in, and its macro"); + REQUIRE(r.text.find("local lib = {}") != std::string::npos); + REQUIRE(r.text.find("print(\"hi\")") != std::string::npos); + } // Its errors are the directive's. r = ALPreprocessor::run("--#endif\n--#if\n--#endif\n", options(true)); - ensure("an #endif without #if, and an #if without an expression: " + messages(r), - messages(r).find("E 0:") != std::string::npos && messages(r).find("E 1:") != std::string::npos); + { + INFO("an #endif without #if, and an #if without an expression: " + messages(r)); + REQUIRE(messages(r).find("E 0:") != std::string::npos); + REQUIRE(messages(r).find("E 1:") != std::string::npos); + } // Read as a script's lines are, by what finds includes and names. - ensure_equals("--#include's name", ALPreprocessor::directiveName(" --#include \"x\"", true), size_t(5)); - ensure("not with a blank", ALPreprocessor::directiveName("--# include \"x\"", true) == std::string_view::npos); - ensure("not # in SLua", ALPreprocessor::directiveName("#include \"x\"", true) == std::string_view::npos); - ensure_equals("# in LSL, blanks about it", ALPreprocessor::directiveName(" # include \"x\"", false), size_t(3)); - ensure("not --# in LSL", ALPreprocessor::directiveName("--#include \"x\"", false) == std::string_view::npos); + UNSCOPED_INFO("--#include's name"); + CHECK(ALPreprocessor::directiveName(" --#include \"x\"", true) == size_t(5)); + UNSCOPED_INFO("not with a blank"); + REQUIRE(ALPreprocessor::directiveName("--# include \"x\"", true) == std::string_view::npos); + UNSCOPED_INFO("not # in SLua"); + REQUIRE(ALPreprocessor::directiveName("#include \"x\"", true) == std::string_view::npos); + UNSCOPED_INFO("# in LSL, blanks about it"); + CHECK(ALPreprocessor::directiveName(" # include \"x\"", false) == size_t(3)); + UNSCOPED_INFO("not --# in LSL"); + REQUIRE(ALPreprocessor::directiveName("--#include \"x\"", false) == std::string_view::npos); // And by the lines a verbatim reader sees: --# is a mark of its own. const std::vector tokens = ALPreprocessor::tokenize("--#define A 1\n#a\n", true); - ensure("the mark, then the directive's words", tokens.size() > 2 && tokens[0].text == "--#" && tokens[1].text == "define"); + { + INFO("the mark, then the directive's words"); + REQUIRE(tokens.size() > 2); + CHECK(tokens[0].text == "--#"); + CHECK(tokens[1].text == "define"); + } } - template<> template<> - void alpreprocessor_object::test<42>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: SLua: a --#define continues over a backslash before any line ending", "[alpreprocessor]") { - set_test_name("SLua: a --#define goes on over a backslash at its line's end, before LF, CRLF or a lone CR, and past blanks with a warning; an include's CRLF lines read as LF"); const std::string lf = "--#define SAY(x) \\\n" " ll.OwnerSay(x) \\\n" " print(x)\n" "SAY(\"hi\")\n"; ALPreprocessor::Result r = ALPreprocessor::run(lf, options(true)); - ensure_equals("nothing wrong", messages(r), std::string()); + UNSCOPED_INFO("nothing wrong"); + CHECK(messages(r) == std::string()); const std::string joined = r.text; const size_t used = joined.find("ll.OwnerSay(\"hi\")"); - ensure("the body on the line that uses it: " + joined, - used != std::string::npos && joined.find("print(\"hi\")") > used && joined.find('\n', used) > joined.find("print(\"hi\")") && - joined.find('\\') == std::string::npos); + { + INFO("the body on the line that uses it: " + joined); + REQUIRE(used != std::string::npos); + CHECK(joined.find("print(\"hi\")") > used); + CHECK(joined.find('\n', used) > joined.find("print(\"hi\")")); + REQUIRE(joined.find('\\') == std::string::npos); + } const auto ended = [](const std::string& text, const char* end) { std::string out; for (char c : text) @@ -1688,30 +2132,35 @@ namespace tut return out; }; r = ALPreprocessor::run(ended(lf, "\r\n"), options(true)); - ensure_equals("CRLF said nothing", messages(r), std::string()); - ensure_equals("CRLF", r.text, joined); + UNSCOPED_INFO("CRLF said nothing"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("CRLF"); + CHECK(r.text == joined); r = ALPreprocessor::run(ended(lf, "\r"), options(true)); - ensure_equals("a lone CR", r.text, joined); + UNSCOPED_INFO("a lone CR"); + CHECK(r.text == joined); r = ALPreprocessor::run("--#define SAY(x) \\ \n" " ll.OwnerSay(x) \\\t\n" " print(x)\n" "SAY(\"hi\")\n", options(true)); - ensure_equals("blanks after the backslash", r.text, joined); - ensure_equals("said of each", messages(r), + UNSCOPED_INFO("blanks after the backslash"); + CHECK(r.text == joined); + UNSCOPED_INFO("said of each"); + CHECK(messages(r) == std::string("W 0: backslash and newline separated by space\nW 1: backslash and newline separated by space\n")); add("say.luau", "--#define SAY(x) \\\r\n print(x)\r\n"); r = ALPreprocessor::run("--#include \"say.luau\"\nSAY(1)\n", options(true)); - ensure_equals("an include with CRLF said nothing", messages(r), std::string()); - ensure("and its macro goes on over its lines: " + r.text, r.text.find("print(1)") != std::string::npos); + UNSCOPED_INFO("an include with CRLF said nothing"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("and its macro goes on over its lines: " + r.text); + REQUIRE(r.text.find("print(1)") != std::string::npos); } - template<> template<> - void alpreprocessor_object::test<43>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: every golden file with CRLF or lone CR line ends comes out as expected", "[alpreprocessor]") { - set_test_name("every golden file and its includes, their lines ended in CRLF or in a lone CR as a Windows checkout or an old Mac would, come out as their expected text, saying nothing"); namespace fs = std::filesystem; const fs::path dir = fsyspath(AL_ALSCRIPT_TEST_DIR) / "preprocessor"; S32 checked = 0; @@ -1728,30 +2177,42 @@ namespace tut { const std::string said = ending[1] ? " with CRLF" : " with a lone CR"; ALPreprocessor::Result r = runGolden(entry.path(), ending); - ensure_equals("problems in " + name + said, messages(r), std::string()); - ensure_equals("output of " + name + said, r.text, want); + UNSCOPED_INFO("problems in " + name + said); + REQUIRE(messages(r) == std::string()); + UNSCOPED_INFO("output of " + name + said); + REQUIRE(r.text == want); } ++checked; } - ensure("some golden files", checked > 0); + UNSCOPED_INFO("some golden files"); + CHECK(checked > 0); } - template<> template<> - void alpreprocessor_object::test<44>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: usesDirectives and wanted: why a script is preprocessed", "[alpreprocessor]") { - set_test_name("a script holds a directive where a line is one, outside an LSL block comment; why one is preprocessed: enveloped, then its directives, then the setting"); typedef ALPreprocessor P; - ensure("#define", P::usesDirectives("#define X 1\ndefault { state_entry() { } }\n", false)); - ensure("one further down, indented", P::usesDirectives("integer x;\n #include \"lib.lsl\"\n", false)); - ensure("none: # only in a string", !P::usesDirectives("default { state_entry() { llSay(0, \"#define\"); } }\n", false)); - ensure("none: a line comment", !P::usesDirectives("// #define X 1\ndefault { }\n", false)); - ensure("none: in a block comment", !P::usesDirectives("/* notes\n#define X 1\n*/\ndefault { }\n", false)); - ensure("after a block comment closes", P::usesDirectives("/* notes */\n#define X 1\n", false)); - ensure("not opened by one in a string", P::usesDirectives("string s = \"/*\";\n#define X 1\n", false)); - ensure("SLua's --#", P::usesDirectives("--#define X 1\nprint(X)\n", true)); - ensure("none: Luau's length operator first on a line", !P::usesDirectives("local n =\n #items\n", true)); - ensure("none: an SLua comment", !P::usesDirectives("-- # a heading\nprint(1)\n", true)); - ensure("none: LSL's # in SLua's place", !P::usesDirectives("#define X 1\n", true)); + UNSCOPED_INFO("#define"); + CHECK(P::usesDirectives("#define X 1\ndefault { state_entry() { } }\n", false)); + UNSCOPED_INFO("one further down, indented"); + CHECK(P::usesDirectives("integer x;\n #include \"lib.lsl\"\n", false)); + UNSCOPED_INFO("none: # only in a string"); + CHECK_FALSE(P::usesDirectives("default { state_entry() { llSay(0, \"#define\"); } }\n", false)); + UNSCOPED_INFO("none: a line comment"); + CHECK_FALSE(P::usesDirectives("// #define X 1\ndefault { }\n", false)); + UNSCOPED_INFO("none: in a block comment"); + CHECK_FALSE(P::usesDirectives("/* notes\n#define X 1\n*/\ndefault { }\n", false)); + UNSCOPED_INFO("after a block comment closes"); + CHECK(P::usesDirectives("/* notes */\n#define X 1\n", false)); + UNSCOPED_INFO("not opened by one in a string"); + CHECK(P::usesDirectives("string s = \"/*\";\n#define X 1\n", false)); + UNSCOPED_INFO("SLua's --#"); + CHECK(P::usesDirectives("--#define X 1\nprint(X)\n", true)); + UNSCOPED_INFO("none: Luau's length operator first on a line"); + CHECK_FALSE(P::usesDirectives("local n =\n #items\n", true)); + UNSCOPED_INFO("none: an SLua comment"); + CHECK_FALSE(P::usesDirectives("-- # a heading\nprint(1)\n", true)); + UNSCOPED_INFO("none: LSL's # in SLua's place"); + CHECK_FALSE(P::usesDirectives("#define X 1\n", true)); bool asked = false; const auto directives = [&asked](bool answer) { @@ -1760,18 +2221,26 @@ namespace tut return answer; }; }; - ensure("enveloped first, its text not read", P::wanted(true, directives(true), true) == P::Wanted::Enveloped && !asked); - ensure("then its directives, the setting on or off", - P::wanted(false, directives(true), true) == P::Wanted::Directives && P::wanted(false, directives(true), false) == P::Wanted::Directives); - ensure("then the setting", P::wanted(false, directives(false), true) == P::Wanted::Setting); - ensure("else not", P::wanted(false, directives(false), false) == P::Wanted::No); - ensure("by text", P::wanted("#define X 1\n", false, false, false) == P::Wanted::Directives); + { + INFO("enveloped first, its text not read"); + CHECK(P::wanted(true, directives(true), true) == P::Wanted::Enveloped); + CHECK_FALSE(asked); + } + { + INFO("then its directives, the setting on or off"); + CHECK(P::wanted(false, directives(true), true) == P::Wanted::Directives); + CHECK(P::wanted(false, directives(true), false) == P::Wanted::Directives); + } + UNSCOPED_INFO("then the setting"); + CHECK(P::wanted(false, directives(false), true) == P::Wanted::Setting); + UNSCOPED_INFO("else not"); + CHECK(P::wanted(false, directives(false), false) == P::Wanted::No); + UNSCOPED_INFO("by text"); + CHECK(P::wanted("#define X 1\n", false, false, false) == P::Wanted::Directives); } - template<> template<> - void alpreprocessor_object::test<45>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a require only the old search found is said with a fix each way", "[alpreprocessor]") { - set_test_name("a require found nowhere that the search before found: said where, with a fix for each way to say it now, the alias's not preferred"); ALPreprocessor::Options o = options(true); o.resolve = [](const ALPreprocessor::Ask& ask, ALPreprocessor::Include& out) { if (ask.name == "util") @@ -1782,26 +2251,49 @@ namespace tut return ALPreprocessor::Found::No; }; const ALPreprocessor::Result r = ALPreprocessor::run("local util = require('util')\nlocal other = require(\"other\")\n", o); - ensure_equals("both said", r.problems.size(), size_t(2)); + UNSCOPED_INFO("both said"); + REQUIRE(r.problems.size() == size_t(2)); const ALScriptProblem& moved = r.problems[0]; - ensure_equals("where the search found it", moved.key, std::string("PreprocModuleSearched")); - ensure("its words", moved.args == std::vector({ "util", "/inc/util.luau" }) && moved.message.find("/inc/util.luau") != std::string::npos); - ensure_equals("a fix for each way", moved.fixes.size(), size_t(2)); + UNSCOPED_INFO("where the search found it"); + CHECK(moved.key == std::string("PreprocModuleSearched")); + { + INFO("its words"); + CHECK(moved.args == std::vector({ "util", "/inc/util.luau" })); + REQUIRE(moved.message.find("/inc/util.luau") != std::string::npos); + } + UNSCOPED_INFO("a fix for each way"); + REQUIRE(moved.fixes.size() == size_t(2)); const ALScriptFix& plain = moved.fixes[0]; - ensure("the require's string put in, its quote kept", plain.edits.size() == 1 && plain.edits[0].text == "'@shared/util'" && - plain.edits[0].line == 0 && plain.edits[0].column == 21 && plain.edits[0].endColumn == 27); - ensure("preferred", plain.preferred && plain.key == "ScriptFixRequireAs"); + { + INFO("the require's string put in, its quote kept"); + REQUIRE(plain.edits.size() == 1); + CHECK(plain.edits[0].text == "'@shared/util'"); + CHECK(plain.edits[0].line == 0); + CHECK(plain.edits[0].column == 21); + CHECK(plain.edits[0].endColumn == 27); + } + { + INFO("preferred"); + CHECK(plain.preferred); + CHECK(plain.key == "ScriptFixRequireAs"); + } const ALScriptFix& alias = moved.fixes[1]; - ensure("the alias's named in it, and not preferred", alias.key == "ScriptFixRequireAlias" && alias.args.size() == 3 && alias.args[1] == "inc" && - !alias.preferred && !alias.safe); - ensure_equals("one the search never found: as before", r.problems[1].key, std::string("PreprocModuleNotFound")); - ensure("with no fix", r.problems[1].fixes.empty()); + { + INFO("the alias's named in it, and not preferred"); + CHECK(alias.key == "ScriptFixRequireAlias"); + REQUIRE(alias.args.size() == 3); + CHECK(alias.args[1] == "inc"); + CHECK_FALSE(alias.preferred); + CHECK_FALSE(alias.safe); + } + UNSCOPED_INFO("one the search never found: as before"); + CHECK(r.problems[1].key == std::string("PreprocModuleNotFound")); + UNSCOPED_INFO("with no fix"); + REQUIRE(r.problems[1].fixes.empty()); } - template<> template<> - void alpreprocessor_object::test<46>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a require of a file beside a same-named folder init warns naming both", "[alpreprocessor]") { - set_test_name("a require of a file beside a folder's init of its name: taken, and a warning naming both"); ALPreprocessor::Options o = options(true); o.resolve = [](const ALPreprocessor::Ask& ask, ALPreprocessor::Include& out) { out.text = "return 1\n"; @@ -1811,28 +2303,46 @@ namespace tut return ALPreprocessor::Found::Yes; }; const ALPreprocessor::Result r = ALPreprocessor::run("local both = require('./both')\nprint(both)\n", o); - ensure_equals("one said", r.problems.size(), size_t(1)); + UNSCOPED_INFO("one said"); + REQUIRE(r.problems.size() == size_t(1)); const ALScriptProblem& said = r.problems[0]; - ensure("a warning", said.severity == ALScriptProblem::Severity::Warning && said.key == "PreprocModuleBesideInit"); - ensure("naming both, each from the folder both are in", said.args == std::vector({ "./both", "both.luau", "both/init.luau" })); - ensure("the module taken", r.text.find("return 1") != std::string::npos); + { + INFO("a warning"); + CHECK(said.severity == ALScriptProblem::Severity::Warning); + CHECK(said.key == "PreprocModuleBesideInit"); + } + UNSCOPED_INFO("naming both, each from the folder both are in"); + CHECK(said.args == std::vector({ "./both", "both.luau", "both/init.luau" })); + UNSCOPED_INFO("the module taken"); + REQUIRE(r.text.find("return 1") != std::string::npos); } - template<> template<> - void alpreprocessor_object::test<47>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a missing require says why: keyed where known else in Luau's words", "[alpreprocessor]") { - set_test_name("a require found nowhere said with why: keyed where the reason has a known shape, the module's name first, else in Luau's words"); ALPreprocessor::Options o = options(true); o.resolve = [](const ALPreprocessor::Ask& ask, ALPreprocessor::Include& out) { out.why = ask.name == "./a" ? "could not resolve child component \"a\"" : ask.name == "./b" ? "something odd" : ""; return ALPreprocessor::Found::No; }; const ALPreprocessor::Result r = ALPreprocessor::run("require('./a')\nrequire('./b')\nrequire('./c')\n", o); - ensure_equals("each said", r.problems.size(), size_t(3)); - ensure("keyed", r.problems[0].key == "PreprocRequireNoChild" && r.problems[0].args == std::vector({ "./a", "a" }) && - r.problems[0].message == "could not find module './a': could not resolve child component \"a\""); - ensure("in Luau's words", r.problems[1].key == "PreprocModuleNotFoundWhy" && r.problems[1].args == std::vector({ "./b", "something odd" })); - ensure("no reason: as before", r.problems[2].key == "PreprocModuleNotFound" && r.problems[2].args == std::vector{ "./c" }); + UNSCOPED_INFO("each said"); + REQUIRE(r.problems.size() == size_t(3)); + { + INFO("keyed"); + CHECK(r.problems[0].key == "PreprocRequireNoChild"); + CHECK(r.problems[0].args == std::vector({ "./a", "a" })); + CHECK(r.problems[0].message == "could not find module './a': could not resolve child component \"a\""); + } + { + INFO("in Luau's words"); + CHECK(r.problems[1].key == "PreprocModuleNotFoundWhy"); + CHECK(r.problems[1].args == std::vector({ "./b", "something odd" })); + } + { + INFO("no reason: as before"); + CHECK(r.problems[2].key == "PreprocModuleNotFound"); + CHECK(r.problems[2].args == std::vector{ "./c" }); + } } // How often each module ran, as a module says it through `counted`, @@ -1840,10 +2350,8 @@ namespace tut // own, and what it sets is not the script's. static std::map gRuns; - template<> template<> - void alpreprocessor_object::test<48>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: SLua's bundled require is Luau's: each module once; nil runs again", "[alpreprocessor]") { - set_test_name("SLua's require, run as a save sends it, is Luau's: a module run once, at its first require, every later one the same value -- but nil, run again; one never required not run; a module's globals its own"); add("count", "counted(\"count\")\nreturn {}\n"); add("lazy", "counted(\"lazy\")\nreturn {}\n"); add("never", "counted(\"never\")\nreturn {}\n"); @@ -1863,7 +2371,8 @@ namespace tut "shared = own.inner == a\n" "ownSees = own.sees\n", options(true)); - ensure_equals("problems", messages(r), std::string()); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); // Run in SLua's VM, its globals read back. gRuns.clear(); @@ -1883,9 +2392,11 @@ namespace tut // module of the scripter's making. lua_setmemcat(L, LUA_FIRST_USER_MEMCAT); const std::string bytecode = Luau::compile(r.text); - ensure("loads: " + r.text, luau_load(L, "=bundle", bytecode.data(), bytecode.size(), 0) == 0); + UNSCOPED_INFO("loads: " + r.text); + REQUIRE(luau_load(L, "=bundle", bytecode.data(), bytecode.size(), 0) == 0); const int status = lua_pcall(L, 0, 0, 0); - ensure(std::string("runs: ") + (status != 0 && lua_isstring(L, -1) ? lua_tostring(L, -1) : "") + "\n" + r.text, status == 0); + UNSCOPED_INFO(std::string("runs: ") + (status != 0 && lua_isstring(L, -1) ? lua_tostring(L, -1) : "") + "\n" + r.text); + CHECK(status == 0); const auto truth = [L](const char* name) { lua_getglobal(L, name); const bool value = lua_toboolean(L, -1); @@ -1898,20 +2409,31 @@ namespace tut lua_pop(L, 1); return value; }; - ensure_equals("required three times, run once", gRuns["count"], 1); - ensure("and the same value each time", truth("same") && truth("shared")); - ensure_equals("not run before its first require", number("lazyBefore"), 0.0); - ensure_equals("then once, however often required", gRuns["lazy"], 1); - ensure_equals("never required, never run", gRuns["never"], 0); - ensure_equals("nil kept by nothing: run again, as Luau runs it", gRuns["nothing"], 2); - ensure("a module's global its own", truth("ownSees") && !truth("leaked")); + UNSCOPED_INFO("required three times, run once"); + CHECK(gRuns["count"] == 1); + { + INFO("and the same value each time"); + CHECK(truth("same")); + CHECK(truth("shared")); + } + UNSCOPED_INFO("not run before its first require"); + CHECK(number("lazyBefore") == 0.0); + UNSCOPED_INFO("then once, however often required"); + CHECK(gRuns["lazy"] == 1); + UNSCOPED_INFO("never required, never run"); + CHECK(gRuns["never"] == 0); + UNSCOPED_INFO("nil kept by nothing: run again, as Luau runs it"); + CHECK(gRuns["nothing"] == 2); + { + INFO("a module's global its own"); + CHECK(truth("ownSees")); + CHECK_FALSE(truth("leaked")); + } lua_close(L); } - template<> template<> - void alpreprocessor_object::test<49>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: @line comments where asked agree with the map; none if nothing moved", "[alpreprocessor]") { - set_test_name("@line comments, where asked: a reader counting lines from each finds every line of the text where the map does, and where nothing moved there are none"); // Read as the plugin reads them: the line after a comment is the // line it says of the file it names, and each line after the next. const auto readsTrue = [](const ALPreprocessor::Result& r, const std::string& script, std::string& why) { @@ -1955,16 +2477,25 @@ namespace tut lua.lineComments = true; 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); - ensure_equals("problems", messages(r), std::string()); - ensure("the reader finds every line: " + why, readsTrue(r, "main.luau", why)); - ensure("the header still first: " + r.text, r.text.rfind("--!strict\ndo\n", 0) == 0); - ensure("a module's first: " + r.text, r.text.find(" [1] = function()\n-- @line 1 \"util\"\nlocal M = {}\n") != std::string::npos); - ensure("one past a directive: " + r.text, r.text.find("-- @line 2 \"deep\"\nreturn { k = 2 }\n") != std::string::npos); - ensure("the script's after the modules: " + r.text, r.text.find("end\n-- @line 2 \"main.luau\"\nlocal util = require(1)\n") != std::string::npos); - ensure_equals("three, no more", std::count(r.text.begin(), r.text.end(), '@'), 3); - ensure("none mapped anywhere", !r.map.toSource(S32(std::count(r.text.begin(), r.text.begin() + r.text.find("-- @line 2 \"main"), '\n')), 0).found()); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("the reader finds every line: " + why); + CHECK(readsTrue(r, "main.luau", why)); + 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); + REQUIRE(r.text.find(" [1] = function()\n-- @line 1 \"util\"\nlocal M = {}\n") != std::string::npos); + UNSCOPED_INFO("one past a directive: " + r.text); + REQUIRE(r.text.find("-- @line 2 \"deep\"\nreturn { k = 2 }\n") != std::string::npos); + UNSCOPED_INFO("the script's after the modules: " + r.text); + REQUIRE(r.text.find("end\n-- @line 2 \"main.luau\"\nlocal util = require(1)\n") != std::string::npos); + UNSCOPED_INFO("three, no more"); + CHECK(std::count(r.text.begin(), r.text.end(), '@') == 3); + UNSCOPED_INFO("none mapped anywhere"); + REQUIRE_FALSE(r.map.toSource(S32(std::count(r.text.begin(), r.text.begin() + r.text.find("-- @line 2 \"main"), '\n')), 0).found()); const ALPreprocessor::Result plain = ALPreprocessor::run(script, options(true)); - ensure("off: none", plain.text.find("@line") == std::string::npos); + UNSCOPED_INFO("off: none"); + REQUIRE(plain.text.find("@line") == std::string::npos); // LSL: an include's first line, the script's after it, a stretch a // condition left out, a macro over lines. @@ -1972,19 +2503,21 @@ namespace tut ALPreprocessor::Options lsl = options(); 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); - ensure_equals("problems", messages(r), std::string()); - ensure("the reader finds every line: " + why, readsTrue(r, "main.lsl", why)); - ensure("in LSL's comment: " + r.text, r.text.find("// @line 1 \"inc.lsl\"\ninteger x;\n") != std::string::npos); + UNSCOPED_INFO("problems"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("the reader finds every line: " + why); + CHECK(readsTrue(r, "main.lsl", why)); + UNSCOPED_INFO("in LSL's comment: " + r.text); + REQUIRE(r.text.find("// @line 1 \"inc.lsl\"\ninteger x;\n") != std::string::npos); // Nothing moved: none at all. r = ALPreprocessor::run("integer a;\n\ndefault { state_entry() { } }\n", lsl); - ensure("nothing to say: " + r.text, r.text.find("@line") == std::string::npos); + UNSCOPED_INFO("nothing to say: " + r.text); + REQUIRE(r.text.find("@line") == std::string::npos); } - template<> template<> - void alpreprocessor_object::test<50>() + TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: SLua: an unread require is noted with its weight and kept bundled", "[alpreprocessor]") { - set_test_name("SLua: a module required only where nothing reads it is said at each such require, with what it weighs; kept in the bundle, since a require runs it"); add("util", "local M = {}\nfunction M.twice(n) return n * 2 end\nreturn M\n"); add("used", "return 1\n"); const auto notes = [](const ALPreprocessor::Result& r) { @@ -2001,18 +2534,35 @@ namespace tut ALPreprocessor::Result r = ALPreprocessor::run("local util = require(\"util\")\nlocal used = require(\"used\")\nprint(used)\n", options(true)); std::vector said = notes(r); - ensure_equals("one note: " + messages(r), said.size(), size_t(1)); - ensure("a note, at the require", said[0]->severity == ALScriptProblem::Severity::Note && said[0]->line == 0 && said[0]->column == 13); - ensure("naming the local and the module", said[0]->args.size() == 3 && said[0]->args[0] == "util" && said[0]->args[1] == "util"); - ensure("with its weight: " + said[0]->args[2], std::atoi(said[0]->args[2].c_str()) > 0); - ensure("still in the bundle", r.text.find("M.twice") != std::string::npos); - - ensure("a name begun with _ is meant", notes(ALPreprocessor::run("local _util = require(\"util\")\n", options(true))).empty()); - ensure("read through another require of it: removing this one saves nothing", - notes(ALPreprocessor::run("local a = require(\"util\")\nlocal b = require(\"util\")\nprint(b)\n", options(true))).empty()); - ensure("read in an interpolated string", notes(ALPreprocessor::run("local util = require(\"util\")\nprint(`{util}`)\n", options(true))).empty()); - ensure("the require not the whole of the value", notes(ALPreprocessor::run("local util = require(\"util\") or {}\nprint(1)\n", options(true))).empty()); - ensure("a require as a statement, for what it does", notes(ALPreprocessor::run("require(\"util\")\n", options(true))).empty()); + UNSCOPED_INFO("one note: " + messages(r)); + REQUIRE(said.size() == size_t(1)); + { + INFO("a note, at the require"); + REQUIRE(said[0]->severity == ALScriptProblem::Severity::Note); + REQUIRE(said[0]->line == 0); + CHECK(said[0]->column == 13); + } + { + INFO("naming the local and the module"); + REQUIRE(said[0]->args.size() == 3); + REQUIRE(said[0]->args[0] == "util"); + CHECK(said[0]->args[1] == "util"); + } + UNSCOPED_INFO("with its weight: " + said[0]->args[2]); + CHECK(std::atoi(said[0]->args[2].c_str()) > 0); + UNSCOPED_INFO("still in the bundle"); + REQUIRE(r.text.find("M.twice") != std::string::npos); + + UNSCOPED_INFO("a name begun with _ is meant"); + REQUIRE(notes(ALPreprocessor::run("local _util = require(\"util\")\n", options(true))).empty()); + UNSCOPED_INFO("read through another require of it: removing this one saves nothing"); + REQUIRE(notes(ALPreprocessor::run("local a = require(\"util\")\nlocal b = require(\"util\")\nprint(b)\n", options(true))).empty()); + UNSCOPED_INFO("read in an interpolated string"); + REQUIRE(notes(ALPreprocessor::run("local util = require(\"util\")\nprint(`{util}`)\n", options(true))).empty()); + UNSCOPED_INFO("the require not the whole of the value"); + REQUIRE(notes(ALPreprocessor::run("local util = require(\"util\") or {}\nprint(1)\n", options(true))).empty()); + UNSCOPED_INFO("a require as a statement, for what it does"); + REQUIRE(notes(ALPreprocessor::run("require(\"util\")\n", options(true))).empty()); // In a module: said in its file. ALPreprocessor::Include wrap; @@ -2022,7 +2572,13 @@ namespace tut files["wrap"] = wrap; r = ALPreprocessor::run("local w = require(\"wrap\")\nprint(w)\n", options(true)); said = notes(r); - ensure_equals("one, in the module: " + messages(r), said.size(), size_t(1)); - ensure("in its file, at its require", said[0]->file == "disk:/lib/wrap.luau" && said[0]->line == 0 && said[0]->args[0] == "inner"); + UNSCOPED_INFO("one, in the module: " + messages(r)); + REQUIRE(said.size() == size_t(1)); + { + INFO("in its file, at its require"); + REQUIRE(said[0]->file == "disk:/lib/wrap.luau"); + REQUIRE(said[0]->line == 0); + CHECK(said[0]->args[0] == "inner"); + } } } diff --git a/indra/alscript/tests/alpreprocessorc11_test.cpp b/indra/alscript/tests/alpreprocessorc11_test.cpp index 8fd4b1c100..2c9bc1404d 100644 --- a/indra/alscript/tests/alpreprocessorc11_test.cpp +++ b/indra/alscript/tests/alpreprocessorc11_test.cpp @@ -27,7 +27,7 @@ #include "../preprocessor/alpreprocessor.h" -#include "../test/lltut.h" +#include #include #include @@ -40,7 +40,7 @@ // What comes out is compared as tokens, blanks between them made one space, // since how much space stands between two tokens is the implementation's // (6.10.3.2 aside, which the stringizing tests compare exactly). -namespace tut +namespace { struct alpreprocessorc11_data { @@ -152,80 +152,85 @@ namespace tut } }; - typedef test_group alpreprocessorc11_group; - typedef alpreprocessorc11_group::object alpreprocessorc11_object; - alpreprocessorc11_group alpreprocessorc11_instance("alpreprocessorc11"); - - template<> template<> - void alpreprocessorc11_object::test<1>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 5.1.1.2: backslash-newline joins lines first even in names and comments", "[alpreprocessorc11]") { - set_test_name("5.1.1.2: a backslash before a newline joins the lines first, inside a directive's name, a name, a string, a // comment"); - ensure_equals("a directive's name", pp(std::string("#def\\\n" + UNSCOPED_INFO("a directive's name"); + CHECK(pp(std::string("#def\\\n" "ine SPLICED 1\n" - "SPLICED\n")), + "SPLICED\n")) == std::string("1")); - ensure_equals("a name", pp(std::string("#define SPLICED 1\n" + UNSCOPED_INFO("a name"); + CHECK(pp(std::string("#define SPLICED 1\n" "SPLI\\\n" - "CED\n")), + "CED\n")) == std::string("1")); - ensure_equals("but in a string it is Firestorm's newline, which scripts rely on, the line after it", pp(std::string("string s = \"ab\\\n" - "cd\";\n")), + UNSCOPED_INFO("but in a string it is Firestorm's newline, which scripts rely on, the line after it"); + CHECK(pp(std::string("string s = \"ab\\\n" + "cd\";\n")) == std::string("string s = \"ab\\ncd\" ;")); { const ALPreprocessor::Result r = run("// a note \\\nint hidden;\nint shown;\n"); - ensure("a // comment goes on over the join: " + r.text, r.text.find("\nint hidden") == std::string::npos && - r.text.find("int shown;") != std::string::npos); + { + INFO("a // comment goes on over the join: " + r.text); + REQUIRE(r.text.find("\nint hidden") == std::string::npos); + REQUIRE(r.text.find("int shown;") != std::string::npos); + } } - ensure_equals("the lines after keep their numbers", pp(std::string("#define A 1 \\\n" + UNSCOPED_INFO("the lines after keep their numbers"); + CHECK(pp(std::string("#define A 1 \\\n" " + 2\n" - "A __LINE__\n")), + "A __LINE__\n")) == std::string("1 + 2 3")); } - template<> template<> - void alpreprocessorc11_object::test<2>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 5.1.1.2: a comment is one space; in a directive it may run over lines", "[alpreprocessorc11]") { - set_test_name("5.1.1.2: a comment is one space: between tokens, in a macro's body, and over the lines of a directive"); - ensure_equals("between tokens", pp(std::string("a/**/b\n")), + UNSCOPED_INFO("between tokens"); + CHECK(pp(std::string("a/**/b\n")) == std::string("a b")); - ensure_equals("in a body", pp(std::string("#define f(x) x/**/x\n" - "f(1)\n")), + UNSCOPED_INFO("in a body"); + CHECK(pp(std::string("#define f(x) x/**/x\n" + "f(1)\n")) == std::string("1 1")); - ensure_equals("a directive goes on past a comment over lines", pp(std::string("#define LONG 1 /* a\n" + UNSCOPED_INFO("a directive goes on past a comment over lines"); + CHECK(pp(std::string("#define LONG 1 /* a\n" "b */ + 2\n" - "LONG\n")), + "LONG\n")) == std::string("1 + 2")); - ensure_equals("comments at a body's ends are none of it", pp(std::string("#define E /* a */ 1 /* b */\n" - "[E]\n")), + UNSCOPED_INFO("comments at a body's ends are none of it"); + CHECK(pp(std::string("#define E /* a */ 1 /* b */\n" + "[E]\n")) == std::string("[1]")); } - template<> template<> - void alpreprocessorc11_object::test<3>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10: blanks around #; the null directive; # not first on its line is none", "[alpreprocessorc11]") { - set_test_name("6.10: blanks and comments may stand before #, blanks after it; the null directive; # not first on a line is no directive"); - ensure_equals("before and after", pp(std::string(" /* c */ # define X 1\n" - "X\n")), + UNSCOPED_INFO("before and after"); + CHECK(pp(std::string(" /* c */ # define X 1\n" + "X\n")) == std::string("1")); - ensure_equals("the null directive", pp(std::string("#\n" + UNSCOPED_INFO("the null directive"); + CHECK(pp(std::string("#\n" "# /* nothing */\n" - "ok\n")), + "ok\n")) == std::string("ok")); - ensure_equals("# after a token", pp(std::string("x # define Y 2\n" - "Y\n")), + UNSCOPED_INFO("# after a token"); + CHECK(pp(std::string("x # define Y 2\n" + "Y\n")) == std::string("x # define Y 2 Y")); - ensure_equals("a # a macro made is no directive (6.10.3.4)", pp(std::string("#define EMPTY\n" + UNSCOPED_INFO("a # a macro made is no directive (6.10.3.4)"); + CHECK(pp(std::string("#define EMPTY\n" "EMPTY # define W 3\n" - "W\n")), + "W\n")) == std::string("# define W 3 W")); - ensure_equals("a directive's name is as written, case and all", saidOn(run(std::string("#DEFINE Z 1\n"))), std::string("0")); + UNSCOPED_INFO("a directive's name is as written, case and all"); + CHECK(saidOn(run(std::string("#DEFINE Z 1\n"))) == std::string("0")); } - template<> template<> - void alpreprocessorc11_object::test<4>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: in a skipped group only the directives' names are read", "[alpreprocessorc11]") { - set_test_name("6.10.1: in a skipped group only the directives' names are read: any other line, an unknown directive, an unfinished one"); - ensure_equals("nothing said", pp(std::string("#if 0\n" + UNSCOPED_INFO("nothing said"); + CHECK(pp(std::string("#if 0\n" "#foo bar\n" "#include template<> - void alpreprocessorc11_object::test<5>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: defined in both forms; names left over are 0; every #if operator", "[alpreprocessorc11]") { - set_test_name("6.10.1: defined in both forms, names left over are 0, and each operator an #if may have"); - ensure_equals("defined", pp(std::string("#define A\n" + UNSCOPED_INFO("defined"); + CHECK(pp(std::string("#define A\n" "#if defined A && defined(A) && !defined B && defined ( A )\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("a name left over is 0, TRUE too", pp(std::string("#if false || TRUE || sizeof || other\n" + UNSCOPED_INFO("a name left over is 0, TRUE too"); + CHECK(pp(std::string("#if false || TRUE || sizeof || other\n" "no\n" "#else\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("but true is 1, as in C++ and Firestorm's Wave", pp(std::string("#if true && !false\n" + UNSCOPED_INFO("but true is 1, as in C++ and Firestorm's Wave"); + CHECK(pp(std::string("#if true && !false\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("relations", pp(std::string("#if 1 < 2 < 3 && (1 > 2) == 0 && 2 >= 2 && (3 <= 2) == 0 && 1 != 2\n" + UNSCOPED_INFO("relations"); + CHECK(pp(std::string("#if 1 < 2 < 3 && (1 > 2) == 0 && 2 >= 2 && (3 <= 2) == 0 && 1 != 2\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("bits and unary", pp(std::string("#if (7 & 3) == 3 && (4 | 1) == 5 && (6 ^ 3) == 5 && ~0 == -1 && -(-3) == 3 && +4 == 4 && !0 == 1 && !7 == 0\n" + UNSCOPED_INFO("bits and unary"); + CHECK(pp(std::string("#if (7 & 3) == 3 && (4 | 1) == 5 && (6 ^ 3) == 5 && ~0 == -1 && -(-3) == 3 && +4 == 4 && !0 == 1 && !7 == 0\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("arithmetic", pp(std::string("#if 17 % 5 == 2 && -7 / 2 == -3 && -7 % 2 == -1 && 1 << 62 > 0 && (1 << 3) >> 1 == 4 && 2 * 3 + 4 == 10 && 2 + 3 * 4 == 14\n" + UNSCOPED_INFO("arithmetic"); + CHECK(pp(std::string("#if 17 % 5 == 2 && -7 / 2 == -3 && -7 % 2 == -1 && 1 << 62 > 0 && (1 << 3) >> 1 == 4 && 2 * 3 + 4 == 10 && 2 + 3 * 4 == 14\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("integers every way C writes them", pp(std::string("#if 017 == 15 && 0XfF == 255 && 10U == 10 && 10L == 10 && 10ull == 10 && 10uLL == 10 && 0x7FFFFFFFFFFFFFFF > 0\n" + UNSCOPED_INFO("integers every way C writes them"); + CHECK(pp(std::string("#if 017 == 15 && 0XfF == 255 && 10U == 10 && 10L == 10 && 10ull == 10 && 10uLL == 10 && 0x7FFFFFFFFFFFFFFF > 0\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("?: and what it does not work out", pp(std::string("#if (1 ? 2 : 3) == 2 && (0 ? 2 : 3) == 3 && (1 ? 0 : 1 / 0) == 0\n" + UNSCOPED_INFO("?: and what it does not work out"); + CHECK(pp(std::string("#if (1 ? 2 : 3) == 2 && (0 ? 2 : 3) == 3 && (1 ? 0 : 1 / 0) == 0\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("&& and || do not work out their right side where the left decides", pp(std::string("#if 0 && 1 / 0\n" + UNSCOPED_INFO("&& and || do not work out their right side where the left decides"); + CHECK(pp(std::string("#if 0 && 1 / 0\n" "#endif\n" "#if 1 || 1 / 0\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("macros in an #if, function-like too", pp(std::string("#define F(x) ((x) + 1)\n" + UNSCOPED_INFO("macros in an #if, function-like too"); + CHECK(pp(std::string("#define F(x) ((x) + 1)\n" "#define TWO 2\n" "#if F(1) == TWO && F(F(1)) == 3\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); } - template<> template<> - void alpreprocessorc11_object::test<6>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: #if works in intmax_t and uintmax_t; one unsigned operand makes both", "[alpreprocessorc11]") { - set_test_name("6.10.1: an #if works in intmax_t and uintmax_t, and an unsigned operand makes the other unsigned"); - ensure_equals("-1 < 0u is false", pp(std::string("#if -1 < 0u\n" + UNSCOPED_INFO("-1 < 0u is false"); + CHECK(pp(std::string("#if -1 < 0u\n" "no\n" "#else\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("unsigned wraps", pp(std::string("#if 0u - 1 == 18446744073709551615u\n" + UNSCOPED_INFO("unsigned wraps"); + CHECK(pp(std::string("#if 0u - 1 == 18446744073709551615u\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("a decimal too big for intmax_t is unsigned", pp(std::string("#if 18446744073709551615 == -1\n" + UNSCOPED_INFO("a decimal too big for intmax_t is unsigned"); + CHECK(pp(std::string("#if 18446744073709551615 == -1\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("a hex one too", pp(std::string("#if 0xFFFFFFFFFFFFFFFF > 0 && 0xFFFFFFFFFFFFFFFF == -1\n" + UNSCOPED_INFO("a hex one too"); + CHECK(pp(std::string("#if 0xFFFFFFFFFFFFFFFF > 0 && 0xFFFFFFFFFFFFFFFF == -1\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("the least intmax_t", pp(std::string("#if -9223372036854775807 - 1 < 0\n" + UNSCOPED_INFO("the least intmax_t"); + CHECK(pp(std::string("#if -9223372036854775807 - 1 < 0\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("?:'s result is unsigned where either side is", pp(std::string("#if (1 ? -1 : 0u) > 0\n" + UNSCOPED_INFO("?:'s result is unsigned where either side is"); + CHECK(pp(std::string("#if (1 ? -1 : 0u) > 0\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); } - template<> template<> - void alpreprocessorc11_object::test<7>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: an #if that is no integer constant expression is said on its line", "[alpreprocessorc11]") { - set_test_name("6.10.1: an #if refuses what is no integer constant expression, each said on its line"); - ensure_equals("too big, a float, a string, a comma, an assignment, % by zero, a call of no macro, nothing", saidOn(run(std::string("#if 18446744073709551616\n" + UNSCOPED_INFO("too big, a float, a string, a comma, an assignment, % by zero, a call of no macro, nothing"); + CHECK(saidOn(run(std::string("#if 18446744073709551616\n" "#endif\n" "#if 1.0\n" "#endif\n" @@ -357,259 +375,290 @@ namespace tut "#if G(1)\n" "#endif\n" "#if\n" - "#endif\n"))), std::string("0 2 4 6 8 10 12 14")); + "#endif\n"))) == std::string("0 2 4 6 8 10 12 14")); } - template<> template<> - void alpreprocessorc11_object::test<8>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: #if takes character constants with the values C gives them", "[alpreprocessorc11]") { - set_test_name("6.10.1: #if takes character constants, as C gives their values"); - ensure_equals("letters, escapes, octal and hex", pp(std::string("#if 'A' == 65 && '\\n' == 10 && '\\x41' == 65 && '\\0' == 0 && '\\'' == 39 && '\\\\' == 92 && '0' == 48 && '\\101' == 65\n" + UNSCOPED_INFO("letters, escapes, octal and hex"); + CHECK(pp(std::string("#if 'A' == 65 && '\\n' == 10 && '\\x41' == 65 && '\\0' == 0 && '\\'' == 39 && '\\\\' == 92 && '0' == 48 && '\\101' == 65\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); } - template<> template<> - void alpreprocessorc11_object::test<9>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: extra tokens after #ifdef and #ifndef and #else and #endif are said", "[alpreprocessorc11]") { - set_test_name("6.10.1: #ifdef, #ifndef, #else and #endif take nothing more, and something more is said"); - ensure_equals("extra tokens", saidOn(run(std::string("#ifdef A B\n" + UNSCOPED_INFO("extra tokens"); + CHECK(saidOn(run(std::string("#ifdef A B\n" "#endif\n" "#ifndef A B\n" "#endif\n" "#if 1\n" "#else junk\n" "#endif junk\n" - "#undef A B\n"))), std::string("W0 W2 W5 W6 W7")); - ensure_equals("#ifdef needs a name", saidOn(run(std::string("#ifdef\n" + "#undef A B\n"))) == std::string("W0 W2 W5 W6 W7")); + UNSCOPED_INFO("#ifdef needs a name"); + CHECK(saidOn(run(std::string("#ifdef\n" "#endif\n" "#ifdef 1\n" - "#endif\n"))), std::string("0 2")); + "#endif\n"))) == std::string("0 2")); } - template<> template<> - void alpreprocessorc11_object::test<10>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.2: an #include's name may come of macros in quotes or angle brackets", "[alpreprocessorc11]") { - set_test_name("6.10.2: an #include's name may come of macros, in quotes or in angle brackets"); files["lib.lsl"] = "LIB\n"; files["vers2.h"] = "VERS\n"; - ensure_equals("angle brackets", pp(std::string("#define HDR \n" - "#include HDR\n")), + UNSCOPED_INFO("angle brackets"); + CHECK(pp(std::string("#define HDR \n" + "#include HDR\n")) == std::string("LIB")); - ensure("asked in brackets", !asked.empty() && asked.back().name == "lib.lsl" && asked.back().angled); - ensure_equals("quotes", pp(std::string("#define Q \"lib.lsl\"\n" - "#include Q\n")), + { + INFO("asked in brackets"); + REQUIRE_FALSE(asked.empty()); + CHECK(asked.back().name == "lib.lsl"); + CHECK(asked.back().angled); + } + UNSCOPED_INFO("quotes"); + CHECK(pp(std::string("#define Q \"lib.lsl\"\n" + "#include Q\n")) == std::string("LIB")); - ensure("asked in quotes", asked.back().name == "lib.lsl" && !asked.back().angled); - ensure_equals("made by stringizing", pp(std::string("#define STR(x) #x\n" + { + INFO("asked in quotes"); + CHECK(asked.back().name == "lib.lsl"); + CHECK_FALSE(asked.back().angled); + } + UNSCOPED_INFO("made by stringizing"); + CHECK(pp(std::string("#define STR(x) #x\n" "#define XSTR(x) STR(x)\n" "#define INCFILE(n) vers ## n\n" - "#include XSTR(INCFILE(2).h)\n")), + "#include XSTR(INCFILE(2).h)\n")) == std::string("VERS")); - ensure_equals("something after the name", saidOn(run(std::string("#include \"lib.lsl\" extra\n"))), std::string("W0")); - ensure_equals("no name at all", saidOn(run(std::string("#include\n" - "#include 42\n"))), std::string("0 1")); + UNSCOPED_INFO("something after the name"); + CHECK(saidOn(run(std::string("#include \"lib.lsl\" extra\n"))) == std::string("W0")); + UNSCOPED_INFO("no name at all"); + CHECK(saidOn(run(std::string("#include\n" + "#include 42\n"))) == std::string("0 1")); } - template<> template<> - void alpreprocessorc11_object::test<11>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3 EXAMPLE 6: a redefinition the same but for blanks is none; any other is said", "[alpreprocessorc11]") { - set_test_name("6.10.3 EXAMPLE 6: a redefinition the same but for blanks is none; any other is said"); - ensure_equals("valid ones say nothing", saidOn(run(std::string("#define OBJ_LIKE (1-1)\n" + UNSCOPED_INFO("valid ones say nothing"); + CHECK(saidOn(run(std::string("#define OBJ_LIKE (1-1)\n" "#define OBJ_LIKE /* white space */ (1-1) /* other */\n" "#define FUNC_LIKE(a) ( a )\n" "#define FUNC_LIKE( a )( /* note the white space */ \\\n" " a /* other stuff on this line\n" - " */ )\n"))), std::string("")); - ensure_equals("invalid ones are each said", saidOn(run(std::string("#define OBJ_LIKE (1-1)\n" + " */ )\n"))) == std::string("")); + UNSCOPED_INFO("invalid ones are each said"); + CHECK(saidOn(run(std::string("#define OBJ_LIKE (1-1)\n" "#define FUNC_LIKE(a) ( a )\n" "#define OBJ_LIKE (0)\n" "#define OBJ_LIKE (1 - 1)\n" "#define FUNC_LIKE(b) ( a )\n" - "#define FUNC_LIKE(b) ( b )\n"))), std::string("W2 W3 W4 W5")); + "#define FUNC_LIKE(b) ( b )\n"))) == std::string("W2 W3 W4 W5")); } - template<> template<> - void alpreprocessorc11_object::test<12>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3: a ( right after the name makes it function-like; uncalled it is a name", "[alpreprocessorc11]") { - set_test_name("6.10.3: object-like or function-like by a ( straight after the name; a function-like name without ( is a name"); - ensure_equals("a space before ( makes it object-like", pp(std::string("#define f (x)\n" - "f\n")), + UNSCOPED_INFO("a space before ( makes it object-like"); + CHECK(pp(std::string("#define f (x)\n" + "f\n")) == std::string("(x)")); - ensure_equals("a body straight after the name", pp(std::string("#define X+1\n" - "[X]\n")), + UNSCOPED_INFO("a body straight after the name"); + CHECK(pp(std::string("#define X+1\n" + "[X]\n")) == std::string("[+1]")); - ensure_equals("a name alone, at the end or before something else", pp(std::string("#define f(x) \n" - "f; f\n")), + UNSCOPED_INFO("a name alone, at the end or before something else"); + CHECK(pp(std::string("#define f(x) \n" + "f; f\n")) == std::string("f; f")); - ensure_equals("the ( may be on the next line", pp(std::string("#define f(x) \n" + UNSCOPED_INFO("the ( may be on the next line"); + CHECK(pp(std::string("#define f(x) \n" "f\n" - "(1)\n")), + "(1)\n")) == std::string("<1>")); - ensure_equals("a ( that a macro makes is not the invocation's", pp(std::string("#define f(x) \n" + UNSCOPED_INFO("a ( that a macro makes is not the invocation's"); + CHECK(pp(std::string("#define f(x) \n" "#define LP (\n" - "f LP 1)\n")), + "f LP 1)\n")) == std::string("f ( 1)")); } - template<> template<> - void alpreprocessorc11_object::test<13>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3: arguments part at commas not in parentheses; empty counts; arity said", "[alpreprocessorc11]") { - set_test_name("6.10.3: arguments: commas inside parentheses stay, inside brackets and braces they part; an empty argument is one; too many or few are said"); - ensure_equals("parentheses", pp(std::string("#define N(a) \n" - "N((1, 2))\n")), + UNSCOPED_INFO("parentheses"); + CHECK(pp(std::string("#define N(a) \n" + "N((1, 2))\n")) == std::string("<(1, 2)>")); - ensure_equals("brackets and braces", pp(std::string("#define M(a, b) \n" - "M([1, 2]) M({1, 2})\n")), + UNSCOPED_INFO("brackets and braces"); + CHECK(pp(std::string("#define M(a, b) \n" + "M([1, 2]) M({1, 2})\n")) == std::string("<[1|2]> <{1|2}>")); - ensure_equals("empty", pp(std::string("#define N(a) \n" + UNSCOPED_INFO("empty"); + CHECK(pp(std::string("#define N(a) \n" "#define Z() 0\n" - "N() Z() Z( )\n")), + "N() Z() Z( )\n")) == std::string("<> 0 0")); - ensure_equals("over lines", pp(std::string("#define M(a, b) \n" + UNSCOPED_INFO("over lines"); + CHECK(pp(std::string("#define M(a, b) \n" "M(1,\n" "2\n" - ")\n")), + ")\n")) == std::string("<1|2>")); - ensure_equals("arity", saidOn(run(std::string("#define Z() 0\n" + UNSCOPED_INFO("arity"); + CHECK(saidOn(run(std::string("#define Z() 0\n" "#define M(a, b) \n" "Z(1)\n" "M(1)\n" - "M(1, 2, 3)\n"))), std::string("2 3 4")); + "M(1, 2, 3)\n"))) == std::string("2 3 4")); } - template<> template<> - void alpreprocessorc11_object::test<14>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3: __VA_ARGS__ is a variadic macro's alone; it and defined are no macros", "[alpreprocessorc11]") { - set_test_name("6.10.3: __VA_ARGS__ is a variadic macro's alone, and neither it nor defined may be a macro"); - ensure_equals("variadic", pp(std::string("#define V(...) [__VA_ARGS__]\n" - "V() V(a) V(a, b) V((a, b), c)\n")), + UNSCOPED_INFO("variadic"); + CHECK(pp(std::string("#define V(...) [__VA_ARGS__]\n" + "V() V(a) V(a, b) V((a, b), c)\n")) == std::string("[] [a] [a, b] [(a, b), c]")); - ensure_equals("after named parameters", pp(std::string("#define W(a, ...) [a|__VA_ARGS__]\n" - "W(1,) W(1, 2, 3)\n")), + UNSCOPED_INFO("after named parameters"); + CHECK(pp(std::string("#define W(a, ...) [a|__VA_ARGS__]\n" + "W(1,) W(1, 2, 3)\n")) == std::string("[1|] [1|2, 3]")); - ensure_equals("refused", saidOn(run(std::string("#define A(x) __VA_ARGS__\n" + UNSCOPED_INFO("refused"); + CHECK(saidOn(run(std::string("#define A(x) __VA_ARGS__\n" "#define __VA_ARGS__ 1\n" "#define defined 1\n" "#undef defined\n" - "#define B(__VA_ARGS__) 1\n"))), std::string("0 1 2 3 4")); + "#define B(__VA_ARGS__) 1\n"))) == std::string("0 1 2 3 4")); } - template<> template<> - void alpreprocessorc11_object::test<15>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.1: an argument is expanded first on its own unless # or ## is beside it", "[alpreprocessorc11]") { - set_test_name("6.10.3.1: an argument is expanded first, on its own, unless # or ## is beside it"); - ensure_equals("beside # and ##, not", pp(std::string("#define STR(x) #x\n" + UNSCOPED_INFO("beside # and ##, not"); + CHECK(pp(std::string("#define STR(x) #x\n" "#define XSTR(x) STR(x)\n" "#define CAT(a, b) a ## b\n" "#define XCAT(a, b) CAT(a, b)\n" "#define FOUR 4\n" - "STR(FOUR) XSTR(FOUR) CAT(FOUR, x) XCAT(FOUR, x)\n")), + "STR(FOUR) XSTR(FOUR) CAT(FOUR, x) XCAT(FOUR, x)\n")) == std::string("\"FOUR\" \"4\" FOURx 4x")); - ensure_equals("on its own: a name at an argument's end takes nothing after the invocation", pp(std::string("#define id(x) x\n" + UNSCOPED_INFO("on its own: a name at an argument's end takes nothing after the invocation"); + CHECK(pp(std::string("#define id(x) x\n" "#define h() H\n" - "id(h)()\n")), + "id(h)()\n")) == std::string("H")); - ensure_equals("a comma an argument makes parts the arguments of the macro it goes on to", pp(std::string("#define COMMA ,\n" + UNSCOPED_INFO("a comma an argument makes parts the arguments of the macro it goes on to"); + CHECK(pp(std::string("#define COMMA ,\n" "#define f(a, b) [a|b]\n" "#define g(x) f(x)\n" - "g(1 COMMA 2)\n")), + "g(1 COMMA 2)\n")) == std::string("[1|2]")); } - template<> template<> - void alpreprocessorc11_object::test<16>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.2: # makes one string with blanks made one and literals escaped", "[alpreprocessorc11]") { - set_test_name("6.10.3.2: # makes one string: blanks at the ends dropped, those inside one space, a literal's quotes and backslashes escaped"); - ensure_equals("blanks", pp(std::string("#define STR(x) #x\n" - "STR( a + b )\n")), + UNSCOPED_INFO("blanks"); + CHECK(pp(std::string("#define STR(x) #x\n" + "STR( a + b )\n")) == std::string("\"a + b\"")); - ensure_equals("a comment is a blank", pp(std::string("#define STR(x) #x\n" - "STR(a/**/b)\n")), + UNSCOPED_INFO("a comment is a blank"); + CHECK(pp(std::string("#define STR(x) #x\n" + "STR(a/**/b)\n")) == std::string("\"a b\"")); - ensure_equals("a newline is a blank", pp(std::string("#define STR(x) #x\n" + UNSCOPED_INFO("a newline is a blank"); + CHECK(pp(std::string("#define STR(x) #x\n" "STR( a\n" - " b )\n")), + " b )\n")) == std::string("\"a b\"")); - ensure_equals("no blank where none was", pp(std::string("#define STR(x) #x\n" - "STR(a+b)\n")), + UNSCOPED_INFO("no blank where none was"); + CHECK(pp(std::string("#define STR(x) #x\n" + "STR(a+b)\n")) == std::string("\"a+b\"")); - ensure_equals("a string's quotes and backslashes", pp(std::string("#define STR(x) #x\n" - "STR(\"a\\n\")\n")), + UNSCOPED_INFO("a string's quotes and backslashes"); + CHECK(pp(std::string("#define STR(x) #x\n" + "STR(\"a\\n\")\n")) == std::string("\"\\\"a\\\\n\\\"\"")); - ensure_equals("a character's", pp(std::string("#define STR(x) #x\n" - "STR('\"') STR('\\'')\n")), + UNSCOPED_INFO("a character's"); + CHECK(pp(std::string("#define STR(x) #x\n" + "STR('\"') STR('\\'')\n")) == std::string("\"'\\\"'\" \"'\\\\''\"")); - ensure_equals("outside a literal, a backslash is as it is", pp(std::string("#define STR(x) #x\n" - "STR(: @\\n)\n")), + UNSCOPED_INFO("outside a literal, a backslash is as it is"); + CHECK(pp(std::string("#define STR(x) #x\n" + "STR(: @\\n)\n")) == std::string("\": @\\n\"")); - ensure_equals("nothing", pp(std::string("#define STR(x) #x\n" - "STR()\n")), + UNSCOPED_INFO("nothing"); + CHECK(pp(std::string("#define STR(x) #x\n" + "STR()\n")) == std::string("\"\"")); } - template<> template<> - void alpreprocessorc11_object::test<17>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.3: ## pastes; an empty argument is a placemarker; the result rescans", "[alpreprocessorc11]") { - set_test_name("6.10.3.3: ## pastes, an empty argument is a placemarker, and what it makes is expanded again"); - ensure_equals("tokens", pp(std::string("#define CAT(a, b) a ## b\n" - "CAT(ll, Say) CAT(-, -) CAT(<, <=) CAT(1, 2) CAT(+, =)\n")), + UNSCOPED_INFO("tokens"); + CHECK(pp(std::string("#define CAT(a, b) a ## b\n" + "CAT(ll, Say) CAT(-, -) CAT(<, <=) CAT(1, 2) CAT(+, =)\n")) == std::string("llSay -- <<= 12 +=")); - ensure_equals("a name made is expanded", pp(std::string("#define CAT(a, b) a ## b\n" + UNSCOPED_INFO("a name made is expanded"); + CHECK(pp(std::string("#define CAT(a, b) a ## b\n" "#define AB 42\n" - "CAT(A, B)\n")), + "CAT(A, B)\n")) == std::string("42")); - ensure_equals("empty on either side", pp(std::string("#define CAT(a, b) a ## b\n" - "[CAT(, x)] [CAT(x, )] [CAT(, )]\n")), + UNSCOPED_INFO("empty on either side"); + CHECK(pp(std::string("#define CAT(a, b) a ## b\n" + "[CAT(, x)] [CAT(x, )] [CAT(, )]\n")) == std::string("[x] [x] []")); - ensure_equals("EXAMPLE 5", pp(std::string("#define t(x,y,z) x ## y ## z\n" + UNSCOPED_INFO("EXAMPLE 5"); + CHECK(pp(std::string("#define t(x,y,z) x ## y ## z\n" "int j[] = { t(1,2,3), t(,4,5), t(6,,7), t(8,9,),\n" - " t(10,,), t(,11,), t(,,12), t(,,) };\n")), + " t(10,,), t(,11,), t(,,12), t(,,) };\n")) == std::string("int j[] = { 123, 45, 67, 89, 10, 11, 12, };")); - ensure_equals("the EXAMPLE's hash_hash: ## made of # ## # is no operator", pp(std::string("#define hash_hash # ## #\n" + UNSCOPED_INFO("the EXAMPLE's hash_hash: ## made of # ## # is no operator"); + CHECK(pp(std::string("#define hash_hash # ## #\n" "#define mkstr(a) # a\n" "#define in_between(a) mkstr(a)\n" "#define join(c, d) in_between(c hash_hash d)\n" - "char p[] = join(x, y);\n")), + "char p[] = join(x, y);\n")) == std::string("char p[] = \"x ## y\";")); - ensure_equals("a paste that makes no one token is said", saidOn(run(std::string("#define CAT(a, b) a ## b\n" + UNSCOPED_INFO("a paste that makes no one token is said"); + CHECK(saidOn(run(std::string("#define CAT(a, b) a ## b\n" "CAT(x, +)\n" - "CAT(\"a\", \"b\")\n"))), std::string("1 2")); + "CAT(\"a\", \"b\")\n"))) == std::string("1 2")); } - template<> template<> - void alpreprocessorc11_object::test<18>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.4: a macro's own name stays a name; one handed on takes later arguments", "[alpreprocessorc11]") { - set_test_name("6.10.3.4: rescanning: a macro's own name is not expanded again, even later, and one it hands on takes its arguments from what follows"); - ensure_equals("the EXAMPLE", pp(std::string("#define f(a) a*g\n" + UNSCOPED_INFO("the EXAMPLE"); + CHECK(pp(std::string("#define f(a) a*g\n" "#define g(a) f(a)\n" - "f(2)(9)\n")), + "f(2)(9)\n")) == std::string("2*9*g")); - ensure_equals("mutual", pp(std::string("#define x y\n" + UNSCOPED_INFO("mutual"); + CHECK(pp(std::string("#define x y\n" "#define y x\n" - "x y\n")), + "x y\n")) == std::string("x y")); - ensure_equals("painted: a name met while its macro was being replaced stays a name", pp(std::string("#define obj(x) x obj\n" - "obj(obj)(1)\n")), + UNSCOPED_INFO("painted: a name met while its macro was being replaced stays a name"); + CHECK(pp(std::string("#define obj(x) x obj\n" + "obj(obj)(1)\n")) == std::string("obj obj(1)")); - ensure_equals("a name handed on finds its ( in the source", pp(std::string("#define f(x) \n" + UNSCOPED_INFO("a name handed on finds its ( in the source"); + CHECK(pp(std::string("#define f(x) \n" "#define g f\n" "g(1) g\n" - "(2)\n")), + "(2)\n")) == std::string("<1> <2>")); - ensure_equals("and through two macros", pp(std::string("#define NIL(xxx) xxx\n" + UNSCOPED_INFO("and through two macros"); + CHECK(pp(std::string("#define NIL(xxx) xxx\n" "#define G_0(arg) NIL(G_1)(arg)\n" "#define G_1(arg) NIL(arg)\n" - "G_0(42)\n")), + "G_0(42)\n")) == std::string("42")); } - template<> template<> - void alpreprocessorc11_object::test<19>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.5 EXAMPLE 3: macros naming themselves or in arguments; placemarkers; #", "[alpreprocessorc11]") { - set_test_name("6.10.3.5 EXAMPLE 3: macros that name themselves, macros in arguments, placemarkers and #"); - ensure_equals("the whole", pp(std::string("#define x 3\n" + UNSCOPED_INFO("the whole"); + CHECK(pp(std::string("#define x 3\n" "#define f(a) f(x * (a))\n" "#undef x\n" "#define x 2\n" @@ -627,15 +676,14 @@ namespace tut "g(x+(3,4)-w) | h 5) & m\n" "(f)^m(m);\n" "p() i[q()] = { q(1), r(2,3), r(4,), r(,5), r(,) };\n" - "char c[2][6] = { str(hello), str() };\n")), + "char c[2][6] = { str(hello), str() };\n")) == std::string("f(2 * (y+1)) + f(2 * (f(2 * (z[0])))) % f(2 * (0)) + t(1); f(2 * (2+(3,4)-0,1)) | f(2 * (~ 5)) & f(2 * (0,1))^m(0,1); int i[] = { 1, 23, 4, 5, }; char c[2][6] = { \"hello\", \"\" };")); } - template<> template<> - void alpreprocessorc11_object::test<20>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.5 EXAMPLE 4: # and ## and macros made of both", "[alpreprocessorc11]") { - set_test_name("6.10.3.5 EXAMPLE 4: #, ##, and macros made of both"); - ensure_equals("debug, fputs, glue", pp(std::string("#define str(s) # s\n" + UNSCOPED_INFO("debug, fputs, glue"); + CHECK(pp(std::string("#define str(s) # s\n" "#define xstr(s) str(s)\n" "#define debug(s, t) printf(\"x\" # s \"= %d, x\" # t \"= %s\", \\\n" " x ## s, x ## t)\n" @@ -648,193 +696,201 @@ namespace tut "fputs(str(strncmp(\"abc\\0d\", \"abc\", '\\4') // this goes away\n" " == 0) str(: @\\n), s);\n" "glue(HIGH, LOW);\n" - "xglue(HIGH, LOW)\n")), + "xglue(HIGH, LOW)\n")) == std::string("printf(\"x\" \"1\" \"= %d, x\" \"2\" \"= %s\", x1, x2); fputs(\"strncmp(\\\"abc\\\\0d\\\", \\\"abc\\\", '\\\\4') == 0\" \": @\\n\", s); \"hello\"; \"hello\" \", world\"")); } - template<> template<> - void alpreprocessorc11_object::test<21>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.5 EXAMPLE 7: variadic macros", "[alpreprocessorc11]") { - set_test_name("6.10.3.5 EXAMPLE 7: variadic macros"); - ensure_equals("debug, showlist, report", pp(std::string("#define debug(...) fprintf(stderr, __VA_ARGS__)\n" + UNSCOPED_INFO("debug, showlist, report"); + CHECK(pp(std::string("#define debug(...) fprintf(stderr, __VA_ARGS__)\n" "#define showlist(...) puts(#__VA_ARGS__)\n" "#define report(test, ...) ((test)?puts(#test):\\\n" " printf(__VA_ARGS__))\n" "debug(\"Flag\");\n" "debug(\"X = %d\\n\", x);\n" "showlist(The first, second, and third items.);\n" - "report(x>y, \"x is %d but y is %d\", x, y);\n")), + "report(x>y, \"x is %d but y is %d\", x, y);\n")) == std::string("fprintf(stderr, \"Flag\"); fprintf(stderr, \"X = %d\\n\", x); puts(\"The first, second, and third items.\"); ((x>y)?puts(\"x>y\"): printf(\"x is %d but y is %d\", x, y));")); } - template<> template<> - void alpreprocessorc11_object::test<22>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.4: #line sets __LINE__ and __FILE__ even from macros; a bad one is said", "[alpreprocessorc11]") { - set_test_name("6.10.4: #line sets the next line's __LINE__, and __FILE__ with a name; its numbers may come of macros; out of range is said"); - ensure_equals("a number", pp(std::string("#line 100\n" + UNSCOPED_INFO("a number"); + CHECK(pp(std::string("#line 100\n" "__LINE__\n" - "__LINE__\n")), + "__LINE__\n")) == std::string("100 101")); - ensure_equals("and a name", pp(std::string("#line 7 \"other.lsl\"\n" - "__LINE__ __FILE__\n")), + UNSCOPED_INFO("and a name"); + CHECK(pp(std::string("#line 7 \"other.lsl\"\n" + "__LINE__ __FILE__\n")) == std::string("7 \"other.lsl\"")); - ensure_equals("of macros", pp(std::string("#define L 40\n" + UNSCOPED_INFO("of macros"); + CHECK(pp(std::string("#define L 40\n" "#line L\n" - "__LINE__\n")), + "__LINE__\n")) == std::string("40")); - ensure_equals("refused", saidOn(run(std::string("#line 0\n" + UNSCOPED_INFO("refused"); + CHECK(saidOn(run(std::string("#line 0\n" "#line abc\n" "#line\n" - "#line 10 name\n"))), std::string("0 1 2 3")); + "#line 10 name\n"))) == std::string("0 1 2 3")); } - template<> template<> - void alpreprocessorc11_object::test<23>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.5 to 6.10.7: #error goes on; unknown #pragmas pass; # alone is nothing", "[alpreprocessorc11]") { - set_test_name("6.10.5, 6.10.6, 6.10.7: #error says its words and the run goes on; a #pragma not known is passed over; # alone is nothing"); { const ALPreprocessor::Result r = run("#error one two\nafter\n"); - ensure_equals("said as written", messages(r), std::string("E 0: one two\n")); - ensure_equals("and on", flat(r.text), std::string("after")); + UNSCOPED_INFO("said as written"); + CHECK(messages(r) == std::string("E 0: one two\n")); + UNSCOPED_INFO("and on"); + CHECK(flat(r.text) == std::string("after")); } - ensure_equals("pragmas passed over", pp(std::string("#pragma STDC FP_CONTRACT ON\n" + UNSCOPED_INFO("pragmas passed over"); + CHECK(pp(std::string("#pragma STDC FP_CONTRACT ON\n" "#pragma whatever it says\n" - "ok\n")), + "ok\n")) == std::string("ok")); - ensure_equals("the null directive", pp(std::string("#\n" - "ok\n")), + UNSCOPED_INFO("the null directive"); + CHECK(pp(std::string("#\n" + "ok\n")) == std::string("ok")); } - template<> template<> - void alpreprocessorc11_object::test<24>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.8: __LINE__ __FILE__ __DATE__ __TIME__ as C has them; each is defined", "[alpreprocessorc11]") { - set_test_name("6.10.8: __LINE__ and __FILE__ where they stand, __DATE__ and __TIME__ as C writes them, and #ifdef knows each"); - ensure_equals("lines from 1", pp(std::string("__LINE__\n" + UNSCOPED_INFO("lines from 1"); + CHECK(pp(std::string("__LINE__\n" "\n" - "__LINE__\n")), + "__LINE__\n")) == std::string("1 3")); - ensure_equals("defined", pp(std::string("#if defined(__LINE__) && defined __FILE__ && defined(__DATE__) && defined __TIME__\n" + UNSCOPED_INFO("defined"); + CHECK(pp(std::string("#if defined(__LINE__) && defined __FILE__ && defined(__DATE__) && defined __TIME__\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); { const std::string text = flat(run("__DATE__ __TIME__\n").text); - ensure("\"Mmm dd yyyy\" \"hh:mm:ss\", a day under 10 with a space: " + text, - std::regex_match(text, std::regex("\"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [ 123][0-9] [0-9]{4}\" \"[0-2][0-9]:[0-5][0-9]:[0-6][0-9]\""))); + UNSCOPED_INFO("\"Mmm dd yyyy\" \"hh:mm:ss\", a day under 10 with a space: " + text); + CHECK(std::regex_match(text, std::regex("\"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [ 123][0-9] [0-9]{4}\" \"[0-2][0-9]:[0-5][0-9]:[0-6][0-9]\""))); } } - template<> template<> - void alpreprocessorc11_object::test<25>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.9: _Pragma is #pragma as an operator a macro can make", "[alpreprocessorc11]") { - set_test_name("6.10.9: _Pragma is #pragma as an operator, where a macro can make it"); files["p.lsl"] = "_Pragma(\"once\")\nP\n"; - ensure_equals("once", pp(std::string("#include \"p.lsl\"\n" - "#include \"p.lsl\"\n")), + UNSCOPED_INFO("once"); + CHECK(pp(std::string("#include \"p.lsl\"\n" + "#include \"p.lsl\"\n")) == std::string("P")); - ensure_equals("made by a macro, and gone from the text", pp(std::string("#define DO(x) _Pragma(#x)\n" - "DO(whatever) ok\n")), + UNSCOPED_INFO("made by a macro, and gone from the text"); + CHECK(pp(std::string("#define DO(x) _Pragma(#x)\n" + "DO(whatever) ok\n")) == std::string("ok")); } - template<> template<> - void alpreprocessorc11_object::test<26>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: tokens a macro sets side by side stay apart: -f(1) is two minuses", "[alpreprocessorc11]") { - set_test_name("the text keeps apart tokens that a macro set beside each other: -f(1) is still two minuses"); - ensure_equals("a minus before a macro's", pp(std::string("#define f(x) -x\n" - "-f(1)\n")), + UNSCOPED_INFO("a minus before a macro's"); + CHECK(pp(std::string("#define f(x) -x\n" + "-f(1)\n")) == std::string("- -1")); - ensure_equals("a plus", pp(std::string("#define PLUS +\n" - "+PLUS\n")), + UNSCOPED_INFO("a plus"); + CHECK(pp(std::string("#define PLUS +\n" + "+PLUS\n")) == std::string("+ +")); - ensure_equals("an empty macro between two", pp(std::string("#define E\n" - "x E-E-y\n")), + UNSCOPED_INFO("an empty macro between two"); + CHECK(pp(std::string("#define E\n" + "x E-E-y\n")) == std::string("x - -y")); - ensure_equals("two invocations", pp(std::string("#define I(x) x\n" - "I(-)I(-)\n")), + UNSCOPED_INFO("two invocations"); + CHECK(pp(std::string("#define I(x) x\n" + "I(-)I(-)\n")) == std::string("- -")); - ensure_equals("names", pp(std::string("#define A a\n" + UNSCOPED_INFO("names"); + CHECK(pp(std::string("#define A a\n" "#define B b\n" - "A B\n")), + "A B\n")) == std::string("a b")); - ensure_equals("a paste means it", pp(std::string("#define CAT(a, b) a ## b\n" - "CAT(-, -)\n")), + UNSCOPED_INFO("a paste means it"); + CHECK(pp(std::string("#define CAT(a, b) a ## b\n" + "CAT(-, -)\n")) == std::string("--")); } - template<> template<> - void alpreprocessorc11_object::test<27>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.4: an empty macro defers an expansion that one more scan makes", "[alpreprocessorc11]") { - set_test_name("6.10.3.4: expansion deferred by an empty macro, and made to happen by a macro that rescans"); - ensure_equals("deferred", pp(std::string("#define EMPTY()\n" + UNSCOPED_INFO("deferred"); + CHECK(pp(std::string("#define EMPTY()\n" "#define DEFER(id) id EMPTY()\n" "#define A() 123\n" - "DEFER(A)()\n")), + "DEFER(A)()\n")) == std::string("A ()")); - ensure_equals("and expanded by one more scan", pp(std::string("#define EMPTY()\n" + UNSCOPED_INFO("and expanded by one more scan"); + CHECK(pp(std::string("#define EMPTY()\n" "#define DEFER(id) id EMPTY()\n" "#define EXPAND(...) __VA_ARGS__\n" "#define A() 123\n" - "EXPAND(DEFER(A)())\n")), + "EXPAND(DEFER(A)())\n")) == std::string("123")); - ensure_equals("a macro passed as an argument and called in the body", pp(std::string("#define apply(m, x) m(x)\n" + UNSCOPED_INFO("a macro passed as an argument and called in the body"); + CHECK(pp(std::string("#define apply(m, x) m(x)\n" "#define sq(x) ((x)*(x))\n" - "apply(sq, 3)\n")), + "apply(sq, 3)\n")) == std::string("((3)*(3))")); - ensure_equals("each other through arguments", pp(std::string("#define f(x) g(x)\n" + UNSCOPED_INFO("each other through arguments"); + CHECK(pp(std::string("#define f(x) g(x)\n" "#define g(x) f(x)\n" - "f(1) g(2)\n")), + "f(1) g(2)\n")) == std::string("f(1) g(2)")); } - template<> template<> - void alpreprocessorc11_object::test<28>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.2: #__VA_ARGS__ is the arguments with their commas and blanks made one", "[alpreprocessorc11]") { - set_test_name("6.10.3.2: # of __VA_ARGS__: the arguments and their commas, blanks as written between them made one"); - ensure_equals("none", pp(std::string("#define S(...) #__VA_ARGS__\n" - "S()\n")), + UNSCOPED_INFO("none"); + CHECK(pp(std::string("#define S(...) #__VA_ARGS__\n" + "S()\n")) == std::string("\"\"")); - ensure_equals("commas kept as written", pp(std::string("#define S(...) #__VA_ARGS__\n" - "S(a,b) S( a , b ) S(a, b)\n")), + UNSCOPED_INFO("commas kept as written"); + CHECK(pp(std::string("#define S(...) #__VA_ARGS__\n" + "S(a,b) S( a , b ) S(a, b)\n")) == std::string("\"a,b\" \"a , b\" \"a, b\"")); - ensure_equals("a string among them", pp(std::string("#define S(...) #__VA_ARGS__\n" - "S(\"x\", y)\n")), + UNSCOPED_INFO("a string among them"); + CHECK(pp(std::string("#define S(...) #__VA_ARGS__\n" + "S(\"x\", y)\n")) == std::string("\"\\\"x\\\", y\"")); } - template<> template<> - void alpreprocessorc11_object::test<29>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3.3: a paste making // or two tokens is said and leaves them as they were", "[alpreprocessorc11]") { - set_test_name("6.10.3.3: a paste that makes a comment's start, or any two tokens, is said, and the tokens are left as they were"); - ensure_equals("// and a name with a mark", saidOn(run(std::string("#define CAT(a, b) a ## b\n" + UNSCOPED_INFO("// and a name with a mark"); + CHECK(saidOn(run(std::string("#define CAT(a, b) a ## b\n" "CAT(/, /)\n" - "CAT(x, .)\n"))), std::string("1 2")); + "CAT(x, .)\n"))) == std::string("1 2")); } - template<> template<> - void alpreprocessorc11_object::test<30>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: #ifdef knows function-like and predefined macros; #undef says nothing", "[alpreprocessorc11]") { - set_test_name("6.10.1: #ifdef knows a function-like macro and a predefined one; #undef and a new body say nothing"); - ensure_equals("defined", pp(std::string("#define F(x) x\n" + UNSCOPED_INFO("defined"); + CHECK(pp(std::string("#define F(x) x\n" "#ifdef F\n" "yes\n" "#endif\n" "#ifndef __LINE__\n" "no\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("undef, then any body", saidOn(run(std::string("#define A 1\n" + UNSCOPED_INFO("undef, then any body"); + CHECK(saidOn(run(std::string("#define A 1\n" "#undef A\n" "#define A 2\n" - "#undef NEVER_DEFINED\n"))), std::string("")); + "#undef NEVER_DEFINED\n"))) == std::string("")); } - template<> template<> - void alpreprocessorc11_object::test<31>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: #elif chains take the first true group and skip nested groups whole", "[alpreprocessorc11]") { - set_test_name("6.10.1: #elif and #else chains: the first true group only, nested groups inside skipped ones skipped whole"); - ensure_equals("a chain", pp(std::string("#define V 3\n" + UNSCOPED_INFO("a chain"); + CHECK(pp(std::string("#define V 3\n" "#if V == 1\n" "one\n" "#elif V == 2\n" @@ -845,9 +901,10 @@ namespace tut "again\n" "#else\n" "else\n" - "#endif\n")), + "#endif\n")) == std::string("three")); - ensure_equals("nested", pp(std::string("#if 1\n" + UNSCOPED_INFO("nested"); + CHECK(pp(std::string("#if 1\n" "#if 0\n" "a\n" "#elif 1\n" @@ -862,78 +919,79 @@ namespace tut "#if 1\n" "e\n" "#endif\n" - "#endif\n")), + "#endif\n")) == std::string("b d")); - ensure_equals("an #elif with nothing, where it is worked out", saidOn(run(std::string("#if 0\n" + UNSCOPED_INFO("an #elif with nothing, where it is worked out"); + CHECK(saidOn(run(std::string("#if 0\n" "#elif\n" - "#endif\n"))), std::string("1")); - ensure_equals("but not where it is skipped", saidOn(run(std::string("#if 1\n" + "#endif\n"))) == std::string("1")); + UNSCOPED_INFO("but not where it is skipped"); + CHECK(saidOn(run(std::string("#if 1\n" "#elif\n" - "#endif\n"))), std::string("")); + "#endif\n"))) == std::string("")); } - template<> template<> - void alpreprocessorc11_object::test<32>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.1: unsigned shift and divide; GCC's negative right shift; bare 0x refused", "[alpreprocessorc11]") { - set_test_name("6.10.1: more integer arithmetic: unsigned shifts and division, a negative right shift as GCC and Wave do it, a bare 0x refused"); - ensure_equals("unsigned", pp(std::string("#if 1u << 63 > 0 && 18446744073709551615u / 2 == 9223372036854775807 && (0u - 1) >> 63 == 1\n" + UNSCOPED_INFO("unsigned"); + CHECK(pp(std::string("#if 1u << 63 > 0 && 18446744073709551615u / 2 == 9223372036854775807 && (0u - 1) >> 63 == 1\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("signed", pp(std::string("#if -1 >> 1 == -1 && (-1) / 2 == 0 && -8 >> 1 == -4\n" + UNSCOPED_INFO("signed"); + CHECK(pp(std::string("#if -1 >> 1 == -1 && (-1) / 2 == 0 && -8 >> 1 == -4\n" "yes\n" - "#endif\n")), + "#endif\n")) == std::string("yes")); - ensure_equals("0x alone, and a suffix C has not got", saidOn(run(std::string("#if 0x\n" + UNSCOPED_INFO("0x alone, and a suffix C has not got"); + CHECK(saidOn(run(std::string("#if 0x\n" "#endif\n" "#if 10lul\n" - "#endif\n"))), std::string("0 2")); + "#endif\n"))) == std::string("0 2")); } - template<> template<> - void alpreprocessorc11_object::test<33>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.2: an include has its own lines and name until the file it returns to", "[alpreprocessorc11]") { - set_test_name("6.10.2: an include's own lines and name, and the file it came back to after it"); files["inc.lsl"] = "inc __LINE__ __FILE__\n#define FROM_INC 1\n"; - ensure_equals("inside and after", pp(std::string("one\n" + UNSCOPED_INFO("inside and after"); + CHECK(pp(std::string("one\n" "#include \"inc.lsl\"\n" - "__LINE__ __FILE__ FROM_INC\n")), + "__LINE__ __FILE__ FROM_INC\n")) == std::string("one inc 1 \"inc.lsl\" 3 \"main.lsl\" 1")); files["half.lsl"] = "#define f(x) \nf(\n"; - ensure_equals("an invocation cannot run out of an include", saidOn(run(std::string("#include \"half.lsl\"\n" - "1)\n"))), std::string("1")); + UNSCOPED_INFO("an invocation cannot run out of an include"); + CHECK(saidOn(run(std::string("#include \"half.lsl\"\n" + "1)\n"))) == std::string("1")); } - template<> template<> - void alpreprocessorc11_object::test<34>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: files that end without a newline: a directive or invocation or conditional", "[alpreprocessorc11]") { - set_test_name("files that end without a newline: a directive, an invocation, a conditional"); - ensure_equals("a directive last", pp(std::string("#define X 1\n" + UNSCOPED_INFO("a directive last"); + CHECK(pp(std::string("#define X 1\n" "X\n" - "#undef X")), + "#undef X")) == std::string("1")); - ensure_equals("an invocation last", pp(std::string("#define f(x) \n" - "f(1)")), + UNSCOPED_INFO("an invocation last"); + CHECK(pp(std::string("#define f(x) \n" + "f(1)")) == std::string("<1>")); - ensure_equals("an #if left open", saidOn(run(std::string("#if 1\n" - "x"))), std::string("0")); + UNSCOPED_INFO("an #if left open"); + CHECK(saidOn(run(std::string("#if 1\n" + "x"))) == std::string("0")); } - template<> template<> - void alpreprocessorc11_object::test<35>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 6.10.3: LSL's own words may name macros since C knows no keywords", "[alpreprocessorc11]") { - set_test_name("6.10.3: a macro named as LSL names its own words: C knows no keywords, so any name is a macro's"); - ensure_equals("words and events", pp(std::string("#define default state_x\n" + UNSCOPED_INFO("words and events"); + CHECK(pp(std::string("#define default state_x\n" "#define state_entry on_rez\n" "#define jump return\n" - "default { state_entry() { jump; } }\n")), + "default { state_entry() { jump; } }\n")) == std::string("state_x { on_rez() { return; } }")); } - template<> template<> - void alpreprocessorc11_object::test<36>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: SLua: #if takes Luau's and/or/not/~= as well as C's operators", "[alpreprocessorc11]") { - set_test_name("SLua: #if written as Luau writes it: and, or, not, ~=, and C's too"); { ALPreprocessor::Options lua = options(); lua.lua = true; @@ -942,15 +1000,15 @@ namespace tut "--#if B or A ~= 2\ntwo\n--#endif\n" "--#if (A && !B) == 1\nthree\n--#endif\n" "--#if not A or B\nno\n--#else\nfour\n--#endif\n", lua); - ensure_equals("said", messages(r), std::string()); - ensure_equals("taken", flat(r.text), std::string("one two three four")); + UNSCOPED_INFO("said"); + CHECK(messages(r) == std::string()); + UNSCOPED_INFO("taken"); + CHECK(flat(r.text) == std::string("one two three four")); } } - template<> template<> - void alpreprocessorc11_object::test<37>() + TEST_CASE_METHOD(alpreprocessorc11_data, "alpreprocessorc11: 5.1.1.2: lines end at CRLF and CR too; blanks after a backslash are warned of", "[alpreprocessorc11]") { - set_test_name("5.1.1.2: a line ends at CRLF and at a lone CR as at LF, so a backslash before either joins; on a directive's line blanks between the backslash and the newline are let by with a warning, elsewhere they join nothing"); const auto ended = [](std::string text, const char* end) { std::string out; for (char c : text) @@ -964,25 +1022,37 @@ namespace tut " llSay(0, x)\n" "default { state_entry() { SAY(\"hi\"); } }\n"; const std::string joined = pp(lf); - ensure("the macro's lines are its body: " + joined, joined.find("PROBLEMS") == std::string::npos && joined.find("llSay") != std::string::npos && - joined.find('\\') == std::string::npos && joined.find("default") == 0); - ensure_equals("CRLF", pp(ended(lf, "\r\n")), joined); - ensure_equals("a lone CR", pp(ended(lf, "\r")), joined); - ensure_equals("the lines after keep their numbers", pp(ended("#define A 1 \\\n + 2\nA __LINE__\n", "\r\n")), std::string("1 + 2 3")); + { + INFO("the macro's lines are its body: " + joined); + REQUIRE(joined.find("PROBLEMS") == std::string::npos); + REQUIRE(joined.find("llSay") != std::string::npos); + REQUIRE(joined.find('\\') == std::string::npos); + CHECK(joined.find("default") == 0); + } + UNSCOPED_INFO("CRLF"); + CHECK(pp(ended(lf, "\r\n")) == joined); + UNSCOPED_INFO("a lone CR"); + CHECK(pp(ended(lf, "\r")) == joined); + UNSCOPED_INFO("the lines after keep their numbers"); + CHECK(pp(ended("#define A 1 \\\n + 2\nA __LINE__\n", "\r\n")) == std::string("1 + 2 3")); { const ALPreprocessor::Result r = run("#define SAY(x) \\ \t\n" " llOwnerSay(x); \\ \n" " llSay(0, x)\n" "default { state_entry() { SAY(\"hi\"); } }\n"); - ensure_equals("blanks after a directive's backslash", flat(r.text), joined); - ensure_equals("said of each", messages(r), + UNSCOPED_INFO("blanks after a directive's backslash"); + CHECK(flat(r.text) == joined); + UNSCOPED_INFO("said of each"); + CHECK(messages(r) == std::string("W 0: backslash and newline separated by space\nW 1: backslash and newline separated by space\n")); } { const ALPreprocessor::Result r = run("// a note \\ \nint shown;\n"); - ensure("a note's backslash and a blank take no line: " + r.text, r.text.find("int shown;") != std::string::npos); - ensure_equals("nothing said", messages(r), std::string()); + UNSCOPED_INFO("a note's backslash and a blank take no line: " + r.text); + REQUIRE(r.text.find("int shown;") != std::string::npos); + UNSCOPED_INFO("nothing said"); + CHECK(messages(r) == std::string()); } } } diff --git a/indra/alscript/tests/alscriptenvelope_test.cpp b/indra/alscript/tests/alscriptenvelope_test.cpp index ae9583de04..a0e40038aa 100644 --- a/indra/alscript/tests/alscriptenvelope_test.cpp +++ b/indra/alscript/tests/alscriptenvelope_test.cpp @@ -27,13 +27,13 @@ #include "../preprocessor/alscriptenvelope.h" #include "../preprocessor/aluploadheader.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct alscriptenvelope_data { @@ -90,28 +90,27 @@ namespace tut } }; - typedef test_group alscriptenvelope_group; - typedef alscriptenvelope_group::object alscriptenvelope_object; - alscriptenvelope_group alscriptenvelope_instance("alscriptenvelope"); - - template<> template<> - void alscriptenvelope_object::test<1>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: Firestorm's escaping puts a bar after each / or * before a / or * or |", "[alscriptenvelope]") { - set_test_name("the escaping is Firestorm's: a bar after every / or * that a /, * or | follows"); - ensure_equals("comments", ALScriptEnvelope::encodeSource("/* a */ // b"), std::string("/|* a *|/ /|/ b")); - ensure_equals("a bar of its own", ALScriptEnvelope::encodeSource("x /| y"), std::string("x /|| y")); - ensure_equals("a run of stars", ALScriptEnvelope::encodeSource("***"), std::string("*|*|*")); - ensure_equals("nothing to escape", ALScriptEnvelope::encodeSource("a / b * c"), std::string("a / b * c")); - ensure_equals("and back", ALScriptEnvelope::decodeSource("/|* a *|/ /|/ b"), std::string("/* a */ // b")); - ensure_equals("back from stars", ALScriptEnvelope::decodeSource("*|*|*"), std::string("***")); + UNSCOPED_INFO("comments"); + CHECK(ALScriptEnvelope::encodeSource("/* a */ // b") == std::string("/|* a *|/ /|/ b")); + UNSCOPED_INFO("a bar of its own"); + CHECK(ALScriptEnvelope::encodeSource("x /| y") == std::string("x /|| y")); + UNSCOPED_INFO("a run of stars"); + CHECK(ALScriptEnvelope::encodeSource("***") == std::string("*|*|*")); + UNSCOPED_INFO("nothing to escape"); + CHECK(ALScriptEnvelope::encodeSource("a / b * c") == std::string("a / b * c")); + UNSCOPED_INFO("and back"); + CHECK(ALScriptEnvelope::decodeSource("/|* a *|/ /|/ b") == std::string("/* a */ // b")); + UNSCOPED_INFO("back from stars"); + CHECK(ALScriptEnvelope::decodeSource("*|*|*") == std::string("***")); const std::string source = "default\n{\n state_entry()\n {\n /* hi */ llSay(0, \"a // b\");\n }\n}\n"; - ensure_equals("round trip", ALScriptEnvelope::decodeSource(ALScriptEnvelope::encodeSource(source)), source); + UNSCOPED_INFO("round trip"); + CHECK(ALScriptEnvelope::decodeSource(ALScriptEnvelope::encodeSource(source)) == source); } - template<> template<> - void alscriptenvelope_object::test<2>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: an LSL asset the preprocessor wrote reads back as its parts", "[alscriptenvelope]") { - set_test_name("an LSL asset the preprocessor wrote reads back as its parts"); const std::string asset = "//start_unprocessed_text\n" "/*#define X 1\n" @@ -123,25 +122,33 @@ namespace tut "//last_compiled 09/20/2026 10:11:12\n" "//mono\n" "default { state_entry() { llSay(0, (string)1); } }\n"; - ensure("looks wrapped", ALScriptEnvelope::looksWrapped(asset)); + UNSCOPED_INFO("looks wrapped"); + CHECK(ALScriptEnvelope::looksWrapped(asset)); std::optional envelope = ALScriptEnvelope::parse(asset); - ensure("parsed", envelope.has_value()); - ensure("lsl", !envelope->lua); - ensure_equals("the source", envelope->source, + UNSCOPED_INFO("parsed"); + REQUIRE(envelope.has_value()); + UNSCOPED_INFO("lsl"); + CHECK_FALSE(envelope->lua); + UNSCOPED_INFO("the source"); + CHECK(envelope->source == std::string("#define X 1\n/* a comment */\ndefault { state_entry() { llSay(0, (string)X); } }")); - ensure_equals("the target", envelope->compileTarget, std::string("mono")); - ensure_equals("the program", envelope->programVersion, std::string("Firestorm-Releasex64 7.1.11 (76496)")); - ensure_equals("the date", envelope->lastCompiled, std::string("09/20/2026 10:11:12")); - ensure_equals("the expanded code", envelope->expanded, std::string("default { state_entry() { llSay(0, (string)1); } }\n")); - - ensure("plain source is not an envelope", !ALScriptEnvelope::parse("default { }").has_value()); - ensure("a start without an end is not one", !ALScriptEnvelope::parse("//start_unprocessed_text\n/* x").has_value()); + UNSCOPED_INFO("the target"); + CHECK(envelope->compileTarget == std::string("mono")); + UNSCOPED_INFO("the program"); + CHECK(envelope->programVersion == std::string("Firestorm-Releasex64 7.1.11 (76496)")); + UNSCOPED_INFO("the date"); + CHECK(envelope->lastCompiled == std::string("09/20/2026 10:11:12")); + UNSCOPED_INFO("the expanded code"); + CHECK(envelope->expanded == std::string("default { state_entry() { llSay(0, (string)1); } }\n")); + + UNSCOPED_INFO("plain source is not an envelope"); + REQUIRE_FALSE(ALScriptEnvelope::parse("default { }").has_value()); + UNSCOPED_INFO("a start without an end is not one"); + REQUIRE_FALSE(ALScriptEnvelope::parse("//start_unprocessed_text\n/* x").has_value()); } - template<> template<> - void alscriptenvelope_object::test<3>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: what is written is what Firestorm writes and reads back", "[alscriptenvelope]") { - set_test_name("what is written is what Firestorm writes, and reads back"); ALScriptEnvelope envelope; envelope.source = "// src\ndefault { }"; envelope.expanded = "default { }\n"; @@ -149,21 +156,23 @@ namespace tut envelope.programVersion = "Alchemy 9.0.0"; envelope.lastCompiled = "now"; const std::string asset = envelope.wrap(); - ensure_equals("byte for byte", - asset, + UNSCOPED_INFO("byte for byte"); + CHECK(asset == std::string("//start_unprocessed_text\n/*/|/ src\ndefault { }*/\n//end_unprocessed_text\n" "//nfo_preprocessor_version 0\n//program_version Alchemy 9.0.0\n//last_compiled now\n//lsl2\ndefault { }\n")); std::optional back = ALScriptEnvelope::parse(asset); - ensure("reads back", back.has_value()); - ensure_equals("the source", back->source, envelope.source); - ensure_equals("the expanded code", back->expanded, envelope.expanded); - ensure_equals("the target", back->compileTarget, envelope.compileTarget); + UNSCOPED_INFO("reads back"); + REQUIRE(back.has_value()); + UNSCOPED_INFO("the source"); + CHECK(back->source == envelope.source); + UNSCOPED_INFO("the expanded code"); + CHECK(back->expanded == envelope.expanded); + UNSCOPED_INFO("the target"); + CHECK(back->compileTarget == envelope.compileTarget); } - template<> template<> - void alscriptenvelope_object::test<4>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: SLua gets a long comment at a level the source has none of", "[alscriptenvelope]") { - set_test_name("SLua gets a long comment at a level the source has none of"); ALScriptEnvelope envelope; envelope.lua = true; envelope.source = "local t = {[[a]], [=[b]=]}\nprint(t)"; @@ -172,35 +181,47 @@ namespace tut envelope.programVersion = "Alchemy"; envelope.lastCompiled = "now"; const std::string asset = envelope.wrap(); - ensure("level two, since the source closes levels zero and one", - asset.find("--[==[local t") != std::string::npos && asset.find("]==]\n--end_unprocessed_text\n") != std::string::npos); - ensure("looks wrapped", ALScriptEnvelope::looksWrapped(asset)); + { + INFO("level two, since the source closes levels zero and one"); + REQUIRE(asset.find("--[==[local t") != std::string::npos); + REQUIRE(asset.find("]==]\n--end_unprocessed_text\n") != std::string::npos); + } + UNSCOPED_INFO("looks wrapped"); + CHECK(ALScriptEnvelope::looksWrapped(asset)); std::optional back = ALScriptEnvelope::parse(asset); - ensure("reads back", back.has_value()); - ensure("lua", back->lua); - ensure_equals("the source", back->source, envelope.source); - ensure_equals("the expanded code", back->expanded, envelope.expanded); - ensure_equals("the target", back->compileTarget, std::string("luau")); + UNSCOPED_INFO("reads back"); + REQUIRE(back.has_value()); + UNSCOPED_INFO("lua"); + CHECK(back->lua); + UNSCOPED_INFO("the source"); + CHECK(back->source == envelope.source); + UNSCOPED_INFO("the expanded code"); + CHECK(back->expanded == envelope.expanded); + UNSCOPED_INFO("the target"); + CHECK(back->compileTarget == std::string("luau")); } - template<> template<> - void alscriptenvelope_object::test<5>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: a directive is a line of its own", "[alscriptenvelope]") { - set_test_name("a directive is a line of its own"); - ensure_equals("mono", ALScriptEnvelope::directiveOf("// x\n//mono\ndefault{}", false), std::string("mono")); + UNSCOPED_INFO("mono"); + CHECK(ALScriptEnvelope::directiveOf("// x\n//mono\ndefault{}", false) == std::string("mono")); // As Firestorm has it: mono anywhere wins over lsl2 anywhere. - ensure_equals("mono over lsl2", ALScriptEnvelope::directiveOf("//lsl2\n//mono\n", false), std::string("mono")); - ensure_equals("lsl2", ALScriptEnvelope::directiveOf("//lsl2\n", false), std::string("lsl2")); - ensure_equals("none", ALScriptEnvelope::directiveOf("//monolith\n", false), std::string()); - ensure_equals("not mid-line", ALScriptEnvelope::directiveOf("x //mono\n", false), std::string()); - ensure_equals("lua", ALScriptEnvelope::directiveOf("--luau\n", true), std::string("luau")); - ensure_equals("not lua's in lsl", ALScriptEnvelope::directiveOf("--luau\n", false), std::string()); + UNSCOPED_INFO("mono over lsl2"); + CHECK(ALScriptEnvelope::directiveOf("//lsl2\n//mono\n", false) == std::string("mono")); + UNSCOPED_INFO("lsl2"); + CHECK(ALScriptEnvelope::directiveOf("//lsl2\n", false) == std::string("lsl2")); + UNSCOPED_INFO("none"); + CHECK(ALScriptEnvelope::directiveOf("//monolith\n", false) == std::string()); + UNSCOPED_INFO("not mid-line"); + CHECK(ALScriptEnvelope::directiveOf("x //mono\n", false) == std::string()); + UNSCOPED_INFO("lua"); + CHECK(ALScriptEnvelope::directiveOf("--luau\n", true) == std::string("luau")); + UNSCOPED_INFO("not lua's in lsl"); + CHECK(ALScriptEnvelope::directiveOf("--luau\n", false) == std::string()); } - template<> template<> - void alscriptenvelope_object::test<6>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: SLua's long comment level avoids a closer formed with the source's tail", "[alscriptenvelope]") { - set_test_name("SLua's long comment closes where the source ends, not at a closer the source's tail makes with it"); struct Case { std::string name; @@ -231,56 +252,70 @@ namespace tut size_t level = 0; size_t body = 0; const size_t end = luaCommentEnd(asset, level, body); - ensure_equals(c.name + ": the level", level, c.level); - ensure_equals(c.name + ": the comment closes where the source ends", end, body + c.source.size()); - ensure_equals(c.name + ": and the end line follows the closer", - asset.substr(end + level + 2, 24), + UNSCOPED_INFO(c.name + ": the level"); + REQUIRE(level == c.level); + UNSCOPED_INFO(c.name + ": the comment closes where the source ends"); + REQUIRE(end == body + c.source.size()); + UNSCOPED_INFO(c.name + ": and the end line follows the closer"); + REQUIRE(asset.substr(end + level + 2, 24) == std::string("\n--end_unprocessed_text\n")); std::optional back = ALScriptEnvelope::parse(asset); - ensure(c.name + ": reads back", back.has_value()); - ensure_equals(c.name + ": the source", back->source, c.source); - ensure_equals(c.name + ": the expanded code", back->expanded, envelope.expanded); + UNSCOPED_INFO(c.name + ": reads back"); + REQUIRE(back.has_value()); + UNSCOPED_INFO(c.name + ": the source"); + REQUIRE(back->source == c.source); + UNSCOPED_INFO(c.name + ": the expanded code"); + REQUIRE(back->expanded == envelope.expanded); } } - template<> template<> - void alscriptenvelope_object::test<7>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: compiledFrom sees past spacing and comments and dropped parts but not edits", "[alscriptenvelope]") { - set_test_name("a compiled half the source makes now, spacing, comments and an optimizer's leavings aside; and one it does not"); const std::string made = "integer g = 3;\nfloat unused = 1.0;\nhelper() { llSay(0, \"a\"); }\n" "default\n{\n state_entry()\n {\n g = 4;\n llSay(0, (string)g);\n }\n}\n"; - ensure("itself", ALScriptEnvelope::compiledFrom(made, made, false)); - ensure("spaced and commented otherwise", - ALScriptEnvelope::compiledFrom(made, "// made here\ninteger g=3;float unused=1.0;helper(){llSay(0,\"a\");}" + UNSCOPED_INFO("itself"); + CHECK(ALScriptEnvelope::compiledFrom(made, made, false)); + UNSCOPED_INFO("spaced and commented otherwise"); + CHECK(ALScriptEnvelope::compiledFrom(made, "// made here\ninteger g=3;float unused=1.0;helper(){llSay(0,\"a\");}" "default{state_entry(){g=4;llSay(0,(string)g);}}", false)); - ensure("an unused global and function left out", - ALScriptEnvelope::compiledFrom(made, "integer g = 3;\ndefault { state_entry() { g = 4; llSay(0, (string)g); } }", false)); + UNSCOPED_INFO("an unused global and function left out"); + CHECK(ALScriptEnvelope::compiledFrom(made, "integer g = 3;\ndefault { state_entry() { g = 4; llSay(0, (string)g); } }", false)); // 3 is a token the script has elsewhere: changed here, it is // still not what the source makes. - ensure("a number changed", !ALScriptEnvelope::compiledFrom(made, "integer g = 3;\ndefault { state_entry() { g = 3; llSay(0, (string)g); } }", false)); - ensure("a line added", - !ALScriptEnvelope::compiledFrom(made, "integer g = 3;\ndefault { state_entry() { g = 4; llSay(0, (string)g); llDie(); } }", false)); - ensure("a part added", !ALScriptEnvelope::compiledFrom(made, made + "extra() { }\n", false)); - ensure("in another order", !ALScriptEnvelope::compiledFrom(made, "default { state_entry() { g = 4; llSay(0, (string)g); } }\ninteger g = 3;", false)); + UNSCOPED_INFO("a number changed"); + CHECK_FALSE(ALScriptEnvelope::compiledFrom(made, "integer g = 3;\ndefault { state_entry() { g = 3; llSay(0, (string)g); } }", false)); + UNSCOPED_INFO("a line added"); + CHECK_FALSE(ALScriptEnvelope::compiledFrom(made, "integer g = 3;\ndefault { state_entry() { g = 4; llSay(0, (string)g); llDie(); } }", false)); + UNSCOPED_INFO("a part added"); + CHECK_FALSE(ALScriptEnvelope::compiledFrom(made, made + "extra() { }\n", false)); + UNSCOPED_INFO("in another order"); + CHECK_FALSE(ALScriptEnvelope::compiledFrom(made, "default { state_entry() { g = 4; llSay(0, (string)g); } }\ninteger g = 3;", false)); const std::string lua = "local n = 1\nprint(n)\n"; - ensure("SLua: the same tokens", ALScriptEnvelope::compiledFrom(lua, "local n=1 -- one\nprint(n)", true)); - ensure("SLua: not", !ALScriptEnvelope::compiledFrom(lua, "local n = 2\nprint(n)\n", true)); - ensure("SLua: nothing left out", !ALScriptEnvelope::compiledFrom(lua, "local n = 1\n", true)); - - ensure("compiled here: told, transformed or not", ALScriptEnvelope::comparable(made, false, true, true)); - ensure("elsewhere, untransformed: told", ALScriptEnvelope::comparable(made, false, false, false)); - ensure("elsewhere, transformed: not", !ALScriptEnvelope::comparable(made, false, false, true)); - ensure("the time asked for: not", !ALScriptEnvelope::comparable("string t = __TIME__;", false, true, false)); - ensure("who compiled it: not", !ALScriptEnvelope::comparable("local a = __AGENTNAME__", true, true, false)); - ensure("named in a comment only: told", ALScriptEnvelope::comparable("// built __DATE__\ninteger x;", false, true, false)); + UNSCOPED_INFO("SLua: the same tokens"); + CHECK(ALScriptEnvelope::compiledFrom(lua, "local n=1 -- one\nprint(n)", true)); + UNSCOPED_INFO("SLua: not"); + CHECK_FALSE(ALScriptEnvelope::compiledFrom(lua, "local n = 2\nprint(n)\n", true)); + UNSCOPED_INFO("SLua: nothing left out"); + CHECK_FALSE(ALScriptEnvelope::compiledFrom(lua, "local n = 1\n", true)); + + UNSCOPED_INFO("compiled here: told, transformed or not"); + CHECK(ALScriptEnvelope::comparable(made, false, true, true)); + UNSCOPED_INFO("elsewhere, untransformed: told"); + CHECK(ALScriptEnvelope::comparable(made, false, false, false)); + UNSCOPED_INFO("elsewhere, transformed: not"); + CHECK_FALSE(ALScriptEnvelope::comparable(made, false, false, true)); + UNSCOPED_INFO("the time asked for: not"); + CHECK_FALSE(ALScriptEnvelope::comparable("string t = __TIME__;", false, true, false)); + UNSCOPED_INFO("who compiled it: not"); + CHECK_FALSE(ALScriptEnvelope::comparable("local a = __AGENTNAME__", true, true, false)); + UNSCOPED_INFO("named in a comment only: told"); + CHECK(ALScriptEnvelope::comparable("// built __DATE__\ninteger x;", false, true, false)); } - template<> template<> - void alscriptenvelope_object::test<8>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: codeLine counts the envelope's own lines before the code", "[alscriptenvelope]") { - set_test_name("the line the code begins on in the asset, as the region counts a line it names: the envelope's own before it"); for (const bool lua : { true, false }) { ALScriptEnvelope envelope; @@ -303,15 +338,15 @@ namespace tut from = nl + 1; } const int at = envelope.codeLine(); - ensure_equals(std::string(lua ? "SLua" : "LSL") + ": the code's first line there", lines[static_cast(at)], std::string("first")); - ensure_equals(std::string(lua ? "SLua" : "LSL") + ": after the source's lines and the envelope's own", at, 9); + UNSCOPED_INFO(std::string(lua ? "SLua" : "LSL") + ": the code's first line there"); + REQUIRE(lines[static_cast(at)] == std::string("first")); + UNSCOPED_INFO(std::string(lua ? "SLua" : "LSL") + ": after the source's lines and the envelope's own"); + REQUIRE(at == 9); } } - template<> template<> - void alscriptenvelope_object::test<9>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: wrappedSize is the size of the asset wrap makes", "[alscriptenvelope]") { - set_test_name("how long an envelope's asset is, worked out without making it, is how long the asset made is"); const char* sources[] = { "", "default { }\n", "a /* b */ c // d\n/*|*/ **// x", "local s = [[x]] .. [==[y]==]\n", "]]=]==]" }; for (const bool lua : { false, true }) { @@ -326,21 +361,20 @@ namespace tut envelope.compileTarget = target; envelope.programVersion = "Alchemy Test 1.2.3"; envelope.lastCompiled = "2026-09-27T12:00:00Z"; - ensure_equals(std::string(lua ? "SLua " : "LSL ") + "'" + source + "' for '" + target + "'", envelope.wrappedSize(), + UNSCOPED_INFO(std::string(lua ? "SLua " : "LSL ") + "'" + source + "' for '" + target + "'"); + REQUIRE(envelope.wrappedSize() == envelope.wrap().size()); } } } } - template<> template<> - void alscriptenvelope_object::test<10>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: a header goes between the target line and the code and parses back out", "[alscriptenvelope]") { - set_test_name("a header goes between the target line and the code, and comes back out of it, the code alone left"); const ALScriptEnvelope lsl = alscriptenvelope_data::made(false, true); const std::string asset = lsl.wrap(); - ensure_equals("byte for byte", - asset, + UNSCOPED_INFO("byte for byte"); + CHECK(asset == std::string("//start_unprocessed_text\n/*") + ALScriptEnvelope::encodeSource(lsl.source) + "*/\n//end_unprocessed_text\n//nfo_preprocessor_version 0\n//program_version Alchemy\n//last_compiled today\n//mono\n" + lsl.header + lsl.expanded); @@ -349,67 +383,95 @@ namespace tut const std::string name = lua ? "SLua: " : "LSL: "; const ALScriptEnvelope written = alscriptenvelope_data::made(lua, true); std::optional back = ALScriptEnvelope::parse(written.wrap()); - ensure(name + "reads back", back.has_value()); - ensure_equals(name + "the header", back->header, written.header); - ensure_equals(name + "the code alone", back->expanded, written.expanded); - ensure_equals(name + "the source", back->source, written.source); - ensure_equals(name + "the target", back->compileTarget, written.compileTarget); - ensure_equals(name + "written again, the same", back->wrap(), written.wrap()); - ensure_equals(name + "its size worked out", written.wrappedSize(), written.wrap().size()); - ensure_equals(name + "and from the fields", - ALScriptEnvelope::wrappedSize(lua, written.source, written.expanded, written.compileTarget, written.programVersion, - written.lastCompiled, written.header), + UNSCOPED_INFO(name + "reads back"); + REQUIRE(back.has_value()); + UNSCOPED_INFO(name + "the header"); + REQUIRE(back->header == written.header); + UNSCOPED_INFO(name + "the code alone"); + REQUIRE(back->expanded == written.expanded); + UNSCOPED_INFO(name + "the source"); + REQUIRE(back->source == written.source); + UNSCOPED_INFO(name + "the target"); + REQUIRE(back->compileTarget == written.compileTarget); + UNSCOPED_INFO(name + "written again, the same"); + REQUIRE(back->wrap() == written.wrap()); + UNSCOPED_INFO(name + "its size worked out"); + REQUIRE(written.wrappedSize() == written.wrap().size()); + UNSCOPED_INFO(name + "and from the fields"); + REQUIRE(ALScriptEnvelope::wrappedSize(lua, written.source, written.expanded, written.compileTarget, written.programVersion, + written.lastCompiled, written.header) == written.wrap().size()); // Without one, as it was: nothing taken, and Firestorm's bytes // with nothing added. const ALScriptEnvelope plain = alscriptenvelope_data::made(lua, false); back = ALScriptEnvelope::parse(plain.wrap()); - ensure(name + "none: reads back", back.has_value()); - ensure_equals(name + "none: no header", back->header, std::string()); - ensure_equals(name + "none: the code", back->expanded, plain.expanded); - ensure_equals(name + "none: the header's bytes and no more", plain.wrap().size() + written.header.size(), written.wrap().size()); + UNSCOPED_INFO(name + "none: reads back"); + REQUIRE(back.has_value()); + UNSCOPED_INFO(name + "none: no header"); + REQUIRE(back->header == std::string()); + UNSCOPED_INFO(name + "none: the code"); + REQUIRE(back->expanded == plain.expanded); + UNSCOPED_INFO(name + "none: the header's bytes and no more"); + REQUIRE(plain.wrap().size() + written.header.size() == written.wrap().size()); } // A header that does not end its line gets a newline of its own. ALScriptEnvelope unended = alscriptenvelope_data::made(false, true); unended.header.pop_back(); - ensure("a newline after it", unended.wrap() == alscriptenvelope_data::made(false, true).wrap()); - ensure_equals("and counted", unended.wrappedSize(), unended.wrap().size()); + UNSCOPED_INFO("a newline after it"); + CHECK(unended.wrap() == alscriptenvelope_data::made(false, true).wrap()); + UNSCOPED_INFO("and counted"); + REQUIRE(unended.wrappedSize() == unended.wrap().size()); // With no target line, it follows the date. ALScriptEnvelope untargeted = alscriptenvelope_data::made(false, true); untargeted.compileTarget.clear(); std::optional back = ALScriptEnvelope::parse(untargeted.wrap()); - ensure("untargeted: the header taken", back && back->header == untargeted.header && back->expanded == untargeted.expanded); + { + INFO("untargeted: the header taken"); + REQUIRE(back); + REQUIRE(back->header == untargeted.header); + CHECK(back->expanded == untargeted.expanded); + } // The plugin's banner, should one stand there, is taken out too. ALScriptEnvelope theirs = alscriptenvelope_data::made(false, false); theirs.header = "// ================ sl-vscode-plugin meta ================\n// @file a.lsl\n" "// =======================================================\n"; back = ALScriptEnvelope::parse(theirs.wrap()); - ensure("the plugin's taken", back && back->header == theirs.header && back->expanded == theirs.expanded); + { + INFO("the plugin's taken"); + REQUIRE(back); + REQUIRE(back->header == theirs.header); + CHECK(back->expanded == theirs.expanded); + } // Only the first: a second, which the author's code begins with, is // the code's. ALScriptEnvelope twice = alscriptenvelope_data::made(false, true); twice.expanded = twice.header + twice.expanded; back = ALScriptEnvelope::parse(twice.wrap()); - ensure("one header taken", back && back->header == twice.header && back->expanded == twice.expanded); + { + INFO("one header taken"); + REQUIRE(back); + REQUIRE(back->header == twice.header); + CHECK(back->expanded == twice.expanded); + } } - template<> template<> - void alscriptenvelope_object::test<11>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: a header moves the code's first line by its own lines and nothing else", "[alscriptenvelope]") { - set_test_name("a header moves the line the code begins on by its own lines, and nothing else"); for (const bool lua : { false, true }) { const std::string name = lua ? "SLua: " : "LSL: "; const ALScriptEnvelope without = alscriptenvelope_data::made(lua, false); const ALScriptEnvelope with = alscriptenvelope_data::made(lua, true); const int lines = static_cast(std::count(with.header.begin(), with.header.end(), '\n')); - ensure_equals(name + "the header's lines", lines, 5); - ensure_equals(name + "the code begins that much later", with.codeLine(), without.codeLine() + lines); + UNSCOPED_INFO(name + "the header's lines"); + REQUIRE(lines == 5); + UNSCOPED_INFO(name + "the code begins that much later"); + REQUIRE(with.codeLine() == without.codeLine() + lines); // The asset's line codeLine() + n is the code's line n either // way, and the code's lines are the ones the source map is of. @@ -418,20 +480,22 @@ namespace tut const std::vector code = alscriptenvelope_data::linesOf(with.expanded); for (size_t i = 0; i < code.size(); ++i) { - ensure_equals(name + "code line " + std::to_string(i) + " without", plain[static_cast(without.codeLine()) + i], code[i]); - ensure_equals(name + "code line " + std::to_string(i) + " with", headed[static_cast(with.codeLine()) + i], code[i]); + UNSCOPED_INFO(name + "code line " + std::to_string(i) + " without"); + REQUIRE(plain[static_cast(without.codeLine()) + i] == code[i]); + UNSCOPED_INFO(name + "code line " + std::to_string(i) + " with"); + REQUIRE(headed[static_cast(with.codeLine()) + i] == code[i]); } // And read back from the asset, as a save hears of it. const std::optional back = ALScriptEnvelope::parse(with.wrap()); - ensure(name + "reads back", back.has_value()); - ensure_equals(name + "read back, the same line", back->codeLine(), with.codeLine()); + UNSCOPED_INFO(name + "reads back"); + REQUIRE(back.has_value()); + UNSCOPED_INFO(name + "read back, the same line"); + REQUIRE(back->codeLine() == with.codeLine()); } } - template<> template<> - void alscriptenvelope_object::test<12>() + TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: a header fools neither compiledFrom nor the check that the code was edited", "[alscriptenvelope]") { - set_test_name("a header fools neither compiledFrom nor the check that the code was edited"); for (const bool lua : { false, true }) { const std::string name = lua ? "SLua: " : "LSL: "; @@ -443,18 +507,28 @@ namespace tut const std::optional held = ALScriptEnvelope::parse(sent.wrap()); const std::optional came = ALScriptEnvelope::parse(later.wrap()); - ensure(name + "both read", held && came); - ensure(name + "the headers apart", held->header != came->header); + { + INFO(name + "both read"); + REQUIRE(held); + REQUIRE(came); + } + UNSCOPED_INFO(name + "the headers apart"); + REQUIRE(held->header != came->header); // What the bridge holds up to tell an edit of the compiled half: // the code the same, whenever it went up. - ensure(name + "the code the same", held->expanded == came->expanded); - ensure(name + "compiled from the expansion", ALScriptEnvelope::compiledFrom(sent.expanded, came->expanded, lua)); + UNSCOPED_INFO(name + "the code the same"); + REQUIRE(held->expanded == came->expanded); + UNSCOPED_INFO(name + "compiled from the expansion"); + REQUIRE(ALScriptEnvelope::compiledFrom(sent.expanded, came->expanded, lua)); // Even left in, a header is comments, which it passes over. - ensure(name + "a header in the code passed over", ALScriptEnvelope::compiledFrom(sent.expanded, sent.header + sent.expanded, lua)); - ensure(name + "the source told as it was", ALScriptEnvelope::comparable(came->source, lua, true, false)); + UNSCOPED_INFO(name + "a header in the code passed over"); + REQUIRE(ALScriptEnvelope::compiledFrom(sent.expanded, sent.header + sent.expanded, lua)); + UNSCOPED_INFO(name + "the source told as it was"); + 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. - ensure_equals(name + "the hash again", ALUploadHeader::hashOf(came->compileTarget, came->source, came->expanded), + UNSCOPED_INFO(name + "the hash again"); + REQUIRE(ALUploadHeader::hashOf(came->compileTarget, came->source, came->expanded) == ALUploadHeader::parse(came->header, lua)->hash); } } diff --git a/indra/alscript/tests/alscriptfixes_test.cpp b/indra/alscript/tests/alscriptfixes_test.cpp index 7cccc172b4..044fa7c2a1 100644 --- a/indra/alscript/tests/alscriptfixes_test.cpp +++ b/indra/alscript/tests/alscriptfixes_test.cpp @@ -35,13 +35,13 @@ #include "../preprocessor/alpreprocessor.h" #include "../lsl/optimizer/allsloptimizer.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct alscriptfixes_data { @@ -109,16 +109,23 @@ namespace tut { const ALScriptProblems problems = check(script, lua); const ALScriptProblem* problem = keyed(problems, key); - ensure("said: " + key + "\n" + said(problems), problem != nullptr); - ensure("a fix: " + said(problems), !problem->fixes.empty()); + UNSCOPED_INFO("said: " + key + "\n" + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("a fix: " + said(problems)); + REQUIRE_FALSE(problem->fixes.empty()); const ALScriptFix& fix = problem->fixes.front(); - ensure("preferred", fix.preferred); - ensure_equals("its words", fix.title, title); + UNSCOPED_INFO("preferred"); + REQUIRE(fix.preferred); + UNSCOPED_INFO("its words"); + REQUIRE(fix.title == title); const std::optional made = ALScriptFixes::apply(script, fix); - ensure("applies", made.has_value()); + UNSCOPED_INFO("applies"); + REQUIRE(made.has_value()); const ALScriptProblems after = check(*made, lua); - ensure("gone from:\n" + *made + "\n" + said(after), keyed(after, key) == nullptr); - ensure("no error in its place:\n" + *made + "\n" + said(after), errors(after) <= errors(problems) - (problem->severity == ALScriptProblem::Severity::Error ? 1 : 0)); + UNSCOPED_INFO("gone from:\n" + *made + "\n" + said(after)); + REQUIRE(keyed(after, key) == nullptr); + UNSCOPED_INFO("no error in its place:\n" + *made + "\n" + said(after)); + REQUIRE(errors(after) <= errors(problems) - (problem->severity == ALScriptProblem::Severity::Error ? 1 : 0)); return *made; } @@ -127,7 +134,8 @@ namespace tut static void place(const std::string& script, const std::string& piece, S32& line, S32& column, S32& end) { const size_t at = script.find(piece); - ensure("in the script: " + piece, at != std::string::npos); + UNSCOPED_INFO("in the script: " + piece); + REQUIRE(at != std::string::npos); const size_t start = script.rfind('\n', at); line = static_cast(std::count(script.begin(), script.begin() + at, '\n')); column = static_cast(at - (start == std::string::npos ? 0 : start + 1)); @@ -161,11 +169,14 @@ namespace tut { continue; } - ensure("a refactor", action.kind == ALScriptFix::Kind::Refactor); + UNSCOPED_INFO("a refactor"); + REQUIRE(action.kind == ALScriptFix::Kind::Refactor); const std::optional made = ALScriptFixes::apply(script, action); - ensure("applies", made.has_value()); + UNSCOPED_INFO("applies"); + REQUIRE(made.has_value()); const ALScriptProblems after = check(*made, lua); - ensure("no error in:\n" + *made + "\n" + said(after), errors(after) == 0); + UNSCOPED_INFO("no error in:\n" + *made + "\n" + said(after)); + REQUIRE(errors(after) == 0); return *made; } return "offered: " + titles(offered); @@ -180,73 +191,79 @@ namespace tut return acted(script, lua, line, column, line, chosen ? end : column, title); } }; - typedef test_group alscriptfixes_group; - typedef alscriptfixes_group::object object; - tut::alscriptfixes_group alscriptfixes_test("ALScriptFixes"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a missing semicolon is put where it is missing", "[ALScriptFixes]") { - set_test_name("a missing semicolon is put where it is missing"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string made = fixed("default\n{\n state_entry()\n {\n integer a = 1\n llOwnerSay((string)a);\n }\n}\n", false, "LSLSyntaxMissing", "Insert ';'"); - ensure("after the 1: " + made, made.find("integer a = 1;\n") != std::string::npos); + UNSCOPED_INFO("after the 1: " + made); + REQUIRE(made.find("integer a = 1;\n") != std::string::npos); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL name spelt wrong becomes the nearest in scope or builtin", "[ALScriptFixes]") { - set_test_name("an LSL name spelt wrong is changed to the nearest in scope or among the builtins"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); std::string made = fixed("default\n{\n state_entry()\n {\n llownersay(\"hi\");\n }\n}\n", false, "LSLUndeclared", "Change 'llownersay' to 'llOwnerSay'"); - ensure("the call: " + made, made.find("llOwnerSay(\"hi\")") != std::string::npos); + UNSCOPED_INFO("the call: " + made); + REQUIRE(made.find("llOwnerSay(\"hi\")") != std::string::npos); made = fixed("default\n{\n state_entry()\n {\n integer count = 1;\n llOwnerSay((string)coutn);\n }\n}\n", false, "LSLUndeclared", "Change 'coutn' to 'count'"); - ensure("the local: " + made, made.find("(string)count)") != std::string::npos); + UNSCOPED_INFO("the local: " + made); + REQUIRE(made.find("(string)count)") != std::string::npos); // A local declared after the place is not in scope there, and not // offered. const ALScriptProblems problems = check("default\n{\n state_entry()\n {\n llOwnerSay((string)coutn);\n integer count = 1;\n }\n}\n", false); const ALScriptProblem* later = keyed(problems, "LSLUndeclared"); - ensure("said: " + said(problems), later != nullptr); - ensure("not a name declared after: " + said(problems), later->fixes.empty() || later->fixes.front().args[1] != "count"); + 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")); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated LSL function is replaced only where it has one replacement", "[ALScriptFixes]") { - set_test_name("a deprecated LSL function with one replacement is replaced, and one with several is not"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string made = fixed("default\n{\n state_entry()\n {\n llSoundPreload(\"boom\");\n }\n}\n", false, "LSLDeprecatedWithReplacement", "Use 'llPreloadSound' instead of 'llSoundPreload'"); - ensure("the call: " + made, made.find("llPreloadSound(\"boom\");") != std::string::npos); + UNSCOPED_INFO("the call: " + made); + REQUIRE(made.find("llPreloadSound(\"boom\");") != std::string::npos); const ALScriptProblems problems = check("default\n{\n state_entry()\n {\n llSound(\"boom\", 1.0, TRUE, FALSE);\n }\n}\n", false); const ALScriptProblem* several = keyed(problems, "LSLDeprecatedWithReplacement"); - ensure("said: " + said(problems), several != nullptr); - ensure("no fix among several: " + said(problems), several->fixes.empty()); + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(several != nullptr); + UNSCOPED_INFO("no fix among several: " + said(problems)); + REQUIRE(several->fixes.empty()); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an unused SLua local is marked unused safely", "[ALScriptFixes]") { - set_test_name("an unused SLua local is marked unused, safely"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); const std::string made = fixed("local unused = 1\n", true, "LuauLintLocalUnused", "Rename to '_unused'"); - ensure_equals("marked", made, std::string("local _unused = 1\n")); + UNSCOPED_INFO("marked"); + CHECK(made == std::string("local _unused = 1\n")); const ALScriptProblems problems = check("local function helper() end\n", true); const ALScriptProblem* problem = keyed(problems, "LuauLintFunctionUnused"); - ensure("said: " + said(problems), problem != nullptr && !problem->fixes.empty()); - ensure("safe", problem->fixes.front().safe); + { + INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + REQUIRE_FALSE(problem->fixes.empty()); + } + UNSCOPED_INFO("safe"); + CHECK(problem->fixes.front().safe); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an SLua key spelt wrong is changed to the nearest there is", "[ALScriptFixes]") { - set_test_name("an SLua key spelt wrong is changed to the nearest there is"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); if (newSolver) { // Whose nonstrict mode says nothing of a key not there. @@ -255,35 +272,14 @@ namespace tut luau.setConfig(config); } const std::string made = fixed("ll.OwnerSya(\"hi\")\n", true, "LuauKeyNotFoundDidYouMean", "Change 'OwnerSya' to 'OwnerSay'"); - ensure_equals("the call", made, std::string("ll.OwnerSay(\"hi\")\n")); + UNSCOPED_INFO("the call"); + CHECK(made == std::string("ll.OwnerSay(\"hi\")\n")); } - template<> template<> - void object::test<10>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated SLua call is replaced as the definitions say; never safe", "[ALScriptFixes]") { - set_test_name("an SLua global spelt wrong is changed to the nearest name in scope"); - ensure("definitions: " + error, luauLoaded); - const ALScriptProblems problems = check("pirnt(\"hi\")\n", true); - const ALScriptProblem* problem = nullptr; - for (const ALScriptProblem& each : problems) - { - if (each.key.find("UnknownGlobal") != std::string::npos) - { - problem = &each; - } - } - ensure("said: " + said(problems), problem != nullptr); - const std::string made = fixed("pirnt(\"hi\")\n", true, problem->key, "Change 'pirnt' to 'print'"); - ensure_equals("the call", made, std::string("print(\"hi\")\n")); - const std::string local = fixed("local count = 1\nprint(coutn)\n", true, problem->key, "Change 'coutn' to 'count'"); - ensure_equals("a local", local, std::string("local count = 1\nprint(count)\n")); - } - - template<> template<> - void object::test<6>() - { - set_test_name("a deprecated SLua call is replaced by what the definitions say to use, never as safe"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); const ALScriptProblems problems = check("local n = ll.Abs(-1)\nprint(n)\n", true); const ALScriptProblem* problem = nullptr; for (const ALScriptProblem& each : problems) @@ -293,186 +289,274 @@ namespace tut problem = &each; } } - ensure("said: " + said(problems), problem != nullptr); - ensure("a fix: " + said(problems), !problem->fixes.empty()); - ensure("not safe", !problem->fixes.front().safe); + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("a fix: " + said(problems)); + REQUIRE_FALSE(problem->fixes.empty()); + UNSCOPED_INFO("not safe"); + CHECK_FALSE(problem->fixes.front().safe); const std::optional made = ALScriptFixes::apply("local n = ll.Abs(-1)\nprint(n)\n", problem->fixes.front()); - ensure("applies", made.has_value()); + UNSCOPED_INFO("applies"); + REQUIRE(made.has_value()); const ALScriptProblems after = check(*made, true); for (const ALScriptProblem& each : after) { - ensure("no longer deprecated:\n" + *made + "\n" + said(after), each.code != "DeprecatedApi"); + UNSCOPED_INFO("no longer deprecated:\n" + *made + "\n" + said(after)); + REQUIRE(each.code != "DeprecatedApi"); } - ensure_equals("no errors:\n" + *made + "\n" + said(after), errors(after), static_cast(0)); + UNSCOPED_INFO("no errors:\n" + *made + "\n" + said(after)); + CHECK(errors(after) == static_cast(0)); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: edits are made in the text's order and overlapping ones are refused", "[ALScriptFixes]") { - set_test_name("edits are made in the text's order, and overlapping ones are refused"); ALScriptFix fix; fix.edits.push_back({ 1, 0, 1, 3, "BAR" }); fix.edits.push_back({ 0, 0, 0, 0, ">" }); - ensure_equals("both", ALScriptFixes::apply("foo\nbar\n", fix).value_or("refused"), std::string(">foo\nBAR\n")); + UNSCOPED_INFO("both"); + CHECK(ALScriptFixes::apply("foo\nbar\n", fix).value_or("refused") == std::string(">foo\nBAR\n")); fix.edits.push_back({ 1, 2, 1, 3, "x" }); - ensure("overlap refused", !ALScriptFixes::apply("foo\nbar\n", fix).has_value()); + UNSCOPED_INFO("overlap refused"); + REQUIRE_FALSE(ALScriptFixes::apply("foo\nbar\n", fix).has_value()); ALScriptFix past; past.edits.push_back({ 0, 4, 0, 4, "!" }); - ensure("past the line's end refused", !ALScriptFixes::apply("foo\nbar\n", past).has_value()); + UNSCOPED_INFO("past the line's end refused"); + REQUIRE_FALSE(ALScriptFixes::apply("foo\nbar\n", past).has_value()); ALScriptFix at_end; at_end.edits.push_back({ 0, 3, 0, 3, ";" }); - ensure_equals("at the line's end", ALScriptFixes::apply("foo\nbar\n", at_end).value_or("refused"), std::string("foo;\nbar\n")); + UNSCOPED_INFO("at the line's end"); + CHECK(ALScriptFixes::apply("foo\nbar\n", at_end).value_or("refused") == std::string("foo;\nbar\n")); } - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: NOLINT and NOLINTNEXTLINE suppress named or all lints; never a type error", "[ALScriptFixes]") { - set_test_name("NOLINT and NOLINTNEXTLINE suppress lints by name, bare or listed, and never an error"); ALScriptProblem lint; lint.severity = ALScriptProblem::Severity::Warning; lint.source = ALScriptProblem::Source::Lint; lint.code = "LocalUnused"; - ensure("named", ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINT(LocalUnused)", "", true)); - ensure("among others", ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINT(ShadowLocal, LocalUnused)", "", true)); - ensure("bare", ALScriptFixes::suppressed(lint, "local x = 1 --[[ NOLINT ]]", "", true)); - ensure("another name", !ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINT(ShadowLocal)", "", true)); - ensure("the line before", ALScriptFixes::suppressed(lint, "local x = 1", "-- NOLINTNEXTLINE(LocalUnused)", true)); - ensure("NOLINT on the line before is not about this one", !ALScriptFixes::suppressed(lint, "local x = 1", "-- NOLINT(LocalUnused)", true)); - ensure("not in a string", !ALScriptFixes::suppressed(lint, "local x = \"-- NOLINT\"", "", true)); - ensure("not in a long string", !ALScriptFixes::suppressed(lint, "local x = [[ -- NOLINT ]]", "", true)); - ensure("not a longer word", !ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINTING", "", true)); + UNSCOPED_INFO("named"); + CHECK(ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINT(LocalUnused)", "", true)); + UNSCOPED_INFO("among others"); + CHECK(ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINT(ShadowLocal, LocalUnused)", "", true)); + UNSCOPED_INFO("bare"); + CHECK(ALScriptFixes::suppressed(lint, "local x = 1 --[[ NOLINT ]]", "", true)); + UNSCOPED_INFO("another name"); + CHECK_FALSE(ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINT(ShadowLocal)", "", true)); + UNSCOPED_INFO("the line before"); + CHECK(ALScriptFixes::suppressed(lint, "local x = 1", "-- NOLINTNEXTLINE(LocalUnused)", true)); + UNSCOPED_INFO("NOLINT on the line before is not about this one"); + CHECK_FALSE(ALScriptFixes::suppressed(lint, "local x = 1", "-- NOLINT(LocalUnused)", true)); + UNSCOPED_INFO("not in a string"); + CHECK_FALSE(ALScriptFixes::suppressed(lint, "local x = \"-- NOLINT\"", "", true)); + UNSCOPED_INFO("not in a long string"); + CHECK_FALSE(ALScriptFixes::suppressed(lint, "local x = [[ -- NOLINT ]]", "", true)); + UNSCOPED_INFO("not a longer word"); + CHECK_FALSE(ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINTING", "", true)); lint.severity = ALScriptProblem::Severity::Error; - ensure("a lint made an error, still", ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINT", "", true)); + UNSCOPED_INFO("a lint made an error, still"); + CHECK(ALScriptFixes::suppressed(lint, "local x = 1 -- NOLINT", "", true)); ALScriptProblem type; type.severity = ALScriptProblem::Severity::Error; type.source = ALScriptProblem::Source::Types; - ensure("a type error, never", !ALScriptFixes::suppressed(type, "local x: number = \"a\" -- NOLINT", "", true)); + UNSCOPED_INFO("a type error, never"); + CHECK_FALSE(ALScriptFixes::suppressed(type, "local x: number = \"a\" -- NOLINT", "", true)); ALScriptProblem lsl; lsl.severity = ALScriptProblem::Severity::Warning; lsl.source = ALScriptProblem::Source::Lint; lsl.code = "20009"; lsl.key = "LSLDeclaredButNotUsed"; - ensure_equals("its name", ALScriptFixes::lintName(lsl, false), std::string("DeclaredButNotUsed")); - ensure("by name", ALScriptFixes::suppressed(lsl, "integer x; // NOLINT(DeclaredButNotUsed)", "", false)); - ensure("by number", ALScriptFixes::suppressed(lsl, "integer x; /* NOLINT(20009) */", "", false)); - ensure("not in a string", !ALScriptFixes::suppressed(lsl, "string s = \"// NOLINT\";", "", false)); + UNSCOPED_INFO("its name"); + CHECK(ALScriptFixes::lintName(lsl, false) == std::string("DeclaredButNotUsed")); + UNSCOPED_INFO("by name"); + CHECK(ALScriptFixes::suppressed(lsl, "integer x; // NOLINT(DeclaredButNotUsed)", "", false)); + UNSCOPED_INFO("by number"); + CHECK(ALScriptFixes::suppressed(lsl, "integer x; /* NOLINT(20009) */", "", false)); + UNSCOPED_INFO("not in a string"); + CHECK_FALSE(ALScriptFixes::suppressed(lsl, "string s = \"// NOLINT\";", "", false)); } - template<> template<> - void object::test<9>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a suppression joins a NOLINT or the line's comment or makes its own", "[ALScriptFixes]") { - set_test_name("the suppression goes in a NOLINT already there, the comment the line ends in, or one of its own"); ALScriptProblem lint; lint.severity = ALScriptProblem::Severity::Warning; lint.source = ALScriptProblem::Source::Lint; lint.code = "LocalUnused"; auto made = [&lint](const std::string& line, bool lua) { const std::optional fix = ALScriptFixes::suppression(lint, line, lua); - ensure("a fix for: " + line, fix.has_value()); - ensure("a suppression", fix->kind == ALScriptFix::Kind::Suppress && !fix->preferred && fix->safe); + UNSCOPED_INFO("a fix for: " + line); + REQUIRE(fix.has_value()); + { + INFO("a suppression"); + REQUIRE(fix->kind == ALScriptFix::Kind::Suppress); + REQUIRE_FALSE(fix->preferred); + REQUIRE(fix->safe); + } const std::string after = ALScriptFixes::apply(line, *fix).value_or("refused"); - ensure("suppresses: " + after, ALScriptFixes::suppressed(lint, after, "", lua)); + UNSCOPED_INFO("suppresses: " + after); + REQUIRE(ALScriptFixes::suppressed(lint, after, "", lua)); return after; }; - ensure_equals("its own", made("local x = 1", true), std::string("local x = 1 -- NOLINT(LocalUnused)")); - ensure_equals("the line's comment", made("local x = 1 -- why", true), std::string("local x = 1 -- why NOLINT(LocalUnused)")); - ensure_equals("a NOLINT's list", made("local x = 1 -- NOLINT(ShadowLocal)", true), std::string("local x = 1 -- NOLINT(ShadowLocal, LocalUnused)")); - ensure_equals("after a block comment", made("local x = 1 --[[ why ]]", true), std::string("local x = 1 --[[ why ]] -- NOLINT(LocalUnused)")); - ensure("nothing where a bare NOLINT says it", !ALScriptFixes::suppression(lint, "local x = 1 -- NOLINT", true).has_value()); + UNSCOPED_INFO("its own"); + CHECK(made("local x = 1", true) == std::string("local x = 1 -- NOLINT(LocalUnused)")); + UNSCOPED_INFO("the line's comment"); + CHECK(made("local x = 1 -- why", true) == std::string("local x = 1 -- why NOLINT(LocalUnused)")); + UNSCOPED_INFO("a NOLINT's list"); + CHECK(made("local x = 1 -- NOLINT(ShadowLocal)", true) == std::string("local x = 1 -- NOLINT(ShadowLocal, LocalUnused)")); + UNSCOPED_INFO("after a block comment"); + CHECK(made("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"; - ensure_equals("LSL", made(" integer x;", false), std::string(" integer x; // NOLINT(DeclaredButNotUsed)")); + UNSCOPED_INFO("LSL"); + CHECK(made(" 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); + CHECK(luauLoaded); + const ALScriptProblems problems = check("pirnt(\"hi\")\n", true); + const ALScriptProblem* problem = nullptr; + for (const ALScriptProblem& each : problems) + { + if (each.key.find("UnknownGlobal") != std::string::npos) + { + problem = &each; + } + } + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + const std::string made = fixed("pirnt(\"hi\")\n", true, problem->key, "Change 'pirnt' to 'print'"); + UNSCOPED_INFO("the call"); + CHECK(made == std::string("print(\"hi\")\n")); + const std::string local = fixed("local count = 1\nprint(coutn)\n", true, problem->key, "Change 'coutn' to 'count'"); + UNSCOPED_INFO("a local"); + CHECK(local == std::string("local count = 1\nprint(count)\n")); } - template<> template<> - void object::test<11>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL value of the wrong type is cast where LSL has the cast", "[ALScriptFixes]") { - set_test_name("an LSL value of the wrong type is cast to the right one, where LSL casts it"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); std::string made = fixed("default\n{\n state_entry()\n {\n llOwnerSay(5 + 1);\n }\n}\n", false, "LSLArgumentWrongType", "Cast to string"); - ensure("the argument: " + made, made.find("llOwnerSay((string)(5 + 1));") != std::string::npos); + UNSCOPED_INFO("the argument: " + made); + REQUIRE(made.find("llOwnerSay((string)(5 + 1));") != std::string::npos); made = fixed("default\n{\n state_entry()\n {\n llSetText(\"a\", <1, 1, 1>, \"1\");\n }\n}\n", false, "LSLArgumentWrongType", "Cast to float"); - ensure("the third, past a vector's commas: " + made, made.find("<1, 1, 1>, (float)\"1\")") != std::string::npos); + UNSCOPED_INFO("the third, past a vector's commas: " + made); + REQUIRE(made.find("<1, 1, 1>, (float)\"1\")") != std::string::npos); made = fixed("default\n{\n state_entry()\n {\n string s = 5;\n llOwnerSay(s);\n }\n}\n", false, "LSLWrongTypeInAssignment", "Cast to string"); - ensure("the declaration: " + made, made.find("string s = (string)5;") != std::string::npos); + UNSCOPED_INFO("the declaration: " + made); + REQUIRE(made.find("string s = (string)5;") != std::string::npos); made = fixed("default\n{\n state_entry()\n {\n string s;\n s = 6;\n llOwnerSay(s);\n }\n}\n", false, "LSLInvalidOperator", "Cast to string"); - ensure("the assignment: " + made, made.find("s = (string)6;") != std::string::npos); + UNSCOPED_INFO("the assignment: " + made); + REQUIRE(made.find("s = (string)6;") != std::string::npos); made = fixed("string f()\n{\n return 5;\n}\ndefault\n{\n state_entry()\n {\n llOwnerSay(f());\n }\n}\n", false, "LSLBadReturnType", "Cast to string"); - ensure("the return: " + made, made.find("return (string)5;") != std::string::npos); + UNSCOPED_INFO("the return: " + made); + REQUIRE(made.find("return (string)5;") != std::string::npos); // No cast LSL has: a list is no vector. const ALScriptProblems problems = check("default\n{\n state_entry()\n {\n llSetPos([1]);\n }\n}\n", false); const ALScriptProblem* none = keyed(problems, "LSLArgumentWrongType"); - ensure("said: " + said(problems), none != nullptr && none->fixes.empty()); + { + INFO("said: " + said(problems)); + REQUIRE(none != nullptr); + REQUIRE(none->fixes.empty()); + } } - template<> template<> - void object::test<12>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: the LSL warnings that say what to write are written so", "[ALScriptFixes]") { - set_test_name("the LSL warnings that say what to write are written so"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + 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"; std::string made = fixed(head + " a == 3;\n" + tail, false, "LSLEqAsStatement", "Assign with '='"); - ensure("assigned: " + made, made.find(" a = 3;\n") != std::string::npos); + UNSCOPED_INFO("assigned: " + made); + REQUIRE(made.find(" a = 3;\n") != std::string::npos); made = fixed(head + " a *= 1.5;\n" + tail, false, "LSLIntFloatMulAssign", "Multiply as a float, then cast to integer"); - ensure("rewritten: " + made, made.find("a = (integer)(a * 1.5);") != std::string::npos); + UNSCOPED_INFO("rewritten: " + made); + REQUIRE(made.find("a = (integer)(a * 1.5);") != std::string::npos); made = fixed(head + " state default;\n" + tail, false, "LSLChangeToCurrentState", "Write 'return' instead"); - ensure("returned: " + made, made.find(" return;\n") != std::string::npos); + UNSCOPED_INFO("returned: " + made); + REQUIRE(made.find(" return;\n") != std::string::npos); // An assignment used as a condition: either was meant, so neither // is preferred, and the brackets change nothing. const std::string script = head + " if (a = 1) llOwnerSay(\"one\");\n" + tail; const ALScriptProblems problems = check(script, false); const ALScriptProblem* problem = keyed(problems, "LSLAssignmentInComparison"); - ensure("said: " + said(problems), problem != nullptr && problem->fixes.size() >= 2); - ensure("neither preferred", !problem->fixes[0].preferred && !problem->fixes[1].preferred); - ensure_equals("compared", ALScriptFixes::apply(script, problem->fixes[0]).value_or("").find("if (a == 1)") != std::string::npos, true); - ensure("bracketed, safely", problem->fixes[1].safe && ALScriptFixes::apply(script, problem->fixes[1]).value_or("").find("if ((a = 1))") != std::string::npos); + { + INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + REQUIRE(problem->fixes.size() >= 2); + } + { + INFO("neither preferred"); + REQUIRE_FALSE(problem->fixes[0].preferred); + CHECK_FALSE(problem->fixes[1].preferred); + } + UNSCOPED_INFO("compared"); + REQUIRE((ALScriptFixes::apply(script, problem->fixes[0]).value_or("").find("if (a == 1)") != std::string::npos) == true); + { + INFO("bracketed, safely"); + REQUIRE(problem->fixes[1].safe); + REQUIRE(ALScriptFixes::apply(script, problem->fixes[1]).value_or("").find("if ((a = 1))") != std::string::npos); + } const std::optional quiet = ALScriptFixes::apply(script, problem->fixes[1]); - ensure("and quiet after", quiet && keyed(check(*quiet, false), "LSLAssignmentInComparison") == nullptr); + { + INFO("and quiet after"); + REQUIRE(quiet); + REQUIRE(keyed(check(*quiet, false), "LSLAssignmentInComparison") == nullptr); + } } - template<> template<> - void object::test<13>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: the SLua fixes read off what the checker marks", "[ALScriptFixes]") { - set_test_name("the SLua fixes read off what the checker marks"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); std::string made = fixed("--!strict\nlocal n = 5\nlocal s: string = n\nprint(s)\n", true, "LuauTypeMismatch", "Wrap in tostring()"); - ensure("the value a declaration is given: " + made, made.find("local s: string = tostring(n)") != std::string::npos); + UNSCOPED_INFO("the value a declaration is given: " + made); + REQUIRE(made.find("local s: string = tostring(n)") != std::string::npos); made = fixed("--!strict\nlocal t = {}\nfunction t:m() return self end\nprint(t.m())\n", true, "LuauRequiresSelf", "Call with ':'"); - ensure("with a colon: " + made, made.find("print(t:m())") != std::string::npos); + UNSCOPED_INFO("with a colon: " + made); + REQUIRE(made.find("print(t:m())") != std::string::npos); made = fixed("local function f()\n counter = 1\n return counter\nend\nprint(f())\n", true, "LuauLintGlobalUsedAsLocalFunction", "Make 'counter' local"); - ensure("local: " + made, made.find(" local counter = 1\n") != std::string::npos); + UNSCOPED_INFO("local: " + made); + REQUIRE(made.find(" local counter = 1\n") != std::string::npos); made = fixed("--!strict\nlocal x\nprint(x)\n", true, "LuauLintUninitializedLocal", "Initialize 'x' with nil"); - ensure("given nil: " + made, made.find("local x = nil\n") != std::string::npos); + UNSCOPED_INFO("given nil: " + made); + REQUIRE(made.find("local x = nil\n") != std::string::npos); made = fixed("--!nonstrickt\nprint(1)\n", true, "LuauLintDirectiveUnknownDidYouMean", "Change 'nonstrickt' to 'nonstrict'"); - ensure("spelt: " + made, made.compare(0, 12, "--!nonstrict") == 0); + UNSCOPED_INFO("spelt: " + made); + CHECK(made.compare(0, 12, "--!nonstrict") == 0); } - template<> template<> - void object::test<14>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an unused LSL declaration is removed unless its value does something", "[ALScriptFixes]") { - set_test_name("an LSL declaration nothing uses is taken out whole, and a local only where what it is given does nothing"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string state = "default\n{\n state_entry()\n {\n llOwnerSay(\"x\");\n }\n}\n"; std::string made = fixed("integer unused = 5;\n" + state, false, "LSLDeclaredButNotUsed", "Remove 'unused'"); - ensure_equals("the global's line gone", made, state); + UNSCOPED_INFO("the global's line gone"); + CHECK(made == state); made = fixed("integer helper(integer x)\n{\n return x;\n}\n" + state, false, "LSLDeclaredButNotUsed", "Remove 'helper'"); - ensure_equals("the function's lines gone", made, state); + UNSCOPED_INFO("the function's lines gone"); + CHECK(made == state); made = fixed("default\n{\n state_entry()\n {\n integer a = 1 + 2;\n llOwnerSay(\"x\");\n }\n}\n", false, "LSLDeclaredButNotUsed", "Remove 'a'"); - ensure_equals("the local's line gone", made, state); + UNSCOPED_INFO("the local's line gone"); + CHECK(made == state); const ALScriptProblem* problem = nullptr; const ALScriptProblems problems = check("default\n{\n state_entry()\n {\n integer h = llListen(0, \"\", NULL_KEY, \"\");\n }\n}\n", false); @@ -480,26 +564,29 @@ namespace tut { problem = each.key == "LSLDeclaredButNotUsed" ? &each : problem; } - ensure("said: " + said(problems), problem != nullptr); - ensure("kept where what it is given does something", problem->fixes.empty()); + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("kept where what it is given does something"); + REQUIRE(problem->fixes.empty()); // One that shares its line keeps the line. ALScriptProblem shared; shared.line = 0; ALScriptFixes::offerRemoval(shared, "integer a = 1; integer b = 2;\n", 0, 8, 0, 13, "a"); - ensure_equals("only itself, and the space after it", ALScriptFixes::apply("integer a = 1; integer b = 2;\n", shared.fixes.front()).value_or(""), + 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"); - ensure_equals("and the space before it", ALScriptFixes::apply("integer a = 1; integer b = 2;\n", last.fixes.front()).value_or(""), + 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")); } - template<> template<> - void object::test<15>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: what the optimizer did is offered as a change to the source", "[ALScriptFixes]") { - set_test_name("what the optimizer did is offered as a change to the source, where the source says what it read"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + 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"; ALPreprocessor::Options options; @@ -516,159 +603,197 @@ namespace tut folded = p.key == "OptimizerFolded" && !p.fixes.empty() ? &p : folded; unreachable = p.key == "OptimizerRemovedUnreachable" && !p.fixes.empty() ? &p : unreachable; } - ensure("a fold offered: " + notes, folded != nullptr); - ensure("never preferred: the source is the scripter's", !folded->fixes.front().preferred); + UNSCOPED_INFO("a fold offered: " + notes); + REQUIRE(folded != nullptr); + UNSCOPED_INFO("never preferred: the source is the scripter's"); + CHECK_FALSE(folded->fixes.front().preferred); std::string made = ALScriptFixes::apply(script, folded->fixes.front()).value_or("refused"); - ensure("written in: " + made, made.find("llOwnerSay(\"3\");") != std::string::npos); - ensure("what can never run offered: " + notes, unreachable != nullptr && unreachable->fixes.front().preferred && unreachable->fixes.front().safe); + UNSCOPED_INFO("written in: " + made); + REQUIRE(made.find("llOwnerSay(\"3\");") != std::string::npos); + { + INFO("what can never run offered: " + notes); + REQUIRE(unreachable != nullptr); + REQUIRE(unreachable->fixes.front().preferred); + CHECK(unreachable->fixes.front().safe); + } made = ALScriptFixes::apply(script, unreachable->fixes.front()).value_or("refused"); - ensure("gone: " + made, made.find("never") == std::string::npos && made.find(" return;\n }\n") != std::string::npos); + { + INFO("gone: " + made); + REQUIRE(made.find("never") == std::string::npos); + REQUIRE(made.find(" return;\n }\n") != std::string::npos); + } } - template<> template<> - void object::test<16>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL expression chosen goes in a local before its statement where safe", "[ALScriptFixes]") { - set_test_name("an LSL expression chosen goes into a local just before its statement, where that runs it as it ran"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string head = "default\n{\n state_entry()\n {\n"; const std::string tail = " }\n}\n"; std::string made = actedOn(head + " llOwnerSay((string)(llGetUnixTime() + 5));\n" + tail, false, "llGetUnixTime() + 5", true, "Put it in a local, 'value'"); - ensure_equals("on its own line", made, + UNSCOPED_INFO("on its own line"); + CHECK(made == head + " integer value = llGetUnixTime() + 5;\n llOwnerSay((string)(value));\n" + tail); // After another statement on the line, just before its own; and // named as nothing in the script is. made = actedOn(head + " integer value = 1; llOwnerSay((string)(value * 2.5));\n" + tail, false, "value * 2.5", true, "Put it in a local, 'value2'"); - ensure_equals("beside", made, head + " integer value = 1; float value2 = value * 2.5; llOwnerSay((string)(value2));\n" + tail); + UNSCOPED_INFO("beside"); + CHECK(made == head + " integer value = 1; float value2 = value * 2.5; llOwnerSay((string)(value2));\n" + tail); made = actedOn(head + " llSetRot(llEuler2Rot(<0, 0, 1>));\n" + tail, false, "llEuler2Rot(<0, 0, 1>)", true, "Put it in a local, 'value'"); - ensure("a rotation as LSL says it: " + made, made.find("rotation value = llEuler2Rot(<0, 0, 1>);") != std::string::npos); + UNSCOPED_INFO("a rotation as LSL says it: " + made); + REQUIRE(made.find("rotation value = llEuler2Rot(<0, 0, 1>);") != std::string::npos); // 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; - ensure("a condition: " + actedOn(none, false, "i < 10", true, "Put it in a local, 'value'"), - actedOn(none, false, "i < 10", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("a condition: " + actedOn(none, false, "i < 10", true, "Put it in a local, 'value'")); + CHECK(actedOn(none, false, "i < 10", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); none = head + " integer i;\n if (i) llOwnerSay((string)(i + 1));\n" + tail; - ensure("under an if", actedOn(none, false, "i + 1", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("under an if"); + CHECK(actedOn(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; place(none, "i = 4", line, column, end); - ensure("assigned to", acted(none, false, line, column, line, column + 1, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("assigned to"); + CHECK(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; - ensure("the whole statement", actedOn(none, false, "llOwnerSay((string)llGetUnixTime())", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("the whole statement"); + CHECK(actedOn(none, false, "llOwnerSay((string)llGetUnixTime())", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); } - template<> template<> - void object::test<17>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL if-else is inverted: the condition turned and the branches swapped", "[ALScriptFixes]") { - set_test_name("an LSL if with an else is inverted: the condition turned round, the branches swapped"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string head = "default\n{\n touch_start(integer n)\n {\n"; const std::string tail = " }\n}\n"; std::string made = actedOn(head + " if (n == 1)\n {\n llOwnerSay(\"one\");\n }\n else\n {\n" " llOwnerSay(\"many\");\n }\n" + tail, false, "if", false, "Invert the if"); - ensure_equals("braces", made, + UNSCOPED_INFO("braces"); + CHECK(made == head + " if (n != 1)\n {\n llOwnerSay(\"many\");\n }\n else\n {\n" " llOwnerSay(\"one\");\n }\n" + tail); made = actedOn(head + " if (!n) llOwnerSay(\"none\"); else llOwnerSay(\"some\");\n" + tail, false, "if", false, "Invert the if"); - ensure_equals("a not taken off", made, head + " if (n) llOwnerSay(\"some\"); else llOwnerSay(\"none\");\n" + tail); + UNSCOPED_INFO("a not taken off"); + CHECK(made == head + " if (n) llOwnerSay(\"some\"); else llOwnerSay(\"none\");\n" + tail); made = actedOn(head + " if (n > 2) llOwnerSay(\"many\"); else llOwnerSay(\"few\");\n" + tail, false, "if", false, "Invert the if"); - ensure_equals("an order under a not", made, head + " if (!(n > 2)) llOwnerSay(\"few\"); else llOwnerSay(\"many\");\n" + tail); + 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; - ensure("not with an else if", actedOn(chain, false, "if", false, "Invert the if").rfind("offered", 0) == 0); + UNSCOPED_INFO("not with an else if"); + CHECK(actedOn(chain, false, "if", false, "Invert the if").rfind("offered", 0) == 0); const std::string lone = head + " if (n == 1) llOwnerSay(\"one\");\n" + tail; - ensure("not without an else", actedOn(lone, false, "if", false, "Invert the if").rfind("offered", 0) == 0); + UNSCOPED_INFO("not without an else"); + CHECK(actedOn(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 = actedOn(head + " if (n) llOwnerSay(\"a\"); else while (n--) if (n == 3) llOwnerSay(\"c\");\n" + tail, false, "if", false, "Invert the if"); - ensure_equals("braced", made, head + " if (!n) { while (n--) if (n == 3) llOwnerSay(\"c\"); } else llOwnerSay(\"a\");\n" + tail); + UNSCOPED_INFO("braced"); + CHECK(made == head + " if (!n) { while (n--) if (n == 3) llOwnerSay(\"c\"); } else llOwnerSay(\"a\");\n" + tail); } - template<> template<> - void object::test<18>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL state asking for an event it does not hear is offered a handler", "[ALScriptFixes]") { - set_test_name("a handler is offered for an event the LSL state asks for and does not hear, laid out as its others are"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string script = "default\n{\n state_entry()\n {\n llListen(0, \"\", NULL_KEY, \"\");\n llSetTimerEvent(1.0);\n" " }\n}\n"; std::string made = actedOn(script, false, "llListen", false, "Add a handler for 'listen'"); - ensure_equals("before the brace, after a blank line", made, + UNSCOPED_INFO("before the brace, after a blank line"); + CHECK(made == "default\n{\n state_entry()\n {\n llListen(0, \"\", NULL_KEY, \"\");\n llSetTimerEvent(1.0);\n }\n\n" " listen(integer channel, string name, key id, string msg)\n {\n }\n}\n"); - ensure("the timer too", actions(script, false, 4, 8, 4, 8).size() == 2); + UNSCOPED_INFO("the timer too"); + REQUIRE(actions(script, false, 4, 8, 4, 8).size() == 2); // Heard already: nothing. const std::string heard = "default\n{\n state_entry() {\n llSetTimerEvent(1.0);\n }\n\n timer() {\n }\n}\n"; - ensure("heard: " + titles(actions(heard, false, 3, 8, 3, 8)), actions(heard, false, 3, 8, 3, 8).empty()); + UNSCOPED_INFO("heard: " + titles(actions(heard, false, 3, 8, 3, 8))); + REQUIRE(actions(heard, false, 3, 8, 3, 8).empty()); // The brace where the state's first handler has it. const std::string same = "default\n{\n state_entry() {\n llSensorRepeat(\"\", NULL_KEY, AGENT, 10.0, PI, 5.0);\n }\n}\n"; made = actedOn(same, false, "llSensorRepeat", false, "Add a handler for 'sensor'"); - ensure("on its line: " + made, made.find("\n\n sensor(integer num_detected) {\n }\n}\n") != std::string::npos); + UNSCOPED_INFO("on its line: " + made); + REQUIRE(made.find("\n\n sensor(integer num_detected) {\n }\n}\n") != std::string::npos); // Asked for from a function a handler calls, and from one that one // calls: offered all the same. const std::string helper = "arm()\n{\n llSetTimerEvent(1.0);\n}\nstart()\n{\n arm();\n}\n" "default\n{\n state_entry()\n {\n start();\n }\n}\n"; made = actedOn(helper, false, "start();\n }", false, "Add a handler for 'timer'"); - ensure("through two calls: " + made, made.find(" timer()\n {\n }\n") != std::string::npos); + UNSCOPED_INFO("through two calls: " + made); + REQUIRE(made.find(" timer()\n {\n }\n") != std::string::npos); } - template<> template<> - void object::test<19>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a SLua expression chosen goes in a local where it surely runs as before", "[ALScriptFixes]") { - set_test_name("a SLua expression chosen goes into a local, where that runs it as surely as it ran"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); std::string made = actedOn("local function f(n: number)\n print(n * 2 + 1)\nend\nf(1)\n", true, "n * 2", true, "Put it in a local, 'value'"); - ensure_equals("before the statement", made, "local function f(n: number)\n local value = n * 2\n print(value + 1)\nend\nf(1)\n"); + UNSCOPED_INFO("before the statement"); + CHECK(made == "local function f(n: number)\n local value = n * 2\n print(value + 1)\nend\nf(1)\n"); made = actedOn("local x = 1; print(x + 1)\n", true, "x + 1", true, "Put it in a local, 'value'"); - ensure_equals("beside", made, "local x = 1; local value = x + 1; print(value)\n"); + 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"; - ensure("not what and may skip", actedOn(skipped, true, "tostring(x)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("not what and may skip"); + CHECK(actedOn(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"; - ensure("not a loop's condition", actedOn(looped, true, "x < 10", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("not a loop's condition"); + CHECK(actedOn(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"; - ensure("not an elseif's condition", actedOn(elseif, true, "x + 1", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("not an elseif's condition"); + CHECK(actedOn(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"; - ensure("not the whole statement", actedOn(whole, true, "print(math.abs(-1))", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - ensure("its argument yes: " + actedOn(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'"), - actedOn(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'") == "local value = math.abs(-1)\nprint(value)\n"); + UNSCOPED_INFO("not the whole statement"); + CHECK(actedOn(whole, true, "print(math.abs(-1))", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("its argument yes: " + actedOn(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'")); + CHECK(actedOn(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'") == "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"; - ensure("not a last argument", actedOn(two + "print(g(1))\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - ensure("not a last return", actedOn(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); - ensure("not a table's last item", actedOn(two + "local t = { g(1) }\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - ensure("not to two names", actedOn(two + "local a, b = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - ensure("not a for-in's values", actedOn("for k, v in pairs({}) do end\n", true, "pairs({})", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + UNSCOPED_INFO("not a last argument"); + CHECK(actedOn(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(actedOn(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(actedOn(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(actedOn(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(actedOn("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. - ensure("an argument before the last: " + actedOn(two + "print(g(1), 3)\n", true, "g(1)", true, "Put it in a local, 'value'"), - actedOn(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"); - ensure("to one name: " + actedOn(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'"), - actedOn(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) != 0); + UNSCOPED_INFO("an argument before the last: " + actedOn(two + "print(g(1), 3)\n", true, "g(1)", true, "Put it in a local, 'value'")); + CHECK(actedOn(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: " + actedOn(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'")); + CHECK(actedOn(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) != 0); } - template<> template<> - void object::test<20>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a SLua if is inverted; a join interpolated; a local given its type", "[ALScriptFixes]") { - set_test_name("a SLua if is inverted, a concatenation interpolated, a local given the type it has"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); std::string made = actedOn("local x = 1\nif x == 1 then\n print(\"one\")\nelse\n print(\"other\")\nend\n", true, "if", false, "Invert the if"); - ensure_equals("inverted", made, "local x = 1\nif x ~= 1 then\n print(\"other\")\nelse\n print(\"one\")\nend\n"); + UNSCOPED_INFO("inverted"); + CHECK(made == "local x = 1\nif x ~= 1 then\n print(\"other\")\nelse\n print(\"one\")\nend\n"); made = actedOn("local x = 1\nif not (x > 1) then\n print(\"few\")\nelse\n print(\"many\")\nend\n", true, "if", false, "Invert the if"); - ensure_equals("a not taken off", made, "local x = 1\nif x > 1 then\n print(\"many\")\nelse\n print(\"few\")\nend\n"); + UNSCOPED_INFO("a not taken off"); + CHECK(made == "local x = 1\nif x > 1 then\n print(\"many\")\nelse\n print(\"few\")\nend\n"); made = actedOn("local x = 1\nprint(\"n = \" .. x .. \"!\")\n", true, "..", false, "Write it as an interpolated string"); - ensure_equals("interpolated", made, "local x = 1\nprint(`n = {x}!`)\n"); + UNSCOPED_INFO("interpolated"); + CHECK(made == "local x = 1\nprint(`n = {x}!`)\n"); made = actedOn("local count = 5\nprint(count)\n", true, "count", false, "Declare it as 'number'"); - ensure_equals("annotated", made, "local count: number = 5\nprint(count)\n"); + UNSCOPED_INFO("annotated"); + CHECK(made == "local count: number = 5\nprint(count)\n"); } - template<> template<> - void object::test<21>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a SLua script asking for an event it does not hear is offered a handler", "[ALScriptFixes]") { - set_test_name("a handler is offered for an event a SLua script asks for and does not hear, with the parameters the definitions give"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); // Nonstrict, as the grid compiles: the new solver checking strict // cannot yet push an overloaded function's parameter types into a // function given to it, so every LLEvents:on there is an error it @@ -678,23 +803,26 @@ namespace tut luau.setConfig(grid); const std::string script = "ll.Listen(0, \"\", ll.GetOwner(), \"\")\n"; std::string made = actedOn(script, true, "Listen", false, "Add a handler for 'listen'"); - ensure_equals("at the end", made, + UNSCOPED_INFO("at the end"); + 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"; - ensure("heard: " + titles(actions(heard, true, 0, 3, 0, 3)), actedOn(heard, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) == 0); + UNSCOPED_INFO("heard: " + titles(actions(heard, true, 0, 3, 0, 3))); + CHECK(actedOn(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"; - ensure("offered where only taken off: " + titles(actions(off, true, 0, 3, 0, 3)), - actedOn(off, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) != 0); + UNSCOPED_INFO("offered where only taken off: " + titles(actions(off, true, 0, 3, 0, 3))); + CHECK(actedOn(off, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) != 0); } - template<> template<> - void object::test<22>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a refactor's name is fresh and a line put in maps to its source line", "[ALScriptFixes]") { - set_test_name("a refactor's name is one the text has nowhere, and one put in at a line's start is mapped to the source line it began as"); - ensure_equals("free", ALScriptFixes::freshName("local x = 1", "value"), std::string("value")); - ensure_equals("taken", ALScriptFixes::freshName("local value = 1 -- value2", "value"), std::string("value3")); - ensure_equals("a part is not a word", ALScriptFixes::freshName("local values = 1", "value"), std::string("value")); + UNSCOPED_INFO("free"); + CHECK(ALScriptFixes::freshName("local x = 1", "value") == std::string("value")); + UNSCOPED_INFO("taken"); + CHECK(ALScriptFixes::freshName("local value = 1 -- value2", "value") == std::string("value3")); + UNSCOPED_INFO("a part is not a word"); + CHECK(ALScriptFixes::freshName("local values = 1", "value") == std::string("value")); // A line the preprocessor carries as it stands, a line down for the // directive it drops. ALPreprocessor::Options options; @@ -704,19 +832,21 @@ namespace tut const ALPreprocessor::Result result = ALPreprocessor::run(script, options); ALScriptProblem held; const ALSourceMap::Loc carried = result.map.toExpanded(0, 5, 8); - ensure("carried", carried.found()); + UNSCOPED_INFO("carried"); + REQUIRE(carried.found()); held.fixes.push_back(ALScriptFixes::titled("ScriptActionExtract", "Put it in a local, '[1]'", { "value" })); held.fixes.back().edits.push_back({ carried.line, 0, carried.line, 0, " integer value = 1;\n" }); ALScriptFixes::mapThrough(result.map, held); - ensure_equals("kept", held.fixes.size(), static_cast(1)); - ensure_equals("the source's line", held.fixes.front().edits.front().line, 5); - ensure_equals("its start", held.fixes.front().edits.front().column, 0); + UNSCOPED_INFO("kept"); + REQUIRE(held.fixes.size() == static_cast(1)); + UNSCOPED_INFO("the source's line"); + CHECK(held.fixes.front().edits.front().line == 5); + UNSCOPED_INFO("its start"); + CHECK(held.fixes.front().edits.front().column == 0); } - template<> template<> - void object::test<23>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a module's exports are the names of the table it returns however built", "[ALScriptFixes]") { - set_test_name("a module's exports are the names of the table it returns, however it builds it"); const auto listed = [](const std::string& source) { std::string out; for (const std::string& name : ALLuauExports::of(source)) @@ -725,24 +855,29 @@ namespace tut } return out; }; - ensure_equals("a table returned", listed("local function greet() end\nreturn { greet = greet, count = 3, [\"spaced out\"] = 1, 4 }\n"), + UNSCOPED_INFO("a table returned"); + CHECK(listed("local function greet() end\nreturn { greet = greet, count = 3, [\"spaced out\"] = 1, 4 }\n") == std::string("greet count")); - ensure_equals("a local table filled in", - listed("local M = { count = 3 }\nfunction M.greet() end\nfunction M:reset() end\nM.name = \"util\"\nM[\"size\"] = 2\n" - "local other = {}\nother.hidden = 1\nreturn M\n"), + UNSCOPED_INFO("a local table filled in"); + CHECK(listed("local M = { count = 3 }\nfunction M.greet() end\nfunction M:reset() end\nM.name = \"util\"\nM[\"size\"] = 2\n" + "local other = {}\nother.hidden = 1\nreturn M\n") == std::string("count greet reset name size")); - ensure_equals("through setmetatable", listed("local M = {}\nM.x = 1\nreturn setmetatable(M, { __index = M })\n"), std::string("x")); - ensure_equals("each once", listed("local M = {}\nM.x = 1\nM.x = 2\nreturn M\n"), std::string("x")); - ensure_equals("a function: no names", listed("return function() end\n"), std::string()); - ensure_equals("nothing returned", listed("local M = {}\nM.x = 1\n"), std::string()); - ensure_equals("a module that does not parse", listed("local M = {\nreturn M\n"), std::string()); - ensure_equals("not a keyword", listed("return { [\"end\"] = 1, ok = 2 }\n"), std::string("ok")); + UNSCOPED_INFO("through setmetatable"); + CHECK(listed("local M = {}\nM.x = 1\nreturn setmetatable(M, { __index = M })\n") == std::string("x")); + UNSCOPED_INFO("each once"); + CHECK(listed("local M = {}\nM.x = 1\nM.x = 2\nreturn M\n") == std::string("x")); + UNSCOPED_INFO("a function: no names"); + CHECK(listed("return function() end\n") == std::string()); + UNSCOPED_INFO("nothing returned"); + CHECK(listed("local M = {}\nM.x = 1\n") == std::string()); + UNSCOPED_INFO("a module that does not parse"); + CHECK(listed("local M = {\nreturn M\n") == std::string()); + UNSCOPED_INFO("not a keyword"); + CHECK(listed("return { [\"end\"] = 1, ok = 2 }\n") == std::string("ok")); } - template<> template<> - void object::test<24>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a require goes after the opening requires or comments apart from code", "[ALScriptFixes]") { - set_test_name("a require goes after the requires a script opens with, else after its comments, apart from the code"); ALScriptProblem problem; problem.key = "LuauUnknownGlobal"; problem.args = { "util" }; @@ -751,29 +886,38 @@ namespace tut ALScriptFixes::offerRequire(problem, text, module, field); return problem.fixes.empty() ? std::string("none") : ALScriptFixes::apply(text, problem.fixes.front()).value_or("refused"); }; - ensure_equals("after the last require", made("--!strict\nlocal a = require(\"a\")\nlocal b = require(\"b\").b\n\nprint(util.x)\n", "util", false), + 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")); - ensure_equals("its words", problem.fixes.front().title, std::string("Require 'util'")); - ensure_equals("after the comments, the blank line kept", - made("--!strict\n--[[ A header\n over lines ]]\n\nprint(util.x)\n", "@lib/util", false), + UNSCOPED_INFO("its words"); + 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) == std::string("--!strict\n--[[ A header\n over lines ]]\nlocal util = require(\"@lib/util\")\n\nprint(util.x)\n")); - ensure_equals("apart from code that follows at once", made("print(util)\n", "helpers", true), + 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")); - ensure_equals("a field's words", problem.fixes.front().title, std::string("Take 'util' from 'helpers'")); - ensure_equals("past a last line with no break", made("-- only a comment", "util", false), + UNSCOPED_INFO("a field's words"); + 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) == std::string("-- only a comment\nlocal util = require(\"util\")")); - ensure_equals("a name quoted", made("print(util)\n", "a\"b", false), std::string("local util = require(\"a\\\"b\")\n\nprint(util)\n")); + UNSCOPED_INFO("a name quoted"); + CHECK(made("print(util)\n", "a\"b", false) == std::string("local util = require(\"a\\\"b\")\n\nprint(util)\n")); problem.fixes.clear(); ALScriptFixes::offerRequire(problem, "print(util)\n", "util", false); ALScriptFixes::offerRequire(problem, "print(util)\n", "util", false); - ensure_equals("offered once", problem.fixes.size(), static_cast(1)); - ensure("neither preferred nor safe", !problem.fixes.front().preferred && !problem.fixes.front().safe); + UNSCOPED_INFO("offered once"); + REQUIRE(problem.fixes.size() == static_cast(1)); + { + INFO("neither preferred nor safe"); + CHECK_FALSE(problem.fixes.front().preferred); + CHECK_FALSE(problem.fixes.front().safe); + } } - template<> template<> - void object::test<25>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL include exports its macros and top-level functions and globals", "[ALScriptFixes]") { - set_test_name("an LSL include declares its macros and the functions and globals at its top, a fragment and all"); const auto listed = [](const std::string& source) { std::string out; for (const std::string& name : ALLSLExports::of(source)) @@ -782,23 +926,23 @@ namespace tut } return out; }; - ensure_equals("everything at the top", - listed("// helpers\n#define CHANNEL -42\n#define say(x) \\\n llOwnerSay(x)\n#ifdef DEBUG\n#include \"more.lsl\"\n#endif\n" + UNSCOPED_INFO("everything at the top"); + CHECK(listed("// helpers\n#define CHANNEL -42\n#define say(x) \\\n llOwnerSay(x)\n#ifdef DEBUG\n#include \"more.lsl\"\n#endif\n" "integer gCount = 0;\nlist gSeen;\nvector gAt = <1, 2, 3>;\n" "string greet(string name)\n{\n integer inner = 1;\n return \"hi \" + name;\n}\n" - "reset() { gCount = 0; }\ninline integer twice(integer n) { return n * 2; }\n"), + "reset() { gCount = 0; }\ninline integer twice(integer n) { return n * 2; }\n") == std::string("CHANNEL say gCount gSeen gAt greet reset twice")); - ensure_equals("a state holds no declarations", - listed("integer shared;\ndefault\n{\n state_entry()\n {\n integer local = 1;\n }\n}\nstate other { touch_start(integer n) {} }\n"), + UNSCOPED_INFO("a state holds no declarations"); + CHECK(listed("integer shared;\ndefault\n{\n state_entry()\n {\n integer local = 1;\n }\n}\nstate other { touch_start(integer n) {} }\n") == std::string("shared")); - ensure_equals("each once", listed("#define A 1\n#undef A\n#define A 2\n"), std::string("A")); - ensure_equals("SLua is no LSL", listed("local function greet() end\nprint(greet())\nreturn { greet = greet }\n"), std::string()); + UNSCOPED_INFO("each once"); + CHECK(listed("#define A 1\n#undef A\n#define A 2\n") == std::string("A")); + UNSCOPED_INFO("SLua is no LSL"); + CHECK(listed("local function greet() end\nprint(greet())\nreturn { greet = greet }\n") == std::string()); } - template<> template<> - void object::test<26>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL include goes after opening directives or comments apart from code", "[ALScriptFixes]") { - set_test_name("an include goes after the directives an LSL script opens with, else after its comments, apart from the code"); ALScriptProblem problem; problem.key = "LSLUndeclared"; problem.args = { "greet" }; @@ -807,17 +951,22 @@ namespace tut ALScriptFixes::offerInclude(problem, text, include); return problem.fixes.empty() ? std::string("none") : ALScriptFixes::apply(text, problem.fixes.front()).value_or("refused"); }; - ensure_equals("after the last directive", - made("// Hello\n#include \"a.lsl\"\n#define LONG \\\n 1\n\ndefault {}\n", "helpers.lsl"), + 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")); - ensure_equals("its words", problem.fixes.front().title, std::string("Include 'helpers.lsl'")); - ensure_equals("after the comments, the blank line kept", made("/* A header\n over lines */\n\ndefault {}\n", "helpers"), + UNSCOPED_INFO("its words"); + 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") == std::string("/* A header\n over lines */\n#include \"helpers\"\n\ndefault {}\n")); - ensure_equals("apart from code that follows at once", made("default {}\n", "helpers"), std::string("#include \"helpers\"\n\ndefault {}\n")); - ensure_equals("no name with a quote in it", made("default {}\n", "a\"b"), std::string("none")); + UNSCOPED_INFO("apart from code that follows at once"); + CHECK(made("default {}\n", "helpers") == std::string("#include \"helpers\"\n\ndefault {}\n")); + UNSCOPED_INFO("no name with a quote in it"); + 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. - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + 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); for (const char* name : { "greet", "gCount", "CHANNEL" }) @@ -827,14 +976,13 @@ namespace tut { said_so = said_so || (each.key == "LSLUndeclared" && each.args.size() == 1 && each.args[0] == name); } - ensure(std::string("undeclared: ") + name + "\n" + said(problems), said_so); + UNSCOPED_INFO(std::string("undeclared: ") + name + "\n" + said(problems)); + REQUIRE(said_so); } } - template<> template<> - void object::test<27>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a source fix maps into the expansion where copied but not over a macro", "[ALScriptFixes]") { - set_test_name("a fix made over the source is taken into the expansion where it lands on what the expansion copied, and not where a macro stood"); ALPreprocessor::Options options; options.fileName = "main.lsl"; options.resolve = [](const ALPreprocessor::Ask&, ALPreprocessor::Include&) { return ALPreprocessor::Found::No; }; @@ -856,43 +1004,59 @@ namespace tut return made.has_value(); }; std::string out; - ensure("a word the expansion copied", into(edited(5, 20, 5, 25, "hi"), out)); + UNSCOPED_INFO("a word the expansion copied"); + CHECK(into(edited(5, 20, 5, 25, "hi"), out)); std::string expected = result.text; expected.replace(expected.find("\"hello\""), 7, "\"hi\""); - ensure_equals("replaced there alone", out, expected); + UNSCOPED_INFO("replaced there alone"); + CHECK(out == expected); ALScriptFix over_macro = edited(6, 27, 6, 28, "3"); - ensure("not where a macro stood", !ALScriptFixes::intoExpansion(result.map, over_macro)); - ensure_equals("and the fix as it was", over_macro.edits.front().line, 6); - ensure("nor across lines unless whole", !into(edited(5, 8, 6, 8, ""), out)); - - ensure("a line put in at a line's start", into(edited(7, 0, 7, 0, " integer x;\n"), out)); + UNSCOPED_INFO("not where a macro stood"); + CHECK_FALSE(ALScriptFixes::intoExpansion(result.map, over_macro)); + UNSCOPED_INFO("and the fix as it was"); + CHECK(over_macro.edits.front().line == 6); + UNSCOPED_INFO("nor across lines unless whole"); + CHECK_FALSE(into(edited(5, 8, 6, 8, ""), out)); + + UNSCOPED_INFO("a line put in at a line's start"); + CHECK(into(edited(7, 0, 7, 0, " integer x;\n"), out)); const size_t put = out.find("integer x;"); - ensure("before the line it was put before", put != std::string::npos && put < out.find("\"bye\"") && put > out.find("(string)")); - ensure("whole lines taken out", into(edited(5, 0, 6, 0, ""), out)); - ensure("that line gone, the rest kept", out.find("hello") == std::string::npos && out.find("\"bye\"") != std::string::npos && - out.find("(string)") != std::string::npos); + { + INFO("before the line it was put before"); + REQUIRE(put != std::string::npos); + CHECK(put < out.find("\"bye\"")); + CHECK(put > out.find("(string)")); + } + UNSCOPED_INFO("whole lines taken out"); + CHECK(into(edited(5, 0, 6, 0, ""), out)); + { + INFO("that line gone, the rest kept"); + REQUIRE(out.find("hello") == std::string::npos); + REQUIRE(out.find("\"bye\"") != std::string::npos); + REQUIRE(out.find("(string)") != std::string::npos); + } } - template<> template<> - void object::test<28>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a repeated LSL string goes into a global after the others; uses named", "[ALScriptFixes]") { - set_test_name("a string written the same several times over goes into a global after the ones the script opens with, each use named"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string script = "string gGreeting = \"hello there\";\n" "default\n{\n state_entry()\n {\n llOwnerSay(\"hello there\");\n" " llSetText(\"hello there\", <1.0, 1.0, 1.0>, 1.0);\n }\n" " touch_start(integer n)\n {\n llOwnerSay(\"hello there\");\n llOwnerSay(\"other\");\n }\n}\n"; const std::string made = actedOn(script, false, "\"hello there\");", false, "Put the string in a global, 'gHelloThere', for its 3 uses"); - ensure_equals("declared after the globals, each use named, the global's own value kept", made, + UNSCOPED_INFO("declared after the globals, each use named, the global's own value kept"); + CHECK(made == "string gGreeting = \"hello there\";\nstring gHelloThere = \"hello there\";\n" "default\n{\n state_entry()\n {\n llOwnerSay(gHelloThere);\n" " llSetText(gHelloThere, <1.0, 1.0, 1.0>, 1.0);\n }\n" " touch_start(integer n)\n {\n llOwnerSay(gHelloThere);\n llOwnerSay(\"other\");\n }\n}\n"); // A short one costs more as a global than it saves: the list says // so, weighing it. A long one, used as often, is lighter. - ensure("heavier on LSO: " + std::to_string(ALScriptWeigh::lso(made).total) + " from " + std::to_string(ALScriptWeigh::lso(script).total), - ALScriptWeigh::lso(made).total >= ALScriptWeigh::lso(script).total); + UNSCOPED_INFO("heavier on LSO: " + std::to_string(ALScriptWeigh::lso(made).total) + " from " + std::to_string(ALScriptWeigh::lso(script).total)); + CHECK(ALScriptWeigh::lso(made).total >= ALScriptWeigh::lso(script).total); std::string long_script = script; std::string long_made = made; for (std::string* text : { &long_script, &long_made }) @@ -902,67 +1066,89 @@ namespace tut text->replace(at, 11, "hello there, and a sentence of some length said to the owner"); } } - ensure("a long one lighter on LSO: " + std::to_string(ALScriptWeigh::lso(long_made).total) + " from " + - std::to_string(ALScriptWeigh::lso(long_script).total), - ALScriptWeigh::lso(long_made).total < ALScriptWeigh::lso(long_script).total); - ensure("not for a string used once", actedOn(script, false, "\"other\"", false, "Put the string in a global, 'gOther', for its 1 uses").rfind("offered", 0) == 0); + UNSCOPED_INFO("a long one lighter on LSO: " + std::to_string(ALScriptWeigh::lso(long_made).total) + " from " + + std::to_string(ALScriptWeigh::lso(long_script).total)); + CHECK(ALScriptWeigh::lso(long_made).total < ALScriptWeigh::lso(long_script).total); + UNSCOPED_INFO("not for a string used once"); + CHECK(actedOn(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"; const std::string placed = actedOn(bare, false, "\"hi", false, "Put the string in a global, 'gHi', for its 2 uses"); - ensure("ahead of the state, a blank line after: " + placed, placed.rfind("string gHi = \"hi\\n\";\n\ndefault\n", 0) == 0); + UNSCOPED_INFO("ahead of the state, a blank line after: " + placed); + CHECK(placed.rfind("string gHi = \"hi\\n\";\n\ndefault\n", 0) == 0); } - template<> template<> - void object::test<29>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a list literal is written as a sum bracketed where part of more", "[ALScriptFixes]") { - set_test_name("a list written out is written as a sum, each element meaning what it meant, and the sum bracketed where it is part of more"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string script = "list gFixed = [1, 2];\n" "default\n{\n state_entry()\n {\n integer n = 5;\n" " list l = [n, \"two\", <3.0, 3.0, 3.0>, -4, n + 3, llGetListLength(gFixed)];\n" " llOwnerSay((string)[1, 2]);\n }\n}\n"; std::string made = actedOn(script, false, "[n,", false, "Write the list as a sum"); - ensure_equals("each where it would mean what it meant", made, + UNSCOPED_INFO("each where it would mean what it meant"); + CHECK(made == "list gFixed = [1, 2];\n" "default\n{\n state_entry()\n {\n integer n = 5;\n" " list l = (list)n + \"two\" + <3.0, 3.0, 3.0> + (-4) + (n + 3) + llGetListLength(gFixed);\n" " llOwnerSay((string)[1, 2]);\n }\n}\n"); made = actedOn(script, false, "[1, 2]);", false, "Write the list as a sum"); - ensure("bracketed under a cast: " + made, made.find("llOwnerSay((string)((list)1 + 2));") != std::string::npos); - ensure("not a global's value, which must be written out", actedOn(script, false, "[1, 2];", false, "Write the list as a sum").rfind("offered", 0) == 0); + 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(actedOn(script, false, "[1, 2];", false, "Write the list as a sum").rfind("offered", 0) == 0); } - template<> template<> - void object::test<30>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a fix that takes code out says so and one that only renames does not", "[ALScriptFixes]") { - set_test_name("a fix that takes code out says so, and one that only renames does not, so that a save makes the one and not the other"); - ensure("builtins: " + error, lslLoaded); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); + UNSCOPED_INFO("definitions: " + error); + 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); const ALScriptProblem* unused = keyed(lsl, "LSLDeclaredButNotUsed"); - ensure("said", unused != nullptr && !unused->fixes.empty()); - ensure("a removal is safe, and removes", unused->fixes.front().safe && unused->fixes.front().removes); + { + INFO("said"); + REQUIRE(unused != nullptr); + REQUIRE_FALSE(unused->fixes.empty()); + } + { + INFO("a removal is safe, and removes"); + REQUIRE(unused->fixes.front().safe); + CHECK(unused->fixes.front().removes); + } const ALScriptProblems lua = check("local function f()\n local unused = 1\nend\nf()\n", true); const ALScriptProblem* local = keyed(lua, "LuauLintLocalUnused"); - ensure("said: " + said(lua), local != nullptr && !local->fixes.empty()); - ensure("a rename is safe, and removes nothing", local->fixes.front().safe && !local->fixes.front().removes); + { + INFO("said: " + said(lua)); + REQUIRE(local != nullptr); + REQUIRE_FALSE(local->fixes.empty()); + } + { + INFO("a rename is safe, and removes nothing"); + REQUIRE(local->fixes.front().safe); + CHECK_FALSE(local->fixes.front().removes); + } } - template<> template<> - void object::test<31>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: short names guess one edit away; a swap is one edit; ties not preferred", "[ALScriptFixes]") { - set_test_name("a short name gets a guess only one edit away, a swap is one edit, and names as near as each other are all offered and none preferred"); - ensure("builtins: " + error, lslLoaded); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); const std::string head = "default\n{\n state_entry()\n {\n"; const std::string tail = " }\n}\n"; // One character: nothing but itself in another case; two edits // made PI of it once. ALScriptProblems problems = check(head + " llOwnerSay((string)n);\n" + tail, false); const ALScriptProblem* problem = keyed(problems, "LSLUndeclared"); - ensure("said: " + said(problems), problem != nullptr); - ensure("no guess at n: " + titles(problem->fixes), problem->fixes.empty()); + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("no guess at n: " + titles(problem->fixes)); + REQUIRE(problem->fixes.empty()); const auto unknown = [](const ALScriptProblems& all) -> const ALScriptProblem* { for (const ALScriptProblem& each : all) { @@ -975,58 +1161,83 @@ namespace tut }; problems = check("print(xy)\n", true); problem = unknown(problems); - ensure("said: " + said(problems), problem != nullptr); - ensure("no guess at xy two edits from ll: " + titles(problem->fixes), problem->fixes.empty()); + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("no guess at xy two edits from ll: " + titles(problem->fixes)); + REQUIRE(problem->fixes.empty()); // Two as near: both, neither preferred. problems = check(head + " integer cat = 1;\n integer car = 2;\n llOwnerSay((string)(cat + car + cax));\n" + tail, false); problem = keyed(problems, "LSLUndeclared"); - ensure("said: " + said(problems), problem != nullptr); - ensure_equals("both: " + titles(problem->fixes), problem->fixes.size(), size_t(2)); - ensure("neither preferred", !problem->fixes[0].preferred && !problem->fixes[1].preferred); + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("both: " + titles(problem->fixes)); + REQUIRE(problem->fixes.size() == size_t(2)); + { + INFO("neither preferred"); + REQUIRE_FALSE(problem->fixes[0].preferred); + CHECK_FALSE(problem->fixes[1].preferred); + } problems = check("local cat, car = 1, 2\nprint(cat, car, cax)\n", true); problem = unknown(problems); - ensure("said: " + said(problems), problem != nullptr); - ensure("both in SLua: " + titles(problem->fixes), problem->fixes.size() == 2 && !problem->fixes[0].preferred && !problem->fixes[1].preferred); + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + { + INFO("both in SLua: " + titles(problem->fixes)); + REQUIRE(problem->fixes.size() == 2); + REQUIRE_FALSE(problem->fixes[0].preferred); + CHECK_FALSE(problem->fixes[1].preferred); + } // One near a short name: offered, not preferred; one near a longer // name, preferred, a swap being one edit. problems = check(head + " integer cat = 1;\n llOwnerSay((string)(cat + cta));\n" + tail, false); problem = keyed(problems, "LSLUndeclared"); - ensure("said: " + said(problems), problem != nullptr && problem->fixes.size() == 1); - ensure("a guess at three characters is not preferred", !problem->fixes.front().preferred); + { + INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + REQUIRE(problem->fixes.size() == 1); + } + UNSCOPED_INFO("a guess at three characters is not preferred"); + CHECK_FALSE(problem->fixes.front().preferred); problems = check(head + " integer total = 1;\n llOwnerSay((string)(total + totla));\n" + tail, false); problem = keyed(problems, "LSLUndeclared"); - ensure("said: " + said(problems), problem != nullptr && problem->fixes.size() == 1 && problem->fixes.front().preferred); - ensure_equals("swapped back", ALScriptFixes::editDistance("totla", "total"), size_t(1)); + { + INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + REQUIRE(problem->fixes.size() == 1); + CHECK(problem->fixes.front().preferred); + } + UNSCOPED_INFO("swapped back"); + CHECK(ALScriptFixes::editDistance("totla", "total") == size_t(1)); // A name one edit past how far a guess may be is no guess, even // where it is the only one that near. problems = check(head + " integer count = 1;\n llOwnerSay((string)(count + cqunx));\n" + tail, false); problem = keyed(problems, "LSLUndeclared"); - ensure("said: " + said(problems), problem != nullptr); - ensure("two edits from a five-letter name: no guess: " + titles(problem->fixes), problem->fixes.empty()); + UNSCOPED_INFO("said: " + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("two edits from a five-letter name: no guess: " + titles(problem->fixes)); + REQUIRE(problem->fixes.empty()); } - template<> template<> - void object::test<32>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a fix's edits are applied in text order: insertions before replacements", "[ALScriptFixes]") { - set_test_name("a fix's edits are made in the text's order, something put in at a place before a stretch replaced from there, whatever order it gives them"); for (const bool insertion_first : { true, false }) { ALScriptFix fix; const ALScriptEdit put{ 0, 0, 0, 0, "local v = print(x)\n" }; const ALScriptEdit swap{ 0, 0, 0, 8, "v" }; fix.edits = insertion_first ? std::vector{ put, swap } : std::vector{ swap, put }; - ensure_equals(insertion_first ? "put in first" : "replaced first", ALScriptFixes::apply("print(x)\n", fix).value_or("refused"), + UNSCOPED_INFO((insertion_first ? "put in first" : "replaced first")); + REQUIRE(ALScriptFixes::apply("print(x)\n", fix).value_or("refused") == std::string("local v = print(x)\nv\n")); } ALScriptFix two; two.edits = { { 0, 0, 0, 0, "a" }, { 0, 0, 0, 0, "b" } }; - ensure_equals("two at one place in the order given", ALScriptFixes::apply("x", two).value_or("refused"), std::string("abx")); + UNSCOPED_INFO("two at one place in the order given"); + CHECK(ALScriptFixes::apply("x", two).value_or("refused") == std::string("abx")); } - template<> template<> - void object::test<33>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: the optimizer's removal is added unpreferred; other fixes keep their mark", "[ALScriptFixes]") { - set_test_name("the optimizer's removal, offered beside the analyzer's, takes the preferred mark off its own fix only"); ALScriptProblem problem; problem.key = "OptimizerRemovedLocal"; problem.args = { "x" }; @@ -1040,112 +1251,155 @@ namespace tut problem.column = 0; problem.endColumn = 1; ALScriptFixes::attachOptimizer(problem, "integer x;\n"); - ensure_equals("nothing added", problem.fixes.size(), size_t(1)); - ensure("still preferred", problem.fixes.front().preferred); + UNSCOPED_INFO("nothing added"); + REQUIRE(problem.fixes.size() == size_t(1)); + UNSCOPED_INFO("still preferred"); + CHECK(problem.fixes.front().preferred); // Where it can, its own is added unpreferred. problem.line = problem.endLine = 0; problem.column = 8; problem.endColumn = 9; ALScriptFixes::attachOptimizer(problem, "integer x;\n"); - ensure("added: " + std::to_string(problem.fixes.size()), problem.fixes.size() == 2 && !problem.fixes.back().preferred && problem.fixes.front().preferred); + { + INFO("added: " + std::to_string(problem.fixes.size())); + REQUIRE(problem.fixes.size() == 2); + CHECK_FALSE(problem.fixes.back().preferred); + CHECK(problem.fixes.front().preferred); + } } - template<> template<> - void object::test<34>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: LSL's operators and zero indexes in SLua fixed; only ~= is safe", "[ALScriptFixes]") { - set_test_name("SLua written with LSL's habits -- != && || ! -- changed to SLua's words, only ~= safe; an index or a loop from 0 counted from 1"); - ensure_equals("~=", fixed("local a, b = 1, 2\nif a != b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '!=' to '~='"), + UNSCOPED_INFO("~="); + CHECK(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")); - ensure_equals("and, apart from what it joins", fixed("local a, b = true, false\nif a&&b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '&&' to 'and'"), + UNSCOPED_INFO("and, apart from what it joins"); + CHECK(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")); - ensure_equals("or", fixed("local a, b = true, false\nif a || b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '||' to 'or'"), + UNSCOPED_INFO("or"); + CHECK(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")); - ensure_equals("not, apart from its operand", fixed("local a = true\nif(!a) then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '!' to 'not'"), + UNSCOPED_INFO("not, apart from its operand"); + CHECK(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"); const ALScriptProblem* negated = keyed(denied, "LuauUnexpectedDidYouMean"); - ensure("~= safe, the same whatever it compares", unequal && unequal->fixes.front().safe); - ensure("not unsafe: 0 is true in SLua", negated && !negated->fixes.front().safe); + { + INFO("~= safe, the same whatever it compares"); + REQUIRE(unequal); + CHECK(unequal->fixes.front().safe); + } + { + INFO("not unsafe: 0 is true in SLua"); + REQUIRE(negated); + CHECK_FALSE(negated->fixes.front().safe); + } - ensure_equals("an index from 0", fixed("local t = {}\ntable.insert(t, 0, 5)\n", true, "LuauLintTableInsertZero", "Change '0' to '1'"), + UNSCOPED_INFO("an index from 0"); + CHECK(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")); - ensure_equals("a loop from 0", fixed("local t = {1, 2}\nfor i = 0, #t do print(t[i]) end\n", true, "LuauLintForRangeZero", "Change '0' to '1'"), + UNSCOPED_INFO("a loop from 0"); + CHECK(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")); } - template<> template<> - void object::test<35>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: LSL fixes: an if's ';' and event parameters and untyped ones and braces", "[ALScriptFixes]") { - set_test_name("LSL: an if ended by its ';', an event's parameters as it takes them, a parameter with no type, a declaration braced, a missing ';' safe"); - ensure_equals("the if's ';' out", fixed("default { state_entry() { integer x = llGetUnixTime(); if (x); llOwnerSay(\"a\"); } }", false, "LSLEmptyIf", - "Take out the ';' that ends the if"), + UNSCOPED_INFO("the if's ';' out"); + CHECK(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\"); } }")); - ensure_equals("a wrong type, the name kept", fixed("default { touch_start(string n) { llOwnerSay((string)n); } }", false, "LSLArgumentWrongTypeEvent", - "Write the parameters 'touch_start' takes"), + UNSCOPED_INFO("a wrong type, the name kept"); + CHECK(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); } }")); - ensure_equals("too few, the builtins' names", fixed("default { touch_start() { } }", false, "LSLTooFewArgumentsEvent", "Write the parameters 'touch_start' takes"), + UNSCOPED_INFO("too few, the builtins' names"); + CHECK(fixed("default { touch_start() { } }", false, "LSLTooFewArgumentsEvent", "Write the parameters 'touch_start' takes") == std::string("default { touch_start(integer num_detected) { } }")); - ensure_equals("too many", fixed("default { touch_start(integer n, integer m) { } }", false, "LSLTooManyArgumentsEvent", "Write the parameters 'touch_start' takes"), + UNSCOPED_INFO("too many"); + CHECK(fixed("default { touch_start(integer n, integer m) { } }", false, "LSLTooManyArgumentsEvent", "Write the parameters 'touch_start' takes") == std::string("default { touch_start(integer n) { } }")); - ensure_equals("an event's with no type", fixed("default { touch_start(n) { } }", false, "LSLParameterUntyped", "Write the parameters 'touch_start' takes"), + UNSCOPED_INFO("an event's with no type"); + CHECK(fixed("default { touch_start(n) { } }", false, "LSLParameterUntyped", "Write the parameters 'touch_start' takes") == std::string("default { touch_start(integer n) { } }")); - ensure_equals("a declaration as an if's body, braced", fixed("default { state_entry() { if (llGetUnixTime()) integer i = 1; } }", false, "LSLDeclarationInvalidHere", - "Put it in braces of its own"), + UNSCOPED_INFO("a declaration as an if's body, braced"); + CHECK(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); const ALScriptProblem* bare = keyed(untyped, "LSLParameterUntyped"); - ensure("a function's: every type, none preferred", bare && bare->fixes.size() == 7 && - std::none_of(bare->fixes.begin(), bare->fixes.end(), [](const ALScriptFix& fix) { return fix.preferred; })); + { + INFO("a function's: every type, none preferred"); + REQUIRE(bare); + REQUIRE(bare->fixes.size() == 7); + CHECK(std::none_of(bare->fixes.begin(), bare->fixes.end(), [](const ALScriptFix& fix) { return fix.preferred; })); + } const std::optional typed = ALScriptFixes::apply(function, bare->fixes.front()); - ensure("declared as the first", typed && typed->rfind("say(integer x)", 0) == 0 && errors(check(*typed, false)) == 0); + { + INFO("declared as the first"); + REQUIRE(typed); + REQUIRE(typed->rfind("say(integer x)", 0) == 0); + CHECK(errors(check(*typed, false)) == 0); + } const ALScriptProblems missing = check("default { state_entry() { integer x = 1 llOwnerSay(\"a\"); } }", false); const ALScriptProblem* ended = keyed(missing, "LSLSyntaxMissing"); - ensure("a missing ';' put in safely", ended && !ended->fixes.empty() && ended->fixes.front().safe); + { + INFO("a missing ';' put in safely"); + REQUIRE(ended); + REQUIRE_FALSE(ended->fixes.empty()); + CHECK(ended->fixes.front().safe); + } } - template<> template<> - void object::test<36>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a handler's missing brace goes on its own line level with the next one", "[ALScriptFixes]") { - set_test_name("a handler's missing brace is put on a line of its own, level with the handler after it, and the script checks clean"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + 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"; const std::string made = fixed(script, false, "LSLSyntaxMissing", "Insert '}'"); - ensure("closed after the call, its own line: " + made, made.find("\"Hello, Avatar!\");\n }\n\n touch_start(") != std::string::npos); - ensure("no error left", errors(check(made, false)) == 0); + UNSCOPED_INFO("closed after the call, its own line: " + made); + REQUIRE(made.find("\"Hello, Avatar!\");\n }\n\n touch_start(") != std::string::npos); + UNSCOPED_INFO("no error left"); + CHECK(errors(check(made, false)) == 0); } - template<> template<> - void object::test<37>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlCompoundAssign's fix writes x op= y safely; a field and a join too", "[ALScriptFixes]") { - set_test_name("SlCompoundAssign's fix: x = x op y written x op= y, safe; a field and a join too"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); const std::string made = fixed("local x = 1\nx = x + 2 * x\nprint(x)\n", true, "LuauLintSlCompoundAssign", "Write it x += ..."); - ensure_equals("the text", made, std::string("local x = 1\nx += 2 * x\nprint(x)\n")); + UNSCOPED_INFO("the text"); + CHECK(made == std::string("local x = 1\nx += 2 * x\nprint(x)\n")); const ALScriptProblems problems = check("local x = 1\nx = x + 2 * x\nprint(x)\n", true); const ALScriptProblem* problem = keyed(problems, "LuauLintSlCompoundAssign"); - ensure("safe", problem && problem->fixes.front().safe); - ensure_equals("a field, joined", fixed("local t = { n = \"a\" }\nt.n = t.n ..\"b\" .. \"c\"\nprint(t.n)\n", true, "LuauLintSlCompoundAssign", - "Write it t.n ..= ..."), + { + INFO("safe"); + REQUIRE(problem); + CHECK(problem->fixes.front().safe); + } + UNSCOPED_INFO("a field, joined"); + CHECK(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")); } - template<> template<> - void object::test<38>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlLoopInvariantCall's fix: the call in a fresh local before the loop", "[ALScriptFixes]") { - set_test_name("SlLoopInvariantCall's fix: the call in a local before the loop, named as nothing else is, and read in the check; not safe"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string script = "default { state_entry() {\n" " list l = [1, 2]; integer length = 3; integer i;\n" " for (i = 0; i < llGetListLength(l); ++i) llOwnerSay((string)(i + length));\n" "} }\n"; const std::string made = fixed(script, false, "LSLSlLoopInvariantCall", "Work out llGetListLength once, before the loop"); - ensure_equals("the text", made, + UNSCOPED_INFO("the text"); + CHECK(made == std::string("default { state_entry() {\n" " list l = [1, 2]; integer length = 3; integer i;\n" " integer length2 = llGetListLength(l);\n" @@ -1153,7 +1407,11 @@ namespace tut "} }\n")); const ALScriptProblems problems = check(script, false); const ALScriptProblem* problem = keyed(problems, "LSLSlLoopInvariantCall"); - ensure("not safe", problem && !problem->fixes.front().safe); + { + INFO("not safe"); + REQUIRE(problem); + CHECK_FALSE(problem->fixes.front().safe); + } // Not where the loop is all its if has: a declaration may not be. const ALScriptProblems in_if = check("default { state_entry() {\n" " list l; integer i;\n" @@ -1161,58 +1419,76 @@ namespace tut "} }\n", false); const ALScriptProblem* alone = keyed(in_if, "LSLSlLoopInvariantCall"); - ensure("said, with no fix", alone && alone->fixes.empty()); + { + INFO("said, with no fix"); + REQUIRE(alone); + REQUIRE(alone->fixes.empty()); + } } - template<> template<> - void object::test<39>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlNumberTruth's fixes: n ~= 0 bracketed where needed and not n as n == 0", "[ALScriptFixes]") { - set_test_name("SlNumberTruth's fixes: n ~= 0, an if-then-else bracketed first, and not n as n == 0; not safe"); - ensure("definitions: " + error, luauLoaded); - ensure_equals("compared", fixed("local n = 0\nif n then print(n) end\n", true, "LuauLintSlNumberTruth", "Compare it: n ~= 0"), + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); + UNSCOPED_INFO("compared"); + CHECK(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")); - ensure_equals("bracketed", 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"), + UNSCOPED_INFO("bracketed"); + CHECK(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")); - ensure_equals("not", fixed("local n = 0\nprint(not n)\n", true, "LuauLintSlNumberTruthNot", "Ask whether it is 0: n == 0"), + UNSCOPED_INFO("not"); + CHECK(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"); - ensure("not safe", problem && !problem->fixes.front().safe); + { + INFO("not safe"); + REQUIRE(problem); + CHECK_FALSE(problem->fixes.front().safe); + } } - template<> template<> - void object::test<40>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlNilSentinel's fixes: nil for SLua's finds and -1 for llcompat's; safe", "[ALScriptFixes]") { - set_test_name("SlNilSentinel's fixes: nil where SLua's find answers it, -1 where llcompat's does; a find on the right put on the left; safe"); - ensure("definitions: " + error, luauLoaded); - ensure_equals("never", fixed("local l = {1}\nif ll.ListFindList(l, {2}) == -1 then print(l) end\n", true, "LuauLintSlNilSentinel", - "Write it ll.ListFindList(...) == nil"), + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); + UNSCOPED_INFO("never"); + CHECK(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")); - ensure_equals("on the right", fixed("local i = table.find({1}, 1)\nprint(-1 ~= i)\n", true, "LuauLintSlNilSentinelAlways", "Write it i ~= nil"), + UNSCOPED_INFO("on the right"); + CHECK(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")); - ensure_equals("ordered, bracketed", fixed("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint((i) < 0)\n", true, "LuauLintSlNilSentinelOrder", - "Write it i == nil"), + UNSCOPED_INFO("ordered, bracketed"); + CHECK(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")); - ensure_equals("ordered, on the right", fixed("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint(0 <= i)\n", true, - "LuauLintSlNilSentinelOrder", "Write it i ~= nil"), + UNSCOPED_INFO("ordered, on the right"); + CHECK(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")); - ensure_equals("llcompat's", fixed("print(llcompat.ListFindList({1}, {1}) ~= nil)\n", true, "LuauLintSlNilSentinelAlways", - "Write it llcompat.ListFindList(...) ~= -1"), + UNSCOPED_INFO("llcompat's"); + CHECK(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"); - ensure("safe", problem && !problem->fixes.empty() && problem->fixes.front().safe); + { + INFO("safe"); + REQUIRE(problem); + REQUIRE_FALSE(problem->fixes.empty()); + CHECK(problem->fixes.front().safe); + } } - template<> template<> - void object::test<41>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated ll function is renamed only where the arguments match", "[ALScriptFixes]") { - set_test_name("a deprecated ll function's replacement put in by name only where it takes the same arguments: not " - "ll.SetPrimitiveParams', which wants a link first"); - ensure("definitions: " + error, luauLoaded); - ensure_equals("the same arguments", fixed("ll.SetLinkPrimitiveParams(LINK_THIS, {})\n", true, "LuauLintDeprecatedMemberUse", - "Use 'll.SetLinkPrimitiveParamsFast' instead of 'll.SetLinkPrimitiveParams'"), + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); + UNSCOPED_INFO("the same arguments"); + CHECK(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" }) { @@ -1223,41 +1499,49 @@ namespace tut { for (const ALScriptFix& fix : problem.fixes) { - ensure("no replacement by name: " + std::string(call) + fix.title, fix.key != "ScriptFixUseInstead"); + UNSCOPED_INFO("no replacement by name: " + std::string(call) + fix.title); + REQUIRE(fix.key != "ScriptFixUseInstead"); } } } } } - template<> template<> - void object::test<42>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: making a script --!strict changes its head's mode or puts it first", "[ALScriptFixes]") { - set_test_name("a script made --!strict: its head's --!nonstrict or --!nocheck changed, else it put first; nothing where it is already"); const auto made = [](const std::string& text) { const std::optional fix = ALScriptFixes::strictFix(text); return fix ? ALScriptFixes::apply(text, *fix).value_or("(not made)") : std::string("(none)"); }; - ensure_equals("put first", made("-- a door\nlocal x = 1\n"), std::string("--!strict\n-- a door\nlocal x = 1\n")); - ensure_equals("changed", made("-- a door\n--!nonstrict\nlocal x = 1\n"), std::string("-- a door\n--!strict\nlocal x = 1\n")); - ensure_equals("from nocheck", made("--!nocheck\nprint(1)\n"), std::string("--!strict\nprint(1)\n")); - ensure_equals("already", made("--!strict\nprint(1)\n"), std::string("(none)")); - ensure_equals("past the head, not read", made("print(1)\n--!nonstrict\n"), std::string("--!strict\nprint(1)\n--!nonstrict\n")); + UNSCOPED_INFO("put first"); + CHECK(made("-- a door\nlocal x = 1\n") == std::string("--!strict\n-- a door\nlocal x = 1\n")); + UNSCOPED_INFO("changed"); + CHECK(made("-- a door\n--!nonstrict\nlocal x = 1\n") == std::string("-- a door\n--!strict\nlocal x = 1\n")); + UNSCOPED_INFO("from nocheck"); + CHECK(made("--!nocheck\nprint(1)\n") == std::string("--!strict\nprint(1)\n")); + UNSCOPED_INFO("already"); + CHECK(made("--!strict\nprint(1)\n") == std::string("(none)")); + UNSCOPED_INFO("past the head, not read"); + CHECK(made("print(1)\n--!nonstrict\n") == std::string("--!strict\nprint(1)\n--!nonstrict\n")); } - template<> template<> - void object::test<43>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an unread require is offered for removal neither safe nor preferred", "[ALScriptFixes]") { - set_test_name("a require nothing reads may be taken out, its line with it: offered after marking it unused, neither safe nor preferred, since a require runs its module"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); // The fixture's statics named by its class: MSVC finds none of them // from a lambda, even one holding `this`. const auto removal = [this](const std::string& script) -> const ALScriptFix* { static ALScriptProblems problems; problems = check(script, true); const ALScriptProblem* problem = alscriptfixes_data::keyed(problems, "LuauLintImportUnused"); - ensure("said: " + alscriptfixes_data::said(problems), problem != nullptr && !problem->fixes.empty()); - ensure_equals("marking it unused first", problem->fixes.front().title.substr(0, 9), std::string("Rename to")); + { + INFO("said: " + alscriptfixes_data::said(problems)); + REQUIRE(problem != nullptr); + REQUIRE_FALSE(problem->fixes.empty()); + } + UNSCOPED_INFO("marking it unused first"); + REQUIRE(problem->fixes.front().title.substr(0, 9) == std::string("Rename to")); for (const ALScriptFix& fix : problem->fixes) { if (fix.title.rfind("Remove the require", 0) == 0) @@ -1269,27 +1553,33 @@ namespace tut }; const std::string script = "local util = require(\"util\")\nprint(1)\n"; const ALScriptFix* fix = removal(script); - ensure("offered", fix != nullptr); - ensure_equals("its words", fix->title, std::string("Remove the require of \"util\"")); - ensure("neither preferred nor safe", !fix->preferred && !fix->safe); - ensure_equals("its line gone", ALScriptFixes::apply(script, *fix).value_or("(not made)"), std::string("print(1)\n")); + UNSCOPED_INFO("offered"); + REQUIRE(fix != nullptr); + UNSCOPED_INFO("its words"); + CHECK(fix->title == std::string("Remove the require of \"util\"")); + { + INFO("neither preferred nor safe"); + REQUIRE_FALSE(fix->preferred); + CHECK_FALSE(fix->safe); + } + UNSCOPED_INFO("its line gone"); + CHECK(ALScriptFixes::apply(script, *fix).value_or("(not made)") == std::string("print(1)\n")); const std::string bare = "local util = require 'util'; print(1)\n"; fix = removal(bare); - ensure("without brackets, and its ;", fix != nullptr); - ensure_equals("the statement alone gone", ALScriptFixes::apply(bare, *fix).value_or("(not made)"), std::string("print(1)\n")); + UNSCOPED_INFO("without brackets, and its ;"); + REQUIRE(fix != nullptr); + UNSCOPED_INFO("the statement alone gone"); + CHECK(ALScriptFixes::apply(bare, *fix).value_or("(not made)") == std::string("print(1)\n")); - ensure("a module named by an expression: nothing to name", removal("local name = 'util'\nlocal util = require(name)\n") == nullptr); + UNSCOPED_INFO("a module named by an expression: nothing to name"); + REQUIRE(removal("local name = 'util'\nlocal util = require(name)\n") == nullptr); } - template<> template<> - void object::test<44>() + TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated call with other arguments is rewritten as a call that works", "[ALScriptFixes]") { - set_test_name("a deprecated call whose replacement takes other arguments is written as a call that works -- ll.VecDist's " - "vector.magnitude(a - b), ll.Round's half up, ll.SetPrimitiveParams' link, table.getn's # -- an argument " - "bracketed where it does not bind as one; none where the event's table is wanted, the call does not close " - "on its line or is not made; vector.normalize not preferred; LSL's llParticleSystem not put for llMakeFire"); - ensure("definitions: " + error, luauLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(luauLoaded); // The fixture's statics named by its class: MSVC finds none of them // from a lambda, even one holding `this`. const auto deprecated = [this](const std::string& script) -> const ALScriptProblem* { @@ -1302,52 +1592,75 @@ namespace tut return &problem; } } - ensure("said: " + alscriptfixes_data::said(problems), false); + UNSCOPED_INFO("said: " + alscriptfixes_data::said(problems)); + REQUIRE(false); return nullptr; }; const auto written = [&](const std::string& script) { const ALScriptProblem* problem = deprecated(script); - ensure("a fix: " + script, !problem->fixes.empty()); + UNSCOPED_INFO("a fix: " + script); + REQUIRE_FALSE(problem->fixes.empty()); const ALScriptFix& fix = problem->fixes.front(); - ensure("preferred, not safe: " + fix.title, fix.preferred && !fix.safe); + { + INFO("preferred, not safe: " + fix.title); + REQUIRE(fix.preferred); + REQUIRE_FALSE(fix.safe); + } const std::string made = ALScriptFixes::apply(script, fix).value_or("(not made)"); const ALScriptProblems after = check(made, true); for (const ALScriptProblem& each : after) { - ensure("no longer deprecated:\n" + made, each.key.rfind("LuauLintDeprecated", 0) != 0); + UNSCOPED_INFO("no longer deprecated:\n" + made); + REQUIRE(each.key.rfind("LuauLintDeprecated", 0) != 0); } - ensure_equals("no errors:\n" + made + "\n" + alscriptfixes_data::said(after), alscriptfixes_data::errors(after), static_cast(0)); + UNSCOPED_INFO("no errors:\n" + made + "\n" + alscriptfixes_data::said(after)); + REQUIRE(alscriptfixes_data::errors(after) == static_cast(0)); return fix.title + "\n" + made; }; const std::string vectors = "local a, b = vector(1, 0, 0), vector(0, 1, 0)\n"; - ensure_equals("a distance", written(vectors + "print(ll.VecDist(a, ll.GetPos()))\n"), + UNSCOPED_INFO("a distance"); + CHECK(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"); - ensure_equals("bracketed", written(vectors + "print(ll.VecDist(a + b, b))\n"), + UNSCOPED_INFO("bracketed"); + CHECK(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"); - ensure_equals("half up", written("local n = 2\nprint(ll.Round(n * 0.25))\n"), + UNSCOPED_INFO("half up"); + CHECK(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")); - ensure_equals("a link first", written("ll.SetPrimitiveParams({PRIM_SIZE, vector(1, 1, 1)})\n"), + UNSCOPED_INFO("a link first"); + CHECK(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")); - ensure_equals("a length", written("local t = {1, 2}\nprint(table.getn(t))\n"), + UNSCOPED_INFO("a length"); + CHECK(written("local t = {1, 2}\nprint(table.getn(t))\n") == std::string("Write it #t\nlocal t = {1, 2}\nprint(#t)\n")); - ensure_equals("a name", written("print(ll.Abs(-1))\n"), std::string("Use 'math.abs' instead of 'll.Abs'\nprint(math.abs(-1))\n")); + UNSCOPED_INFO("a name"); + CHECK(written("print(ll.Abs(-1))\n") == std::string("Use 'math.abs' instead of 'll.Abs'\nprint(math.abs(-1))\n")); const ALScriptProblem* norm = deprecated("print(ll.VecNorm(vector(0, 0, 0)))\n"); - ensure("normalize offered: " + alscriptfixes_data::said({ *norm }), !norm->fixes.empty() && norm->fixes.front().title == "Use 'vector.normalize' instead of 'll.VecNorm'"); - ensure("not preferred, NaN at zero", !norm->fixes.front().preferred); + { + INFO(("normalize offered: " + alscriptfixes_data::said({ *norm }))); + REQUIRE_FALSE(norm->fixes.empty()); + CHECK(norm->fixes.front().title == "Use 'vector.normalize' instead of 'll.VecNorm'"); + } + UNSCOPED_INFO("not preferred, NaN at zero"); + CHECK_FALSE(norm->fixes.front().preferred); for (const char* script : { "LLEvents:on(\"touch_start\", function(detected) print(ll.DetectedKey(0)) end)\n", "local a = vector(1, 0, 0)\nprint(ll.VecDist(a,\n a))\n", "local f = ll.VecDist\nprint(f)\n", "print(ll.GetListEntryType({1}, 0))\n" }) { const ALScriptProblem* problem = deprecated(script); - ensure("no fix: " + std::string(script) + alscriptfixes_data::said({ *problem }), problem->fixes.empty()); + UNSCOPED_INFO(("no fix: " + std::string(script) + alscriptfixes_data::said({ *problem }))); + REQUIRE(problem->fixes.empty()); } - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + 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"); - ensure("said: " + alscriptfixes_data::said(lsl), fire != nullptr); - ensure("no llParticleSystem by name: " + alscriptfixes_data::said(lsl), fire->fixes.empty()); + UNSCOPED_INFO("said: " + alscriptfixes_data::said(lsl)); + REQUIRE(fire != nullptr); + UNSCOPED_INFO("no llParticleSystem by name: " + alscriptfixes_data::said(lsl)); + REQUIRE(fire->fixes.empty()); } } diff --git a/indra/alscript/tests/alscriptformatter_test.cpp b/indra/alscript/tests/alscriptformatter_test.cpp index f74d8ef06e..a8527f361a 100644 --- a/indra/alscript/tests/alscriptformatter_test.cpp +++ b/indra/alscript/tests/alscriptformatter_test.cpp @@ -26,9 +26,9 @@ #include "../core/alscriptformatter.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alscriptformatter_data { @@ -44,13 +44,9 @@ namespace tut return ALScriptFormatter::format(text, options); } }; - typedef test_group alscriptformatter_group; - typedef alscriptformatter_group::object alscriptformatter_object; - tut::alscriptformatter_group alscriptformatter_instance("alscriptformatter"); // LSL: braces, a hanging statement, a list across lines, a directive. - template<> template<> - void alscriptformatter_object::test<1>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: LSL: braces; a hanging statement; a list across lines; a directive", "[alscriptformatter]") { const std::string in = "#define X 1\n" @@ -86,31 +82,42 @@ namespace tut " }\n" " }\n" "}\n"; - ensure_equals("lsl structure", lsl(in), want); + UNSCOPED_INFO("lsl structure"); + CHECK(lsl(in) == want); } // LSL spacing: operators, unary signs, vectors, casts, comments. - template<> template<> - void alscriptformatter_object::test<2>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: LSL spacing: operators; unary signs; vectors; casts; comments", "[alscriptformatter]") { - ensure_equals("binary", lsl("x=a+b*c;\n"), std::string("x = a + b * c;\n")); - ensure_equals("unary", lsl("x = -a * -b;\n"), std::string("x = -a * -b;\n")); - ensure_equals("not", lsl("if(!x)y=~z;\n"), std::string("if (!x) y = ~z;\n")); - ensure_equals("vector", lsl("v=<1,-2,3>*<0,0,1>;\n"), std::string("v = <1, -2, 3> * <0, 0, 1>;\n")); - ensure_equals("comparison", lsl("if(ad)x++;\n"), std::string("if (a < b && c > d) x++;\n")); - ensure_equals("cast kept", lsl("x=(integer)y;\n"), std::string("x = (integer)y;\n")); - ensure_equals("cast spaced kept", lsl("x=(integer) y;\n"), std::string("x = (integer) y;\n")); - ensure_equals("for", lsl("for(i=0;i*<0,0,1>;\n") == std::string("v = <1, -2, 3> * <0, 0, 1>;\n")); + UNSCOPED_INFO("comparison"); + CHECK(lsl("if(ad)x++;\n") == std::string("if (a < b && c > d) x++;\n")); + UNSCOPED_INFO("cast kept"); + CHECK(lsl("x=(integer)y;\n") == std::string("x = (integer)y;\n")); + UNSCOPED_INFO("cast spaced kept"); + CHECK(lsl("x=(integer) y;\n") == std::string("x = (integer) y;\n")); + UNSCOPED_INFO("for"); + CHECK(lsl("for(i=0;i template<> - void alscriptformatter_object::test<3>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: Luau: blocks by keyword; a function in a call; a table; its own spacing", "[alscriptformatter]") { const std::string in = "local function f(a,b)\n" @@ -150,62 +157,75 @@ namespace tut " a = 1,\n" " b = { 1, 2 },\n" "}\n"; - ensure_equals("luau structure", lua(in), want); - ensure_equals("generics kept", lua("local x: Array = {}\n"), std::string("local x: Array = {}\n")); - ensure_equals("method kept", lua("obj:method(1)\n"), std::string("obj:method(1)\n")); - ensure_equals("not", lua("if not(x)then end\n"), std::string("if not (x) then end\n")); - ensure_equals("long string kept", lua("s = [[\n a\n]]\nx=1\n"), std::string("s = [[\n a\n]]\nx = 1\n")); + UNSCOPED_INFO("luau structure"); + CHECK(lua(in) == want); + UNSCOPED_INFO("generics kept"); + CHECK(lua("local x: Array = {}\n") == std::string("local x: Array = {}\n")); + UNSCOPED_INFO("method kept"); + CHECK(lua("obj:method(1)\n") == std::string("obj:method(1)\n")); + UNSCOPED_INFO("not"); + CHECK(lua("if not(x)then end\n") == std::string("if not (x) then end\n")); + UNSCOPED_INFO("long string kept"); + CHECK(lua("s = [[\n a\n]]\nx=1\n") == std::string("s = [[\n a\n]]\nx = 1\n")); } // Blank lines, trailing whitespace, and the end of the file. - template<> template<> - void alscriptformatter_object::test<4>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: blank lines; trailing whitespace; the end of the file", "[alscriptformatter]") { - ensure_equals("blanks shortened", lsl("\n\nx = 1; \n\n\n\n\ny = 2;\n\n\n"), std::string("x = 1;\n\n\ny = 2;\n")); - ensure_equals("a newline added", lsl("x = 1;"), std::string("x = 1;\n")); - ensure_equals("tabs", [] { + UNSCOPED_INFO("blanks shortened"); + CHECK(lsl("\n\nx = 1; \n\n\n\n\ny = 2;\n\n\n") == std::string("x = 1;\n\n\ny = 2;\n")); + UNSCOPED_INFO("a newline added"); + CHECK(lsl("x = 1;") == std::string("x = 1;\n")); + UNSCOPED_INFO("tabs"); + CHECK([] { ALScriptFormatter::Options options; options.tabs = true; return ALScriptFormatter::format("{\nx;\n}\n", options); - }(), std::string("{\n\tx;\n}\n")); + }() == std::string("{\n\tx;\n}\n")); } // Only some lines, the rest as written and every line kept. - template<> template<> - void alscriptformatter_object::test<5>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: only some lines formatted with the rest as written and every line kept", "[alscriptformatter]") { const std::string in = "default\n{\nstate_entry()\n{\n\n\nx=1;\n}\n}\n"; ALScriptFormatter::Options options; - ensure_equals("one line", ALScriptFormatter::formatLines(in, options, 6, 6), std::string("default\n{\nstate_entry()\n{\n\n\n x = 1;\n}\n}\n")); - ensure_equals("blanks kept", ALScriptFormatter::formatLines(in, options, 4, 7), std::string("default\n{\nstate_entry()\n{\n\n\n x = 1;\n }\n}\n")); + UNSCOPED_INFO("one line"); + CHECK(ALScriptFormatter::formatLines(in, options, 6, 6) == std::string("default\n{\nstate_entry()\n{\n\n\n x = 1;\n}\n}\n")); + UNSCOPED_INFO("blanks kept"); + CHECK(ALScriptFormatter::formatLines(in, options, 4, 7) == std::string("default\n{\nstate_entry()\n{\n\n\n x = 1;\n }\n}\n")); // A string across lines is one line to the formatter, left alone // unless the whole of it is asked for. const std::string across = "x = \"a\nb\";\ny=2;\n"; - ensure_equals("across, partly", ALScriptFormatter::formatLines(across, options, 1, 2), std::string("x = \"a\nb\";\ny = 2;\n")); - ensure_equals("across, whole", ALScriptFormatter::formatLines(across, options, 0, 1), std::string("x = \"a\nb\";\ny=2;\n")); + UNSCOPED_INFO("across, partly"); + CHECK(ALScriptFormatter::formatLines(across, options, 1, 2) == std::string("x = \"a\nb\";\ny = 2;\n")); + UNSCOPED_INFO("across, whole"); + CHECK(ALScriptFormatter::formatLines(across, options, 0, 1) == std::string("x = \"a\nb\";\ny=2;\n")); } // Luau's own: an if-expression opens no block, `const` and `export` // read as the keywords they are, and a function's body starts afresh. - template<> template<> - void alscriptformatter_object::test<6>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: Luau: an if-expression opens no block and `const` and `export` are keywords", "[alscriptformatter]") { - ensure_equals("an if-expression", lua("local x = if a then 1 else 2\ny = 3\n"), std::string("local x = if a then 1 else 2\ny = 3\n")); - ensure_equals("chained", lua("local x = if a then 1 elseif b then 2 else 3\ny = 3\n"), std::string("local x = if a then 1 elseif b then 2 else 3\ny = 3\n")); - ensure_equals("else if, as one expression", lua("return if a then 1 else if b then 2 else 3\n"), std::string("return if a then 1 else if b then 2 else 3\n")); - ensure_equals("across lines", lua("local x =\nif a then 1\nelse 2\ny = 3\n"), std::string("local x =\nif a then 1\nelse 2\ny = 3\n")); - ensure_equals("a statement if still opens its block", lua("if a then\nx = 1\nelse\nx = 2\nend\ny = 3\n"), + UNSCOPED_INFO("an if-expression"); + CHECK(lua("local x = if a then 1 else 2\ny = 3\n") == std::string("local x = if a then 1 else 2\ny = 3\n")); + UNSCOPED_INFO("chained"); + CHECK(lua("local x = if a then 1 elseif b then 2 else 3\ny = 3\n") == std::string("local x = if a then 1 elseif b then 2 else 3\ny = 3\n")); + UNSCOPED_INFO("else if, as one expression"); + CHECK(lua("return if a then 1 else if b then 2 else 3\n") == std::string("return if a then 1 else if b then 2 else 3\n")); + UNSCOPED_INFO("across lines"); + CHECK(lua("local x =\nif a then 1\nelse 2\ny = 3\n") == std::string("local x =\nif a then 1\nelse 2\ny = 3\n")); + UNSCOPED_INFO("a statement if still opens its block"); + CHECK(lua("if a then\nx = 1\nelse\nx = 2\nend\ny = 3\n") == std::string("if a then\n x = 1\nelse\n x = 2\nend\ny = 3\n")); - ensure_equals("a statement if inside a function inside an expression", - lua("local f = if a then function()\nif b then\nreturn 1\nend\nend else nil\ny = 3\n"), + UNSCOPED_INFO("a statement if inside a function inside an expression"); + CHECK(lua("local f = if a then function()\nif b then\nreturn 1\nend\nend else nil\ny = 3\n") == std::string("local f = if a then function()\n if b then\n return 1\n end\nend else nil\ny = 3\n")); - ensure_equals("const and export", lua("export const n=1\nconst function f(a,b)\nreturn a+b\nend\n"), + UNSCOPED_INFO("const and export"); + CHECK(lua("export const n=1\nconst function f(a,b)\nreturn a+b\nend\n") == std::string("export const n = 1\nconst function f(a, b)\n return a + b\nend\n")); } - template<> template<> - void alscriptformatter_object::test<7>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: a continued directive's braces open and close nothing of the script's", "[alscriptformatter]") { - set_test_name("a directive continued over several lines is the directive's: its braces open and close nothing of the script's"); const std::string text = "#define BLOCK(x) { \\\n" " llSay(0, x); \\\n" @@ -218,8 +238,8 @@ namespace tut "}\n" "}\n"; const std::string out = lsl(text); - ensure_equals("the macro as written, the script as it should be", - out, + UNSCOPED_INFO("the macro as written, the script as it should be"); + CHECK(out == std::string("#define BLOCK(x) { \\\n" " llSay(0, x); \\\n" "}\n" @@ -232,14 +252,12 @@ namespace tut "}\n")); // SLua's are written --#, and are as written too: a function-like // macro's ( stays against its name, and a # is no operator's. - ensure_equals("SLua's directives as written, the script as it should be", - lua("--#define SQUARE(x) ((x)*(x))\n--#define LEN(t) #t\nlocal function f(a)\nreturn SQUARE(a)+LEN(a)\nend\n"), + UNSCOPED_INFO("SLua's directives as written, the script as it should be"); + CHECK(lua("--#define SQUARE(x) ((x)*(x))\n--#define LEN(t) #t\nlocal function f(a)\nreturn SQUARE(a)+LEN(a)\nend\n") == std::string("--#define SQUARE(x) ((x)*(x))\n--#define LEN(t) #t\nlocal function f(a)\n return SQUARE(a) + LEN(a)\nend\n")); } - template<> template<> - void alscriptformatter_object::test<8>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: breaksInStrings marks line breaks inside strings and not inside comments", "[alscriptformatter]") { - set_test_name("the line breaks that stand inside a string are known, so that tidying lines leaves them and the blanks before them alone"); const auto marked = [](const std::vector& breaks) { std::string out; for (const bool in : breaks) @@ -250,41 +268,42 @@ namespace tut }; // A Luau long string over five lines, with blank lines in it; a long // comment is not a string. - ensure_equals("a long string", marked(ALScriptFormatter::breaksInStrings("local s = [[\n a \n\n\n\nb]]\nprint(s)\n", true)), std::string("11111000")); - ensure_equals("a long comment", marked(ALScriptFormatter::breaksInStrings("--[[\n\n]]\nprint(1)\n", true)), std::string("00000")); - ensure_equals("a string continued by a backslash", marked(ALScriptFormatter::breaksInStrings("print(\"a \\\nb\")\n", true)), std::string("100")); + UNSCOPED_INFO("a long string"); + CHECK(marked(ALScriptFormatter::breaksInStrings("local s = [[\n a \n\n\n\nb]]\nprint(s)\n", true)) == std::string("11111000")); + UNSCOPED_INFO("a long comment"); + CHECK(marked(ALScriptFormatter::breaksInStrings("--[[\n\n]]\nprint(1)\n", true)) == std::string("00000")); + UNSCOPED_INFO("a string continued by a backslash"); + CHECK(marked(ALScriptFormatter::breaksInStrings("print(\"a \\\nb\")\n", true)) == std::string("100")); // LSL: a break written into a string, an escaped quote that does not // end it, and quotes in comments that begin nothing. - ensure_equals("an LSL string", marked(ALScriptFormatter::breaksInStrings("string s = \"a \n\n\nb\";\n// \"not\n/* \" */ integer i;\n", false)), + UNSCOPED_INFO("an LSL string"); + CHECK(marked(ALScriptFormatter::breaksInStrings("string s = \"a \n\n\nb\";\n// \"not\n/* \" */ integer i;\n", false)) == std::string("1110000")); - ensure_equals("an escaped quote", marked(ALScriptFormatter::breaksInStrings("string s = \"a\\\"\nb\";\n", false)), std::string("100")); + UNSCOPED_INFO("an escaped quote"); + CHECK(marked(ALScriptFormatter::breaksInStrings("string s = \"a\\\"\nb\";\n", false)) == std::string("100")); } - template<> template<> - void alscriptformatter_object::test<9>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: a comment-only line is indented as a statement there would be", "[alscriptformatter]") { - set_test_name("a line holding nothing but a comment keeps it, indented as a statement there would be"); - ensure_equals("LSL", - lsl("// first\ndefault\n{\n// a note\nstate_entry()\n{\n/* over\n lines */\nif (x)\n// why\nllSay(0, \"a\");\n}\n}\n"), + UNSCOPED_INFO("LSL"); + CHECK(lsl("// first\ndefault\n{\n// a note\nstate_entry()\n{\n/* over\n lines */\nif (x)\n// why\nllSay(0, \"a\");\n}\n}\n") == std::string("// first\ndefault\n{\n // a note\n state_entry()\n {\n /* over\n lines */\n if (x)\n" " // why\n llSay(0, \"a\");\n }\n}\n")); - ensure_equals("Luau", - lua("-- first\nlocal function f()\n-- a note\nreturn 1\nend\n--[[ over\n lines ]]\n"), + UNSCOPED_INFO("Luau"); + CHECK(lua("-- first\nlocal function f()\n-- a note\nreturn 1\nend\n--[[ over\n lines ]]\n") == std::string("-- first\nlocal function f()\n -- a note\n return 1\nend\n--[[ over\n lines ]]\n")); // Some lines asked for, as the studio asks for all of a script's: // every line kept, the comments too. ALScriptFormatter::Options options; - ensure_equals("LSL, by lines", ALScriptFormatter::formatLines("{\n// note\n/* a\nb */\n}\n", options, 0, 5), + UNSCOPED_INFO("LSL, by lines"); + CHECK(ALScriptFormatter::formatLines("{\n// note\n/* a\nb */\n}\n", options, 0, 5) == std::string("{\n // note\n /* a\nb */\n}\n")); options.lua = true; - ensure_equals("Luau, by lines", ALScriptFormatter::formatLines("do\n-- note\nend\n", options, 0, 3), std::string("do\n -- note\nend\n")); + UNSCOPED_INFO("Luau, by lines"); + CHECK(ALScriptFormatter::formatLines("do\n-- note\nend\n", options, 0, 3) == std::string("do\n -- note\nend\n")); } - template<> template<> - void alscriptformatter_object::test<10>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: a line past the width breaks at the commas of its widest bracket", "[alscriptformatter]") { - set_test_name("a line past the width broken at the commas of its widest bracket, or of the table a call ends with on the call's " - "line, each part a level further in and broken again where still too long, its comment after the last; formatted " - "again, the same; not where no width is set, nor by formatLines; formatEach gives it as one line's"); ALScriptFormatter::Options options; options.lua = true; options.width = 100; @@ -318,31 +337,37 @@ namespace tut " print(\"a string with no comma in it that is longer than the width of one hundred columns, and so on\")\n" "end\n"; const std::string out = ALScriptFormatter::format(in, options); - ensure_equals("broken", out, want); - ensure_equals("the same again", ALScriptFormatter::format(out, options), want); + UNSCOPED_INFO("broken"); + CHECK(out == want); + UNSCOPED_INFO("the same again"); + CHECK(ALScriptFormatter::format(out, options) == want); ALScriptFormatter::Options none = options; none.width = 0; - ensure("no width, no break", ALScriptFormatter::format(in, none).find("{\n") == std::string::npos); + UNSCOPED_INFO("no width, no break"); + REQUIRE(ALScriptFormatter::format(in, none).find("{\n") == std::string::npos); const std::string lines = ALScriptFormatter::formatLines(in, options, 0, 5); - ensure_equals("formatLines keeps every line's number", std::count(lines.begin(), lines.end(), '\n'), std::count(in.begin(), in.end(), '\n')); + UNSCOPED_INFO("formatLines keeps every line's number"); + CHECK(std::count(lines.begin(), lines.end(), '\n') == std::count(in.begin(), in.end(), '\n')); const std::vector each = ALScriptFormatter::formatEach(in, options, 0, 5); - ensure_equals("one a line, and the empty one after the last break", each.size(), size_t(6)); - ensure_equals("the broken line's, breaks and all", each[1] + "\n", want.substr(want.find(" local r"), want.find(" print(string") - want.find(" local r"))); - ensure_equals("a line asked for", ALScriptFormatter::formatEach(in, options, 2, 2)[1], in.substr(in.find(" local r"), in.find(" -- sent") + 8 - in.find(" local r"))); + UNSCOPED_INFO("one a line, and the empty one after the last break"); + 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"))); // 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); - ensure_equals("a line each, blank or not", blanks.size(), size_t(7)); - ensure_equals("the code where it was", blanks[5], std::string("print(1)")); + UNSCOPED_INFO("a line each, blank or not"); + REQUIRE(blanks.size() == size_t(7)); + UNSCOPED_INFO("the code where it was"); + CHECK(blanks[5] == std::string("print(1)")); } - template<> template<> - void alscriptformatter_object::test<11>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: LSL past the width: vectors stay whole and tabs count a level wide", "[alscriptformatter]") { - set_test_name("LSL past the width: a vector or rotation never broken, nor spaced inside where a line begins with one; a call's list " - "on the call's line; tabs a level wide; not a line with a comment inside it"); ALScriptFormatter::Options options; options.width = 100; const std::string in = "default\n{\n state_entry()\n {\n" @@ -368,26 +393,28 @@ namespace tut "<1, 1, 1>, 1.0);\n" " }\n}\n"; const std::string out = ALScriptFormatter::format(in, options); - ensure_equals("broken", out, want); - ensure_equals("the same again", ALScriptFormatter::format(out, options), want); + UNSCOPED_INFO("broken"); + CHECK(out == want); + UNSCOPED_INFO("the same again"); + CHECK(ALScriptFormatter::format(out, options) == want); // With tabs, each a level's width: 2 levels of 4 and the call's 92 // are 100, which fits; at 99 it does not. ALScriptFormatter::Options tabs = options; tabs.tabs = true; const std::string call = "llSay(0, \"" + std::string(92 - 13, 'x') + "\");"; - ensure_equals("a call of 92", call.size(), size_t(92)); + UNSCOPED_INFO("a call of 92"); + REQUIRE(call.size() == size_t(92)); const std::string tabbed = "default\n{\n state_entry()\n {\n " + call + "\n }\n}\n"; - ensure("fits", ALScriptFormatter::format(tabbed, tabs).find("\t\t" + call + "\n") != std::string::npos); + UNSCOPED_INFO("fits"); + REQUIRE(ALScriptFormatter::format(tabbed, tabs).find("\t\t" + call + "\n") != std::string::npos); tabs.width = 99; - ensure("does not", ALScriptFormatter::format(tabbed, tabs).find("\t\tllSay(\n\t\t\t0,\n") != std::string::npos); + UNSCOPED_INFO("does not"); + REQUIRE(ALScriptFormatter::format(tabbed, tabs).find("\t\tllSay(\n\t\t\t0,\n") != std::string::npos); } - template<> template<> - void alscriptformatter_object::test<12>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: braceSpaces spaces Luau's braces inside but not {} or interpolation or LSL's", "[alscriptformatter]") { - set_test_name("Luau's braces spaced inside as asked, whatever stands next to them, a table type's too; never {} nor an " - "interpolated string's; a type's colon before a bracket spaced as written; LSL's braces as they were"); const std::string in = "local states: {[string]: {[string]:(...any) -> ()}} = {}\n" "local t = {{1}, {2, 3}, [k] = v, -1, #t}\n" "local params: { any } = ll.ParseStringKeepNulls(msg, { \"\\n\" }, {})\n" @@ -398,68 +425,74 @@ namespace tut "local params: { any } = ll.ParseStringKeepNulls(msg, { \"\\n\" }, {})\n" "print(`a {b} c {{d}}`, `{ {1} }`)\n" "local f: (number) -> () = obj:method(1)\n"; - ensure_equals("StyLua's, by default", lua(in), want); + UNSCOPED_INFO("StyLua's, by default"); + CHECK(lua(in) == want); ALScriptFormatter::Options none; none.lua = true; none.braceSpaces = false; - ensure_equals("none", ALScriptFormatter::format(in, none), + UNSCOPED_INFO("none"); + CHECK(ALScriptFormatter::format(in, none) == std::string("local states: {[string]: {[string]:(...any) -> ()}} = {}\n" "local t = {{1}, {2, 3}, [k] = v, -1, #t}\n" "local params: {any} = ll.ParseStringKeepNulls(msg, {\"\\n\"}, {})\n" "print(`a {b} c {{d}}`, `{ {1} }`)\n" "local f: (number) -> () = obj:method(1)\n")); - ensure_equals("a type's colon, spaced", lua("local states: {[string]: (...any) -> ()} = {}\n"), + UNSCOPED_INFO("a type's colon, spaced"); + CHECK(lua("local states: {[string]: (...any) -> ()} = {}\n") == std::string("local states: { [string]: (...any) -> () } = {}\n")); ALScriptFormatter::Options lsl_none; lsl_none.braceSpaces = false; - ensure_equals("LSL's braces", ALScriptFormatter::format("default { state_entry() { llSay(0, \"a\"); } }\n", lsl_none), + UNSCOPED_INFO("LSL's braces"); + CHECK(ALScriptFormatter::format("default { state_entry() { llSay(0, \"a\"); } }\n", lsl_none) == std::string("default { state_entry() { llSay(0, \"a\"); } }\n")); } - template<> template<> - void alscriptformatter_object::test<13>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: a sign hugs the bracket after it and is spaced from a < or minus before it", "[alscriptformatter]") { - set_test_name("a sign against the bracket or brace after it, and after a < that compares or a minus that subtracts, spaced " - "from it: -(a), #{ 1 }, i < -1, x - #t, in Luau and in LSL; LSL's vector's first sign still against its <"); - ensure_equals("Luau", lua("local x = - (a)\nlocal y = # (t)\nlocal z = # {1}\nif i <#t and i <-1 and i > - (2) then end\n" - "local w = not (a)\nlocal q = a - (b)\nreturn -(x)\nlocal d = #{1} -#{2} - -#t + -#t\n"), + UNSCOPED_INFO("Luau"); + CHECK(lua("local x = - (a)\nlocal y = # (t)\nlocal z = # {1}\nif i <#t and i <-1 and i > - (2) then end\n" + "local w = not (a)\nlocal q = a - (b)\nreturn -(x)\nlocal d = #{1} -#{2} - -#t + -#t\n") == std::string("local x = -(a)\nlocal y = #(t)\nlocal z = #{ 1 }\nif i < #t and i < -1 and i > -(2) then end\n" "local w = not (a)\nlocal q = a - (b)\nreturn -(x)\nlocal d = #{ 1 } - #{ 2 } - -#t + -#t\n")); - ensure_equals("LSL", lsl("default { state_entry() { integer x = - (a); vector v = < -1, 0, 0 >; if (i <-1) x = ! (b); } }\n"), + UNSCOPED_INFO("LSL"); + CHECK(lsl("default { state_entry() { integer x = - (a); vector v = < -1, 0, 0 >; if (i <-1) x = ! (b); } }\n") == std::string("default { state_entry() { integer x = -(a); vector v = <-1, 0, 0>; if (i < -1) x = !(b); } }\n")); - ensure_equals("a list of vectors", lsl("list l = [<-1, 0, 0>, < -2, 0, 0 >];\n"), std::string("list l = [<-1, 0, 0>, <-2, 0, 0>];\n")); + UNSCOPED_INFO("a list of vectors"); + CHECK(lsl("list l = [<-1, 0, 0>, < -2, 0, 0 >];\n") == std::string("list l = [<-1, 0, 0>, <-2, 0, 0>];\n")); } - template<> template<> - void alscriptformatter_object::test<14>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: what the author lined up stays lined up: runs of = and trailing comments", "[alscriptformatter]") { - set_test_name("what the author lined up stays lined up: a run's =, at the author's column or past the longest, a comment on a line " - "of its own going on past; a run's trailing comments where its code changed; not equal columns by chance; tabs to " - "their stops"); const std::string lined = "local CHANNEL = 1 -- channel\n" "local OWNER_ONLY = true -- owner\n" "-- a note over the next\n" "local USE_OWNER_SAY = true -- say\n"; - ensure_equals("as it was", lua(lined), lined); - ensure_equals("spaced afresh, lined up again", lua("local a =1 -- one\nlocal bbb = {2,3} -- two\n"), + UNSCOPED_INFO("as it was"); + CHECK(lua(lined) == lined); + UNSCOPED_INFO("spaced afresh, lined up again"); + CHECK(lua("local a =1 -- one\nlocal bbb = {2,3} -- two\n") == std::string("local a = 1 -- one\nlocal bbb = { 2, 3 } -- two\n")); - ensure_equals("the author's column", lua("local a = 1\nlocal bb = 2\n"), std::string("local a = 1\nlocal bb = 2\n")); - ensure_equals("past the longest", lua("t[i+1] = 1\nt[ii] = 2\n"), std::string("t[i + 1] = 1\nt[ii] = 2\n")); - ensure_equals("not by chance", lua("ab=1\nc = 2\n"), std::string("ab = 1\nc = 2\n")); - ensure_equals("not across a blank line", lua("local a = 1\n\nlocal bb = 2\n"), std::string("local a = 1\n\nlocal bb = 2\n")); + UNSCOPED_INFO("the author's column"); + CHECK(lua("local a = 1\nlocal bb = 2\n") == std::string("local a = 1\nlocal bb = 2\n")); + UNSCOPED_INFO("past the longest"); + CHECK(lua("t[i+1] = 1\nt[ii] = 2\n") == std::string("t[i + 1] = 1\nt[ii] = 2\n")); + UNSCOPED_INFO("not by chance"); + CHECK(lua("ab=1\nc = 2\n") == std::string("ab = 1\nc = 2\n")); + UNSCOPED_INFO("not across a blank line"); + CHECK(lua("local a = 1\n\nlocal bb = 2\n") == std::string("local a = 1\n\nlocal bb = 2\n")); const std::string fields = "local t = {\n alpha = 1,\n b = 2,\n}\n"; - ensure_equals("a table's fields", lua(fields), fields); - ensure_equals("LSL, by tabs", lsl("integer a\t= 1;\ninteger bb\t= 2;\n"), std::string("integer a = 1;\ninteger bb = 2;\n")); + UNSCOPED_INFO("a table's fields"); + CHECK(lua(fields) == fields); + UNSCOPED_INFO("LSL, by tabs"); + CHECK(lsl("integer a\t= 1;\ninteger bb\t= 2;\n") == std::string("integer a = 1;\ninteger bb = 2;\n")); // The formatter's own, again: the same. - ensure_equals("again", lua(lua("local a =1 -- one\nlocal bbb = {2,3} -- two\n")), + UNSCOPED_INFO("again"); + CHECK(lua(lua("local a =1 -- one\nlocal bbb = {2,3} -- two\n")) == std::string("local a = 1 -- one\nlocal bbb = { 2, 3 } -- two\n")); } - template<> template<> - void alscriptformatter_object::test<15>() + TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: a comment before elseif or else or until or end keeps the author's depth", "[alscriptformatter]") { - set_test_name("a comment the author wrote out of the block over an elseif, else, until or end stays as far in as that line; one " - "written in the block stays in it; in an empty block, as written where the text is indented; flat text, in"); const std::string chain = "if a then\n" " x()\n" "-- about b\n" @@ -469,15 +502,19 @@ namespace tut "else\n" " z()\n" "end\n"; - ensure_equals("as written", lua(chain), chain); + UNSCOPED_INFO("as written"); + CHECK(lua(chain) == chain); const std::string repeat = "local function f()\n" " repeat\n" " -- why\n" " until x\n" "end\n"; - ensure_equals("an empty block, indented", lua(repeat), repeat); - ensure_equals("flat", lua("if a then\nx()\n-- note\nend\n"), std::string("if a then\n x()\n -- note\nend\n")); - ensure_equals("LSL", lsl("default\n{\n touch_start(integer n)\n {\n x();\n // about the end\n }\n}\n"), + UNSCOPED_INFO("an empty block, indented"); + CHECK(lua(repeat) == repeat); + UNSCOPED_INFO("flat"); + CHECK(lua("if a then\nx()\n-- note\nend\n") == std::string("if a then\n x()\n -- note\nend\n")); + UNSCOPED_INFO("LSL"); + CHECK(lsl("default\n{\n touch_start(integer n)\n {\n x();\n // about the end\n }\n}\n") == std::string("default\n{\n touch_start(integer n)\n {\n x();\n // about the end\n }\n}\n")); } } diff --git a/indra/alscript/tests/alscriptjoblane_test.cpp b/indra/alscript/tests/alscriptjoblane_test.cpp index cc19f76b35..0a2f3162c9 100644 --- a/indra/alscript/tests/alscriptjoblane_test.cpp +++ b/indra/alscript/tests/alscriptjoblane_test.cpp @@ -26,7 +26,7 @@ #include "../preprocessor/alscriptjoblane.h" -#include "../test/lltut.h" +#include #include #include @@ -43,7 +43,7 @@ namespace typedef ALScriptJobLane Lane; } -namespace tut +namespace { struct alscriptjoblane_data { @@ -71,55 +71,72 @@ namespace tut } }; - typedef test_group alscriptjoblane_group; - typedef alscriptjoblane_group::object alscriptjoblane_object; - alscriptjoblane_group alscriptjoblane_instance("alscriptjoblane"); - - template<> template<> - void alscriptjoblane_object::test<1>() + TEST_CASE_METHOD(alscriptjoblane_data, "alscriptjoblane: runs go before checks in order and the worker starts once per drain", "[alscriptjoblane]") { - set_test_name("runs before checks, each in the order put in; the worker started once while it drains"); - ensure("the first starts the worker", lane.put(queued("check a", "a", true), take)); - ensure("the rest do not", !lane.put(queued("run b", "b", false), take) && !lane.put(queued("check c", "c", true), take) && - !lane.put(queued("run d", "d", false), take)); + UNSCOPED_INFO("the first starts the worker"); + CHECK(lane.put(queued("check a", "a", true), take)); + { + INFO("the rest do not"); + CHECK_FALSE(lane.put(queued("run b", "b", false), take)); + CHECK_FALSE(lane.put(queued("check c", "c", true), take)); + CHECK_FALSE(lane.put(queued("run d", "d", false), take)); + } lane.drain(); - ensure("runs first", ran == std::vector({ "run b", "run d", "check a", "check c" })); - ensure("drained: the next starts it again", lane.put(queued("run e", "e", false), take)); + UNSCOPED_INFO("runs first"); + CHECK(ran == std::vector({ "run b", "run d", "check a", "check c" })); + UNSCOPED_INFO("drained: the next starts it again"); + CHECK(lane.put(queued("run e", "e", false), take)); } - template<> template<> - void alscriptjoblane_object::test<2>() + TEST_CASE_METHOD(alscriptjoblane_data, "alscriptjoblane: a later check replaces a waiting one and stops a running one; runs never do", "[alscriptjoblane]") { - set_test_name("a later check of a script stands for the one waiting, and tells the one running; a run is no check's to stand for"); lane.put(queued("check a1", "a", true), take); lane.put(queued("check b", "b", true), take); lane.put(queued("check a2", "a", true), take); - ensure("the waiting one taken", jobs[0]->taken && !jobs[0]->stopped && !jobs[1]->taken); - ensure_equals("and let go of", lane.waiting(), size_t(2)); + { + INFO("the waiting one taken"); + REQUIRE(jobs[0]->taken); + REQUIRE_FALSE(jobs[0]->stopped); + CHECK_FALSE(jobs[1]->taken); + } + UNSCOPED_INFO("and let go of"); + CHECK(lane.waiting() == size_t(2)); lane.put(queued("run a", "a", false), take); - ensure("a run takes nothing", !jobs[2]->taken); + UNSCOPED_INFO("a run takes nothing"); + CHECK_FALSE(jobs[2]->taken); // Put in while the first is running: the running one told. Lane lane2; lane2.put(queued("check x1", "x", true, [this, &lane2]() { lane2.put(queued("check x2", "x", true), &alscriptjoblane_data::take); }), take); lane2.drain(); - ensure("the running one told to stop", jobs.back()->name == "check x2" && jobs[jobs.size() - 2]->taken && jobs[jobs.size() - 2]->stopped); - ensure("and the later one ran after", ran.back() == "check x2"); + { + INFO("the running one told to stop"); + REQUIRE(jobs.back()->name == "check x2"); + REQUIRE(jobs[jobs.size() - 2]->taken); + REQUIRE(jobs[jobs.size() - 2]->stopped); + } + UNSCOPED_INFO("and the later one ran after"); + CHECK(ran.back() == "check x2"); } - template<> template<> - void alscriptjoblane_object::test<3>() + TEST_CASE_METHOD(alscriptjoblane_data, "alscriptjoblane: a throwing job is logged and the next goes on; a closed lane takes nothing", "[alscriptjoblane]") { - set_test_name("a job that throws is said, and the next goes on; closed, nothing more is taken"); lane.put(queued("throws", "a", false, []() { throw std::runtime_error("nope"); }), take); lane.put(queued("next", "b", false), take); lane.drain(); - ensure("the next ran", ran == std::vector({ "throws", "next" })); + UNSCOPED_INFO("the next ran"); + CHECK(ran == std::vector({ "throws", "next" })); lane.put(queued("left", "c", false), take); lane.close(); - ensure("let go of", lane.waiting() == 0); - ensure("nothing taken once closed", !lane.put(queued("late", "d", false), take) && lane.waiting() == 0); + UNSCOPED_INFO("let go of"); + CHECK(lane.waiting() == 0); + { + INFO("nothing taken once closed"); + CHECK_FALSE(lane.put(queued("late", "d", false), take)); + CHECK(lane.waiting() == 0); + } lane.drain(); - ensure("and nothing run", ran.size() == 2); + UNSCOPED_INFO("and nothing run"); + REQUIRE(ran.size() == 2); } } diff --git a/indra/alscript/tests/alscriptlexicon_test.cpp b/indra/alscript/tests/alscriptlexicon_test.cpp index 4e422abd7f..0098b6e1ba 100644 --- a/indra/alscript/tests/alscriptlexicon_test.cpp +++ b/indra/alscript/tests/alscriptlexicon_test.cpp @@ -27,127 +27,258 @@ #include "../core/alscriptlexicon.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { - struct alscriptlexicon_data + TEST_CASE("alscriptlexicon: each LSL word has one kind; keywords are control words or types or reserved", "[alscriptlexicon]") { - }; - typedef test_group alscriptlexicon_group; - typedef alscriptlexicon_group::object alscriptlexicon_object; - tut::alscriptlexicon_group alscriptlexicon_instance("alscriptlexicon"); - - template<> template<> - void alscriptlexicon_object::test<1>() - { - set_test_name("each LSL word is of one kind; a keyword is a control word, a type or a reserved word"); using namespace ALScriptLexicon; for (const char* word : { "default", "state", "jump", "return", "if", "else", "for", "do", "while" }) { - ensure(std::string("control: ") + word, lslWord(word) == LSL_CONTROL && isLslKeyword(word)); + { + INFO(std::string("control: ") + word); + REQUIRE(lslWord(word) == LSL_CONTROL); + REQUIRE(isLslKeyword(word)); + } } for (const char* word : { "integer", "float", "string", "key", "vector", "rotation", "quaternion", "list" }) { - ensure(std::string("type: ") + word, lslWord(word) == LSL_TYPE && isLslType(word) && isLslKeyword(word)); + { + INFO(std::string("type: ") + word); + REQUIRE(lslWord(word) == LSL_TYPE); + REQUIRE(isLslType(word)); + REQUIRE(isLslKeyword(word)); + } + } + { + INFO("event and print are reserved"); + CHECK(lslWord("event") == LSL_RESERVED); + CHECK(lslWord("print") == LSL_RESERVED); + CHECK(isLslKeyword("print")); + } + { + INFO("TRUE and FALSE are constants, not keywords"); + CHECK(lslWord("TRUE") == LSL_CONSTANT); + CHECK(lslWord("FALSE") == LSL_CONSTANT); + CHECK_FALSE(isLslKeyword("TRUE")); } - ensure("event and print are reserved", lslWord("event") == LSL_RESERVED && lslWord("print") == LSL_RESERVED && isLslKeyword("print")); - ensure("TRUE and FALSE are constants, not keywords", lslWord("TRUE") == LSL_CONSTANT && lslWord("FALSE") == LSL_CONSTANT && !isLslKeyword("TRUE")); for (const char* word : { "inline", "const", "break", "continue", "switch", "case" }) { - ensure(std::string("extension, not a keyword: ") + word, lslWord(word) == LSL_EXTENSION && !isLslKeyword(word)); + { + INFO(std::string("extension, not a keyword: ") + word); + REQUIRE(lslWord(word) == LSL_EXTENSION); + REQUIRE_FALSE(isLslKeyword(word)); + } + } + { + INFO("a name is none of them"); + CHECK(lslWord("count") == LSL_NAME); + CHECK(lslWord("llSay") == LSL_NAME); + CHECK(lslWord("") == LSL_NAME); + } + { + INFO("case matters"); + CHECK(lslWord("Integer") == LSL_NAME); + CHECK(lslWord("true") == LSL_NAME); } - ensure("a name is none of them", lslWord("count") == LSL_NAME && lslWord("llSay") == LSL_NAME && lslWord("") == LSL_NAME); - ensure("case matters", lslWord("Integer") == LSL_NAME && lslWord("true") == LSL_NAME); } - template<> template<> - void alscriptlexicon_object::test<2>() + TEST_CASE("alscriptlexicon: Luau's reserved and statement words are keywords; type and typeof are not", "[alscriptlexicon]") { - set_test_name("Luau's reserved words and its statement words are keywords; type and typeof are not"); using namespace ALScriptLexicon; for (const char* word : { "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "continue", "export", "const" }) { - ensure(std::string("keyword: ") + word, isLuauKeyword(word) && isKeyword(true, word)); + { + INFO(std::string("keyword: ") + word); + REQUIRE(isLuauKeyword(word)); + REQUIRE(isKeyword(true, word)); + } + } + { + INFO("type and typeof are called"); + CHECK_FALSE(isLuauKeyword("type")); + CHECK_FALSE(isLuauKeyword("typeof")); + } + { + INFO("a name is not"); + CHECK_FALSE(isLuauKeyword("print")); + CHECK_FALSE(isLuauKeyword("ll")); + } + { + INFO("the language decides"); + CHECK(isKeyword(false, "state")); + CHECK_FALSE(isKeyword(true, "state")); + CHECK(isKeyword(true, "local")); + CHECK_FALSE(isKeyword(false, "local")); } - ensure("type and typeof are called", !isLuauKeyword("type") && !isLuauKeyword("typeof")); - ensure("a name is not", !isLuauKeyword("print") && !isLuauKeyword("ll")); - ensure("the language decides", isKeyword(false, "state") && !isKeyword(true, "state") && isKeyword(true, "local") && !isKeyword(false, "local")); } - template<> template<> - void alscriptlexicon_object::test<3>() + TEST_CASE("alscriptlexicon: a name is ASCII letters and digits and underscores; no digit first", "[alscriptlexicon]") { - set_test_name("a name is ASCII letters, digits and underscores, not beginning with a digit"); using namespace ALScriptLexicon; - ensure("letters and an underscore", isName("_count2") && isName("x") && isName("llSay")); - ensure("not a digit first", !isName("2x")); - ensure("not empty, nor with other bytes", !isName("") && !isName("a-b") && !isName("a.b") && !isName("caf\xc3\xa9")); - ensure("the bytes", isNameStart('_') && !isNameStart('7') && isNameByte('7') && !isNameByte('$')); + { + INFO("letters and an underscore"); + CHECK(isName("_count2")); + CHECK(isName("x")); + CHECK(isName("llSay")); + } + UNSCOPED_INFO("not a digit first"); + CHECK_FALSE(isName("2x")); + { + INFO("not empty, nor with other bytes"); + CHECK_FALSE(isName("")); + CHECK_FALSE(isName("a-b")); + CHECK_FALSE(isName("a.b")); + CHECK_FALSE(isName("caf\xc3\xa9")); + } + { + INFO("the bytes"); + CHECK(isNameStart('_')); + CHECK_FALSE(isNameStart('7')); + CHECK(isNameByte('7')); + CHECK_FALSE(isNameByte('$')); + } } - template<> template<> - void alscriptlexicon_object::test<4>() + TEST_CASE("alscriptlexicon: a long bracket's level and where its closer of that level ends", "[alscriptlexicon]") { - set_test_name("a long bracket's level, and where its closer of that level ends"); using namespace ALScriptLexicon; - ensure_equals("[[", longBracketLevel("x = [[a]]", 4), 0); - ensure_equals("[==[", longBracketLevel("[==[a]==]", 0), 2); - ensure_equals("not one", longBracketLevel("[=a", 0), -1); - ensure_equals("not a bracket", longBracketLevel("a[[", 0), -1); - ensure_equals("past the end", longBracketLevel("[[", 5), -1); - ensure_equals("the closer of its level, not another's", longBracketClose("a]=]b]]c", 0, 0), size_t(7)); - ensure_equals("level two", longBracketClose("a]]]=]]==]x", 0, 2), size_t(10)); - ensure("none", longBracketClose("a]=]", 0, 0) == std::string_view::npos); + UNSCOPED_INFO("[["); + CHECK(longBracketLevel("x = [[a]]", 4) == 0); + UNSCOPED_INFO("[==["); + CHECK(longBracketLevel("[==[a]==]", 0) == 2); + UNSCOPED_INFO("not one"); + CHECK(longBracketLevel("[=a", 0) == -1); + UNSCOPED_INFO("not a bracket"); + CHECK(longBracketLevel("a[[", 0) == -1); + UNSCOPED_INFO("past the end"); + CHECK(longBracketLevel("[[", 5) == -1); + UNSCOPED_INFO("the closer of its level, not another's"); + CHECK(longBracketClose("a]=]b]]c", 0, 0) == size_t(7)); + UNSCOPED_INFO("level two"); + CHECK(longBracketClose("a]]]=]]==]x", 0, 2) == size_t(10)); + UNSCOPED_INFO("none"); + REQUIRE(longBracketClose("a]=]", 0, 0) == std::string_view::npos); } - template<> template<> - void alscriptlexicon_object::test<5>() + TEST_CASE("alscriptlexicon: LSL: strings run past escapes and breaks; comments to the break or closer", "[alscriptlexicon]") { - set_test_name("LSL: a string to its quote past escapes and breaks; a line comment to the break; a block comment to its closer"); using namespace ALScriptLexicon; const std::string_view text = "a \"b\\\"c\nd\" // e\n/* f */ g /* h"; Stretch s = stretchAt(text, 0, false); - ensure("code, one byte", s.kind == Kind::Code && s.end == 1); + { + INFO("code, one byte"); + CHECK(s.kind == Kind::Code); + CHECK(s.end == 1); + } s = stretchAt(text, 2, false); - ensure("the string, across the break", s.kind == Kind::String && s.closed && s.open == 1 && s.close == 1 && text.substr(2, s.end - 2) == "\"b\\\"c\nd\""); + { + INFO("the string, across the break"); + CHECK(s.kind == Kind::String); + CHECK(s.closed); + CHECK(s.open == 1); + CHECK(s.close == 1); + CHECK(text.substr(2, s.end - 2) == "\"b\\\"c\nd\""); + } const size_t slash = text.find("//"); s = stretchAt(text, slash, false); - ensure("the line comment, to the break", s.kind == Kind::Comment && s.closed && s.close == 0 && text[s.end] == '\n' && s.open == 2); + { + INFO("the line comment, to the break"); + REQUIRE(s.kind == Kind::Comment); + REQUIRE(s.closed); + REQUIRE(s.close == 0); + CHECK(text[s.end] == '\n'); + CHECK(s.open == 2); + } const size_t block = text.find("/*"); s = stretchAt(text, block, false); - ensure("the block comment", s.kind == Kind::Comment && s.closed && s.close == 2 && text.substr(block, s.end - block) == "/* f */"); + { + INFO("the block comment"); + CHECK(s.kind == Kind::Comment); + CHECK(s.closed); + CHECK(s.close == 2); + CHECK(text.substr(block, s.end - block) == "/* f */"); + } s = stretchAt(text, text.rfind("/*"), false); - ensure("a block comment the text runs out in", s.kind == Kind::Comment && !s.closed && s.end == text.size() && s.close == 0); - ensure("no char constants", stretchAt("'x'", 0, false).kind == Kind::Code); - ensure("no -- comment", stretchAt("--x", 0, false).kind == Kind::Code); - ensure("a string not closed", !stretchAt("\"abc", 0, false).closed); + { + INFO("a block comment the text runs out in"); + CHECK(s.kind == Kind::Comment); + CHECK_FALSE(s.closed); + REQUIRE(s.end == text.size()); + CHECK(s.close == 0); + } + UNSCOPED_INFO("no char constants"); + CHECK(stretchAt("'x'", 0, false).kind == Kind::Code); + UNSCOPED_INFO("no -- comment"); + CHECK(stretchAt("--x", 0, false).kind == Kind::Code); + UNSCOPED_INFO("a string not closed"); + CHECK_FALSE(stretchAt("\"abc", 0, false).closed); } - template<> template<> - void alscriptlexicon_object::test<6>() + TEST_CASE("alscriptlexicon: SLua: quoted strings and line comments end at the line; long ones at closers", "[alscriptlexicon]") { - set_test_name("SLua: quoted strings end at their line, long strings and comments at their closer, a line comment at its break"); using namespace ALScriptLexicon; for (const char* quoted : { "\"a\\\"b\"", "'a\\'b'", "`a{b}c`" }) { const Stretch s = stretchAt(quoted, 0, true); - ensure(std::string("closed: ") + quoted, s.kind == Kind::String && s.closed && s.end == std::string_view(quoted).size()); + { + INFO(std::string("closed: ") + quoted); + REQUIRE(s.kind == Kind::String); + REQUIRE(s.closed); + REQUIRE(s.end == std::string_view(quoted).size()); + } } Stretch s = stretchAt("'abc\nd'", 0, true); - ensure("a quoted string ends at its break", s.kind == Kind::String && !s.closed && s.end == 4); + { + INFO("a quoted string ends at its break"); + CHECK(s.kind == Kind::String); + CHECK_FALSE(s.closed); + CHECK(s.end == 4); + } s = stretchAt("'a\\\nb'", 0, true); - ensure("but for an escaped one", s.closed && s.end == 6); + { + INFO("but for an escaped one"); + CHECK(s.closed); + CHECK(s.end == 6); + } s = stretchAt("[==[a]]b]==]c", 0, true); - ensure("a long string", s.kind == Kind::String && s.closed && s.open == 4 && s.close == 4 && s.end == 12); + { + INFO("a long string"); + CHECK(s.kind == Kind::String); + CHECK(s.closed); + CHECK(s.open == 4); + CHECK(s.close == 4); + CHECK(s.end == 12); + } s = stretchAt("--[[a\nb]] c", 0, true); - ensure("a long comment", s.kind == Kind::Comment && s.closed && s.open == 4 && s.close == 2 && s.end == 9); + { + INFO("a long comment"); + CHECK(s.kind == Kind::Comment); + CHECK(s.closed); + CHECK(s.open == 4); + CHECK(s.close == 2); + CHECK(s.end == 9); + } s = stretchAt("-- a\nb", 0, true); - ensure("a line comment", s.kind == Kind::Comment && s.closed && s.close == 0 && s.end == 4); + { + INFO("a line comment"); + CHECK(s.kind == Kind::Comment); + CHECK(s.closed); + CHECK(s.close == 0); + CHECK(s.end == 4); + } s = stretchAt("--[[ a", 0, true); - ensure("a long comment the text runs out in", s.kind == Kind::Comment && !s.closed && s.end == 6); - ensure("[ alone is code", stretchAt("[1]", 0, true).kind == Kind::Code); - ensure("no // comment", stretchAt("//x", 0, true).kind == Kind::Code); + { + INFO("a long comment the text runs out in"); + CHECK(s.kind == Kind::Comment); + CHECK_FALSE(s.closed); + CHECK(s.end == 6); + } + UNSCOPED_INFO("[ alone is code"); + CHECK(stretchAt("[1]", 0, true).kind == Kind::Code); + UNSCOPED_INFO("no // comment"); + CHECK(stretchAt("//x", 0, true).kind == Kind::Code); } } diff --git a/indra/alscript/tests/alscriptlintpass_test.cpp b/indra/alscript/tests/alscriptlintpass_test.cpp index 8dc9a1060f..795fb705bc 100644 --- a/indra/alscript/tests/alscriptlintpass_test.cpp +++ b/indra/alscript/tests/alscriptlintpass_test.cpp @@ -30,13 +30,13 @@ #include "../lint/alscriptfixes.h" #include "../lint/alscriptlintpass.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct alscriptlintpass_data { @@ -118,18 +118,25 @@ namespace tut { const ALScriptProblems problems = check(script, lua); const ALScriptProblem* problem = keyed(problems, key); - ensure("said: " + key + "\n" + said(problems), problem != nullptr); - ensure("a fix: " + said(problems), !problem->fixes.empty()); + UNSCOPED_INFO("said: " + key + "\n" + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("a fix: " + said(problems)); + REQUIRE_FALSE(problem->fixes.empty()); const ALScriptFix& fix = problem->fixes.front(); - ensure("preferred", fix.preferred); - ensure_equals("its words", fix.title, title); - ensure_equals("safe or not", fix.safe, safe); + UNSCOPED_INFO("preferred"); + REQUIRE(fix.preferred); + UNSCOPED_INFO("its words"); + REQUIRE(fix.title == title); + UNSCOPED_INFO("safe or not"); + REQUIRE(fix.safe == safe); const std::optional made = ALScriptFixes::apply(script, fix); - ensure("applies", made.has_value()); + UNSCOPED_INFO("applies"); + REQUIRE(made.has_value()); const ALScriptProblems after = check(*made, lua); - ensure("gone from:\n" + *made + "\n" + said(after), keyed(after, key) == nullptr); - ensure("no error in its place:\n" + *made + "\n" + said(after), - errors(after) <= errors(problems) - (problem->severity == ALScriptProblem::Severity::Error ? 1 : 0)); + UNSCOPED_INFO("gone from:\n" + *made + "\n" + said(after)); + REQUIRE(keyed(after, key) == nullptr); + UNSCOPED_INFO("no error in its place:\n" + *made + "\n" + said(after)); + REQUIRE(errors(after) <= errors(problems) - (problem->severity == ALScriptProblem::Severity::Error ? 1 : 0)); return *made; } @@ -138,21 +145,19 @@ namespace tut { const ALScriptProblems problems = check(script, lua); const ALScriptProblem* problem = keyed(problems, key); - ensure("said: " + key + "\n" + said(problems), problem != nullptr); - ensure("no fix: " + said(problems), problem->fixes.empty()); + UNSCOPED_INFO("said: " + key + "\n" + said(problems)); + REQUIRE(problem != nullptr); + UNSCOPED_INFO("no fix: " + said(problems)); + REQUIRE(problem->fixes.empty()); } }; // Raised from TUT's fifty: stages C and D of the SLua help plan add a // test for each rule. - typedef test_group alscriptlintpass_group; - typedef alscriptlintpass_group::object object; - tut::alscriptlintpass_group alscriptlintpass_test("ALScriptLintPass"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlTableCompare: a table compared with {}; with items or a string no fix", "[ALScriptLintPass]") { - set_test_name("SlTableCompare: a table compared with {} on either side, by length where it is a list; with items, or a string, no fix"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local t = {1}\n" "if t == {} then print(1) end\n" "if {} ~= t then print(2) end\n" @@ -164,26 +169,27 @@ namespace tut "local function f(l) return l ~= {} end\n" "if t == nil or t == d then print(f(t)) end\n", "SlTableCompare", ALScriptProblem::Severity::Error); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("1 LuauLintSlTableCompare|t|#t == 0\n" "2 LuauLintSlTableCompareAlways|t|#t > 0\n" "4 LuauLintSlTableCompare|d|next(d) == nil\n" "5 LuauLintSlTableCompareItems|t\n" "7 LuauLintSlTableCompareItems|s\n" "8 LuauLintSlTableCompareAlways|l|next(l) ~= nil\n")); - ensure_equals("a list", fixed("local t = {1}\nif t == {} then print(t) end\n", "LuauLintSlTableCompare", "Write it #t == 0", false), + UNSCOPED_INFO("a list"); + CHECK(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")); - ensure_equals("a table, on the right, bracketed", fixed("local d = {a = 1}\nprint({} ~= (d))\n", "LuauLintSlTableCompareAlways", "Write it next(d) ~= nil", false), + UNSCOPED_INFO("a table, on the right, bracketed"); + CHECK(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")); unfixed("local t = {1}\nprint(t == {1})\n", "LuauLintSlTableCompareItems"); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlZeroIndex: counting from 0 in lists and loops and subs and finds", "[ALScriptLintPass]") { - set_test_name("SlZeroIndex: a list at 0, a loop from 0 to a length less 1 that indexes, string.sub and ll from 0, a find against 0; " - "not a table keyed at 0, nor a loop given to llcompat"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local t = {1, 2}\n" "print(t[0])\n" "for i = 0, #t - 1 do print(t[i]) end\n" @@ -198,7 +204,8 @@ namespace tut "h[0] = 2\n" "print(d[0], h[0], llcompat.SubStringIndex(s, \"a\") == 0)\n", "SlZeroIndex", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("1 LuauLintSlZeroIndex|t\n" "2 LuauLintSlZeroIndexLoop|#t|t[i]|i\n" "3 LuauLintSlZeroIndexLoop|#t|t[i]|i\n" @@ -210,31 +217,34 @@ namespace tut "7 LuauLintSlZeroIndexArg|ll.GetSubString\n" "8 LuauLintSlZeroIndexFound|ll.SubStringIndex(...)|ll.SubStringIndex\n" "8 LuauLintSlZeroIndexFound|table.find(...)|table.find\n")); - ensure_equals("a list", fixed("local t = {1}\nprint(t[0])\n", "LuauLintSlZeroIndex", "Write it t[1]", false), + UNSCOPED_INFO("a list"); + CHECK(fixed("local t = {1}\nprint(t[0])\n", "LuauLintSlZeroIndex", "Write it t[1]", false) == std::string("local t = {1}\nprint(t[1])\n")); - ensure_equals("a loop", fixed("local t = {1}\nfor i = 0, #t - 1 do print(t[i]) end\n", "LuauLintSlZeroIndexLoop", "Write it for i = 1, #t", false), + UNSCOPED_INFO("a loop"); + CHECK(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")); - ensure_equals("a method to a length less 1", fixed("local s = \"abc\"\nprint(s:sub(0, #s - 1))\n", "LuauLintSlZeroIndexSub", "Write it s:sub(1, #s)", false), + UNSCOPED_INFO("a method to a length less 1"); + CHECK(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")); - ensure_equals("string's, to a number", fixed("print(string.sub(\"abc\", 0, 2))\n", "LuauLintSlZeroIndexSub", "Write it string.sub(\"abc\", 1, 3)", false), + UNSCOPED_INFO("string's, to a number"); + CHECK(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")); - ensure_equals("ll's, one from the end kept", fixed("print(ll.GetSubString(\"abc\", 0, -1))\n", "LuauLintSlZeroIndexArg", - "Write it ll.GetSubString(\"abc\", 1, -1)", false), + UNSCOPED_INFO("ll's, one from the end kept"); + CHECK(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")); - ensure_equals("a find", fixed("print(ll.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlZeroIndexFound", "Write it ll.SubStringIndex(...) == 1", false), + UNSCOPED_INFO("a find"); + CHECK(fixed("print(ll.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlZeroIndexFound", "Write it ll.SubStringIndex(...) == 1", false) == std::string("print(ll.SubStringIndex(\"ab\", \"a\") == 1)\n")); unfixed("local t = {1}\nfor i = 0, #t - 1 do print(i, t[i]) end\n", "LuauLintSlZeroIndexLoop"); unfixed("local s = \"abc\"\nlocal n = 2\nprint(s:sub(0, n))\n", "LuauLintSlZeroIndexSub"); unfixed("local s = \"abc\"\nprint(ll.GetSubString(s, 0, #s))\n", "LuauLintSlZeroIndexArg"); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlCompatCall: llcompat's where ll's means the same; not where it differs", "[ALScriptLintPass]") { - set_test_name("SlCompatCall: llcompat's where ll's means the same -- the same function, indexes that are numbers, a boolean against 1 " - "or 0, a find against -1 or 0, an answer unread, one ll's only deprecates; not where ll deprecates one that differs, nor an " - "answer read otherwise"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local s = \"abc\"\n" "llcompat.Say(0, s)\n" "print(llcompat.GetSubString(s, 0, -1))\n" @@ -249,7 +259,8 @@ namespace tut "print(n, g, llcompat.ListFindList({1}, {1}) == -1)\n" "print(llcompat.List2Json(JSON_ARRAY, {s}))\n", "SlCompatCall", ALScriptProblem::Severity::Note); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("1 LuauLintSlCompatCall|llcompat.Say|ll.Say(...)\n" "2 LuauLintSlCompatCall|llcompat.GetSubString|ll.GetSubString(s, 1, -1)\n" "4 LuauLintSlCompatCall|llcompat.SameGroup|ll.SameGroup(...)\n" @@ -258,26 +269,30 @@ namespace tut "7 LuauLintSlCompatCall|llcompat.SubStringIndex|ll.SubStringIndex(...) == nil\n" "8 LuauLintSlCompatCall|llcompat.SameGroup|ll.SameGroup(...)\n" "12 LuauLintSlCompatCall|llcompat.List2Json|ll.List2Json(...)\n")); - ensure_equals("the same", fixed("llcompat.Say(0, \"hi\")\n", "LuauLintSlCompatCall", "Write it ll.Say(...)", true), + UNSCOPED_INFO("the same"); + CHECK(fixed("llcompat.Say(0, \"hi\")\n", "LuauLintSlCompatCall", "Write it ll.Say(...)", true) == std::string("ll.Say(0, \"hi\")\n")); - ensure_equals("indexes", fixed("print(llcompat.GetSubString(\"abc\", 0, 1))\n", "LuauLintSlCompatCall", "Write it ll.GetSubString(\"abc\", 1, 2)", true), + UNSCOPED_INFO("indexes"); + CHECK(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")); - ensure_equals("a boolean on the right", fixed("print(0 == llcompat.SameGroup(ll.GetOwner()))\n", "LuauLintSlCompatCall", "Write it not ll.SameGroup(...)", true), + UNSCOPED_INFO("a boolean on the right"); + CHECK(fixed("print(0 == llcompat.SameGroup(ll.GetOwner()))\n", "LuauLintSlCompatCall", "Write it not ll.SameGroup(...)", true) == std::string("print(not ll.SameGroup(ll.GetOwner()))\n")); - ensure_equals("a boolean", fixed("print(llcompat.SameGroup(ll.GetOwner()) ~= 0)\n", "LuauLintSlCompatCall", "Write it ll.SameGroup(...)", true), + UNSCOPED_INFO("a boolean"); + CHECK(fixed("print(llcompat.SameGroup(ll.GetOwner()) ~= 0)\n", "LuauLintSlCompatCall", "Write it ll.SameGroup(...)", true) == std::string("print(ll.SameGroup(ll.GetOwner()))\n")); - ensure_equals("a find, first", fixed("print(llcompat.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) == 1", true), + UNSCOPED_INFO("a find, first"); + CHECK(fixed("print(llcompat.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) == 1", true) == std::string("print(ll.SubStringIndex(\"ab\", \"a\") == 1)\n")); - ensure_equals("a find, on the right", fixed("print(-1 < llcompat.SubStringIndex(\"ab\", \"a\"))\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) ~= nil", true), + UNSCOPED_INFO("a find, on the right"); + CHECK(fixed("print(-1 < llcompat.SubStringIndex(\"ab\", \"a\"))\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) ~= nil", true) == std::string("print(ll.SubStringIndex(\"ab\", \"a\") ~= nil)\n")); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlBooleanNumber: a boolean against a number; an if of 1 and 0 a note", "[ALScriptLintPass]") { - set_test_name("SlBooleanNumber: a boolean compared with a number, on either side, an error; an if-then-else of 1 and 0 compared with " - "one, a note; not a number compared with one"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local on = false\n" "local n = 1\n" "if on == 1 or 0 ~= on then print(1) end\n" @@ -285,29 +300,30 @@ namespace tut "if (if on then 1 else 0) == 1 or (if n > 2 then 0 else 1) ~= 0 then print(3) end\n" "if n == 1 or (if on then 2 else 0) == 1 then print(4) end\n", "SlBooleanNumber", ALScriptProblem::Severity::Error); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("2 LuauLintSlBooleanNumber|on|1\n" "2 LuauLintSlBooleanNumberAlways|on|0\n" "3 LuauLintSlBooleanNumber|ll.SameGroup(ll.GetOwner())|0\n" "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")); - ensure_equals("true", fixed("local on = false\nprint(on == 1)\n", "LuauLintSlBooleanNumber", "Write it on", false), + UNSCOPED_INFO("true"); + CHECK(fixed("local on = false\nprint(on == 1)\n", "LuauLintSlBooleanNumber", "Write it on", false) == std::string("local on = false\nprint(on)\n")); - ensure_equals("false, on the right", fixed("print(0 == ll.SameGroup(ll.GetOwner()))\n", "LuauLintSlBooleanNumber", "Write it not ll.SameGroup(ll.GetOwner())", false), + UNSCOPED_INFO("false, on the right"); + CHECK(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")); - ensure_equals("a choice", fixed("local n = 1\nprint((if n > 2 then 0 else 1) ~= 0)\n", "LuauLintSlBooleanNumberChoice", "Write it not (n > 2)", false), + UNSCOPED_INFO("a choice"); + CHECK(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")); unfixed("local n = 1\nprint((n > 2) == 5)\n", "LuauLintSlBooleanNumber"); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlGlobalAssign: globals and global functions made local; not with _G", "[ALScriptLintPass]") { - set_test_name("SlGlobalAssign: a global made at the top, once, a note; a function made global in a block, a warning; a function " - "at the top SlGlobalFunction's, a note, off on its own; not a name the script is given; each made local, in place or " - "declared before it is first named, and not where _G is"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string script = "count = 0\n" "function bump() count += 1; total = count end\n" "local function early() return later end\n" @@ -322,45 +338,51 @@ namespace tut "print(bump, early, outer, shared, total, x, y)\n" "count = 1\n"; const std::string said = found(script, "SlGlobalAssign", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("0 LuauLintSlGlobalAssign|count (another severity)\n" "1 LuauLintSlGlobalInFunction|total (another severity)\n" "3 LuauLintSlGlobalAssign|later (another severity)\n" "6 LuauLintSlGlobalFunctionInScope|inner\n" "7 LuauLintSlGlobalFunctionInScope|shared\n" "10 LuauLintSlGlobalAssign|x, y (another severity)\n")); - ensure_equals("the function at the top", found(script, "SlGlobalFunction", ALScriptProblem::Severity::Note), + UNSCOPED_INFO("the function at the top"); + CHECK(found(script, "SlGlobalFunction", ALScriptProblem::Severity::Note) == std::string("1 LuauLintSlGlobalFunction|bump\n")); - ensure_equals("off on its own: the rest still said", found("--!nolint SlGlobalFunction\n" + script, "SlGlobalAssign", ALScriptProblem::Severity::Warning), + UNSCOPED_INFO("off on its own: the rest still said"); + CHECK(found("--!nolint SlGlobalFunction\n" + script, "SlGlobalAssign", ALScriptProblem::Severity::Warning) == std::string("1 LuauLintSlGlobalAssign|count (another severity)\n" "2 LuauLintSlGlobalInFunction|total (another severity)\n" "4 LuauLintSlGlobalAssign|later (another severity)\n" "7 LuauLintSlGlobalFunctionInScope|inner\n" "8 LuauLintSlGlobalFunctionInScope|shared\n" "11 LuauLintSlGlobalAssign|x, y (another severity)\n")); - ensure("and it not", found("--!nolint SlGlobalFunction\n" + script, "SlGlobalFunction", ALScriptProblem::Severity::Note).empty()); - ensure("nor the function where SlGlobalAssign alone is off", - found("--!nolint SlGlobalAssign\n" + script, "SlGlobalFunction", ALScriptProblem::Severity::Note) == "2 LuauLintSlGlobalFunction|bump\n"); - ensure_equals("in place", fixed("count = 0\nprint(count)\n", "LuauLintSlGlobalAssign", "Write it local count", false), + UNSCOPED_INFO("and it not"); + 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(fixed("count = 0\nprint(count)\n", "LuauLintSlGlobalAssign", "Write it local count", false) == std::string("local count = 0\nprint(count)\n")); - ensure_equals("declared first", fixed("local function early() return later end\nlater = 5\nprint(early())\n", "LuauLintSlGlobalAssign", - "Declare local later before it is first named", false), + UNSCOPED_INFO("declared first"); + CHECK(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")); - ensure_equals("a function", fixed("function f() return 1 end\nprint(f())\n", "LuauLintSlGlobalFunction", "Write it local function f", false), + UNSCOPED_INFO("a function"); + CHECK(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")); - ensure_equals("in a block", fixed("local function outer()\n function inner() return 1 end\n return inner()\nend\nprint(outer())\n", - "LuauLintSlGlobalFunctionInScope", "Write it local function inner", false), + UNSCOPED_INFO("in a block"); + CHECK(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")); unfixed("local function outer()\n function shared() return 2 end\nend\nprint(outer, shared)\n", "LuauLintSlGlobalFunctionInScope"); unfixed("print(_G)\ny = 2\n", "LuauLintSlGlobalAssign"); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlParenCondition: a whole condition in brackets; taken out safely", "[ALScriptLintPass]") { - set_test_name("SlParenCondition: if, elseif, while, until and an if-then-else bracketed whole, a note; not brackets round a part; " - "the brackets taken out, a blank kept where a word would run on, safe; selene's name allows it"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local x = true\n" "if (x) then print(1) elseif (not x) then print(2) end\n" "while(x)do break end\n" @@ -369,27 +391,29 @@ namespace tut "if (x) and (x) then print(3) end\n" "if x then print(4) end\n", "SlParenCondition", ALScriptProblem::Severity::Note); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("1 LuauLintSlParenCondition|if\n" "1 LuauLintSlParenCondition|elseif\n" "2 LuauLintSlParenCondition|while\n" "3 LuauLintSlParenCondition|until\n" "4 LuauLintSlParenCondition|if\n")); - ensure_equals("spaced", fixed("local x = true\nif (x) then print(1) end\n", "LuauLintSlParenCondition", "Take out the brackets", true), + UNSCOPED_INFO("spaced"); + CHECK(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")); - ensure_equals("run on", fixed("local x = true\nwhile(x)do break end\n", "LuauLintSlParenCondition", "Take out the brackets", true), + UNSCOPED_INFO("run on"); + CHECK(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")); - ensure_equals("allowed by selene's name", found("local x = true\n-- selene: allow(parenthese_conditions)\nif (x) then print(1) end\n", - "SlParenCondition", ALScriptProblem::Severity::Note), + 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) == std::string()); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlAlmostSwapped: a = b then b = a is a warning fixed as a swap", "[ALScriptLintPass]") { - set_test_name("SlAlmostSwapped: a = b then b = a, locals, globals or fields, a warning, fixed as a swap; not a swap, nor with " - "something between"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local a, b = 1, 2\n" "a = b\n" "b = a\n" @@ -400,19 +424,19 @@ namespace tut "print(a)\n" "b = a\n", "SlAlmostSwapped", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + 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")); - ensure_equals("swapped", fixed("local a, b = 1, 2\na = b\nb = a\nprint(a, b)\n", "LuauLintSlAlmostSwapped", "Write it a, b = b, a", false), + UNSCOPED_INFO("swapped"); + CHECK(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")); } - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlMustUse: an answer left unread; given back to the first argument", "[ALScriptLintPass]") { - set_test_name("SlMustUse: ll's, llcompat's, a library's, a global's and a string's method that only answer, their answers unread; " - "given back where the first given is a variable of the kind answered; not a call that does something"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local l = {1, 2}\n" "local s = \"abc\"\n" "ll.DeleteSubList(l, 1, 1)\n" @@ -426,27 +450,28 @@ namespace tut "math.random()\n" "print(ll.DeleteSubList(l, 1, 1))\n", "SlMustUse", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("2 LuauLintSlMustUse|ll.DeleteSubList\n" "3 LuauLintSlMustUse|string.upper\n" "4 LuauLintSlMustUse|s:lower\n" "5 LuauLintSlMustUse|string.len\n" "6 LuauLintSlMustUse|tostring\n" "7 LuauLintSlMustUse|llcompat.GetPos\n")); - ensure_equals("a list", fixed("local l = {1, 2}\nll.DeleteSubList(l, 1, 1)\nprint(l)\n", "LuauLintSlMustUse", "Write it l = ll.DeleteSubList(...)", false), + UNSCOPED_INFO("a list"); + CHECK(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")); - ensure_equals("a method", fixed("local s = \"abc\"\ns:upper()\nprint(s)\n", "LuauLintSlMustUse", "Write it s = s:upper(...)", false), + UNSCOPED_INFO("a method"); + CHECK(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")); unfixed("local s = \"abc\"\nstring.len(s)\n", "LuauLintSlMustUse"); unfixed("string.upper(\"abc\")\n", "LuauLintSlMustUse"); } - template<> template<> - void object::test<9>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlGeneralizedFor: pairs and ipairs and next in a for become the table", "[ALScriptLintPass]") { - set_test_name("SlGeneralizedFor: pairs, ipairs and next, t in a for, a note, fixed as the table itself, not safe; not a function " - "of the script's, nor for over a table already"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local t = {1, 2}\n" "for k, v in pairs(t) do print(k, v) end\n" "for i, v in ipairs(t) do print(i, v) end\n" @@ -455,22 +480,23 @@ namespace tut "for k, v in walk(t) do print(k, v) end\n" "for k, v in t do print(k, v) end\n", "SlGeneralizedFor", ALScriptProblem::Severity::Note); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("1 LuauLintSlGeneralizedFor|t|pairs(t)\n" "2 LuauLintSlGeneralizedForList|t|ipairs(t)\n" "3 LuauLintSlGeneralizedFor|t|next, t\n")); - ensure_equals("pairs", fixed("local t = {1}\nfor k, v in pairs(t) do print(k, v) end\n", "LuauLintSlGeneralizedFor", "Write it in t", false), + UNSCOPED_INFO("pairs"); + CHECK(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")); - ensure_equals("next", fixed("local t = {1}\nfor k, v in next, t do print(k, v) end\n", "LuauLintSlGeneralizedFor", "Write it in t", false), + UNSCOPED_INFO("next"); + CHECK(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")); } - template<> template<> - void object::test<10>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlEmptyBlock: an empty block is a warning; an empty then or else fixed", "[ALScriptLintPass]") { - set_test_name("SlEmptyBlock: an empty if, elseif, else, while, for and repeat, a warning; not one holding a comment; an empty then " - "before an else turned round, and an empty else taken out, safe; selene's names allow it"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local c = true\n" "if c then end\n" "if c then else print(1) end\n" @@ -484,7 +510,8 @@ namespace tut "end\n" "while c do break end\n", "SlEmptyBlock", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("1 LuauLintSlEmptyBlock|if\n" "2 LuauLintSlEmptyBlock|if\n" "3 LuauLintSlEmptyBlock|elseif\n" @@ -493,23 +520,25 @@ namespace tut "6 LuauLintSlEmptyBlock|for\n" "7 LuauLintSlEmptyBlock|for\n" "8 LuauLintSlEmptyBlock|repeat\n")); - ensure_equals("turned round", fixed("local c = true\nif c then else print(1) end\n", "LuauLintSlEmptyBlock", "Write it if not c then", false), + UNSCOPED_INFO("turned round"); + CHECK(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")); - ensure_equals("an else", fixed("local c = true\nif c then print(3) else end\n", "LuauLintSlEmptyBlock", "Take out the empty else", true), + UNSCOPED_INFO("an else"); + CHECK(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")); - ensure_equals("an else on its own line", fixed("local c = true\nif c then\n print(3)\nelse\nend\n", "LuauLintSlEmptyBlock", "Take out the empty else", true), + UNSCOPED_INFO("an else on its own line"); + CHECK(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")); - ensure_equals("selene's names", 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), + 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) == std::string()); } - template<> template<> - void object::test<11>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlIndexDivision: a / in a list's index is a warning fixed as //", "[ALScriptLintPass]") { - set_test_name("SlIndexDivision: a / in what indexes a list, alone, bracketed or in a sum, read or written, a warning, fixed as //; " - "not //, nor a table that is no list, nor a count"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local t = {1, 2, 3}\n" "local n = #t\n" "print(t[n / 2], t[(n + 1) / 2], t[n / 2 + 1], t[n // 2])\n" @@ -517,22 +546,21 @@ namespace tut "local d = {a = 1}\n" "print(d[n / 2], string.rep(\"x\", n / 2))\n", "SlIndexDivision", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("2 LuauLintSlIndexDivision|n / 2|n // 2\n" "2 LuauLintSlIndexDivision|(n + 1) / 2|(n + 1) // 2\n" "2 LuauLintSlIndexDivision|n / 2|n // 2\n" "3 LuauLintSlIndexDivision|n / 2|n // 2\n")); - ensure_equals("rounded down", fixed("local t = {1}\nlocal n = #t\nprint(t[n / 2])\n", "LuauLintSlIndexDivision", "Write it n // 2", false), + UNSCOPED_INFO("rounded down"); + CHECK(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")); } - template<> template<> - void object::test<12>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlVectorProduct: vectors' * as a number is vector.dot; % is vector.cross", "[ALScriptLintPass]") { - set_test_name("SlVectorProduct: two vectors multiplied where a number is wanted -- compared, added to one, given to math -- a " - "warning, fixed as vector.dot; taken % each other, the cross product as in LSL, a note naming vector.cross; not a " - "product kept a vector, nor by a number"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local a = vector(1, 0, 0)\n" "local b = vector(0, 1, 0)\n" "if a * b > 0.5 then print(1) end\n" @@ -541,25 +569,27 @@ namespace tut "local s = a * b\n" "print(a * 2, s, c)\n", "SlVectorProduct", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("2 LuauLintSlVectorProduct|a * b|a|b\n" "3 LuauLintSlVectorProduct|a * b|a|b\n" "3 LuauLintSlVectorProduct|a * b|a|b\n" "4 LuauLintSlVectorCross|a % b|a|b (another severity)\n")); - ensure_equals("a note", found("local a = vector(1, 0, 0)\nlocal c = a % a\nprint(c)\n", "SlVectorProduct", ALScriptProblem::Severity::Note), + 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")); - ensure_equals("dot", fixed("local a = vector(1, 0, 0)\nprint(a * a > 0.5)\n", "LuauLintSlVectorProduct", "Write it vector.dot(a, a)", false), + UNSCOPED_INFO("dot"); + CHECK(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")); - ensure_equals("cross", fixed("local a = vector(1, 0, 0)\nlocal c = a % a\nprint(c)\n", "LuauLintSlVectorCross", "Write it vector.cross(a, a)", false), + UNSCOPED_INFO("cross"); + CHECK(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")); } - template<> template<> - void object::test<13>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlSleepingCall in SLua: a sleeping call warned in loops; fixed as Fast", "[ALScriptLintPass]") { - set_test_name("SlSleepingCall in SLua: a call that sleeps where a Fast one does not, a note, and a warning in a loop or a timer; " - "fixed as the Fast call, its arguments put in or, where only settled, written again; a texture's, no fix"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local v = vector(1, 2, 3)\n" "ll.SetPos(v)\n" "for i = 1, 3 do ll.SetRot(quaternion(0, 0, 0, 1)) end\n" @@ -569,32 +599,34 @@ namespace tut "ll.SetLinkPrimitiveParamsFast(LINK_THIS, {})\n" "llcompat.SetPos(v)\n", "SlSleepingCall", ALScriptProblem::Severity::Note); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("1 LuauLintSlSleepingCall|ll.SetPos|0.2|ll.SetLinkPrimitiveParamsFast\n" "2 LuauLintSlSleepingCallOften|ll.SetRot|0.2|ll.SetLinkPrimitiveParamsFast (another severity)\n" "3 LuauLintSlSleepingCallOften|ll.SetPrimitiveParams|0.2|ll.SetLinkPrimitiveParamsFast (another severity)\n" "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")); - ensure_equals("put in", fixed("local v = vector(1, 2, 3)\nll.SetPos(v)\n", "LuauLintSlSleepingCall", - "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_POSITION, v})", false), + UNSCOPED_INFO("put in"); + CHECK(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")); - ensure_equals("a link first, in a timer", fixed("LLTimers:every(1, function() ll.SetPrimitiveParams({}) end)\n", "LuauLintSlSleepingCallOften", - "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {})", false), + UNSCOPED_INFO("a link first, in a timer"); + CHECK(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")); - ensure_equals("written again", fixed("ll.SetLinkRenderMaterial(LINK_THIS, \"m\", 0)\n", "LuauLintSlSleepingCall", - "Write it ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_RENDER_MATERIAL, 0, \"m\"})", false), + UNSCOPED_INFO("written again"); + CHECK(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")); unfixed("ll.SetTexture(\"x\", 0)\n", "LuauLintSlSleepingTexture"); unfixed("ll.SetRenderMaterial(tostring(1), 0)\n", "LuauLintSlSleepingCall"); } - template<> template<> - void object::test<14>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlSleepingCall in LSL: the same; fixed over commas and comments", "[ALScriptLintPass]") { - set_test_name("SlSleepingCall in LSL: the same table, a warning in a loop or the timer event; fixed over the text, a vector's commas " - "and a comment in the way"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string said = found("default {\n" " state_entry() {\n" " llSetPos(<1, 2, 3>);\n" @@ -608,28 +640,28 @@ namespace tut " }\n" "}\n", "SlSleepingCall", ALScriptProblem::Severity::Note, false); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("2 LSLSlSleepingCall|llSetPos|0.2|llSetLinkPrimitiveParamsFast\n" "4 LSLSlSleepingCallOften|llSetRot|0.2|llSetLinkPrimitiveParamsFast (another severity)\n" "5 LSLSlSleepingCall|llSetLinkRenderMaterial|0.2|llSetLinkPrimitiveParamsFast\n" "6 LSLSlSleepingTexture|llSetTexture|0.2|PRIM_TEXTURE\n" "9 LSLSlSleepingCallOften|llSetPrimitiveParams|0.2|llSetLinkPrimitiveParamsFast (another severity)\n")); - ensure_equals("put in", fixed("default { state_entry() { llSetPos( <1, 2, 3> /* here */ ); } }\n", "LSLSlSleepingCall", - "Write it llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POSITION, <1, 2, 3> /* here */])", false, false), + UNSCOPED_INFO("put in"); + CHECK(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")); - ensure_equals("written again", 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), + UNSCOPED_INFO("written again"); + CHECK(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")); unfixed("default { state_entry() { llSetRenderMaterial(llGetInventoryName(INVENTORY_MATERIAL, 0), 0); } }\n", "LSLSlSleepingCall", false); } - template<> template<> - void object::test<15>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlMergeablePrimParams in SLua: a run of prim-params calls merged", "[ALScriptLintPass]") { - set_test_name("SlMergeablePrimParams in SLua: a run of the same prim-params call, its rules written out, a note; merged with " - "PRIM_LINK_TARGET where the link changes, or a list sends its rules elsewhere; broken by anything between, a call " - "whose arguments read the world, or another function"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + 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" "ll.SetLinkPrimitiveParamsFast(2, {PRIM_GLOW, ALL_SIDES, 0})\n" @@ -639,26 +671,28 @@ namespace tut "ll.SetPrimitiveParams({PRIM_GLOW, ALL_SIDES, 0})\n" "ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0})\n", "SlMergeablePrimParams", ALScriptProblem::Severity::Note); - ensure_equals("each", said, std::string("0 LuauLintSlMergeablePrimParams|3|ll.SetLinkPrimitiveParamsFast\n")); - ensure_equals("a link changed", fixed("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5})\nll.SetLinkPrimitiveParamsFast(2, {PRIM_GLOW, ALL_SIDES, 0})\n", + UNSCOPED_INFO("each"); + CHECK(said == std::string("0 LuauLintSlMergeablePrimParams|3|ll.SetLinkPrimitiveParamsFast\n")); + UNSCOPED_INFO("a link changed"); + CHECK(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), + false) == std::string("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5, PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 0})\n")); - ensure_equals("sent elsewhere, then back", fixed("ll.SetLinkPrimitiveParamsFast(1, {PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 1})\n" + UNSCOPED_INFO("sent elsewhere, then back"); + CHECK(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), + 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")); } - template<> template<> - void object::test<16>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlMergeablePrimParams in LSL: the same over the text keeping the last ;", "[ALScriptLintPass]") { - set_test_name("SlMergeablePrimParams in LSL: the same, over the text, each call's ; but the last's taken with it"); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string said = found("default {\n" " state_entry() {\n" " llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5]);\n" @@ -668,34 +702,37 @@ namespace tut " }\n" "}\n", "SlMergeablePrimParams", ALScriptProblem::Severity::Note, false); - ensure_equals("each", said, std::string("2 LSLSlMergeablePrimParams|2|llSetLinkPrimitiveParamsFast\n")); - ensure_equals("a link changed", fixed("default { state_entry() {\n llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5]);\n" + UNSCOPED_INFO("each"); + CHECK(said == std::string("2 LSLSlMergeablePrimParams|2|llSetLinkPrimitiveParamsFast\n")); + UNSCOPED_INFO("a link changed"); + CHECK(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), + 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")); - ensure_equals("no link", fixed("default { state_entry() { llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5]); llSetPrimitiveParams([PRIM_COLOR, " + UNSCOPED_INFO("no link"); + CHECK(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), + false, false) == std::string("default { state_entry() { llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5, PRIM_COLOR, ALL_SIDES, <1, 0, 0>, 1.0]); } }\n")); } - template<> template<> - void object::test<17>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlCostlyListen and SlFastTimer and SlFastSensor: notes in both languages", "[ALScriptLintPass]") { - set_test_name("SlCostlyListen, SlFastTimer, SlFastSensor in both languages: notes, with no fix; not a listen narrowed, a timer " - "stopped or of a tenth, nor a sensor once a second"); - ensure("definitions: " + error, loaded); - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const auto three = [&](const std::string& script, bool lua) { return found(script, "SlCostlyListen", ALScriptProblem::Severity::Note, lua) + found(script, "SlFastTimer", ALScriptProblem::Severity::Note, lua) + found(script, "SlFastSensor", ALScriptProblem::Severity::Note, lua); }; - ensure_equals("SLua", three("ll.Listen(0, \"\", NULL_KEY, \"\")\n" + UNSCOPED_INFO("SLua"); + CHECK(three("ll.Listen(0, \"\", NULL_KEY, \"\")\n" "ll.Listen(PUBLIC_CHANNEL, \"\", \"\", \"hello\")\n" "ll.Listen(0, \"Bob\", NULL_KEY, \"\")\n" "ll.Listen(7, \"\", NULL_KEY, \"\")\n" @@ -705,13 +742,14 @@ namespace tut "llcompat.SetTimerEvent(0)\n" "ll.SensorRepeat(\"\", NULL_KEY, AGENT, 10, PI, 0.5)\n" "ll.SensorRepeat(\"\", NULL_KEY, AGENT, 10, PI, 1)\n", - true), + true) == std::string("0 LuauLintSlCostlyListen\n" "1 LuauLintSlCostlyListen\n" "4 LuauLintSlFastTimer|0.05\n" "5 LuauLintSlFastTimer|0.02\n" "8 LuauLintSlFastSensor|0.5\n")); - ensure_equals("LSL", three("default {\n" + UNSCOPED_INFO("LSL"); + CHECK(three("default {\n" " state_entry() {\n" " llListen(PUBLIC_CHANNEL, \"\", NULL_KEY, \"\");\n" " llListen(0, \"\", llGetOwner(), \"\");\n" @@ -720,20 +758,20 @@ namespace tut " llSensorRepeat(\"\", NULL_KEY, AGENT, 10.0, PI, 0.25);\n" " }\n" "}\n", - false), + false) == std::string("2 LSLSlCostlyListen\n" "4 LSLSlFastTimer|0.05\n" "6 LSLSlFastSensor|0.25\n")); } - template<> template<> - void object::test<18>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlStringBuild: a string grown in a loop; in SLua its pieces in a table", "[ALScriptLintPass]") { - set_test_name("SlStringBuild: a string joined to in a loop, from outside it, once for each string at its outermost loop, in both " - "languages; in SLua put in a table where it is a local of the block read in the loop by its appends alone"); - ensure("definitions: " + error, loaded); - ensure("builtins: " + error, lslLoaded); - ensure_equals("SLua", found("local s = \"\"\n" + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); + UNSCOPED_INFO("SLua"); + CHECK(found("local s = \"\"\n" "for i = 1, 3 do s ..= tostring(i) end\n" "local t = \"\"\n" "for i = 1, 3 do t = t .. i; print(t) end\n" @@ -743,23 +781,26 @@ namespace tut "for i = 1, 2 do for j = 1, 2 do u ..= \"y\" end end\n" "for i = 1, 2 do local v = \"\"; v ..= \"z\"; print(v) end\n" "print(s, u)\n", - "SlStringBuild", ALScriptProblem::Severity::Note), + "SlStringBuild", ALScriptProblem::Severity::Note) == std::string("1 LuauLintSlStringBuild|s\n" "3 LuauLintSlStringBuild|t\n" "5 LuauLintSlStringBuild|g\n" "7 LuauLintSlStringBuild|u\n")); - ensure_equals("in a table", 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), + UNSCOPED_INFO("in a table"); + CHECK(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")); - ensure_equals("named anew, indented", fixed("local function f()\n local s = \"\"\n local sParts = 1\n for i = 1, 3 do\n s = s .. i\n end\n" + UNSCOPED_INFO("named anew, indented"); + CHECK(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), + "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")); unfixed("local t = \"\"\nfor i = 1, 3 do t = t .. i; print(t) end\n", "LuauLintSlStringBuild"); 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"); - ensure_equals("LSL", found("default {\n" + UNSCOPED_INFO("LSL"); + CHECK(found("default {\n" " state_entry() {\n" " string s;\n" " integer i;\n" @@ -770,19 +811,19 @@ namespace tut " llOwnerSay(s + t);\n" " }\n" "}\n", - "SlStringBuild", ALScriptProblem::Severity::Note, false), + "SlStringBuild", ALScriptProblem::Severity::Note, false) == std::string("4 LSLSlStringBuild|s\n" "6 LSLSlStringBuild|t\n")); - ensure("the converter's note names it", std::string(ALLSLToSLua::lintOf("SluaStringBuild")) == "SlStringBuild"); + UNSCOPED_INFO("the converter's note names it"); + CHECK(std::string(ALLSLToSLua::lintOf("SluaStringBuild")) == "SlStringBuild"); } - template<> template<> - void object::test<19>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlRepeatedCall in SLua: a steady call made again kept in a local", "[ALScriptLintPass]") { - set_test_name("SlRepeatedCall in SLua: ll.GetOwner() and its kin called again in one function's body, not a nested one's; a " - "note, fixed as a local before the body's first statement that calls it"); - ensure("definitions: " + error, loaded); - ensure_equals("each", found("local function report()\n" + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); + UNSCOPED_INFO("each"); + CHECK(found("local function report()\n" " print(ll.GetOwner())\n" " if ll.GetOwner() == ll.GetKey() then print(ll.GetOwner()) end\n" " print(ll.GetKey())\n" @@ -790,22 +831,22 @@ namespace tut "print(ll.GetOwner(), llcompat.GetOwner())\n" "LLEvents:on(\"touch_start\", function() print(ll.GetScriptName()) print(ll.GetScriptName()) end)\n" "print(report)\n", - "SlRepeatedCall", ALScriptProblem::Severity::Note), + "SlRepeatedCall", ALScriptProblem::Severity::Note) == std::string("1 LuauLintSlRepeatedCall|ll.GetOwner()|3\n" "2 LuauLintSlRepeatedCall|ll.GetKey()|2\n" "6 LuauLintSlRepeatedCall|ll.GetScriptName()|2\n")); - ensure_equals("kept", 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), + UNSCOPED_INFO("kept"); + CHECK(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")); } - template<> template<> - void object::test<20>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlRepeatedCall in LSL: steady and pure calls repeated kept in a local", "[ALScriptLintPass]") { - set_test_name("SlRepeatedCall in LSL: a steady call, and a pure one given what the body never changes, declared by the body before; " - "not one given a loop's counter"); - ensure("builtins: " + error, lslLoaded); - ensure_equals("each", found("default {\n" + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); + UNSCOPED_INFO("each"); + CHECK(found("default {\n" " touch_start(integer n) {\n" " string s = llDetectedName(0);\n" " llOwnerSay((string)llGetOwner());\n" @@ -814,46 +855,48 @@ namespace tut " for (i = 0; i < 2; ++i) llOwnerSay(llGetSubString(s, i, i) + llGetSubString(s, i, i));\n" " }\n" "}\n", - "SlRepeatedCall", ALScriptProblem::Severity::Note, false), + "SlRepeatedCall", ALScriptProblem::Severity::Note, false) == std::string("3 LSLSlRepeatedCall|llGetOwner()|2\n" "4 LSLSlRepeatedCall|llToUpper(s)|2\n")); - ensure_equals("a steady one", 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), + UNSCOPED_INFO("a steady one"); + CHECK(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")); - ensure_equals("a pure one", 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), + UNSCOPED_INFO("a pure one"); + CHECK(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")); } - template<> template<> - void object::test<21>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: migration: migration lints and notes and llcompat calls left from LSL", "[ALScriptLintPass]") { - set_test_name("migration: a migration rule's lint, a converter's note and a call to llcompat are left from LSL; a performance " - "rule's lint and ll's own deprecation are not"); ALScriptProblem p; p.source = ALScriptProblem::Source::Lint; p.code = "SlZeroIndex"; - ensure("a migration rule's", ALScriptLintPass::migration(p)); + UNSCOPED_INFO("a migration rule's"); + CHECK(ALScriptLintPass::migration(p)); p.code = "SlSleepingCall"; - ensure("not a performance rule's", !ALScriptLintPass::migration(p)); + UNSCOPED_INFO("not a performance rule's"); + CHECK_FALSE(ALScriptLintPass::migration(p)); p.code = ""; p.key = "LuauLintDeprecatedMemberUse"; p.args = { "llcompat.Say" }; - ensure("llcompat's", ALScriptLintPass::migration(p)); + UNSCOPED_INFO("llcompat's"); + CHECK(ALScriptLintPass::migration(p)); p.args = { "ll.SetPrimitiveParams" }; - ensure("not ll's", !ALScriptLintPass::migration(p)); + UNSCOPED_INFO("not ll's"); + CHECK_FALSE(ALScriptLintPass::migration(p)); ALScriptProblem note; note.source = ALScriptProblem::Source::Assistant; note.key = "SluaNote"; - ensure("a note", ALScriptLintPass::migration(note)); + UNSCOPED_INFO("a note"); + CHECK(ALScriptLintPass::migration(note)); } - template<> template<> - void object::test<22>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlForIndexAssign: a numeric for's variable set in its body is a warning", "[ALScriptLintPass]") { - set_test_name("SlForIndexAssign: a numeric for's variable set in its body, plainly or by +=, a warning at each, in a function it makes " - "too; not a local of the same name in it, not a generic for's, not a while's; no fix"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local list = {\"a\", \"a\", \"b\"}\n" "local start = 1\n" "for i = 1, #list - 1 do\n" @@ -874,20 +917,18 @@ namespace tut "local n = 1\n" "while n < 3 do n = n + 1 end\n", "SlForIndexAssign", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("5 LuauLintSlForIndexAssign|i\n" "7 LuauLintSlForIndexAssign|i\n" "8 LuauLintSlForIndexAssign|i\n")); unfixed("for i = 1, 3 do i = 2 end\n", "LuauLintSlForIndexAssign"); } - template<> template<> - void object::test<23>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlGlobalAssign: a global set in a function and named elsewhere goes local", "[ALScriptLintPass]") { - set_test_name("SlGlobalAssign: a global first set inside a function and named by more than one statement of the top, a note, " - "declared local before the first; one function alone naming it left to Luau's GlobalUsedAsLocal; one set at the top " - "said as before"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string shared = "function init()\n" " count = 0\n" "end\n" @@ -896,34 +937,35 @@ namespace tut " print(count)\n" "end)\n" "init()\n"; - ensure_equals("set in one, named in another", found(shared, "SlGlobalAssign", ALScriptProblem::Severity::Note), + UNSCOPED_INFO("set in one, named in another"); + CHECK(found(shared, "SlGlobalAssign", ALScriptProblem::Severity::Note) == std::string("1 LuauLintSlGlobalInFunction|count\n")); - ensure_equals("declared before the first that names it", fixed(shared, "LuauLintSlGlobalInFunction", "Declare local count before it is first named", false), + UNSCOPED_INFO("declared before the first that names it"); + CHECK(fixed(shared, "LuauLintSlGlobalInFunction", "Declare local count before it is first named", false) == std::string("local count\n") + shared); - ensure("one function alone: Luau's", - found("LLEvents:on(\"touch_start\", function(detected)\n total = 1\n print(total)\nend)\n", "SlGlobalAssign", ALScriptProblem::Severity::Note) + 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); - ensure("set at the top too: said there, not here", - found("count = 0\nfunction bump() count += 1 end\nbump()\n", "SlGlobalAssign", ALScriptProblem::Severity::Note).find("LuauLintSlGlobalInFunction") == + UNSCOPED_INFO("set at the top too: said there, not here"); + REQUIRE(found("count = 0\nfunction bump() count += 1 end\nbump()\n", "SlGlobalAssign", ALScriptProblem::Severity::Note).find("LuauLintSlGlobalInFunction") == std::string::npos); - ensure("not where _G is", - found(std::string("print(_G)\n") + shared, "SlGlobalAssign", ALScriptProblem::Severity::Note).find("LuauLintSlGlobalInFunction") == std::string::npos); - ensure("set at the top in a block of it: not in a function", - found("if true then\n x = 1\nend\nfunction f() x = 2 end\nf()\nprint(x)\n", "SlGlobalAssign", ALScriptProblem::Severity::Note) + UNSCOPED_INFO("not where _G is"); + REQUIRE(found(std::string("print(_G)\n") + shared, "SlGlobalAssign", ALScriptProblem::Severity::Note).find("LuauLintSlGlobalInFunction") == std::string::npos); + UNSCOPED_INFO("set at the top in a block of it: not in a function"); + REQUIRE(found("if true then\n x = 1\nend\nfunction f() x = 2 end\nf()\nprint(x)\n", "SlGlobalAssign", ALScriptProblem::Severity::Note) .find("LuauLintSlGlobalInFunction") == std::string::npos); - ensure("made by function f() at the top, its own lint off: made there", - found("--!nolint SlGlobalFunction\nfunction onTouch() end\nfunction reset() onTouch = nil end\nreset()\nonTouch()\n", "SlGlobalAssign", + UNSCOPED_INFO("made by function f() at the top, its own lint off: made there"); + REQUIRE(found("--!nolint SlGlobalFunction\nfunction onTouch() end\nfunction reset() onTouch = nil end\nreset()\nonTouch()\n", "SlGlobalAssign", ALScriptProblem::Severity::Note) .find("LuauLintSlGlobalInFunction") == std::string::npos); } - template<> template<> - void object::test<24>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlIntegerPast32Bits: an integer literal past 0xFFFFFFFF reads as -1", "[ALScriptLintPass]") { - set_test_name("SlIntegerPast32Bits: a whole number past 0xFFFFFFFF, decimal or hexadecimal, read as -1 and as 1 with a minus before it; " - "not one within 32 bits, a float, or one in a string or a comment"); - ensure("builtins: " + error, lslLoaded); - ensure_equals("LSL", found("integer a = 4294967296;\n" + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); + UNSCOPED_INFO("LSL"); + CHECK(found("integer a = 4294967296;\n" "integer b = 0x100000000;\n" "integer c = -4294967296;\n" "integer d = 99999999999999999999999;\n" @@ -934,23 +976,21 @@ namespace tut " vector v = <4294967296, 0, 0>;\n" " llOwnerSay((string)(k + b + c + d + e + f + g) + (string)h + (string)i + j + (string)v);\n" "} }\n", - "SlIntegerPast32Bits", ALScriptProblem::Severity::Warning, false), + "SlIntegerPast32Bits", ALScriptProblem::Severity::Warning, false) == std::string("0 LSLSlIntegerPast32Bits|4294967296|-1\n" "1 LSLSlIntegerPast32Bits|0x100000000|-1\n" "2 LSLSlIntegerPast32Bits|-4294967296|1\n" "3 LSLSlIntegerPast32Bits|99999999999999999999999|-1\n" "7 LSLSlIntegerPast32Bits|4294967296|-1\n" "8 LSLSlIntegerPast32Bits|4294967296|-1\n")); - ensure_equals("not SLua's, whose numbers are doubles", found("local a = 4294967296\n", "SlIntegerPast32Bits", ALScriptProblem::Severity::Warning, true), + UNSCOPED_INFO("not SLua's, whose numbers are doubles"); + CHECK(found("local a = 4294967296\n", "SlIntegerPast32Bits", ALScriptProblem::Severity::Warning, true) == std::string()); } - template<> template<> - void object::test<25>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlNumberTruth beyond numbers: other types read as conditions are fixed", "[ALScriptLintPass]") { - set_test_name("SlNumberTruth beyond numbers: a uuid, a vector, a quaternion, a string, a list and an integer read as a condition, or " - "under not, each fixed as LSL asked it; not a boolean, a table that is no list, nor what may be nil though typed not -- " - "a table read by a key, a local declared with no value, or one given either"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local id = ll.GetOwner()\n" "local v = ll.GetPos()\n" "local r = ll.GetRot()\n" @@ -972,7 +1012,8 @@ namespace tut "local b = true\n" "if t or b or ll.GetOwner() then print(t) end\n", "SlNumberTruth", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("6 LuauLintSlUuidTruth|id|id.istruthy\n" "7 LuauLintSlVectorTruth|v|v ~= ZERO_VECTOR\n" "7 LuauLintSlStringTruth|s|s ~= \"\"\n" @@ -986,34 +1027,40 @@ namespace tut "11 LuauLintSlQuaternionTruthNot|r|r == ZERO_ROTATION\n" "11 LuauLintSlIntegerTruthNot|i|i == 0i\n" "19 LuauLintSlUuidTruth|ll.GetOwner()|ll.GetOwner().istruthy\n")); - ensure_equals("a uuid", fixed("local id = ll.GetOwner()\nif id then print(id) end\n", "LuauLintSlUuidTruth", "Write it id.istruthy", false), + UNSCOPED_INFO("a uuid"); + CHECK(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")); - ensure_equals("a uuid under not", fixed("if not ll.GetOwner() then print(1) end\n", "LuauLintSlUuidTruthNot", "Write it not ll.GetOwner().istruthy", false), + UNSCOPED_INFO("a uuid under not"); + CHECK(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")); - ensure_equals("a vector", fixed("local v = ll.GetPos()\nwhile v do break end\n", "LuauLintSlVectorTruth", "Write it v ~= ZERO_VECTOR", false), + UNSCOPED_INFO("a vector"); + CHECK(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")); - ensure_equals("a quaternion", fixed("local r = ll.GetRot()\nprint(not r)\n", "LuauLintSlQuaternionTruthNot", "Write it r == ZERO_ROTATION", false), + UNSCOPED_INFO("a quaternion"); + CHECK(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")); - ensure_equals("a string, an if-then-else bracketed first", fixed("local c = true\nif (if c then \"a\" else \"\") then print(c) end\n", - "LuauLintSlStringTruth", "Write it (if c then \"a\" else \"\") ~= \"\"", false), + UNSCOPED_INFO("a string, an if-then-else bracketed first"); + CHECK(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")); - ensure_equals("a list", fixed("local l = {1}\nif l then print(l) end\n", "LuauLintSlListTruth", "Write it #l > 0", false), + UNSCOPED_INFO("a list"); + CHECK(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")); - ensure_equals("a list under not", fixed("local l = {1}\nprint(not l)\n", "LuauLintSlListTruthNot", "Write it #l == 0", false), + UNSCOPED_INFO("a list under not"); + CHECK(fixed("local l = {1}\nprint(not l)\n", "LuauLintSlListTruthNot", "Write it #l == 0", false) == std::string("local l = {1}\nprint(#l == 0)\n")); - ensure_equals("an integer", fixed("local i = 5i\nif i then print(i) end\n", "LuauLintSlIntegerTruth", "Write it i ~= 0i", false), + UNSCOPED_INFO("an integer"); + CHECK(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")); - ensure_equals("a number read by a key: not said either", found("local counts: {[string]: number} = {}\nif counts[\"a\"] then print(1) end\n", - "SlNumberTruth", ALScriptProblem::Severity::Warning), + 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) == std::string()); } - template<> template<> - void object::test<26>() + TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlMustUse over SLua's own types: their makers and readers left unread", "[ALScriptLintPass]") { - set_test_name("SlMustUse over SLua's own: integer's, a uuid, a vector and a quaternion made by calling their names or create, " - "buffer's that read one or make one, and table.create, their answers unread; an integer's given back to it; not " - "buffer's that write one, nor an answer read"); - ensure("definitions: " + error, loaded); + UNSCOPED_INFO("definitions: " + error); + CHECK(loaded); const std::string said = found("local i = 5i\n" "local b = buffer.create(4)\n" "integer.add(i, 1)\n" @@ -1030,7 +1077,8 @@ namespace tut "buffer.fill(b, 0, 0)\n" "print(integer.add(i, 1), buffer.readu8(b, 0), vector(1, 2, 3))\n", "SlMustUse", ALScriptProblem::Severity::Warning); - ensure_equals("each", said, + UNSCOPED_INFO("each"); + CHECK(said == std::string("2 LuauLintSlMustUse|integer.add\n" "3 LuauLintSlMustUse|uuid\n" "4 LuauLintSlMustUse|uuid.create\n" @@ -1041,7 +1089,8 @@ namespace tut "9 LuauLintSlMustUse|buffer.len\n" "10 LuauLintSlMustUse|buffer.create\n" "11 LuauLintSlMustUse|table.create\n")); - ensure_equals("an integer's", fixed("local i = 5i\ninteger.band(i, 3i)\nprint(i)\n", "LuauLintSlMustUse", "Write it i = integer.band(...)", false), + UNSCOPED_INFO("an integer's"); + CHECK(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")); unfixed("vector(1, 2, 3)\n", "LuauLintSlMustUse"); } diff --git a/indra/alscript/tests/alscriptmessages_test.cpp b/indra/alscript/tests/alscriptmessages_test.cpp index c43ad4e4a9..ea3936b87d 100644 --- a/indra/alscript/tests/alscriptmessages_test.cpp +++ b/indra/alscript/tests/alscriptmessages_test.cpp @@ -26,145 +26,184 @@ #include "../core/alscriptmessages.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { - struct alscriptmessages_data + TEST_CASE("alscriptmessages: an LSL compiler's line gives its zero-based place and its level and words", "[alscriptmessages]") { - }; - typedef test_group alscriptmessages_group; - typedef alscriptmessages_group::object alscriptmessages_object; - tut::alscriptmessages_group alscriptmessages_instance("alscriptmessages"); - - template<> template<> - void alscriptmessages_object::test<1>() - { - set_test_name("an LSL compiler's line gives its place, its level and its words, zero-based as the viewer counts"); const ALScriptMessages::Place place = ALScriptMessages::readDiagnostic("(12, 4) : ERROR : Syntax error", false); - ensure_equals("the line as it came", place.line, 12); - ensure_equals("the column too", place.column, 4); - ensure("a column was given", place.hasColumn); - ensure_equals("the level", place.level, std::string("ERROR")); - ensure_equals("the words", place.message, std::string("Syntax error")); + UNSCOPED_INFO("the line as it came"); + CHECK(place.line == 12); + UNSCOPED_INFO("the column too"); + CHECK(place.column == 4); + UNSCOPED_INFO("a column was given"); + CHECK(place.hasColumn); + UNSCOPED_INFO("the level"); + CHECK(place.level == std::string("ERROR")); + UNSCOPED_INFO("the words"); + CHECK(place.message == std::string("Syntax error")); const ALScriptMessages::Place warned = ALScriptMessages::readDiagnostic("(3, 0) : WARNING : Unused variable x", false); - ensure_equals("a warning says so", warned.level, std::string("WARNING")); + UNSCOPED_INFO("a warning says so"); + CHECK(warned.level == std::string("WARNING")); // Anything else is an error with no place: "Math Error" comes so. const ALScriptMessages::Place bare = ALScriptMessages::readDiagnostic("Math Error", false); - ensure_equals("no place", bare.line, 0); - ensure("and none given", !bare.hasColumn && !bare.hasLine && place.hasLine); - ensure_equals("an error all the same", bare.level, std::string("ERROR")); - ensure_equals("the words as they came", bare.message, std::string("Math Error")); + UNSCOPED_INFO("no place"); + CHECK(bare.line == 0); + { + INFO("and none given"); + CHECK_FALSE(bare.hasColumn); + CHECK_FALSE(bare.hasLine); + CHECK(place.hasLine); + } + UNSCOPED_INFO("an error all the same"); + CHECK(bare.level == std::string("ERROR")); + UNSCOPED_INFO("the words as they came"); + CHECK(bare.message == std::string("Math Error")); } - template<> template<> - void alscriptmessages_object::test<2>() + TEST_CASE("alscriptmessages: Luau's line names its chunk and is moved from one-based to zero-based", "[alscriptmessages]") { - set_test_name("Luau's line names the chunk and counts from one, which the viewer counts from zero"); const ALScriptMessages::Place place = ALScriptMessages::readDiagnostic("script:14: unexpected symbol near 'end'", true); - ensure_equals("one off the line", place.line, 13); - ensure("no column", !place.hasColumn); - ensure_equals("an error", place.level, std::string("ERROR")); - ensure_equals("the words past the colon", place.message, std::string("unexpected symbol near 'end'")); + UNSCOPED_INFO("one off the line"); + CHECK(place.line == 13); + UNSCOPED_INFO("no column"); + CHECK_FALSE(place.hasColumn); + UNSCOPED_INFO("an error"); + CHECK(place.level == std::string("ERROR")); + UNSCOPED_INFO("the words past the colon"); + CHECK(place.message == std::string("unexpected symbol near 'end'")); // Line one stays line zero rather than going negative. - ensure_equals("the first line", ALScriptMessages::readDiagnostic("x:1: bad", true).line, 0); + UNSCOPED_INFO("the first line"); + CHECK(ALScriptMessages::readDiagnostic("x:1: bad", true).line == 0); // A line of neither form is no place: the whole is the words. const ALScriptMessages::Place other = ALScriptMessages::readDiagnostic("Math Error", true); - ensure_equals("nothing found", other.message, std::string("Math Error")); - ensure("no line named", !other.hasLine && place.hasLine); + UNSCOPED_INFO("nothing found"); + CHECK(other.message == std::string("Math Error")); + { + INFO("no line named"); + CHECK_FALSE(other.hasLine); + CHECK(place.hasLine); + } } - template<> template<> - void alscriptmessages_object::test<3>() + TEST_CASE("alscriptmessages: every line of a compile's answer in order", "[alscriptmessages]") { - set_test_name("every line of a compile's answer, in order"); LLSD errors; errors.append("(1, 2) : ERROR : first"); errors.append("(3, 4) : WARNING : second"); const std::vector places = ALScriptMessages::readDiagnostics(errors, false); - ensure_equals("both", places.size(), size_t(2)); - ensure_equals("in order", places[1].message, std::string("second")); - ensure_equals("with their places", places[1].line, 3); + UNSCOPED_INFO("both"); + REQUIRE(places.size() == size_t(2)); + UNSCOPED_INFO("in order"); + CHECK(places[1].message == std::string("second")); + UNSCOPED_INFO("with their places"); + CHECK(places[1].line == 3); } - template<> template<> - void alscriptmessages_object::test<4>() + TEST_CASE("alscriptmessages: a script with no default state is taken for Lua", "[alscriptmessages]") { - set_test_name("a script with no default state is taken for Lua"); - ensure("no state anywhere", ALScriptMessages::looksLikeLua("local x = 1\nprint(x)\n")); - ensure("a default state is LSL", !ALScriptMessages::looksLikeLua("default\n{\n state_entry()\n {\n }\n}\n")); - ensure("however it is spaced", !ALScriptMessages::looksLikeLua("default {")); - ensure("and after other code", !ALScriptMessages::looksLikeLua("integer n;\nstate two { }\ndefault {\n}\n")); + UNSCOPED_INFO("no state anywhere"); + CHECK(ALScriptMessages::looksLikeLua("local x = 1\nprint(x)\n")); + UNSCOPED_INFO("a default state is LSL"); + CHECK_FALSE(ALScriptMessages::looksLikeLua("default\n{\n state_entry()\n {\n }\n}\n")); + UNSCOPED_INFO("however it is spaced"); + CHECK_FALSE(ALScriptMessages::looksLikeLua("default {")); + UNSCOPED_INFO("and after other code"); + CHECK_FALSE(ALScriptMessages::looksLikeLua("integer n;\nstate two { }\ndefault {\n}\n")); } - template<> template<> - void alscriptmessages_object::test<5>() + TEST_CASE("alscriptmessages: a run-time error's header names the object and script and marks what follows", "[alscriptmessages]") { - set_test_name("a run-time error's header names the object and the script, and the lines that follow it belong with it"); ALScriptMessages::Header named; - ensure("a header", ALScriptMessages::readRuntimeHeader("Object [script:My Script] Script run-time error", named)); - ensure_equals("the object", named.object, std::string("Object")); - ensure_equals("the script, spaces and all", named.script, std::string("My Script")); - ensure("ending with the words marks it", ALScriptMessages::endsRuntimeError("Object [script:x] Script run-time error")); - ensure("an ordinary line is no header", !ALScriptMessages::readRuntimeHeader("Object: hello there", named)); - ensure("nor marked", !ALScriptMessages::endsRuntimeError("Object: hello there")); + UNSCOPED_INFO("a header"); + REQUIRE(ALScriptMessages::readRuntimeHeader("Object [script:My Script] Script run-time error", named)); + UNSCOPED_INFO("the object"); + CHECK(named.object == std::string("Object")); + UNSCOPED_INFO("the script, spaces and all"); + CHECK(named.script == std::string("My Script")); + UNSCOPED_INFO("ending with the words marks it"); + CHECK(ALScriptMessages::endsRuntimeError("Object [script:x] Script run-time error")); + UNSCOPED_INFO("an ordinary line is no header"); + CHECK_FALSE(ALScriptMessages::readRuntimeHeader("Object: hello there", named)); + UNSCOPED_INFO("nor marked"); + CHECK_FALSE(ALScriptMessages::endsRuntimeError("Object: hello there")); // The words alone, without the brackets, are not a header. - ensure("no script named", !ALScriptMessages::readRuntimeHeader("Script run-time error", named)); + UNSCOPED_INFO("no script named"); + CHECK_FALSE(ALScriptMessages::readRuntimeHeader("Script run-time error", named)); } - template<> template<> - void alscriptmessages_object::test<6>() + TEST_CASE("alscriptmessages: where a run-time error happened is read by the VM that said it", "[alscriptmessages]") { - set_test_name("where a run-time error happened, from the lines after the header, by the VM that said it"); ALScriptMessages::Location where; const std::vector luau = { "Object [script:s] Script run-time error", "s:7: attempt to index nil", "stack traceback:" }; - ensure("Luau's", ALScriptMessages::readRuntimeLocation(luau, true, where)); - ensure_equals("one off its line", where.line, 6); - ensure_equals("no column", where.column, -1); - ensure_equals("the words", where.message, std::string("attempt to index nil")); + UNSCOPED_INFO("Luau's"); + REQUIRE(ALScriptMessages::readRuntimeLocation(luau, true, where)); + UNSCOPED_INFO("one off its line"); + CHECK(where.line == 6); + UNSCOPED_INFO("no column"); + CHECK(where.column == -1); + UNSCOPED_INFO("the words"); + CHECK(where.message == std::string("attempt to index nil")); const std::vector lsl = { "Object [script:s] Script run-time error", "(5, 2) : ERROR : Stack-Heap Collision" }; - ensure("LSL's", ALScriptMessages::readRuntimeLocation(lsl, false, where)); - ensure_equals("the line as it came", where.line, 5); - ensure_equals("and the column", where.column, 2); - ensure_equals("the words past the level", where.message, std::string("Stack-Heap Collision")); + UNSCOPED_INFO("LSL's"); + CHECK(ALScriptMessages::readRuntimeLocation(lsl, false, where)); + UNSCOPED_INFO("the line as it came"); + CHECK(where.line == 5); + UNSCOPED_INFO("and the column"); + CHECK(where.column == 2); + UNSCOPED_INFO("the words past the level"); + CHECK(where.message == std::string("Stack-Heap Collision")); // Nothing says where: the caller falls back to the first line // that is not the header. const std::vector nowhere = { "Object [script:s] Script run-time error", "Math Error" }; - ensure("nothing found", !ALScriptMessages::readRuntimeLocation(nowhere, false, where)); + UNSCOPED_INFO("nothing found"); + CHECK_FALSE(ALScriptMessages::readRuntimeLocation(nowhere, false, where)); // An LSL error read as Luau's is not read by it. - ensure("the wrong VM finds nothing", !ALScriptMessages::readRuntimeLocation(lsl, true, where) || where.line != 5); + UNSCOPED_INFO("the wrong VM finds nothing"); + CHECK((!ALScriptMessages::readRuntimeLocation(lsl, true, where) || where.line != 5)); } - template<> template<> - void alscriptmessages_object::test<7>() + TEST_CASE("alscriptmessages: a Luau traceback frame gives its chunk and line and nothing else is one", "[alscriptmessages]") { - set_test_name("a Luau traceback's frames, each its chunk and its line, and nothing else read as one"); ALScriptMessages::Frame frame; - ensure("a bare frame", ALScriptMessages::readStackFrame("s:7", frame)); - ensure_equals("its chunk", frame.chunk, std::string("s")); - ensure_equals("its line, one off", frame.line, 6); - ensure("indented, with the function", ALScriptMessages::readStackFrame(" My Script:12 function counter", frame)); - ensure_equals("a script's name, spaces and all", frame.chunk, std::string("My Script")); - ensure_equals("that line", frame.line, 11); - ensure("as Lua quotes a chunk", ALScriptMessages::readStackFrame("[string \"My Script\"]:30: in function 'tick'", frame)); - ensure_equals("the quoted name, spaces and all", frame.chunk, std::string("My Script")); - ensure_equals("and its line", frame.line, 29); - ensure("the heading is no frame", !ALScriptMessages::readStackFrame("stack traceback:", frame)); - ensure("nor a C function", !ALScriptMessages::readStackFrame("[C] function error", frame)); - ensure("nor the error's own line", !ALScriptMessages::readStackFrame("s:7: attempt to index nil", frame)); - ensure("nor words", !ALScriptMessages::readStackFrame("Math Error", frame)); - ensure("nor a line zero", !ALScriptMessages::readStackFrame("s:0", frame)); + UNSCOPED_INFO("a bare frame"); + REQUIRE(ALScriptMessages::readStackFrame("s:7", frame)); + UNSCOPED_INFO("its chunk"); + CHECK(frame.chunk == std::string("s")); + UNSCOPED_INFO("its line, one off"); + CHECK(frame.line == 6); + UNSCOPED_INFO("indented, with the function"); + CHECK(ALScriptMessages::readStackFrame(" My Script:12 function counter", frame)); + UNSCOPED_INFO("a script's name, spaces and all"); + CHECK(frame.chunk == std::string("My Script")); + UNSCOPED_INFO("that line"); + CHECK(frame.line == 11); + UNSCOPED_INFO("as Lua quotes a chunk"); + CHECK(ALScriptMessages::readStackFrame("[string \"My Script\"]:30: in function 'tick'", frame)); + UNSCOPED_INFO("the quoted name, spaces and all"); + CHECK(frame.chunk == std::string("My Script")); + UNSCOPED_INFO("and its line"); + CHECK(frame.line == 29); + UNSCOPED_INFO("the heading is no frame"); + CHECK_FALSE(ALScriptMessages::readStackFrame("stack traceback:", frame)); + UNSCOPED_INFO("nor a C function"); + CHECK_FALSE(ALScriptMessages::readStackFrame("[C] function error", frame)); + UNSCOPED_INFO("nor the error's own line"); + CHECK_FALSE(ALScriptMessages::readStackFrame("s:7: attempt to index nil", frame)); + UNSCOPED_INFO("nor words"); + CHECK_FALSE(ALScriptMessages::readStackFrame("Math Error", frame)); + UNSCOPED_INFO("nor a line zero"); + CHECK_FALSE(ALScriptMessages::readStackFrame("s:0", frame)); } - template<> template<> - void alscriptmessages_object::test<8>() + TEST_CASE("alscriptmessages: a SLua fault as the simulator says it: its place and the script's frames", "[alscriptmessages]") { // As the simulator's executor says a fault: the error as the VM // raised it, then lua_debugtrace's frames -- the script loaded as // "=lua_script", so its chunk reads `lua_script`; a C function as // `[C]`; a deep stack's middle folded -- each on a line of its // own (slua Executor/src/Script.cpp, VM/src/ldebug.cpp). - set_test_name("a SLua fault as the simulator says it: where it happened, and each frame of the script"); const std::vector said = { "Object [script:Counter] Script run-time error", "lua_script:12: attempt to index nil with 'field'", "[C] function error", @@ -172,79 +211,119 @@ namespace tut "... (+4 frames)", "lua_script:30" }; ALScriptMessages::Header named; - ensure("the header", ALScriptMessages::readRuntimeHeader(said[0], named)); - ensure_equals("names the script", named.script, std::string("Counter")); + UNSCOPED_INFO("the header"); + REQUIRE(ALScriptMessages::readRuntimeHeader(said[0], named)); + UNSCOPED_INFO("names the script"); + CHECK(named.script == std::string("Counter")); ALScriptMessages::Location where; - ensure("where", ALScriptMessages::readRuntimeLocation(said, true, where)); - ensure_equals("its line", where.line, 11); - ensure_equals("its words", where.message, std::string("attempt to index nil with 'field'")); + UNSCOPED_INFO("where"); + REQUIRE(ALScriptMessages::readRuntimeLocation(said, true, where)); + UNSCOPED_INFO("its line"); + CHECK(where.line == 11); + UNSCOPED_INFO("its words"); + CHECK(where.message == std::string("attempt to index nil with 'field'")); std::vector frames; for (size_t i = 1; i < said.size(); ++i) { ALScriptMessages::Frame frame; if (ALScriptMessages::readStackFrame(said[i], frame)) { - ensure_equals("the script's chunk", frame.chunk, std::string("lua_script")); + UNSCOPED_INFO("the script's chunk"); + REQUIRE(frame.chunk == std::string("lua_script")); frames.push_back(frame.line); } } - ensure_equals("the script's two frames, and nothing else", frames.size(), size_t(2)); - ensure_equals("the function's", frames[0], 11); - ensure_equals("the one that called it", frames[1], 29); + UNSCOPED_INFO("the script's two frames, and nothing else"); + REQUIRE(frames.size() == size_t(2)); + UNSCOPED_INFO("the function's"); + CHECK(frames[0] == 11); + UNSCOPED_INFO("the one that called it"); + CHECK(frames[1] == 29); } - template<> template<> - void alscriptmessages_object::test<9>() + TEST_CASE("alscriptmessages: continuesRuntimeError tells a run-time error's lines from a script's own", "[alscriptmessages]") { - set_test_name("what goes on with a run-time error after its words, and what a script says besides"); for (const char* goes_on : { "lua_script:12: attempt to index nil with 'field'", "stack traceback:", "[C] function error", "lua_script:12 function tick", " lua_script:30", "... (+4 frames)", "(12, 3) : ERROR : Stack-Heap Collision" }) { - ensure(std::string("goes on: ") + goes_on, ALScriptMessages::continuesRuntimeError(goes_on)); + UNSCOPED_INFO(std::string("goes on: ") + goes_on); + REQUIRE(ALScriptMessages::continuesRuntimeError(goes_on)); } for (const char* besides : { "hello", "Counter: 12 visitors", "", " ", "Object [script:Counter] Script run-time error" }) { - ensure(std::string("its own: ") + besides, !ALScriptMessages::continuesRuntimeError(besides)); + UNSCOPED_INFO(std::string("its own: ") + besides); + REQUIRE_FALSE(ALScriptMessages::continuesRuntimeError(besides)); } } - template<> template<> - void alscriptmessages_object::test<10>() + TEST_CASE("alscriptmessages: a line in the other compiler's form is read in that form", "[alscriptmessages]") { - set_test_name("a line in the other compiler's form is read in that form: an LSL script the grid compiles for Luau's VM may be " - "answered in either"); const ALScriptMessages::Place luau = ALScriptMessages::readDiagnostic("script:7: bad thing", false); - ensure("Luau's, for LSL", luau.hasLine && luau.line == 6 && !luau.hasColumn && luau.message == "bad thing"); + { + INFO("Luau's, for LSL"); + CHECK(luau.hasLine); + CHECK(luau.line == 6); + CHECK_FALSE(luau.hasColumn); + CHECK(luau.message == "bad thing"); + } const ALScriptMessages::Place lsl = ALScriptMessages::readDiagnostic("(4, 2) : ERROR : Syntax error", true); - ensure("LSL's, for SLua", lsl.hasLine && lsl.line == 4 && lsl.hasColumn && lsl.column == 2 && lsl.message == "Syntax error"); + { + INFO("LSL's, for SLua"); + CHECK(lsl.hasLine); + CHECK(lsl.line == 4); + CHECK(lsl.hasColumn); + CHECK(lsl.column == 2); + CHECK(lsl.message == "Syntax error"); + } } - template<> template<> - void alscriptmessages_object::test<11>() + TEST_CASE("alscriptmessages: Tailslide's line and the places it names count from one; its level as said", "[alscriptmessages]") { - set_test_name("Tailslide's line, as the grid's LSL compiler for Luau's VM says it: its line and the places its words name " - "count from one, which the viewer counts from zero; its level as it says it"); const std::string said = "Line 320: WARN: Declaration of `activeRequest' in this scope shadows previous declaration at (345, 1)"; for (const bool lua : { false, true }) { const ALScriptMessages::Place place = ALScriptMessages::readDiagnostic(said, lua); - ensure("a line named", place.hasLine && !place.hasColumn); - ensure_equals("one off the line", place.line, 319); - ensure_equals("its level", place.level, std::string("WARN")); - ensure_equals("the words past the level", - place.message, std::string("Declaration of `activeRequest' in this scope shadows previous declaration at (345, 1)")); - ensure_equals("the place the words name", place.mentions.size(), size_t(1)); + { + INFO("a line named"); + REQUIRE(place.hasLine); + REQUIRE_FALSE(place.hasColumn); + } + UNSCOPED_INFO("one off the line"); + REQUIRE(place.line == 319); + UNSCOPED_INFO("its level"); + REQUIRE(place.level == std::string("WARN")); + UNSCOPED_INFO("the words past the level"); + REQUIRE(place.message == std::string("Declaration of `activeRequest' in this scope shadows previous declaration at (345, 1)")); + UNSCOPED_INFO("the place the words name"); + REQUIRE(place.mentions.size() == size_t(1)); const ALScriptMessages::Mention& named = place.mentions.front(); - ensure_equals("where it stands", place.message.substr(named.at, named.length), std::string("(345, 1)")); - ensure("one off each", named.line == 344 && named.column == 0); + UNSCOPED_INFO("where it stands"); + REQUIRE(place.message.substr(named.at, named.length) == std::string("(345, 1)")); + { + INFO("one off each"); + REQUIRE(named.line == 344); + REQUIRE(named.column == 0); + } } const ALScriptMessages::Place error = ALScriptMessages::readDiagnostic("Line 1: ERROR: Duplicate declaration of `x'; previously " "declared at (1, 9). And (2, 3).", false); - ensure("an error on the first line", error.hasLine && error.line == 0 && error.level == "ERROR"); - ensure_equals("each place named", error.mentions.size(), size_t(2)); - ensure("in order", error.mentions[0].line == 0 && error.mentions[0].column == 8 && error.mentions[1].line == 1 && - error.mentions[1].column == 2); + { + INFO("an error on the first line"); + CHECK(error.hasLine); + CHECK(error.line == 0); + CHECK(error.level == "ERROR"); + } + UNSCOPED_INFO("each place named"); + REQUIRE(error.mentions.size() == size_t(2)); + { + INFO("in order"); + CHECK(error.mentions[0].line == 0); + CHECK(error.mentions[0].column == 8); + CHECK(error.mentions[1].line == 1); + CHECK(error.mentions[1].column == 2); + } // Words with a place in them, in LSL's own form, are LSL's. - ensure("no Tailslide in LSL's", ALScriptMessages::readDiagnostic("(4, 2) : ERROR : at (1, 1)", false).mentions.empty()); + UNSCOPED_INFO("no Tailslide in LSL's"); + REQUIRE(ALScriptMessages::readDiagnostic("(4, 2) : ERROR : at (1, 1)", false).mentions.empty()); } } diff --git a/indra/alscript/tests/alscriptnameindex_test.cpp b/indra/alscript/tests/alscriptnameindex_test.cpp index 0b71961279..9dc7d20fcc 100644 --- a/indra/alscript/tests/alscriptnameindex_test.cpp +++ b/indra/alscript/tests/alscriptnameindex_test.cpp @@ -26,11 +26,11 @@ #include "../core/alscriptnameindex.h" -#include "../test/lltut.h" +#include #include -namespace tut +namespace { struct alscriptnameindex_data { @@ -42,56 +42,79 @@ namespace tut } }; - typedef test_group alscriptnameindex_group; - typedef alscriptnameindex_group::object alscriptnameindex_object; - alscriptnameindex_group alscriptnameindex_instance("alscriptnameindex"); - - template<> template<> - void alscriptnameindex_object::test<1>() + TEST_CASE_METHOD(alscriptnameindex_data, "alscriptnameindex: once built an item's changes move what its names give and the generation", "[alscriptnameindex]") { - set_test_name("built once; an item renamed, added, taken away or moved out of reach changes what its names give, and the generation with it"); ALScriptNameIndex index; - ensure("not built, nothing given", !index.built() && index.named("lib").empty()); + { + INFO("not built, nothing given"); + CHECK_FALSE(index.built()); + REQUIRE(index.named("lib").empty()); + } index.changed(id(1), std::string("lib")); - ensure("nothing kept before it is built", index.named("lib").empty()); + UNSCOPED_INFO("nothing kept before it is built"); + REQUIRE(index.named("lib").empty()); index.build({ { id(1), "lib" }, { id(2), "lib" }, { id(3), "door" } }); - ensure("two of a name, in order", index.named("lib") == std::vector({ id(1), id(2) }) && index.size() == 3); + { + INFO("two of a name, in order"); + CHECK(index.named("lib") == std::vector({ id(1), id(2) })); + REQUIRE(index.size() == 3); + } U32 generation = index.generation(); index.changed(id(3), std::string("door")); - ensure("the same said again: nothing moves", index.generation() == generation); + UNSCOPED_INFO("the same said again: nothing moves"); + CHECK(index.generation() == generation); index.changed(id(9), std::nullopt); - ensure("an item never listed gone: nothing moves", index.generation() == generation); + UNSCOPED_INFO("an item never listed gone: nothing moves"); + CHECK(index.generation() == generation); index.changed(id(2), std::string("util")); - ensure("renamed: under its new name only", index.named("lib") == std::vector({ id(1) }) && index.named("util") == std::vector({ id(2) })); - ensure("and the generation moved", index.generation() != generation); + { + INFO("renamed: under its new name only"); + CHECK(index.named("lib") == std::vector({ id(1) })); + CHECK(index.named("util") == std::vector({ id(2) })); + } + UNSCOPED_INFO("and the generation moved"); + CHECK(index.generation() != generation); generation = index.generation(); index.changed(id(4), std::string("lib")); - ensure("added, after the rest", index.named("lib") == std::vector({ id(1), id(4) })); + UNSCOPED_INFO("added, after the rest"); + CHECK(index.named("lib") == std::vector({ id(1), id(4) })); index.changed(id(1), std::nullopt); - ensure("taken away, or into the trash", index.named("lib") == std::vector({ id(4) }) && index.size() == 3); + { + INFO("taken away, or into the trash"); + CHECK(index.named("lib") == std::vector({ id(4) })); + REQUIRE(index.size() == 3); + } index.changed(id(3), std::nullopt); - ensure("a name with nothing left gives nothing", index.named("door").empty()); + UNSCOPED_INFO("a name with nothing left gives nothing"); + REQUIRE(index.named("door").empty()); generation = index.generation(); index.forget(); - ensure("forgotten: built again at the next question", !index.built() && index.generation() != generation); + { + INFO("forgotten: built again at the next question"); + CHECK_FALSE(index.built()); + CHECK(index.generation() != generation); + } } - template<> template<> - void alscriptnameindex_object::test<2>() + TEST_CASE_METHOD(alscriptnameindex_data, "alscriptnameindex: forEach visits every listed item once with its current name", "[alscriptnameindex]") { - set_test_name("every item listed is visited once with its name, as it is now"); ALScriptNameIndex index; index.build({ { id(1), "lib" }, { id(2), "lib" }, { id(3), "door" } }); index.changed(id(2), std::string("util")); index.changed(id(3), std::nullopt); std::map seen; - index.forEach([&seen](const LLUUID& item, const std::string& name) { ensure("once each", seen.emplace(item, name).second); }); - ensure_equals("what is listed", seen.size(), size_t(2)); - ensure("by its name now", seen[id(1)] == "lib" && seen[id(2)] == "util"); + index.forEach([&seen](const LLUUID& item, const std::string& name) { UNSCOPED_INFO("once each"); REQUIRE(seen.emplace(item, name).second); }); + UNSCOPED_INFO("what is listed"); + REQUIRE(seen.size() == size_t(2)); + { + INFO("by its name now"); + CHECK(seen[id(1)] == "lib"); + CHECK(seen[id(2)] == "util"); + } } } diff --git a/indra/alscript/tests/alscriptoutlinepairs_test.cpp b/indra/alscript/tests/alscriptoutlinepairs_test.cpp index 85ef806058..f89add0759 100644 --- a/indra/alscript/tests/alscriptoutlinepairs_test.cpp +++ b/indra/alscript/tests/alscriptoutlinepairs_test.cpp @@ -26,9 +26,9 @@ #include "../core/alscriptoutlinepairs.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alscriptoutlinepairs_data { @@ -49,29 +49,25 @@ namespace tut return entry; } }; - typedef test_group alscriptoutlinepairs_group; - typedef alscriptoutlinepairs_group::object alscriptoutlinepairs_object; - alscriptoutlinepairs_group alscriptoutlinepairs_instance("alscriptoutlinepairs"); - template<> template<> - void alscriptoutlinepairs_object::test<1>() + TEST_CASE_METHOD(alscriptoutlinepairs_data, "alscriptoutlinepairs: functions pair by name wherever they are; lone ones and globals do not", "[alscriptoutlinepairs]") { - set_test_name("functions paired by name, wherever each now is; one on a side alone, and a global, not paired"); typedef ALScriptSymbolKind K; const std::vector left = { symbol("count", K::Variable, 0, 0), symbol("a", K::Function, 1, 4), symbol("b", K::Function, 5, 9), symbol("gone", K::Function, 10, 12) }; const std::vector right = { symbol("count", K::Variable, 0, 0), symbol("b", K::Function, 1, 5), symbol("made", K::Function, 6, 7), symbol("a", K::Function, 8, 12) }; const std::vector pairs = ALScriptOutlinePairs::pair(left, right); - ensure_equals("two", pairs.size(), size_t(2)); - ensure("a with a, in the left's order", pairs[0] == Pair{ 1, 4, 8, 12 }); - ensure("b with b", pairs[1] == Pair{ 5, 9, 1, 5 }); + UNSCOPED_INFO("two"); + REQUIRE(pairs.size() == size_t(2)); + UNSCOPED_INFO("a with a, in the left's order"); + CHECK(pairs[0] == Pair{ 1, 4, 8, 12 }); + UNSCOPED_INFO("b with b"); + CHECK(pairs[1] == Pair{ 5, 9, 1, 5 }); } - template<> template<> - void alscriptoutlinepairs_object::test<2>() + TEST_CASE_METHOD(alscriptoutlinepairs_data, "alscriptoutlinepairs: events by state; inner functions by parent; repeats in order; kinds match", "[alscriptoutlinepairs]") { - set_test_name("an event by its state, a function by the function it is in; of a name twice, the first with the first; a function not an event of the name"); typedef ALScriptSymbolKind K; const std::vector left = { symbol("default", K::State, 0, 9), symbol("state_entry", K::Event, 1, 3, 1), symbol("touch_start", K::Event, 4, 8, 1), @@ -84,22 +80,33 @@ namespace tut symbol("inner", K::Function, 16, 18, 1), symbol("touch_start", K::Event, 20, 21), }; const std::vector pairs = ALScriptOutlinePairs::pair(left, right); - ensure_equals("six", pairs.size(), size_t(6)); - ensure("default with default", pairs[0] == Pair{ 0, 9, 5, 12 }); - ensure("its state_entry with its own, not running's", pairs[1] == Pair{ 1, 3, 6, 8 }); - ensure("running with running, and its state_entry", pairs[2] == Pair{ 10, 14, 0, 4 } && pairs[3] == Pair{ 11, 13, 1, 3 }); - ensure("the first helper with the first", pairs[4] == Pair{ 15, 20, 13, 14 }); - ensure("inner by the helper it is in, the first; the second helper with the second", pairs[5] == Pair{ 21, 22, 15, 19 }); + UNSCOPED_INFO("six"); + REQUIRE(pairs.size() == size_t(6)); + UNSCOPED_INFO("default with default"); + CHECK(pairs[0] == Pair{ 0, 9, 5, 12 }); + UNSCOPED_INFO("its state_entry with its own, not running's"); + CHECK(pairs[1] == Pair{ 1, 3, 6, 8 }); + { + INFO("running with running, and its state_entry"); + CHECK(pairs[2] == Pair{ 10, 14, 0, 4 }); + CHECK(pairs[3] == Pair{ 11, 13, 1, 3 }); + } + UNSCOPED_INFO("the first helper with the first"); + CHECK(pairs[4] == Pair{ 15, 20, 13, 14 }); + UNSCOPED_INFO("inner by the helper it is in, the first; the second helper with the second"); + CHECK(pairs[5] == Pair{ 21, 22, 15, 19 }); } - template<> template<> - void alscriptoutlinepairs_object::test<3>() + TEST_CASE_METHOD(alscriptoutlinepairs_data, "alscriptoutlinepairs: a span ending at the start of a line ends on the line before", "[alscriptoutlinepairs]") { - set_test_name("a span ending at the start of a line ends on the line before"); ALScriptOutlineEntry left = symbol("a", ALScriptSymbolKind::Function, 2, 6); ALScriptOutlineEntry right = symbol("a", ALScriptSymbolKind::Function, 3, 8); left.span.endColumn = 0; const std::vector pairs = ALScriptOutlinePairs::pair({ left }, { right }); - ensure("its last line the one before", pairs.size() == 1 && pairs[0] == Pair{ 2, 5, 3, 8 }); + { + INFO("its last line the one before"); + REQUIRE(pairs.size() == 1); + CHECK(pairs[0] == Pair{ 2, 5, 3, 8 }); + } } } diff --git a/indra/alscript/tests/alscriptsnapshot_test.cpp b/indra/alscript/tests/alscriptsnapshot_test.cpp index 445e7fd98a..035f15ac54 100644 --- a/indra/alscript/tests/alscriptsnapshot_test.cpp +++ b/indra/alscript/tests/alscriptsnapshot_test.cpp @@ -25,9 +25,9 @@ #include "../preprocessor/alscriptsnapshot.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alscriptsnapshot_data { @@ -41,14 +41,8 @@ namespace tut } }; - typedef test_group alscriptsnapshot_group; - typedef alscriptsnapshot_group::object alscriptsnapshot_object; - alscriptsnapshot_group alscriptsnapshot_instance("alscriptsnapshot"); - - template<> template<> - void alscriptsnapshot_object::test<1>() + TEST_CASE_METHOD(alscriptsnapshot_data, "alscriptsnapshot: a run uses answers and reports misses and notes each unknown name once", "[alscriptsnapshot]") { - set_test_name("a run takes what was answered, says what was not found, and notes each name nobody looked up once"); ALScriptSnapshot snapshot; snapshot.options().fileName = "main.lsl"; ALPreprocessor::Include lib; @@ -59,19 +53,27 @@ namespace tut snapshot.answer(ask("gone.lsl"), ALPreprocessor::Found::No); const ALPreprocessor::Result made = snapshot.run( "#include \"lib.lsl\"\n#include \"gone.lsl\"\n#include \"new.lsl\"\n#include \"new.lsl\"\ndefault { state_entry() { helper(); } }\n"); - ensure("the answered one in", made.text.find("integer helper()") != std::string::npos); - ensure("the missing one said", std::any_of(made.problems.begin(), made.problems.end(), [](const ALScriptProblem& p) { return p.key == "PreprocIncludeNotFound"; })); + UNSCOPED_INFO("the answered one in"); + REQUIRE(made.text.find("integer helper()") != std::string::npos); + UNSCOPED_INFO("the missing one said"); + CHECK(std::any_of(made.problems.begin(), made.problems.end(), [](const ALScriptProblem& p) { return p.key == "PreprocIncludeNotFound"; })); const std::vector missed = snapshot.missed(); - ensure("the unknown one noted once", missed.size() == 1 && missed[0].name == "new.lsl"); - ensure("taken away by the asking", snapshot.missed().empty()); + { + INFO("the unknown one noted once"); + REQUIRE(missed.size() == 1); + CHECK(missed[0].name == "new.lsl"); + } + UNSCOPED_INFO("taken away by the asking"); + REQUIRE(snapshot.missed().empty()); } - template<> template<> - void alscriptsnapshot_object::test<2>() + TEST_CASE_METHOD(alscriptsnapshot_data, "alscriptsnapshot: keyOf is the name and who asks and whether it is a require", "[alscriptsnapshot]") { - set_test_name("a name's key is its name, who asks, and whether it is a require"); - ensure("the same", ALScriptSnapshot::keyOf(ask("a")) == ALScriptSnapshot::keyOf(ask("a"))); - ensure("another asking", ALScriptSnapshot::keyOf(ask("a", "disk:/x.lsl")) != ALScriptSnapshot::keyOf(ask("a"))); - ensure("a require", ALScriptSnapshot::keyOf(ask("a", std::string(), true)) != ALScriptSnapshot::keyOf(ask("a"))); + UNSCOPED_INFO("the same"); + CHECK(ALScriptSnapshot::keyOf(ask("a")) == ALScriptSnapshot::keyOf(ask("a"))); + UNSCOPED_INFO("another asking"); + CHECK(ALScriptSnapshot::keyOf(ask("a", "disk:/x.lsl")) != ALScriptSnapshot::keyOf(ask("a"))); + UNSCOPED_INFO("a require"); + CHECK(ALScriptSnapshot::keyOf(ask("a", std::string(), true)) != ALScriptSnapshot::keyOf(ask("a"))); } } diff --git a/indra/alscript/tests/alscriptstack_test.cpp b/indra/alscript/tests/alscriptstack_test.cpp index 74771fb7f5..ea56f77974 100644 --- a/indra/alscript/tests/alscriptstack_test.cpp +++ b/indra/alscript/tests/alscriptstack_test.cpp @@ -28,7 +28,7 @@ #include "../preprocessor/alpreprocessor.h" -#include "../test/lltut.h" +#include #include #include @@ -56,24 +56,15 @@ namespace } } -namespace tut +namespace { - struct alscriptstack_data - { - }; - - typedef test_group alscriptstack_group; - typedef alscriptstack_group::object alscriptstack_object; - alscriptstack_group alscriptstack_instance("alscriptstack"); - - template<> template<> - void alscriptstack_object::test<1>() + TEST_CASE("alscriptstack: the work runs on the asking thread and what it throws comes back", "[alscriptstack]") { - set_test_name("the work runs, on the thread that asked, and what it throws comes back"); const std::thread::id asking = std::this_thread::get_id(); std::thread::id ran; alScriptOnLargeStack([&]() { ran = std::this_thread::get_id(); }); - ensure("on the same thread", ran == asking); + UNSCOPED_INFO("on the same thread"); + CHECK(ran == asking); bool caught = false; try @@ -84,17 +75,17 @@ namespace tut { caught = std::string(e.what()) == "from below"; } - ensure("thrown again once back", caught); + UNSCOPED_INFO("thrown again once back"); + CHECK(caught); } - template<> template<> - void alscriptstack_object::test<2>() + TEST_CASE("alscriptstack: a pool thread with a half megabyte stack goes eight megabytes deep", "[alscriptstack]") { - set_test_name("a pool's thread, whose own stack is half a megabyte on a Mac, goes eight megabytes deep"); int got = -1; std::thread worker([&]() { alScriptOnLargeStack([&]() { got = descend(8000); }); }); worker.join(); - ensure_equals("all the way down and back", got, 8000); + UNSCOPED_INFO("all the way down and back"); + CHECK(got == 8000); // And the preprocessor as deep as its own bounds let a script go, // on such a thread. @@ -117,13 +108,15 @@ namespace tut }); }); preprocessing.join(); - ensure("expanded, within the bound", !result.overran && result.text.find("1;") != std::string::npos); + { + INFO("expanded, within the bound"); + CHECK_FALSE(result.overran); + REQUIRE(result.text.find("1;") != std::string::npos); + } } - template<> template<> - void alscriptstack_object::test<3>() + TEST_CASE("alscriptstack: the stack is kept between jobs and a nested job runs where it stands", "[alscriptstack]") { - set_test_name("the thread's stack kept from one job to the next, and a job begun inside another run on it where it stands"); int first = -1, second = -1, nested = -1; bool threw = false; std::thread worker([&]() { @@ -143,9 +136,13 @@ namespace tut }); }); worker.join(); - ensure_equals("the first all the way", first, 8000); - ensure("a throw between", threw); - ensure_equals("the second, on the same stack", second, 4000); - ensure_equals("and one inside it", nested, 3000); + UNSCOPED_INFO("the first all the way"); + CHECK(first == 8000); + UNSCOPED_INFO("a throw between"); + CHECK(threw); + UNSCOPED_INFO("the second, on the same stack"); + CHECK(second == 4000); + UNSCOPED_INFO("and one inside it"); + CHECK(nested == 3000); } } diff --git a/indra/alscript/tests/alscripttempfiles_test.cpp b/indra/alscript/tests/alscripttempfiles_test.cpp index eda560df85..b16871f137 100644 --- a/indra/alscript/tests/alscripttempfiles_test.cpp +++ b/indra/alscript/tests/alscripttempfiles_test.cpp @@ -30,12 +30,12 @@ #include "llfile.h" #include "lluuid.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct alscripttempfiles_data { @@ -67,48 +67,48 @@ namespace tut static bool there(const std::string& path) { return LLFile::isfile(path); } }; - typedef test_group alscripttempfiles_group; - typedef alscripttempfiles_group::object alscripttempfiles_object; - alscripttempfiles_group alscripttempfiles_instance("alscripttempfiles"); - - template<> template<> - void alscripttempfiles_object::test<1>() + TEST_CASE_METHOD(alscripttempfiles_data, "alscripttempfiles: a copy's name is the cleaned script name and id and language's extension", "[alscripttempfiles]") { - set_test_name("a copy's name: the script's without what a file system refuses, the id, the language's extension"); - ensure_equals("LSL", ALScriptTempFiles::nameFor("/tmp", "My Script", "abc", false), std::string("/tmp/sl_script_My Script_abc.lsl")); - ensure_equals("SLua, the name cleaned", ALScriptTempFiles::nameFor("/tmp/", "a/b:c?", "abc", true), + UNSCOPED_INFO("LSL"); + CHECK(ALScriptTempFiles::nameFor("/tmp", "My Script", "abc", false) == std::string("/tmp/sl_script_My Script_abc.lsl")); + UNSCOPED_INFO("SLua, the name cleaned"); + CHECK(ALScriptTempFiles::nameFor("/tmp/", "a/b:c?", "abc", true) == std::string("/tmp/sl_script_abc_abc.luau")); - ensure_equals("nothing left of the name", ALScriptTempFiles::nameFor("/tmp/", "<>*", "abc", false), std::string("/tmp/sl_script_abc.lsl")); - ensure("a copy", ALScriptTempFiles::isCopy("/tmp/sl_script_x_abc.lsl.log")); - ensure("a notecard's", ALScriptTempFiles::isCopy("/tmp/sl_notecard_n_abc.txt")); - ensure("not one", !ALScriptTempFiles::isCopy("/tmp/notes_sl_script_.txt")); + UNSCOPED_INFO("nothing left of the name"); + CHECK(ALScriptTempFiles::nameFor("/tmp/", "<>*", "abc", false) == std::string("/tmp/sl_script_abc.lsl")); + UNSCOPED_INFO("a copy"); + CHECK(ALScriptTempFiles::isCopy("/tmp/sl_script_x_abc.lsl.log")); + UNSCOPED_INFO("a notecard's"); + CHECK(ALScriptTempFiles::isCopy("/tmp/sl_notecard_n_abc.txt")); + UNSCOPED_INFO("not one"); + CHECK_FALSE(ALScriptTempFiles::isCopy("/tmp/notes_sl_script_.txt")); } - template<> template<> - void alscripttempfiles_object::test<2>() + TEST_CASE_METHOD(alscripttempfiles_data, "alscripttempfiles: two editors of one script share a copy that goes with the last of them", "[alscripttempfiles]") { - set_test_name("two editors of one script hold one copy, and it goes with the last of them, whichever that is"); ALScriptTempFiles files(lists, "one"); const std::string copy = ALScriptTempFiles::nameFor(temp, "s", "id", false); std::shared_ptr studio = files.claim(copy); std::shared_ptr window = files.claim(copy); - ensure("one hold", studio == window); + UNSCOPED_INFO("one hold"); + CHECK(studio == window); put(copy); studio.reset(); - ensure("the other still has it", there(copy)); + UNSCOPED_INFO("the other still has it"); + CHECK(there(copy)); window.reset(); - ensure("gone with the last", !there(copy)); + UNSCOPED_INFO("gone with the last"); + CHECK_FALSE(there(copy)); // Held again after it went: written again, and listed once. std::shared_ptr again = files.claim(copy); put(copy); - ensure("there", there(copy)); + UNSCOPED_INFO("there"); + CHECK(there(copy)); } - template<> template<> - void alscripttempfiles_object::test<3>() + TEST_CASE_METHOD(alscripttempfiles_data, "alscripttempfiles: a sweep takes what ended sessions left but not what a running one lists", "[alscripttempfiles]") { - set_test_name("a sweep takes what sessions that are over left, and leaves what a running one lists, and what is no copy"); // A session that crashed: its list, and its lock that nobody holds. const std::string left = ALScriptTempFiles::nameFor(temp, "left", "1", false); const std::string left_log = left + ".log"; @@ -130,33 +130,57 @@ namespace tut std::shared_ptr held = running.claim(shared); ALScriptTempFiles sweeper(lists, "now"); - ensure_equals("three went", sweeper.sweep(), size_t(3)); - ensure("the crashed one's copy", !there(left)); - ensure("and its log", !there(left_log)); - ensure("the older one's", !there(older)); - ensure("one a running session lists stays", there(shared)); - ensure("what is no copy stays", there(notes)); - ensure("the crashed one's list and lock gone", !there(lists + "crashed.list") && !there(lists + "crashed.lock")); - ensure("and the older one's list", !there(lists + "older.list")); - ensure("the running one's kept", there(lists + "running.list") && there(lists + "running.lock")); - - ensure_equals("swept again, nothing", sweeper.sweep(), size_t(0)); - ensure("the running one's still", there(shared)); + UNSCOPED_INFO("three went"); + CHECK(sweeper.sweep() == size_t(3)); + UNSCOPED_INFO("the crashed one's copy"); + CHECK_FALSE(there(left)); + UNSCOPED_INFO("and its log"); + CHECK_FALSE(there(left_log)); + UNSCOPED_INFO("the older one's"); + CHECK_FALSE(there(older)); + UNSCOPED_INFO("one a running session lists stays"); + CHECK(there(shared)); + UNSCOPED_INFO("what is no copy stays"); + CHECK(there(notes)); + { + INFO("the crashed one's list and lock gone"); + CHECK_FALSE(there(lists + "crashed.list")); + CHECK_FALSE(there(lists + "crashed.lock")); + } + UNSCOPED_INFO("and the older one's list"); + CHECK_FALSE(there(lists + "older.list")); + { + INFO("the running one's kept"); + CHECK(there(lists + "running.list")); + CHECK(there(lists + "running.lock")); + } + + UNSCOPED_INFO("swept again, nothing"); + CHECK(sweeper.sweep() == size_t(0)); + UNSCOPED_INFO("the running one's still"); + CHECK(there(shared)); } - template<> template<> - void alscripttempfiles_object::test<4>() + TEST_CASE_METHOD(alscripttempfiles_data, "alscripttempfiles: a session that lets go of everything leaves no list or lock or copy", "[alscripttempfiles]") { - set_test_name("a session that ends letting go of everything leaves no list, no lock, and no copy"); const std::string copy = ALScriptTempFiles::nameFor(temp, "s", "id", false); { ALScriptTempFiles files(lists, "clean"); std::shared_ptr held = files.claim(copy); put(copy); - ensure("listed", there(lists + "clean.list") && there(lists + "clean.lock")); + { + INFO("listed"); + CHECK(there(lists + "clean.list")); + CHECK(there(lists + "clean.lock")); + } + } + UNSCOPED_INFO("no copy"); + CHECK_FALSE(there(copy)); + { + INFO("no list, no lock"); + CHECK_FALSE(there(lists + "clean.list")); + CHECK_FALSE(there(lists + "clean.lock")); } - ensure("no copy", !there(copy)); - ensure("no list, no lock", !there(lists + "clean.list") && !there(lists + "clean.lock")); // One that let go of the store first, a hold outliving it: the // lock is kept until the hold goes. @@ -167,9 +191,16 @@ namespace tut put(copy); } ALScriptTempFiles sweeper(lists, "now"); - ensure_equals("still running, as far as a sweep can tell", sweeper.sweep(), size_t(0)); - ensure("its copy kept", there(copy)); + UNSCOPED_INFO("still running, as far as a sweep can tell"); + CHECK(sweeper.sweep() == size_t(0)); + UNSCOPED_INFO("its copy kept"); + CHECK(there(copy)); held.reset(); - ensure("gone with the hold", !there(copy) && !there(lists + "late.list") && !there(lists + "late.lock")); + { + INFO("gone with the hold"); + CHECK_FALSE(there(copy)); + CHECK_FALSE(there(lists + "late.list")); + CHECK_FALSE(there(lists + "late.lock")); + } } } diff --git a/indra/alscript/tests/alscripttextcache_test.cpp b/indra/alscript/tests/alscripttextcache_test.cpp index cbea4350a1..1f3b2b9c25 100644 --- a/indra/alscript/tests/alscripttextcache_test.cpp +++ b/indra/alscript/tests/alscripttextcache_test.cpp @@ -26,72 +26,100 @@ #include "../preprocessor/alscripttextcache.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alscripttextcache_data { ALScriptTextCache cache; }; - typedef test_group alscripttextcache_group; - typedef alscripttextcache_group::object alscripttextcache_object; - alscripttextcache_group alscripttextcache_instance("alscripttextcache"); - - template<> template<> - void alscripttextcache_object::test<1>() + TEST_CASE_METHOD(alscripttextcache_data, "alscripttextcache: a text is taken only as its asset; a failure is kept until put or forgotten", "[alscripttextcache]") { - set_test_name("a text held by its identity and asset, taken only as that asset; a failure kept until put or forgotten"); const LLUUID one = LLUUID::generateNewID(), two = LLUUID::generateNewID(); std::string text; - ensure("nothing held", !cache.take("inventory:a", one, text) && !cache.holds("inventory:a")); + { + INFO("nothing held"); + REQUIRE_FALSE(cache.take("inventory:a", one, text)); + CHECK_FALSE(cache.holds("inventory:a")); + } cache.failed("inventory:a"); - ensure("failed", cache.hasFailed("inventory:a")); + UNSCOPED_INFO("failed"); + CHECK(cache.hasFailed("inventory:a")); cache.put("inventory:a", one, "integer a;\n"); - ensure("put: no longer failed", !cache.hasFailed("inventory:a")); - ensure("taken as its asset", cache.take("inventory:a", one, text) && text == "integer a;\n"); - ensure("not as another", !cache.take("inventory:a", two, text)); - ensure("held whatever the asset", cache.held("inventory:a", text) && cache.paths() == std::vector{ "inventory:a" }); + UNSCOPED_INFO("put: no longer failed"); + CHECK_FALSE(cache.hasFailed("inventory:a")); + { + INFO("taken as its asset"); + CHECK(cache.take("inventory:a", one, text)); + CHECK(text == "integer a;\n"); + } + UNSCOPED_INFO("not as another"); + CHECK_FALSE(cache.take("inventory:a", two, text)); + { + INFO("held whatever the asset"); + CHECK(cache.held("inventory:a", text)); + CHECK(cache.paths() == std::vector{ "inventory:a" }); + } cache.put("inventory:a", two, "integer b;\n"); - ensure_equals("put again: the bytes counted once", cache.bytes(), size_t(11)); + UNSCOPED_INFO("put again: the bytes counted once"); + CHECK(cache.bytes() == size_t(11)); cache.failed("inventory:b"); cache.forgetFailure("inventory:b"); - ensure("forgotten", !cache.hasFailed("inventory:b")); + UNSCOPED_INFO("forgotten"); + CHECK_FALSE(cache.hasFailed("inventory:b")); } - template<> template<> - void alscripttextcache_object::test<2>() + TEST_CASE_METHOD(alscripttextcache_data, "alscripttextcache: past the budget the least lately read goes first; the newest always stays", "[alscripttextcache]") { - set_test_name("past the budget the least lately read goes first, and the newest stays whatever its size"); const LLUUID asset = LLUUID::generateNewID(); const std::string third(ALScriptTextCache::BUDGET / 3 + 1, 'x'); std::string text; cache.put("a", asset, third); cache.put("b", asset, third); - ensure("a read, so kept longer", cache.take("a", asset, text)); + UNSCOPED_INFO("a read, so kept longer"); + CHECK(cache.take("a", asset, text)); cache.put("c", asset, third); - ensure("b, least lately read, gone", !cache.holds("b") && cache.holds("a") && cache.holds("c")); + { + INFO("b, least lately read, gone"); + CHECK_FALSE(cache.holds("b")); + CHECK(cache.holds("a")); + CHECK(cache.holds("c")); + } cache.put("huge", asset, std::string(ALScriptTextCache::BUDGET + 1, 'y')); - ensure("the newest kept alone", cache.holds("huge") && cache.paths().size() == 1); + { + INFO("the newest kept alone"); + CHECK(cache.holds("huge")); + REQUIRE(cache.paths().size() == 1); + } } - template<> template<> - void alscripttextcache_object::test<3>() + TEST_CASE_METHOD(alscripttextcache_data, "alscripttextcache: a hundred texts past the budget trim to within it; failures are bounded", "[alscripttextcache]") { - set_test_name("a hundred texts past the budget: the least lately read go, in one trim, until what is held is within it; failures bounded"); const LLUUID asset = LLUUID::generateNewID(); const std::string fiftieth(ALScriptTextCache::BUDGET / 50, 'x'); for (int i = 0; i < 100; ++i) { cache.put("t" + std::to_string(i), asset, fiftieth); } - ensure("within the budget", cache.bytes() <= ALScriptTextCache::BUDGET); - ensure("the newest kept, the oldest gone", cache.holds("t99") && cache.holds("t60") && !cache.holds("t0") && !cache.holds("t40")); + UNSCOPED_INFO("within the budget"); + CHECK(cache.bytes() <= ALScriptTextCache::BUDGET); + { + INFO("the newest kept, the oldest gone"); + CHECK(cache.holds("t99")); + CHECK(cache.holds("t60")); + CHECK_FALSE(cache.holds("t0")); + CHECK_FALSE(cache.holds("t40")); + } for (size_t i = 0; i <= ALScriptTextCache::FAILURES_KEPT; ++i) { cache.failed("f" + std::to_string(i)); } - ensure("past the bound, the failures before it let go of", !cache.hasFailed("f0") && cache.hasFailed("f" + std::to_string(ALScriptTextCache::FAILURES_KEPT))); + { + INFO("past the bound, the failures before it let go of"); + CHECK_FALSE(cache.hasFailed("f0")); + CHECK(cache.hasFailed("f" + std::to_string(ALScriptTextCache::FAILURES_KEPT))); + } } } diff --git a/indra/alscript/tests/alscriptweight_test.cpp b/indra/alscript/tests/alscriptweight_test.cpp index 2dd4ecc3b2..1a6b689415 100644 --- a/indra/alscript/tests/alscriptweight_test.cpp +++ b/indra/alscript/tests/alscriptweight_test.cpp @@ -31,13 +31,13 @@ #include "../luau/alluausharedstart.h" #include "../preprocessor/alsourcemap.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct alscriptweight_data { @@ -133,7 +133,8 @@ namespace tut } for (auto edit = rewrite.edits.rbegin(); edit != rewrite.edits.rend(); ++edit) { - ensure("one line each", edit->line == edit->endLine); + UNSCOPED_INFO("one line each"); + REQUIRE(edit->line == edit->endLine); std::string& line = lines[static_cast(edit->line)]; line.replace(static_cast(edit->column), static_cast(edit->endColumn - edit->column), edit->text); } @@ -156,15 +157,11 @@ namespace tut return out; } }; - typedef test_group alscriptweight_group; - typedef alscriptweight_group::object alscriptweight_object; - alscriptweight_group alscriptweight_instance("ALScriptWeight"); // SLua weighs what the server charges, the bytecode; each function is // its prototype, where its lines are, and each line the instructions // that came of it. - template<> template<> - void alscriptweight_object::test<1>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: SLua weighs the bytecode the server charges; each function a part", "[ALScriptWeight]") { const std::string script = "local function greet(name: string)\n" " ll.OwnerSay(\"hello \" .. name)\n" @@ -181,42 +178,72 @@ namespace tut "nothing()\n" "greet(tostring(count(10)))\n"; const ALScriptWeight weight = ALScriptWeigh::slua(script); - ensure("compiled: " + weight.error, weight.compiled); - ensure_equals("against SLua's limit", weight.limit, size_t(131072)); - ensure("not an estimate", !weight.estimate); - ensure("something, and not much", weight.total > 50 && weight.total < 2000); + UNSCOPED_INFO("compiled: " + weight.error); + CHECK(weight.compiled); + UNSCOPED_INFO("against SLua's limit"); + CHECK(weight.limit == size_t(131072)); + UNSCOPED_INFO("not an estimate"); + CHECK_FALSE(weight.estimate); + { + INFO("something, and not much"); + CHECK(weight.total > 50); + CHECK(weight.total < 2000); + } const ALScriptWeight::Part* greet = named(weight, "greet"); const ALScriptWeight::Part* count = named(weight, "count"); - ensure("each function a part:" + listed(weight), greet && count); - ensure_equals("where it is declared", greet->line, 0); - ensure_equals("and where it ends", count->line, 4); - ensure("to its last line", count->endLine >= 8); + { + INFO("each function a part:" + listed(weight)); + 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); const ALScriptWeight::Part* nothing = named(weight, "nothing"); - ensure("a function that does nothing weighs least:" + listed(weight), - nothing && nothing->bytes < greet->bytes && nothing->bytes < count->bytes); - ensure("the script itself, and its strings", named(weight, "script") && named(weight, "strings")); + { + INFO("a function that does nothing weighs least:" + listed(weight)); + REQUIRE(nothing); + REQUIRE(nothing->bytes < greet->bytes); + CHECK(nothing->bytes < count->bytes); + } + { + INFO("the script itself, and its strings"); + REQUIRE(named(weight, "script")); + REQUIRE(named(weight, "strings")); + } const size_t parts = std::accumulate(weight.parts.begin(), weight.parts.end(), size_t(0), [](size_t sum, const ALScriptWeight::Part& p) { return sum + p.bytes; }); - ensure("the parts are nearly the whole: " + std::to_string(parts) + " of " + std::to_string(weight.total), - parts <= weight.total && weight.total - parts < 16); + { + INFO("the parts are nearly the whole: " + std::to_string(parts) + " of " + std::to_string(weight.total)); + CHECK(parts <= weight.total); + CHECK(weight.total - parts < 16); + } bool body = false; for (const ALScriptWeight::Line& line : weight.lines) { body = body || (line.line == 7 && line.bytes > 0); } - ensure("the loop's body has bytes of its own", body); + UNSCOPED_INFO("the loop's body has bytes of its own"); + CHECK(body); } // A long string is weight as much as code is; and a script that does // not compile says so. - template<> template<> - void alscriptweight_object::test<2>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: a long string weighs as code does; a script that fails to compile says so", "[ALScriptWeight]") { const std::string words(2000, 'x'); const ALScriptWeight heavy = ALScriptWeigh::slua("local s = \"" + words + "\"\nprint(s)\n"); const ALScriptWeight light = ALScriptWeigh::slua("local s = \"x\"\nprint(s)\n"); - ensure("both compiled", heavy.compiled && light.compiled); - ensure("the string weighs", heavy.total >= light.total + 2000); + { + INFO("both compiled"); + CHECK(heavy.compiled); + CHECK(light.compiled); + } + UNSCOPED_INFO("the string weighs"); + CHECK(heavy.total >= light.total + 2000); // Heavy, it is a part of its own, and no longer the table of // strings'. const ALScriptWeight::Part* constant = nullptr; @@ -226,10 +253,24 @@ namespace tut } // At the line that loads it: a local never set again is folded into // where it is used, print(s), as -O1 compiles it. - ensure("a part of its own:" + listed(heavy), constant && constant->bytes >= 2000 && constant->line == 1); - ensure("named by its start: " + (constant ? constant->name : std::string()), constant && constant->name.rfind("\"xxxx", 0) == 0); - ensure("not the strings' too", named(heavy, "strings") && named(heavy, "strings")->bytes < 100); - ensure("a light one stays the strings'", std::none_of(light.parts.begin(), light.parts.end(), [](const ALScriptWeight::Part& one) { + { + INFO("a part of its own:" + listed(heavy)); + REQUIRE(constant); + REQUIRE(constant->bytes >= 2000); + CHECK(constant->line == 1); + } + { + INFO("named by its start: " + (constant ? constant->name : std::string())); + REQUIRE(constant); + CHECK(constant->name.rfind("\"xxxx", 0) == 0); + } + { + INFO("not the strings' too"); + REQUIRE(named(heavy, "strings")); + CHECK(named(heavy, "strings")->bytes < 100); + } + UNSCOPED_INFO("a light one stays the strings'"); + CHECK(std::none_of(light.parts.begin(), light.parts.end(), [](const ALScriptWeight::Part& one) { return one.kind == ALScriptWeight::Part::Kind::Constant && one.name != "strings"; })); // And for the LSL targets: LSO's in its code, Mono's in its strings, @@ -246,8 +287,15 @@ namespace tut sum += part.bytes; } const std::string target = ALScriptWeight::nameOf(one.target); - ensure(target + ": a part of its own:" + listed(one), one.compiled && found && found->bytes >= 2000 && found->line == 0); - ensure(target + ": counted once", sum <= one.total); + { + INFO(target + ": a part of its own:" + listed(one)); + REQUIRE(one.compiled); + REQUIRE(found); + REQUIRE(found->bytes >= 2000); + REQUIRE(found->line == 0); + } + UNSCOPED_INFO(target + ": counted once"); + REQUIRE(sum <= one.total); } // A table's values written in it, which no instruction loads -- the // template carries them: heavy ones are parts of their own too, at @@ -259,7 +307,8 @@ namespace tut { values += one.kind == ALScriptWeight::Part::Kind::Constant && one.name != "strings" && one.bytes >= 300 && one.line >= 0 && one.line <= 3; } - ensure_equals("each value a part of its own, at the table:" + listed(table), values, size_t(2)); + UNSCOPED_INFO("each value a part of its own, at the table:" + listed(table)); + CHECK(values == size_t(2)); // Named by whole characters: the cut, 32 bytes in, falls inside // one, or just after one. const auto whole = [](const std::string& name) { @@ -296,20 +345,29 @@ namespace tut { found = one.kind == ALScriptWeight::Part::Kind::Constant && one.name != "strings" ? &one : found; } - ensure("a wide one named: " + listed(wide), found && whole(found->name) && found->name.find("\xE2\x80\xA6") != std::string::npos); + { + INFO("a wide one named: " + listed(wide)); + REQUIRE(found); + REQUIRE(whole(found->name)); + REQUIRE(found->name.find("\xE2\x80\xA6") != std::string::npos); + } } const ALScriptWeight broken = ALScriptWeigh::slua("local function (\n"); - ensure("a script that does not compile", !broken.compiled && !broken.error.empty()); + { + INFO("a script that does not compile"); + CHECK_FALSE(broken.compiled); + REQUIRE_FALSE(broken.error.empty()); + } } // LSO weighs its image to the top of its heap: the registers, the // globals, the functions, the states with their handlers, and the heap; // each function, state, handler and global a part, where it is. - template<> template<> - void alscriptweight_object::test<3>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: LSO weighs its image to the top of its heap", "[ALScriptWeight]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string script = "integer gCount = 0;\n" "string gName = \"a name a little longer than a word\";\n" "integer twice(integer n)\n" @@ -328,29 +386,50 @@ namespace tut " }\n" "}\n"; const ALScriptWeight weight = ALScriptWeigh::lso(script); - ensure("compiled: " + weight.error, weight.compiled); - ensure_equals("against LSO's limit", weight.limit, size_t(16384)); - ensure("well under it", weight.total > 0 && weight.total < 4096); + UNSCOPED_INFO("compiled: " + weight.error); + CHECK(weight.compiled); + UNSCOPED_INFO("against LSO's limit"); + CHECK(weight.limit == size_t(16384)); + { + INFO("well under it"); + CHECK(weight.total > 0); + CHECK(weight.total < 4096); + } const ALScriptWeight::Part* twice = named(weight, "twice"); const ALScriptWeight::Part* state = named(weight, "default"); const ALScriptWeight::Part* touch = named(weight, "touch_start"); - ensure("each a part:" + listed(weight), twice && state && touch && named(weight, "state_entry") && named(weight, "registers")); - ensure_equals("a function where it is", twice->line, 2); - ensure_equals("a handler in its state", touch->within, std::string("default")); + { + INFO("each a part:" + listed(weight)); + REQUIRE(twice); + REQUIRE(state); + REQUIRE(touch); + REQUIRE(named(weight, "state_entry")); + REQUIRE(named(weight, "registers")); + } + UNSCOPED_INFO("a function where it is"); + CHECK(twice->line == 2); + UNSCOPED_INFO("a handler in its state"); + CHECK(touch->within == std::string("default")); // Where its handlers begin and a jump table entry for each; each // handler, from where it begins to where it ends, its own part. - ensure_equals("a state is its table, its handlers apart:" + listed(weight), state->bytes, size_t(5 + 8 * 2)); + UNSCOPED_INFO("a state is its table, its handlers apart:" + listed(weight)); + CHECK(state->bytes == size_t(5 + 8 * 2)); const ALScriptWeight::Part* count = named(weight, "gCount"); const ALScriptWeight::Part* name = named(weight, "gName"); - ensure("the globals too", count && name); - ensure("a string global weighs its string", name->bytes > count->bytes); + { + INFO("the globals too"); + REQUIRE(count); + REQUIRE(name); + } + UNSCOPED_INFO("a string global weighs its string"); + CHECK(name->bytes > count->bytes); } // What does not fit in 16 KB says so. - template<> template<> - void alscriptweight_object::test<4>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: what does not fit in 16 KB says so", "[ALScriptWeight]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); std::string script = "list gWords = ["; for (int i = 0; i < 600; ++i) { @@ -358,37 +437,28 @@ namespace tut } script += "];\ndefault\n{\n state_entry()\n {\n llOwnerSay(llList2String(gWords, 0));\n }\n}\n"; const ALScriptWeight weight = ALScriptWeigh::lso(script); - ensure("does not fit: " + weight.error + " " + std::to_string(weight.total), !weight.compiled && !weight.error.empty()); - ensure("and weighs more than there is", weight.total > weight.limit); + { + INFO("does not fit: " + weight.error + " " + std::to_string(weight.total)); + CHECK_FALSE(weight.compiled); + REQUIRE_FALSE(weight.error.empty()); + } + UNSCOPED_INFO("and weighs more than there is"); + CHECK(weight.total > weight.limit); const ALScriptWeight broken = ALScriptWeigh::lso("default { state_entry() { integer x = ; } }\n"); - ensure("a script that does not parse", !broken.compiled && !broken.error.empty()); - } - - // A long list parses on every platform: each element nests the parse two - // states deeper, and the parser's limit is the same everywhere. - template<> template<> - void alscriptweight_object::test<10>() - { - ensure("builtins: " + error, lslLoaded); - std::string script = "list gNumbers = ["; - for (int i = 0; i < 3000; ++i) { - script += (i ? ", " : "") + std::to_string(i); + INFO("a script that does not parse"); + CHECK_FALSE(broken.compiled); + REQUIRE_FALSE(broken.error.empty()); } - script += "];\ndefault\n{\n state_entry()\n {\n llOwnerSay((string)llGetListLength(gNumbers));\n }\n}\n"; - lsl.check(script); - ensure("it parses", lsl.parsed()); - const ALScriptWeight weight = ALScriptWeigh::lso(script); - ensure("and is weighed: " + weight.error, weight.total > 0); } // Mono is an estimate: the IL sized by the opcode table with what it // declares, over what an assembly costs; each function and handler a // part where it is, and the parts are the whole. - template<> template<> - void alscriptweight_object::test<5>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: Mono is an estimate: the IL sized by the opcode table with what it declares", "[ALScriptWeight]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string script = "integer gCount = 0;\n" "integer twice(integer n)\n" "{\n" @@ -406,30 +476,49 @@ namespace tut " }\n" "}\n"; const ALScriptWeight weight = ALScriptWeigh::mono(script); - ensure("compiled: " + weight.error, weight.compiled); - ensure("an estimate", weight.estimate); - ensure_equals("against Mono's limit", weight.limit, size_t(65536)); - ensure("over what an assembly costs, and under the limit", weight.total > ALScriptWeigh::MONO_BASE_BYTES && weight.total < 8192); + UNSCOPED_INFO("compiled: " + weight.error); + CHECK(weight.compiled); + UNSCOPED_INFO("an estimate"); + CHECK(weight.estimate); + UNSCOPED_INFO("against Mono's limit"); + CHECK(weight.limit == size_t(65536)); + { + INFO("over what an assembly costs, and under the limit"); + CHECK(weight.total > ALScriptWeigh::MONO_BASE_BYTES); + CHECK(weight.total < 8192); + } const ALScriptWeight::Part* twice = named(weight, "twice"); const ALScriptWeight::Part* touch = named(weight, "touch_start"); const ALScriptWeight::Part* entry = named(weight, "state_entry"); - ensure("each a part:" + listed(weight), twice && touch && entry && named(weight, "globals") && named(weight, "assembly")); - ensure_equals("a function where it is", twice->line, 1); - ensure_equals("a handler in its state", touch->within, std::string("default")); - ensure_equals("where it is", touch->line, 11); - ensure("a long string weighs:" + listed(weight), touch->bytes > entry->bytes); + { + INFO("each a part:" + listed(weight)); + REQUIRE(twice); + REQUIRE(touch); + REQUIRE(entry); + REQUIRE(named(weight, "globals")); + REQUIRE(named(weight, "assembly")); + } + UNSCOPED_INFO("a function where it is"); + CHECK(twice->line == 1); + UNSCOPED_INFO("a handler in its state"); + CHECK(touch->within == std::string("default")); + UNSCOPED_INFO("where it is"); + CHECK(touch->line == 11); + UNSCOPED_INFO("a long string weighs:" + listed(weight)); + CHECK(touch->bytes > entry->bytes); const size_t parts = std::accumulate(weight.parts.begin(), weight.parts.end(), size_t(0), [](size_t sum, const ALScriptWeight::Part& p) { return sum + p.bytes; }); - ensure_equals("the parts are the whole", parts, weight.total); + UNSCOPED_INFO("the parts are the whole"); + CHECK(parts == weight.total); } // LSL on Luau: the fork's own compiler, the asset as the server makes // it, the bytecode read back as SLua's is; and again with its lines, // for what each comes to. - template<> template<> - void alscriptweight_object::test<6>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: LSL on Luau: the asset as the server makes it with the fork's compiler", "[ALScriptWeight]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::string script = "integer gCount = 0;\n" "integer twice(integer n)\n" "{\n" @@ -447,72 +536,127 @@ namespace tut " }\n" "}\n"; const ALScriptWeight weight = ALScriptWeigh::lslLuau(script); - ensure("compiled: " + weight.error + listed(weight), weight.compiled); - ensure_equals("against Luau's limit", weight.limit, size_t(131072)); - ensure("not an estimate", !weight.estimate); - ensure("something, and not much: " + std::to_string(weight.total) + listed(weight), weight.total > 50 && weight.total < 4096); + UNSCOPED_INFO("compiled: " + weight.error + listed(weight)); + CHECK(weight.compiled); + UNSCOPED_INFO("against Luau's limit"); + CHECK(weight.limit == size_t(131072)); + UNSCOPED_INFO("not an estimate"); + CHECK_FALSE(weight.estimate); + { + INFO("something, and not much: " + std::to_string(weight.total) + listed(weight)); + CHECK(weight.total > 50); + CHECK(weight.total < 4096); + } const ALScriptWeight::Part* twice = named(weight, "twice"); - ensure("a function a part:" + listed(weight), twice != nullptr); - ensure("where it is: " + std::to_string(twice->line), twice->line == 1); + UNSCOPED_INFO("a function a part:" + listed(weight)); + REQUIRE(twice != nullptr); + UNSCOPED_INFO("where it is: " + std::to_string(twice->line)); + CHECK(twice->line == 1); const ALScriptWeight::Part* touch = named(weight, "touch_start"); - ensure("a handler in its state:" + listed(weight), touch && touch->within == "default" && touch->kind == ALScriptWeight::Part::Kind::Handler); - ensure_equals("where it is", touch->line, 11); + { + INFO("a handler in its state:" + listed(weight)); + REQUIRE(touch); + REQUIRE(touch->within == "default"); + CHECK(touch->kind == ALScriptWeight::Part::Kind::Handler); + } + UNSCOPED_INFO("where it is"); + CHECK(touch->line == 11); // Its lines, read off the compiler's lined bytecode, which the // server's is not: the total stays what the server charges. - ensure("each line weighed:" + lined(weight), at(weight, 9) > 0 && at(weight, 13) > 0 && at(weight, 3) > 0); - ensure("the handler's long line its own:" + lined(weight), at(weight, 13) > at(weight, 9)); - ensure("nothing past the script's lines:" + lined(weight), within(weight, 16, 1000) == 0); + { + INFO("each line weighed:" + lined(weight)); + CHECK(at(weight, 9) > 0); + CHECK(at(weight, 13) > 0); + CHECK(at(weight, 3) > 0); + } + UNSCOPED_INFO("the handler's long line its own:" + lined(weight)); + CHECK(at(weight, 13) > at(weight, 9)); + UNSCOPED_INFO("nothing past the script's lines:" + lined(weight)); + CHECK(within(weight, 16, 1000) == 0); const ALScriptWeight broken = ALScriptWeigh::lslLuau("default { state_entry() { integer x = ; } }\n"); - ensure("a script that does not compile", !broken.compiled && !broken.error.empty()); + { + INFO("a script that does not compile"); + CHECK_FALSE(broken.compiled); + REQUIRE_FALSE(broken.error.empty()); + } } // LSO by line: what the compiler wrote while it stood at each // statement and expression, a function's missing return at its closing // brace, and a global's value on its line. A function's lines are its // code, which is the function less its header. - template<> template<> - void alscriptweight_object::test<7>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: LSO by line: what the compiler wrote at each statement and expression", "[ALScriptWeight]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const ALScriptWeight weight = ALScriptWeigh::lso(byLine()); - ensure("compiled: " + weight.error, weight.compiled); - ensure("by line:" + lined(weight), !weight.lines.empty()); - ensure("the loop's body" + lined(weight), at(weight, 13) > 0); - ensure("the loop itself: its test, its step and its jump back" + lined(weight), at(weight, 11) > 0); - ensure("a long string weighs more than a short one" + lined(weight), at(weight, 15) > at(weight, 16) + 40); - ensure("the return it does not write, at its closing brace" + lined(weight), at(weight, 17) > 0); - ensure("a function's return" + lined(weight), at(weight, 4) > 0); - ensure("a global string's value" + lined(weight), at(weight, 1) >= 34); - ensure("a brace that makes nothing" + lined(weight), at(weight, 7) == 0 && at(weight, 3) == 0); + UNSCOPED_INFO("compiled: " + weight.error); + CHECK(weight.compiled); + UNSCOPED_INFO("by line:" + lined(weight)); + REQUIRE_FALSE(weight.lines.empty()); + UNSCOPED_INFO("the loop's body" + lined(weight)); + CHECK(at(weight, 13) > 0); + UNSCOPED_INFO("the loop itself: its test, its step and its jump back" + lined(weight)); + CHECK(at(weight, 11) > 0); + UNSCOPED_INFO("a long string weighs more than a short one" + lined(weight)); + CHECK(at(weight, 15) > at(weight, 16) + 40); + UNSCOPED_INFO("the return it does not write, at its closing brace" + lined(weight)); + CHECK(at(weight, 17) > 0); + UNSCOPED_INFO("a function's return" + lined(weight)); + CHECK(at(weight, 4) > 0); + UNSCOPED_INFO("a global string's value" + lined(weight)); + CHECK(at(weight, 1) >= 34); + { + INFO("a brace that makes nothing" + lined(weight)); + CHECK(at(weight, 7) == 0); + CHECK(at(weight, 3) == 0); + } const ALScriptWeight::Part* entry = named(weight, "state_entry"); const ALScriptWeight::Part* twice = named(weight, "twice"); - ensure("both parts:" + listed(weight), entry && twice); - ensure_equals("a handler's lines are all of it but its header, where its code begins", entry->bytes - within(weight, entry->line, entry->endLine), + { + INFO("both parts:" + listed(weight)); + REQUIRE(entry); + REQUIRE(twice); + } + UNSCOPED_INFO("a handler's lines are all of it but its header, where its code begins"); + CHECK(entry->bytes - within(weight, entry->line, entry->endLine) == size_t(5)); - ensure_equals("a function's lines are all of it but its header: where its code begins, its type and its parameter's", - twice->bytes - within(weight, twice->line, twice->endLine), size_t(9)); + UNSCOPED_INFO("a function's lines are all of it but its header: where its code begins, its type and its parameter's"); + CHECK(twice->bytes - within(weight, twice->line, twice->endLine) == size_t(9)); } // Mono by line, the same way: each instruction, with its string, is // the line it was written for; a global's initialiser is its line's. - template<> template<> - void alscriptweight_object::test<8>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: Mono by line: each instruction with its string is its line's", "[ALScriptWeight]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const ALScriptWeight weight = ALScriptWeigh::mono(byLine()); - ensure("compiled: " + weight.error, weight.compiled); - ensure("by line:" + lined(weight), !weight.lines.empty()); - ensure("the loop's body" + lined(weight), at(weight, 13) > 0); - ensure("the loop itself" + lined(weight), at(weight, 11) > 0); - ensure("a long string weighs more than a short one" + lined(weight), at(weight, 15) > at(weight, 16) + 40); - ensure("the return it does not write, at its closing brace" + lined(weight), at(weight, 17) > 0); - ensure("a global string's initialiser" + lined(weight), at(weight, 1) >= 34); - ensure("a brace that makes nothing" + lined(weight), at(weight, 7) == 0 && at(weight, 3) == 0); + UNSCOPED_INFO("compiled: " + weight.error); + CHECK(weight.compiled); + UNSCOPED_INFO("by line:" + lined(weight)); + REQUIRE_FALSE(weight.lines.empty()); + UNSCOPED_INFO("the loop's body" + lined(weight)); + CHECK(at(weight, 13) > 0); + UNSCOPED_INFO("the loop itself" + lined(weight)); + CHECK(at(weight, 11) > 0); + UNSCOPED_INFO("a long string weighs more than a short one" + lined(weight)); + CHECK(at(weight, 15) > at(weight, 16) + 40); + UNSCOPED_INFO("the return it does not write, at its closing brace" + lined(weight)); + CHECK(at(weight, 17) > 0); + UNSCOPED_INFO("a global string's initialiser" + lined(weight)); + CHECK(at(weight, 1) >= 34); + { + INFO("a brace that makes nothing" + lined(weight)); + CHECK(at(weight, 7) == 0); + CHECK(at(weight, 3) == 0); + } for (const ALScriptWeight::Part& part : weight.parts) { if (part.line >= 0) { - ensure(part.name + "'s lines are no more than it" + lined(weight), within(weight, part.line, part.endLine) <= part.bytes); + UNSCOPED_INFO(part.name + "'s lines are no more than it" + lined(weight)); + REQUIRE(within(weight, part.line, part.endLine) <= part.bytes); } } size_t all = 0; @@ -520,16 +664,20 @@ namespace tut { all += line.bytes; } - ensure("the lines are less than the whole", all > 0 && all < weight.total); + { + INFO("the lines are less than the whole"); + CHECK(all > 0); + CHECK(all < weight.total); + } } // In the source's places: a part in an include is in the include, at // its line there, and a line's bytes are the source line's; what the // map has no origin for keeps its part without a place. - template<> template<> - void alscriptweight_object::test<9>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: in the source's places: a part in an include is at its line there", "[ALScriptWeight]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const std::vector lines = { "integer twice(integer n)", "{", " return n * 2;", @@ -565,45 +713,84 @@ namespace tut return map; }; const ALScriptWeight weight = ALScriptWeigh::lso(expanded); - ensure("compiled: " + weight.error, weight.compiled); + UNSCOPED_INFO("compiled: " + weight.error); + CHECK(weight.compiled); const ALScriptWeight placed = weight.inSource(mapped(true)); - ensure_equals("the same weight", placed.total, weight.total); + UNSCOPED_INFO("the same weight"); + CHECK(placed.total == weight.total); const ALScriptWeight::Part* twice = named(placed, "twice"); const ALScriptWeight::Part* entry = named(placed, "state_entry"); - ensure("both:" + listed(placed), twice && entry); - ensure_equals("a function in the include", twice->file, std::string("disk:/scripts/lib.lsl")); - ensure_equals("at its line there", twice->line, 0); - ensure_equals("to its end there", twice->endLine, 3); - ensure("a handler in the script", entry->file.empty()); - ensure_equals("at the script's line", entry->line, 3); + { + INFO("both:" + listed(placed)); + REQUIRE(twice); + REQUIRE(entry); + } + UNSCOPED_INFO("a function in the include"); + CHECK(twice->file == std::string("disk:/scripts/lib.lsl")); + UNSCOPED_INFO("at its line there"); + CHECK(twice->line == 0); + UNSCOPED_INFO("to its end there"); + CHECK(twice->endLine == 3); + UNSCOPED_INFO("a handler in the script"); + REQUIRE(entry->file.empty()); + UNSCOPED_INFO("at the script's line"); + CHECK(entry->line == 3); size_t in_include = 0, in_script = 0; for (const ALScriptWeight::Line& line : placed.lines) { - ensure("a line of one of the two" + lined(placed), line.file.empty() || line.file == "disk:/scripts/lib.lsl"); - ensure("not the #include's" + lined(placed), !(line.file.empty() && line.line == 0)); + UNSCOPED_INFO("a line of one of the two" + lined(placed)); + REQUIRE((line.file.empty() || line.file == "disk:/scripts/lib.lsl")); + UNSCOPED_INFO("not the #include's" + lined(placed)); + REQUIRE_FALSE((line.file.empty() && line.line == 0)); (line.file.empty() ? in_script : in_include) += line.bytes; } - ensure("the include's return is its line's", in_include > 0); - ensure("the call is the script's", in_script > 0); + UNSCOPED_INFO("the include's return is its line's"); + CHECK(in_include > 0); + UNSCOPED_INFO("the call is the script's"); + CHECK(in_script > 0); size_t all = 0; for (const ALScriptWeight::Line& line : weight.lines) { all += line.bytes; } - ensure_equals("every line's bytes somewhere", in_include + in_script, all); + UNSCOPED_INFO("every line's bytes somewhere"); + CHECK(in_include + in_script == all); const ALScriptWeight lost = weight.inSource(mapped(false)); - ensure("a part the map has no origin for is kept" + listed(lost), named(lost, "twice") != nullptr); - ensure_equals("without a place", named(lost, "twice")->line, -1); - ensure_equals("the handler still placed", named(lost, "state_entry")->line, 3); + UNSCOPED_INFO("a part the map has no origin for is kept" + listed(lost)); + 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); + } + + // A long list parses on every platform: each element nests the parse two + // states deeper, and the parser's limit is the same everywhere. + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: a long list parses on every platform: each element nests two states deeper", "[ALScriptWeight]") + { + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); + std::string script = "list gNumbers = ["; + for (int i = 0; i < 3000; ++i) + { + script += (i ? ", " : "") + std::to_string(i); + } + script += "];\ndefault\n{\n state_entry()\n {\n llOwnerSay((string)llGetListLength(gNumbers));\n }\n}\n"; + lsl.check(script); + UNSCOPED_INFO("it parses"); + CHECK(lsl.parsed()); + const ALScriptWeight weight = ALScriptWeigh::lso(script); + UNSCOPED_INFO("and is weighed: " + weight.error); + CHECK(weight.total > 0); } // Mono's user string heap holds a string once, however many loads of // it there are: a second load costs the instruction alone, on its line. - template<> template<> - void alscriptweight_object::test<11>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: Mono's user string heap holds a string once however often it is loaded", "[ALScriptWeight]") { - ensure("builtins: " + error, lslLoaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(lslLoaded); const auto script = [](int says) { std::string text = "default\n{\n state_entry()\n {\n"; for (int i = 0; i < says; ++i) @@ -615,17 +802,24 @@ namespace tut const ALScriptWeight one = ALScriptWeigh::mono(script(1)); const ALScriptWeight two = ALScriptWeigh::mono(script(2)); const ALScriptWeight three = ALScriptWeigh::mono(script(3)); - ensure("compiled: " + one.error + two.error + three.error, one.compiled && two.compiled && three.compiled); - ensure_equals("the third load as the second", three.total - two.total, two.total - one.total); - ensure("the second less than the string" + lined(two), two.total - one.total < 20); - ensure("the string is the first line's" + lined(two), at(two, 4) > at(two, 5) + 80); + { + INFO("compiled: " + one.error + two.error + three.error); + CHECK(one.compiled); + CHECK(two.compiled); + CHECK(three.compiled); + } + UNSCOPED_INFO("the third load as the second"); + CHECK(three.total - two.total == two.total - one.total); + UNSCOPED_INFO("the second less than the string" + lined(two)); + CHECK(two.total - one.total < 20); + UNSCOPED_INFO("the string is the first line's" + lined(two)); + CHECK(at(two, 4) > at(two, 5) + 80); } // SLua's handlers and callbacks are named as the script means them, a // state's with its state, each part from its function to its end; and // a line carries the strings and constants it is first to name. - template<> template<> - void alscriptweight_object::test<12>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: SLua's handlers and callbacks are named as the script means them", "[ALScriptWeight]") { const std::string script = "local greeting = \"a sentence of some length, said to the owner as the script starts up\"\n" "local function twice(n: number): number\n" @@ -645,7 +839,8 @@ namespace tut " print(\"tick\")\n" "end)\n"; const ALScriptWeight weight = ALScriptWeigh::slua(script); - ensure("compiled: " + weight.error, weight.compiled); + UNSCOPED_INFO("compiled: " + weight.error); + CHECK(weight.compiled); const auto find = [&](const std::string& name, const std::string& within) -> const ALScriptWeight::Part* { for (const ALScriptWeight::Part& part : weight.parts) { @@ -657,32 +852,60 @@ namespace tut return nullptr; }; const ALScriptWeight::Part* twice = find("twice", ""); - ensure("a local function, from its function to its end:" + listed(weight), twice && twice->line == 1 && twice->endLine == 3 && twice->endColumn == 3); - ensure("not a handler", twice->kind == ALScriptWeight::Part::Kind::Function); + { + INFO("a local function, from its function to its end:" + listed(weight)); + REQUIRE(twice); + REQUIRE(twice->line == 1); + REQUIRE(twice->endLine == 3); + CHECK(twice->endColumn == 3); + } + UNSCOPED_INFO("not a handler"); + CHECK(twice->kind == ALScriptWeight::Part::Kind::Function); const ALScriptWeight::Part* entry = find("state_entry", "default"); - ensure("a state's handler, with its state:" + listed(weight), entry && entry->kind == ALScriptWeight::Part::Kind::Handler); - ensure("from its function to its end", entry->line == 6 && entry->column == 18 && entry->endLine == 8); - ensure("a state named by a string:" + listed(weight), find("touch_start", "other place") != nullptr); + { + INFO("a state's handler, with its state:" + listed(weight)); + REQUIRE(entry); + CHECK(entry->kind == ALScriptWeight::Part::Kind::Handler); + } + { + INFO("from its function to its end"); + REQUIRE(entry->line == 6); + REQUIRE(entry->column == 18); + CHECK(entry->endLine == 8); + } + UNSCOPED_INFO("a state named by a string:" + listed(weight)); + REQUIRE(find("touch_start", "other place") != nullptr); const ALScriptWeight::Part* touched = find("touch_start", ""); - ensure("LLEvents' handler by its event:" + listed(weight), touched && touched->kind == ALScriptWeight::Part::Kind::Handler && touched->line == 11); + { + INFO("LLEvents' handler by its event:" + listed(weight)); + REQUIRE(touched); + REQUIRE(touched->kind == ALScriptWeight::Part::Kind::Handler); + CHECK(touched->line == 11); + } const ALScriptWeight::Part* tick = find("LLTimers:every", ""); - ensure("what LLTimers calls, by how it is set going:" + listed(weight), tick && tick->kind == ALScriptWeight::Part::Kind::Function); + { + INFO("what LLTimers calls, by how it is set going:" + listed(weight)); + REQUIRE(tick); + CHECK(tick->kind == ALScriptWeight::Part::Kind::Function); + } // The greeting, a constant the compiler puts where it is used, is // that line's; the strings every line names are counted on some // line, so that the lines come to most of the whole. - ensure("the long string on the line that says it:" + lined(weight), at(weight, 7) > 80); - ensure("a short one on its own line:" + lined(weight), at(weight, 12) > 4 + std::string("touched").size()); + UNSCOPED_INFO("the long string on the line that says it:" + lined(weight)); + CHECK(at(weight, 7) > 80); + UNSCOPED_INFO("a short one on its own line:" + lined(weight)); + REQUIRE(at(weight, 12) > 4 + std::string("touched").size()); const size_t lines = within(weight, 0, 100); - ensure("most of the whole on some line: " + std::to_string(lines) + " of " + std::to_string(weight.total), lines * 3 > weight.total * 2); + UNSCOPED_INFO("most of the whole on some line: " + std::to_string(lines) + " of " + std::to_string(weight.total)); + CHECK(lines * 3 > weight.total * 2); } // Each string the table keeps: what it takes up, how many instructions // name it and how many load it as a value, the first line naming it, // and a function's name as such. They come to the table, its count // before them. - template<> template<> - void alscriptweight_object::test<13>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: each string the table keeps: its bytes and uses and loads and first line", "[ALScriptWeight]") { const std::string script = "local function greet(name: string)\n" // 0 " ll.OwnerSay(\"hello \" .. name)\n" // 1 @@ -693,7 +916,8 @@ namespace tut "greet(\"hello again\")\n" // 6 "greet(\"hello again\")\n"; // 7 const ALScriptWeight w = ALScriptWeigh::slua(script); - ensure("compiled: " + w.error, w.compiled); + UNSCOPED_INFO("compiled: " + w.error); + CHECK(w.compiled); const auto of = [&w](const std::string& text) -> const ALScriptWeight::String* { for (const ALScriptWeight::String& one : w.strings) { @@ -705,41 +929,79 @@ namespace tut return nullptr; }; const ALScriptWeight::String* again = of("hello again"); - ensure("a value loaded twice", again && again->uses == 2 && again->loads == 2 && again->line == 6 && again->bytes == 12); + { + INFO("a value loaded twice"); + REQUIRE(again); + REQUIRE(again->uses == 2); + REQUIRE(again->loads == 2); + REQUIRE(again->line == 6); + CHECK(again->bytes == 12); + } const ALScriptWeight::String* colour = of("colour"); - ensure("a field's name: named, never loaded", colour && colour->uses >= 2 && colour->loads == 0 && colour->line == 4); + { + INFO("a field's name: named, never loaded"); + REQUIRE(colour); + REQUIRE(colour->uses >= 2); + REQUIRE(colour->loads == 0); + CHECK(colour->line == 4); + } const ALScriptWeight::String* blue = of("blue"); - ensure("a comparison's: named, never loaded", blue && blue->uses == 1 && blue->loads == 0); + { + INFO("a comparison's: named, never loaded"); + REQUIRE(blue); + REQUIRE(blue->uses == 1); + CHECK(blue->loads == 0); + } const ALScriptWeight::String* say = of("OwnerSay"); - ensure("an import's path", say && say->uses >= 1 && say->loads == 0 && say->line == 1); + { + INFO("an import's path"); + REQUIRE(say); + REQUIRE(say->uses >= 1); + REQUIRE(say->loads == 0); + CHECK(say->line == 1); + } const ALScriptWeight::String* greet = of("greet"); - ensure("a function's name, at the line it starts on", greet && greet->name && greet->uses == 0 && greet->line == 0); + { + INFO("a function's name, at the line it starts on"); + REQUIRE(greet); + REQUIRE(greet->name); + REQUIRE(greet->uses == 0); + CHECK(greet->line == 0); + } size_t bytes = 0; for (const ALScriptWeight::String& one : w.strings) { bytes += one.bytes; } const ALScriptWeight::Part* table = named(w, "strings"); - ensure("they come to the table, with its count", table && table->bytes == bytes + 1); + { + INFO("they come to the table, with its count"); + REQUIRE(table); + CHECK(table->bytes == bytes + 1); + } // LSL compiled for Luau's VM: its strings, at the lines the lined // compile says. - ensure("the builtins: " + error, lslLoaded); + UNSCOPED_INFO("the builtins: " + error); + CHECK(lslLoaded); const ALScriptWeight lsl = ALScriptWeigh::lslLuau(byLine()); bool found = false; for (const ALScriptWeight::String& one : lsl.strings) { found = found || (one.text == "a sentence of some length, said to the owner on every start" && one.line == 15); } - ensure("LSL on Luau's, placed", lsl.compiled && found); + { + INFO("LSL on Luau's, placed"); + CHECK(lsl.compiled); + CHECK(found); + } } // Strings that start alike: where keeping the start once and joining it // back on would weigh less, what it would save -- the table's bytes // before less after, less the join at each load and the local once. // Only strings loaded as values; a short start saves nothing. - template<> template<> - void alscriptweight_object::test<14>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: strings that start alike: where keeping the start once would weigh less", "[ALScriptWeight]") { const std::string start = "You have touched the object of the day: "; const std::vector rests = { "alpha", "bravo", "charlie", "delta" }; @@ -753,11 +1015,15 @@ namespace tut // Two sharing a start too short to be worth it. script += "ll.OwnerSay(\"short one\")\nll.OwnerSay(\"short two\")\n"; const ALScriptWeight w = ALScriptWeigh::slua(script); - ensure("compiled: " + w.error, w.compiled); - ensure_equals("one start worth keeping", w.sharedStarts.size(), size_t(1)); + UNSCOPED_INFO("compiled: " + w.error); + CHECK(w.compiled); + UNSCOPED_INFO("one start worth keeping"); + REQUIRE(w.sharedStarts.size() == size_t(1)); const ALScriptWeight::SharedStart& shared = w.sharedStarts.front(); - ensure_equals("the start the four share", shared.start, start); - ensure_equals("the four, not the field's", shared.strings.size(), rests.size()); + UNSCOPED_INFO("the start the four share"); + CHECK(shared.start == start); + UNSCOPED_INFO("the four, not the field's"); + REQUIRE(shared.strings.size() == rests.size()); size_t before = 0; size_t after = 1 + start.size(); for (const std::string& rest : rests) @@ -766,10 +1032,15 @@ namespace tut after += 1 + rest.size(); } const size_t cost = after + rests.size() * ALScriptWeigh::SHARED_START_PER_LOAD + ALScriptWeigh::SHARED_START_ONCE; - ensure_equals("what it saves", shared.saved, before - cost); + UNSCOPED_INFO("what it saves"); + CHECK(shared.saved == before - cost); for (const size_t i : shared.strings) { - ensure("each one of them", w.strings[i].text.rfind(start, 0) == 0 && w.strings[i].text != start + "zulu"); + { + INFO("each one of them"); + REQUIRE(w.strings[i].text.rfind(start, 0) == 0); + REQUIRE(w.strings[i].text != start + "zulu"); + } } // The costs against the compiler's own: the four written whole, and @@ -784,23 +1055,32 @@ namespace tut } const ALScriptWeight as_whole = ALScriptWeigh::slua(whole); const ALScriptWeight as_apart = ALScriptWeigh::slua(apart); - ensure("both compiled", as_whole.compiled && as_apart.compiled && as_whole.sharedStarts.size() == 1); + { + INFO("both compiled"); + CHECK(as_whole.compiled); + CHECK(as_apart.compiled); + REQUIRE(as_whole.sharedStarts.size() == 1); + } const S64 really = S64(as_whole.total) - S64(as_apart.total); - ensure(llformat("saves about what was said: %lld against %zu", (long long)really, as_whole.sharedStarts[0].saved), - std::abs(really - S64(as_whole.sharedStarts[0].saved)) <= 8); + UNSCOPED_INFO(llformat("saves about what was said: %lld against %zu", (long long)really, as_whole.sharedStarts[0].saved)); + CHECK(std::abs(really - S64(as_whole.sharedStarts[0].saved)) <= 8); // Nothing where only two share a start: two loads cost more than // one start saves, until the start is long. - ensure("two with a middling start: nothing", - ALScriptWeigh::slua("ll.OwnerSay(\"twenty bytes of start, then one\")\nll.OwnerSay(\"twenty bytes of start, then two\")\n").sharedStarts.empty()); - ensure("two with a long one: worth it", - ALScriptWeigh::slua("ll.OwnerSay(\"a start long enough to be worth keeping once, then one\")\n" + UNSCOPED_INFO("two with a middling start: nothing"); + REQUIRE(ALScriptWeigh::slua("ll.OwnerSay(\"twenty bytes of start, then one\")\nll.OwnerSay(\"twenty bytes of start, then two\")\n").sharedStarts.empty()); + UNSCOPED_INFO("two with a long one: worth it"); + REQUIRE(ALScriptWeigh::slua("ll.OwnerSay(\"a start long enough to be worth keeping once, then one\")\n" "ll.OwnerSay(\"a start long enough to be worth keeping once, then two\")\n") .sharedStarts.size() == 1); // Never inside a character. const ALScriptWeight cut = ALScriptWeigh::slua("ll.OwnerSay(\"a start long enough to be worth keeping once \xC3\xA9t\xC3\xA9\")\n" "ll.OwnerSay(\"a start long enough to be worth keeping once \xC3\xA8re\")\n"); - ensure("cut before the character", cut.sharedStarts.size() == 1 && cut.sharedStarts[0].start == "a start long enough to be worth keeping once "); + { + INFO("cut before the character"); + REQUIRE(cut.sharedStarts.size() == 1); + CHECK(cut.sharedStarts[0].start == "a start long enough to be worth keeping once "); + } } // A shared start kept once in the script itself: each literal of its @@ -812,8 +1092,7 @@ namespace tut // works out as it compiles and a string not of them left alone. What it // weighs after is what was said apart, with nothing left to keep once; // and it checks, strict as well. - template<> template<> - void alscriptweight_object::test<15>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: a shared start kept once in the script itself", "[ALScriptWeight]") { const std::string start = "You have touched the object of the day: "; const std::string script = "--!strict\n" // 0 @@ -825,52 +1104,75 @@ namespace tut "print \"" + start + "delta\"\n" // 6 "ll.OwnerSay(\"other\")\n"; // 7 const ALScriptWeight before = ALScriptWeigh::slua(script); - ensure("weighed: " + before.error, before.compiled); - ensure_equals("one start", before.sharedStarts.size(), size_t(1)); + UNSCOPED_INFO("weighed: " + before.error); + CHECK(before.compiled); + UNSCOPED_INFO("one start"); + REQUIRE(before.sharedStarts.size() == size_t(1)); const std::vector strings = textsOf(before, before.sharedStarts[0]); - ensure("not the require's path, which is no string to keep once", - std::find(strings.begin(), strings.end(), start + "x") == strings.end()); + UNSCOPED_INFO("not the require's path, which is no string to keep once"); + REQUIRE(std::find(strings.begin(), strings.end(), start + "x") == strings.end()); ALLuauSharedStart::Rewrite rewrite; std::string error; const bool done = ALLuauSharedStart::rewrite(script, before.sharedStarts[0].start, strings, rewrite, error); - ensure("rewritten: " + error, done); - ensure_equals("its name", rewrite.name, std::string("sharedStart")); + UNSCOPED_INFO("rewritten: " + error); + CHECK(done); + UNSCOPED_INFO("its name"); + CHECK(rewrite.name == std::string("sharedStart")); const std::string after = applied(script, rewrite); const auto has = [&after](const std::string& text) { return after.find(text) != std::string::npos; }; - ensure("the local past the --! comment: " + after, after.rfind("--!strict\nlocal sharedStart\nsharedStart = \"" + start + "\"\n\n", 0) == 0); - ensure("a call's: " + after, has("ll.OwnerSay(sharedStart .. \"alpha\")")); - ensure("constant strings joined, still folded: " + after, has("ll.OwnerSay(sharedStart .. (\"bravo\" .. \"!\"))")); - ensure("a table's value, not its key: " + after, has("[\"" + start + "key\"] = sharedStart .. \"charlie\"")); - ensure("a length Luau works out, left: " + after, has("print(#\"" + start + "delta\")")); - ensure("and a call with no brackets: " + after, has("print (sharedStart .. \"delta\")")); - ensure("a require's path left: " + after, has("require(\"" + start + "x\")")); - ensure("another string left: " + after, has("ll.OwnerSay(\"other\")")); + UNSCOPED_INFO("the local past the --! comment: " + after); + CHECK(after.rfind("--!strict\nlocal sharedStart\nsharedStart = \"" + start + "\"\n\n", 0) == 0); + UNSCOPED_INFO("a call's: " + after); + CHECK(has("ll.OwnerSay(sharedStart .. \"alpha\")")); + UNSCOPED_INFO("constant strings joined, still folded: " + after); + CHECK(has("ll.OwnerSay(sharedStart .. (\"bravo\" .. \"!\"))")); + UNSCOPED_INFO("a table's value, not its key: " + after); + CHECK(has("[\"" + start + "key\"] = sharedStart .. \"charlie\"")); + UNSCOPED_INFO("a length Luau works out, left: " + after); + CHECK(has("print(#\"" + start + "delta\")")); + UNSCOPED_INFO("and a call with no brackets: " + after); + CHECK(has("print (sharedStart .. \"delta\")")); + UNSCOPED_INFO("a require's path left: " + after); + CHECK(has("require(\"" + start + "x\")")); + UNSCOPED_INFO("another string left: " + after); + CHECK(has("ll.OwnerSay(\"other\")")); // What it weighs now, and that it checks. const ALScriptWeight weighed = ALScriptWeigh::slua(after); - ensure("weighed after: " + weighed.error, weighed.compiled); + UNSCOPED_INFO("weighed after: " + weighed.error); + CHECK(weighed.compiled); // Nothing left to keep once: the start is the local's one value, // kept once already, and the require's path no literal to keep. - ensure_equals("nothing offered again", weighed.sharedStarts.size(), size_t(0)); - ensure(llformat("lighter: %zu then %zu", before.total, weighed.total), weighed.total < before.total); + UNSCOPED_INFO("nothing offered again"); + REQUIRE(weighed.sharedStarts.size() == size_t(0)); + UNSCOPED_INFO(llformat("lighter: %zu then %zu", before.total, weighed.total)); + CHECK(weighed.total < before.total); ALLuauService service; llifstream in(std::string(AL_LSL_DEFINITIONS_DIR) + "/secondlife.d.luau", std::ios::binary); std::stringstream definitions; definitions << in.rdbuf(); - ensure("definitions: " + error, service.loadDefinitions(definitions.str(), error)); + UNSCOPED_INFO("definitions: " + error); + REQUIRE(service.loadDefinitions(definitions.str(), error)); std::string said; for (const ALScriptProblem& p : service.check(after)) { said += p.severity == ALScriptProblem::Severity::Error && p.message.find("require") == std::string::npos ? p.message + "\n" : std::string(); } - ensure("checks, strict:\n" + said + "---\n" + after, said.empty()); + UNSCOPED_INFO("checks, strict:\n" + said + "---\n" + after); + REQUIRE(said.empty()); - ensure("nothing to make of a script without them", - !ALLuauSharedStart::rewrite("print(\"x\")\n", start, strings, rewrite, error) && !error.empty()); + { + INFO("nothing to make of a script without them"); + CHECK_FALSE(ALLuauSharedStart::rewrite("print(\"x\")\n", start, strings, rewrite, error)); + REQUIRE_FALSE(error.empty()); + } const bool taken = ALLuauSharedStart::rewrite("local sharedStart = 1\nprint(\"" + start + "alpha\")\n", start, { start + "alpha" }, rewrite, error); - ensure("rewritten with a name taken: " + error, taken); - ensure_equals("a name it has taken, another", rewrite.name, std::string("sharedStart2")); - ensure_equals("quoted as SLua reads it", ALLuauSharedStart::quoted("a\"b\\c\n\x01" "2"), std::string("\"a\\\"b\\\\c\\n\\0012\"")); + UNSCOPED_INFO("rewritten with a name taken: " + error); + CHECK(taken); + UNSCOPED_INFO("a name it has taken, another"); + CHECK(rewrite.name == std::string("sharedStart2")); + UNSCOPED_INFO("quoted as SLua reads it"); + CHECK(ALLuauSharedStart::quoted("a\"b\\c\n\x01" "2") == std::string("\"a\\\"b\\\\c\\n\\0012\"")); } // Strings loaded in closures: each function that loads one, and each @@ -878,8 +1180,7 @@ namespace tut // script's body close it over as it ends, which the saving counts. // What it says is about what the compiler makes: a closure shared // before frees its constant made anew, a byte or two each. - template<> template<> - void alscriptweight_object::test<16>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: strings loaded in closures: the saving counts each closure's capture", "[ALScriptWeight]") { const std::string start = "You have touched the object of the day: "; const std::vector rests = { "alpha", "bravo", "charlie" }; @@ -893,13 +1194,20 @@ namespace tut } const ALScriptWeight as_whole = ALScriptWeigh::slua(whole); const ALScriptWeight as_apart = ALScriptWeigh::slua(apart); - ensure("both compiled: " + as_whole.error + as_apart.error, as_whole.compiled && as_apart.compiled); - ensure_equals("one start", as_whole.sharedStarts.size(), size_t(1)); + { + INFO("both compiled: " + as_whole.error + as_apart.error); + CHECK(as_whole.compiled); + CHECK(as_apart.compiled); + } + UNSCOPED_INFO("one start"); + REQUIRE(as_whole.sharedStarts.size() == size_t(1)); const ALScriptWeight::SharedStart& shared = as_whole.sharedStarts.front(); - ensure_equals("the start the three share", shared.start, start); + UNSCOPED_INFO("the start the three share"); + CHECK(shared.start == start); for (const size_t i : shared.strings) { - ensure_equals("each loaded in a function of its own", as_whole.strings[i].functions.size(), size_t(1)); + UNSCOPED_INFO("each loaded in a function of its own"); + REQUIRE(as_whole.strings[i].functions.size() == size_t(1)); } size_t before = 0; size_t after = 1 + start.size(); @@ -910,10 +1218,11 @@ namespace tut } const size_t cost = after + rests.size() * (ALScriptWeigh::SHARED_START_PER_LOAD + ALScriptWeigh::SHARED_START_PER_FUNCTION) + ALScriptWeigh::SHARED_START_ONCE + ALScriptWeigh::SHARED_START_CAPTURED; - ensure_equals("what it saves, a capture for each closure", shared.saved, before - cost); + UNSCOPED_INFO("what it saves, a capture for each closure"); + CHECK(shared.saved == before - cost); const S64 really = S64(as_whole.total) - S64(as_apart.total); - ensure(llformat("saves about what was said: %lld against %zu", (long long)really, shared.saved), - std::abs(really - S64(shared.saved)) <= S64(8 + 2 * rests.size())); + UNSCOPED_INFO(llformat("saves about what was said: %lld against %zu", (long long)really, shared.saved)); + REQUIRE(std::abs(really - S64(shared.saved)) <= S64(8 + 2 * rests.size())); } // Where the start's local goes: before the first of the script that is @@ -923,8 +1232,7 @@ namespace tut // comment on it. What would not parse is refused, as is a start not // every string of which is written out; and a byte of no character is // quoted in hex. - template<> template<> - void alscriptweight_object::test<17>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: where the start's local goes: before the first thing that is not a comment", "[ALScriptWeight]") { const std::string start = "You have touched the object of the day: "; const std::vector strings = { start + "alpha", start + "bravo" }; @@ -934,40 +1242,60 @@ namespace tut ALLuauSharedStart::Rewrite rewrite; std::string why; const bool done = ALLuauSharedStart::rewrite(script, start, strings, rewrite, why); - ensure("rewritten: " + why + "\n" + script, done); + UNSCOPED_INFO("rewritten: " + why + "\n" + script); + REQUIRE(done); return alscriptweight_data::applied(script, rewrite); }; const auto heads = [&](const std::string& head, const std::string& text) { return text.rfind(head + local, 0) == 0; }; std::string after = kept("-- Door script\n--!strict\n" + said); - ensure("past an ordinary comment and the --! one: " + after, heads("-- Door script\n--!strict\n", after)); - ensure("and the strings kept: " + after, after.find("ll.OwnerSay(sharedStart .. \"alpha\")") != std::string::npos); + UNSCOPED_INFO("past an ordinary comment and the --! one: " + after); + CHECK(heads("-- Door script\n--!strict\n", after)); + UNSCOPED_INFO("and the strings kept: " + after); + REQUIRE(after.find("ll.OwnerSay(sharedStart .. \"alpha\")") != std::string::npos); after = kept("\n--!strict\n" + said); - ensure("past a blank first line: " + after, heads("\n--!strict\n", after)); + UNSCOPED_INFO("past a blank first line: " + after); + CHECK(heads("\n--!strict\n", after)); after = kept("--!strict\n\n--!optimize 2\n" + said); - ensure("past --! comments apart: " + after, heads("--!strict\n\n--!optimize 2\n", after)); + UNSCOPED_INFO("past --! comments apart: " + after); + CHECK(heads("--!strict\n\n--!optimize 2\n", after)); after = kept("--!strict\n " + said); - ensure("at the start of an indented line: " + after, heads("--!strict\n", after) && after.find(local + " ll.OwnerSay") != std::string::npos); + { + INFO("at the start of an indented line: " + after); + CHECK(heads("--!strict\n", after)); + REQUIRE(after.find(local + " ll.OwnerSay") != std::string::npos); + } after = kept("--[[ The door ]] " + said); - ensure("after a comment on the same line: " + after, heads("--[[ The door ]] ", after)); + UNSCOPED_INFO("after a comment on the same line: " + after); + CHECK(heads("--[[ The door ]] ", after)); ALLuauSharedStart::Rewrite rewrite; std::string error; - ensure("a call's string on a line of its own would be ambiguous bracketed", - !ALLuauSharedStart::rewrite("ll.OwnerSay\n\"" + start + "alpha\"\nll.OwnerSay(\"" + start + "bravo\")\n", start, strings, rewrite, error) && - error.find("does not parse") != std::string::npos); - ensure("nothing made of it", rewrite.edits.empty()); - ensure("a string not written out: " + error, - !ALLuauSharedStart::rewrite("ll.OwnerSay(\"" + start + "alpha\")\n", start, strings, rewrite, error) && - error.find("not every one") != std::string::npos); - ensure("none written out, a local joined on: " + error, - !ALLuauSharedStart::rewrite("local greeting = \"" + start + "\"\nll.OwnerSay(greeting .. \"alpha\")\nll.OwnerSay(greeting .. \"bravo\")\n", - start, strings, rewrite, error) && - error.find("none of these strings") != std::string::npos); + { + INFO("a call's string on a line of its own would be ambiguous bracketed"); + REQUIRE_FALSE(ALLuauSharedStart::rewrite("ll.OwnerSay\n\"" + start + "alpha\"\nll.OwnerSay(\"" + start + "bravo\")\n", start, strings, rewrite, error)); + REQUIRE(error.find("does not parse") != std::string::npos); + } + UNSCOPED_INFO("nothing made of it"); + REQUIRE(rewrite.edits.empty()); + { + INFO("a string not written out: " + error); + CHECK_FALSE(ALLuauSharedStart::rewrite("ll.OwnerSay(\"" + start + "alpha\")\n", start, strings, rewrite, error)); + REQUIRE(error.find("not every one") != std::string::npos); + } + { + 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)); + REQUIRE(error.find("none of these strings") != std::string::npos); + } - ensure_equals("a byte of no character in hex, a whole one as it is", ALLuauSharedStart::quoted("\xFF" "a\xC3\xA9\xC3"), + UNSCOPED_INFO("a byte of no character in hex, a whole one as it is"); + CHECK(ALLuauSharedStart::quoted("\xFF" "a\xC3\xA9\xC3") == std::string("\"\\xFFa\xC3\xA9\\xC3\"")); - ensure_equals("an overlong form is no character", ALLuauSharedStart::quoted("\xC0\x80"), std::string("\"\\xC0\\x80\"")); - ensure_equals("four bytes, whole", ALLuauSharedStart::quoted("\xF0\x9F\x98\x80"), std::string("\"\xF0\x9F\x98\x80\"")); + UNSCOPED_INFO("an overlong form is no character"); + CHECK(ALLuauSharedStart::quoted("\xC0\x80") == std::string("\"\\xC0\\x80\"")); + UNSCOPED_INFO("four bytes, whole"); + CHECK(ALLuauSharedStart::quoted("\xF0\x9F\x98\x80") == std::string("\"\xF0\x9F\x98\x80\"")); } // What is not offered: strings Luau makes as it compiles -- an @@ -976,20 +1304,19 @@ namespace tut // is not, the start kept once already, and a module's strings in the // source's places. What is: strings a `..` that is not folded goes on // to, each a literal of its own, kept once in place. - template<> template<> - void alscriptweight_object::test<18>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: what is not offered: strings Luau makes as it compiles", "[ALScriptWeight]") { const std::string start = "You have touched the object of the day: "; - ensure("an interpolated string's pattern", - ALScriptWeigh::slua("local function said(name: string)\n" + UNSCOPED_INFO("an interpolated string's pattern"); + REQUIRE(ALScriptWeigh::slua("local function said(name: string)\n" " ll.OwnerSay(`" + start + "{name}, first`)\n" " ll.OwnerSay(`" + start + "{name}, second`)\n" " ll.OwnerSay(`" + start + "{name}, third`)\n" "end\n" "said(\"x\")\n") .sharedStarts.empty()); - ensure("strings joined on a local's constant value", - ALScriptWeigh::slua("local greeting = \"" + start + "\"\n" + UNSCOPED_INFO("strings joined on a local's constant value"); + REQUIRE(ALScriptWeigh::slua("local greeting = \"" + start + "\"\n" "ll.OwnerSay(greeting .. \"alpha\")\n" "ll.OwnerSay(greeting .. \"bravo\")\n" "ll.OwnerSay(greeting .. \"charlie\")\n") @@ -998,9 +1325,10 @@ namespace tut return "type Mood = " + type + "\nlocal function feel(mood: Mood) end\n" "feel(\"" + start + "happy\")\nfeel(\"" + start + "sad\")\nfeel(\"" + start + "bored\")\n"; }; - ensure_equals("given for a string", ALScriptWeigh::slua(feelings("string")).sharedStarts.size(), size_t(1)); - ensure("given for a singleton", - ALScriptWeigh::slua(feelings("\"" + start + "happy\" | \"" + start + "sad\" | \"" + start + "bored\"")).sharedStarts.empty()); + UNSCOPED_INFO("given for a string"); + REQUIRE(ALScriptWeigh::slua(feelings("string")).sharedStarts.size() == size_t(1)); + UNSCOPED_INFO("given for a singleton"); + REQUIRE(ALScriptWeigh::slua(feelings("\"" + start + "happy\" | \"" + start + "sad\" | \"" + start + "bored\"")).sharedStarts.empty()); // Kept once, with an interpolated string sharing the start beside // them: neither it nor the start's local is offered again. @@ -1012,15 +1340,23 @@ namespace tut "ll.OwnerSay(\"" + start + "charlie\")\n" "said(\"x\")\n"; const ALScriptWeight kept = ALScriptWeigh::slua(kept_script); - ensure("weighed: " + kept.error, kept.compiled && kept.sharedStarts.size() == 1); - ensure_equals("the three written out, not the pattern", kept.sharedStarts[0].strings.size(), size_t(3)); + { + INFO("weighed: " + kept.error); + CHECK(kept.compiled); + REQUIRE(kept.sharedStarts.size() == 1); + } + UNSCOPED_INFO("the three written out, not the pattern"); + REQUIRE(kept.sharedStarts[0].strings.size() == size_t(3)); ALLuauSharedStart::Rewrite rewrite; std::string error; bool done = ALLuauSharedStart::rewrite(kept_script, start, textsOf(kept, kept.sharedStarts[0]), rewrite, error); - ensure("rewritten: " + error, done); + UNSCOPED_INFO("rewritten: " + error); + CHECK(done); const ALScriptWeight again = ALScriptWeigh::slua(applied(kept_script, rewrite)); - ensure("weighed again: " + again.error, again.compiled); - ensure_equals("nothing offered again", again.sharedStarts.size(), size_t(0)); + UNSCOPED_INFO("weighed again: " + again.error); + CHECK(again.compiled); + UNSCOPED_INFO("nothing offered again"); + REQUIRE(again.sharedStarts.size() == size_t(0)); // Joined on to a name, each piece is loaded on its own. const std::string joined = "local function touched(name: string)\n" @@ -1030,13 +1366,24 @@ namespace tut "end\n" "touched(\"x\")\n"; const ALScriptWeight pieces = ALScriptWeigh::slua(joined); - ensure("weighed: " + pieces.error, pieces.compiled && pieces.sharedStarts.size() == 1 && pieces.sharedStarts[0].start == start); + { + INFO("weighed: " + pieces.error); + CHECK(pieces.compiled); + REQUIRE(pieces.sharedStarts.size() == 1); + CHECK(pieces.sharedStarts[0].start == start); + } done = ALLuauSharedStart::rewrite(joined, start, textsOf(pieces, pieces.sharedStarts[0]), rewrite, error); - ensure("rewritten: " + error, done); + UNSCOPED_INFO("rewritten: " + error); + CHECK(done); const std::string pieces_after = applied(joined, rewrite); - ensure("each piece kept in place: " + pieces_after, pieces_after.find("ll.OwnerSay(name .. sharedStart .. \"alpha\" .. \"!\")") != std::string::npos); + UNSCOPED_INFO("each piece kept in place: " + pieces_after); + REQUIRE(pieces_after.find("ll.OwnerSay(name .. sharedStart .. \"alpha\" .. \"!\")") != std::string::npos); const ALScriptWeight lighter = ALScriptWeigh::slua(pieces_after); - ensure(llformat("lighter: %zu then %zu", pieces.total, lighter.total), lighter.compiled && lighter.total < pieces.total); + { + INFO(llformat("lighter: %zu then %zu", pieces.total, lighter.total)); + CHECK(lighter.compiled); + CHECK(lighter.total < pieces.total); + } // A module's strings, in the source's places, are not the script's // to keep once: the first three lines are a module's. @@ -1067,42 +1414,58 @@ namespace tut return map; }; const ALScriptWeight bundled = ALScriptWeigh::slua(bundle); - ensure("weighed: " + bundled.error, bundled.compiled && bundled.sharedStarts.size() == 1); - ensure_equals("all the script's: kept", bundled.inSource(mapped(0)).sharedStarts.size(), size_t(1)); - ensure_equals("two a module's: nothing", bundled.inSource(mapped(3)).sharedStarts.size(), size_t(0)); + { + INFO("weighed: " + bundled.error); + CHECK(bundled.compiled); + REQUIRE(bundled.sharedStarts.size() == 1); + } + UNSCOPED_INFO("all the script's: kept"); + REQUIRE(bundled.inSource(mapped(0)).sharedStarts.size() == size_t(1)); + UNSCOPED_INFO("two a module's: nothing"); + REQUIRE(bundled.inSource(mapped(3)).sharedStarts.size() == size_t(0)); } // LSL's constants are folded as the grid's compiler folds them: a // constant read is its value in the bytecode, as if written out, and // not a global looked up by its name. - template<> template<> - void alscriptweight_object::test<19>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: LSL's constants are folded as the grid's compiler folds them", "[ALScriptWeight]") { const ALScriptWeight named = ALScriptWeigh::slua("local x = PRIM_SIZE\nprint(x)\n"); const ALScriptWeight written = ALScriptWeigh::slua("local x = 7\nprint(x)\n"); - ensure("both compiled", named.compiled && written.compiled); - ensure_equals("the same as its value written out", named.total, written.total); + { + INFO("both compiled"); + CHECK(named.compiled); + CHECK(written.compiled); + } + UNSCOPED_INFO("the same as its value written out"); + CHECK(named.total == written.total); bool looked_up = false; for (const ALScriptWeight::String& one : named.strings) { looked_up = looked_up || one.text == "PRIM_SIZE"; } - ensure("not its name among the strings", !looked_up); + UNSCOPED_INFO("not its name among the strings"); + CHECK_FALSE(looked_up); } // A heavy string is named on one line as a string of the table is, a // return in it written out as a break and a tab are. - template<> template<> - void alscriptweight_object::test<20>() + TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: a heavy string is named on one line as a string of the table is", "[ALScriptWeight]") { const ALScriptWeight weight = ALScriptWeigh::slua("local s = \"a\\rb\\tc" + std::string(300, 'x') + "\"\nprint(s)\n"); - ensure("compiled: " + weight.error, weight.compiled); + UNSCOPED_INFO("compiled: " + weight.error); + CHECK(weight.compiled); const ALScriptWeight::Part* found = nullptr; for (const ALScriptWeight::Part& one : weight.parts) { found = one.kind == ALScriptWeight::Part::Kind::Constant && one.name != "strings" ? &one : found; } - ensure("a part of its own:" + listed(weight), found != nullptr); - ensure("named on one line: " + found->name, found->name.rfind("\"a\\rb\\tc", 0) == 0 && found->name.find('\r') == std::string::npos); + UNSCOPED_INFO("a part of its own:" + listed(weight)); + REQUIRE(found != nullptr); + { + INFO("named on one line: " + found->name); + REQUIRE(found->name.rfind("\"a\\rb\\tc", 0) == 0); + REQUIRE(found->name.find('\r') == std::string::npos); + } } } diff --git a/indra/alscript/tests/alsourcemap_test.cpp b/indra/alscript/tests/alsourcemap_test.cpp index 186641fc0f..1cadd08022 100644 --- a/indra/alscript/tests/alsourcemap_test.cpp +++ b/indra/alscript/tests/alsourcemap_test.cpp @@ -25,7 +25,7 @@ #include "../preprocessor/alsourcemap.h" -#include "../test/lltut.h" +#include namespace { @@ -43,20 +43,10 @@ namespace } } -namespace tut +namespace { - struct alsourcemap_data - { - }; - - typedef test_group alsourcemap_group; - typedef alsourcemap_group::object alsourcemap_object; - alsourcemap_group alsourcemap_instance("alsourcemap"); - - template<> template<> - void alsourcemap_object::test<1>() + TEST_CASE("alsourcemap: verbatim tokens in a run are one segment; a macro or line or file starts anew", "[alsourcemap]") { - set_test_name("verbatim tokens that carry on from one another are one segment; a macro's product, another line or another file start their own"); ALSourceMap map; map.addFile("script", "script"); map.addFile("lib", "disk:/lib"); @@ -67,19 +57,39 @@ namespace tut map.add(segment(0, 14, 1, 0, 0, 12)); // ; -- not where the last left off map.add(segment(1, 0, 4, 1, 0, 0)); // another file map.finish(); - ensure("the first three one", map.toSource(0, 8).column == 8 && map.toSource(0, 0).line == 0); + { + INFO("the first three one"); + CHECK(map.toSource(0, 8).column == 8); + CHECK(map.toSource(0, 0).line == 0); + } ALSourceMap::Loc begin, end; - ensure("one stretch copied as it stands", map.verbatimSpan(0, 0, 9, begin, end) && begin.column == 0 && end.column == 9); - ensure("the macro's to its invocation", map.toSource(0, 11).line == 3 && map.toSource(0, 11).column == 2); - ensure("the next its own", map.toSource(0, 14).column == 12); - ensure("the other file's", map.toSource(1, 2).file == 1 && map.toSource(1, 2).column == 2); - ensure("and back", map.toExpanded(0, 0, 8).line == 0 && map.toExpanded(0, 0, 8).column == 8); + { + INFO("one stretch copied as it stands"); + CHECK(map.verbatimSpan(0, 0, 9, begin, end)); + CHECK(begin.column == 0); + CHECK(end.column == 9); + } + { + INFO("the macro's to its invocation"); + CHECK(map.toSource(0, 11).line == 3); + CHECK(map.toSource(0, 11).column == 2); + } + UNSCOPED_INFO("the next its own"); + CHECK(map.toSource(0, 14).column == 12); + { + INFO("the other file's"); + CHECK(map.toSource(1, 2).file == 1); + CHECK(map.toSource(1, 2).column == 2); + } + { + INFO("and back"); + CHECK(map.toExpanded(0, 0, 8).line == 0); + CHECK(map.toExpanded(0, 0, 8).column == 8); + } } - template<> template<> - void alsourcemap_object::test<2>() + TEST_CASE("alsourcemap: the segment at or before a column on a long line; nothing on a line with none", "[alsourcemap]") { - set_test_name("a long line of a macro's products looked up: the segment at or before the column, the first before any, nothing on a line with none"); ALSourceMap map; map.addFile("script", "script"); for (S32 i = 0; i < 3000; ++i) @@ -88,17 +98,23 @@ namespace tut } map.add(segment(2, 0, 3, 0, 5, 0)); map.finish(); - ensure_equals("the first", map.toSource(0, 4).column, 0); - ensure_equals("in the middle", map.toSource(0, 4 + 1500 * 3 + 1).column, 1500); - ensure_equals("before any: the first", map.toSource(0, 1).column, 0); - ensure_equals("past the last: the last", map.toSource(0, 20000).column, 2999); - ensure("a line with none: nowhere", !map.toSource(1, 0).found() && map.toSource(2, 1).found()); + UNSCOPED_INFO("the first"); + CHECK(map.toSource(0, 4).column == 0); + UNSCOPED_INFO("in the middle"); + CHECK(map.toSource(0, 4 + 1500 * 3 + 1).column == 1500); + UNSCOPED_INFO("before any: the first"); + CHECK(map.toSource(0, 1).column == 0); + UNSCOPED_INFO("past the last: the last"); + CHECK(map.toSource(0, 20000).column == 2999); + { + INFO("a line with none: nowhere"); + REQUIRE_FALSE(map.toSource(1, 0).found()); + REQUIRE(map.toSource(2, 1).found()); + } } - template<> template<> - void alsourcemap_object::test<3>() + TEST_CASE("alsourcemap: a map composed through another reads a stretch through each inner segment", "[alsourcemap]") { - set_test_name("a map read through another: one copied stretch over several of the other's segments read through each, exact where they are"); // The inner map: its output line 0 is `abc` from the script's line // 4, then `MACRO` a macro made at line 9 column 1, then `xyz` from // the include's line 2. @@ -116,26 +132,49 @@ namespace tut outer.add(segment(1, 0, 3, 0, 7, 0)); outer.finish(); const ALSourceMap through = outer.composed(inner); - ensure("the script's own, exact", through.toSource(0, 3).file == 0 && through.toSource(0, 3).line == 4 && through.toSource(0, 3).column == 1); - ensure("the macro's, to its invocation", through.toSource(0, 7).line == 9 && through.toSource(0, 7).column == 1); - ensure("the include's, exact", through.toSource(0, 11).file == 1 && through.toSource(0, 11).column == 1); - ensure("a line the inner has nothing for: dropped", !through.toSource(1, 0).found()); + { + INFO("the script's own, exact"); + CHECK(through.toSource(0, 3).file == 0); + CHECK(through.toSource(0, 3).line == 4); + CHECK(through.toSource(0, 3).column == 1); + } + { + INFO("the macro's, to its invocation"); + CHECK(through.toSource(0, 7).line == 9); + CHECK(through.toSource(0, 7).column == 1); + } + { + INFO("the include's, exact"); + CHECK(through.toSource(0, 11).file == 1); + CHECK(through.toSource(0, 11).column == 1); + } + UNSCOPED_INFO("a line the inner has nothing for: dropped"); + REQUIRE_FALSE(through.toSource(1, 0).found()); ALSourceMap::Loc begin, end; - ensure("the copied stretches stay verbatim", through.verbatimSpan(0, 2, 5, begin, end) && !through.verbatimSpan(0, 5, 10, begin, end)); + { + INFO("the copied stretches stay verbatim"); + CHECK(through.verbatimSpan(0, 2, 5, begin, end)); + CHECK_FALSE(through.verbatimSpan(0, 5, 10, begin, end)); + } } - template<> template<> - void alsourcemap_object::test<4>() + TEST_CASE("alsourcemap: a text mapped to itself: every line its own even the last without a newline", "[alsourcemap]") { - set_test_name("a text mapped to itself: every line its own, the last without a newline too"); const ALSourceMap map = ALSourceMap::identity("one\ntwo three\nlast", "same"); - ensure("named", map.files().size() == 1 && map.files()[0].name == "same"); - ensure("each line", map.toSource(1, 5).line == 1 && map.toSource(1, 5).column == 5 && map.toSource(2, 2).line == 2); + { + INFO("named"); + REQUIRE(map.files().size() == 1); + CHECK(map.files()[0].name == "same"); + } + { + INFO("each line"); + CHECK(map.toSource(1, 5).line == 1); + CHECK(map.toSource(1, 5).column == 5); + CHECK(map.toSource(2, 2).line == 2); + } } - template<> template<> - void alsourcemap_object::test<5>() + TEST_CASE("alsourcemap: a verbatim stretch on a long line: the first segment to reach the column", "[alsourcemap]") { - set_test_name("a verbatim stretch found on a long line: the first segment that reaches the column, as a walk from the line's start finds it"); ALSourceMap map; map.addFile("script", "script"); // A long line of alternating pieces: a macro's product, then a @@ -150,11 +189,25 @@ namespace tut map.finish(); ALSourceMap::Loc begin, end; const S32 at = 7 * 250 + 4; - ensure("within a token copied, deep in the line", map.verbatimSpan(0, at + 1, at + 3, begin, end) && begin.line == 1 && begin.column == 2501 && end.column == 2503); - ensure("an insertion just after it goes with it", map.verbatimSpan(0, at + 3, at + 3, begin, end) && begin.column == 2503); - ensure("an insertion just before it goes with the macro's product before it", !map.verbatimSpan(0, at, at, begin, end)); - ensure("and so does a stretch from its first column", !map.verbatimSpan(0, at, at + 3, begin, end)); - ensure("into the macro's product after it: not the source's", !map.verbatimSpan(0, at + 1, at + 5, begin, end)); - ensure("past the line's end", !map.verbatimSpan(0, out + 2, out + 3, begin, end)); + { + INFO("within a token copied, deep in the line"); + REQUIRE(map.verbatimSpan(0, at + 1, at + 3, begin, end)); + CHECK(begin.line == 1); + CHECK(begin.column == 2501); + CHECK(end.column == 2503); + } + { + INFO("an insertion just after it goes with it"); + CHECK(map.verbatimSpan(0, at + 3, at + 3, begin, end)); + CHECK(begin.column == 2503); + } + UNSCOPED_INFO("an insertion just before it goes with the macro's product before it"); + CHECK_FALSE(map.verbatimSpan(0, at, at, begin, end)); + UNSCOPED_INFO("and so does a stretch from its first column"); + CHECK_FALSE(map.verbatimSpan(0, at, at + 3, begin, end)); + UNSCOPED_INFO("into the macro's product after it: not the source's"); + CHECK_FALSE(map.verbatimSpan(0, at + 1, at + 5, begin, end)); + UNSCOPED_INFO("past the line's end"); + CHECK_FALSE(map.verbatimSpan(0, out + 2, out + 3, begin, end)); } } diff --git a/indra/alscript/tests/aluploadheader_test.cpp b/indra/alscript/tests/aluploadheader_test.cpp index 027907f959..113f67a595 100644 --- a/indra/alscript/tests/aluploadheader_test.cpp +++ b/indra/alscript/tests/aluploadheader_test.cpp @@ -28,13 +28,13 @@ #include "lldate.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct aluploadheader_data { @@ -65,25 +65,19 @@ namespace tut } }; - typedef test_group aluploadheader_group; - typedef aluploadheader_group::object aluploadheader_object; - aluploadheader_group aluploadheader_instance("aluploadheader"); - - template<> template<> - void aluploadheader_object::test<1>() + TEST_CASE_METHOD(aluploadheader_data, "aluploadheader: ours is written byte for byte in either language with only the fields it has", "[aluploadheader]") { - set_test_name("ours is written byte for byte, a field only where there is one, in each language's comments"); const ALUploadHeader header = aluploadheader_data::full(); - ensure_equals("LSL", - header.write(false), + UNSCOPED_INFO("LSL"); + CHECK(header.write(false) == std::string("// ================ alchemy meta ================\n" "// @file net/door.lsl\n" "// @hash xxh128:0123456789abcdef0123456789abcdef\n" "// @date 2026-10-07 14:03:11\n" "// @creator Rye Resident\n" "// ==============================================\n")); - ensure_equals("SLua", - header.write(true), + UNSCOPED_INFO("SLua"); + CHECK(header.write(true) == std::string("-- ================ alchemy meta ================\n" "-- @file net/door.lsl\n" "-- @hash xxh128:0123456789abcdef0123456789abcdef\n" @@ -94,8 +88,8 @@ namespace tut ALUploadHeader bare; bare.hash = "xxh128:00"; bare.date = "now"; - ensure_equals("no file and no creator", - bare.write(false), + UNSCOPED_INFO("no file and no creator"); + CHECK(bare.write(false) == std::string("// ================ alchemy meta ================\n// @hash xxh128:00\n// @date now\n" "// ==============================================\n")); @@ -103,21 +97,22 @@ namespace tut { ALUploadHeader absolute = bare; absolute.file = where; - ensure_equals(std::string("never absolute: ") + where, absolute.write(false), bare.write(false)); + UNSCOPED_INFO(std::string("never absolute: ") + where); + REQUIRE(absolute.write(false) == bare.write(false)); } ALUploadHeader aliased = bare; aliased.file = "@lib/util.luau"; - ensure("an alias is no root", aliased.write(true).find("-- @file @lib/util.luau\n") != std::string::npos); + UNSCOPED_INFO("an alias is no root"); + REQUIRE(aliased.write(true).find("-- @file @lib/util.luau\n") != std::string::npos); ALUploadHeader broken = bare; broken.creator = "two\nlines\r"; - ensure("a line break in a field stays in its comment", broken.write(false).find("// @creator two lines \n") != std::string::npos); + UNSCOPED_INFO("a line break in a field stays in its comment"); + REQUIRE(broken.write(false).find("// @creator two lines \n") != std::string::npos); } - template<> template<> - void aluploadheader_object::test<2>() + TEST_CASE_METHOD(aluploadheader_data, "aluploadheader: ours reads back and take() steps past it to the code", "[aluploadheader]") { - set_test_name("ours reads back, and take() steps past it to the code"); for (const bool lua : { false, true }) { const ALUploadHeader written = aluploadheader_data::full(); @@ -126,29 +121,39 @@ namespace tut std::string_view rest = text; const std::optional back = ALUploadHeader::take(rest, lua); const std::string name = lua ? "SLua: " : "LSL: "; - ensure(name + "read", back.has_value()); - ensure(name + "ours", back->ours); - ensure_equals(name + "file", back->file, written.file); - ensure_equals(name + "hash", back->hash, written.hash); - ensure_equals(name + "date", back->date, written.date); - ensure_equals(name + "creator", back->creator, written.creator); - ensure_equals(name + "no key", back->creatorId, std::string()); - ensure_equals(name + "the code is what is left", std::string(rest), code); - ensure(name + "not in the other language's comments", !ALUploadHeader::parse(text, !lua).has_value()); + UNSCOPED_INFO(name + "read"); + REQUIRE(back.has_value()); + UNSCOPED_INFO(name + "ours"); + REQUIRE(back->ours); + UNSCOPED_INFO(name + "file"); + REQUIRE(back->file == written.file); + UNSCOPED_INFO(name + "hash"); + REQUIRE(back->hash == written.hash); + UNSCOPED_INFO(name + "date"); + REQUIRE(back->date == written.date); + UNSCOPED_INFO(name + "creator"); + REQUIRE(back->creator == written.creator); + UNSCOPED_INFO(name + "no key"); + REQUIRE(back->creatorId == std::string()); + UNSCOPED_INFO(name + "the code is what is left"); + REQUIRE(std::string(rest) == code); + UNSCOPED_INFO(name + "not in the other language's comments"); + REQUIRE_FALSE(ALUploadHeader::parse(text, !lua).has_value()); } // Written on Windows, every line ends in a carriage return as well. std::string_view crlf = "// ================ alchemy meta ================\r\n// @hash h\r\n// ===\r\ncode"; const std::optional windows = ALUploadHeader::take(crlf, false); - ensure("carriage returns", windows.has_value()); - ensure_equals("and none in a field", windows->hash, std::string("h")); - ensure_equals("past it", std::string(crlf), std::string("code")); + UNSCOPED_INFO("carriage returns"); + REQUIRE(windows.has_value()); + UNSCOPED_INFO("and none in a field"); + CHECK(windows->hash == std::string("h")); + UNSCOPED_INFO("past it"); + CHECK(std::string(crlf) == std::string("code")); } - template<> template<> - void aluploadheader_object::test<3>() + TEST_CASE_METHOD(aluploadheader_data, "aluploadheader: the plugin's block reads as it writes it", "[aluploadheader]") { - set_test_name("the plugin's block reads as it writes it"); // As sl-vscode-plugin's prefixWithMetaInformation writes it, its // creator lines and all. const std::string text = "// ================ sl-vscode-plugin meta ================\n" @@ -161,26 +166,37 @@ namespace tut "default { }\n"; std::string_view rest = text; const std::optional back = ALUploadHeader::take(rest, false); - ensure("read", back.has_value()); - ensure("not ours", !back->ours); - ensure_equals("file", back->file, std::string("scripts/door.lsl")); - ensure_equals("its sha256, as it is", back->hash, std::string("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")); - ensure_equals("date", back->date, std::string("2026-10-07 14:03:11")); - ensure_equals("creator", back->creator, std::string("Rye Resident")); - ensure_equals("key", back->creatorId, std::string("0b8f2ef9-1d4a-4f4c-9b62-7d3a1c4e5f60")); - ensure_equals("the code is what is left", std::string(rest), std::string("default { }\n")); + UNSCOPED_INFO("read"); + REQUIRE(back.has_value()); + UNSCOPED_INFO("not ours"); + CHECK_FALSE(back->ours); + UNSCOPED_INFO("file"); + CHECK(back->file == std::string("scripts/door.lsl")); + UNSCOPED_INFO("its sha256, as it is"); + CHECK(back->hash == std::string("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")); + UNSCOPED_INFO("date"); + CHECK(back->date == std::string("2026-10-07 14:03:11")); + UNSCOPED_INFO("creator"); + CHECK(back->creator == std::string("Rye Resident")); + UNSCOPED_INFO("key"); + CHECK(back->creatorId == std::string("0b8f2ef9-1d4a-4f4c-9b62-7d3a1c4e5f60")); + UNSCOPED_INFO("the code is what is left"); + CHECK(std::string(rest) == std::string("default { }\n")); const std::optional slua = ALUploadHeader::parse("-- ================ sl-vscode-plugin meta ================\n" "-- @file a.luau\n-- @someday more\n" "-- =======================================================\n", true); - ensure("SLua's, a field to come passed over", slua.has_value() && slua->file == "a.luau" && !slua->ours); + { + INFO("SLua's, a field to come passed over"); + REQUIRE(slua.has_value()); + REQUIRE(slua->file == "a.luau"); + CHECK_FALSE(slua->ours); + } } - template<> template<> - void aluploadheader_object::test<4>() + TEST_CASE_METHOD(aluploadheader_data, "aluploadheader: what only looks like a header is code", "[aluploadheader]") { - set_test_name("what only looks like a header is code"); const char* texts[] = { // Nothing of the kind. "default { }\n", @@ -200,8 +216,10 @@ namespace tut for (const char* text : texts) { std::string_view rest = text; - ensure(std::string("not a header: ") + text, !ALUploadHeader::take(rest, false).has_value()); - ensure_equals(std::string("and nothing taken: ") + text, std::string(rest), std::string(text)); + UNSCOPED_INFO(std::string("not a header: ") + text); + REQUIRE_FALSE(ALUploadHeader::take(rest, false).has_value()); + UNSCOPED_INFO(std::string("and nothing taken: ") + text); + REQUIRE(std::string(rest) == std::string(text)); } std::string runs_on = "// ================ alchemy meta ================\n"; for (int i = 0; i < 40; ++i) @@ -209,44 +227,56 @@ namespace tut runs_on += "// @x y\n"; } runs_on += "// ====\n"; - ensure("a block far longer than any header", !ALUploadHeader::parse(runs_on, false).has_value()); + UNSCOPED_INFO("a block far longer than any header"); + REQUIRE_FALSE(ALUploadHeader::parse(runs_on, false).has_value()); } - template<> template<> - void aluploadheader_object::test<5>() + TEST_CASE_METHOD(aluploadheader_data, "aluploadheader: the hash is XXH128 of v1 and the target and source and code split by noughts", "[aluploadheader]") { - set_test_name("the hash is XXH128 over v1, the target, the source and the code, each after a nought, as xxhsum writes it"); using namespace std::string_literals; const std::string hash = ALUploadHeader::hashOf("mono", "#define X 1\nX", "1"); - ensure_equals("xxHash's own", hash, aluploadheader_data::xxhsum("v1\0mono\0#define X 1\nX\0001"s)); - ensure_equals("its scheme and 32 digits", hash.size(), std::string("xxh128:").size() + 32); - ensure_equals("plain: the text alone", ALUploadHeader::hashOfPlain("luau", "print(1)"), + UNSCOPED_INFO("xxHash's own"); + CHECK(hash == aluploadheader_data::xxhsum("v1\0mono\0#define X 1\nX\0001"s)); + UNSCOPED_INFO("its scheme and 32 digits"); + REQUIRE(hash.size() == std::string("xxh128:").size() + 32); + UNSCOPED_INFO("plain: the text alone"); + CHECK(ALUploadHeader::hashOfPlain("luau", "print(1)") == aluploadheader_data::xxhsum("v1\0luau\0print(1)"s)); - ensure_equals("no target", ALUploadHeader::hashOfPlain("", "x"), aluploadheader_data::xxhsum("v1\0\0x"s)); - - ensure("the same, the same", hash == ALUploadHeader::hashOf("mono", "#define X 1\nX", "1")); - ensure("another target", hash != ALUploadHeader::hashOf("lsl2", "#define X 1\nX", "1")); - ensure("another source", hash != ALUploadHeader::hashOf("mono", "#define X 2\nX", "1")); - ensure("other code", hash != ALUploadHeader::hashOf("mono", "#define X 1\nX", "2")); - ensure("where the source ends", ALUploadHeader::hashOf("mono", "ab", "c") != ALUploadHeader::hashOf("mono", "a", "bc")); - ensure("wrapped with nothing expanded is not plain", ALUploadHeader::hashOf("mono", "x", "") != ALUploadHeader::hashOfPlain("mono", "x")); + UNSCOPED_INFO("no target"); + CHECK(ALUploadHeader::hashOfPlain("", "x") == aluploadheader_data::xxhsum("v1\0\0x"s)); + + UNSCOPED_INFO("the same, the same"); + CHECK(hash == ALUploadHeader::hashOf("mono", "#define X 1\nX", "1")); + UNSCOPED_INFO("another target"); + CHECK(hash != ALUploadHeader::hashOf("lsl2", "#define X 1\nX", "1")); + UNSCOPED_INFO("another source"); + CHECK(hash != ALUploadHeader::hashOf("mono", "#define X 2\nX", "1")); + UNSCOPED_INFO("other code"); + CHECK(hash != ALUploadHeader::hashOf("mono", "#define X 1\nX", "2")); + UNSCOPED_INFO("where the source ends"); + CHECK(ALUploadHeader::hashOf("mono", "ab", "c") != ALUploadHeader::hashOf("mono", "a", "bc")); + UNSCOPED_INFO("wrapped with nothing expanded is not plain"); + CHECK(ALUploadHeader::hashOf("mono", "x", "") != ALUploadHeader::hashOfPlain("mono", "x")); } - template<> template<> - void aluploadheader_object::test<6>() + TEST_CASE_METHOD(aluploadheader_data, "aluploadheader: a date as @date writes it in UTC; and what is absolute", "[aluploadheader]") { - set_test_name("a date as @date writes it, in UTC; and what is absolute"); - ensure_equals("the epoch", ALUploadHeader::dateOf(LLDate(0.0)), std::string("1970-01-01 00:00:00")); - ensure_equals("a day", ALUploadHeader::dateOf(LLDate(1791381791.0)), std::string("2026-10-07 14:03:11")); - ensure_equals("a leap day's last second", ALUploadHeader::dateOf(LLDate(951868799.5)), std::string("2000-02-29 23:59:59")); + UNSCOPED_INFO("the epoch"); + CHECK(ALUploadHeader::dateOf(LLDate(0.0)) == std::string("1970-01-01 00:00:00")); + UNSCOPED_INFO("a day"); + CHECK(ALUploadHeader::dateOf(LLDate(1791381791.0)) == std::string("2026-10-07 14:03:11")); + UNSCOPED_INFO("a leap day's last second"); + CHECK(ALUploadHeader::dateOf(LLDate(951868799.5)) == std::string("2000-02-29 23:59:59")); for (const char* path : { "/a", "\\a", "\\\\host\\a", "C:\\a", "c:/a", "z:a", "~/a", "~rye/a" }) { - ensure(std::string("absolute: ") + path, ALUploadHeader::absolute(path)); + UNSCOPED_INFO(std::string("absolute: ") + path); + REQUIRE(ALUploadHeader::absolute(path)); } for (const char* path : { "", "a", "net/door.lsl", "../shared/util.luau", "@lib/util.luau", "1:x", ":a" }) { - ensure(std::string("not: ") + path, !ALUploadHeader::absolute(path)); + UNSCOPED_INFO(std::string("not: ") + path); + REQUIRE_FALSE(ALUploadHeader::absolute(path)); } } } diff --git a/indra/lscript/CMakeLists.txt b/indra/lscript/CMakeLists.txt index 740199fe1d..97957452eb 100755 --- a/indra/lscript/CMakeLists.txt +++ b/indra/lscript/CMakeLists.txt @@ -172,17 +172,16 @@ endif() set_source_files_properties(${lscript_sources} PROPERTIES COMPILE_OPTIONS "${lscript_ieee_options}") # tests -# LL's compiler over a handful of scripts, each to LSO and to CIL. -al_add_test(allslreference PROJECT lscript LIBRARIES lscript llcommon) +# LL's compiler over a handful of scripts, each to LSO and to CIL; and # Tailslide's LSO images and CIL, and the Mono weigher, held against LL's -# compiler's over Tailslide's test scripts. -al_add_test( - allslreferencecheck +# compiler's over Tailslide's test scripts. One Catch2 binary, each file run +# in a process of its own. +al_add_test_binary( + lscript_tests PROJECT lscript + SOURCES tests/allslreference_test.cpp tests/allslreferencecheck_test.cpp LIBRARIES lscript alscript llcommon DEFINES "AL_LSL_DEFINITIONS_DIR=\"${LSL_DEFINITIONS_DIR}\"" "AL_ALSCRIPT_TEST_DIR=\"${INDRA_SOURCE_DIR}/alscript/tests\"" ) - - diff --git a/indra/lscript/tests/allslreference_test.cpp b/indra/lscript/tests/allslreference_test.cpp index 62ecb0b9b2..51d3c90265 100644 --- a/indra/lscript/tests/allslreference_test.cpp +++ b/indra/lscript/tests/allslreference_test.cpp @@ -30,7 +30,7 @@ #include "../lscript_library.h" #include "lltimer.h" -#include "../test/lltut.h" +#include #include @@ -93,20 +93,10 @@ namespace constexpr size_t NER = 92; } -namespace tut +namespace { - struct allslreference_data - { - }; - - typedef test_group allslreference_group; - typedef allslreference_group::object allslreference_object; - allslreference_group allslreference_instance("allslreference"); - - template<> template<> - void allslreference_object::test<1>() + TEST_CASE("allslreference: a script compiles to a whole LSO image and to CIL named from its id", "[allslreference]") { - set_test_name("a script compiles to a whole LSO image, and to CIL whose class is named from the script's id"); const std::string text = "integer g = 3;\n" "default\n" "{\n" @@ -116,26 +106,35 @@ namespace tut " }\n" "}\n"; const ALLSLReference::Result lso = ALLSLReference::compile(text, Target::LSO); - ensure("LSO: " + lso.messages, lso.ok); - ensure_equals("the image fills LSO's memory", lso.image.size(), (size_t)16384); - ensure_equals("top of memory", reg32(lso.image, TM), 16384); - ensure_equals("LSO2", reg32(lso.image, VN), 0x0200); - ensure_equals("the default state handles state_entry", reg64(lso.image, NER), (U64)0x1); + UNSCOPED_INFO("LSO: " + lso.messages); + CHECK(lso.ok); + UNSCOPED_INFO("the image fills LSO's memory"); + REQUIRE(lso.image.size() == (size_t)16384); + UNSCOPED_INFO("top of memory"); + CHECK(reg32(lso.image, TM) == 16384); + UNSCOPED_INFO("LSO2"); + CHECK(reg32(lso.image, VN) == 0x0200); + UNSCOPED_INFO("the default state handles state_entry"); + CHECK(reg64(lso.image, NER) == (U64)0x1); // LL's compiler calls every library function by its two-byte form. - ensure("llSay is library function 23", holds(lso.image, { 0xd1, 0x00, 23 })); + UNSCOPED_INFO("llSay is library function 23"); + CHECK(holds(lso.image, { 0xd1, 0x00, 23 })); const ALLSLReference::Result cil = ALLSLReference::compile(text, Target::CIL); - ensure("CIL: " + cil.messages, cil.ok); - ensure("the class: " + cil.cil, has(cil.cil, ".class public auto ansi serializable beforefieldinit LSL_00000000_0000_0000_0000_000000000000 extends")); - ensure("the global a field: " + cil.cil, has(cil.cil, ".field public int32 'g'")); - ensure("the handler a method: " + cil.cil, has(cil.cil, "instance default void edefaultstate_entry() cil managed")); - ensure("llSay called by name: " + cil.cil, has(cil.cil, "Library::'llSay'(int32, string)")); + UNSCOPED_INFO("CIL: " + cil.messages); + CHECK(cil.ok); + UNSCOPED_INFO("the class: " + cil.cil); + CHECK(has(cil.cil, ".class public auto ansi serializable beforefieldinit LSL_00000000_0000_0000_0000_000000000000 extends")); + UNSCOPED_INFO("the global a field: " + cil.cil); + CHECK(has(cil.cil, ".field public int32 'g'")); + UNSCOPED_INFO("the handler a method: " + cil.cil); + CHECK(has(cil.cil, "instance default void edefaultstate_entry() cil managed")); + UNSCOPED_INFO("llSay called by name: " + cil.cil); + CHECK(has(cil.cil, "Library::'llSay'(int32, string)")); } - template<> template<> - void allslreference_object::test<2>() + TEST_CASE("allslreference: the grid's definitions: a post-2015 function at its number and new constants", "[allslreference]") { - set_test_name("the grid's definitions: a function newer than the 2015 table at its own number, and constants newer than the 2015 lexer"); const std::string text = "default\n" "{\n" " state_entry()\n" @@ -145,20 +144,24 @@ namespace tut " }\n" "}\n"; const ALLSLReference::Result lso = ALLSLReference::compile(text, Target::LSO); - ensure("LSO: " + lso.messages, lso.ok); - ensure("llLinksetDataWrite is library function 650", holds(lso.image, { 0xd1, 0x02, 0x8a })); + UNSCOPED_INFO("LSO: " + lso.messages); + CHECK(lso.ok); + UNSCOPED_INFO("llLinksetDataWrite is library function 650"); + CHECK(holds(lso.image, { 0xd1, 0x02, 0x8a })); const ALLSLReference::Result cil = ALLSLReference::compile(text, Target::CIL); - ensure("CIL: " + cil.messages, cil.ok); - ensure("llLinksetDataWrite: " + cil.cil, has(cil.cil, "Library::'llLinksetDataWrite'(string, string)")); - ensure("PRIM_GLTF_BASE_COLOR is 48: " + cil.cil, has(cil.cil, "ldc.i4 48")); - ensure("OBJECT_DAMAGE is 51: " + cil.cil, has(cil.cil, "ldc.i4 51")); + UNSCOPED_INFO("CIL: " + cil.messages); + CHECK(cil.ok); + UNSCOPED_INFO("llLinksetDataWrite: " + cil.cil); + CHECK(has(cil.cil, "Library::'llLinksetDataWrite'(string, string)")); + UNSCOPED_INFO("PRIM_GLTF_BASE_COLOR is 48: " + cil.cil); + CHECK(has(cil.cil, "ldc.i4 48")); + UNSCOPED_INFO("OBJECT_DAMAGE is 51: " + cil.cil); + CHECK(has(cil.cil, "ldc.i4 51")); } - template<> template<> - void allslreference_object::test<3>() + TEST_CASE("allslreference: the events new since 2015 compile and LSO's default state has each one's bit", "[allslreference]") { - set_test_name("the events new since 2015 compile, and LSO's default state carries each one's bit"); const std::string text = "default\n" "{\n" " experience_permissions(key agent) {}\n" @@ -172,24 +175,26 @@ namespace tut " final_damage(integer n) {}\n" "}\n"; const ALLSLReference::Result lso = ALLSLReference::compile(text, Target::LSO); - ensure("LSO: " + lso.messages, lso.ok); + UNSCOPED_INFO("LSO: " + lso.messages); + CHECK(lso.ok); // Events 35 to 43, bits 34 to 42. - ensure_equals("their bits", reg64(lso.image, NER), (U64)0x1ff << 34); + UNSCOPED_INFO("their bits"); + CHECK(reg64(lso.image, NER) == (U64)0x1ff << 34); const ALLSLReference::Result cil = ALLSLReference::compile(text, Target::CIL); - ensure("CIL: " + cil.messages, cil.ok); + UNSCOPED_INFO("CIL: " + cil.messages); + CHECK(cil.ok); for (const char* handler : { "edefaultexperience_permissions(", "edefaulttransaction_result(", "edefaultpath_update(", "edefaultexperience_permissions_denied(", "edefaultlinkset_data(", "edefaultgame_control(", "edefaulton_death(", "edefaulton_damage(", "edefaultfinal_damage(" }) { - ensure(std::string(handler) + ": " + cil.cil, has(cil.cil, handler)); + UNSCOPED_INFO(std::string(handler) + ": " + cil.cil); + REQUIRE(has(cil.cil, handler)); } } - template<> template<> - void allslreference_object::test<4>() + TEST_CASE("allslreference: globals read in functions and events and remote_data compile and free cleanly", "[allslreference]") { - set_test_name("a global read in a function and an event, and a remote_data handler, compile and free cleanly"); const std::string text = "integer g = 1;\n" "integer f() { return g + 1; }\n" "default\n" @@ -200,43 +205,53 @@ namespace tut for (Target target : { Target::LSO, Target::CIL }) { const ALLSLReference::Result r = ALLSLReference::compile(text, target); - ensure("compiles: " + r.messages, r.ok); + UNSCOPED_INFO("compiles: " + r.messages); + REQUIRE(r.ok); } } - template<> template<> - void allslreference_object::test<5>() + TEST_CASE("allslreference: LL's compiler rejects bad syntax and on LSO a list or unset global in a list", "[allslreference]") { - set_test_name("what LL's compiler rejects: a syntax error; and, building LSO's image alone, a list or an unset global in a global's list"); const ALLSLReference::Result syntax = ALLSLReference::compile("default { state_entry() { integer = 1; } }\n", Target::LSO); - ensure("a syntax error: " + syntax.messages, !syntax.ok && has(syntax.messages, "ERROR : Syntax error")); + { + INFO("a syntax error: " + syntax.messages); + CHECK_FALSE(syntax.ok); + CHECK(has(syntax.messages, "ERROR : Syntax error")); + } // A global list's elements are checked as LSO's image lays them out // (LSCP_LIST_BUILD_SIMPLE), a pass the CIL never runs. const std::string nested = "list a = [1];\nlist b = [a];\ndefault { state_entry() { llOwnerSay((string)b); } }\n"; const ALLSLReference::Result nested_lso = ALLSLReference::compile(nested, Target::LSO); - ensure("LSO rejects a list in a global's list: " + nested_lso.messages, - !nested_lso.ok && has(nested_lso.messages, "Lists can't be included in lists")); + { + INFO("LSO rejects a list in a global's list: " + nested_lso.messages); + CHECK_FALSE(nested_lso.ok); + CHECK(has(nested_lso.messages, "Lists can't be included in lists")); + } const ALLSLReference::Result nested_cil = ALLSLReference::compile(nested, Target::CIL); - ensure("CIL takes it: " + nested_cil.messages, nested_cil.ok); + UNSCOPED_INFO("CIL takes it: " + nested_cil.messages); + CHECK(nested_cil.ok); const std::string unset = "string s;\nlist l = [s];\ndefault { state_entry() { llOwnerSay((string)l); } }\n"; const ALLSLReference::Result unset_lso = ALLSLReference::compile(unset, Target::LSO); - ensure("LSO rejects an unset global in a global's list: " + unset_lso.messages, - !unset_lso.ok && has(unset_lso.messages, "Unitialized variables can't be included in lists")); + { + INFO("LSO rejects an unset global in a global's list: " + unset_lso.messages); + CHECK_FALSE(unset_lso.ok); + CHECK(has(unset_lso.messages, "Unitialized variables can't be included in lists")); + } const ALLSLReference::Result unset_cil = ALLSLReference::compile(unset, Target::CIL); - ensure("CIL takes it: " + unset_cil.messages, unset_cil.ok); + UNSCOPED_INFO("CIL takes it: " + unset_cil.messages); + CHECK(unset_cil.ok); } - template<> template<> - void allslreference_object::test<6>() + TEST_CASE("allslreference: LL's VM runs compiled state_entry code with a stand-in answering llSay", "[allslreference]") { - set_test_name("LL's VM runs a compiled image's state_entry, a global, a function and a cast in it, its library call answered by a stand-in"); const std::string text = "integer g = 6;\n" "integer twice(integer n) { return n * 2; }\n" "default { state_entry() { llSay(0, (string)(twice(g) * 3 + 6)); } }\n"; const ALLSLReference::Result lso = ALLSLReference::compile(text, Target::LSO); - ensure("LSO: " + lso.messages, lso.ok); + UNSCOPED_INFO("LSO: " + lso.messages); + CHECK(lso.ok); // llSay is function 23: heard here for the run, as it was after. gSaid.clear(); @@ -251,9 +266,13 @@ namespace tut vm.runQuanta(FALSE, LLUUID::null, &error, 1.f, events, timer); } } - ensure("no fault: " + std::string(error ? error : ""), !error); - ensure_equals("one handler run", events, (U32)1); - ensure_equals("said once", gSaid.size(), (size_t)1); - ensure_equals("what it worked out", gSaid[0], std::string("42")); + UNSCOPED_INFO("no fault: " + std::string(error ? error : "")); + REQUIRE_FALSE(error); + UNSCOPED_INFO("one handler run"); + CHECK(events == (U32)1); + UNSCOPED_INFO("said once"); + REQUIRE(gSaid.size() == (size_t)1); + UNSCOPED_INFO("what it worked out"); + CHECK(gSaid[0] == std::string("42")); } } diff --git a/indra/lscript/tests/allslreferencecheck_test.cpp b/indra/lscript/tests/allslreferencecheck_test.cpp index 1f9aa1baf1..57681e1ded 100644 --- a/indra/lscript/tests/allslreferencecheck_test.cpp +++ b/indra/lscript/tests/allslreferencecheck_test.cpp @@ -35,7 +35,7 @@ #include "alscriptweight.h" #include "llfile.h" -#include "../test/lltut.h" +#include #include #include @@ -224,7 +224,7 @@ namespace } } -namespace tut +namespace { struct allslreferencecheck_data { @@ -233,15 +233,11 @@ namespace tut std::string error; allslreferencecheck_data() { loaded = lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error); } }; - typedef test_group allslreferencecheck_group; - typedef allslreferencecheck_group::object allslreferencecheck_object; - allslreferencecheck_group allslreferencecheck_instance("allslreferencecheck"); - template<> template<> - void allslreferencecheck_object::test<1>() + TEST_CASE_METHOD(allslreferencecheck_data, "allslreferencecheck: LSO: Tailslide's image is LL's byte for byte; and what each refuses", "[allslreferencecheck]") { - set_test_name("LSO: Tailslide's image is LL's byte for byte; and what each refuses"); - ensure("builtins: " + error, loaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(loaded); // None, since the port's bitstream-move-to-end.patch: a bit stream // moved to its end grew a byte past it, which a last global left all // zeros kept, and all after it moved. @@ -282,17 +278,20 @@ namespace tut } where += name + " from byte " + std::to_string(at) + ", in its " + regionOf(ll.image, at) + "\n"; } - ensure("most of them: " + std::to_string(same), same >= 75); - ensure("the images that part: " + listed(parted) + "\n" + where, parted == PARTED); - ensure("LL's compiles alone: " + listed(only_ll), only_ll == ONLY_LL); - ensure("Tailslide compiles alone: " + listed(only_tailslide), only_tailslide == ONLY_TAILSLIDE); + UNSCOPED_INFO("most of them: " + std::to_string(same)); + CHECK(same >= 75); + UNSCOPED_INFO("the images that part: " + listed(parted) + "\n" + where); + CHECK(parted == PARTED); + UNSCOPED_INFO("LL's compiles alone: " + listed(only_ll)); + CHECK(only_ll == ONLY_LL); + UNSCOPED_INFO("Tailslide compiles alone: " + listed(only_tailslide)); + CHECK(only_tailslide == ONLY_TAILSLIDE); } - template<> template<> - void allslreferencecheck_object::test<2>() + TEST_CASE_METHOD(allslreferencecheck_data, "allslreferencecheck: CIL: Tailslide's matches LL's method for method up to spelling", "[allslreferencecheck]") { - set_test_name("CIL: Tailslide's is LL's, method for method, but for spelling an assembler reads alike"); - ensure("builtins: " + error, loaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(loaded); // What only LSO's image building refuses: a list in a global's list. // Its constants are LL's since the port's mono-constants-as-ll.patch: // an integer literal by ldc.i4 alone, and a minus before a number in @@ -331,17 +330,20 @@ namespace tut where += name + ": [" + (at < written.size() ? written[at] : "") + "] against LL's [" + (at < theirs.size() ? theirs[at] : "") + "]\n"; } } - ensure("most of them: " + std::to_string(both), both >= 75); - ensure("the same as LL's:\n" + where, parted.empty()); - ensure("LL's compiles alone: " + listed(only_ll), only_ll == ONLY_LL); - ensure("Tailslide compiles alone: " + listed(only_tailslide), only_tailslide == ONLY_TAILSLIDE); + UNSCOPED_INFO("most of them: " + std::to_string(both)); + CHECK(both >= 75); + UNSCOPED_INFO("the same as LL's:\n" + where); + REQUIRE(parted.empty()); + UNSCOPED_INFO("LL's compiles alone: " + listed(only_ll)); + CHECK(only_ll == ONLY_LL); + UNSCOPED_INFO("Tailslide compiles alone: " + listed(only_tailslide)); + CHECK(only_tailslide == ONLY_TAILSLIDE); } - template<> template<> - void allslreferencecheck_object::test<3>() + TEST_CASE_METHOD(allslreferencecheck_data, "allslreferencecheck: Mono: the weigher reads either compiler's CIL alike and both weigh the same", "[allslreferencecheck]") { - set_test_name("Mono: the weigher reads either compiler's CIL alike, and both weigh the same"); - ensure("builtins: " + error, loaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(loaded); std::string wrong; size_t both = 0; for (const auto& [name, text] : corpus()) @@ -368,15 +370,16 @@ namespace tut wrong += name + ": LL's weighs " + std::to_string(theirs.total) + ", Tailslide's " + std::to_string(tailslide.total) + "\n"; } } - ensure("most of them: " + std::to_string(both), both >= 75); - ensure(wrong, wrong.empty()); + UNSCOPED_INFO("most of them: " + std::to_string(both)); + CHECK(both >= 75); + UNSCOPED_INFO(wrong); + REQUIRE(wrong.empty()); } - template<> template<> - void allslreferencecheck_object::test<4>() + TEST_CASE_METHOD(allslreferencecheck_data, "allslreferencecheck: integer literals past 32 bits read -1 as on 32-bit hosts; both make one image", "[allslreferencecheck]") { - set_test_name("integer literals past 32 bits as the grid's 32-bit hosts read them, -1, decimal or hexadecimal: the same image from either"); - ensure("builtins: " + error, loaded); + UNSCOPED_INFO("builtins: " + error); + CHECK(loaded); const std::string text = "integer a = 4294967296;\n" "integer b = 0x100000000;\n" "integer c = 99999999999999999999999;\n" @@ -384,9 +387,12 @@ namespace tut "default { state_entry() { llOwnerSay((string)(a + b + c + d + 4294967297 - 0x1FFFFFFFF)); } }\n"; std::vector ours; std::string why; - ensure("Tailslide compiles it: " + why, ALScriptWeigh::tailslideLSO(text, ours, why)); + UNSCOPED_INFO("Tailslide compiles it: " + why); + REQUIRE(ALScriptWeigh::tailslideLSO(text, ours, why)); const ALLSLReference::Result ll = ALLSLReference::compile(text, Target::LSO); - ensure("LL's compiles it", ll.ok); - ensure("the same image", ours == ll.image); + UNSCOPED_INFO("LL's compiles it"); + CHECK(ll.ok); + UNSCOPED_INFO("the same image"); + CHECK(ours == ll.image); } } From e661a8e8c6581ff80e96869da740d57f3f6940a1 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 01:39:24 -0400 Subject: [PATCH 5/7] alscript's and lscript's tests read as Catch2 The idiom pass over TM21's conversion, by seven agents on copies, merged back onto it. Names, messages and assertion strength only: every case and every assertion is where the conversion put it. - Names: 602 of 657 changed. Nearly all were set_test_name sentences with commas, which a Catch2 test spec reads as "or", and many ran past 100 characters. - Messages: 4,872 to 4,044, with 207 CAPTUREs. Most that remain say what a check means, or carry the script a failure has to show; one CAPTURE of a result's text now stands for the hundreds that ended in it. - 18 asserting helpers are named require_*: require_fixed, require_converted, require_checks_clean and the like. - 136 REQUIREs that guarded nothing are CHECKs, and 9 CHECKs whose failure would let the next line read past an end are REQUIREs. - alscriptfixes' UNSCOPED_INFO and REQUIRE(false) is a FAIL. tut_parity.py holds all 45 files and their 6 second runs under --recounted, every case's count unchanged, with 23 sites reviewed as never running in a passing case (the conversion's 24, less the REQUIRE(false)). Old test bugs the agents found, left as they were: - messages built before the call that fills them, so they show nothing or an earlier call's error: alpreprocessor, alluauservice, alluauconfig, allslservice, alscriptweight, allslreferencecheck; - reads with no guard, where a failure would crash rather than fail: results of named(), linkOf(), dueAt(), of(), parse() and the like in almodulelook, almasterlinks, almasterindex, almasterwatch, almasterburst, alscriptenvelope, alnotecardformat, allsloptimizer and alscriptweight; [0] or front() of what nothing sized in alpreprocessor, alincludesearch, aldiskcache, allsleffects, allslservice, alscriptfixes, alluauservice, alscriptformatter and alscriptlexicon; - checks that cannot fail: alpreprocessor compares a find() with itself; allsltoslua looks for ll.OwnerSay where the default writes print, and two order checks pass when the text they order is missing; - checks that test something else: allsloptimizer's "weighs what it says" weighs nothing; alscriptweight's "where it ends" checks where it starts; almessagemap's checks after a failed match read the last match's; - alscriptfixes builds four messages by calling an asserting helper, so it runs twice; a label before CHECK(require_*(...)) is taken by the helper's first assertion, not the CHECK; - allsltoslua's "handler over both" checks run only if a span starts on LSL line 4; - leftovers: allsleffects includes linden_common.h twice, alluauservice has a duplicated comment, and alscriptlintpass a comment about TUT's fifty-test limit. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/tests/aldiskcache_test.cpp | 7 +- indra/alscript/tests/aldiskincludes_test.cpp | 13 +- indra/alscript/tests/alincludesearch_test.cpp | 22 +- indra/alscript/tests/allinelabel_test.cpp | 5 +- indra/alscript/tests/allslconsts_test.cpp | 20 +- indra/alscript/tests/allslcosts_test.cpp | 68 +- indra/alscript/tests/allsleffects_test.cpp | 6 - indra/alscript/tests/allsloptimizer_test.cpp | 146 +- indra/alscript/tests/allslservice_test.cpp | 275 +- indra/alscript/tests/allsltoslua_test.cpp | 3589 +++++++++-------- indra/alscript/tests/alluauconfig_test.cpp | 51 +- indra/alscript/tests/alluaufragment_test.cpp | 37 +- .../alscript/tests/alluaunavigation_test.cpp | 85 +- indra/alscript/tests/alluauservice_test.cpp | 147 +- indra/alscript/tests/almasterburst_test.cpp | 39 +- indra/alscript/tests/almasterindex_test.cpp | 50 - indra/alscript/tests/almasterlinks_test.cpp | 37 +- indra/alscript/tests/almastermatch_test.cpp | 7 +- indra/alscript/tests/almasterplan_test.cpp | 9 +- indra/alscript/tests/almasterqueue_test.cpp | 58 +- indra/alscript/tests/almasterwatch_test.cpp | 27 +- indra/alscript/tests/almessagemap_test.cpp | 26 - indra/alscript/tests/almodulelook_test.cpp | 6 +- .../alscript/tests/alnotecardformat_test.cpp | 26 +- indra/alscript/tests/alpreprocessor_test.cpp | 262 +- .../alscript/tests/alpreprocessorc11_test.cpp | 19 +- .../alscript/tests/alscriptenvelope_test.cpp | 96 +- indra/alscript/tests/alscriptfixes_test.cpp | 439 +- .../alscript/tests/alscriptformatter_test.cpp | 16 - indra/alscript/tests/alscriptjoblane_test.cpp | 8 +- indra/alscript/tests/alscriptlexicon_test.cpp | 176 +- .../alscript/tests/alscriptlintpass_test.cpp | 189 +- .../alscript/tests/alscriptmessages_test.cpp | 27 +- .../alscript/tests/alscriptnameindex_test.cpp | 1 - .../tests/alscriptoutlinepairs_test.cpp | 2 - .../alscript/tests/alscriptsnapshot_test.cpp | 4 +- indra/alscript/tests/alscriptstack_test.cpp | 3 +- .../alscript/tests/alscripttempfiles_test.cpp | 11 +- .../alscript/tests/alscripttextcache_test.cpp | 4 +- indra/alscript/tests/alscriptweight_test.cpp | 271 +- indra/alscript/tests/alsourcemap_test.cpp | 22 +- indra/alscript/tests/aluploadheader_test.cpp | 39 +- indra/lscript/tests/allslreference_test.cpp | 12 +- .../tests/allslreferencecheck_test.cpp | 11 +- 44 files changed, 2735 insertions(+), 3633 deletions(-) diff --git a/indra/alscript/tests/aldiskcache_test.cpp b/indra/alscript/tests/aldiskcache_test.cpp index 9330b1051d..b4eefea5db 100644 --- a/indra/alscript/tests/aldiskcache_test.cpp +++ b/indra/alscript/tests/aldiskcache_test.cpp @@ -127,11 +127,8 @@ namespace const std::string file = s.write("lib/util.lsl", "integer one;\n"); ALDiskCache cache; std::string text; - { - INFO("read"); - REQUIRE(cache.read(file, text)); - CHECK(text == "integer one;\n"); - } + REQUIRE(cache.read(file, text)); + CHECK(text == "integer one;\n"); s.write("lib/util.lsl", "integer three;\n"); { INFO("read again once it changed"); diff --git a/indra/alscript/tests/aldiskincludes_test.cpp b/indra/alscript/tests/aldiskincludes_test.cpp index bbcd435820..a37b6d2ea7 100644 --- a/indra/alscript/tests/aldiskincludes_test.cpp +++ b/indra/alscript/tests/aldiskincludes_test.cpp @@ -96,11 +96,8 @@ namespace ALDiskIncludes blessed; blessed.bless(s.at("lib")); - { - INFO("blessed"); - CHECK(blessed.blessed()); - REQUIRE(blessed.folders().size() == 1); - } + CHECK(blessed.blessed()); + REQUIRE(blessed.folders().size() == 1); UNSCOPED_INFO("a file under it is read, as it stands"); CHECK(blessed.admits(lib).value_or("") == lib); UNSCOPED_INFO("a path from a root elsewhere is not"); @@ -204,7 +201,6 @@ namespace s.write("project/.lslrc", "{\"include\": [\"../lib\", \"shared/\"]}"); s.write("project/src/deep/main.lsl", "x\n"); const std::vector listed = ALDiskIncludes::lslrcFolders(s.at("project")); - UNSCOPED_INFO("both"); REQUIRE(listed.size() == size_t(2)); UNSCOPED_INFO("the one above, from beside the file"); CHECK(listed[0] == s.at("lib")); @@ -316,7 +312,7 @@ namespace // A link inside that leads outside is where it leads. std::error_code ec; fs::create_directory_symlink(s.under("secret"), s.under("project/escape"), ec); - UNSCOPED_INFO("linked: " + ec.message()); + UNSCOPED_INFO("linked: " << ec.message()); REQUIRE_FALSE(ec); UNSCOPED_INFO("not through a link out"); CHECK_FALSE(none.mayFromConfig(s.at("project/escape"), s.at("project"))); @@ -397,7 +393,7 @@ namespace std::error_code ec; fs::remove(s.under("first/.luaurc"), ec); fs::create_symlink(s.under("elsewhere/.luaurc"), s.under("first/.luaurc"), ec); - UNSCOPED_INFO("linked: " + ec.message()); + UNSCOPED_INFO("linked: " << ec.message()); REQUIRE_FALSE(ec); UNSCOPED_INFO("not through a link out"); CHECK(blessed.atTop(".luaurc") == files{ third, second }); @@ -459,7 +455,6 @@ namespace CHECK(ALDiskIncludes::extensionOf("defs.LSLH", ALDiskIncludes::scriptExtensions(false)) == size_t(5)); UNSCOPED_INFO("past a name of its own"); CHECK(ALDiskIncludes::extensionOf(".luau", lua) == size_t(0)); - UNSCOPED_INFO("none"); CHECK(ALDiskIncludes::extensionOf("notes.txt", lua) == size_t(0)); UNSCOPED_INFO("a require of one in any case is that file"); CHECK(ALDiskIncludes::namesFor("Util.LUAU", true, true) == diff --git a/indra/alscript/tests/alincludesearch_test.cpp b/indra/alscript/tests/alincludesearch_test.cpp index 282d872231..d41eb3b09a 100644 --- a/indra/alscript/tests/alincludesearch_test.cpp +++ b/indra/alscript/tests/alincludesearch_test.cpp @@ -652,19 +652,17 @@ namespace tut { under = under_tree(file); } - UNSCOPED_INFO(std::string(one.what) + ": as LAD9 has it"); + INFO(one.what); REQUIRE(under == std::string(one.chosen)); if (*one.name != '@') { // A name the search before took: asked of it. const std::string before = searched(one.name, own, disk); - UNSCOPED_INFO(std::string(one.what) + ": as before"); REQUIRE((before.empty() ? before : under_tree(before)) == std::string(one.before)); } else if (!*one.rule) { // An alias's, where all agree: found so before. - UNSCOPED_INFO(std::string(one.what) + ": as before"); REQUIRE(under == std::string(one.before)); } } @@ -874,7 +872,7 @@ namespace tut const auto missed = [&](const std::string& name, const ALIncludeSearch::Asking& who) { ALPreprocessor::Include include; std::vector aliases; - UNSCOPED_INFO(name + ": found nothing now"); + CAPTURE(name); REQUIRE(search.resolve(alincludesearch_data::ask(name, true, who.self), include, who, disk, nullptr, false, &aliases) == ALPreprocessor::Found::No); return include; @@ -907,7 +905,7 @@ namespace tut UNSCOPED_INFO("from an object, where none reaches it"); CHECK(include.searched == solo); UNSCOPED_INFO("through a studio alias of the folder that holds it, named after it"); - CHECK(said(include) == std::string("@other/solo +@other")); + REQUIRE(said(include) == std::string("@other/solo +@other")); UNSCOPED_INFO("the folder named"); CHECK(include.moves[0].folder == s.at("other")); include = missed("deep/net", in_object); @@ -939,9 +937,7 @@ namespace tut const ALIncludeSearch::Asking own{ ALIncludeIdentity::ofFile(script), true }; ALPreprocessor::Include found; std::vector aliases; - UNSCOPED_INFO("found"); CHECK(search.resolve(ask("./both", true, own.self), found, own, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); - UNSCOPED_INFO("the file"); CHECK(found.path == ALIncludeIdentity::ofFile(file)); UNSCOPED_INFO("the init passed over, by its path from the folder both are in"); CHECK(found.passedOver == std::string("both/init.luau")); @@ -983,9 +979,7 @@ namespace tut CHECK(why("@c1") == std::string("detected alias cycle (@c1 -> @c2 -> @c1)")); UNSCOPED_INFO("an alias whose folder is not there"); CHECK(why("@gone/x") == std::string("the alias stands for './gone', which is not there")); - UNSCOPED_INFO("reserved"); REQUIRE(why("@sl-std/x").find("reserved") != std::string::npos); - UNSCOPED_INFO("climbing"); REQUIRE(why("@lib/../x").find("may not climb") != std::string::npos); // Said as a module not found, for what explains it. @@ -1060,7 +1054,7 @@ namespace tut } ALPreprocessor::Include found; std::vector aliases; - UNSCOPED_INFO("resolves: " + one.path); + CAPTURE(one.path); REQUIRE(search.resolve(ask(one.path, true, own.self), found, own, disk, nullptr, false, &aliases) == ALPreprocessor::Found::Yes); } @@ -1186,7 +1180,7 @@ namespace tut UNSCOPED_INFO("broken: its alias gone"); REQUIRE(resolved("@lib/net", &why).empty()); { - INFO("said why, naming it: " + why); + INFO("said why, naming it: " << why); CHECK(why.find("the configuration '") == 0); REQUIRE(why.find(".config.luau' could not be read: ") != std::string::npos); } @@ -1259,9 +1253,9 @@ namespace tut } return out; }; - UNSCOPED_INFO("offered while the world is let in: " + offered(on)); + UNSCOPED_INFO("offered while the world is let in: " << offered(on)); REQUIRE(offered(on).find("@inv") != std::string::npos); - UNSCOPED_INFO("not otherwise: " + offered(off)); + UNSCOPED_INFO("not otherwise: " << offered(off)); REQUIRE(offered(off).find("@inv") == std::string::npos); // Beside a folder on disk, which is read only while the disk is. @@ -1428,7 +1422,7 @@ namespace tut const std::string inner = ALIncludeIdentity::ofFile(s.write("proj/src/main.luau", "return 1\n")); const ALIncludeSearch::Where both = where(false, true, { s.at("inc"), s.at("proj") }); const std::string said = why("@lib/x", inner, both); - UNSCOPED_INFO("not reached through it: " + said); + UNSCOPED_INFO("not reached through it: " << said); CHECK(said.find("the configuration '" + s.at("proj/src/.luaurc") + "' could not be read: ") == 0); UNSCOPED_INFO("a path walks as ever"); CHECK(why("./near", inner, both) == std::string("found")); diff --git a/indra/alscript/tests/allinelabel_test.cpp b/indra/alscript/tests/allinelabel_test.cpp index dc7de09fd8..23f8bba655 100644 --- a/indra/alscript/tests/allinelabel_test.cpp +++ b/indra/alscript/tests/allinelabel_test.cpp @@ -113,7 +113,6 @@ namespace REQUIRE(ALLineLabel::relative("proj", "proj/util.luau").empty()); REQUIRE(ALLineLabel::relative("/home/me", "util.luau").empty()); } - UNSCOPED_INFO("nothing"); REQUIRE(ALLineLabel::relative("", "").empty()); } @@ -158,7 +157,6 @@ namespace TEST_CASE_METHOD(allinelabel_data, "allinelabel: quotable makes a label safe for a reader with no escapes", "[allinelabel]") { - UNSCOPED_INFO("each"); CHECK(ALLineLabel::quotable("a\"b\\c\nd\re\tf\x7fg") == std::string("a'b/c_d_e_f_g")); UNSCOPED_INFO("the rest as it was"); CHECK(ALLineLabel::quotable("lib/my util (2).luau \xc3\xa9") == std::string("lib/my util (2).luau \xc3\xa9")); @@ -190,7 +188,7 @@ namespace CHECK(label(fileOf("util.luau", "disk:/home/me/proj/lib/util.luau"), false) == std::string("lib/util.luau")); for (const char* path : { "disk:/home/me/proj/lib/util.luau", "disk:/home/me/shared/util.luau", "disk:/opt/x.luau" }) { - UNSCOPED_INFO("never from a root: " + std::string(path)); + CAPTURE(path); REQUIRE_FALSE(fromRoot(label.of(fileOf("x", path), false))); } } @@ -249,7 +247,6 @@ namespace REQUIRE(r.text.find("-- @line 1 \"../shared/util.luau\"\nreturn { b = 2 }\n") != std::string::npos); UNSCOPED_INFO("the script's: " + r.text); REQUIRE(r.text.find("-- @line 1 \"main.luau\"\nlocal a = require(1)\n") != std::string::npos); - UNSCOPED_INFO("three files"); REQUIRE(r.map.files().size() == size_t(3)); { INFO("each still called its __SHORTFILE__"); diff --git a/indra/alscript/tests/allslconsts_test.cpp b/indra/alscript/tests/allslconsts_test.cpp index 8334ad58ac..336b7106d4 100644 --- a/indra/alscript/tests/allslconsts_test.cpp +++ b/indra/alscript/tests/allslconsts_test.cpp @@ -63,11 +63,9 @@ namespace { UNSCOPED_INFO("builtins: " + error); CHECK(lslLoaded); - UNSCOPED_INFO("nothing wrong"); CHECK(said("const integer A = 1;\n" "default { state_entry() { const float f = A * 2; llOwnerSay((string)(f + A)); } }\n") == std::string()); - UNSCOPED_INFO("each write, where it is"); CHECK(said("const integer A = 1;\n" "f(const integer n) { n = 2; }\n" "default\n{\n state_entry()\n {\n const vector v = <1, 2, 3>;\n A += 1;\n v.x = 2;\n ++A;\n" @@ -76,7 +74,6 @@ namespace "ConstAssigned 7:8 A\n" "ConstAssigned 8:8 v\n" "ConstAssigned 9:8 A\n")); - UNSCOPED_INFO("no value"); CHECK(said("const integer A;\ndefault { state_entry() { const string s; llOwnerSay(s + (string)A); } }\n") == std::string("ConstWithoutValue 0:14 A\n" "ConstWithoutValue 1:39 s\n")); @@ -95,7 +92,6 @@ namespace "integer twice(integer x) { return x * 2; }\n" "const integer f(integer x) { integer y = twice(x) + fixed; return llAbs(y); }\n" + handler) == std::string()); - UNSCOPED_INFO("each thing it may not do, where it does it"); CHECK(said(globals + "const integer f(integer x)\n{\n counter = x;\n llOwnerSay(\"hi\");\n if (x) state other;\n return x + counter;\n}\n" "default { state_entry() { counter = 1; } }\nstate other { state_entry() { } }\n") == @@ -124,9 +120,7 @@ namespace "const integer C = changing + 1;\n" "default { state_entry() { changing = 2; llOwnerSay((string)[FLAGS, STEP, TWICE, UP, NAME, SAME, PLAIN, C] + (string)L); } }\n"; const ALPreprocessor::Result r = ALPreprocessor::run(source, ALPreprocessor::Options()); - UNSCOPED_INFO("nothing wrong"); CHECK(said(source) == std::string()); - UNSCOPED_INFO("each written"); CHECK(r.text == std::string("integer FLAGS = 20;\n" "float STEP = 0.5235988;\n" @@ -148,7 +142,6 @@ namespace CHECK(twenty.column == 22); } // The names are where they are now: the list's two lines are one. - UNSCOPED_INFO("nine"); REQUIRE(r.consts.size() == 9); { INFO("a name after the folded list, a line up"); @@ -189,7 +182,6 @@ namespace "ConstNotKnown 2:18 KEYS\n")); // A plain global is left to the compiler: only a const is worked out. const std::string plain = "integer A = 2 * 3;\ndefault { state_entry() { llOwnerSay((string)A); } }\n"; - UNSCOPED_INFO("not a const"); CHECK(ALPreprocessor::run(plain, ALPreprocessor::Options()).text == plain); } @@ -226,7 +218,6 @@ namespace const ALPreprocessor::Result r = ALPreprocessor::run(source, o); UNSCOPED_INFO("optimized: " + r.text); CHECK(r.optimized); - UNSCOPED_INFO("nothing wrong"); CHECK(said(source) == std::string()); // The list stays a literal: as a sum it would need a helper the // script has no other use for, which costs Mono more than it saves. @@ -254,7 +245,6 @@ namespace "const integer twice(const integer n) { return n * 2; }\n" "default { state_entry() { llOwnerSay((string)twice(LIMIT)); } }\n", ALPreprocessor::Options()); - UNSCOPED_INFO("three"); 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); @@ -266,16 +256,14 @@ namespace const size_t handler = r.text.find("default"); for (const auto& [word, from] : std::vector>{ { "LIMIT", handler }, { "twice", handler }, { "n * 2", 0 } }) { + CAPTURE(word); const ALScriptHover hover = declaredAt(word, from); - { - INFO(word + ": found"); - REQUIRE(hover.found); - REQUIRE(hover.hasDefinition); - } + REQUIRE(hover.found); + REQUIRE(hover.hasDefinition); const bool matches = std::any_of(r.consts.begin(), r.consts.end(), [&](const ALPreprocessor::Result::Const& c) { return c.line == hover.definitionLine && c.column == hover.definitionColumn; }); - UNSCOPED_INFO(word + ": at a const's place, " + std::to_string(hover.definitionLine) + ":" + std::to_string(hover.definitionColumn)); + CAPTURE(hover.definitionLine, hover.definitionColumn); REQUIRE(matches); } } diff --git a/indra/alscript/tests/allslcosts_test.cpp b/indra/alscript/tests/allslcosts_test.cpp index f6dfcae33a..1c81249e1c 100644 --- a/indra/alscript/tests/allslcosts_test.cpp +++ b/indra/alscript/tests/allslcosts_test.cpp @@ -84,13 +84,13 @@ namespace body + "llOwnerSay((string)[i, j, f, s, k, v] + (string)l);\n} }\n"; } - S32 size(Target t, const Snippet& s, int copies) + S32 require_compiled_size(Target t, const Snippet& s, int copies) { const std::string text = script(s, copies); const ALScriptWeight weight = t == Target::LSO ? ALScriptWeigh::lso(text) : t == Target::Mono ? ALScriptWeigh::mono(text) : ALScriptWeigh::lslLuau(text); - UNSCOPED_INFO("compiles for " + std::string(nameOf(t)) + ": " + weight.error + "\n" + text); + INFO("compiles for " << nameOf(t) << ": " << weight.error << "\n" << text); REQUIRE(weight.compiled); return S32(weight.total); } @@ -105,11 +105,11 @@ namespace }; Delta delta(Target t, const Snippet& a, const Snippet& b) { - const S32 a1 = size(t, a, 1); - const S32 b1 = size(t, b, 1); + const S32 a1 = require_compiled_size(t, a, 1); + const S32 b1 = require_compiled_size(t, b, 1); Delta d; d.first = b1 - a1; - d.each = !a.body.empty() && !b.body.empty() ? (size(t, b, 2) - b1) - (size(t, a, 2) - a1) : d.first; + d.each = !a.body.empty() && !b.body.empty() ? (require_compiled_size(t, b, 2) - b1) - (require_compiled_size(t, a, 2) - a1) : d.first; return d; } @@ -385,21 +385,20 @@ namespace const Delta d = delta(t, fact.a, fact.b); said(fact.what, t, d, 0); const char v = fact.once ? verdict({ d.first, d.first }) : verdict(d); - UNSCOPED_INFO(std::string(fact.what) + " on " + nameOf(t)); + INFO(fact.what << " on " << nameOf(t)); REQUIRE(v == fact.verdicts[n]); // A flag on is a form smaller there; one off may be for what // a first use costs a script that the pair does not show // (allslcosts.cpp says where). if (fact.flag && ALLSLCosts::of(t).*fact.flag) { - UNSCOPED_INFO(std::string(fact.what) + ": the flag on " + nameOf(t) + ", and the form not smaller"); + UNSCOPED_INFO("the flag is on and the form not smaller"); REQUIRE(v == '<'); } } } } - // What a function costs, and a call of it, a local and a jump. TEST_CASE_METHOD(allslcosts_data, "ALLSLCosts: what a function costs and a call of it and a local and a jump", "[ALLSLCosts]") { UNSCOPED_INFO("builtins: " + error); @@ -410,40 +409,33 @@ namespace const Snippet param{ "f1(integer a) { }", "" }; for (const Target t : TARGETS) { - const ALLSLCosts& c = ALLSLCosts::of(t); - const std::string where = std::string(" on ") + nameOf(t); + const ALLSLCosts& c = ALLSLCosts::of(t); + INFO("on " << nameOf(t)); const Delta local = delta(t, { "", "integer z = i; j = z;" }, { "", "j = i;" }); said("a local", t, local, -c.local); - UNSCOPED_INFO("a local" + where); REQUIRE(-local.each == c.local); const Delta jump = delta(t, { "", "jump L; @L;" }, { "", "" }); said("a jump", t, jump, -c.jump); - UNSCOPED_INFO("a jump" + where + ": " + std::to_string(-jump.first)); REQUIRE(-jump.first == c.jump); const Delta f2 = delta(t, none, two); const Delta f18 = delta(t, none, eighteen); said("a function", t, f2, c.function + 2 * c.functionChar); - UNSCOPED_INFO("a function's name, by the character" + where); REQUIRE((f18.first - f2.first) / 16 == c.functionChar); - UNSCOPED_INFO("a function" + where); REQUIRE(f2.first - 2 * c.functionChar == c.function); const Delta p = delta(t, two, param); said("a parameter", t, p, c.parameter); - UNSCOPED_INFO("a parameter" + where); REQUIRE(p.first == c.parameter); const Delta call = delta(t, { "f1() { }", "f1();" }, { "f1() { }", "f1(); f1();" }); said("a call", t, call, c.call); - UNSCOPED_INFO("a call" + where); REQUIRE(call.each == c.call); const Delta argued = delta(t, { "f1(integer a) { }", "f1(i);" }, { "f1(integer a) { }", "f1(i); f1(i);" }); said("a call with an argument", t, argued, c.call + c.argument); - UNSCOPED_INFO("an argument" + where); REQUIRE(argued.each - c.call == c.argument); for (const char* text : { "hello", "hello world, hello" }) @@ -451,7 +443,7 @@ namespace const S32 length = S32(strlen(text)); const Delta string = delta(t, { "", "s = s;" }, { "", "s = \"" + std::string(text) + "\";" }); said("a string of " + std::to_string(length) + ", held once", t, string, c.stringHeld + length * c.stringHeldChar); - UNSCOPED_INFO("a string of " + std::to_string(length) + ", held once" + where); + CAPTURE(length); REQUIRE(string.first - string.each == c.stringHeld + length * c.stringHeldChar); } @@ -459,9 +451,7 @@ namespace const Delta first2 = delta(t, two, { "f1() { }", "f1();" }); const Delta first18 = delta(t, eighteen, { "a_much_longer_name() { }", "a_much_longer_name();" }); said("a first call", t, first2, c.call + c.reference + 2 * c.referenceChar); - UNSCOPED_INFO("a first call's reference, by the character" + where); REQUIRE((first18.first - first2.first) / 16 == c.referenceChar); - UNSCOPED_INFO("a first call's reference" + where); REQUIRE(first2.first - c.call - 2 * c.referenceChar == c.reference); // The most elements of a literal added to a list bare: at it @@ -478,20 +468,19 @@ namespace }; if (c.elementsForListMost > 0) { - UNSCOPED_INFO("the most added bare, smaller" + where); + UNSCOPED_INFO("the most added bare, smaller"); REQUIRE(verdict(bare(c.elementsForListMost)) == '<'); - UNSCOPED_INFO("one more, larger" + where); + UNSCOPED_INFO("one more, larger"); REQUIRE(verdict(bare(c.elementsForListMost + 1)) == '>'); } else { - UNSCOPED_INFO("eight added bare, smaller" + where); + UNSCOPED_INFO("eight added bare, smaller"); REQUIRE(verdict(bare(8)) == '<'); } } } - // A global never assigned, against its value written where it is read. TEST_CASE_METHOD(allslcosts_data, "ALLSLCosts: a global never assigned against its value written where it is read", "[ALLSLCosts]") { UNSCOPED_INFO("builtins: " + error); @@ -516,8 +505,8 @@ namespace const auto put = [](std::string text, const std::string& what) { return text.replace(text.find('@'), 1, what); }; for (const Target t : TARGETS) { - const ALLSLCosts& c = ALLSLCosts::of(t); - const std::string where = std::string(" on ") + nameOf(t); + const ALLSLCosts& c = ALLSLCosts::of(t); + INFO("on " << nameOf(t)); for (const Kind& kind : kinds) { std::string value; @@ -532,11 +521,9 @@ namespace const ALLSLCosts::Held& held = c.*kind.held; said(std::string(kind.what) + " global", t, own, held.global); said(std::string(kind.what) + " written out", t, out, held.first); - UNSCOPED_INFO(std::string(kind.what) + " global" + where); + CAPTURE(kind.what); REQUIRE(own.first == held.global); - UNSCOPED_INFO(std::string(kind.what) + " written out, first" + where); REQUIRE(out.first == held.first); - UNSCOPED_INFO(std::string(kind.what) + " written out, each" + where); REQUIRE(out.each == held.each); } // A string at two lengths, what a character adds the same. @@ -551,11 +538,9 @@ namespace const std::string what = "a string of " + std::to_string(length); said(what + " global", t, own, held.global); said(what + " written out", t, out, held.first); - UNSCOPED_INFO(what + " global" + where); + CAPTURE(length); REQUIRE(own.first == held.global); - UNSCOPED_INFO(what + " written out, first" + where); REQUIRE(out.first == held.first); - UNSCOPED_INFO(what + " written out, each" + where); REQUIRE(out.each == held.each); } } @@ -602,18 +587,17 @@ namespace { bytes = part.name == one.name ? S32(part.bytes) : bytes; } - UNSCOPED_INFO(std::string(one.what) + ": the function weighed on " + nameOf(t) + ": " + whole.error); + INFO(one.what << " on " << nameOf(t)); + UNSCOPED_INFO("the function weighed: " << whole.error); REQUIRE(bytes > 0); - const S32 measured = size(t, one.inlined, 1) - size(t, one.called, 1); + const S32 measured = require_compiled_size(t, one.inlined, 1) - require_compiled_size(t, one.called, 1); ALLSLCosts::Inlining f = one.f; f.bytes = bytes; const S32 estimate = ALLSLCosts::of(t).inlined(f); said(std::string(one.what) + " (estimated)", t, { measured, measured }, estimate); - { - INFO(std::string(one.what) + " on " + nameOf(t) + ": estimated " + std::to_string(estimate) + ", measured " + std::to_string(measured)); - REQUIRE((estimate < 0) == (measured < 0)); - REQUIRE(std::abs(estimate - measured) <= std::max(12, std::abs(measured) / 4)); - } + CAPTURE(estimate, measured); + REQUIRE((estimate < 0) == (measured < 0)); + REQUIRE(std::abs(estimate - measured) <= std::max(12, std::abs(measured) / 4)); } } } @@ -627,8 +611,8 @@ namespace CHECK(lslLoaded); for (const Target t : TARGETS) { - const ALLSLCosts& c = ALLSLCosts::of(t); - const std::string where = std::string(" on ") + nameOf(t); + const ALLSLCosts& c = ALLSLCosts::of(t); + INFO("on " << nameOf(t)); if (c.listShapeLeast == 0) { continue; @@ -648,7 +632,6 @@ namespace least = std::min(least, -delta(t, a, b).each); } said("the least a list's shape saves", t, { least, least }, c.listShapeLeast); - UNSCOPED_INFO("the least a list's shape saves at a place" + where); REQUIRE(least == c.listShapeLeast); // A helper each: a cast to a list, something added after one, // something added before one. @@ -660,7 +643,6 @@ namespace most = std::max(most, d.first - d.each); } said("the most a helper costs", t, { most, most }, c.listHelperMost); - UNSCOPED_INFO("the most a list's helper new to the script costs" + where); REQUIRE(most == c.listHelperMost); } } diff --git a/indra/alscript/tests/allsleffects_test.cpp b/indra/alscript/tests/allsleffects_test.cpp index ed64bb7d6d..8c9217db14 100644 --- a/indra/alscript/tests/allsleffects_test.cpp +++ b/indra/alscript/tests/allsleffects_test.cpp @@ -126,7 +126,6 @@ namespace "r() { k += 2; s(); }\n" "s() { r(); }\n" "default { state_entry() { f(); } }\n"); - UNSCOPED_INFO("parsed"); REQUIRE(p.script != nullptr); const ALLSLEffects effects(p.script); const auto is = [&](const char* name, std::initializer_list globals, bool impure) { @@ -138,7 +137,6 @@ namespace } return same; }; - UNSCOPED_INFO("a writes g"); CHECK(is("a", { "g" }, false)); UNSCOPED_INFO("b writes h, and g through a"); CHECK(is("b", { "g", "h" }, false)); @@ -181,12 +179,10 @@ namespace " x = y + same(y++);\n" // 11: and here after " g = bump();\n" // 12: the store is after "} }\n"); - UNSCOPED_INFO("parsed"); REQUIRE(p.script != nullptr); const ALLSLEffects effects(p.script); const std::vector bumps = p.calls("bump"); const std::vector sames = p.calls("same"); - UNSCOPED_INFO("the calls"); REQUIRE(bumps.size() == size_t(11)); const auto first = [&](LSLFunctionExpression* call) { return effects.mayRunFirst(statementOf(call), call); }; UNSCOPED_INFO("0: the right operand runs first"); @@ -231,7 +227,6 @@ namespace " l = [look(), bump()];\n" // bump writes what look reads " l = [bump(), same(2)];\n" // same changes nothing, and bump does "} }\n"); - UNSCOPED_INFO("parsed"); REQUIRE(q.script != nullptr); const ALLSLEffects quiet(q.script); const auto firstQ = [&](LSLFunctionExpression* call) { return quiet.mayRunFirst(statementOf(call), call); }; @@ -253,7 +248,6 @@ namespace "integer near() { return look(); }\n" "integer bump() { return ++h; }\n" "default { state_entry() { me(); loud(); near(); bump(); } }\n"); - UNSCOPED_INFO("parsed"); REQUIRE(p.script != nullptr); const ALLSLEffects effects(p.script); UNSCOPED_INFO("reading the world is past the script"); diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index 05552e0dde..5f936f1ae4 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -173,7 +173,7 @@ namespace // A script optimized for a target with the defaults, what it became // checked to compile there. - static std::string optimized(const std::string& source, ALLSLOptimizer::Target target) + static std::string require_optimized(const std::string& source, ALLSLOptimizer::Target target) { ALLSLOptimizer::Options o = options(); o.target = target; @@ -211,17 +211,12 @@ namespace const std::string source = "integer unused = 5;\ninteger used = 2;\n" + wrap("", " integer x = used * 3 + 1;\n llSay(0, (string)x);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); - UNSCOPED_INFO("text"); CHECK(r.text == wrap("", " llSay(0, \"7\");\n")); - UNSCOPED_INFO("noted the global"); CHECK(has(r, "removed the unused global unused")); UNSCOPED_INFO("noted the fold: " + notes(r)); CHECK(has(r, "folded used * 3 + 1 to 7")); - UNSCOPED_INFO("noted the local"); CHECK(has(r, "removed the unused local x")); - UNSCOPED_INFO("smaller"); CHECK(r.sizeAfter < r.sizeBefore); } @@ -231,15 +226,12 @@ namespace // it stays a variable and the fold shows in its declaration. const std::string source = wrap("", " float a = 0.1 + 0.2;\n float b = PI * 2;\n float c = 1.0 / 3;\n if (llFrand(1) < 0.5)\n {\n a = b = c = 0;\n }\n llSay(0, (string)a + (string)b + (string)c);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); // 0.1f + 0.2f is 0.3 exactly as a single, PI * 2 is 6.2831855f, // and each reads back as the same single. - UNSCOPED_INFO("a: " + r.text); + UNSCOPED_INFO("as written: " + r.text); REQUIRE(r.text.find("float a = 0.3;") != std::string::npos); - UNSCOPED_INFO("b"); REQUIRE(r.text.find("float b = 6.2831855;") != std::string::npos); - UNSCOPED_INFO("c"); REQUIRE(r.text.find("float c = 0.33333334;") != std::string::npos); // A float that is a whole number prints as an integer where LSL @@ -260,7 +252,6 @@ namespace { const std::string source = wrap("", " llSay(0, (string)(2147483647 + 1));\n llSay(0, (string)(1 / 0));\n llSay(0, (string)(-2147483648 / -1));\n llSay(0, (string)(7 / 2 * 2));\n llSay(0, (string)(1 << 40));\n llSay(0, (string)(-7 % 3));\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); UNSCOPED_INFO("wrapped: " + r.text); REQUIRE(r.text.find("llSay(0, \"-2147483648\");") != std::string::npos); @@ -281,7 +272,6 @@ namespace { const std::string source = wrap("", " integer a = llAbs(-3);\n float b = llSqrt(16);\n string c = llToUpper(\"abc\");\n string d = llGetSubString(\"abcdef\", 4, 1);\n string e = llDeleteSubString(\"abcdef\", 1, 3);\n integer f = llStringLength(\"hello\");\n string g = llEscapeURL(\"a b\");\n string h = llBase64ToString(llStringToBase64(\"hi\"));\n integer i = llList2Integer([1, 2, 3], -1);\n string j = llList2String([1, 2.5, <1,2,3>], 1);\n list k = llList2List([1, 2, 3, 4], 1, 2);\n integer l = llFloor(2.7) + llCeil(2.2);\n string m = llGetSubString(\"h\xc3\xa9llo\", 0, 1);\n integer n = llGetUnixTime();\n string o = llList2CSV([1, \"a\", 2.0]);\n if (llFrand(1) < 0.5)\n {\n a = f = i = l = n = 0; b = 0; c = d = e = g = h = j = m = o = \"\"; k = [];\n }\n llSay(0, (string)a + (string)b + c + d + e + (string)f + g + h + (string)i + j + llList2CSV(k) + (string)l + m + (string)n + o);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); UNSCOPED_INFO("abs: " + r.text); REQUIRE(r.text.find("integer a = 3;") != std::string::npos); @@ -320,7 +310,6 @@ namespace const std::string source = wrap("integer g;\ninteger h;\n", " integer a = g + 0;\n integer b = g * 1;\n integer c = g - -1;\n integer d = g + -2;\n integer e = (integer)g;\n integer f = g * 0;\n integer i = (g == 0);\n if (g != 0) h = 1;\n if (!!g) h = 2;\n if (!(g == h)) h = 3;\n if (!g) h = 4; else h = 5;\n h = a + b + c + d + e + f + i;\n a = b = c = d = e = f = i = 0;\n g = h;\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); UNSCOPED_INFO("plus zero: " + r.text); REQUIRE(r.text.find("integer a = g;") != std::string::npos); @@ -357,15 +346,11 @@ namespace const std::string source = "f()\n{\n return;\n llSay(0, \"never\");\n}\ng()\n{\n}\n" "default\n{\n state_entry()\n {\n if (0) llSay(0, \"no\");\n if (1) llSay(0, \"yes\"); else llSay(0, \"no\");\n while (0) llSay(0, \"no\");\n @unused;\n f();\n ;\n {}\n 3;\n }\n}\nstate other\n{\n state_entry()\n {\n }\n}\n"; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); - UNSCOPED_INFO("text"); CHECK(r.text == "f()\n{\n return;\n}\n\ndefault\n{\n state_entry()\n {\n llSay(0, \"yes\");\n f();\n }\n}\n"); UNSCOPED_INFO("noted the return"); CHECK(has(r, "can never run")); - UNSCOPED_INFO("noted g"); CHECK(has(r, "removed the unused function g")); - UNSCOPED_INFO("noted the state"); CHECK(has(r, "removed the state other")); UNSCOPED_INFO("noted the label"); CHECK(has(r, "nothing jumps to")); @@ -419,13 +404,9 @@ namespace ALLSLOptimizer::Options o = options(); o.shrinknames = true; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); - UNSCOPED_INFO("renamed counter"); REQUIRE(r.renamed.count("counter") == 1); - UNSCOPED_INFO("renamed bump"); REQUIRE(r.renamed.count("bump") == 1); - UNSCOPED_INFO("renamed total"); REQUIRE(r.renamed.count("total") == 1); UNSCOPED_INFO("the event stays"); REQUIRE(r.text.find("touch_start(integer ") != std::string::npos); @@ -441,36 +422,26 @@ namespace REQUIRE(r.text.find("counter") == std::string::npos); REQUIRE(r.text.find("bump") == std::string::npos); } - UNSCOPED_INFO("noted"); CHECK(has(r, "renamed the function bump to")); } TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a script with errors is left alone and the map leads back", "[allsloptimizer]") { ALLSLOptimizer::Result r = ALLSLOptimizer::run("default { state_entry() { integer x = ; } }", options()); - UNSCOPED_INFO("not optimized"); CHECK_FALSE(r.optimized); - { - INFO("an error"); - REQUIRE(r.problems.size() == 1); - CHECK(r.problems[0].severity == ALScriptProblem::Severity::Error); - } + REQUIRE(r.problems.size() == 1); + CHECK(r.problems[0].severity == ALScriptProblem::Severity::Error); UNSCOPED_INFO("the source as it was"); CHECK(r.text == "default { state_entry() { integer x = ; } }"); const std::string source = "integer g = 3;\n\n\ndefault\n{\n state_entry()\n {\n llSay(0, (string)g);\n }\n}\n"; r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); - UNSCOPED_INFO("text"); CHECK(r.text == "default\n{\n state_entry()\n {\n llSay(0, \"3\");\n }\n}\n"); // The llSay is on output line 4 and came from source line 7. const ALSourceMap::Loc loc = r.map.toSource(4, 8); - UNSCOPED_INFO("found"); REQUIRE(loc.found()); - UNSCOPED_INFO("line"); CHECK(loc.line == 7); - UNSCOPED_INFO("column"); CHECK(loc.column == 8); } @@ -486,6 +457,7 @@ namespace { continue; } + CAPTURE(name); const std::string stem = entry.path().stem().string(); const fs::path expected = dir / (stem + "_expected.lsl"); const auto read = [](const fs::path& path) { @@ -501,7 +473,7 @@ namespace o.addstrings = first.find("addstrings") != std::string::npos; o.target = first.find("lso") != std::string::npos ? ALLSLOptimizer::Target::LSO : first.find("luau") != std::string::npos ? ALLSLOptimizer::Target::Luau : ALLSLOptimizer::Target::Mono; ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); - UNSCOPED_INFO("optimized " + name + ": " + notes(r)); + UNSCOPED_INFO("optimized: " + notes(r)); REQUIRE(r.optimized); if (const char* write = std::getenv("AL_OPTIMIZER_WRITE_EXPECTED"); write && *write) { @@ -509,9 +481,7 @@ namespace out << r.text; continue; } - UNSCOPED_INFO("expected file for " + name); REQUIRE(fs::exists(expected)); - UNSCOPED_INFO("output of " + name); REQUIRE(r.text == read(expected)); ++checked; } @@ -522,7 +492,6 @@ namespace { const std::string source = wrap("", " rotation a = llEuler2Rot(<0, 0, PI_BY_TWO>);\n vector b = llRot2Euler(<0, 0, 0.7071068, 0.7071068>);\n rotation c = llAxisAngle2Rot(<0, 0, 2>, PI);\n vector d = llRot2Axis(<0, 0, 0.7071068, 0.7071068>);\n float e = llRot2Angle(<0, 0, 0.7071068, 0.7071068>);\n vector f = llRot2Fwd(<0, 0, 0.7071068, 0.7071068>);\n vector h = llRot2Up(<0.7071068, 0, 0, 0.7071068>);\n vector i = llRot2Axis(<0, 0, 0, 1>);\n vector j = llRot2Fwd(<0, 0, 2, 2>);\n if (llFrand(1) < 0.5)\n {\n a = c = ZERO_ROTATION; b = d = f = h = i = j = ZERO_VECTOR; e = 0;\n }\n llSay(0, (string)a + (string)b + (string)c + (string)d + (string)e + (string)f + (string)h + (string)i + (string)j);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; // A quarter turn about z: x and y zero, z and s a half root two. @@ -558,7 +527,6 @@ namespace { const std::string source = wrap("", " string a = llJsonGetValue(\"{\\\"name\\\": \\\"Ann\\\", \\\"tags\\\": [\\\"x\\\", 7, {\\\"k\\\": \\\"v\\\"}]}\", [\"name\"]);\n string b = llJsonGetValue(\"{\\\"tags\\\": [\\\"x\\\", -7]}\", [\"tags\", 1]);\n string c = llJsonGetValue(\"{\\\"tags\\\": [\\\"x\\\", 7, {\\\"k\\\": \\\"v\\\"}]}\", [\"tags\", 2, \"k\"]);\n string d = llJsonGetValue(\"[1.5]\", [0]);\n string e = llJsonGetValue(\"[true]\", [0]);\n string f = llJsonGetValue(\"[1]\", [3]);\n string g = llJsonGetValue(\"{\\\"a\\\": 1, \\\"a\\\": 2}\", [\"a\"]);\n string h = llJsonGetValue(\"[1,]\", [0]);\n string i = llJsonGetValue(\"[\\\"a\\\\\\\"b\\\"]\", [0]);\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = i = \"\";\n }\n llSay(0, a + b + c + d + e + f + g + h + i);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; UNSCOPED_INFO("a key: " + r.text); @@ -582,7 +550,6 @@ namespace // The answers that are constants are the constants' names. const std::string more = wrap("", " string a = llJsonGetValue(\"[true, null]\", [0]);\n string b = llJsonGetValue(\"[1]\", [3]);\n string c = llJsonValueType(\"{\\\"a\\\": [1, {}]}\", [\"a\", 1]);\n string d = llJsonValueType(\"{\\\"a\\\": 1}\", [\"b\"]);\n string e = llJsonValueType(\"[1,]\", []);\n string f = llList2Json(JSON_OBJECT, [\"name\", \"Ann Lee\", \"count\", 3]);\n string g = llList2Json(JSON_ARRAY, [1, \"true\"]);\n string h = llList2Json(JSON_ARRAY, [1.5]);\n list i = llJson2List(\"[1, \\\"two\\\", -3]\");\n list j = llJson2List(\"{\\\"k\\\": \\\"v\\\"}\");\n list k = llJson2List(\"[true]\");\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = \"\"; i = j = k = [];\n }\n llSay(0, a + b + c + d + e + f + g + h + llList2CSV(i) + llList2CSV(j) + llList2CSV(k));\n"); ALLSLOptimizer::Result m = ALLSLOptimizer::run(more, options()); - UNSCOPED_INFO("optimized"); CHECK(m.optimized); auto hasm = [&m](const char* text) { return m.text.find(text) != std::string::npos; }; UNSCOPED_INFO("true is JSON_TRUE: " + m.text); @@ -611,7 +578,6 @@ namespace // value in, at a key or an index, where the document is plain. const std::string sets = wrap("", " string a = llJsonGetValue(\"{\\\"a\\\": [1, {\\\"b\\\": true}, \\\"c\\\"]}\", [\"a\"]);\n string b = llJsonSetValue(\"{\\\"a\\\": 1}\", [\"b\"], \"two\");\n string c = llJsonSetValue(\"[1, 2]\", [1], \"9\");\n string d = llJsonSetValue(\"[1, 2]\", [JSON_APPEND], JSON_TRUE);\n string e = llJsonSetValue(\"[1, 2]\", [5], \"9\");\n string f = llJsonSetValue(\"{}\", [\"a\", \"b\"], \"x\");\n string g = llJsonSetValue(\"[1]\", [0], \"1.5\");\n string h = llJsonGetValue(\"[1.5]\", []);\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = \"\";\n }\n llSay(0, a + b + c + d + e + f + g + h);\n"); ALLSLOptimizer::Result n = ALLSLOptimizer::run(sets, options()); - UNSCOPED_INFO("optimized"); CHECK(n.optimized); auto hasn = [&n](const char* text) { return n.text.find(text) != std::string::npos; }; UNSCOPED_INFO("a nested value, compact: " + n.text); @@ -681,7 +647,6 @@ namespace CHECK(r.optimized); // The inliner's own text is what the optimizer read. const ALLSLInliner::Result put = ALLSLInliner::run(source); - UNSCOPED_INFO("five put in place"); CHECK(put.inlined == 5); UNSCOPED_INFO("the block where the call was: " + put.text); REQUIRE(put.text.find(" {\nstring what = \"hi\";\ninteger n = i;\n\n integer i_1 = n * 2;\n llSay(0, what + (string)i_1);\n\n}\n") != std::string::npos); @@ -726,7 +691,6 @@ namespace CHECK(said == 5); // The map leads from the block back to the function's own lines. const size_t at = r.text.find("llSay(0, what + (string)i_1);"); - UNSCOPED_INFO("found"); REQUIRE(at != std::string::npos); S32 line = 0; for (size_t i = 0; i < at; ++i) @@ -860,7 +824,6 @@ namespace CHECK_FALSE(has("inner(integer k)")); CHECK_FALSE(has("outer()\n")); } - UNSCOPED_INFO("three calls went"); CHECK(put.inlined == 3); ALLSLOptimizer::Options o = options(); o.inlining = true; @@ -923,7 +886,6 @@ namespace } UNSCOPED_INFO("no stray ; where the declaration stood: " + put.text); CHECK_FALSE(has("};")); - UNSCOPED_INFO("six went"); CHECK(put.inlined == 6); // A temporary above an if whose condition has the call, and above // a for whose first part has it; not above a while, whose @@ -958,7 +920,6 @@ namespace CHECK(got("while (both(llAbs(g)) < 9) g++;")); UNSCOPED_INFO("so does the else-if: " + above.text); CHECK(got("else if (both(llAbs(g))) g = 2;")); - UNSCOPED_INFO("two went"); CHECK(above.inlined == 2); } TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: recursive functions stay and a label after the call is the return's target", "[allsloptimizer]") @@ -1014,7 +975,6 @@ namespace CHECK_FALSE(has("_ret")); CHECK(has(" @next;")); } - UNSCOPED_INFO("one went"); CHECK(put.inlined == 1); } TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: a run without notes says nothing and one out of budget stops where it is", "[allsloptimizer]") @@ -1035,7 +995,6 @@ namespace o.notes = true; o.visitBudget = 1; ALLSLOptimizer::Result stopped = ALLSLOptimizer::run(source, o); - UNSCOPED_INFO("it stopped early"); CHECK(stopped.stoppedEarly); { INFO("and said so: " + notes(stopped)); @@ -1059,7 +1018,6 @@ namespace " llSetPrimitiveParams([PRIM_TEXTURE, 0, s, <1, 1, 0>, ZERO_VECTOR, 0.0]);\n" " llSay(n, llList2CSV(l) + s);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); UNSCOPED_INFO("a zero appended stays: " + r.text); REQUIRE(r.text.find("l = l + 0;") != std::string::npos); @@ -1083,7 +1041,6 @@ namespace // The literal's elements, however it is written now, all there: // the texture's face and the glow. const size_t call = r.text.find("llSetPrimitiveParams("); - UNSCOPED_INFO("the call"); REQUIRE(call != std::string::npos); const std::string args = r.text.substr(call, r.text.find(';', call) - call); UNSCOPED_INFO("the face, a zero: " + args); @@ -1355,10 +1312,10 @@ namespace TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: list literal sums bracket elements and stay literals where order shows", "[allsloptimizer]") { // On LSO, where a list's shapes are made wherever they are smaller: - // Mono weighs them against the helpers they need (test 41). + // Mono weighs them against the helpers they need (see the case on Mono's list shapes). ALLSLOptimizer::Options lso = options(); lso.target = ALLSLOptimizer::Target::LSO; - const auto compiles = [](const ALLSLOptimizer::Result& r) { + const auto require_compiles = [](const ALLSLOptimizer::Result& r) { ALLSLService service; const ALScriptProblems said = service.check(r.text); for (const ALScriptProblem& p : said) @@ -1382,7 +1339,7 @@ namespace // Each element added to the list bare (ALLSLCosts::elementForList). UNSCOPED_INFO("the comparison bracketed: " + r.text); REQUIRE(r.text.find("loc_params + 41 + (-1 < --loc_sitTargetsRemaining) + <0.0, 0.0, 0.0> + <0.0, 0.0, 0.0, 1.0>") != std::string::npos); - compiles(r); + require_compiles(r); // The first element under the cast, bracketed where the cast would // take less of it; the rest where the sum would. @@ -1397,7 +1354,7 @@ namespace REQUIRE(r.text.find("l = (list)-1 + 3;") != std::string::npos); UNSCOPED_INFO("a product under the cast, a difference after it: " + r.text); REQUIRE(r.text.find("l = (list)(a * b) + (a - b);") != std::string::npos); - compiles(r); + require_compiles(r); // A sum takes its right side first, so the elements would be taken // last to first: where more than one of them could see another's @@ -1415,7 +1372,7 @@ namespace REQUIRE(r.text.find("l = [llGetUnixTime(), bump()];") != std::string::npos); UNSCOPED_INFO("reads alone, a sum: " + r.text); REQUIRE(r.text.find("l = (list)i + g;") != std::string::npos); - compiles(r); + require_compiles(r); } TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: removed unreachable code is quoted by its first line cut to a few words", "[allsloptimizer]") @@ -1425,11 +1382,8 @@ namespace body += "\");\n"; const ALLSLOptimizer::Result r = ALLSLOptimizer::run(wrap("", body), options()); const auto note = std::find_if(r.problems.begin(), r.problems.end(), [](const ALScriptProblem& p) { return p.key == "OptimizerRemovedUnreachable"; }); - { - INFO("said"); - REQUIRE(note != r.problems.end()); - REQUIRE(note->args.size() == 1); - } + REQUIRE(note != r.problems.end()); + REQUIRE(note->args.size() == 1); { INFO("not all of it"); REQUIRE(note->args[0].size() < 80); @@ -1451,13 +1405,11 @@ namespace // Room for the inliner's first round and not much more: it stops, // and leaves the optimizer nothing. const ALLSLInliner::Result once = ALLSLInliner::run(source, ALLSLInliner::Asked(), size_t(-1)); - UNSCOPED_INFO("the inliner visits"); CHECK(once.visited > 0); o.visitBudget = once.visited / 4; const ALLSLOptimizer::Result short_of = ALLSLOptimizer::run(source, o); UNSCOPED_INFO("the optimizer stopped for what the inliner spent"); CHECK(short_of.stoppedEarly); - UNSCOPED_INFO("said"); CHECK(std::any_of(short_of.problems.begin(), short_of.problems.end(), [](const ALScriptProblem& p) { return p.key == "OptimizerStoppedEarly"; })); const ALLSLInliner::Result cut = ALLSLInliner::run(source, ALLSLInliner::Asked(), 1); { @@ -1546,7 +1498,7 @@ namespace REQUIRE(r.text.find("[llGetTime(), llGetTime()]") != std::string::npos); for (const char* pure : { "llRound", "llListFindList", "llListSort", "llDumpList2String", "llMD5String", "llAcos", "llAsin" }) { - UNSCOPED_INFO(std::string(pure) + " is pure"); + CAPTURE(pure); REQUIRE(ALLSLTraits::pure(pure)); } { @@ -1646,7 +1598,8 @@ namespace for (const char* label : { "@_ret_1;", "@_ret_2;", "@_ret_3;", "@_ret_4;", "@_ret_5;", "@_ret_6;" }) { const size_t first = put.text.find(label); - UNSCOPED_INFO(std::string("one ") + label + ": " + put.text); + CAPTURE(label); + UNSCOPED_INFO("at most once: " + put.text); REQUIRE((first == std::string::npos || put.text.find(label, first + 1) == std::string::npos)); } UNSCOPED_INFO("g runs before f(4) and f writes g: the call stays: " + put.text); @@ -1882,8 +1835,8 @@ namespace REQUIRE(refused.has_value()); { INFO("as a warning with the parser's words"); - REQUIRE(refused->key == "OptimizerWroteUncompilable"); - REQUIRE(refused->severity == ALScriptProblem::Severity::Warning); + CHECK(refused->key == "OptimizerWroteUncompilable"); + CHECK(refused->severity == ALScriptProblem::Severity::Warning); REQUIRE_FALSE(refused->args[0].empty()); } } @@ -1969,11 +1922,8 @@ namespace REQUIRE(r.text.find("say(") == std::string::npos); UNSCOPED_INFO("the large one stays: " + r.text); REQUIRE(r.text.find("tell(") != std::string::npos); - { - INFO("weighed"); - REQUIRE(r.weight.compiled); - REQUIRE(r.weight.total > 0); - } + REQUIRE(r.weight.compiled); + REQUIRE(r.weight.total > 0); const ALScriptProblem* chose = nullptr; for (const ALScriptProblem& p : r.problems) { @@ -2502,21 +2452,29 @@ namespace const std::string hashOfX = "\"70c6ab518c36e18d9925c3c27c048b26\""; for (const Target target : { Target::LSO, Target::Mono, Target::Luau }) { - UNSCOPED_INFO("a hash of one character is smaller as the call"); + INFO("a hash of one character is smaller as the call"); REQUIRE_FALSE(folded(target, " llOwnerSay(llMD5String(\"x\", 0));\n")); } // LSO writes the argument where it is used, as it would the answer. - UNSCOPED_INFO("a hash of a longer string is smaller as its answer on LSO"); - CHECK(folded(Target::LSO, " llOwnerSay(llMD5String(\"Hello, Avatar!\", 0));\n")); + { + INFO("a hash of a longer string is smaller as its answer on LSO"); + CHECK(folded(Target::LSO, " llOwnerSay(llMD5String(\"Hello, Avatar!\", 0));\n")); + } // Mono and Luau hold a string once: where a function holds the // answer already, it costs a load. const std::string held = " llOwnerSay(llMD5String(\"x\", 0));\n llOwnerSay(" + hashOfX + ");\n"; - UNSCOPED_INFO("held already on Mono"); - CHECK(folded(Target::Mono, held)); - UNSCOPED_INFO("held already on Luau"); - CHECK(folded(Target::Luau, held)); - UNSCOPED_INFO("LSO writes it again"); - CHECK_FALSE(folded(Target::LSO, held)); + { + INFO("held already on Mono"); + CHECK(folded(Target::Mono, held)); + } + { + INFO("held already on Luau"); + CHECK(folded(Target::Luau, held)); + } + { + INFO("LSO writes it again"); + CHECK_FALSE(folded(Target::LSO, held)); + } } TEST_CASE_METHOD(allsloptimizer_data, "allsloptimizer: same-named labels in a handler keep one short name when names shrink", "[allsloptimizer]") { @@ -3105,7 +3063,7 @@ namespace " g = c & 7;\n" " g = a >> 2;\n" " llOwnerSay((string)[a, b, c, f, e, g, h]);\n"), { "g", "h" }); - const std::string mono = optimized(source, ALLSLOptimizer::Target::Mono); + const std::string mono = require_optimized(source, ALLSLOptimizer::Target::Mono); UNSCOPED_INFO("Mono: a - b as a + -b: " + mono); CHECK(in(mono, "g = a + -b;")); UNSCOPED_INFO("Mono: -= as += -"); @@ -3131,7 +3089,7 @@ namespace CHECK(in(mono, "g = c & 7;")); } - const std::string lso = optimized(source, ALLSLOptimizer::Target::LSO); + const std::string lso = require_optimized(source, ALLSLOptimizer::Target::LSO); { INFO("LSO: a - b stays: " + lso); CHECK(in(lso, "g = a - b;")); @@ -3150,7 +3108,7 @@ namespace CHECK(in(lso, "if (a < b && b > 2)")); } - const std::string luau = optimized(source, ALLSLOptimizer::Target::Luau); + const std::string luau = require_optimized(source, ALLSLOptimizer::Target::Luau); UNSCOPED_INFO("Luau: a shift as a product: " + luau); CHECK(in(luau, "g = a * 8;")); UNSCOPED_INFO("Luau: a doubling as a sum"); @@ -3199,7 +3157,7 @@ namespace " touch_start(integer n)\n {\n llOwnerSay((string)g + t);\n }\n}\n"; for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { - const std::string text = optimized(source, target); + const std::string text = require_optimized(source, target); const bool lso = target == ALLSLOptimizer::Target::LSO; const bool mono = target == ALLSLOptimizer::Target::Mono; UNSCOPED_INFO("a truth returned: " + text); @@ -3265,7 +3223,7 @@ namespace " }\n}\n"; for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { - const std::string text = optimized(source, target); + const std::string text = require_optimized(source, target); const bool luau = target == ALLSLOptimizer::Target::Luau; UNSCOPED_INFO("a for known to run as a do, its step in the check: " + text); REQUIRE((luau ? in(text, "for (i = 0; i < 10; ++i)") : in(text, "while(++i < 10);"))); @@ -3310,7 +3268,7 @@ namespace "}\n"; for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { - const std::string text = optimized(source, target); + const std::string text = require_optimized(source, target); UNSCOPED_INFO("a local set to the owner: " + text); REQUIRE(in(text, "if (owner)")); UNSCOPED_INFO("a detected key, its ways turned round"); @@ -3347,7 +3305,7 @@ namespace " touch_start(integer t)\n {\n llOwnerSay((string)got);\n }\n}\n"; for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { - const std::string text = optimized(source, target); + const std::string text = require_optimized(source, target); { INFO("a parameter never read gone, and its arguments: " + text); REQUIRE(in(text, "say(string message)")); @@ -3398,7 +3356,7 @@ namespace " kept = t;\n }\n}\n"; for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { - const std::string text = optimized(source, target); + const std::string text = require_optimized(source, target); // A read of the clock changes nothing, and goes with it. UNSCOPED_INFO("a value set again before it is read not set: " + text); REQUIRE(in(text, "integer i = (integer)llFrand(9);")); @@ -3440,7 +3398,7 @@ namespace " llSetPos(llGetPos() + <1.5, 2.5, 3.5>);\n llSetPos(llGetPos() + <1.5, 2.5, 3.5>);\n" " llSetPos(llGetPos() + <1.5, 2.5, 3.5>);\n llSetPos(llGetPos() + <1.5, 2.5, 3.5>);\n" " }\n}\n"; - const std::string lso = optimized(source, ALLSLOptimizer::Target::LSO); + const std::string lso = require_optimized(source, ALLSLOptimizer::Target::LSO); { INFO("LSO: a long string at three places kept in a global: " + lso); CHECK(in(lso, "string s = \"a sentence long enough to be worth holding\";")); @@ -3451,7 +3409,7 @@ namespace UNSCOPED_INFO("LSO: a global's reads stay, as large as a local's there"); CHECK(in(lso, "llSay(channel, \"one\");")); - const std::string luau = optimized(source, ALLSLOptimizer::Target::Luau); + const std::string luau = require_optimized(source, ALLSLOptimizer::Target::Luau); { INFO("Luau: a global read three times read once: " + luau); CHECK(in(luau, "integer localChannel = channel;")); @@ -3464,7 +3422,7 @@ namespace UNSCOPED_INFO("Luau: constants held once in a function already"); CHECK_FALSE(in(luau, "string s = ")); - const std::string mono = optimized(source, ALLSLOptimizer::Target::Mono); + const std::string mono = require_optimized(source, ALLSLOptimizer::Target::Mono); UNSCOPED_INFO("Mono: a vector at four places kept in a local, its frame paid for: " + mono); CHECK(in(mono, "vector v = <1.5, 2.5, 3.5>;")); UNSCOPED_INFO("Mono: three reads of a global not worth a local's frame"); @@ -3482,7 +3440,7 @@ namespace " @out;\n llOwnerSay(\"done\");\n }\n}\n"; for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { - const std::string text = optimized(source, target); + const std::string text = require_optimized(source, target); const bool lso = target == ALLSLOptimizer::Target::LSO; const bool luau = target == ALLSLOptimizer::Target::Luau; UNSCOPED_INFO("two literals joined on LSO alone, by default: " + text); @@ -3517,7 +3475,7 @@ namespace " }\n}\n"; for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::Luau }) { - const std::string text = optimized(source, target); + const std::string text = require_optimized(source, target); size_t minus_ones = 0; for (size_t at = text.find("llOwnerSay(\"-1\");"); at != std::string::npos; at = text.find("llOwnerSay(\"-1\");", at + 1)) { @@ -3557,7 +3515,7 @@ namespace " }\n}\n"; for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Mono }) { - const std::string text = optimized(source, target); + const std::string text = require_optimized(source, target); { INFO("each folded as the VM's single: " + text); REQUIRE(in(text, "llOwnerSay(\"1024.000000\");")); @@ -3571,7 +3529,7 @@ namespace UNSCOPED_INFO("-0 made 0, not -0: " + text); REQUIRE_FALSE(in(text, "-0.000000")); } - const std::string luau = optimized(source, ALLSLOptimizer::Target::Luau); + const std::string luau = require_optimized(source, ALLSLOptimizer::Target::Luau); { INFO("a single, not the double: " + luau); CHECK(in(luau, "(string)0.8414709568023682")); diff --git a/indra/alscript/tests/allslservice_test.cpp b/indra/alscript/tests/allslservice_test.cpp index 1fc17cb456..10291907b8 100644 --- a/indra/alscript/tests/allslservice_test.cpp +++ b/indra/alscript/tests/allslservice_test.cpp @@ -85,7 +85,6 @@ namespace { UNSCOPED_INFO("builtins load: " + error); CHECK(loaded); - UNSCOPED_INFO("service says so"); CHECK(service.hasBuiltins()); } @@ -121,17 +120,13 @@ namespace const ALScriptProblem* problem = coded(problems, "10011"); UNSCOPED_INFO("the wrong-type error: " + said(problems)); REQUIRE(problem != nullptr); - UNSCOPED_INFO("an error"); CHECK(problem->severity == ALScriptProblem::Severity::Error); - UNSCOPED_INFO("from the checks"); CHECK(problem->source == ALScriptProblem::Source::Types); UNSCOPED_INFO("on the call's line, zero-based"); CHECK(problem->line == 4); // Taken apart by its code, for another language to say: the // words in the order the marks have them. - UNSCOPED_INFO("the key"); CHECK(problem->key == std::string("LSLArgumentWrongType")); - UNSCOPED_INFO("five words"); REQUIRE(problem->args.size() == size_t(5)); UNSCOPED_INFO("the type passed"); CHECK(problem->args[0] == std::string("string")); @@ -159,13 +154,10 @@ namespace const ALScriptProblem* problem = coded(problems, "20009"); UNSCOPED_INFO("the unused warning: " + said(problems)); REQUIRE(problem != nullptr); - UNSCOPED_INFO("a warning"); CHECK(problem->severity == ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("from the lint"); CHECK(problem->source == ALScriptProblem::Source::Lint); UNSCOPED_INFO("on the declaration's line"); CHECK(problem->line == 0); - UNSCOPED_INFO("no errors"); CHECK(errors(problems) == 0); } @@ -181,10 +173,8 @@ namespace " }\n" "}\n"); UNSCOPED_INFO("an error: " + said(problems)); - CHECK(errors(problems) > 0); - UNSCOPED_INFO("from the parser"); + REQUIRE(errors(problems) > 0); CHECK(problems.front().source == ALScriptProblem::Source::Parser); - UNSCOPED_INFO("on a line past the first"); CHECK(problems.front().line > 0); } @@ -192,11 +182,8 @@ namespace { ALLSLService bare; std::string why; - UNSCOPED_INFO("refused"); REQUIRE_FALSE(bare.loadBuiltins("/nonexistent/builtins.txt", why)); - UNSCOPED_INFO("with a reason"); REQUIRE_FALSE(why.empty()); - UNSCOPED_INFO("nothing loaded"); CHECK_FALSE(bare.hasBuiltins()); } @@ -229,15 +216,10 @@ namespace } return false; }; - UNSCOPED_INFO("the global"); CHECK(has("count", ALScriptSymbolKind::Variable, "integer count")); - UNSCOPED_INFO("the function"); CHECK(has("half", ALScriptSymbolKind::Function, "float half(integer n)")); - UNSCOPED_INFO("the state"); CHECK(has("other", ALScriptSymbolKind::State, "state other")); - UNSCOPED_INFO("the event's parameter"); CHECK(has("total", ALScriptSymbolKind::Parameter, "integer total")); - UNSCOPED_INFO("the local before"); CHECK(has("before", ALScriptSymbolKind::Variable, "string before")); bool after = false, builtin = false; for (const ALScriptCompletion& c : found) @@ -245,9 +227,7 @@ namespace after = after || c.text == "after"; builtin = builtin || c.text == "llSay"; } - UNSCOPED_INFO("not the local after"); CHECK_FALSE(after); - UNSCOPED_INFO("not the builtins"); CHECK_FALSE(builtin); } @@ -265,17 +245,11 @@ namespace " }\n" "}\n"; ALScriptHover hover = service.hover(script, 5, 26); // on `half` - UNSCOPED_INFO("found"); REQUIRE(hover.found); - UNSCOPED_INFO("as declared"); CHECK(hover.label == std::string("float half(integer n)")); - { - INFO("declared on the first line"); - CHECK(hover.hasDefinition); - CHECK(hover.definitionLine == 0); - } + CHECK(hover.hasDefinition); + CHECK(hover.definitionLine == 0); hover = service.hover(script, 5, 10); // on `llSay` - UNSCOPED_INFO("a builtin is found"); REQUIRE(hover.found); UNSCOPED_INFO("as declared: " + hover.label); REQUIRE(hover.label.find("llSay(") != std::string::npos); @@ -283,21 +257,14 @@ namespace CHECK_FALSE(hover.hasDefinition); ALScriptSignature sig = service.signature(script, 5, 15); // in llSay's first argument - UNSCOPED_INFO("in the call"); REQUIRE(sig.found); UNSCOPED_INFO("the builtin's signature: " + sig.label); REQUIRE((sig.label.find("llSay(") == 0 || sig.label.find(" llSay(") != std::string::npos)); - UNSCOPED_INFO("two parameters"); REQUIRE(sig.parameters.size() == size_t(2)); - UNSCOPED_INFO("the first"); CHECK(sig.active == 0); sig = service.signature(script, 5, 30); // just inside half( - { - INFO("the inner call"); - REQUIRE(sig.found); - CHECK(sig.label == "float half(integer n)"); - } - UNSCOPED_INFO("its only parameter"); + REQUIRE(sig.found); + CHECK(sig.label == "float half(integer n)"); CHECK(sig.active == 0); UNSCOPED_INFO("none at the top"); REQUIRE_FALSE(service.signature(script, 0, 0).found); @@ -323,38 +290,25 @@ namespace " state_entry() { state default; }\n" "}\n"; ALScriptReferences refs = service.references(script, 5, 16); // on the second `count` - { - INFO("found"); - REQUIRE(refs.found); - CHECK(refs.name == "count"); - } - UNSCOPED_INFO("a variable"); + REQUIRE(refs.found); + CHECK(refs.name == "count"); CHECK(refs.kind == ALScriptSymbolKind::Variable); - { - INFO(llformat("declared on the first line, not %d:%d-%d (%d)", refs.definition.line, refs.definition.column, refs.definition.endColumn, (int)refs.hasDefinition)); - CHECK(refs.hasDefinition); - CHECK(refs.definition.line == 0); - CHECK(refs.definition.column == 8); - CHECK(refs.definition.endColumn == 13); - } - UNSCOPED_INFO("four places"); + CHECK(refs.hasDefinition); + CHECK(refs.definition.line == 0); + CHECK(refs.definition.column == 8); + CHECK(refs.definition.endColumn == 13); REQUIRE(refs.references.size() == size_t(4)); - UNSCOPED_INFO("the declaration first"); CHECK(refs.references[0] == refs.definition); { INFO("the cast's operand last"); CHECK(refs.references[3].line == 6); CHECK(refs.references[3].column == 25); } - UNSCOPED_INFO("renamable"); CHECK(refs.renamable); refs = service.references(script, 6, 10); // on llSay - { - INFO("a builtin is found"); - REQUIRE(refs.found); - CHECK(refs.name == "llSay"); - } + REQUIRE(refs.found); + CHECK(refs.name == "llSay"); { INFO("used once, declared nowhere here"); REQUIRE(refs.references.size() == 1); @@ -363,40 +317,26 @@ namespace } refs = service.references(script, 7, 15); // on `other` in `state other;` - { - INFO("a state is found"); - REQUIRE(refs.found); - CHECK(refs.kind == ALScriptSymbolKind::State); - } - { - INFO("declared where it is"); - CHECK(refs.hasDefinition); - CHECK(refs.definition.line == 10); - CHECK(refs.definition.column == 6); - } + REQUIRE(refs.found); + CHECK(refs.kind == ALScriptSymbolKind::State); + CHECK(refs.hasDefinition); + CHECK(refs.definition.line == 10); + CHECK(refs.definition.column == 6); UNSCOPED_INFO("the declaration and the change"); REQUIRE(refs.references.size() == size_t(2)); - UNSCOPED_INFO("renamable"); CHECK(refs.renamable); refs = service.references(script, 12, 26); // on `default` in `state default;` - { - INFO("default is found"); - REQUIRE(refs.found); - CHECK(refs.name == "default"); - } + REQUIRE(refs.found); + CHECK(refs.name == "default"); UNSCOPED_INFO("but is not the script's to rename"); CHECK_FALSE(refs.renamable); refs = service.references(script, 3, 6); // on state_entry - { - INFO("an event is found"); - REQUIRE(refs.found); - CHECK(refs.kind == ALScriptSymbolKind::Event); - } - UNSCOPED_INFO(llformat("in both states, not %d places", (int)refs.references.size())); + REQUIRE(refs.found); + CHECK(refs.kind == ALScriptSymbolKind::Event); + UNSCOPED_INFO("in both states"); REQUIRE(refs.references.size() == 2); - UNSCOPED_INFO("not renamable"); CHECK_FALSE(refs.renamable); } @@ -420,54 +360,30 @@ namespace for (const ALScriptOutlineEntry& e : outline) names += " " + e.name + llformat("@%d", e.depth); UNSCOPED_INFO("six entries:" + names); REQUIRE(outline.size() == size_t(6)); - { - INFO("the global"); - CHECK(outline[0].name == "count"); - CHECK(outline[0].kind == ALScriptSymbolKind::Variable); - CHECK(outline[0].detail == "integer count"); - CHECK(outline[0].depth == 0); - } - { - INFO("the function, as declared"); - CHECK(outline[1].name == "half"); - CHECK(outline[1].kind == ALScriptSymbolKind::Function); - CHECK(outline[1].detail == "float half(integer n)"); - } - { - INFO("its name where it is"); - CHECK(outline[1].nameSpan.line == 1); - CHECK(outline[1].nameSpan.column == 6); - CHECK(outline[1].nameSpan.endColumn == 10); - } - { - INFO("default, a state"); - CHECK(outline[2].name == "default"); - CHECK(outline[2].kind == ALScriptSymbolKind::State); - CHECK(outline[2].depth == 0); - } - { - INFO("spanning its lines"); - CHECK(outline[2].span.line == 2); - CHECK(outline[2].span.endLine == 8); - } - { - INFO("state_entry, one deeper"); - CHECK(outline[3].name == "state_entry"); - CHECK(outline[3].kind == ALScriptSymbolKind::Event); - CHECK(outline[3].depth == 1); - } - { - INFO("touch_start with its parameter"); - CHECK(outline[4].name == "touch_start"); - CHECK(outline[4].detail == "touch_start(integer total)"); - CHECK(outline[4].span.line == 5); - CHECK(outline[4].span.endLine == 7); - } - { - INFO("the other state"); - CHECK(outline[5].name == "other"); - CHECK(outline[5].kind == ALScriptSymbolKind::State); - } + CHECK(outline[0].name == "count"); + CHECK(outline[0].kind == ALScriptSymbolKind::Variable); + CHECK(outline[0].detail == "integer count"); + CHECK(outline[0].depth == 0); + CHECK(outline[1].name == "half"); + CHECK(outline[1].kind == ALScriptSymbolKind::Function); + CHECK(outline[1].detail == "float half(integer n)"); + CHECK(outline[1].nameSpan.line == 1); + CHECK(outline[1].nameSpan.column == 6); + CHECK(outline[1].nameSpan.endColumn == 10); + CHECK(outline[2].name == "default"); + CHECK(outline[2].kind == ALScriptSymbolKind::State); + CHECK(outline[2].depth == 0); + CHECK(outline[2].span.line == 2); + CHECK(outline[2].span.endLine == 8); + CHECK(outline[3].name == "state_entry"); + CHECK(outline[3].kind == ALScriptSymbolKind::Event); + CHECK(outline[3].depth == 1); + CHECK(outline[4].name == "touch_start"); + CHECK(outline[4].detail == "touch_start(integer total)"); + CHECK(outline[4].span.line == 5); + CHECK(outline[4].span.endLine == 7); + CHECK(outline[5].name == "other"); + CHECK(outline[5].kind == ALScriptSymbolKind::State); } TEST_CASE_METHOD(allslservice_data, "allslservice: semantic tokens tell every name by what it is", "[allslservice]") @@ -645,16 +561,10 @@ namespace // A call not yet closed, the caret after an argument half typed. std::string script = head + " llSay(0, be" + tail; ALScriptSignature sig = service.signature(script, 7, 19); - { - INFO("the call is found"); - REQUIRE(sig.found); - REQUIRE(sig.label.find("llSay(") != std::string::npos); - } - UNSCOPED_INFO("at its second parameter"); + REQUIRE(sig.found); + REQUIRE(sig.label.find("llSay(") != std::string::npos); CHECK(sig.active == 1); - UNSCOPED_INFO("the text does not parse"); CHECK_FALSE(service.parsed()); - UNSCOPED_INFO("but was understood"); CHECK(service.understood()); const std::string in_scope = names(script, 7, 17); UNSCOPED_INFO("the local before, in scope:" + in_scope); @@ -666,14 +576,10 @@ namespace // The bracket just typed, and a comma just typed. sig = service.signature(head + " llSay(" + tail, 7, 14); - UNSCOPED_INFO("just opened"); REQUIRE(sig.found); - UNSCOPED_INFO("at the first parameter"); CHECK(sig.active == 0); sig = service.signature(head + " llSay(0," + tail, 7, 16); - UNSCOPED_INFO("after the comma"); REQUIRE(sig.found); - UNSCOPED_INFO("at the second parameter"); CHECK(sig.active == 1); sig = service.signature(head + " llSay(0, \"hel" + tail, 7, 20); { @@ -682,22 +588,16 @@ namespace CHECK(sig.active == 1); } sig = service.signature(head + " half(" + tail, 7, 13); - { - INFO("the script's own function"); - REQUIRE(sig.found); - CHECK(sig.label == "float half(integer n)"); - } + REQUIRE(sig.found); + CHECK(sig.label == "float half(integer n)"); // A name alone, and a statement without its semicolon. - UNSCOPED_INFO("a word alone"); REQUIRE(names(head + " bef" + tail, 7, 8).find(" before ") != std::string::npos); - UNSCOPED_INFO("no semicolon"); REQUIRE(names(head + " llSay(0, before)" + tail, 7, 17).find(" before ") != std::string::npos); // What the check's other questions answer of a text that does not // parse: the rest of it, rather than nothing. script = head + " llSay(0, be" + tail; - UNSCOPED_INFO("the outline"); REQUIRE(service.outline(script).size() == 4); const std::vector tokens = service.semanticTokens(script); bool before_named = false; @@ -707,7 +607,6 @@ namespace } UNSCOPED_INFO("the names before the break"); CHECK(before_named); - UNSCOPED_INFO("the hints"); REQUIRE_FALSE(service.inlayHints(head + " llSay(0, \"x\");\n llOwnerSay(" + tail, true).empty()); // A function being declared at the top, before the states. @@ -718,7 +617,6 @@ namespace REQUIRE(hover.found); CHECK(hover.label == "integer count"); } - UNSCOPED_INFO("its references"); REQUIRE(service.references(script, 6, 26).references.size() == 2); // A block left open at the end. @@ -751,14 +649,10 @@ namespace }; // A call not closed, the block closed on the next line. ALScriptProblem p = first_error(head + " llSay(0, \"hi\"\n }\n}\n"); - UNSCOPED_INFO("missing the bracket: " + p.message); CHECK(p.message == std::string("Missing ')'.")); - { - INFO("keyed"); - CHECK(p.key == "LSLSyntaxMissing"); - REQUIRE(p.args.size() == 1); - CHECK(p.args[0] == "')'"); - } + CHECK(p.key == "LSLSyntaxMissing"); + REQUIRE(p.args.size() == 1); + CHECK(p.args[0] == "')'"); { INFO("on the string's closing quote, not the next line's brace"); CHECK(p.line == 4); @@ -767,7 +661,6 @@ namespace } // A statement not ended. p = first_error(head + " llSay(0, \"hi\")\n llOwnerSay(\"x\");\n }\n}\n"); - UNSCOPED_INFO("missing the semicolon: " + p.message); CHECK(p.message == std::string("Missing ';'.")); { INFO("after the call"); @@ -775,7 +668,7 @@ namespace CHECK(p.column == 21); } p = first_error(head + " integer x = 5 // five\n }\n}\n"); - UNSCOPED_INFO("past a comment: " + p.message); + UNSCOPED_INFO("past a comment"); CHECK(p.message == std::string("Missing ';'.")); { INFO("on the number"); @@ -800,7 +693,6 @@ namespace CHECK(loaded); const std::string script = "integer unused;\nlist g = [1, 2];\ninteger h = g;\ndefault\n{\n state_entry()\n {\n integer never;\n }\n}\n"; const ALScriptProblems first = service.check(script, true); - UNSCOPED_INFO("something to say: " + said(first)); REQUIRE_FALSE(first.empty()); service.outline(script); service.hover(script, 0, 9); @@ -831,15 +723,11 @@ namespace const std::string tail = "default\n{\n state_entry() { }\n}\n"; // As Nexii's linkset library has it. ALScriptProblem p = first_error("list ObjectLinksetSittingAvatars(object) {\n return [];\n}\n" + tail); - UNSCOPED_INFO("the first: " + p.message); CHECK(p.message == std::string("The parameter 'object' needs its type before it: integer, float, string, key, vector, rotation or list.")); - { - INFO("keyed"); - CHECK(p.key == "LSLParameterUntyped"); - REQUIRE(p.args.size() == 1); - CHECK(p.args[0] == "object"); - } + CHECK(p.key == "LSLParameterUntyped"); + REQUIRE(p.args.size() == 1); + CHECK(p.args[0] == "object"); { INFO("on the name, whole"); CHECK(p.line == 0); @@ -850,14 +738,14 @@ namespace p = first_error("f(integer a, b) { }\n" + tail); { INFO("after a comma: " + p.message); - CHECK(p.key == "LSLParameterUntyped"); + REQUIRE(p.key == "LSLParameterUntyped"); CHECK(p.args[0] == "b"); CHECK(p.column == 13); } p = first_error("default\n{\n touch_start(total)\n {\n }\n}\n"); { INFO("an event's: " + p.message); - CHECK(p.key == "LSLParameterUntyped"); + REQUIRE(p.key == "LSLParameterUntyped"); CHECK(p.args[0] == "total"); CHECK(p.line == 2); } @@ -1054,21 +942,17 @@ namespace CHECK(loaded); llifstream in(std::string(AL_SCRIPT_TEMPLATES_DIR) + "/lsl.xml", std::ios::in | std::ios::binary); LLSD templates; - { - INFO("read"); - CHECK(in.is_open()); - CHECK(LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE); - REQUIRE(templates.isArray()); - } - UNSCOPED_INFO("some"); + CHECK(in.is_open()); + CHECK(LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE); + REQUIRE(templates.isArray()); REQUIRE(templates.size() > 0); for (LLSD::array_const_iterator it = templates.beginArray(); it != templates.endArray(); ++it) { const std::string name = (*it)["name"].asString(); const ALScriptProblems problems = service.check((*it)["body"].asString()); - UNSCOPED_INFO(name + " parses"); + CAPTURE(name); REQUIRE(service.parsed()); - UNSCOPED_INFO(name + ": " + said(problems)); + INFO(said(problems)); REQUIRE(errors(problems) == size_t(0)); } } @@ -1090,14 +974,10 @@ namespace const std::string open = "default\n{\n state_entry()\n {\n llSay(0, \"Hello, Avatar!\");\n\n"; // A handler with parameters, read as a call. ALScriptProblem p = first_error(open + " touch_start(integer total_number)\n {\n llSay(0, \"Touched.\");\n }\n}\n"); - UNSCOPED_INFO("the brace: " + p.message); CHECK(p.message == std::string("Missing '}'.")); - { - INFO("keyed"); - CHECK(p.key == "LSLSyntaxMissing"); - REQUIRE(p.args.size() == 1); - CHECK(p.args[0] == "'}'"); - } + CHECK(p.key == "LSLSyntaxMissing"); + REQUIRE(p.args.size() == 1); + CHECK(p.args[0] == "'}'"); { INFO("on the semicolon before it"); CHECK(p.line == 4); @@ -1106,7 +986,7 @@ namespace } // One without, its brace on the next line. p = first_error(open + " touch_start()\n {\n }\n}\n"); - UNSCOPED_INFO("the brace, a head without parameters: " + p.message); + UNSCOPED_INFO("a head without parameters"); CHECK(p.message == std::string("Missing '}'.")); { INFO("on the same semicolon"); @@ -1115,7 +995,7 @@ namespace } // A state after one not closed. p = first_error("default\n{\n state_entry()\n {\n }\n\nstate other\n{\n state_entry()\n {\n }\n}\n"); - UNSCOPED_INFO("the state's brace: " + p.message); + UNSCOPED_INFO("a state after one not closed"); CHECK(p.message == std::string("Missing '}'.")); { INFO("after the handler's"); @@ -1198,18 +1078,15 @@ namespace "}\n"; const std::vector whole = service.outline(script); const std::vector shape = service.outline(script, false); - UNSCOPED_INFO("as many"); REQUIRE(shape.size() == whole.size()); for (size_t i = 0; i < shape.size(); ++i) { - { - INFO("the same " + whole[i].name); - REQUIRE(shape[i].name == whole[i].name); - REQUIRE(shape[i].kind == whole[i].kind); - REQUIRE(shape[i].depth == whole[i].depth); - REQUIRE(shape[i].span == whole[i].span); - REQUIRE(shape[i].nameSpan == whole[i].nameSpan); - } + CAPTURE(whole[i].name); + REQUIRE(shape[i].name == whole[i].name); + REQUIRE(shape[i].kind == whole[i].kind); + REQUIRE(shape[i].depth == whole[i].depth); + REQUIRE(shape[i].span == whole[i].span); + REQUIRE(shape[i].nameSpan == whole[i].nameSpan); UNSCOPED_INFO("no words of its declaration: " + shape[i].detail); REQUIRE(shape[i].detail.empty()); } diff --git a/indra/alscript/tests/allsltoslua_test.cpp b/indra/alscript/tests/allsltoslua_test.cpp index 4feae12592..c85486f357 100644 --- a/indra/alscript/tests/allsltoslua_test.cpp +++ b/indra/alscript/tests/allsltoslua_test.cpp @@ -80,7 +80,7 @@ namespace } // Written again, and it must have been. - ALLSLToSLua::Result convert(const std::string& lsl) + ALLSLToSLua::Result require_converted(const std::string& lsl) { ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl); std::string why; @@ -88,7 +88,7 @@ namespace { why += p.message + "\n"; } - UNSCOPED_INFO("converted: " + why); + CAPTURE(why); REQUIRE(r.converted); return r; } @@ -111,9 +111,8 @@ namespace } // What SLua's check says of it: nothing unwanted. - void checksClean(const ALLSLToSLua::Result& r) + void require_checks_clean(const ALLSLToSLua::Result& r) { - UNSCOPED_INFO("the definitions"); REQUIRE(definitions); std::string said; for (const ALScriptProblem& p : service.check(r.text)) @@ -123,7 +122,7 @@ namespace said += llformat("[%d:%d] %s\n", p.line + 1, p.column + 1, p.message.c_str()); } } - UNSCOPED_INFO("checks as SLua:\n" + said + "---\n" + r.text); + CAPTURE(said, r.text); REQUIRE(said.empty()); } @@ -159,20 +158,20 @@ namespace } return {}; } - static std::string commentAt(const std::string& slua, const std::string& begins) + static std::string require_comment_at(const std::string& slua, const std::string& begins) { const size_t at = slua.find(begins); - UNSCOPED_INFO("written: " + begins + "\n" + slua); + CAPTURE(begins, slua); REQUIRE(at != std::string::npos); return commentAt(slua, at); } // A toggle turned the other way in SLua: a dash taken out of its // opener's ---, or put in a block comment's --; the comment that // begins there then. - static std::string toggled(const std::string& slua, const std::string& opener, bool off = true) + static std::string require_toggled(const std::string& slua, const std::string& opener, bool off = true) { const size_t at = slua.find(opener); - UNSCOPED_INFO("written: " + opener + "\n" + slua); + CAPTURE(opener, slua); REQUIRE(at != std::string::npos); std::string other = slua; if (off) @@ -225,252 +224,239 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a one-state script as locals and LLEvents handlers; state_entry last", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer gCount = 2;\n" - "string greet(string who) { return \"Hello, \" + who; }\n" - "default {\n" - " state_entry() { llSay(0, greet(\"world\")); }\n" - " touch_start(integer total_number) {\n" - " gCount += total_number;\n" - " llSay(0, greet(llDetectedName(0)) + \" \" + (string)gCount);\n" - " }\n" - "}\n"); - UNSCOPED_INFO("the global: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer gCount = 2;\n" + "string greet(string who) { return \"Hello, \" + who; }\n" + "default {\n" + " state_entry() { llSay(0, greet(\"world\")); }\n" + " touch_start(integer total_number) {\n" + " gCount += total_number;\n" + " llSay(0, greet(llDetectedName(0)) + \" \" + (string)gCount);\n" + " }\n" + "}\n"); + CAPTURE(r.text); + UNSCOPED_INFO("the global"); CHECK(has(r, "local gCount = 2")); { - INFO("the function: " + r.text); + INFO("the function"); CHECK(has(r, "local function greet(who)")); CHECK(has(r, "return \"Hello, \" .. who")); } { - INFO("the handler: " + r.text); + INFO("the handler"); CHECK(has(r, "LLEvents:on(\"touch_start\", function(detected)")); CHECK(has(r, "local total_number = #detected")); } - UNSCOPED_INFO("the step: " + r.text); + UNSCOPED_INFO("the step"); CHECK(has(r, "gCount += total_number")); { - INFO("what was detected from the table: " + r.text); + INFO("what was detected from the table"); CHECK(has(r, "detected[1]:getName()")); CHECK(noted(r, "SluaDetectedTable")); } - UNSCOPED_INFO("an integer joined to text, interpolated: " + r.text); + UNSCOPED_INFO("an integer joined to text, interpolated"); CHECK(has(r, "ll.Say(0, `{greet(detected[1]:getName())} {gCount}`)")); { - INFO("state_entry last: " + r.text); + INFO("state_entry last"); CHECK(r.text.find("-- state_entry") > r.text.find("LLEvents:on")); CHECK(has(r, "ll.Say(0, greet(\"world\"))")); } - UNSCOPED_INFO("the note over its line: " + r.text); + UNSCOPED_INFO("the note over its line"); CHECK(has(r, "-- LSL: detected[n] is what LSL read")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: expressions: truth tests; 1 or 0 comparisons; / and % toward nought; lists", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " integer a = 7; integer b = 2; float f = 1.5; string s = \"x\"; list l = [1, 2];\n" - " integer same = a == b;\n" - " if (a) llOwnerSay(\"a\");\n" - " if (s) llOwnerSay(\"said\");\n" - " if (a > 1 && llGetListLength(l) > 1) llOwnerSay(\"both\");\n" - " integer q = a / b; integer m = a % b;\n" - " l += 3; l = l + [4] + \"five\";\n" - " integer t = TRUE; if (FALSE) t = 0;\n" - " llOwnerSay((string)f + (string)((integer)\"12abc\"));\n" - " if (l == [1]) llOwnerSay(\"one long\");\n" - " integer flags = a & b | 4;\n" - " vector v = <1, 2, 3>; v.x = 4; v.z += 1;\n" - " llOwnerSay((string)v + (string)same + (string)q + (string)m + (string)t + (string)flags);\n" - "} }\n"); - UNSCOPED_INFO("a comparison as a number: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " integer a = 7; integer b = 2; float f = 1.5; string s = \"x\"; list l = [1, 2];\n" + " integer same = a == b;\n" + " if (a) llOwnerSay(\"a\");\n" + " if (s) llOwnerSay(\"said\");\n" + " if (a > 1 && llGetListLength(l) > 1) llOwnerSay(\"both\");\n" + " integer q = a / b; integer m = a % b;\n" + " l += 3; l = l + [4] + \"five\";\n" + " integer t = TRUE; if (FALSE) t = 0;\n" + " llOwnerSay((string)f + (string)((integer)\"12abc\"));\n" + " if (l == [1]) llOwnerSay(\"one long\");\n" + " integer flags = a & b | 4;\n" + " vector v = <1, 2, 3>; v.x = 4; v.z += 1;\n" + " llOwnerSay((string)v + (string)same + (string)q + (string)m + (string)t + (string)flags);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a comparison as a number"); CHECK(has(r, "local same = if a == b then 1 else 0")); - UNSCOPED_INFO("an integer's truth: " + r.text); + UNSCOPED_INFO("an integer's truth"); CHECK(has(r, "if a ~= 0 then")); - UNSCOPED_INFO("a string's: " + r.text); + UNSCOPED_INFO("a string's"); CHECK(has(r, "if s ~= \"\" then")); - UNSCOPED_INFO("and: " + r.text); CHECK(has(r, "if a > 1 and #l > 1 then")); - UNSCOPED_INFO("integer division: " + r.text); + UNSCOPED_INFO("integer division"); CHECK(has(r, "local q = bit32.s32(a / b)")); - UNSCOPED_INFO("remainder: " + r.text); + UNSCOPED_INFO("remainder"); CHECK(has(r, "local m = math.fmod(a, b)")); { - INFO("a list no other holds grown in place: " + r.text); + INFO("a list no other holds grown in place"); CHECK(has(r, "table.insert(l, 3)")); CHECK(has(r, "table.append(l, 4, \"five\")")); CHECK_FALSE(has(r, "table.extend")); } { - INFO("TRUE and FALSE: " + r.text); + INFO("TRUE and FALSE"); CHECK(has(r, "local t = 1")); CHECK(has(r, "if false then")); } - UNSCOPED_INFO("a float as LSL writes it: " + r.text); + UNSCOPED_INFO("a float as LSL writes it"); CHECK(has(r, "string.format(\"%.6f\", f)")); { - INFO("a string as an integer, as a list's item converts it: " + r.text); + INFO("a string as an integer, as a list's item converts it"); CHECK(has(r, "lslInteger(\"12abc\")")); CHECK(has(r, "return llcompat.List2Integer({ s }, 0)")); } { - INFO("lists compared by length: " + r.text); + INFO("lists compared by length"); CHECK(has(r, "#l == #{ 1 }")); CHECK(noted(r, "SluaListCompare")); } // bit32 answers 0 to 4294967295: made LSL's signed integer, but where // bit32 takes it again. + CHECK(has(r, "bit32.s32(bit32.bor(bit32.band(a, b), 4))")); + CHECK_FALSE(has(r, "local function int32")); { - INFO("bits: " + r.text); - CHECK(has(r, "bit32.s32(bit32.bor(bit32.band(a, b), 4))")); - CHECK_FALSE(has(r, "local function int32")); - } - { - INFO("a vector's part set: " + r.text); + INFO("a vector's part set"); CHECK(has(r, "v = vector(4, v.y, v.z)")); CHECK(has(r, "v = vector(v.x, v.y, v.z + 1)")); } - UNSCOPED_INFO("a vector as LSL writes it: " + r.text); + UNSCOPED_INFO("a vector as LSL writes it"); CHECK(has(r, "ll.DumpList2String({ v }, \"\")")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: loops: numeric for; break and continue jumps; repeat; other jumps noted", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " integer i; integer n;\n" - " for (i = 0; i < 10; ++i) {\n" - " if (i == 3) jump next;\n" - " if (i == 8) jump done;\n" - " n += i;\n" - " @next;\n" - " }\n" - " @done;\n" - " do { n -= 1; } while (n > 5);\n" - " while (n) { n--; }\n" - " jump away;\n" - " n = 1;\n" - " @away;\n" - " llOwnerSay((string)n);\n" - "} }\n"); - UNSCOPED_INFO("a numeric for: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " integer i; integer n;\n" + " for (i = 0; i < 10; ++i) {\n" + " if (i == 3) jump next;\n" + " if (i == 8) jump done;\n" + " n += i;\n" + " @next;\n" + " }\n" + " @done;\n" + " do { n -= 1; } while (n > 5);\n" + " while (n) { n--; }\n" + " jump away;\n" + " n = 1;\n" + " @away;\n" + " llOwnerSay((string)n);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a numeric for"); CHECK(has(r, "for i = 0, 9 do")); - UNSCOPED_INFO("its counter declared by it alone: " + r.text); + UNSCOPED_INFO("its counter declared by it alone"); CHECK_FALSE(has(r, "local i = 0")); - UNSCOPED_INFO("continue, the step Luau's own: " + r.text); + UNSCOPED_INFO("continue, the step Luau's own"); CHECK(has(r, "then continue end")); - UNSCOPED_INFO("break: " + r.text); CHECK(has(r, "break")); - { - INFO("repeat: " + r.text); - CHECK(has(r, "repeat")); - CHECK(has(r, "until not (n > 5)")); - } - UNSCOPED_INFO("a step: " + r.text); + CHECK(has(r, "repeat")); + CHECK(has(r, "until not (n > 5)")); + UNSCOPED_INFO("a step"); CHECK(has(r, "n -= 1")); { - INFO("another jump noted and kept as a mark: " + r.text); + INFO("another jump noted and kept as a mark"); CHECK(noted(r, "SluaJump")); CHECK(has(r, "-- jump away")); CHECK(has(r, "-- @away")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: states as handler tables entered by setState; the timer on LLTimers", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default {\n" - " state_entry() { llSetTimerEvent(1.0); }\n" - " timer() { state running; }\n" - " state_exit() { llOwnerSay(\"leaving\"); }\n" - "}\n" - "state running {\n" - " state_entry() { llOwnerSay(\"running\"); }\n" - " touch_start(integer n) { if (n > 1) { state default; } llOwnerSay(\"touched\"); }\n" - "}\n"); - { - INFO("the tables: " + r.text); + const ALLSLToSLua::Result r = require_converted("default {\n" + " state_entry() { llSetTimerEvent(1.0); }\n" + " timer() { state running; }\n" + " state_exit() { llOwnerSay(\"leaving\"); }\n" + "}\n" + "state running {\n" + " state_entry() { llOwnerSay(\"running\"); }\n" + " touch_start(integer n) { if (n > 1) { state default; } llOwnerSay(\"touched\"); }\n" + "}\n"); + CAPTURE(r.text); + { + INFO("the tables"); CHECK(has(r, "states.default = {")); CHECK(has(r, "states.running = {")); } + CHECK(has(r, "local function setState(name: string)")); + CHECK(has(r, "setState(\"default\")")); { - INFO("setState: " + r.text); - CHECK(has(r, "local function setState(name: string)")); - CHECK(has(r, "setState(\"default\")")); - } - { - INFO("a change ends the event, where anything follows: " + r.text); + INFO("a change ends the event, where anything follows"); CHECK(has(r, "setState(\"running\")\n end,")); CHECK(has(r, "setState(\"default\")\n return\n end")); } { - INFO("the timer on LLTimers: " + r.text); + INFO("the timer on LLTimers"); CHECK(has(r, "setTimer(1")); CHECK(has(r, "LLTimers:every(seconds")); CHECK(noted(r, "SluaTimers")); } { - INFO("calling the state's handler: " + r.text); + INFO("calling the state's handler"); CHECK(has(r, "states[currentState].timer")); CHECK(has(r, "event ~= \"timer\"")); } - UNSCOPED_INFO("state_exit a handler of its state: " + r.text); + UNSCOPED_INFO("state_exit a handler of its state"); CHECK(has(r, "state_exit = function()")); - UNSCOPED_INFO("what is let go of noted: " + r.text); + UNSCOPED_INFO("what is let go of noted"); CHECK(noted(r, "SluaStates")); // Tapple's: a name with no state, which only an edit could write, // said so at the call, before the state it is in is left. const size_t looked = r.text.find(" local entering = states[name]\n if not entering then\n error(\"unknown state: \" .. name, 2)\n end\n"); { - INFO("a state not there said: " + r.text); + INFO("a state not there said"); REQUIRE(looked != std::string::npos); CHECK(looked < r.text.find("local leaving")); } - checksClean(r); + require_checks_clean(r); ALLSLToSLua::Options typed; typed.types = true; - checksClean(ALLSLToSLua::convert("default { touch_start(integer n) { state two; } }\nstate two { state_entry() { state default; } }\n", typed)); + require_checks_clean(ALLSLToSLua::convert("default { touch_start(integer n) { state two; } }\nstate two { state_entry() { state default; } }\n", typed)); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: names: reserved ones renamed; forward declarations; inline assignments", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer table = 1;\n" - "integer first(integer end) { return second(end) + table; }\n" - "integer second(integer x) { return x * 2; }\n" - "default { state_entry() {\n" - " integer i; integer j = (i = 4) + i++;\n" - " llOwnerSay((string)first(j));\n" - "} }\n"); - { - INFO("renamed: " + r.text); - CHECK(has(r, "local table_ = 1")); - CHECK(has(r, "(end_)")); - } - { - INFO("only the one called before it is written declared first: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer table = 1;\n" + "integer first(integer end) { return second(end) + table; }\n" + "integer second(integer x) { return x * 2; }\n" + "default { state_entry() {\n" + " integer i; integer j = (i = 4) + i++;\n" + " llOwnerSay((string)first(j));\n" + "} }\n"); + CAPTURE(r.text); + CHECK(has(r, "local table_ = 1")); + CHECK(has(r, "(end_)")); + { + INFO("only the one called before it is written declared first"); CHECK(has(r, "local second\n")); CHECK(has(r, "local function first(end_)")); CHECK(has(r, "\nfunction second(x)")); } { - INFO("an assignment in place: " + r.text); + INFO("an assignment in place"); CHECK(has(r, "(function() i = 4 return i end)()")); CHECK(noted(r, "SluaAssignInExpression")); } - UNSCOPED_INFO("a step after, what it was: " + r.text); + UNSCOPED_INFO("a step after, what it was"); CHECK(has(r, "(function() local was = i; i += 1 return was end)()")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: LSL that does not parse writes nothing and says why", "[ALLSLToSLua]") { const ALLSLToSLua::Result r = ALLSLToSLua::convert("default { state_entry() { integer = ; } }"); - { - INFO("not converted"); - CHECK_FALSE(r.converted); - REQUIRE(r.text.empty()); - } - UNSCOPED_INFO("why"); - REQUIRE_FALSE(r.problems.empty()); + CHECK_FALSE(r.converted); + CHECK(r.text.empty()); + CHECK_FALSE(r.problems.empty()); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: close to LSL: llcompat's Detected and timer event noted; ll.OwnerSay kept", "[ALLSLToSLua]") @@ -482,77 +468,78 @@ namespace " touch_start(integer n) { llOwnerSay(llDetectedName(0)); }\n" "}\n", close); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); { - INFO("llcompat's Detected: " + r.text); + INFO("llcompat's Detected"); CHECK(has(r, "llcompat.DetectedName(0)")); CHECK(noted(r, "SluaDetected")); } { - INFO("the timer event: " + r.text); + INFO("the timer event"); CHECK(has(r, "llcompat.SetTimerEvent(2")); CHECK(has(r, "LLEvents:on(\"timer\"")); CHECK(noted(r, "SluaTimer")); } { - INFO("ll.OwnerSay, SLua deprecating it for print: " + r.text); + INFO("ll.OwnerSay, SLua deprecating it for print"); CHECK(has(r, "ll.OwnerSay(\"tick\")")); CHECK_FALSE(has(r, "llcompat.OwnerSay")); CHECK(noted(r, "SluaDeprecatedFor", "OwnerSay")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: SLua's ll where it means the same and SLua's own forms for some calls", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " key k = llDetectedKey(0); string s = \"abcdef\"; list l = [\"a\", \"b\"]; integer i = 2;\n" - " if (llSameGroup(k)) llOwnerSay(\"group\");\n" - " integer same = llSameGroup(k);\n" - " string head = llGetSubString(s, 0, 2);\n" - " string tail = llGetSubString(s, i, -1);\n" - " if (llListFindList(l, [\"a\"]) != -1) llOwnerSay(\"found\");\n" - " if (~llSubStringIndex(s, \"c\")) llOwnerSay(\"has c\");\n" - " if (llSubStringIndex(s, \"z\") < 0) llOwnerSay(\"no z\");\n" - " integer at = llListFindList(l, [\"a\", \"b\"]);\n" - " float p = llPow(2.0, 3.0) + llFabs(-1.0) + llVecDist(<1,2,3>, ZERO_VECTOR);\n" - " integer r = llRound(2.5) + llGetUnixTime();\n" - " llOwnerSay(head + tail + (string)same + (string)at + (string)p + (string)r);\n" - "} }\n"); - UNSCOPED_INFO("a boolean in a condition: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { touch_start(integer n) {\n" + " key k = llDetectedKey(0); string s = \"abcdef\"; list l = [\"a\", \"b\"]; integer i = 2;\n" + " if (llSameGroup(k)) llOwnerSay(\"group\");\n" + " integer same = llSameGroup(k);\n" + " string head = llGetSubString(s, 0, 2);\n" + " string tail = llGetSubString(s, i, -1);\n" + " if (llListFindList(l, [\"a\"]) != -1) llOwnerSay(\"found\");\n" + " if (~llSubStringIndex(s, \"c\")) llOwnerSay(\"has c\");\n" + " if (llSubStringIndex(s, \"z\") < 0) llOwnerSay(\"no z\");\n" + " integer at = llListFindList(l, [\"a\", \"b\"]);\n" + " float p = llPow(2.0, 3.0) + llFabs(-1.0) + llVecDist(<1,2,3>, ZERO_VECTOR);\n" + " integer r = llRound(2.5) + llGetUnixTime();\n" + " llOwnerSay(head + tail + (string)same + (string)at + (string)p + (string)r);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a boolean in a condition"); CHECK(has(r, "if ll.SameGroup(k) then")); - UNSCOPED_INFO("and as a number: " + r.text); + UNSCOPED_INFO("and as a number"); CHECK(has(r, "local same = if ll.SameGroup(k) then 1 else 0")); - UNSCOPED_INFO("a constant index moved on: " + r.text); + UNSCOPED_INFO("a constant index moved on"); CHECK(has(r, "ll.GetSubString(s, 1, 3)")); { - INFO("one that is not, through llcompat: " + r.text); + INFO("one that is not, through llcompat"); CHECK(has(r, "llcompat.GetSubString(s, i, -1)")); CHECK(noted(r, "SluaIndex", "GetSubString")); } - UNSCOPED_INFO("one thing found in a list: " + r.text); + UNSCOPED_INFO("one thing found in a list"); CHECK(has(r, "if table.find(l, \"a\") ~= nil then")); - UNSCOPED_INFO("~ of a find: " + r.text); + UNSCOPED_INFO("~ of a find"); CHECK(has(r, "if string.find(s, \"c\", 1, true) ~= nil then")); - UNSCOPED_INFO("< 0 of one: " + r.text); + UNSCOPED_INFO("< 0 of one"); CHECK(has(r, "if string.find(s, \"z\", 1, true) == nil then")); - UNSCOPED_INFO("a find's index as a number, through llcompat: " + r.text); + UNSCOPED_INFO("a find's index as a number, through llcompat"); CHECK(has(r, "llcompat.ListFindList(l, { \"a\", \"b\" })")); { - INFO("^, math and vector: " + r.text); + INFO("^, math and vector"); CHECK(has(r, "2.0 ^ 3.0")); CHECK(has(r, "math.abs(-1.0)")); CHECK(has(r, "vector.magnitude(vector(1, 2, 3) - ZERO_VECTOR)")); } - UNSCOPED_INFO("half up, as LSL rounds, not math.round: " + r.text); + UNSCOPED_INFO("half up, as LSL rounds, not math.round"); CHECK(has(r, "math.floor(2.5 + 0.5)")); { - INFO("os.time and print: " + r.text); + INFO("os.time and print"); CHECK(has(r, "os.time()")); CHECK(has(r, "print(")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: handlers assigned to LLEvents' fields and Luau types on what LSL typed", "[ALLSLToSLua]") @@ -564,23 +551,20 @@ namespace "string greet(string who) { return \"Hi \" + who; }\n" "default { touch_start(integer n) { string s = greet(\"x\"); gCount += n; llOwnerSay(s); } }\n", options); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); { - INFO("a field, as SLua's own scripts write one: " + r.text); + INFO("a field, as SLua's own scripts write one"); CHECK(has(r, "function LLEvents.touch_start(detected: { DetectedEvent })")); CHECK_FALSE(has(r, "LLEvents:on")); CHECK_FALSE(has(r, "= function(")); } - UNSCOPED_INFO("the count typed: " + r.text); + UNSCOPED_INFO("the count typed"); CHECK(has(r, "local n: number = #detected")); - { - INFO("typed: " + r.text); - CHECK(has(r, "local gCount: number = 2")); - CHECK(has(r, "local function greet(who: string): string")); - CHECK(has(r, "local s: string = greet(\"x\")")); - } - checksClean(r); + CHECK(has(r, "local gCount: number = 2")); + CHECK(has(r, "local function greet(who: string): string")); + CHECK(has(r, "local s: string = greet(\"x\")")); + require_checks_clean(r); // Each parameter as the definitions type SLua's handler: a key a // uuid, link_message's id the string SLua passes; none on a key the @@ -592,15 +576,16 @@ namespace " money(key giver, integer amount) { giver = \"someone\"; llOwnerSay((string)giver); }\n" "}\n"; const ALLSLToSLua::Result typed = ALLSLToSLua::convert(handlers, options); - UNSCOPED_INFO("an integer: " + typed.text); + CAPTURE(typed.text); + UNSCOPED_INFO("an integer"); CHECK(has(typed, "function LLEvents.on_rez(param: number)")); - UNSCOPED_INFO("a key a uuid: " + typed.text); + UNSCOPED_INFO("a key a uuid"); CHECK(has(typed, "function LLEvents.listen(c: number, n: string, id: uuid, m: string)")); - UNSCOPED_INFO("link_message's id a string: " + typed.text); + UNSCOPED_INFO("link_message's id a string"); CHECK(has(typed, "function LLEvents.link_message(s: number, num: number, str: string, id: string)")); - UNSCOPED_INFO("a key the body takes as text, under a name of its own: " + typed.text); + UNSCOPED_INFO("a key the body takes as text, under a name of its own"); CHECK(has(typed, "function LLEvents.money(giverKey: uuid, amount: number)\n local giver: string = tostring(giverKey)\n giver = \"someone\"\n")); - checksClean(typed); + require_checks_clean(typed); // Strict as well, under the grid's solver: each handler as the // definitions type it, the text key made text. if (!newSolver) @@ -610,8 +595,8 @@ namespace { said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); } - UNSCOPED_INFO("strict:\n" + said + "---\n" + typed.text); - REQUIRE(said.empty()); + CAPTURE(said); + CHECK(said.empty()); } ALLSLToSLua::Options on = options; on.handlers = ALLSLToSLua::Options::Handlers::On; @@ -624,17 +609,18 @@ namespace const ALLSLToSLua::Result many = ALLSLToSLua::convert("default { touch_start(integer n) { state other; } }\n" "state other { touch_start(integer n) { state default; } }\n", options); + CAPTURE(many.text); // SLua stops the script at a field assigned nil: taken off by // LLEvents:off, as LLEvents:on's are. { - INFO("setState sets the fields: " + many.text); + INFO("setState sets the fields"); CHECK(has(many, "(LLEvents :: any)[event] = handler")); CHECK(has(many, "LLEvents:off(event :: any, handler)")); CHECK_FALSE(has(many, "[event] = nil")); } - UNSCOPED_INFO("a state's handlers typed in its table: " + many.text); + UNSCOPED_INFO("a state's handlers typed in its table"); CHECK(has(many, "touch_start = function(detected: { DetectedEvent })")); - checksClean(many); + require_checks_clean(many); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: no comments where they are not wanted: the notes are still said", "[ALLSLToSLua]") @@ -642,9 +628,9 @@ namespace ALLSLToSLua::Options options; options.comments = false; const ALLSLToSLua::Result r = ALLSLToSLua::convert("default { state_entry() { list l = [7]; integer a = l == [2]; llOwnerSay((string)a); } }\n", options); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("no comment: " + r.text); + UNSCOPED_INFO("no comment"); CHECK_FALSE(has(r, "-- LSL:")); UNSCOPED_INFO("but noted"); CHECK(noted(r, "SluaListCompare")); @@ -652,440 +638,452 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: numeric for where it counts as LSL's did; while where it might not", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " list l = [1, 2, 3]; integer n = 4; integer total;\n" - " integer a; for (a = 0; a < llGetListLength(l); a++) total += llList2Integer(l, a);\n" - " integer b; for (b = 10; b >= 0; b -= 2) total += b;\n" - " integer c; for (c = 1; c <= n; ++c) total += c;\n" - " integer d; for (d = 0; d < n; ++d) { if (total > 100) d = n; }\n" - " integer e; for (e = 0; e < n; ++e) { n--; }\n" - " integer f; for (f = 0; f < 3; ++f) total += f;\n" - " llOwnerSay((string)(total + f));\n" - "} }\n"); - UNSCOPED_INFO("up to a length, walking its items: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " list l = [1, 2, 3]; integer n = 4; integer total;\n" + " integer a; for (a = 0; a < llGetListLength(l); a++) total += llList2Integer(l, a);\n" + " integer b; for (b = 10; b >= 0; b -= 2) total += b;\n" + " integer c; for (c = 1; c <= n; ++c) total += c;\n" + " integer d; for (d = 0; d < n; ++d) { if (total > 100) d = n; }\n" + " integer e; for (e = 0; e < n; ++e) { n--; }\n" + " integer f; for (f = 0; f < 3; ++f) total += f;\n" + " llOwnerSay((string)(total + f));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("up to a length, walking its items"); CHECK(has(r, "for _, item in l do\n total += item")); - UNSCOPED_INFO("down by a step: " + r.text); + UNSCOPED_INFO("down by a step"); CHECK(has(r, "for b = 10, 0, -2 do")); - UNSCOPED_INFO("up to and with: " + r.text); + UNSCOPED_INFO("up to and with"); CHECK(has(r, "for c = 1, n do")); - UNSCOPED_INFO("a counter set in its body keeps the while: " + r.text); + UNSCOPED_INFO("a counter set in its body keeps the while"); CHECK(has(r, "while d < n do")); - UNSCOPED_INFO("a limit set in its body keeps the while: " + r.text); + UNSCOPED_INFO("a limit set in its body keeps the while"); CHECK(has(r, "while e < n do")); { - INFO("a counter read after keeps the while: " + r.text); + INFO("a counter read after keeps the while"); CHECK(has(r, "while f < 3 do")); CHECK(has(r, "local f = 0")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: integers only used as truths become booleans; number reads keep numbers", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer gOn = FALSE;\n" - "integer gSeen;\n" - "integer isOwner(key k) { return k == llGetOwner(); }\n" - "integer anyOf(list l) { return llGetListLength(l); }\n" - "default { touch_start(integer n) {\n" - " gOn = !gOn;\n" - " if (gOn) llOwnerSay(\"on\"); else llOwnerSay(\"off\");\n" - " if (gSeen == FALSE && isOwner(llDetectedKey(0))) gSeen = TRUE;\n" - " integer busy = anyOf([1]);\n" - " while (!busy) busy = TRUE;\n" - " integer count; count++;\n" - " integer shown = TRUE; llOwnerSay((string)shown);\n" - " integer loose = 5; if (loose == TRUE) llOwnerSay(\"one\");\n" - " integer y; integer z = (y = 5); if (y) llOwnerSay((string)z);\n" - " if (count) llOwnerSay((string)count);\n" - "} }\n"); - { - INFO("a global toggled: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer gOn = FALSE;\n" + "integer gSeen;\n" + "integer isOwner(key k) { return k == llGetOwner(); }\n" + "integer anyOf(list l) { return llGetListLength(l); }\n" + "default { touch_start(integer n) {\n" + " gOn = !gOn;\n" + " if (gOn) llOwnerSay(\"on\"); else llOwnerSay(\"off\");\n" + " if (gSeen == FALSE && isOwner(llDetectedKey(0))) gSeen = TRUE;\n" + " integer busy = anyOf([1]);\n" + " while (!busy) busy = TRUE;\n" + " integer count; count++;\n" + " integer shown = TRUE; llOwnerSay((string)shown);\n" + " integer loose = 5; if (loose == TRUE) llOwnerSay(\"one\");\n" + " integer y; integer z = (y = 5); if (y) llOwnerSay((string)z);\n" + " if (count) llOwnerSay((string)count);\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("a global toggled"); CHECK(has(r, "local gOn = false")); CHECK(has(r, "gOn = not gOn")); CHECK(has(r, "if gOn then")); } { - INFO("compared with FALSE: " + r.text); + INFO("compared with FALSE"); CHECK(has(r, "local gSeen = false")); CHECK(has(r, "if not gSeen and isOwner(")); CHECK(has(r, "gSeen = true")); } - UNSCOPED_INFO("a function that answers one: " + r.text); + UNSCOPED_INFO("a function that answers one"); CHECK(has(r, "return k == ll.GetOwner()")); { - INFO("a length, as a truth: " + r.text); + INFO("a length, as a truth"); CHECK(has(r, "return #l ~= 0")); CHECK(has(r, "local busy = anyOf({ 1 })")); CHECK(has(r, "while not busy do")); } { - INFO("stepped: " + r.text); + INFO("stepped"); CHECK(has(r, "local count = 0")); CHECK(has(r, "count += 1")); } - UNSCOPED_INFO("printed: " + r.text); + UNSCOPED_INFO("printed"); CHECK(has(r, "local shown = 1")); { - INFO("more than TRUE, compared with it: " + r.text); + INFO("more than TRUE, compared with it"); CHECK(has(r, "local loose = 5")); CHECK(has(r, "if loose == 1 then")); } - UNSCOPED_INFO("set where the assignment is read: " + r.text); + UNSCOPED_INFO("set where the assignment is read"); CHECK(has(r, "local y = 0")); - checksClean(r); + require_checks_clean(r); ALLSLToSLua::Options typed; typed.types = true; const ALLSLToSLua::Result t = ALLSLToSLua::convert("integer ready(integer n) { return n > 2; }\n" "default { state_entry() { integer ok = ready(3); if (ok) llOwnerSay(\"yes\"); } }\n", typed); + CAPTURE(t.text); { - INFO("typed boolean: " + t.text); + INFO("typed boolean"); CHECK(has(t, "local function ready(n: number): boolean")); CHECK(has(t, "local ok: boolean = ready(3)")); } - checksClean(t); + require_checks_clean(t); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: the right-first memory hack is an append; a visible order is noted", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("list gItems;\n" - "string gLog = \"a\";\n" - "default { touch_start(integer n) {\n" - " gItems = (gItems = []) + gItems + [\"x\"];\n" - " gItems = (gItems = []) + [\"y\"] + gItems;\n" - " gLog = (gLog = \"\") + gLog + \"b\";\n" - " llOwnerSay(gLog + (string)llGetListLength(gItems));\n" - "} }\n"); - UNSCOPED_INFO("appended: " + r.text); + const ALLSLToSLua::Result r = require_converted("list gItems;\n" + "string gLog = \"a\";\n" + "default { touch_start(integer n) {\n" + " gItems = (gItems = []) + gItems + [\"x\"];\n" + " gItems = (gItems = []) + [\"y\"] + gItems;\n" + " gLog = (gLog = \"\") + gLog + \"b\";\n" + " llOwnerSay(gLog + (string)llGetListLength(gItems));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("appended"); CHECK(has(r, "table.insert(gItems, \"x\")")); - UNSCOPED_INFO("prepended: " + r.text); + UNSCOPED_INFO("prepended"); CHECK(has(r, "table.insert(gItems, 1, \"y\")")); - UNSCOPED_INFO("a string: " + r.text); + UNSCOPED_INFO("a string"); CHECK(has(r, "gLog ..= \"b\"")); { - INFO("nothing cleared first: " + r.text); + INFO("nothing cleared first"); CHECK_FALSE(has(r, "gItems = {} return")); CHECK_FALSE(has(r, "gLog = \"\" return")); } { - INFO("noted once for each variable: " + r.text); + INFO("noted once for each variable"); CHECK(noted(r, "SluaMemoryHack")); CHECK(count(r, "-- LSL: (gItems = []) + gItems") == 1); CHECK(count(r, "-- LSL: (gLog = \"\") + gLog") == 1); } UNSCOPED_INFO("not taken as an order that shows"); CHECK_FALSE(orderNoted(r)); - checksClean(r); - - const ALLSLToSLua::Result order = convert("integer gCount;\n" - "integer bump() { return ++gCount; }\n" - "default { state_entry() {\n" - " integer i = 1;\n" - " integer j = i + (i = 5);\n" - " integer k = gCount - bump();\n" - " llOwnerSay((string)(j + k));\n" - "} }\n"); + require_checks_clean(r); + + const ALLSLToSLua::Result order = require_converted("integer gCount;\n" + "integer bump() { return ++gCount; }\n" + "default { state_entry() {\n" + " integer i = 1;\n" + " integer j = i + (i = 5);\n" + " integer k = gCount - bump();\n" + " llOwnerSay((string)(j + k));\n" + "} }\n"); + CAPTURE(order.text); UNSCOPED_INFO("an assignment the other side reads, named"); CHECK(noted(order, "SluaRightFirstSet", "i")); UNSCOPED_INFO("a call that sets what the other side reads, named"); CHECK(noted(order, "SluaRightFirstCall", "bump()")); - UNSCOPED_INFO("said over its line, with what: " + order.text); + UNSCOPED_INFO("said over its line, with what"); CHECK(has(order, "-- LSL: LSL ran the right side of this before the left, and Luau " - "runs the left first: bump() sets gCount, which the other side " - "reads or sets too.")); - UNSCOPED_INFO("said over its line: " + order.text); + "runs the left first: bump() sets gCount, which the other side " + "reads or sets too.")); + UNSCOPED_INFO("said over its line"); CHECK(has(order, "-- LSL: LSL ran the right side of this before the left")); - checksClean(order); - - const ALLSLToSLua::Result quiet = convert("integer gCount;\n" - "integer bump() { return ++gCount; }\n" - "default { state_entry() {\n" - " integer i = 1;\n" - " integer j = (i + 1) * bump();\n" - " string s = \"n: \" + (string)bump();\n" - " llOwnerSay(s + (string)(j + (integer)llGetTime()));\n" - "} }\n"); - UNSCOPED_INFO("nothing either side changes the other reads: " + quiet.text); + require_checks_clean(order); + + const ALLSLToSLua::Result quiet = require_converted("integer gCount;\n" + "integer bump() { return ++gCount; }\n" + "default { state_entry() {\n" + " integer i = 1;\n" + " integer j = (i + 1) * bump();\n" + " string s = \"n: \" + (string)bump();\n" + " llOwnerSay(s + (string)(j + (integer)llGetTime()));\n" + "} }\n"); + CAPTURE(quiet.text); + UNSCOPED_INFO("nothing either side changes the other reads"); CHECK_FALSE(orderNoted(quiet)); - checksClean(quiet); + require_checks_clean(quiet); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: unshared lists grown in place; a list handed on copied and noted", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("list gAll;\n" - "list gSeen;\n" - "list gPassed;\n" - "list gStored;\n" - "list gGrown;\n" - "integer addOne() { gGrown += [1]; return 1; }\n" - "list gKept = [1];\n" - "list parts(string s) { list out = llParseString2List(s, [\",\"], []); out += [\"end\"]; return out; }\n" - "list kept() { return gKept; }\n" - "show(list l) { llOwnerSay(llDumpList2String(l, \",\")); }\n" - "showAll(list l) { show(l); }\n" - "keep(list l) { gStored = l; }\n" - "keepLater(list l) { keep(l); }\n" - "default { touch_start(integer n) {\n" - " gAll = parts(\"a,b\");\n" - " gAll += parts(\"c\");\n" - " gAll = [\"first\"] + gAll;\n" - " list more = [4, 5];\n" - " gAll += more;\n" - " gAll += [llGetTime(), llFrand(1.0)];\n" - " gSeen += [n];\n" - " showAll(gSeen);\n" - " gPassed += [n];\n" - " keepLater(gPassed);\n" - " gKept += [2];\n" - " list a = [1]; list b = a; a += [2];\n" - " show(llListInsertList(gGrown, [addOne()], 0));\n" - " show(kept() + b + a + gAll + gStored);\n" - "} }\n"); - { - INFO("a local returned, grown in place: " + r.text); + const ALLSLToSLua::Result r = require_converted("list gAll;\n" + "list gSeen;\n" + "list gPassed;\n" + "list gStored;\n" + "list gGrown;\n" + "integer addOne() { gGrown += [1]; return 1; }\n" + "list gKept = [1];\n" + "list parts(string s) { list out = llParseString2List(s, [\",\"], []); out += [\"end\"]; return out; }\n" + "list kept() { return gKept; }\n" + "show(list l) { llOwnerSay(llDumpList2String(l, \",\")); }\n" + "showAll(list l) { show(l); }\n" + "keep(list l) { gStored = l; }\n" + "keepLater(list l) { keep(l); }\n" + "default { touch_start(integer n) {\n" + " gAll = parts(\"a,b\");\n" + " gAll += parts(\"c\");\n" + " gAll = [\"first\"] + gAll;\n" + " list more = [4, 5];\n" + " gAll += more;\n" + " gAll += [llGetTime(), llFrand(1.0)];\n" + " gSeen += [n];\n" + " showAll(gSeen);\n" + " gPassed += [n];\n" + " keepLater(gPassed);\n" + " gKept += [2];\n" + " list a = [1]; list b = a; a += [2];\n" + " show(llListInsertList(gGrown, [addOne()], 0));\n" + " show(kept() + b + a + gAll + gStored);\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("a local returned, grown in place"); CHECK(has(r, "local out = ll.ParseString2List(s, { \",\" }, {})")); CHECK(has(r, "table.insert(out, \"end\")")); } { - INFO("a call's list, put on the end: " + r.text); + INFO("a call's list, put on the end"); CHECK(has(r, "gAll = parts(\"a,b\")")); CHECK(has(r, "table.extend(gAll, parts(\"c\"))")); } - UNSCOPED_INFO("prepended: " + r.text); + UNSCOPED_INFO("prepended"); CHECK(has(r, "table.insert(gAll, 1, \"first\")")); - UNSCOPED_INFO("a list by its name: " + r.text); + UNSCOPED_INFO("a list by its name"); CHECK(has(r, "table.extend(gAll, more)")); - UNSCOPED_INFO("several that do not run apart, each had before any is added: " + r.text); + UNSCOPED_INFO("several that do not run apart, each had before any is added"); CHECK(has(r, "table.append(gAll, ll.GetTime(), ll.Frand(1.0))")); - UNSCOPED_INFO("passed to a function that only reads it: " + r.text); + UNSCOPED_INFO("passed to a function that only reads it"); CHECK(has(r, "table.insert(gSeen, n)")); { - INFO("passed to one that keeps it: " + r.text); + INFO("passed to one that keeps it"); CHECK(has(r, "gPassed = table.extend(table.clone(gPassed), { n })")); CHECK(has(r, "-- LSL: LSL's lists were values, and gPassed is passed to a function of the script's that keeps it")); } { - INFO("returned: " + r.text); + INFO("returned"); CHECK(has(r, "gKept = table.extend(table.clone(gKept), { 2 })")); CHECK(has(r, "gKept is returned by a function")); } { - INFO("given to another: " + r.text); + INFO("given to another"); CHECK(has(r, "a = table.extend(table.clone(a), { 2 })")); CHECK(has(r, "a is given to another variable")); } { - INFO("read where a call in the statement grows it: " + r.text); + INFO("read where a call in the statement grows it"); CHECK(has(r, "gGrown = table.extend(table.clone(gGrown), { 1 })")); CHECK(has(r, "gGrown is read where a call of the script's in the same statement changes it")); } - UNSCOPED_INFO("said once for each: " + r.text); + UNSCOPED_INFO("said once for each"); CHECK(count(r, "and gPassed is") == 1); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: strings built in loops joined once from a table; noted where not", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("string gReport;\n" - "default { touch_start(integer n) {\n" - " list names = [\"a\", \"b\", \"c\"];\n" - " integer outParts = 1;\n" - " string out = \"Names: \";\n" - " integer i;\n" - " for (i = 0; i < llGetListLength(names); ++i) {\n" - " string row = \"\";\n" - " integer j;\n" - " for (j = 0; j < 3; ++j) row += (string)j;\n" - " out = out + llList2String(names, i) + row;\n" - " }\n" - " string seen;\n" - " integer k;\n" - " for (k = 0; k < 3; ++k) { seen += \"x\"; if (llStringLength(seen) > 2) llOwnerSay(seen); }\n" - " for (k = 0; k < 2; ++k) gReport += \"y\";\n" - " llOwnerSay(out + seen + gReport + (string)outParts);\n" - "} }\n"); - UNSCOPED_INFO("the outer loop's table, a name of its own: " + r.text); + const ALLSLToSLua::Result r = require_converted("string gReport;\n" + "default { touch_start(integer n) {\n" + " list names = [\"a\", \"b\", \"c\"];\n" + " integer outParts = 1;\n" + " string out = \"Names: \";\n" + " integer i;\n" + " for (i = 0; i < llGetListLength(names); ++i) {\n" + " string row = \"\";\n" + " integer j;\n" + " for (j = 0; j < 3; ++j) row += (string)j;\n" + " out = out + llList2String(names, i) + row;\n" + " }\n" + " string seen;\n" + " integer k;\n" + " for (k = 0; k < 3; ++k) { seen += \"x\"; if (llStringLength(seen) > 2) llOwnerSay(seen); }\n" + " for (k = 0; k < 2; ++k) gReport += \"y\";\n" + " llOwnerSay(out + seen + gReport + (string)outParts);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("the outer loop's table, a name of its own"); CHECK(has(r, "local outParts2 = {}\n for _, item in names do")); - UNSCOPED_INFO("the pieces put in: " + r.text); + UNSCOPED_INFO("the pieces put in"); CHECK(has(r, "table.insert(outParts2, item .. row)")); - UNSCOPED_INFO("joined after: " + r.text); + UNSCOPED_INFO("joined after"); CHECK(has(r, " end\n out ..= table.concat(outParts2)")); - UNSCOPED_INFO("the inner loop's, declared in the outer: " + r.text); + UNSCOPED_INFO("the inner loop's, declared in the outer"); CHECK(has(r, " local rowParts = {}\n for j = 0, 2 do\n table.insert(rowParts, tostring(j))")); { - INFO("declared empty just before it: declared where it is joined: " + r.text); + INFO("declared empty just before it: declared where it is joined"); CHECK(has(r, " end\n local row = table.concat(rowParts)")); CHECK_FALSE(has(r, "local row = \"\"")); } { - INFO("declared with words of its own: added to: " + r.text); + INFO("declared with words of its own: added to"); CHECK(has(r, "local out = \"Names: \"")); CHECK(has(r, "out ..= table.concat(outParts2)")); } { - INFO("read in its loop: noted: " + r.text); + INFO("read in its loop: noted"); CHECK(has(r, "seen ..= \"x\"")); CHECK(has(r, "-- LSL: seen is built with .. in a loop")); } { - INFO("a global: noted: " + r.text); + INFO("a global: noted"); CHECK(has(r, "gReport ..= \"y\"")); CHECK(has(r, "-- LSL: gReport is built with .. in a loop")); } - checksClean(r); + require_checks_clean(r); ALLSLToSLua::Options typed; typed.types = true; const ALLSLToSLua::Result t = ALLSLToSLua::convert("default { state_entry() { string s; integer i; for (i = 0; i < 3; ++i) s += \"z\"; llOwnerSay(s); } }\n", typed); - { - INFO("typed: " + t.text); - CHECK(has(t, "local sParts: { string } = {}")); - CHECK(has(t, "local s: string = table.concat(sParts)")); - } - checksClean(t); + CAPTURE(t.text); + CHECK(has(t, "local sParts: { string } = {}")); + CHECK(has(t, "local s: string = table.concat(sParts)")); + require_checks_clean(t); - const ALLSLToSLua::Result read = convert("default { state_entry() { string t = \"\"; llOwnerSay(t); integer i; for (i = 0; i < 3; ++i) t += \"q\"; llOwnerSay(t); } }\n"); + const ALLSLToSLua::Result read = require_converted("default { state_entry() { string t = \"\"; llOwnerSay(t); integer i; for (i = 0; i < 3; ++i) t += \"q\"; llOwnerSay(t); } }\n"); + CAPTURE(read.text); { - INFO("read between: declared where it was: " + read.text); + INFO("read between: declared where it was"); CHECK(has(read, "local t = \"\"")); CHECK(has(read, "t ..= table.concat(tParts)")); } - checksClean(read); + require_checks_clean(read); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: compound assignments: x op= y; integer /= and %= whole; vector %= cross", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " integer a = 7; float f = 3.0; vector v = <1, 0, 0>;\n" - " a /= 2; a %= 3; f /= 2.0; v %= <0, 1, 0>;\n" - " integer b = 1; integer c = 2; string s = \"x\";\n" - " b = b + c; b = b - (c - 1); b = b - c - 1; c = c * 2 + b; s = s + \"y\" + (string)b;\n" - " llOwnerSay((string)a + (string)f + (string)v + s + (string)(b + c));\n" - "} }\n"); - { - INFO("integer division: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " integer a = 7; float f = 3.0; vector v = <1, 0, 0>;\n" + " a /= 2; a %= 3; f /= 2.0; v %= <0, 1, 0>;\n" + " integer b = 1; integer c = 2; string s = \"x\";\n" + " b = b + c; b = b - (c - 1); b = b - c - 1; c = c * 2 + b; s = s + \"y\" + (string)b;\n" + " llOwnerSay((string)a + (string)f + (string)v + s + (string)(b + c));\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("integer division"); CHECK(has(r, "a = bit32.s32(a / 2)")); CHECK_FALSE(has(r, "//=")); } { - INFO("remainder: " + r.text); + INFO("remainder"); CHECK(has(r, "a = math.fmod(a, 3)")); CHECK_FALSE(has(r, "%=")); } - UNSCOPED_INFO("a float's: " + r.text); + UNSCOPED_INFO("a float's"); CHECK(has(r, "f /= 2.0")); - UNSCOPED_INFO("a vector's cross product: " + r.text); + UNSCOPED_INFO("a vector's cross product"); CHECK(has(r, "v = vector.cross(v, vector(0, 1, 0))")); { - INFO("x = x op y: " + r.text); + INFO("x = x op y"); CHECK(has(r, "b += c")); CHECK(has(r, "b -= c - 1")); } { - INFO("not where the left of the operator is more than x: " + r.text); + INFO("not where the left of the operator is more than x"); CHECK(has(r, "b = b - c - 1")); CHECK(has(r, "c = c * 2 + b")); } - UNSCOPED_INFO("a string's pieces, joined: " + r.text); + UNSCOPED_INFO("a string's pieces, joined"); CHECK(has(r, "s ..= `y{b}`")); - UNSCOPED_INFO("a float's and a vector's as LSL writes them, in one interpolated string: " + r.text); + UNSCOPED_INFO("a float's and a vector's as LSL writes them, in one interpolated string"); CHECK(has(r, "print(`{a}{string.format(\"%.6f\", f)}{ll.DumpList2String({ v }, \"\")}{s}{b + c}`)")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a minus before a negative number is bracketed and not a comment", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() { integer a = -5; llOwnerSay((string)(-(-2147483648)) + (string)(-a) + (string)(- -a)); } }\n"); + const ALLSLToSLua::Result r = require_converted("default { state_entry() { integer a = -5; llOwnerSay((string)(-(-2147483648)) + (string)(-a) + (string)(- -a)); } }\n"); + CAPTURE(r.text); { - INFO("bracketed: " + r.text); + INFO("bracketed"); CHECK(has(r, "-(-2147483648)")); CHECK_FALSE(has(r, "--2147483648")); } - UNSCOPED_INFO("a name as it was: " + r.text); + UNSCOPED_INFO("a name as it was"); CHECK(has(r, "}{-a}{")); - UNSCOPED_INFO("twice: " + r.text); + UNSCOPED_INFO("twice"); CHECK(has(r, "-(-a)")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a one-state script's state_exit never ran: left out and said", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() { llOwnerSay(\"in\"); } state_exit() { llOwnerSay(\"out\"); } touch_start(integer n) { } }\n"); + const ALLSLToSLua::Result r = require_converted("default { state_entry() { llOwnerSay(\"in\"); } state_exit() { llOwnerSay(\"out\"); } touch_start(integer n) { } }\n"); + CAPTURE(r.text); { - INFO("not an event: " + r.text); + INFO("not an event"); CHECK_FALSE(has(r, "\"state_exit\"")); CHECK_FALSE(has(r, "print(\"out\")")); } { - INFO("said where it was: " + r.text); + INFO("said where it was"); CHECK(noted(r, "SluaStateExit")); CHECK(has(r, "-- LSL: state_exit runs as a script leaves a state")); CHECK(has(r, "-- state_exit, left out")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a function SLua lacks left out and said; boolean ll lists kept on llcompat", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " llPointAt(ZERO_VECTOR);\n" - " list e = llGetExperienceList(NULL_KEY);\n" - " if (llList2Integer(llGetPrimitiveParams([PRIM_FULLBRIGHT, 0]), 0)) llOwnerSay((string)llGetListLength(e));\n" - "} }\n"); - { - INFO("left out: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " llPointAt(ZERO_VECTOR);\n" + " list e = llGetExperienceList(NULL_KEY);\n" + " if (llList2Integer(llGetPrimitiveParams([PRIM_FULLBRIGHT, 0]), 0)) llOwnerSay((string)llGetListLength(e));\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("left out"); CHECK_FALSE(has(r, "PointAt(")); CHECK(has(r, "-- llPointAt, left out")); CHECK(noted(r, "SluaAbsent", "llPointAt")); } { - INFO("its empty value where read: " + r.text); + INFO("its empty value where read"); CHECK(has(r, "local e = {}")); CHECK(noted(r, "SluaAbsent", "llGetExperienceList")); } { - INFO("llcompat, said: " + r.text); + INFO("llcompat, said"); CHECK(has(r, "llcompat.GetPrimitiveParams({ PRIM_FULLBRIGHT, 0 })")); CHECK(noted(r, "SluaBoolList", "GetPrimitiveParams")); } - UNSCOPED_INFO("not a truth: " + r.text); + UNSCOPED_INFO("not a truth"); CHECK_FALSE(has(r, "if llcompat.GetPrimitiveParams")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: NULL_KEY and such constants: a uuid as a key and a string as text", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("string gName = NULL_KEY;\n" - "key gKey = NULL_KEY;\n" - "string nothing() { return NULL_KEY; }\n" - "default { touch_start(integer n) {\n" - " key k = llDetectedKey(0);\n" - " if (k != NULL_KEY) llOwnerSay(\"someone\");\n" - " if (NULL_KEY != \"\") llOwnerSay(llGetSubString(NULL_KEY, 0, 7));\n" - " list l = [NULL_KEY, TEXTURE_BLANK];\n" - " if (NULL_KEY) llOwnerSay(gName + nothing() + (string)gKey + (string)llGetListLength(l));\n" - "} }\n"); - UNSCOPED_INFO("a key against a key: " + r.text); + const ALLSLToSLua::Result r = require_converted("string gName = NULL_KEY;\n" + "key gKey = NULL_KEY;\n" + "string nothing() { return NULL_KEY; }\n" + "default { touch_start(integer n) {\n" + " key k = llDetectedKey(0);\n" + " if (k != NULL_KEY) llOwnerSay(\"someone\");\n" + " if (NULL_KEY != \"\") llOwnerSay(llGetSubString(NULL_KEY, 0, 7));\n" + " list l = [NULL_KEY, TEXTURE_BLANK];\n" + " if (NULL_KEY) llOwnerSay(gName + nothing() + (string)gKey + (string)llGetListLength(l));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a key against a key"); CHECK(has(r, "if k ~= NULL_KEY then")); { - INFO("a string where LSL's string was: " + r.text); + INFO("a string where LSL's string was"); CHECK(has(r, "tostring(NULL_KEY) ~= \"\"")); CHECK(has(r, "ll.GetSubString(tostring(NULL_KEY), 1, 8)")); } - UNSCOPED_INFO("in a list, as LSL had it: " + r.text); + UNSCOPED_INFO("in a list, as LSL had it"); CHECK(has(r, "{ tostring(NULL_KEY), tostring(TEXTURE_BLANK) }")); - UNSCOPED_INFO("a string's truth: " + r.text); + UNSCOPED_INFO("a string's truth"); CHECK(has(r, "if tostring(NULL_KEY) ~= \"\" then print(")); { - INFO("given to a string and a key: " + r.text); + INFO("given to a string and a key"); CHECK(has(r, "local gName = tostring(NULL_KEY)")); CHECK(has(r, "local gKey = NULL_KEY")); } - UNSCOPED_INFO("returned as a string function's: " + r.text); + UNSCOPED_INFO("returned as a string function's"); CHECK(has(r, "return tostring(NULL_KEY)")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: Tailslide's test scripts convert and check clean or are refused", "[ALLSLToSLua]") @@ -1108,7 +1106,7 @@ namespace } } std::sort(files.begin(), files.end()); - UNSCOPED_INFO("the scripts: " + dir); + CAPTURE(dir); REQUIRE(files.size() > 100); std::string wrong; size_t converted = 0; @@ -1138,268 +1136,278 @@ namespace } } } - UNSCOPED_INFO("every one as it should be:\n" + wrong); - REQUIRE(wrong.empty()); + CAPTURE(wrong); + CHECK(wrong.empty()); UNSCOPED_INFO("those taken, converted"); - REQUIRE(converted + REFUSED.size() == files.size()); + CHECK(converted + REFUSED.size() == files.size()); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: deprecated ll: SLua's own way where it means the same or llcompat noted", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " string s = \"a:b\"; list l = [\"x\", 2];\n" - " integer at = llSubStringIndex(s, \":\");\n" - " integer n = llStringLength(s) + llRound(1.5);\n" - " llOwnerSay(llList2String(l, at) + (string)(at + n));\n" - "} }\n"); - UNSCOPED_INFO("an index read as LSL's: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " string s = \"a:b\"; list l = [\"x\", 2];\n" + " integer at = llSubStringIndex(s, \":\");\n" + " integer n = llStringLength(s) + llRound(1.5);\n" + " llOwnerSay(llList2String(l, at) + (string)(at + n));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("an index read as LSL's"); CHECK(has(r, "local at = (ll.SubStringIndex(s, \":\") or 0) - 1")); - UNSCOPED_INFO("utf8.len and half up: " + r.text); + UNSCOPED_INFO("utf8.len and half up"); CHECK(has(r, "local n = (utf8.len(s) :: number) + math.floor(1.5 + 0.5)")); { - INFO("llcompat, with SLua's word: " + r.text); + INFO("llcompat, with SLua's word"); CHECK(has(r, "llcompat.List2String(l, at)")); CHECK(has(r, "-- LSL: SLua deprecates ll.List2String: Use '[]' and 'tostring' instead.")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: keys that hold text stay strings: link_message's id and non-UUID text", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("key DOMAIN = \"MY CHANNEL\";\n" - "key gOwner;\n" - "send(key to) { llMessageLinked(LINK_SET, 0, \"\", to); }\n" - "default {\n" - " state_entry() {\n" - " gOwner = llGetOwner();\n" - " llMessageLinked(LINK_SET, 1, \"hi\", DOMAIN);\n" - " send(\"other text\");\n" - " llMessageLinked(LINK_SET, 2, \"x\", gOwner);\n" - " }\n" - " link_message(integer s, integer n, string m, key id) {\n" - " if (id == DOMAIN) llOwnerSay(m);\n" - " if (id == gOwner) llOwnerSay(\"owner\");\n" - " key k = id;\n" - " llOwnerSay((string)llGetOwnerKey(\"abc\") + (string)k);\n" - " }\n" - "}\n"); - UNSCOPED_INFO("a key given text, a string: " + r.text); + const ALLSLToSLua::Result r = require_converted("key DOMAIN = \"MY CHANNEL\";\n" + "key gOwner;\n" + "send(key to) { llMessageLinked(LINK_SET, 0, \"\", to); }\n" + "default {\n" + " state_entry() {\n" + " gOwner = llGetOwner();\n" + " llMessageLinked(LINK_SET, 1, \"hi\", DOMAIN);\n" + " send(\"other text\");\n" + " llMessageLinked(LINK_SET, 2, \"x\", gOwner);\n" + " }\n" + " link_message(integer s, integer n, string m, key id) {\n" + " if (id == DOMAIN) llOwnerSay(m);\n" + " if (id == gOwner) llOwnerSay(\"owner\");\n" + " key k = id;\n" + " llOwnerSay((string)llGetOwnerKey(\"abc\") + (string)k);\n" + " }\n" + "}\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a key given text, a string"); CHECK(has(r, "local DOMAIN = \"MY CHANNEL\"")); { - INFO("text where SLua takes it: " + r.text); + INFO("text where SLua takes it"); CHECK(has(r, "ll.MessageLinked(LINK_SET, 1, \"hi\", DOMAIN)")); CHECK(has(r, "ll.MessageLinked(LINK_SET, 2, \"x\", gOwner)")); CHECK(has(r, "send(\"other text\")")); CHECK(has(r, "ll.MessageLinked(LINK_SET, 0, \"\", to)")); } { - INFO("compared as text: " + r.text); + INFO("compared as text"); CHECK(has(r, "if id == DOMAIN then")); CHECK(has(r, "if id == tostring(gOwner) then")); } { - INFO("a key given the id, text: " + r.text); + INFO("a key given the id, text"); CHECK(has(r, "local k = id")); CHECK(has(r, "{k}`)")); } { - INFO("ll's key given text as it is, said: " + r.text); + INFO("ll's key given text as it is, said"); CHECK(has(r, "ll.GetOwnerKey(\"abc\" :: any)")); CHECK(noted(r, "SluaKeyAsText")); CHECK_FALSE(noted(r, "SluaKeyText")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: notes only where they say something: &&; deprecations; bit32's range", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer sayIt() { llOwnerSay(\"x\"); return 1; }\n" - "default { changed(integer what) {\n" - " if (what & CHANGED_LINK && llGetLinkNumber() == 0) llDie();\n" - " integer m = what | 0x80000000;\n" - " list l = [\"a\"];\n" - " llOwnerSay(llList2String(l, what) + (string)m);\n" - " if ((what & 3) && sayIt()) llDie();\n" - "} }\n"); - { - INFO("bit32's range made LSL's where it shows: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer sayIt() { llOwnerSay(\"x\"); return 1; }\n" + "default { changed(integer what) {\n" + " if (what & CHANGED_LINK && llGetLinkNumber() == 0) llDie();\n" + " integer m = what | 0x80000000;\n" + " list l = [\"a\"];\n" + " llOwnerSay(llList2String(l, what) + (string)m);\n" + " if ((what & 3) && sayIt()) llDie();\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("bit32's range made LSL's where it shows"); CHECK(has(r, "local m = bit32.s32(bit32.bor(what, ")); CHECK(count(r, "-- LSL: bit32") == 1); } - UNSCOPED_INFO("not for a flag's truth: " + r.text); + UNSCOPED_INFO("not for a flag's truth"); CHECK(has(r, "if bit32.btest(what, CHANGED_LINK)")); { - INFO("&& said once, over a call that does something: " + r.text); + INFO("&& said once, over a call that does something"); CHECK(count(r, "leaves its right side unrun") == 1); CHECK(has(r, "LSL ran both sides, the right one first.\n if bit32.btest(what, 3) and sayIt()")); } { - INFO("the deprecated call's reason only: " + r.text); + INFO("the deprecated call's reason only"); CHECK(has(r, "-- LSL: SLua deprecates ll.List2String")); CHECK_FALSE(has(r, "takes indexes from 0")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: flatter expressions: joined text unbracketed; one bit32 call per chain", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " string b = \"b\"; integer n = 3; integer f = 5;\n" - " llOwnerSay(\"a\" + b + \"c\" + (string)n);\n" - " llSetTextureAnim(ANIM_ON | ROTATE | LOOP, ALL_SIDES, 0, 0, 0, TWO_PI, 1.0);\n" - " llOwnerSay((string)((f | 2) & n));\n" - "} }\n"); - UNSCOPED_INFO("a chain, one string: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " string b = \"b\"; integer n = 3; integer f = 5;\n" + " llOwnerSay(\"a\" + b + \"c\" + (string)n);\n" + " llSetTextureAnim(ANIM_ON | ROTATE | LOOP, ALL_SIDES, 0, 0, 0, TWO_PI, 1.0);\n" + " llOwnerSay((string)((f | 2) & n));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a chain, one string"); CHECK(has(r, "print(`a{b}c{n}`)")); - UNSCOPED_INFO("one call: " + r.text); + UNSCOPED_INFO("one call"); CHECK(has(r, "ll.SetTextureAnim(bit32.bor(ANIM_ON, ROTATE, LOOP), ALL_SIDES")); - UNSCOPED_INFO("not across operators: " + r.text); + UNSCOPED_INFO("not across operators"); CHECK(has(r, "bit32.band(bit32.bor(f, 2), n)")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: steps as statements before or after theirs unless something reads x", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer gLine;\n" - "integer bump() { return ++gLine; }\n" - "default { state_entry() {\n" - " integer n = 0;\n" - " llSay(n++, \"a\");\n" - " integer m = ++n;\n" - " integer k = n++ + n;\n" - " llOwnerSay((string)(gLine++));\n" - " llOwnerSay((string)bump() + (string)(gLine++) + (string)(m + k));\n" - "} }\n"); - UNSCOPED_INFO("after: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer gLine;\n" + "integer bump() { return ++gLine; }\n" + "default { state_entry() {\n" + " integer n = 0;\n" + " llSay(n++, \"a\");\n" + " integer m = ++n;\n" + " integer k = n++ + n;\n" + " llOwnerSay((string)(gLine++));\n" + " llOwnerSay((string)bump() + (string)(gLine++) + (string)(m + k));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("after"); CHECK(has(r, "ll.Say(n, \"a\")\nn += 1\n")); - UNSCOPED_INFO("before: " + r.text); + UNSCOPED_INFO("before"); CHECK(has(r, "\nn += 1\nlocal m = n\n")); - UNSCOPED_INFO("read again, kept: " + r.text); + UNSCOPED_INFO("read again, kept"); CHECK(has(r, "local k = (function() local was = n; n += 1 return was end)() + n")); - UNSCOPED_INFO("a global with nothing of the script's called: " + r.text); + UNSCOPED_INFO("a global with nothing of the script's called"); CHECK(has(r, "print(tostring(gLine))\ngLine += 1\n")); - UNSCOPED_INFO("a global beside a call of the script's, kept: " + r.text); + UNSCOPED_INFO("a global beside a call of the script's, kept"); CHECK(has(r, "print(`{bump()}{(function() local was = gLine;")); - UNSCOPED_INFO("a return's step before it: " + r.text); + UNSCOPED_INFO("a return's step before it"); CHECK(has(r, " gLine += 1\n return gLine\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: simpler conditions: negated comparisons flipped; if-set booleans direct", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer gOn;\n" - "default { touch_start(integer n) {\n" - " if (!(n == 2)) llOwnerSay(\"not two\");\n" - " if (!(n != 3)) llOwnerSay(\"three\");\n" - " if (n > 1) gOn = TRUE; else gOn = FALSE;\n" - " if (n > 5) { gOn = FALSE; } else { gOn = TRUE; }\n" - " if (gOn) llOwnerSay(\"on\");\n" - "} }\n"); - { - INFO("flipped: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer gOn;\n" + "default { touch_start(integer n) {\n" + " if (!(n == 2)) llOwnerSay(\"not two\");\n" + " if (!(n != 3)) llOwnerSay(\"three\");\n" + " if (n > 1) gOn = TRUE; else gOn = FALSE;\n" + " if (n > 5) { gOn = FALSE; } else { gOn = TRUE; }\n" + " if (gOn) llOwnerSay(\"on\");\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("flipped"); CHECK(has(r, "if n ~= 2 then")); CHECK(has(r, "if n == 3 then")); } - const ALLSLToSLua::Result nots = convert("integer gOn;\n" - "default { touch_start(integer n) {\n" - " if (!n) llOwnerSay(\"none\"); if (!(n & 4)) llOwnerSay(\"no four\"); if (!(n > 1)) llDie();\n" - " gOn = !gOn; if (gOn) llOwnerSay(\"on\");\n" - "} }\n"); + const ALLSLToSLua::Result nots = require_converted("integer gOn;\n" + "default { touch_start(integer n) {\n" + " if (!n) llOwnerSay(\"none\"); if (!(n & 4)) llOwnerSay(\"no four\"); if (!(n > 1)) llDie();\n" + " gOn = !gOn; if (gOn) llOwnerSay(\"on\");\n" + "} }\n"); + CAPTURE(nots.text); { - INFO("not a number: the other way round: " + nots.text); + INFO("not a number: the other way round"); CHECK(has(nots, "if n == 0 then")); CHECK(has(nots, "if not bit32.btest(n, 4) then")); CHECK(has(nots, "if not (n > 1) then")); CHECK(has(nots, "gOn = not gOn")); } - checksClean(nots); + require_checks_clean(nots); { - INFO("the check itself: " + r.text); + INFO("the check itself"); CHECK(has(r, " gOn = n > 1\n")); CHECK(has(r, " gOn = not (n > 5)\n")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a non-negative counter moved on by one as an index; loops count from 1", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " integer n = llGetInventoryNumber(INVENTORY_SOUND);\n" - " integer i; for (i = 0; i < n; ++i) llOwnerSay(llGetInventoryName(INVENTORY_SOUND, i));\n" - " integer j; for (j = 0; j < n; ++j) llOwnerSay((string)j + llGetInventoryName(INVENTORY_SOUND, j));\n" - " integer k; for (k = n - 1; k >= 0; --k) llOwnerSay(llGetInventoryName(INVENTORY_SOUND, k));\n" - " string s = \"abcdef\";\n" - " integer c; for (c = 0; c <= 2; ++c) llOwnerSay(llGetSubString(s, c, c + 1));\n" - "} }\n"); - UNSCOPED_INFO("counting from 1: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " integer n = llGetInventoryNumber(INVENTORY_SOUND);\n" + " integer i; for (i = 0; i < n; ++i) llOwnerSay(llGetInventoryName(INVENTORY_SOUND, i));\n" + " integer j; for (j = 0; j < n; ++j) llOwnerSay((string)j + llGetInventoryName(INVENTORY_SOUND, j));\n" + " integer k; for (k = n - 1; k >= 0; --k) llOwnerSay(llGetInventoryName(INVENTORY_SOUND, k));\n" + " string s = \"abcdef\";\n" + " integer c; for (c = 0; c <= 2; ++c) llOwnerSay(llGetSubString(s, c, c + 1));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("counting from 1"); CHECK(has(r, "for i = 1, n do\n print(ll.GetInventoryName(INVENTORY_SOUND, i))")); - UNSCOPED_INFO("read as a number too, moved on by one: " + r.text); + UNSCOPED_INFO("read as a number too, moved on by one"); CHECK(has(r, "for j = 0, n - 1 do\n print(`{j}{ll.GetInventoryName(INVENTORY_SOUND, j + 1)}`)")); - UNSCOPED_INFO("down to nought: " + r.text); + UNSCOPED_INFO("down to nought"); CHECK(has(r, "for k = n - 1, 0, -1 do\n print(ll.GetInventoryName(INVENTORY_SOUND, k + 1))")); - UNSCOPED_INFO("to and with, a number added: " + r.text); + UNSCOPED_INFO("to and with, a number added"); CHECK(has(r, "for c = 1, 3 do\n print(ll.GetSubString(s, c, c + 1))")); - UNSCOPED_INFO("no llcompat: " + r.text); + UNSCOPED_INFO("no llcompat"); CHECK_FALSE(has(r, "llcompat.")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: list items read by index directly or through tostring; else llcompat", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("list gNames = [\"a\", \"b\"];\n" - "list parts(string s) { return llParseString2List(s, [\",\"], []); }\n" - "default { state_entry() {\n" - " list p = parts(\"x,y\");\n" - " list mixed = [1, \"two\", 3.0];\n" - " list keys = [llGetOwner(), \"text\"];\n" - " integer n = 1;\n" - " llOwnerSay(llList2String(gNames, 0) + llList2String(p, -1) + llList2String(keys, 1));\n" - " llOwnerSay(llList2String(mixed, 2) + (string)llList2Integer(mixed, 0) + llList2String(gNames, n));\n" - " integer i; for (i = 0; i < llGetListLength(p); ++i) llOwnerSay(llList2String(p, i) + llGetSubString(\"abc\", i, i));\n" - "} }\n"); - UNSCOPED_INFO("from the start, empty past the end: " + r.text); + const ALLSLToSLua::Result r = require_converted("list gNames = [\"a\", \"b\"];\n" + "list parts(string s) { return llParseString2List(s, [\",\"], []); }\n" + "default { state_entry() {\n" + " list p = parts(\"x,y\");\n" + " list mixed = [1, \"two\", 3.0];\n" + " list keys = [llGetOwner(), \"text\"];\n" + " integer n = 1;\n" + " llOwnerSay(llList2String(gNames, 0) + llList2String(p, -1) + llList2String(keys, 1));\n" + " llOwnerSay(llList2String(mixed, 2) + (string)llList2Integer(mixed, 0) + llList2String(gNames, n));\n" + " integer i; for (i = 0; i < llGetListLength(p); ++i) llOwnerSay(llList2String(p, i) + llGetSubString(\"abc\", i, i));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("from the start, empty past the end"); CHECK(has(r, "(gNames[1] or \"\")")); - UNSCOPED_INFO("back from the end, through a function's list: " + r.text); + UNSCOPED_INFO("back from the end, through a function's list"); CHECK(has(r, "(p[#p] or \"\")")); - UNSCOPED_INFO("keys and text made text: " + r.text); + UNSCOPED_INFO("keys and text made text"); CHECK(has(r, "tostring(keys[2] or \"\")")); { - INFO("a float's text is not tostring's, and an index not known: llcompat: " + r.text); + INFO("a float's text is not tostring's, and an index not known: llcompat"); CHECK(has(r, "llcompat.List2String(mixed, 2)")); CHECK(has(r, "llcompat.List2String(gNames, n)")); } - UNSCOPED_INFO("mixed items for a number: llcompat: " + r.text); + UNSCOPED_INFO("mixed items for a number: llcompat"); CHECK(has(r, "llcompat.List2Integer(mixed, 0)")); - UNSCOPED_INFO("within its length, as it is, counting from 1: " + r.text); + UNSCOPED_INFO("within its length, as it is, counting from 1"); CHECK(has(r, "for i = 1, #p do\n print(p[i] .. ll.GetSubString(\"abc\", i, i))")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: unshared lists changed at either end in place; elsewhere a new list", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("list gQueue;\n" - "list gShared;\n" - "keep(list l) { gShared = l; }\n" - "default { touch_start(integer n) {\n" - " gQueue += [n];\n" - " gQueue = llListInsertList(gQueue, [n + 1], 0);\n" - " gQueue = llDeleteSubList(gQueue, 0, 0);\n" - " gQueue = llDeleteSubList(gQueue, -1, -1);\n" - " gQueue = llDeleteSubList(gQueue, 1, 1);\n" - " keep(gShared);\n" - " gShared = llDeleteSubList(gShared, 0, 0);\n" - "} }\n"); - { - INFO("in place: " + r.text); + const ALLSLToSLua::Result r = require_converted("list gQueue;\n" + "list gShared;\n" + "keep(list l) { gShared = l; }\n" + "default { touch_start(integer n) {\n" + " gQueue += [n];\n" + " gQueue = llListInsertList(gQueue, [n + 1], 0);\n" + " gQueue = llDeleteSubList(gQueue, 0, 0);\n" + " gQueue = llDeleteSubList(gQueue, -1, -1);\n" + " gQueue = llDeleteSubList(gQueue, 1, 1);\n" + " keep(gShared);\n" + " gShared = llDeleteSubList(gShared, 0, 0);\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("in place"); CHECK(has(r, "table.insert(gQueue, 1, n + 1)")); CHECK(has(r, "table.remove(gQueue, 1)")); CHECK(has(r, "table.remove(gQueue)\n")); } - UNSCOPED_INFO("elsewhere in the list, as LSL did: " + r.text); + UNSCOPED_INFO("elsewhere in the list, as LSL did"); CHECK(has(r, "gQueue = llcompat.DeleteSubList(gQueue, 1, 1)")); - UNSCOPED_INFO("one held elsewhere, as LSL did: " + r.text); + UNSCOPED_INFO("one held elsewhere, as LSL did"); CHECK(has(r, "gShared = llcompat.DeleteSubList(gShared, 0, 0)")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: notes are keyed with their words and lint; the studio's words used", "[ALLSLToSLua]") @@ -1414,22 +1422,22 @@ namespace "}\n"; ALLSLToSLua::Options close = ALLSLToSLua::Options::closeToLSL(); const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, close); - UNSCOPED_INFO("converted"); CHECK(r.converted); std::string listed; for (const ALScriptProblem& p : r.notes) { listed += p.key + "(" + (p.args.empty() ? std::string() : p.args[0]) + ") " + p.code + "\n"; } + CAPTURE(listed); // Once for each function, with the lint where there is one. - UNSCOPED_INFO("an index: " + listed); + UNSCOPED_INFO("an index"); CHECK(noted(r, "SluaIndex", "GetSubString")); size_t indexes = 0; for (const ALScriptProblem& p : r.notes) { indexes += p.key == "SluaIndex"; const bool compat = p.key == "SluaIndex" || p.key == "SluaIndexFound" || p.key == "SluaBool"; - UNSCOPED_INFO("the lint of " + p.key + ": " + listed); + CAPTURE(p.key); REQUIRE((compat ? p.code == "SlCompatCall" : p.code.empty())); UNSCOPED_INFO("no mark left unfilled"); REQUIRE(p.message == ALScriptProblem::fill(p.message, p.args)); @@ -1437,16 +1445,16 @@ namespace UNSCOPED_INFO("one index note for one function"); CHECK(indexes == size_t(1)); { - INFO("a found index and a boolean: " + listed); + INFO("a found index and a boolean"); CHECK(noted(r, "SluaIndexFound", "SubStringIndex")); CHECK(noted(r, "SluaBool", "SameGroup")); } - UNSCOPED_INFO("words filled: " + listed); + UNSCOPED_INFO("words filled"); CHECK(has(r, "-- LSL: llcompat.GetSubString takes indexes from 0, as LSL did; ll.GetSubString takes them from 1.")); { INFO("the lint by key"); CHECK(std::string(ALLSLToSLua::lintOf("SluaIndex")) == "SlCompatCall"); - REQUIRE_FALSE(ALLSLToSLua::lintOf("SluaListCompare")); + CHECK_FALSE(ALLSLToSLua::lintOf("SluaListCompare")); } // The studio's words: each comment's, each note's, and the header's, @@ -1455,56 +1463,53 @@ namespace return key == "SluaHeader" ? std::string("first\nsecond") : "<" + key + (args.empty() ? "" : " " + args[0]) + ">"; }; const ALLSLToSLua::Result said = ALLSLToSLua::convert(lsl, close); - UNSCOPED_INFO("the header's: " + said.text); + CAPTURE(said.text); + UNSCOPED_INFO("the header's"); CHECK(said.text.rfind("-- first\n-- second\n\n", 0) == 0); - UNSCOPED_INFO("a comment's: " + said.text); + UNSCOPED_INFO("a comment's"); CHECK(has(said, "-- LSL: \n")); { INFO("a note's"); CHECK(noted(said, "SluaIndex", "GetSubString")); CHECK([&] { - for (const ALScriptProblem& p : said.notes) - { - if (p.key == "SluaIndex") + for (const ALScriptProblem& p : said.notes) { - return p.message == ""; + if (p.key == "SluaIndex") + { + return p.message == ""; + } } - } - return false; - }()); + return false; + }()); } } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: LSL comments read back as notes with Done and the lint's fix", "[ALLSLToSLua]") { - UNSCOPED_INFO("the definitions"); CHECK(definitions); const std::string lsl = "default { state_entry() { string s = \"abc\"; llOwnerSay(llGetSubString(s, 0, 2)); } }"; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); ALScriptProblems notes = ALLSLToSLua::notesIn(r.text, nullptr); { - INFO("as many as the converter noted: " + r.text); + INFO("as many as the converter noted"); REQUIRE(notes.size() == r.notes.size()); REQUIRE_FALSE(notes.empty()); } ALScriptProblem* index = nullptr; for (ALScriptProblem& note : notes) { - { - INFO("a note's"); - REQUIRE(note.key == "SluaNote"); - REQUIRE(note.severity == ALScriptProblem::Severity::Note); - REQUIRE(note.fixes.size() == 1); - REQUIRE(note.fixes[0].safe); - REQUIRE(note.fixes[0].removes); - REQUIRE_FALSE(note.fixes[0].preferred); - } + REQUIRE(note.key == "SluaNote"); + REQUIRE(note.severity == ALScriptProblem::Severity::Note); + REQUIRE(note.fixes.size() == 1); + REQUIRE(note.fixes[0].safe); + REQUIRE(note.fixes[0].removes); + REQUIRE_FALSE(note.fixes[0].preferred); index = note.message.rfind("llcompat.GetSubString takes indexes", 0) == 0 ? ¬e : index; } { - INFO("the index's, the lint's: " + r.text); + INFO("the index's, the lint's"); REQUIRE(index); CHECK(index->code == "SlCompatCall"); } @@ -1522,15 +1527,15 @@ namespace { INFO("made: ll's, the note gone"); REQUIRE(made); - REQUIRE(made->find("ll.GetSubString(s, 1, 3)") != std::string::npos); - REQUIRE(made->find("llcompat.GetSubString takes indexes") == std::string::npos); + CHECK(made->find("ll.GetSubString(s, 1, 3)") != std::string::npos); + CHECK(made->find("llcompat.GetSubString takes indexes") == std::string::npos); } const std::optional done = ALScriptFixes::apply(r.text, linked.fixes[1]); { INFO("done: the line gone, the call kept"); REQUIRE(done); - REQUIRE(done->find("llcompat.GetSubString takes indexes") == std::string::npos); - REQUIRE(done->find("llcompat.GetSubString(s, 0, 2)") != std::string::npos); + CHECK(done->find("llcompat.GetSubString takes indexes") == std::string::npos); + CHECK(done->find("llcompat.GetSubString(s, 0, 2)") != std::string::npos); CHECK(std::count(done->begin(), done->end(), '\n') + 1 == std::count(r.text.begin(), r.text.end(), '\n')); } @@ -1553,132 +1558,136 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: parameters only read as truths are booleans; others and events numbers", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("say(integer loud, string text) { if (loud) llShout(0, text); else llSay(0, text); }\n" - "glow(integer on) { if (!on) return; llOwnerSay(\"glow\"); }\n" - "integer next(integer n) { return n + 1; }\n" - "integer exactly(integer f) { return f == TRUE; }\n" - "default {\n" - " touch_start(integer n) {\n" - " say(TRUE, \"hi\");\n" - " say(n > 1, \"many\");\n" - " say(n, \"some\");\n" - " glow(FALSE);\n" - " llOwnerSay((string)next(n));\n" - " exactly(1); exactly(5);\n" - " }\n" - " on_rez(integer p) { if (p) llOwnerSay(\"rezzed\"); }\n" - "}\n"); - { - INFO("read only as a truth: a boolean: " + r.text); + const ALLSLToSLua::Result r = require_converted("say(integer loud, string text) { if (loud) llShout(0, text); else llSay(0, text); }\n" + "glow(integer on) { if (!on) return; llOwnerSay(\"glow\"); }\n" + "integer next(integer n) { return n + 1; }\n" + "integer exactly(integer f) { return f == TRUE; }\n" + "default {\n" + " touch_start(integer n) {\n" + " say(TRUE, \"hi\");\n" + " say(n > 1, \"many\");\n" + " say(n, \"some\");\n" + " glow(FALSE);\n" + " llOwnerSay((string)next(n));\n" + " exactly(1); exactly(5);\n" + " }\n" + " on_rez(integer p) { if (p) llOwnerSay(\"rezzed\"); }\n" + "}\n"); + CAPTURE(r.text); + { + INFO("read only as a truth: a boolean"); CHECK(has(r, "local function say(loud, text)")); CHECK(has(r, "if loud then")); } { - INFO("each call giving one, a number as its truth: " + r.text); + INFO("each call giving one, a number as its truth"); CHECK(has(r, "say(true, \"hi\")")); CHECK(has(r, "say(n > 1, \"many\")")); CHECK(has(r, "say(n ~= 0, \"some\")")); } { - INFO("not, as a truth: " + r.text); + INFO("not, as a truth"); CHECK(has(r, "if not on then")); CHECK(has(r, "glow(false)")); } { - INFO("read as a number: kept one: " + r.text); + INFO("read as a number: kept one"); CHECK(has(r, "return n + 1")); CHECK(has(r, "next_(n)")); } { - INFO("given 5 where asked about TRUE: kept a number: " + r.text); + INFO("given 5 where asked about TRUE: kept a number"); CHECK(has(r, "f == 1")); CHECK(has(r, "exactly(5)")); } - UNSCOPED_INFO("an event's: the grid's number: " + r.text); + UNSCOPED_INFO("an event's: the grid's number"); CHECK(has(r, "if p ~= 0 then")); - checksClean(r); + require_checks_clean(r); ALLSLToSLua::Options typed; typed.types = true; const ALLSLToSLua::Result t = ALLSLToSLua::convert("say(integer loud, string text) { if (loud) llSay(0, text); }\n" "default { state_entry() { say(TRUE, \"hi\"); } }\n", typed); - UNSCOPED_INFO("typed: " + t.text); + CAPTURE(t.text); CHECK(has(t, "local function say(loud: boolean, text: string)")); - checksClean(t); + require_checks_clean(t); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a counting loop from 0 whose counter only indexes walks the items", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("list gNames = [\"a\", \"b\"];\n" - "default { state_entry() {\n" - " list keys = [llGetOwner()];\n" - " integer i;\n" - " for (i = 0; i < llGetListLength(gNames); ++i) llOwnerSay(llList2String(gNames, i));\n" - " integer j;\n" - " for (j = 0; j < llGetListLength(gNames); ++j) {\n" - " integer k;\n" - " for (k = 0; k < llGetListLength(keys); ++k) llOwnerSay(llList2String(gNames, j) + llList2String(keys, k));\n" - " }\n" - " integer m;\n" - " for (m = 1; m < llGetListLength(gNames); ++m) llOwnerSay(llList2String(gNames, m));\n" - " integer n;\n" - " for (n = 0; n < llGetListLength(gNames); ++n) llOwnerSay(llList2String(gNames, n) + (string)n);\n" - "} }\n"); - UNSCOPED_INFO("walked: " + r.text); + const ALLSLToSLua::Result r = require_converted("list gNames = [\"a\", \"b\"];\n" + "default { state_entry() {\n" + " list keys = [llGetOwner()];\n" + " integer i;\n" + " for (i = 0; i < llGetListLength(gNames); ++i) llOwnerSay(llList2String(gNames, i));\n" + " integer j;\n" + " for (j = 0; j < llGetListLength(gNames); ++j) {\n" + " integer k;\n" + " for (k = 0; k < llGetListLength(keys); ++k) llOwnerSay(llList2String(gNames, j) + llList2String(keys, k));\n" + " }\n" + " integer m;\n" + " for (m = 1; m < llGetListLength(gNames); ++m) llOwnerSay(llList2String(gNames, m));\n" + " integer n;\n" + " for (n = 0; n < llGetListLength(gNames); ++n) llOwnerSay(llList2String(gNames, n) + (string)n);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("walked"); CHECK(has(r, "for _, item in gNames do\n print(item)\n")); - UNSCOPED_INFO("one inside another, each its own name, a key's made text: " + r.text); + UNSCOPED_INFO("one inside another, each its own name, a key's made text"); CHECK(has(r, "for _, item2 in keys do\n print(item .. tostring(item2))")); - UNSCOPED_INFO("from the second: counted: " + r.text); + UNSCOPED_INFO("from the second: counted"); CHECK(has(r, "for m = 2, #gNames do\n print(gNames[m])")); - UNSCOPED_INFO("its counter read as a number too: counted: " + r.text); + UNSCOPED_INFO("its counter read as a number too: counted"); CHECK(has(r, "for n = 0, #gNames - 1 do")); - checksClean(r); - - const ALLSLToSLua::Result named = convert("list gNames = [\"a\"];\n" - "integer _ = 2;\n" - "default { state_entry() {\n" - " integer i;\n" - " for (i = 0; i < llGetListLength(gNames); ++i) llOwnerSay(llList2String(gNames, i) + (string)_);\n" - "} }\n"); - UNSCOPED_INFO("an index that would hide the script's _ named afresh: " + named.text); + require_checks_clean(r); + + const ALLSLToSLua::Result named = require_converted("list gNames = [\"a\"];\n" + "integer _ = 2;\n" + "default { state_entry() {\n" + " integer i;\n" + " for (i = 0; i < llGetListLength(gNames); ++i) llOwnerSay(llList2String(gNames, i) + (string)_);\n" + "} }\n"); + CAPTURE(named.text); + UNSCOPED_INFO("an index that would hide the script's _ named afresh"); CHECK(has(named, "for _2, item in gNames do")); - checksClean(named); + require_checks_clean(named); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: strings built in loops: two loops share one; one read in its loop kept", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " string s;\n" - " integer i;\n" - " for (i = 0; i < 2; ++i) s += \"a\";\n" - " integer j;\n" - " for (j = 0; j < 2; ++j) s += \"b\";\n" - " llOwnerSay(s);\n" - " string line;\n" - " string all;\n" - " integer k;\n" - " for (k = 0; k < 3; ++k) { line += \"x\"; all += line; }\n" - " llOwnerSay(all);\n" - "} }\n"); - UNSCOPED_INFO("declared at the first loop's join: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " string s;\n" + " integer i;\n" + " for (i = 0; i < 2; ++i) s += \"a\";\n" + " integer j;\n" + " for (j = 0; j < 2; ++j) s += \"b\";\n" + " llOwnerSay(s);\n" + " string line;\n" + " string all;\n" + " integer k;\n" + " for (k = 0; k < 3; ++k) { line += \"x\"; all += line; }\n" + " llOwnerSay(all);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("declared at the first loop's join"); CHECK(has(r, "local s = table.concat(sParts)")); { - INFO("the second adds to it: " + r.text); + INFO("the second adds to it"); CHECK(has(r, "s ..= table.concat(sParts2)")); CHECK_FALSE(has(r, "local s = table.concat(sParts2)")); } { - INFO("one read in its loop kept as it is: " + r.text); + INFO("one read in its loop kept as it is"); CHECK(has(r, "local line = \"\"")); CHECK_FALSE(has(r, "table.concat(lineParts)")); } { - INFO("the other built from it: " + r.text); + INFO("the other built from it"); CHECK(has(r, "table.insert(allParts, line)")); CHECK(has(r, "local all = table.concat(allParts)")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: bit32 answers made signed where they can pass 2147483647 and it shows", "[ALLSLToSLua]") @@ -1686,49 +1695,50 @@ namespace // A protocol of link messages whose numbers have their top bit set, // as a set of scripts may share: before, the mask's answer was never // the message's number, and no message was heard. - const ALLSLToSLua::Result r = convert("integer MASK = 0xFFFF0000;\n" - "integer MSG = 0xB3470000;\n" - "integer FN_MASK = 0x0000FF00;\n" - "integer FN_RESET = 0x0500;\n" - "default {\n" - " state_entry() {\n" - " llMessageLinked(LINK_SET, MSG | FN_RESET, \"\", \"\");\n" - " }\n" - " link_message(integer sender, integer num, string text, key id) {\n" - " if ((num & MASK) == MSG) {\n" - " integer fn = num & FN_MASK;\n" - " if (fn == FN_RESET) llOwnerSay(\"reset\");\n" - " }\n" - " integer inverse = ~num;\n" - " integer high = num << 4;\n" - " integer low = num >> 2;\n" - " integer part = FN_MASK >> 8;\n" - " if (~llGetPermissions() & PERMISSION_TRIGGER_ANIMATION) llOwnerSay(\"ask\");\n" - " llOwnerSay((string)(inverse + high + low + part));\n" - " }\n" - "}\n"); - UNSCOPED_INFO("the mask's answer signed: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer MASK = 0xFFFF0000;\n" + "integer MSG = 0xB3470000;\n" + "integer FN_MASK = 0x0000FF00;\n" + "integer FN_RESET = 0x0500;\n" + "default {\n" + " state_entry() {\n" + " llMessageLinked(LINK_SET, MSG | FN_RESET, \"\", \"\");\n" + " }\n" + " link_message(integer sender, integer num, string text, key id) {\n" + " if ((num & MASK) == MSG) {\n" + " integer fn = num & FN_MASK;\n" + " if (fn == FN_RESET) llOwnerSay(\"reset\");\n" + " }\n" + " integer inverse = ~num;\n" + " integer high = num << 4;\n" + " integer low = num >> 2;\n" + " integer part = FN_MASK >> 8;\n" + " if (~llGetPermissions() & PERMISSION_TRIGGER_ANIMATION) llOwnerSay(\"ask\");\n" + " llOwnerSay((string)(inverse + high + low + part));\n" + " }\n" + "}\n"); + CAPTURE(r.text); + UNSCOPED_INFO("the mask's answer signed"); CHECK(has(r, "if bit32.s32(bit32.band(num, MASK)) == MSG then")); - UNSCOPED_INFO("the number sent signed: " + r.text); + UNSCOPED_INFO("the number sent signed"); CHECK(has(r, "ll.MessageLinked(LINK_SET, bit32.s32(bit32.bor(MSG, FN_RESET)), \"\", \"\")")); - UNSCOPED_INFO("an & with a constant not below nought as it is: " + r.text); + UNSCOPED_INFO("an & with a constant not below nought as it is"); CHECK(has(r, "local fn = bit32.band(num, FN_MASK)\n")); - UNSCOPED_INFO("~ signed: " + r.text); + UNSCOPED_INFO("~ signed"); CHECK(has(r, "local inverse = bit32.s32(bit32.bnot(num))")); - UNSCOPED_INFO("<< signed: " + r.text); + UNSCOPED_INFO("<< signed"); CHECK(has(r, "local high = bit32.s32(bit32.lshift(num, 4))")); - UNSCOPED_INFO(">> of what may be below nought signed: " + r.text); + UNSCOPED_INFO(">> of what may be below nought signed"); CHECK(has(r, "local low = bit32.s32(bit32.arshift(num, 2))")); - UNSCOPED_INFO(">> of a constant not below nought as it is: " + r.text); + UNSCOPED_INFO(">> of a constant not below nought as it is"); CHECK(has(r, "local part = bit32.arshift(FN_MASK, 8)\n")); - UNSCOPED_INFO("a truth, and what bit32 takes again, as they are: " + r.text); + UNSCOPED_INFO("a truth, and what bit32 takes again, as they are"); CHECK(has(r, "if bit32.btest(bit32.bnot(ll.GetPermissions()), PERMISSION_TRIGGER_ANIMATION) then")); { - INFO("SLua's own bit32.s32, no helper, said once: " + r.text); + INFO("SLua's own bit32.s32, no helper, said once"); CHECK_FALSE(has(r, "local function int32")); CHECK(count(r, "-- LSL: bit32 answers") == 1); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: comments carried over where they stood with blocks at a safe level", "[ALLSLToSLua]") @@ -1773,55 +1783,57 @@ namespace " /* it ends ]] here */\n" " }\n" "}\n"; - const ALLSLToSLua::Result r = convert(lsl); + const ALLSLToSLua::Result r = require_converted(lsl); + CAPTURE(r.text); const auto at = [&r](const std::string& text) { return r.text.find(text); }; - UNSCOPED_INFO("the script's own at the top: " + r.text); + UNSCOPED_INFO("the script's own at the top"); CHECK(has(r, "-- Door script by Someone.\n-- Do as you like with it.\n\n")); { - INFO("over everything, once: " + r.text); + INFO("over everything, once"); CHECK(at("-- Door script by Someone.") < at("local SWING")); CHECK(count(r, "Door script") == 1); } - UNSCOPED_INFO("a global's, what trailed it still after it: " + r.text); + UNSCOPED_INFO("a global's, what trailed it still after it"); CHECK(has(r, "-- How far it swings, in degrees\nlocal SWING = 90.0 -- a right angle\n")); - UNSCOPED_INFO("those inside a list, on their lines: " + r.text); + UNSCOPED_INFO("those inside a list, on their lines"); CHECK(has(r, "local NAMES = {\n \"open\", -- when it is open\n \"closed\" -- when it is shut\n}\n")); - UNSCOPED_INFO("a block comment as Luau's: " + r.text); + UNSCOPED_INFO("a block comment as Luau's"); CHECK(has(r, "--[[ old code:\ninteger unused() { return 1; }\n]]\n")); { - INFO("over the function it stood over: " + r.text); + INFO("over the function it stood over"); CHECK(at("--[[ old code:") > at("local NAMES")); CHECK(at("--[[ old code:") < at("-- Turns the door.")); } - UNSCOPED_INFO("a brace's, after the line that opens it: " + r.text); + UNSCOPED_INFO("a brace's, after the line that opens it"); CHECK(has(r, "-- Turns the door.\nlocal function swing(by) -- by degrees\n")); - UNSCOPED_INFO("over a statement: " + r.text); + UNSCOPED_INFO("over a statement"); CHECK(has(r, " -- the rotation it turns by\n local r = ")); - UNSCOPED_INFO("a trailing one, after its statement: " + r.text); + UNSCOPED_INFO("a trailing one, after its statement"); CHECK(has(r, " ll.SetLinkPrimitiveParamsFast(LINK_THIS, { PRIM_ROT_LOCAL, r * ll.GetLocalRot() }) -- turn\n")); - UNSCOPED_INFO("between branches, over the elseif: " + r.text); + UNSCOPED_INFO("between branches, over the elseif"); CHECK(has(r, " -- when it shuts\n elseif by < 0 then\n")); - UNSCOPED_INFO("an empty branch's, in it: " + r.text); + UNSCOPED_INFO("an empty branch's, in it"); CHECK(has(r, " if false then\n --[[ left off ]]\n elseif by == 0 then\n")); - UNSCOPED_INFO("at a block's end: " + r.text); + UNSCOPED_INFO("at a block's end"); CHECK(has(r, " -- done\nend\n")); - UNSCOPED_INFO("a state's, over its handler: " + r.text); + UNSCOPED_INFO("a state's, over its handler"); CHECK(has(r, "-- on a touch\nLLEvents:on(\"touch_start\"")); - UNSCOPED_INFO("not a block of Luau's: " + r.text); + UNSCOPED_INFO("not a block of Luau's"); CHECK(has(r, " swing(SWING) -- [[ not a block of Luau's ]]\n")); - UNSCOPED_INFO("a level nothing in it closes: " + r.text); + UNSCOPED_INFO("a level nothing in it closes"); CHECK(has(r, " --[=[ it ends ]] here ]=]\nend)")); - checksClean(r); + require_checks_clean(r); ALLSLToSLua::Options none; none.keepComments = false; const ALLSLToSLua::Result bare = ALLSLToSLua::convert(lsl, none); + CAPTURE(bare.text); { - INFO("none where asked: " + bare.text); + INFO("none where asked"); CHECK(bare.converted); - REQUIRE(bare.text.find("Door script") == std::string::npos); - REQUIRE(bare.text.find("degrees") == std::string::npos); - REQUIRE(bare.text.find("--[[") == std::string::npos); + CHECK(bare.text.find("Door script") == std::string::npos); + CHECK(bare.text.find("degrees") == std::string::npos); + CHECK(bare.text.find("--[[") == std::string::npos); } } @@ -1841,7 +1853,7 @@ namespace " llSay(0, (string)count);\n" // 11 " }\n" // 12 "}\n"; // 13 - const ALLSLToSLua::Result r = convert(lsl); + const ALLSLToSLua::Result r = require_converted(lsl); std::vector lines; for (size_t from = 0; from <= r.text.size();) { @@ -1851,15 +1863,13 @@ namespace } for (const ALLSLToSLua::Span& span : r.spans) { - { - INFO("within both: " + std::to_string(span.lslFirst) + " " + std::to_string(span.sluaFirst)); - REQUIRE(span.lslFirst >= 0); - REQUIRE(span.lslLast < 14); - REQUIRE(span.lslFirst <= span.lslLast); - REQUIRE(span.sluaFirst >= 0); - REQUIRE(span.sluaFirst <= span.sluaLast); - REQUIRE(span.sluaLast < static_cast(lines.size())); - } + CAPTURE(span.lslFirst, span.sluaFirst); + REQUIRE(span.lslFirst >= 0); + REQUIRE(span.lslLast < 14); + REQUIRE(span.lslFirst <= span.lslLast); + REQUIRE(span.sluaFirst >= 0); + REQUIRE(span.sluaFirst <= span.sluaLast); + REQUIRE(span.sluaLast < static_cast(lines.size())); } const auto at = [&](S32 lsl_line) -> std::string { for (const ALLSLToSLua::Span& span : r.spans) @@ -1872,95 +1882,96 @@ namespace return "(none)"; }; UNSCOPED_INFO("the global: " + at(0) + "\n" + r.text); - REQUIRE(at(0).find("local count") != std::string::npos); + CHECK(at(0).find("local count") != std::string::npos); UNSCOPED_INFO("the function: " + at(1)); - REQUIRE(at(1).find("function add(") != std::string::npos); + CHECK(at(1).find("function add(") != std::string::npos); { INFO("its statement: " + at(3)); - REQUIRE(at(3).find("count") != std::string::npos); - REQUIRE(at(3).find("n") != std::string::npos); + CHECK(at(3).find("count") != std::string::npos); + CHECK(at(3).find("n") != std::string::npos); } UNSCOPED_INFO("the handler: " + at(7)); - REQUIRE(at(7).find("touch_start") != std::string::npos); + CHECK(at(7).find("touch_start") != std::string::npos); UNSCOPED_INFO("a call under its comment, not the comment: " + at(10)); - REQUIRE(at(10).find("add(1)") != std::string::npos); + CHECK(at(10).find("add(1)") != std::string::npos); UNSCOPED_INFO("the next: " + at(11)); - REQUIRE(at(11).find("ll.Say(0") != std::string::npos); + CHECK(at(11).find("ll.Say(0") != std::string::npos); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: fewer calls: bit32.btest and table.append; hexadecimal kept where safe", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer MASK = 0x0000FF00;\n" - "integer HIGH = 0xF0000000;\n" - "integer S_MASK = 0xF0000000;\n" - "integer BIG = 4294967296;\n" - "integer BIG_HEX = 0x100000000;\n" - "integer WRAPPED = 2147483648;\n" - "key gSitter;\n" - "default { touch_start(integer n) {\n" - " integer flags = llGetParcelFlags(llGetPos());\n" - " if (flags & PARCEL_FLAG_ALLOW_CREATE_OBJECTS) llOwnerSay(\"build\");\n" - " if (!(flags & MASK & 0x300)) llOwnerSay(\"none\");\n" - " if ((flags & 4) == 0) llOwnerSay(\"no 4\");\n" - " integer masked = flags & MASK;\n" - " integer top = flags & 0x80000000;\n" - " integer sign = flags & S_MASK; if (flags & ~S_MASK) sign = 0;\n" - " integer low = 0xFFFF0000; sign = sign | low;\n" - " list params = [PRIM_NAME, \"a\"];\n" - " params += [PRIM_LINK_TARGET, LINK_ROOT, PRIM_POSITION, llGetPos()];\n" - " params += n;\n" - " llSetLinkPrimitiveParamsFast(LINK_THIS, params);\n" - " float f = (float)\"1.5\";\n" - " llOwnerSay((string)(masked + HIGH + top + BIG + BIG_HEX + WRAPPED + sign) + (string)f + (string)gSitter);\n" - "} }\n"); - UNSCOPED_INFO("an & as a condition: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer MASK = 0x0000FF00;\n" + "integer HIGH = 0xF0000000;\n" + "integer S_MASK = 0xF0000000;\n" + "integer BIG = 4294967296;\n" + "integer BIG_HEX = 0x100000000;\n" + "integer WRAPPED = 2147483648;\n" + "key gSitter;\n" + "default { touch_start(integer n) {\n" + " integer flags = llGetParcelFlags(llGetPos());\n" + " if (flags & PARCEL_FLAG_ALLOW_CREATE_OBJECTS) llOwnerSay(\"build\");\n" + " if (!(flags & MASK & 0x300)) llOwnerSay(\"none\");\n" + " if ((flags & 4) == 0) llOwnerSay(\"no 4\");\n" + " integer masked = flags & MASK;\n" + " integer top = flags & 0x80000000;\n" + " integer sign = flags & S_MASK; if (flags & ~S_MASK) sign = 0;\n" + " integer low = 0xFFFF0000; sign = sign | low;\n" + " list params = [PRIM_NAME, \"a\"];\n" + " params += [PRIM_LINK_TARGET, LINK_ROOT, PRIM_POSITION, llGetPos()];\n" + " params += n;\n" + " llSetLinkPrimitiveParamsFast(LINK_THIS, params);\n" + " float f = (float)\"1.5\";\n" + " llOwnerSay((string)(masked + HIGH + top + BIG + BIG_HEX + WRAPPED + sign) + (string)f + (string)gSitter);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("an & as a condition"); CHECK(has(r, "if bit32.btest(flags, PARCEL_FLAG_ALLOW_CREATE_OBJECTS) then")); - UNSCOPED_INFO("not of one, an & of &s one call: " + r.text); + UNSCOPED_INFO("not of one, an & of &s one call"); CHECK(has(r, "if not bit32.btest(flags, MASK, 0x300) then")); - UNSCOPED_INFO("against nought: " + r.text); + UNSCOPED_INFO("against nought"); CHECK(has(r, "if not bit32.btest(flags, 4) then")); - UNSCOPED_INFO("an & as a number is still bit32.band: " + r.text); + UNSCOPED_INFO("an & as a number is still bit32.band"); CHECK(has(r, "local masked = bit32.band(flags, MASK)")); - UNSCOPED_INFO("one bit32 takes, as written, which it reads the same: " + r.text); + UNSCOPED_INFO("one bit32 takes, as written, which it reads the same"); CHECK(has(r, "local top = bit32.s32(bit32.band(flags, 0x80000000))")); { - INFO("values added together: " + r.text); + INFO("values added together"); CHECK(has(r, "table.append(params, PRIM_LINK_TARGET, LINK_ROOT, PRIM_POSITION, ll.GetPos())")); CHECK(has(r, "table.insert(params, n)")); } { - INFO("the null key: " + r.text); + INFO("the null key"); CHECK(has(r, "local gSitter = NULL_KEY")); CHECK_FALSE(has(r, "uuid(\"\")")); } { - INFO("a float from a string: " + r.text); + INFO("a float from a string"); CHECK(has(r, "lslFloat(\"1.5\")")); CHECK(has(r, "return llcompat.List2Float({ s }, 0)")); } - UNSCOPED_INFO("hexadecimal kept: " + r.text); + UNSCOPED_INFO("hexadecimal kept"); CHECK(has(r, "local MASK = 0x0000FF00")); - UNSCOPED_INFO("past 0x7FFFFFFF, which SLua reads as another number, as LSL had it: " + r.text); + UNSCOPED_INFO("past 0x7FFFFFFF, which SLua reads as another number, as LSL had it"); CHECK(has(r, "local HIGH = -268435456")); // As the grid's 32-bit hosts read them: strtoul stops at 0xFFFFFFFF. { - INFO("past 32 bits, -1: " + r.text); + INFO("past 32 bits, -1"); CHECK(has(r, "local BIG = -1\n")); CHECK(has(r, "local BIG_HEX = -1\n")); } { - INFO("and said: " + r.text); + INFO("and said"); CHECK(noted(r, "SluaIntegerPast32Bits", "4294967296")); CHECK(noted(r, "SluaIntegerPast32Bits", "0x100000000")); } { - INFO("a global only bit32 reads keeps its hexadecimal: " + r.text); + INFO("a global only bit32 reads keeps its hexadecimal"); CHECK(has(r, "local S_MASK = 0xF0000000\n")); CHECK(has(r, "bit32.band(flags, S_MASK)")); } - UNSCOPED_INFO("one read as a number does not: " + r.text); + UNSCOPED_INFO("one read as a number does not"); CHECK(has(r, "local HIGH = -268435456")); - UNSCOPED_INFO("nor a local, never set, only bit32 reads: " + r.text); + UNSCOPED_INFO("nor a local, never set, only bit32 reads"); CHECK(has(r, "local low = 0xFFFF0000\n")); // Each said once, over the first place: what SLua reads such a // number as, and what bit32.s32 is for. @@ -1968,62 +1979,64 @@ namespace return std::count_if(r.notes.begin(), r.notes.end(), [key](const ALScriptProblem& p) { return p.key == key; }); }; { - INFO("the hexadecimal's number said once, over the first: " + r.text); + INFO("the hexadecimal's number said once, over the first"); CHECK(times("SluaHexSign") == 1); CHECK(noted(r, "SluaHexSign", "0xF0000000")); CHECK(has(r, "-- LSL: SLua reads 0xF0000000 as 4026531840, where LSL wrapped it to -268435456: bit32 takes either the same, so it " "stays as written where only bit32 reads it, and is -268435456 where it is read as a number.\nlocal HIGH = -268435456")); } { - INFO("bit32.s32 said once: " + r.text); + INFO("bit32.s32 said once"); CHECK(times("SluaBit32Signed") == 1); CHECK(count(r, "-- LSL: bit32 answers 0 to 4294967295") == 1); } - UNSCOPED_INFO("within them, wrapped: " + r.text); + UNSCOPED_INFO("within them, wrapped"); CHECK(has(r, "local WRAPPED = -2147483648\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: numeric for by a constant and a shared counter; one forward declaration", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer ACTIVE_PROPS_STRIDE = 3;\n" - "list activeProps = [\"a\", 1, 2, \"b\", 3, 4];\n" - "setProps(list unattachedProps) {\n" - " integer i;\n" - " for (i = llGetListLength(activeProps) - ACTIVE_PROPS_STRIDE; i >= 0; i -= ACTIVE_PROPS_STRIDE) {\n" - " llOwnerSay((string)i);\n" - " }\n" - " for (i = 0; i < llGetListLength(unattachedProps); i++) {\n" - " llOwnerSay((string)i + llList2String(unattachedProps, i));\n" - " }\n" - " report();\n" - "}\n" - "report() { llOwnerSay(\"done\"); }\n" - "integer twice(integer n) { return n * 2; }\n" - "default { state_entry() { setProps([\"x\"]); llOwnerSay((string)twice(2)); } }\n"); - UNSCOPED_INFO("down by the constant's name: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer ACTIVE_PROPS_STRIDE = 3;\n" + "list activeProps = [\"a\", 1, 2, \"b\", 3, 4];\n" + "setProps(list unattachedProps) {\n" + " integer i;\n" + " for (i = llGetListLength(activeProps) - ACTIVE_PROPS_STRIDE; i >= 0; i -= ACTIVE_PROPS_STRIDE) {\n" + " llOwnerSay((string)i);\n" + " }\n" + " for (i = 0; i < llGetListLength(unattachedProps); i++) {\n" + " llOwnerSay((string)i + llList2String(unattachedProps, i));\n" + " }\n" + " report();\n" + "}\n" + "report() { llOwnerSay(\"done\"); }\n" + "integer twice(integer n) { return n * 2; }\n" + "default { state_entry() { setProps([\"x\"]); llOwnerSay((string)twice(2)); } }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("down by the constant's name"); CHECK(has(r, "for i = #activeProps - ACTIVE_PROPS_STRIDE, 0, -ACTIVE_PROPS_STRIDE do")); - UNSCOPED_INFO("the same counter's other for: " + r.text); + UNSCOPED_INFO("the same counter's other for"); CHECK(has(r, "for i = 0, #unattachedProps - 1 do")); { - INFO("the counter's declaration gone: " + r.text); + INFO("the counter's declaration gone"); CHECK_FALSE(has(r, "local i = 0")); CHECK_FALSE(has(r, "while i")); } { - INFO("only the one called first declared first: " + r.text); + INFO("only the one called first declared first"); CHECK(has(r, "local report\n")); CHECK(has(r, "local function setProps(")); CHECK(has(r, "\nfunction report()")); CHECK(has(r, "local function twice(")); } - checksClean(r); + require_checks_clean(r); - const ALLSLToSLua::Result set = convert("integer gStep = 2;\n" - "default { state_entry() { integer i; for (i = 0; i < 10; i += gStep) llOwnerSay((string)i); gStep = 3; } }\n"); - UNSCOPED_INFO("a step by a global something sets keeps the while: " + set.text); + const ALLSLToSLua::Result set = require_converted("integer gStep = 2;\n" + "default { state_entry() { integer i; for (i = 0; i < 10; i += gStep) llOwnerSay((string)i); gStep = 3; } }\n"); + CAPTURE(set.text); + UNSCOPED_INFO("a step by a global something sets keeps the while"); CHECK(has(set, "while i < 10 do")); - checksClean(set); + require_checks_clean(set); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: comments as laid out: trailing; rules as dashes; the toggle; one-line ifs", "[ALLSLToSLua]") @@ -2054,28 +2067,28 @@ namespace "}\n" "default { state_entry() { llOwnerSay(get(allowed(S_MASK)) + (string)gDebug); } }\n", options); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("a rule of stars: " + r.text); + UNSCOPED_INFO("a rule of stars"); CHECK(has(r, "\n-----------------------------------------\n--------------- CONSTANTS ---------------\n")); - UNSCOPED_INFO("after its statement: " + r.text); + UNSCOPED_INFO("after its statement"); CHECK(has(r, "local S_MASK: number = -268435456 -- -268435456\n")); - UNSCOPED_INFO("a rule of slashes: " + r.text); + UNSCOPED_INFO("a rule of slashes"); CHECK((has(r, "\n---------------------------------------------------------------\n") || - has(r, "------------------------- DEBUGGING ---------------------------\n"))); + has(r, "------------------------- DEBUGGING ---------------------------\n"))); { - INFO("the toggle: " + r.text); + INFO("the toggle"); CHECK(has(r, "---[[\nlocal function get(")); CHECK(has(r, "end\n--]]\n")); } - UNSCOPED_INFO("the toggle the other way: " + r.text); + UNSCOPED_INFO("the toggle the other way"); CHECK(has(r, "--[[\nstring old() { return \"\"; }\n--]]")); - UNSCOPED_INFO("an if on one line on one: " + r.text); + UNSCOPED_INFO("an if on one line on one"); CHECK(has(r, " if bit32.btest(parcelFlags, PARCEL_FLAG_ALLOW_CREATE_OBJECTS) then return 1 end -- may build\n")); - UNSCOPED_INFO("one on two lines on three: " + r.text); + UNSCOPED_INFO("one on two lines on three"); CHECK(has(r, " if bit32.btest(parcelFlags, 4) then\n return 2\n end\n")); { - INFO("after a declaration and a block comment after a step: " + r.text); + INFO("after a declaration and a block comment after a step"); CHECK(has(r, "local n: number = 3 -- three\n")); CHECK(has(r, "n += 1 --[[ and one ]]\n")); } @@ -2098,15 +2111,15 @@ namespace return "(none)"; }; UNSCOPED_INFO("the if on one line beside its line: " + at(15)); - REQUIRE(at(15).find("then return 1 end") != std::string::npos); + CHECK(at(15).find("then return 1 end") != std::string::npos); UNSCOPED_INFO("the next beside its own: " + at(16)); - REQUIRE(at(16).find("if bit32.btest(parcelFlags, 4) then") != std::string::npos); + CHECK(at(16).find("if bit32.btest(parcelFlags, 4) then") != std::string::npos); { INFO("and those after: " + at(18) + " / " + at(19)); - REQUIRE(at(18).find("local n: number = 3") != std::string::npos); - REQUIRE(at(19).find("n += 1") != std::string::npos); + CHECK(at(18).find("local n: number = 3") != std::string::npos); + CHECK(at(19).find("n += 1") != std::string::npos); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: declarations the LSL lined up lined up again and others as written", "[ALLSLToSLua]") @@ -2125,17 +2138,17 @@ namespace " llOwnerSay(PREFIX + (string)(S_MASK + ACTIVE_STRIDE + a + x + yy) + bee);\n" "} }\n", options); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("globals lined up: " + r.text); + UNSCOPED_INFO("globals lined up"); CHECK(has(r, "local S_MASK: number = -268435456 -- the sign\n" - "local ACTIVE_STRIDE: number = 3 -- per prop\n" - "local PREFIX: string = \"ARS#\" -- linkset data\n")); - UNSCOPED_INFO("not lined up, not lined up: " + r.text); + "local ACTIVE_STRIDE: number = 3 -- per prop\n" + "local PREFIX: string = \"ARS#\" -- linkset data\n")); + UNSCOPED_INFO("not lined up, not lined up"); CHECK(has(r, "local a: number = 1\nlocal bee: string = \"b\"\n")); - UNSCOPED_INFO("locals lined up: " + r.text); + UNSCOPED_INFO("locals lined up"); CHECK(has(r, "\nlocal x: number = 1\nlocal yy: number = 2.5\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a vector or rotation cast from text bracketed only where needed", "[ALLSLToSLua]") @@ -2148,88 +2161,89 @@ namespace " llOwnerSay((string)((vector)llList2String(command, 1) * 2.0));\n" " llSetPos((vector)llList2String(command, 1));\n" "} }\n"); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("assigned as it is: " + r.text); + UNSCOPED_INFO("assigned as it is"); CHECK(has(r, "offset = tovector(command[2] or \"\") or ZERO_VECTOR\n")); - UNSCOPED_INFO("a local's value too: " + r.text); + UNSCOPED_INFO("a local's value too"); CHECK(has(r, "local r = toquaternion(command[2] or \"\") or ZERO_ROTATION\n")); - UNSCOPED_INFO("an argument too: " + r.text); + UNSCOPED_INFO("an argument too"); CHECK(has(r, "ll.SetPos(tovector(command[2] or \"\") or ZERO_VECTOR)")); - UNSCOPED_INFO("an operand bracketed: " + r.text); + UNSCOPED_INFO("an operand bracketed"); CHECK(has(r, "(tovector(command[2] or \"\") or ZERO_VECTOR) * 2")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: an integer as text interpolated where joined and by tostring alone", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("set(integer keey, string value) { llLinksetDataWrite(\"ARS#\" + (string)keey, value); }\n" - "default { state_entry() {\n" - " integer n = 4; float f = 1.5; key k = llGetOwner();\n" - " set(n, (string)n);\n" - " llOwnerSay((string)f + \";\" + (string)k + \";\" + (string)(n * 2) + (string)(-n));\n" - "} }\n"); - UNSCOPED_INFO("converted"); + const ALLSLToSLua::Result r = require_converted("set(integer keey, string value) { llLinksetDataWrite(\"ARS#\" + (string)keey, value); }\n" + "default { state_entry() {\n" + " integer n = 4; float f = 1.5; key k = llGetOwner();\n" + " set(n, (string)n);\n" + " llOwnerSay((string)f + \";\" + (string)k + \";\" + (string)(n * 2) + (string)(-n));\n" + "} }\n"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("joined, interpolated: " + r.text); + UNSCOPED_INFO("joined, interpolated"); CHECK(has(r, "ll.LinksetDataWrite(`ARS#{keey}`, value)")); - UNSCOPED_INFO("alone, still text: " + r.text); + UNSCOPED_INFO("alone, still text"); CHECK(has(r, "set(n, tostring(n))")); - UNSCOPED_INFO("a float's six places, a key's text, a product bracketed, a negation bare: " + r.text); + UNSCOPED_INFO("a float's six places, a key's text, a product bracketed, a negation bare"); CHECK(has(r, "print(`{string.format(\"%.6f\", f)};{k};{n * 2}{-n}`)")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a vector as text with five places as LSL wrote it noted once", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer SEAT_NUM = 0;\n" - "vector offset = ZERO_VECTOR;\n" - "set(integer which, string value) { llLinksetDataWrite((string)which, value); }\n" - "default { state_entry() {\n" - " set(7, (string)SEAT_NUM + \";\" + (string)offset);\n" - " llOwnerSay((string)ZERO_ROTATION);\n" - "} }\n"); - UNSCOPED_INFO("converted"); + const ALLSLToSLua::Result r = require_converted("integer SEAT_NUM = 0;\n" + "vector offset = ZERO_VECTOR;\n" + "set(integer which, string value) { llLinksetDataWrite((string)which, value); }\n" + "default { state_entry() {\n" + " set(7, (string)SEAT_NUM + \";\" + (string)offset);\n" + " llOwnerSay((string)ZERO_ROTATION);\n" + "} }\n"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("LSL's text kept: " + r.text); + UNSCOPED_INFO("LSL's text kept"); CHECK(has(r, "set(7, `{SEAT_NUM};{ll.DumpList2String({ offset }, \"\")}`)")); - UNSCOPED_INFO("a rotation's too: " + r.text); + UNSCOPED_INFO("a rotation's too"); CHECK(has(r, "ll.DumpList2String({ ZERO_ROTATION }, \"\")")); { - INFO("said why, once: " + r.text); + INFO("said why, once"); CHECK(noted(r, "SluaVectorText")); - REQUIRE(r.text.find("-- LSL: ll.DumpList2String writes") != std::string::npos); + CHECK(r.text.find("-- LSL: ll.DumpList2String writes") != std::string::npos); CHECK(r.text.find("-- LSL: ll.DumpList2String writes") == r.text.rfind("-- LSL: ll.DumpList2String writes")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: text joined with non-strings interpolated and escaped; strings with ..", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " key k = llGetOwner(); integer n = 3; string s = \"x\"; float f = 0.5;\n" - " llOwnerSay(\"100% `{a}` \\\\ \\\"q\\\"\\n\" + (string)k);\n" - " llOwnerSay(\"n=\" + (string)n + \";\" + s);\n" - " llOwnerSay(\"a\" + llToUpper(\"b\" + (string)k) + (string)f);\n" - " s = s + \"/\" + (string)k;\n" - " string all = \"\";\n" - " integer i; for (i = 0; i < n; ++i) all += (string)k + \",\";\n" - " llOwnerSay(all + s);\n" - "} }\n"); - UNSCOPED_INFO("converted"); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " key k = llGetOwner(); integer n = 3; string s = \"x\"; float f = 0.5;\n" + " llOwnerSay(\"100% `{a}` \\\\ \\\"q\\\"\\n\" + (string)k);\n" + " llOwnerSay(\"n=\" + (string)n + \";\" + s);\n" + " llOwnerSay(\"a\" + llToUpper(\"b\" + (string)k) + (string)f);\n" + " s = s + \"/\" + (string)k;\n" + " string all = \"\";\n" + " integer i; for (i = 0; i < n; ++i) all += (string)k + \",\";\n" + " llOwnerSay(all + s);\n" + "} }\n"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("its text escaped -- a backtick, a brace, a backslash, a line -- and % as it is: " + r.text); + UNSCOPED_INFO("its text escaped -- a backtick, a brace, a backslash, a line -- and % as it is"); CHECK(has(r, "print(`100% \\`\\{a}\\` \\\\ \"q\"\\n{k}`)")); - UNSCOPED_INFO("a number joined too: " + r.text); + UNSCOPED_INFO("a number joined too"); CHECK(has(r, "print(`n={n};{s}`)")); - UNSCOPED_INFO("strings alone, with ..: " + r.text); + UNSCOPED_INFO("strings alone, with .."); CHECK(has(r, "print(all .. s)")); - UNSCOPED_INFO("one inside another's braces: " + r.text); + UNSCOPED_INFO("one inside another's braces"); CHECK(has(r, "print(`a{ll.ToUpper(`b{k}`)}{string.format(\"%.6f\", f)}`)")); - UNSCOPED_INFO("joined onto a string: " + r.text); + UNSCOPED_INFO("joined onto a string"); CHECK(has(r, "s ..= `/{k}`")); - checksClean(r); + require_checks_clean(r); } + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: spans end where their SLua does: blocks at end and statements on a line", "[ALLSLToSLua]") { const std::string lsl = "default\n" // 0 @@ -2244,7 +2258,7 @@ namespace " llSay(0, \"one\");\n" // 9 " }\n" // 10 "}\n"; // 11 - const ALLSLToSLua::Result r = convert(lsl); + const ALLSLToSLua::Result r = require_converted(lsl); std::vector lines; for (size_t from = 0; from <= r.text.size();) { @@ -2289,15 +2303,15 @@ namespace { INFO("the statement in it, a line of its own\n" + all + r.text); REQUIRE(inside); - REQUIRE(inside->lslLast == 6); - REQUIRE(inside->sluaFirst == inside->sluaLast); + CHECK(inside->lslLast == 6); + CHECK(inside->sluaFirst == inside->sluaLast); CHECK(trimmed(inside->sluaFirst).find("ll.Say(0, \"many\")") == 0); } const ALLSLToSLua::Span* next = span(9); { INFO("the next, under the comment: " + (next ? trimmed(next->sluaFirst) : std::string())); REQUIRE(next); - REQUIRE(next->sluaFirst == next->sluaLast); + CHECK(next->sluaFirst == next->sluaLast); REQUIRE(trimmed(next->sluaFirst).find("ll.Say(0, \"one\")") == 0); CHECK(trimmed(next->sluaFirst - 1) == "-- after"); } @@ -2305,8 +2319,8 @@ namespace { INFO("the handler, over all of it"); REQUIRE(handler); - REQUIRE(handler->lslLast == 10); - REQUIRE(handler->sluaFirst < block->sluaFirst); + CHECK(handler->lslLast == 10); + CHECK(handler->sluaFirst < block->sluaFirst); CHECK(handler->sluaLast >= next->sluaLast); } UNSCOPED_INFO("to its own end: " + trimmed(handler->sluaLast)); @@ -2315,23 +2329,24 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: what the SLua says otherwise: calls and names and syntax; no semicolons", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer end = 1;\n" - "default\n" - "{\n" - " state_entry()\n" - " {\n" - " if (end != 2 && TRUE) llSay(0, \"hi\");\n" - " llOwnerSay((string)llAbs(end));\n" - " }\n" - "}\n"); + const ALLSLToSLua::Result r = require_converted("integer end = 1;\n" + "default\n" + "{\n" + " state_entry()\n" + " {\n" + " if (end != 2 && TRUE) llSay(0, \"hi\");\n" + " llOwnerSay((string)llAbs(end));\n" + " }\n" + "}\n"); + CAPTURE(r.text); const auto has_pair = [&r](const std::string& lsl, const std::string& slua) { return std::find(r.same.begin(), r.same.end(), std::make_pair(lsl, slua)) != r.same.end(); }; - UNSCOPED_INFO("llSay as ll.Say: " + r.text); + UNSCOPED_INFO("llSay as ll.Say"); CHECK(has_pair("llSay", "ll.Say")); - UNSCOPED_INFO("llOwnerSay as SLua's print: " + r.text); + UNSCOPED_INFO("llOwnerSay as SLua's print"); CHECK(has_pair("llOwnerSay", "print")); - UNSCOPED_INFO("llAbs as SLua's own: " + r.text); + UNSCOPED_INFO("llAbs as SLua's own"); CHECK(has_pair("llAbs", "math.abs")); { INFO("a name Luau holds, marked"); @@ -2354,35 +2369,35 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: an integer added to a string by += or s = s + joined by ..= alone", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("string gOut;\n" - "string gTwo;\n" - "default { state_entry() {\n" - " integer n = llGetUnixTime();\n" - " gOut += (string)n;\n" - " gTwo = gTwo + (string)n;\n" - " llOwnerSay(gOut + gTwo);\n" - "} }\n"); - UNSCOPED_INFO("converted"); + const ALLSLToSLua::Result r = require_converted("string gOut;\n" + "string gTwo;\n" + "default { state_entry() {\n" + " integer n = llGetUnixTime();\n" + " gOut += (string)n;\n" + " gTwo = gTwo + (string)n;\n" + " llOwnerSay(gOut + gTwo);\n" + "} }\n"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("+=: " + r.text); + UNSCOPED_INFO("+="); CHECK(has(r, "gOut ..= n\n")); - UNSCOPED_INFO("s = s +: " + r.text); + UNSCOPED_INFO("s = s +"); CHECK(has(r, "gTwo ..= n\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: spans: statements sharing an LSL line side by side and not nested", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer x;\n" // 0 - "integer y;\n" // 1 - "default\n" // 2 - "{\n" // 3 - " state_entry()\n" // 4 - " {\n" // 5 - " x = 1; y = 2;\n" // 6 - " llSay(0, (string)(x + y));\n" // 7 - " }\n" // 8 - "}\n"); // 9 + const ALLSLToSLua::Result r = require_converted("integer x;\n" // 0 + "integer y;\n" // 1 + "default\n" // 2 + "{\n" // 3 + " state_entry()\n" // 4 + " {\n" // 5 + " x = 1; y = 2;\n" // 6 + " llSay(0, (string)(x + y));\n" // 7 + " }\n" // 8 + "}\n"); // 9 std::vector shared; std::string all; for (const ALLSLToSLua::Span& each : r.spans) @@ -2394,20 +2409,19 @@ namespace shared.push_back(&each); } } - UNSCOPED_INFO("two statements on the line\n" + all + r.text); + CAPTURE(all, r.text); + UNSCOPED_INFO("two statements on the line"); REQUIRE(shared.size() == size_t(2)); - UNSCOPED_INFO("their SLua apart\n" + all + r.text); + 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) { if (each.lslFirst == 4) { - { - INFO("the handler over both\n" + all); - REQUIRE(each.sluaFirst < shared[0]->sluaFirst); - REQUIRE(each.sluaLast >= shared[1]->sluaLast); - } + INFO("the handler over both"); + REQUIRE(each.sluaFirst < shared[0]->sluaFirst); + REQUIRE(each.sluaLast >= shared[1]->sluaLast); } } } @@ -2417,38 +2431,40 @@ namespace const auto has_pair = [](const ALLSLToSLua::Result& r, const std::string& lsl, const std::string& slua) { return std::find(r.same.begin(), r.same.end(), std::make_pair(lsl, slua)) != r.same.end(); }; - const ALLSLToSLua::Result plain = convert("default { state_entry() {\n" - " integer n = 2;\n" - " if (n > 1) llSay(0, (string)llPow(llFrand(1.0), 2.0));\n" - "} }\n"); - UNSCOPED_INFO("llFrand as ll.Frand: " + plain.text); + const ALLSLToSLua::Result plain = require_converted("default { state_entry() {\n" + " integer n = 2;\n" + " if (n > 1) llSay(0, (string)llPow(llFrand(1.0), 2.0));\n" + "} }\n"); + CAPTURE(plain.text); + UNSCOPED_INFO("llFrand as ll.Frand"); CHECK(has_pair(plain, "llFrand", "ll.Frand")); - UNSCOPED_INFO("llPow, written as ^, not as the call inside it: " + plain.text); + UNSCOPED_INFO("llPow, written as ^, not as the call inside it"); CHECK(std::none_of(plain.same.begin(), plain.same.end(), [](const auto& p) { return p.first == "llPow"; })); { - INFO("a block's braces: " + plain.text); + INFO("a block's braces"); CHECK(has_pair(plain, "{", "then")); CHECK(has_pair(plain, "}", "end")); } { - INFO("no vector, no string joined: no marks for them: " + plain.text); + INFO("no vector, no string joined: no marks for them"); CHECK_FALSE(has_pair(plain, ">", ")")); CHECK_FALSE(has_pair(plain, "<", "vector(")); CHECK_FALSE(has_pair(plain, "+", "..")); CHECK_FALSE(has_pair(plain, "+=", "..=")); } - const ALLSLToSLua::Result marks = convert("string gOut;\n" - "default { state_entry() {\n" - " vector v = <1, 2, llFrand(1.0)>;\n" - " gOut += llGetObjectName();\n" - " llSay(0, gOut + llGetObjectDesc() + (string)v.z);\n" - "} }\n"); + const ALLSLToSLua::Result marks = require_converted("string gOut;\n" + "default { state_entry() {\n" + " vector v = <1, 2, llFrand(1.0)>;\n" + " gOut += llGetObjectName();\n" + " llSay(0, gOut + llGetObjectDesc() + (string)v.z);\n" + "} }\n"); + CAPTURE(marks.text); { - INFO("a vector's brackets: " + marks.text); + INFO("a vector's brackets"); CHECK(has_pair(marks, "<", "vector(")); CHECK(has_pair(marks, ">", ")")); } - UNSCOPED_INFO("a string added to: " + marks.text); + UNSCOPED_INFO("a string added to"); CHECK(has_pair(marks, "+=", "..=")); } @@ -2485,62 +2501,64 @@ namespace " if (n) { /* one */ llOwnerSay(\"y\"); }\n" " }\n" "}\n"; - const ALLSLToSLua::Result r = convert(lsl); - UNSCOPED_INFO("converted"); + const ALLSLToSLua::Result r = require_converted(lsl); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("then: " + r.text); + UNSCOPED_INFO("then"); CHECK(has(r, " if i < 0 then -- no saved setting. use 0\n offset = ZERO_VECTOR\n")); - UNSCOPED_INFO("elseif: " + r.text); + UNSCOPED_INFO("elseif"); CHECK(has(r, " elseif i > 100 then -- too far\n")); - UNSCOPED_INFO("else: " + r.text); + UNSCOPED_INFO("else"); CHECK(has(r, " else -- restore saved setting\n")); { - INFO("not over the if: " + r.text); + INFO("not over the if"); CHECK_FALSE(has(r, "-- no saved setting. use 0\n -- restore")); CHECK(count(r, "restore saved setting") == 1); } - UNSCOPED_INFO("a while's do: " + r.text); + UNSCOPED_INFO("a while's do"); CHECK(has(r, " do -- count down\n")); - UNSCOPED_INFO("repeat: " + r.text); + UNSCOPED_INFO("repeat"); CHECK(has(r, "repeat --[[ at least once ]]\n")); - UNSCOPED_INFO("a for's do: " + r.text); + UNSCOPED_INFO("a for's do"); CHECK(has(r, " do -- each\n")); - UNSCOPED_INFO("a function: " + r.text); + UNSCOPED_INFO("a function"); CHECK(has(r, "local function count() -- how many\n")); - UNSCOPED_INFO("a handler: " + r.text); + UNSCOPED_INFO("a handler"); CHECK(has(r, "LLEvents:on(\"touch_start\", function(detected) -- whoever touches\n")); - UNSCOPED_INFO("one over two lines, running on: " + r.text); + UNSCOPED_INFO("one over two lines, running on"); CHECK(has(r, " if i ~= 0 then --[[ over\n two lines ]]\n print(\"x\")\n")); - UNSCOPED_INFO("a one-line if on one line still: " + r.text); + UNSCOPED_INFO("a one-line if on one line still"); CHECK(has(r, " if n ~= 0 then --[[ one ]] print(\"y\") end\n")); - checksClean(r); + require_checks_clean(r); // The same with the field style, and in a state's table. ALLSLToSLua::Options field; field.handlers = ALLSLToSLua::Options::Handlers::Field; - UNSCOPED_INFO("a field's handler: "); + UNSCOPED_INFO("a field's handler"); CHECK(has(ALLSLToSLua::convert(lsl, field), "function LLEvents.touch_start(detected) -- whoever touches\n")); - const ALLSLToSLua::Result states = convert("default {\n" - " touch_start(integer n) { // first state\n" - " state other;\n" - " }\n" - "}\n" - "state other {\n" - " state_entry() { // entered\n" - " llOwnerSay(\"in\");\n" - " }\n" - "}\n"); - { - INFO("in a state's table: " + states.text); + const ALLSLToSLua::Result states = require_converted("default {\n" + " touch_start(integer n) { // first state\n" + " state other;\n" + " }\n" + "}\n" + "state other {\n" + " state_entry() { // entered\n" + " llOwnerSay(\"in\");\n" + " }\n" + "}\n"); + CAPTURE(states.text); + { + INFO("in a state's table"); CHECK(has(states, "touch_start = function(detected) -- first state\n")); CHECK(has(states, "state_entry = function() -- entered\n")); } - checksClean(states); + require_checks_clean(states); // A single state's state_entry runs at the top: its comment at the // top of what it ran. - const ALLSLToSLua::Result entry = convert("default {\n state_entry() { // set up\n llOwnerSay(\"hi\");\n }\n}\n"); + const ALLSLToSLua::Result entry = require_converted("default {\n state_entry() { // set up\n llOwnerSay(\"hi\");\n }\n}\n"); + CAPTURE(entry.text); { - INFO("state_entry's, kept: " + entry.text); + INFO("state_entry's, kept"); CHECK(has(entry, "-- set up\n")); CHECK(entry.text.find("-- set up") < entry.text.find("ll.OwnerSay")); } @@ -2548,311 +2566,321 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a comment after the brace of an unwritten block goes over its stand-in", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer gMany;\n" - "default {\n" - " touch_start(integer n) {\n" - " if (n > 1) { // many\n" - " gMany = TRUE;\n" - " } else { // one\n" - " gMany = FALSE;\n" - " }\n" - " if (gMany) llOwnerSay(\"many\");\n" - " }\n" - "}\n"); + const ALLSLToSLua::Result r = require_converted("integer gMany;\n" + "default {\n" + " touch_start(integer n) {\n" + " if (n > 1) { // many\n" + " gMany = TRUE;\n" + " } else { // one\n" + " gMany = FALSE;\n" + " }\n" + " if (gMany) llOwnerSay(\"many\");\n" + " }\n" + "}\n"); + CAPTURE(r.text); const size_t many = r.text.find(" -- many\n"); const size_t one = r.text.find(" -- one\n"); const size_t set = r.text.find(" gMany = n > 1\n"); { - INFO("both over the line that stands for the if: " + r.text); + INFO("both over the line that stands for the if"); REQUIRE(set != std::string::npos); CHECK(many < one); CHECK(one < set); } { - INFO("once each: " + r.text); + INFO("once each"); CHECK(count(r, "-- many") == 1); CHECK(count(r, "-- one") == 1); } - checksClean(r); - - const ALLSLToSLua::Result exit = convert("default {\n" - " state_entry() {\n" - " llOwnerSay(\"in\");\n" - " }\n" - " state_exit() { // say goodbye\n" - " llOwnerSay(\"out\");\n" - " }\n" - "}\n"); + require_checks_clean(r); + + const ALLSLToSLua::Result exit = require_converted("default {\n" + " state_entry() {\n" + " llOwnerSay(\"in\");\n" + " }\n" + " state_exit() { // say goodbye\n" + " llOwnerSay(\"out\");\n" + " }\n" + "}\n"); + CAPTURE(exit.text); const size_t left = exit.text.find("-- state_exit, left out"); { - INFO("state_exit's, over the line said in its place: " + exit.text); + INFO("state_exit's, over the line said in its place"); REQUIRE(left != std::string::npos); CHECK(exit.text.find("-- say goodbye\n") < left); CHECK(count(exit, "-- say goodbye") == 1); } - const ALLSLToSLua::Result dashes = convert("default {\n" - " listen(integer c, string name, key id, string s) {\n" - " if (s == \" -- x\") llOwnerSay(\"y\");\n" - " }\n" - "}\n"); - UNSCOPED_INFO("a string's dashes no comment: " + dashes.text); + const ALLSLToSLua::Result dashes = require_converted("default {\n" + " listen(integer c, string name, key id, string s) {\n" + " if (s == \" -- x\") llOwnerSay(\"y\");\n" + " }\n" + "}\n"); + CAPTURE(dashes.text); + UNSCOPED_INFO("a string's dashes no comment"); CHECK(has(dashes, " == \" -- x\" then print(\"y\") end\n")); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: llVecNorm through one vecNorm that keeps zero zero unless known nonzero", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " vector d = llDetectedPos(0) - llGetPos();\n" - " vector a = llVecNorm(d);\n" - " vector b = llVecNorm(llGetVel());\n" - " vector c = llVecNorm(<0, 3, 4>);\n" - " vector z = llVecNorm(ZERO_VECTOR);\n" - " llOwnerSay((string)(a + b + c + z));\n" - "} }\n"); - { - INFO("guarded: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { touch_start(integer n) {\n" + " vector d = llDetectedPos(0) - llGetPos();\n" + " vector a = llVecNorm(d);\n" + " vector b = llVecNorm(llGetVel());\n" + " vector c = llVecNorm(<0, 3, 4>);\n" + " vector z = llVecNorm(ZERO_VECTOR);\n" + " llOwnerSay((string)(a + b + c + z));\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("guarded"); CHECK(has(r, "local a = vecNorm(d)")); CHECK(has(r, "vecNorm(ll.GetVel())")); } - UNSCOPED_INFO("written once: " + r.text); + UNSCOPED_INFO("written once"); CHECK(count(r, "local function vecNorm(v: vector): vector") == 1); - UNSCOPED_INFO("a vector known: " + r.text); + UNSCOPED_INFO("a vector known"); CHECK(has(r, "vector.normalize(vector(0, 3, 4))")); - UNSCOPED_INFO("nought guarded: " + r.text); + UNSCOPED_INFO("nought guarded"); CHECK(has(r, "vecNorm(ZERO_VECTOR)")); - checksClean(r); - const ALLSLToSLua::Result none = convert("default { state_entry() { llOwnerSay((string)llVecMag(<1, 2, 3>)); } }\n"); - UNSCOPED_INFO("no helper unasked: " + none.text); + require_checks_clean(r); + const ALLSLToSLua::Result none = require_converted("default { state_entry() { llOwnerSay((string)llVecMag(<1, 2, 3>)); } }\n"); + CAPTURE(none.text); + UNSCOPED_INFO("no helper unasked"); CHECK_FALSE(has(none, "vecNorm")); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: lists appended with table.extend and + on a copy of the left; no joinLists", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("list gKept = [1];\n" - "list kept() { return gKept; }\n" - "show(list l) { llOwnerSay(llDumpList2String(l, \",\")); }\n" - "default { touch_start(integer n) {\n" - " list all = llParseString2List(\"a,b\", [\",\"], []);\n" - " list more = [4, 5];\n" - " all += more;\n" - " all += all;\n" - " all += llList2List(more, 0, n);\n" - " all += [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, " - "26, 27, 28, 29, 30, 31, 32, \"x\"];\n" - " show(all);\n" - " show(kept() + more + all);\n" - " show(more + n);\n" - " show(n + more);\n" - " show([PRIM_NAME, \"x\"] + more);\n" - " show(llParseString2List(\"c\", [], []) + [1] + [<1, 2, 3>]);\n" - " llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_COLOR, ALL_SIDES, <1, 1, 1>, 1.0] + more);\n" - "} }\n"); - UNSCOPED_INFO("a list by its name: " + r.text); + const ALLSLToSLua::Result r = require_converted("list gKept = [1];\n" + "list kept() { return gKept; }\n" + "show(list l) { llOwnerSay(llDumpList2String(l, \",\")); }\n" + "default { touch_start(integer n) {\n" + " list all = llParseString2List(\"a,b\", [\",\"], []);\n" + " list more = [4, 5];\n" + " all += more;\n" + " all += all;\n" + " all += llList2List(more, 0, n);\n" + " all += [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, " + "26, 27, 28, 29, 30, 31, 32, \"x\"];\n" + " show(all);\n" + " show(kept() + more + all);\n" + " show(more + n);\n" + " show(n + more);\n" + " show([PRIM_NAME, \"x\"] + more);\n" + " show(llParseString2List(\"c\", [], []) + [1] + [<1, 2, 3>]);\n" + " llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_COLOR, ALL_SIDES, <1, 1, 1>, 1.0] + more);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a list by its name"); CHECK(has(r, "table.extend(all, more)")); - UNSCOPED_INFO("itself: " + r.text); + UNSCOPED_INFO("itself"); CHECK(has(r, "table.extend(all, all)")); { - INFO("a call's, run once: " + r.text); + INFO("a call's, run once"); CHECK(has(r, "table.extend(all, llcompat.List2List(more, 0, n))")); CHECK_FALSE(has(r, "for _, item in")); } { - INFO("many values: " + r.text); + INFO("many values"); CHECK(has(r, "table.extend(all, { 0, 1, 2,")); CHECK(has(r, "32, \"x\" })")); } - UNSCOPED_INFO("one copy, of the variable a call answers: " + r.text); + UNSCOPED_INFO("one copy, of the variable a call answers"); CHECK(has(r, "show(table.extend(table.extend(table.clone(kept()), more), all))")); - UNSCOPED_INFO("a variable copied: " + r.text); + UNSCOPED_INFO("a variable copied"); CHECK(has(r, "show(table.extend(table.clone(more), { n }))")); - UNSCOPED_INFO("a value: " + r.text); + UNSCOPED_INFO("a value"); CHECK(has(r, "show(table.extend({ n } :: { any }, more))")); { - INFO("written out: " + r.text); + INFO("written out"); CHECK(has(r, "show(table.extend({ PRIM_NAME, \"x\" } :: { any }, more))")); CHECK(has(r, "table.extend({ PRIM_COLOR, ALL_SIDES, vector(1, 1, 1), 1.0 } :: { any }, more)")); } - UNSCOPED_INFO("a library call's: " + r.text); + UNSCOPED_INFO("a library call's"); CHECK(has(r, "show(table.extend(table.extend(ll.ParseString2List(\"c\", {}, {}) :: { any }, { 1 }), { vector(1, 2, 3) }))")); { - INFO("no helper, nor a move: " + r.text); + INFO("no helper, nor a move"); CHECK_FALSE(has(r, "joinLists")); CHECK_FALSE(has(r, "table.move(")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: list functions through SLua's own where they do as LSL's; else llcompat", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("list gNames = [\"a\", \"b\"];\n" - "list gKept;\n" - "list gL = [1, 2, 3];\n" - "integer bump() { gL += [0]; return 1; }\n" - "default { touch_start(integer n) {\n" - " list l = [1, 2, 3];\n" - " integer i;\n" - " for (i = 0; i < llGetListLength(l); ++i)\n" - " l = llListReplaceList(l, [llList2Integer(l, i) * 2], i, i);\n" - " list held = [1, 2];\n" - " gKept = held;\n" - " integer j;\n" - " for (j = 0; j < llGetListLength(held); ++j)\n" - " held = llListReplaceList(held, [j], j, j);\n" - " integer k;\n" - " for (k = 0; k < llGetListLength(gL); ++k)\n" - " gL = llListReplaceList(gL, [bump()], k, k);\n" - " l = llListReplaceList(l, [7], -1, -1);\n" - " l = llListInsertList(l, [9], llGetListLength(l));\n" - " list part = llList2List(l, 1, 2);\n" - " list all = llList2List(l, 0, -1);\n" - " list rest = llList2List(l, 2, -1);\n" - " list wrapped = llList2List(l, 3, 1);\n" - " llOwnerSay(llDumpList2String(l, \",\"));\n" - " llOwnerSay(llDumpList2String(gNames + [n], \", \"));\n" - " llOwnerSay(llDumpList2String(l + [1.5], \",\"));\n" - " llOwnerSay((string)l);\n" - " integer at = llListFindList(gNames, [\"b\"]);\n" - " integer none = llListFindList(gNames, [NULL_KEY]);\n" - " if (llListFindList(gNames, [NULL_KEY]) != -1) llOwnerSay(\"null\");\n" - " integer parsed = llListFindList(llParseString2List(\"a,b\", [\",\"], []), [\"b\"]);\n" - " llOwnerSay((string)(at + none + parsed) + llDumpList2String(part + all + rest + wrapped + held, \"\"));\n" - "} }\n"); - UNSCOPED_INFO("replaced in place, in Luau's numeric for: " + r.text); + const ALLSLToSLua::Result r = require_converted("list gNames = [\"a\", \"b\"];\n" + "list gKept;\n" + "list gL = [1, 2, 3];\n" + "integer bump() { gL += [0]; return 1; }\n" + "default { touch_start(integer n) {\n" + " list l = [1, 2, 3];\n" + " integer i;\n" + " for (i = 0; i < llGetListLength(l); ++i)\n" + " l = llListReplaceList(l, [llList2Integer(l, i) * 2], i, i);\n" + " list held = [1, 2];\n" + " gKept = held;\n" + " integer j;\n" + " for (j = 0; j < llGetListLength(held); ++j)\n" + " held = llListReplaceList(held, [j], j, j);\n" + " integer k;\n" + " for (k = 0; k < llGetListLength(gL); ++k)\n" + " gL = llListReplaceList(gL, [bump()], k, k);\n" + " l = llListReplaceList(l, [7], -1, -1);\n" + " l = llListInsertList(l, [9], llGetListLength(l));\n" + " list part = llList2List(l, 1, 2);\n" + " list all = llList2List(l, 0, -1);\n" + " list rest = llList2List(l, 2, -1);\n" + " list wrapped = llList2List(l, 3, 1);\n" + " llOwnerSay(llDumpList2String(l, \",\"));\n" + " llOwnerSay(llDumpList2String(gNames + [n], \", \"));\n" + " llOwnerSay(llDumpList2String(l + [1.5], \",\"));\n" + " llOwnerSay((string)l);\n" + " integer at = llListFindList(gNames, [\"b\"]);\n" + " integer none = llListFindList(gNames, [NULL_KEY]);\n" + " if (llListFindList(gNames, [NULL_KEY]) != -1) llOwnerSay(\"null\");\n" + " integer parsed = llListFindList(llParseString2List(\"a,b\", [\",\"], []), [\"b\"]);\n" + " llOwnerSay((string)(at + none + parsed) + llDumpList2String(part + all + rest + wrapped + held, \"\"));\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("replaced in place, in Luau's numeric for"); CHECK(has(r, "for i = 0, #l - 1 do\n l[i + 1] = l[i + 1] * 2\n")); { - INFO("a list held elsewhere, replaced as LSL's: " + r.text); + INFO("a list held elsewhere, replaced as LSL's"); CHECK(has(r, "for j = 0, #held - 1 do\n")); CHECK(has(r, " held = llcompat.ListReplaceList(held, { j }, j, j)\n")); } { - INFO("a global a call of the script's could change: " + r.text); + INFO("a global a call of the script's could change"); CHECK(has(r, "while k < #gL do")); CHECK(has(r, "gL = llcompat.ListReplaceList(gL, { bump() }, k, k)")); } - UNSCOPED_INFO("the last of a list that could be empty: " + r.text); + UNSCOPED_INFO("the last of a list that could be empty"); CHECK(has(r, "l = llcompat.ListReplaceList(l, { 7 }, -1, -1)")); - UNSCOPED_INFO("on the end: " + r.text); + UNSCOPED_INFO("on the end"); CHECK(has(r, "table.insert(l, 9)")); { - INFO("a part, and all of it: " + r.text); + INFO("a part, and all of it"); CHECK(has(r, "local part = table.move(l, 2, 3, 1, {})")); CHECK(has(r, "local all = table.clone(l)")); } { - INFO("to the end, and the part left out, as LSL's: " + r.text); + INFO("to the end, and the part left out, as LSL's"); CHECK(has(r, "local rest = llcompat.List2List(l, 2, -1)")); CHECK(has(r, "local wrapped = llcompat.List2List(l, 3, 1)")); } { - INFO("whole numbers joined: " + r.text); + INFO("whole numbers joined"); CHECK(has(r, "print(table.concat(l, \",\"))")); CHECK(has(r, "print(table.concat(l))")); } - UNSCOPED_INFO("and text: " + r.text); + UNSCOPED_INFO("and text"); CHECK(has(r, "print(table.concat(table.extend(table.clone(gNames), { n }), \", \"))")); - UNSCOPED_INFO("a float as LSL writes one: " + r.text); + UNSCOPED_INFO("a float as LSL writes one"); CHECK(has(r, "print(ll.DumpList2String(table.extend(table.clone(l), { 1.5 }), \",\"))")); - UNSCOPED_INFO("found, its place: " + r.text); + UNSCOPED_INFO("found, its place"); CHECK(has(r, "local at = (table.find(gNames, \"b\") or 0) - 1")); { - INFO("NULL_KEY as LSL's string, as its place and found: " + r.text); + INFO("NULL_KEY as LSL's string, as its place and found"); CHECK(has(r, "local none = (table.find(gNames, tostring(NULL_KEY)) or 0) - 1")); CHECK(has(r, "if table.find(gNames, tostring(NULL_KEY)) ~= nil then")); } - UNSCOPED_INFO("in a library call's list: " + r.text); + UNSCOPED_INFO("in a library call's list"); CHECK(has(r, "local parsed = (table.find(ll.ParseString2List(\"a,b\", { \",\" }, {}) :: { any }, \"b\") or 0) - 1")); - UNSCOPED_INFO("no llcompat but those: " + r.text); + UNSCOPED_INFO("no llcompat but those"); CHECK(count(r, "llcompat.") == 5); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: llSubStringIndex asked only whether it found is a plain string.find", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " string s = llGetObjectName(); key k = llGetOwner();\n" - " if (llSubStringIndex(s, \"a\") != -1) llOwnerSay(\"a\");\n" - " if (llSubStringIndex(s, \"b\") >= 0) llOwnerSay(\"b\");\n" - " if (llSubStringIndex(s, \"c\") > -1) llOwnerSay(\"c\");\n" - " if (llSubStringIndex(s, \"d\") == -1) llOwnerSay(\"no d\");\n" - " if (!~llSubStringIndex(s, \"e\")) llOwnerSay(\"no e\");\n" - " if (~llSubStringIndex(s, \".\")) llOwnerSay(\"dot\");\n" - " if (~llSubStringIndex(s, k)) llOwnerSay(\"owner\");\n" - " if (~llSubStringIndex(s, \"\")) llOwnerSay(\"empty\");\n" - " integer at = llSubStringIndex(s, \"x\");\n" - " llOwnerSay((string)at);\n" - "} }\n"); - { - INFO("found: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { touch_start(integer n) {\n" + " string s = llGetObjectName(); key k = llGetOwner();\n" + " if (llSubStringIndex(s, \"a\") != -1) llOwnerSay(\"a\");\n" + " if (llSubStringIndex(s, \"b\") >= 0) llOwnerSay(\"b\");\n" + " if (llSubStringIndex(s, \"c\") > -1) llOwnerSay(\"c\");\n" + " if (llSubStringIndex(s, \"d\") == -1) llOwnerSay(\"no d\");\n" + " if (!~llSubStringIndex(s, \"e\")) llOwnerSay(\"no e\");\n" + " if (~llSubStringIndex(s, \".\")) llOwnerSay(\"dot\");\n" + " if (~llSubStringIndex(s, k)) llOwnerSay(\"owner\");\n" + " if (~llSubStringIndex(s, \"\")) llOwnerSay(\"empty\");\n" + " integer at = llSubStringIndex(s, \"x\");\n" + " llOwnerSay((string)at);\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("found"); CHECK(has(r, "if string.find(s, \"a\", 1, true) ~= nil then")); CHECK(has(r, "if string.find(s, \"b\", 1, true) ~= nil then")); CHECK(has(r, "if string.find(s, \"c\", 1, true) ~= nil then")); } { - INFO("not: " + r.text); + INFO("not"); CHECK(has(r, "if string.find(s, \"d\", 1, true) == nil then")); CHECK(has(r, "if not (string.find(s, \"e\", 1, true) ~= nil) then")); } - UNSCOPED_INFO("plain, not a pattern: " + r.text); + UNSCOPED_INFO("plain, not a pattern"); CHECK(has(r, "if string.find(s, \".\", 1, true) ~= nil then")); - UNSCOPED_INFO("a key as its text: " + r.text); + UNSCOPED_INFO("a key as its text"); CHECK(has(r, "if string.find(s, tostring(k), 1, true) ~= nil then")); - UNSCOPED_INFO("the empty text: " + r.text); + UNSCOPED_INFO("the empty text"); CHECK(has(r, "if string.find(s, \"\", 1, true) ~= nil then")); - UNSCOPED_INFO("its index in characters, ll's: " + r.text); + UNSCOPED_INFO("its index in characters, ll's"); CHECK(has(r, "local at = (ll.SubStringIndex(s, \"x\") or 0) - 1")); - UNSCOPED_INFO("found by string.find alone: " + r.text); + UNSCOPED_INFO("found by string.find alone"); CHECK(count(r, "SubStringIndex") == 1); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a script that resets its time keeps its own timeBase clock; else ll's", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("float timeBase;\n" - "default {\n" - " state_entry() { llResetTime(); state running; }\n" - "}\n" - "state running {\n" - " state_entry() { timeBase = llGetTime(); }\n" - " touch_start(integer n) {\n" - " float lap = llGetAndResetTime();\n" - " llGetAndResetTime();\n" - " llOwnerSay((string)(lap + timeBase) + \" \" + (string)(llGetTime() * 2));\n" - " }\n" - "}\n"); - { - INFO("the clock, once, over everything: " + r.text); + const ALLSLToSLua::Result r = require_converted("float timeBase;\n" + "default {\n" + " state_entry() { llResetTime(); state running; }\n" + "}\n" + "state running {\n" + " state_entry() { timeBase = llGetTime(); }\n" + " touch_start(integer n) {\n" + " float lap = llGetAndResetTime();\n" + " llGetAndResetTime();\n" + " llOwnerSay((string)(lap + timeBase) + \" \" + (string)(llGetTime() * 2));\n" + " }\n" + "}\n"); + CAPTURE(r.text); + { + INFO("the clock, once, over everything"); CHECK(count(r, "local timeBase = ll.GetTime()") == 1); CHECK(r.text.find("local timeBase = ll.GetTime()") < r.text.find("local states")); } { - INFO("the script's own name made another: " + r.text); + INFO("the script's own name made another"); CHECK(has(r, "local timeBase_ = 0")); CHECK(has(r, "timeBase_ = ll.GetTime() - timeBase")); } - UNSCOPED_INFO("a reset: " + r.text); + UNSCOPED_INFO("a reset"); CHECK(has(r, "state_entry = function()\n timeBase = ll.GetTime()\n setState(\"running\")")); { - INFO("both, as a value, through the helper: " + r.text); + INFO("both, as a value, through the helper"); CHECK(has(r, "local lap = getAndResetTime()")); CHECK(count(r, "local function getAndResetTime(): number") == 1); } - UNSCOPED_INFO("both where nothing reads the time, the reset alone: " + r.text); + UNSCOPED_INFO("both where nothing reads the time, the reset alone"); CHECK(has(r, "local lap = getAndResetTime()\n timeBase = ll.GetTime()\n")); - UNSCOPED_INFO("a read bracketed: " + r.text); + UNSCOPED_INFO("a read bracketed"); CHECK(has(r, "(ll.GetTime() - timeBase) * 2")); { - INFO("nothing of llcompat's: " + r.text); + INFO("nothing of llcompat's"); CHECK_FALSE(has(r, "llcompat.ResetTime")); CHECK_FALSE(has(r, "llcompat.GetAndResetTime")); CHECK_FALSE(noted(r, "SluaCompatOnly", "ResetTime")); CHECK_FALSE(noted(r, "SluaCompatOnly", "GetAndResetTime")); } - checksClean(r); + require_checks_clean(r); - const ALLSLToSLua::Result reads = convert("default { touch_start(integer n) { llOwnerSay((string)llGetTime()); } }\n"); + const ALLSLToSLua::Result reads = require_converted("default { touch_start(integer n) { llOwnerSay((string)llGetTime()); } }\n"); + CAPTURE(reads.text); { - INFO("only read, ll's: " + reads.text); + INFO("only read, ll's"); CHECK(has(reads, "ll.GetTime()")); CHECK_FALSE(has(reads, "timeBase")); } @@ -2862,55 +2890,58 @@ namespace " llResetTime();\n" "} }\n", ALLSLToSLua::Options::closeToLSL()); + CAPTURE(close.text); { - INFO("close to LSL, llcompat's together: " + close.text); + INFO("close to LSL, llcompat's together"); CHECK(has(close, "llcompat.GetTime()")); CHECK(has(close, "llcompat.ResetTime()")); CHECK_FALSE(has(close, "ll.GetTime")); CHECK_FALSE(has(close, "timeBase")); } - checksClean(close); + require_checks_clean(close); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: Base64 through llbase64 with decode noted once; ll's close to LSL", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " key k = llDetectedKey(0);\n" - " string s = llStringToBase64(\"hello\");\n" - " string t = llStringToBase64(k);\n" - " string d = llBase64ToString(s);\n" - " string e = llBase64ToString(t + \"=\");\n" - " llOwnerSay(s + t + d + e);\n" - "} }\n"); - { - INFO("encode: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { touch_start(integer n) {\n" + " key k = llDetectedKey(0);\n" + " string s = llStringToBase64(\"hello\");\n" + " string t = llStringToBase64(k);\n" + " string d = llBase64ToString(s);\n" + " string e = llBase64ToString(t + \"=\");\n" + " llOwnerSay(s + t + d + e);\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("encode"); CHECK(has(r, "local s = llbase64.encode(\"hello\")")); CHECK(has(r, "local t = llbase64.encode(tostring(k))")); } { - INFO("decode: " + r.text); + INFO("decode"); CHECK(has(r, "local d = llbase64.decode(s)")); CHECK(has(r, "local e = llbase64.decode(t .. \"=\")")); } { - INFO("decode noted once: " + r.text); + INFO("decode noted once"); CHECK(noted(r, "SluaBase64Decode")); CHECK(count(r, "-- LSL: llbase64.decode keeps every byte") == 1); } { - INFO("nothing of llcompat's or ll's: " + r.text); + INFO("nothing of llcompat's or ll's"); CHECK_FALSE(has(r, "llcompat.")); CHECK_FALSE(has(r, "ll.StringToBase64")); CHECK_FALSE(has(r, "ll.Base64ToString")); CHECK_FALSE(noted(r, "SluaDeprecatedFor", "StringToBase64")); CHECK_FALSE(noted(r, "SluaDeprecatedFor", "Base64ToString")); } - checksClean(r); + require_checks_clean(r); const ALLSLToSLua::Result close = ALLSLToSLua::convert("default { state_entry() { llOwnerSay(llBase64ToString(llStringToBase64(\"x\"))); } }\n", ALLSLToSLua::Options::closeToLSL()); + CAPTURE(close.text); { - INFO("close to LSL: " + close.text); + INFO("close to LSL"); CHECK(has(close, "ll.Base64ToString(ll.StringToBase64(\"x\"))")); CHECK_FALSE(has(close, "llcompat.")); CHECK_FALSE(has(close, "llbase64.decode(")); @@ -2921,32 +2952,34 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: llAdjustDamage in on_damage as detected adjustDamage; llcompat elsewhere", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("halve() { llAdjustDamage(0, 2.0); }\n" - "default {\n" - " on_damage(integer n) {\n" - " llAdjustDamage(0, 0.5);\n" - " integer i;\n" - " for (i = 1; i < n; ++i) llAdjustDamage(i, 5);\n" - " if (n > 3) halve();\n" - " }\n" - " touch_start(integer n) { llAdjustDamage(0, 1.0); }\n" - "}\n"); - { - INFO("on_damage's own: " + r.text); + const ALLSLToSLua::Result r = require_converted("halve() { llAdjustDamage(0, 2.0); }\n" + "default {\n" + " on_damage(integer n) {\n" + " llAdjustDamage(0, 0.5);\n" + " integer i;\n" + " for (i = 1; i < n; ++i) llAdjustDamage(i, 5);\n" + " if (n > 3) halve();\n" + " }\n" + " touch_start(integer n) { llAdjustDamage(0, 1.0); }\n" + "}\n"); + CAPTURE(r.text); + { + INFO("on_damage's own"); CHECK(has(r, "detected[1]:adjustDamage(0.5)")); CHECK(has(r, "detected[i + 1]:adjustDamage(5)")); CHECK(noted(r, "SluaDetectedTable")); } - UNSCOPED_INFO("a function's, llcompat's: " + r.text); + UNSCOPED_INFO("a function's, llcompat's"); CHECK(has(r, "llcompat.AdjustDamage(0, 2.0)")); - UNSCOPED_INFO("another event's, llcompat's: " + r.text); + UNSCOPED_INFO("another event's, llcompat's"); CHECK(has(r, "llcompat.AdjustDamage(0, 1.0)")); - checksClean(r); + require_checks_clean(r); const ALLSLToSLua::Result close = ALLSLToSLua::convert("default { on_damage(integer n) { llAdjustDamage(0, 0.5); } }\n", ALLSLToSLua::Options::closeToLSL()); + CAPTURE(close.text); { - INFO("close to LSL, llcompat's: " + close.text); + INFO("close to LSL, llcompat's"); CHECK(has(close, "llcompat.AdjustDamage(0, 0.5)")); CHECK_FALSE(has(close, "adjustDamage(")); } @@ -2954,88 +2987,94 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a timer every handler turns off first is LLTimers:once; else :every", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default {\n" - " state_entry() { llSetTimerEvent(5.0); }\n" - " timer() {\n" - " llSetTimerEvent(0.0);\n" - " llOwnerSay(\"once\");\n" - " if (llFrand(1.0) < 0.5) llSetTimerEvent(2);\n" - " }\n" - " touch_start(integer n) { llSetTimerEvent(1); }\n" - "}\n"); - { - INFO("once: " + r.text); + const ALLSLToSLua::Result r = require_converted("default {\n" + " state_entry() { llSetTimerEvent(5.0); }\n" + " timer() {\n" + " llSetTimerEvent(0.0);\n" + " llOwnerSay(\"once\");\n" + " if (llFrand(1.0) < 0.5) llSetTimerEvent(2);\n" + " }\n" + " touch_start(integer n) { llSetTimerEvent(1); }\n" + "}\n"); + CAPTURE(r.text); + { + INFO("once"); CHECK(has(r, "timerHandle = LLTimers:once(seconds, function()")); CHECK_FALSE(has(r, "LLTimers:every(seconds, function")); } - UNSCOPED_INFO("said so: " + r.text); + UNSCOPED_INFO("said so"); CHECK(has(r, "-- time it is set, as its handler turns it off before anything else.")); { - INFO("the handler's own off kept, and set again: " + r.text); + INFO("the handler's own off kept, and set again"); CHECK(has(r, "function timerHandler()\n setTimer(0.0)\n")); CHECK(has(r, "setTimer(2)")); CHECK(has(r, "setTimer(1)")); } - checksClean(r); + require_checks_clean(r); // Tapple's: the local the preamble declared, assigned by a function // statement of its name, which types as it is declared. ALLSLToSLua::Options typed; typed.types = true; const ALLSLToSLua::Result types = ALLSLToSLua::convert("default { state_entry() { llSetTimerEvent(5); } timer() { llOwnerSay(\"t\"); } }\n", typed); + CAPTURE(types.text); { - INFO("a function statement, typed: " + types.text); + INFO("a function statement, typed"); CHECK(has(types, "local timerHandler: (() -> ())? = nil\n")); CHECK(has(types, "\nfunction timerHandler()\n print(\"t\")\nend\n")); CHECK_FALSE(has(types, "timerHandler = function")); } - checksClean(types); + require_checks_clean(types); - const ALLSLToSLua::Result states = convert("default {\n" - " state_entry() { llSetTimerEvent(1); }\n" - " timer() { llSetTimerEvent(0); state two; }\n" - "}\n" - "state two {\n" - " timer() { llSetTimerEvent(FALSE); llOwnerSay(\"two\"); }\n" - " touch_start(integer n) { llSetTimerEvent(3); }\n" - "}\n"); + const ALLSLToSLua::Result states = require_converted("default {\n" + " state_entry() { llSetTimerEvent(1); }\n" + " timer() { llSetTimerEvent(0); state two; }\n" + "}\n" + "state two {\n" + " timer() { llSetTimerEvent(FALSE); llOwnerSay(\"two\"); }\n" + " touch_start(integer n) { llSetTimerEvent(3); }\n" + "}\n"); + CAPTURE(states.text); { - INFO("every state's, once: " + states.text); + INFO("every state's, once"); CHECK(has(states, "timerHandle = LLTimers:once(seconds, function")); CHECK(has(states, "states[currentState].timer")); } - checksClean(states); + require_checks_clean(states); - const ALLSLToSLua::Result without = convert("default {\n" - " state_entry() { llSetTimerEvent(1); state two; }\n" - " timer() { llSetTimerEvent(0); }\n" - "}\n" - "state two {\n" - " touch_start(integer n) { state default; }\n" - "}\n"); + const ALLSLToSLua::Result without = require_converted("default {\n" + " state_entry() { llSetTimerEvent(1); state two; }\n" + " timer() { llSetTimerEvent(0); }\n" + "}\n" + "state two {\n" + " touch_start(integer n) { state default; }\n" + "}\n"); + CAPTURE(without.text); { - INFO("a state with none, every: " + without.text); + INFO("a state with none, every"); CHECK(has(without, "timerHandle = LLTimers:every(seconds, function")); CHECK_FALSE(has(without, "LLTimers:once(seconds, function")); } - const ALLSLToSLua::Result later = convert("default {\n" - " state_entry() { llSetTimerEvent(1); }\n" - " timer() { llOwnerSay(\"tick\"); llSetTimerEvent(0); }\n" - "}\n"); + const ALLSLToSLua::Result later = require_converted("default {\n" + " state_entry() { llSetTimerEvent(1); }\n" + " timer() { llOwnerSay(\"tick\"); llSetTimerEvent(0); }\n" + "}\n"); + CAPTURE(later.text); { - INFO("off after something else, every: " + later.text); + INFO("off after something else, every"); CHECK(has(later, "timerHandle = LLTimers:every(seconds, function")); CHECK_FALSE(has(later, "LLTimers:once(seconds, function")); } - const ALLSLToSLua::Result again = convert("float gNext = 0.0;\n" - "default {\n" - " state_entry() { llSetTimerEvent(1); }\n" - " timer() { llSetTimerEvent(gNext); }\n" - " touch_start(integer n) { gNext = 2.0; }\n" - "}\n"); + const ALLSLToSLua::Result again = require_converted("float gNext = 0.0;\n" + "default {\n" + " state_entry() { llSetTimerEvent(1); }\n" + " timer() { llSetTimerEvent(gNext); }\n" + " touch_start(integer n) { gNext = 2.0; }\n" + "}\n"); + CAPTURE(again.text); { - INFO("set to what is not written nought, every: " + again.text); + INFO("set to what is not written nought, every"); CHECK(has(again, "timerHandle = LLTimers:every(seconds, function")); CHECK_FALSE(has(again, "LLTimers:once(seconds, function")); } @@ -3055,22 +3094,23 @@ namespace " f = f / 2;\n" " llOwnerSay((string)(q + m + sum + back + cut + half + a + b) + (string)f);\n" "} }\n"; - const ALLSLToSLua::Result r = convert(script); + const ALLSLToSLua::Result r = require_converted(script); + CAPTURE(r.text); { - INFO("toward nought: " + r.text); + INFO("toward nought"); CHECK(has(r, "local q = bit32.s32(a / b)")); CHECK(has(r, "local m = math.fmod(a, b)")); } - UNSCOPED_INFO("bracketed as / binds: " + r.text); + UNSCOPED_INFO("bracketed as / binds"); CHECK(has(r, "bit32.s32((a + 1) / (b - 2)) + math.fmod(a + 1, b - 2)")); - UNSCOPED_INFO("a side below nought: " + r.text); + UNSCOPED_INFO("a side below nought"); CHECK(has(r, "bit32.s32(-7 / 2) + math.fmod(7, -2)")); - UNSCOPED_INFO("neither below nought: " + r.text); + UNSCOPED_INFO("neither below nought"); CHECK(has(r, "gSize // 4 + gSize % 3 + bit32.band(n, 255) // 16 + bit32.band(n, 255) % gSize")); - UNSCOPED_INFO("one known, not the other: " + r.text); + UNSCOPED_INFO("one known, not the other"); CHECK(has(r, "local half = bit32.s32(a / gSize)")); { - INFO("compound ones whole: " + r.text); + INFO("compound ones whole"); CHECK(has(r, "a = bit32.s32(a / b)")); CHECK(has(r, "a = math.fmod(a, b)")); CHECK(has(r, "b = bit32.s32(b / 2)")); @@ -3078,25 +3118,23 @@ namespace CHECK_FALSE(has(r, "//=")); CHECK_FALSE(has(r, "%=")); } - UNSCOPED_INFO("a float's: " + r.text); + UNSCOPED_INFO("a float's"); CHECK(has(r, "f /= 2")); { - INFO("unnoted: " + r.text); + INFO("unnoted"); CHECK_FALSE(noted(r, "SluaIntegerDivision")); CHECK_FALSE(noted(r, "SluaModulo")); } - checksClean(r); + require_checks_clean(r); // Typed, each one a number to the checker, strict under the grid's // solver: math.fmod's one answer and bit32.s32's. ALLSLToSLua::Options options; options.types = true; const ALLSLToSLua::Result typed = ALLSLToSLua::convert(script, options); - { - INFO("typed: " + typed.text); - CHECK(has(typed, "local q: number = bit32.s32(a / b)")); - CHECK(has(typed, "local m: number = math.fmod(a, b)")); - } - checksClean(typed); + CAPTURE(typed.text); + CHECK(has(typed, "local q: number = bit32.s32(a / b)")); + CHECK(has(typed, "local m: number = math.fmod(a, b)")); + require_checks_clean(typed); if (!newSolver) { std::string said; @@ -3104,46 +3142,47 @@ namespace { said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); } - UNSCOPED_INFO("strict:\n" + said + "---\n" + typed.text); - REQUIRE(said.empty()); + CAPTURE(said); + CHECK(said.empty()); } } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: keys from empty or capital text noted; the XorBase64Strings pair noted", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " key a = (key)\"\"; key c = (key)\"A2E76FCD-9360-4F6D-A924-938F923DF11D\";\n" - " if ((key)\"\" == NULL_KEY) llOwnerSay(\"same\");\n" - " string s = llGetObjectDesc(); key d = (key)s;\n" - " llOwnerSay((string)a + (string)c + (string)d + llXorBase64(\"a\", \"b\") + llXorBase64Strings(\"a\", \"b\") +\n" - " llXorBase64StringsCorrect(\"a\", \"b\"));\n" - "} }\n"); - { - INFO("no text, NULL_KEY: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { touch_start(integer n) {\n" + " key a = (key)\"\"; key c = (key)\"A2E76FCD-9360-4F6D-A924-938F923DF11D\";\n" + " if ((key)\"\" == NULL_KEY) llOwnerSay(\"same\");\n" + " string s = llGetObjectDesc(); key d = (key)s;\n" + " llOwnerSay((string)a + (string)c + (string)d + llXorBase64(\"a\", \"b\") + llXorBase64Strings(\"a\", \"b\") +\n" + " llXorBase64StringsCorrect(\"a\", \"b\"));\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("no text, NULL_KEY"); CHECK(has(r, "local a = uuid(\"\")")); CHECK(noted(r, "SluaKeyEmpty")); CHECK(has(r, "-- LSL: SLua's uuid of no text is NULL_KEY")); } { - INFO("capitals, small: " + r.text); + INFO("capitals, small"); CHECK(has(r, "uuid(\"A2E76FCD-9360-4F6D-A924-938F923DF11D\")")); CHECK(noted(r, "SluaKeyCase")); } { - INFO("other text as it was: " + r.text); + INFO("other text as it was"); CHECK(has(r, "local d = uuid(s)")); CHECK(noted(r, "SluaUuid")); } - UNSCOPED_INFO("ll.XorBase64 as it is: " + r.text); + UNSCOPED_INFO("ll.XorBase64 as it is"); CHECK(has(r, "ll.XorBase64(\"a\", \"b\")")); { - INFO("the two, LSL's: " + r.text); + INFO("the two, LSL's"); CHECK(has(r, "ll.XorBase64Strings(\"a\", \"b\")")); CHECK(has(r, "ll.XorBase64StringsCorrect(\"a\", \"b\")")); CHECK_FALSE(has(r, "llcompat.")); } { - INFO("said what ll.XorBase64 is not: " + r.text); + INFO("said what ll.XorBase64 is not"); CHECK(noted(r, "SluaXorBase64Wrong")); CHECK(noted(r, "SluaXorBase64Nul")); CHECK_FALSE(noted(r, "SluaDeprecated", "XorBase64Strings")); @@ -3153,65 +3192,67 @@ namespace { if (p.key == "SluaXorBase64Wrong" || p.key == "SluaXorBase64Nul") { - UNSCOPED_INFO("named: " + p.message); + CAPTURE(p.message); REQUIRE(p.message.find("for ll.XorBase64,") != std::string::npos); } } - checksClean(r); + require_checks_clean(r); // Given rather than cast: no text kept as text, as other text that is // no UUID is; capitals the same said, and a UUID written small // nothing at all. - const ALLSLToSLua::Result given = convert("default { touch_start(integer n) {\n" - " key b = \"\"; key c = \"A2E76FCD-9360-4F6D-A924-938F923DF11D\";\n" - " key l = \"a2e76fcd-9360-4f6d-a924-938f923df11d\";\n" - " llOwnerSay((string)llGetOwnerKey(b) + (string)llGetOwnerKey(c) + (string)llGetOwnerKey(l));\n" - "} }\n"); - { - INFO("given no text, text: " + given.text); + const ALLSLToSLua::Result given = require_converted("default { touch_start(integer n) {\n" + " key b = \"\"; key c = \"A2E76FCD-9360-4F6D-A924-938F923DF11D\";\n" + " key l = \"a2e76fcd-9360-4f6d-a924-938f923df11d\";\n" + " llOwnerSay((string)llGetOwnerKey(b) + (string)llGetOwnerKey(c) + (string)llGetOwnerKey(l));\n" + "} }\n"); + CAPTURE(given.text); + { + INFO("given no text, text"); CHECK(has(given, "local b = \"\"")); CHECK(has(given, "ll.GetOwnerKey(b :: any)")); CHECK_FALSE(noted(given, "SluaKeyEmpty")); CHECK(noted(given, "SluaKeyCase")); } { - INFO("nothing else said of them: " + given.text); + INFO("nothing else said of them"); CHECK_FALSE(noted(given, "SluaKeyText")); CHECK_FALSE(noted(given, "SluaUuidText")); } - checksClean(given); + require_checks_clean(given); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a particle rule list written out as a ParticleParams table", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer gFlags = 3;\n" - "default { touch_start(integer n) {\n" - " llParticleSystem([\n" - " PSYS_PART_FLAGS, PSYS_PART_EMISSIVE_MASK | PSYS_PART_INTERP_COLOR_MASK,\n" - " PSYS_PART_START_COLOR, <1, 0, 0>, PSYS_PART_START_ALPHA, 1,\n" - " PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE,\n" - " PSYS_SRC_TEXTURE, NULL_KEY, PSYS_SRC_TARGET_KEY, llGetOwner()\n" - " ]);\n" - " llLinkParticleSystem(LINK_SET, [PSYS_PART_FLAGS, gFlags, PSYS_SRC_BURST_RATE, 0.1]);\n" - " llLinkParticleSystem(2, [PSYS_PART_FLAGS, PSYS_PART_BOUNCE_MASK]);\n" - " llParticleSystem([]);\n" - "} }\n"); - UNSCOPED_INFO("a key to a line: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer gFlags = 3;\n" + "default { touch_start(integer n) {\n" + " llParticleSystem([\n" + " PSYS_PART_FLAGS, PSYS_PART_EMISSIVE_MASK | PSYS_PART_INTERP_COLOR_MASK,\n" + " PSYS_PART_START_COLOR, <1, 0, 0>, PSYS_PART_START_ALPHA, 1,\n" + " PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE,\n" + " PSYS_SRC_TEXTURE, NULL_KEY, PSYS_SRC_TARGET_KEY, llGetOwner()\n" + " ]);\n" + " llLinkParticleSystem(LINK_SET, [PSYS_PART_FLAGS, gFlags, PSYS_SRC_BURST_RATE, 0.1]);\n" + " llLinkParticleSystem(2, [PSYS_PART_FLAGS, PSYS_PART_BOUNCE_MASK]);\n" + " llParticleSystem([]);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a key to a line"); CHECK(has(r, " ll.ParticleSystem({\n" - " emissive = true,\n" - " color_interp = true,\n" - " color_begin = vector(1, 0, 0),\n" - " alpha_begin = 1,\n" - " pattern = PSYS_SRC_PATTERN_EXPLODE,\n" - " texture = tostring(NULL_KEY),\n" - " target_key = ll.GetOwner(),\n" - " })\n")); - { - INFO("on one line as written; flags not of masks as they are: " + r.text); + " emissive = true,\n" + " color_interp = true,\n" + " color_begin = vector(1, 0, 0),\n" + " alpha_begin = 1,\n" + " pattern = PSYS_SRC_PATTERN_EXPLODE,\n" + " texture = tostring(NULL_KEY),\n" + " target_key = ll.GetOwner(),\n" + " })\n")); + { + INFO("on one line as written; flags not of masks as they are"); CHECK(has(r, "ll.LinkParticleSystem(LINK_SET, { flags = gFlags, burst_rate = 0.1 })")); CHECK(has(r, "ll.LinkParticleSystem(2, { bounce = true })")); } - UNSCOPED_INFO("no rules, as they were: " + r.text); + UNSCOPED_INFO("no rules, as they were"); CHECK(has(r, "ll.ParticleSystem({})")); const auto same = [&r](const std::string& lsl, const std::string& slua) { return std::find(r.same.begin(), r.same.end(), std::make_pair(lsl, slua)) != r.same.end(); @@ -3222,102 +3263,106 @@ namespace CHECK(same("PSYS_PART_EMISSIVE_MASK", "emissive")); CHECK(same("PSYS_SRC_BURST_RATE", "burst_rate")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a media rule list as MediaParams with booleans and whitelist pieces", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " integer on = llGetLinkNumber();\n" - " llSetPrimMediaParams(0, [PRIM_MEDIA_AUTO_PLAY, TRUE, PRIM_MEDIA_AUTO_LOOP, 0, PRIM_MEDIA_CURRENT_URL, \"http://a.com\",\n" - " PRIM_MEDIA_WIDTH_PIXELS, 512, PRIM_MEDIA_WHITELIST_ENABLE, 1, PRIM_MEDIA_WHITELIST, \"a.com,*.b.com,\"]);\n" - " if (llSetLinkMedia(LINK_THIS, 1, [PRIM_MEDIA_HOME_URL, \"http://h\"]) == STATUS_OK) llOwnerSay(\"ok\");\n" - " llSetPrimMediaParams(1, [PRIM_MEDIA_WHITELIST, \",a.com\"]);\n" - " llSetPrimMediaParams(2, [PRIM_MEDIA_WHITELIST, \"a\\\\,b\"]);\n" - " llSetPrimMediaParams(3, [PRIM_MEDIA_WHITELIST, \"\"]);\n" - " llSetPrimMediaParams(4, [PRIM_MEDIA_AUTO_PLAY, on]);\n" - " llSetPrimMediaParams(5, [PRIM_MEDIA_AUTO_ZOOM, 2]);\n" - "} }\n"); - UNSCOPED_INFO("a table: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { touch_start(integer n) {\n" + " integer on = llGetLinkNumber();\n" + " llSetPrimMediaParams(0, [PRIM_MEDIA_AUTO_PLAY, TRUE, PRIM_MEDIA_AUTO_LOOP, 0, PRIM_MEDIA_CURRENT_URL, \"http://a.com\",\n" + " PRIM_MEDIA_WIDTH_PIXELS, 512, PRIM_MEDIA_WHITELIST_ENABLE, 1, PRIM_MEDIA_WHITELIST, \"a.com,*.b.com,\"]);\n" + " if (llSetLinkMedia(LINK_THIS, 1, [PRIM_MEDIA_HOME_URL, \"http://h\"]) == STATUS_OK) llOwnerSay(\"ok\");\n" + " llSetPrimMediaParams(1, [PRIM_MEDIA_WHITELIST, \",a.com\"]);\n" + " llSetPrimMediaParams(2, [PRIM_MEDIA_WHITELIST, \"a\\\\,b\"]);\n" + " llSetPrimMediaParams(3, [PRIM_MEDIA_WHITELIST, \"\"]);\n" + " llSetPrimMediaParams(4, [PRIM_MEDIA_AUTO_PLAY, on]);\n" + " llSetPrimMediaParams(5, [PRIM_MEDIA_AUTO_ZOOM, 2]);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a table"); CHECK(has(r, " ll.SetPrimMediaParams(0, {\n" - " auto_play = true,\n" - " auto_loop = false,\n" - " current_url = \"http://a.com\",\n" - " width = 512,\n" - " whitelist_enable = true,\n" - " whitelist = { \"a.com\", \"*.b.com\", \"\" },\n" - " })\n")); - UNSCOPED_INFO("what it answers read: " + r.text); + " auto_play = true,\n" + " auto_loop = false,\n" + " current_url = \"http://a.com\",\n" + " width = 512,\n" + " whitelist_enable = true,\n" + " whitelist = { \"a.com\", \"*.b.com\", \"\" },\n" + " })\n")); + UNSCOPED_INFO("what it answers read"); CHECK(has(r, "if ll.SetLinkMedia(LINK_THIS, 1, { home_url = \"http://h\" }) == STATUS_OK then")); // The join leaves a first piece of none out, escapes a \, and sends // no rule at all for no text. { - INFO("a whitelist the join would change, a list: " + r.text); + INFO("a whitelist the join would change, a list"); CHECK(has(r, "ll.SetPrimMediaParams(1, { PRIM_MEDIA_WHITELIST, \",a.com\" })")); CHECK(has(r, "ll.SetPrimMediaParams(2, { PRIM_MEDIA_WHITELIST, \"a")); CHECK(has(r, "ll.SetPrimMediaParams(3, { PRIM_MEDIA_WHITELIST, \"\" })")); } { - INFO("a truth not written out as one, a list: " + r.text); + INFO("a truth not written out as one, a list"); CHECK(has(r, "ll.SetPrimMediaParams(4, { PRIM_MEDIA_AUTO_PLAY, on })")); CHECK(has(r, "ll.SetPrimMediaParams(5, { PRIM_MEDIA_AUTO_ZOOM, 2 })")); } - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: an HTTP rule list as HttpRequestParams with headers by name", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " string token = llGetObjectDesc();\n" - " llHTTPRequest(\"https://x\", [HTTP_METHOD, \"POST\", HTTP_MIMETYPE, \"application/json\", HTTP_VERIFY_CERT, FALSE,\n" - " HTTP_CUSTOM_HEADER, \"X-B\", token, HTTP_BODY_MAXLENGTH, 16384, HTTP_CUSTOM_HEADER, \"X-A\", \"two\",\n" - " HTTP_ACCEPT, \"application/json\", HTTP_ACCEPT, \"text/plain\"], \"{}\");\n" - "} }\n"); - UNSCOPED_INFO("a table: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { touch_start(integer n) {\n" + " string token = llGetObjectDesc();\n" + " llHTTPRequest(\"https://x\", [HTTP_METHOD, \"POST\", HTTP_MIMETYPE, \"application/json\", HTTP_VERIFY_CERT, FALSE,\n" + " HTTP_CUSTOM_HEADER, \"X-B\", token, HTTP_BODY_MAXLENGTH, 16384, HTTP_CUSTOM_HEADER, \"X-A\", \"two\",\n" + " HTTP_ACCEPT, \"application/json\", HTTP_ACCEPT, \"text/plain\"], \"{}\");\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("a table"); CHECK(has(r, " ll.HTTPRequest(\"https://x\", {\n" - " method = \"POST\",\n" - " mimetype = \"application/json\",\n" - " verify_cert = false,\n" - " custom_header = { [\"X-B\"] = token, [\"X-A\"] = \"two\" },\n" - " max_body_length = 16384,\n" - " accept = { \"application/json\", \"text/plain\" },\n" - " }, \"{}\")\n")); - { - INFO("the headers' order said: " + r.text); + " method = \"POST\",\n" + " mimetype = \"application/json\",\n" + " verify_cert = false,\n" + " custom_header = { [\"X-B\"] = token, [\"X-A\"] = \"two\" },\n" + " max_body_length = 16384,\n" + " accept = { \"application/json\", \"text/plain\" },\n" + " }, \"{}\")\n")); + { + INFO("the headers' order said"); CHECK(noted(r, "SluaHeaderOrder")); CHECK(has(r, "-- LSL: SLua passes ll.HTTPRequest a custom_header table's headers sorted by name")); } - checksClean(r); + require_checks_clean(r); - const ALLSLToSLua::Result sorted = convert("default { touch_start(integer n) {\n" - " key id = llHTTPRequest(\"https://y\", [HTTP_CUSTOM_HEADER, \"A\", \"1\", HTTP_CUSTOM_HEADER, \"B\", \"2\"], \"\");\n" - " llOwnerSay((string)id);\n" - "} }\n"); + const ALLSLToSLua::Result sorted = require_converted("default { touch_start(integer n) {\n" + " key id = llHTTPRequest(\"https://y\", [HTTP_CUSTOM_HEADER, \"A\", \"1\", HTTP_CUSTOM_HEADER, \"B\", \"2\"], \"\");\n" + " llOwnerSay((string)id);\n" + "} }\n"); + CAPTURE(sorted.text); { - INFO("in order by name, nothing said: " + sorted.text); + INFO("in order by name, nothing said"); CHECK(has(sorted, "local id = ll.HTTPRequest(\"https://y\", { custom_header = { [\"A\"] = \"1\", [\"B\"] = \"2\" } }, \"\")")); CHECK_FALSE(noted(sorted, "SluaHeaderOrder")); } - checksClean(sorted); - - const ALLSLToSLua::Result lists = convert("integer gCount;\n" - "string counted() { ++gCount; return (string)gCount; }\n" - "default { touch_start(integer n) {\n" - " string token = llGetObjectDesc();\n" - " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, \"A\", \"1\", HTTP_CUSTOM_HEADER, \"A\", \"2\"], \"\");\n" - " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, token, \"1\"], \"\");\n" - " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_METHOD, \"GET\", HTTP_CUSTOM_HEADER, \"B\", counted()], \"\");\n" - " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_CUSTOM_HEADER, \"B\", counted()], \"\");\n" - "} }\n"); - UNSCOPED_INFO("a name twice, a list: " + lists.text); + require_checks_clean(sorted); + + const ALLSLToSLua::Result lists = require_converted("integer gCount;\n" + "string counted() { ++gCount; return (string)gCount; }\n" + "default { touch_start(integer n) {\n" + " string token = llGetObjectDesc();\n" + " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, \"A\", \"1\", HTTP_CUSTOM_HEADER, \"A\", \"2\"], \"\");\n" + " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, token, \"1\"], \"\");\n" + " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_METHOD, \"GET\", HTTP_CUSTOM_HEADER, \"B\", counted()], \"\");\n" + " llHTTPRequest(\"https://z\", [HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_CUSTOM_HEADER, \"B\", counted()], \"\");\n" + "} }\n"); + CAPTURE(lists.text); + UNSCOPED_INFO("a name twice, a list"); CHECK(has(lists, "ll.HTTPRequest(\"https://z\", { HTTP_CUSTOM_HEADER, \"A\", \"1\", HTTP_CUSTOM_HEADER, \"A\", \"2\" }, \"\")")); - UNSCOPED_INFO("a name not written out, a list: " + lists.text); + UNSCOPED_INFO("a name not written out, a list"); CHECK(has(lists, "ll.HTTPRequest(\"https://z\", { HTTP_CUSTOM_HEADER, token, \"1\" }, \"\")")); - UNSCOPED_INFO("moved past another, a list: " + lists.text); + UNSCOPED_INFO("moved past another, a list"); CHECK(has(lists, "{ HTTP_CUSTOM_HEADER, \"A\", counted(), HTTP_METHOD, \"GET\", HTTP_CUSTOM_HEADER, \"B\", counted() }")); - UNSCOPED_INFO("one after another, run in their order: " + lists.text); + UNSCOPED_INFO("one after another, run in their order"); CHECK(has(lists, "{ custom_header = { [\"A\"] = counted(), [\"B\"] = counted() } }")); - checksClean(lists); + require_checks_clean(lists); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a rule list the table cannot say just so stays a list", "[ALLSLToSLua]") @@ -3334,31 +3379,33 @@ namespace " llLinkParticleSystem(2, [PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | PSYS_PART_WIND_MASK]);\n" " llLinkParticleSystem(3, [PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | (PSYS_PART_BOUNCE_MASK | PSYS_PART_RIBBON_MASK)]);\n" "} }\n"; - const ALLSLToSLua::Result r = convert(script); - UNSCOPED_INFO("not written out: " + r.text); + const ALLSLToSLua::Result r = require_converted(script); + CAPTURE(r.text); + UNSCOPED_INFO("not written out"); CHECK(has(r, "ll.ParticleSystem(gRules)")); - UNSCOPED_INFO("a key twice: " + r.text); + UNSCOPED_INFO("a key twice"); CHECK(has(r, "ll.ParticleSystem({ PSYS_PART_MAX_AGE, 2.0, PSYS_PART_MAX_AGE, 3.0 })")); - UNSCOPED_INFO("another table's rule: " + r.text); + UNSCOPED_INFO("another table's rule"); CHECK(has(r, "ll.ParticleSystem({ PRIM_MEDIA_AUTO_PLAY, 1 })")); - UNSCOPED_INFO("a number for a rule: " + r.text); + UNSCOPED_INFO("a number for a rule"); CHECK(has(r, "ll.ParticleSystem({ 7, 2.0 })")); - UNSCOPED_INFO("a value of another shape: " + r.text); + UNSCOPED_INFO("a value of another shape"); CHECK(has(r, "ll.ParticleSystem({ PSYS_PART_START_ALPHA, vector(1, 0, 0) })")); - UNSCOPED_INFO("no value: " + r.text); + UNSCOPED_INFO("no value"); CHECK(has(r, "ll.ParticleSystem({ PSYS_PART_MAX_AGE })")); { - INFO("flags as they are: " + r.text); + INFO("flags as they are"); CHECK(has(r, "ll.LinkParticleSystem(1, { flags = PSYS_PART_WIND_MASK + PSYS_PART_BOUNCE_MASK })")); CHECK(has(r, "ll.LinkParticleSystem(2, { flags = bit32.bor(PSYS_PART_WIND_MASK, PSYS_PART_WIND_MASK) })")); } - UNSCOPED_INFO("masks bracketed, each true: " + r.text); + UNSCOPED_INFO("masks bracketed, each true"); CHECK(has(r, "ll.LinkParticleSystem(3, { wind = true, bounce = true, ribbon = true })")); ALLSLToSLua::Options plain; plain.idioms = false; const ALLSLToSLua::Result lists = ALLSLToSLua::convert(script, plain); + CAPTURE(lists.text); { - INFO("no idioms, no tables: " + lists.text); + INFO("no idioms, no tables"); CHECK(has(lists, "ll.LinkParticleSystem(3, { PSYS_PART_FLAGS, ")); CHECK_FALSE(has(lists, "wind = true")); } @@ -3366,7 +3413,7 @@ namespace TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: every rule of the three tables matches the definitions' types", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert( + const ALLSLToSLua::Result r = require_converted( "default { touch_start(integer n) {\n" " key k = llGetOwner();\n" " llParticleSystem([PSYS_PART_FLAGS, PSYS_PART_INTERP_COLOR_MASK | PSYS_PART_INTERP_SCALE_MASK | PSYS_PART_BOUNCE_MASK |\n" @@ -3390,6 +3437,7 @@ namespace " HTTP_VERBOSE_THROTTLE, FALSE, HTTP_CUSTOM_HEADER, \"X-A\", \"1\", HTTP_PRAGMA_NO_CACHE, TRUE, HTTP_USER_AGENT, \"me\",\n" " HTTP_ACCEPT, \"text/plain\", HTTP_EXTENDED_ERROR, TRUE], \"\");\n" "} }\n"); + CAPTURE(r.text); // The keys each table was written with, a line each. std::map> written; { @@ -3417,7 +3465,7 @@ namespace { const std::string text = defs.str(); const size_t begin = text.find(std::string("export type ") + type + " = {"); - UNSCOPED_INFO(std::string("the definitions have ") + type); + UNSCOPED_INFO("the definitions have " << type); REQUIRE(begin != std::string::npos); const std::string block = text.substr(begin, text.find("\n}", begin) - begin); std::set fields; @@ -3442,12 +3490,12 @@ namespace { unknown += fields.contains(key) ? "" : " " + key; } - UNSCOPED_INFO(std::string(type) + " has every key written:" + unknown + "\n" + r.text); + UNSCOPED_INFO(type << " has every key written:" << unknown); REQUIRE(unknown.empty()); - UNSCOPED_INFO(std::string(type) + " written with every key it has:" + missing + "\n" + r.text); + UNSCOPED_INFO(type << " written with every key it has:" << missing); REQUIRE(missing.empty()); } - checksClean(r); + require_checks_clean(r); if (!newSolver) { std::string said; @@ -3455,8 +3503,8 @@ namespace { said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); } - UNSCOPED_INFO("strict:\n" + said + "---\n" + r.text); - REQUIRE(said.empty()); + CAPTURE(said); + CHECK(said.empty()); } } } @@ -3465,13 +3513,13 @@ namespace { TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a list written over several lines keeps its lines and comments", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("list gColours = [\n" - " // warm\n" - " \"red\", \"orange\",\n" - " /* cool */ \"blue\" // last\n" - "];\n" - "list gTwo = [1, 2,\n" - " 3]; // three\n" + const ALLSLToSLua::Result r = require_converted("list gColours = [\n" + " // warm\n" + " \"red\", \"orange\",\n" + " /* cool */ \"blue\" // last\n" + "];\n" + "list gTwo = [1, 2,\n" + " 3]; // three\n" "default { touch_start(integer n) {\n" " string activeRequest = llList2Json(JSON_OBJECT, [\n" " \"fn\", \"SELECT\",\n" @@ -3499,11 +3547,12 @@ namespace " PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP\n" " ]);\n" "} }\n"); - UNSCOPED_INFO("a global's, its comments where they were: " + r.text); + CAPTURE(r.text); + UNSCOPED_INFO("a global's, its comments where they were"); CHECK(has(r, "local gColours = {\n -- warm\n \"red\", \"orange\",\n --[[ cool ]] \"blue\" -- last\n}\n")); - UNSCOPED_INFO("closed on the line it closed on, a comment after it trailing that: " + r.text); + UNSCOPED_INFO("closed on the line it closed on, a comment after it trailing that"); CHECK(has(r, "local gTwo = { 1, 2,\n 3 } -- three\n")); - UNSCOPED_INFO("Tapple's, as the LSL had it: " + r.text); + UNSCOPED_INFO("Tapple's, as the LSL had it"); CHECK(has(r, " local activeRequest = ll.List2Json(JSON_OBJECT, {\n" " \"fn\", \"SELECT\",\n" " \"radius\", 10,\n" @@ -3518,40 +3567,41 @@ namespace " \"responders\", \"[]\"\n" " }) -- sent\n")); { - INFO("each comment once: " + r.text); + INFO("each comment once"); CHECK(count(r, "only the owner") == 1); CHECK(count(r, "-- sent") == 1); CHECK(count(r, "-- first") == 1); } - UNSCOPED_INFO("in a line made inside an expression, one line: " + r.text); + UNSCOPED_INFO("in a line made inside an expression, one line"); CHECK(has(r, "(function() m = { 3, 4 } return m end)()")); - UNSCOPED_INFO("in a condition: " + r.text); + UNSCOPED_INFO("in a condition"); CHECK(has(r, " if table.find({ \"a\", -- first\n \"b\" }")); - UNSCOPED_INFO("one written another way, its comment over it: " + r.text); + UNSCOPED_INFO("one written another way, its comment over it"); CHECK(has(r, " -- none\n ll.ParticleSystem({\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a detected event's count only declared where the body names it", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("selectHorse() {}\n" - "default {\n" - " touch_start (integer num) {\n" - " if (llDetectedKey(0) == llGetOwner()) {\n" - " selectHorse();\n" - " }\n" - " }\n" - " collision_start(integer n) { llOwnerSay((string)n); }\n" - " sensor(integer found) { found = 0; }\n" - "}\n"); - { - INFO("none that nothing reads: " + r.text); + const ALLSLToSLua::Result r = require_converted("selectHorse() {}\n" + "default {\n" + " touch_start (integer num) {\n" + " if (llDetectedKey(0) == llGetOwner()) {\n" + " selectHorse();\n" + " }\n" + " }\n" + " collision_start(integer n) { llOwnerSay((string)n); }\n" + " sensor(integer found) { found = 0; }\n" + "}\n"); + CAPTURE(r.text); + { + INFO("none that nothing reads"); CHECK(has(r, "LLEvents:on(\"touch_start\", function(detected)\n -- LSL: detected[n]")); CHECK_FALSE(has(r, "local num")); } - UNSCOPED_INFO("one read: " + r.text); + UNSCOPED_INFO("one read"); CHECK(has(r, "function(detected)\n local n = #detected\n print(tostring(n))\n")); - UNSCOPED_INFO("one set, which needs it there: " + r.text); + UNSCOPED_INFO("one set, which needs it there"); CHECK(has(r, " local found = #detected\n found = 0\n")); // Unused, of what Luau says: only what the LSL itself never read. std::string unused; @@ -3559,9 +3609,8 @@ namespace { unused += p.code == "LocalUnused" ? p.message + "\n" : std::string(); } - UNSCOPED_INFO("unused"); CHECK(unused == std::string("Variable 'found' is never used; prefix with '_' to silence\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: text where ll takes a key passed as it is with :: any and noted once", "[ALLSLToSLua]") @@ -3574,80 +3623,81 @@ namespace " llOwnerSay(llKey2Name(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"));\n" " if (mine(llJsonGetValue(gJson, [\"owner\"]))) llOwnerSay(\"mine\");\n" "} }\n"; - const ALLSLToSLua::Result r = convert(lsl); - UNSCOPED_INFO("Tapple's, as it is: " + r.text); + const ALLSLToSLua::Result r = require_converted(lsl); + CAPTURE(r.text); + UNSCOPED_INFO("Tapple's, as it is"); CHECK(has(r, "ll.Key2Name(ll.JsonGetValue(gJson, { \"selected\" }) :: any)")); - UNSCOPED_INFO("bracketed where it must be: " + r.text); + UNSCOPED_INFO("bracketed where it must be"); CHECK(has(r, "ll.Key2Name((\"a2e76fcd-9360-4f6d-a924-\" .. \"938f923df11d\") :: any)")); - UNSCOPED_INFO("a UUID written out, a uuid: " + r.text); + UNSCOPED_INFO("a UUID written out, a uuid"); CHECK(has(r, "ll.Key2Name(uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"))")); { - INFO("the script's own function's key, uuid(): " + r.text); + INFO("the script's own function's key, uuid()"); CHECK(has(r, "mine(uuid(ll.JsonGetValue(gJson, { \"owner\" })))")); CHECK(noted(r, "SluaUuidText")); } - UNSCOPED_INFO("said once: " + r.text); + UNSCOPED_INFO("said once"); CHECK(count(r, "-- LSL: SLua's ll takes a key as text") == 1); - checksClean(r); + require_checks_clean(r); if (!newSolver) { // ll's calls alone, strict. - const ALLSLToSLua::Result calls = convert("default { touch_start(integer n) {\n" - " llOwnerSay(llKey2Name(llJsonGetValue(\"{}\", [\"selected\"])));\n" - " llOwnerSay((string)llGetOwnerKey(\"a\" + (string)n));\n" - "} }\n"); + const ALLSLToSLua::Result calls = require_converted("default { touch_start(integer n) {\n" + " llOwnerSay(llKey2Name(llJsonGetValue(\"{}\", [\"selected\"])));\n" + " llOwnerSay((string)llGetOwnerKey(\"a\" + (string)n));\n" + "} }\n"); std::string said; for (const ALScriptProblem& p : service.check("--!strict\n" + calls.text)) { said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); } - { - INFO("strict:\n" + said + "---\n" + calls.text); - REQUIRE(said.empty()); - REQUIRE(calls.text.find(":: any") != std::string::npos); - } + CAPTURE(said, calls.text); + CHECK(said.empty()); + CHECK(calls.text.find(":: any") != std::string::npos); } const ALLSLToSLua::Result close = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); + CAPTURE(close.text); { - INFO("llcompat's the same: " + close.text); + INFO("llcompat's the same"); CHECK(close.converted); - REQUIRE(close.text.find(":: any)") != std::string::npos); + CHECK(close.text.find(":: any)") != std::string::npos); } - checksClean(close); + require_checks_clean(close); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: deprecated calls noted with what to use; XorBase64Strings its own note", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " llMakeFire(10, 1.0, 1.0, 1.0, PI, \"\", ZERO_VECTOR);\n" - " llSoundPreload(\"boom\");\n" - " llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 1.0]);\n" - " llOwnerSay(llXorBase64Strings(\"YQ==\", \"Yg==\"));\n" - " llTakeCamera(llGetOwner());\n" - "} }\n"); + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " llMakeFire(10, 1.0, 1.0, 1.0, PI, \"\", ZERO_VECTOR);\n" + " llSoundPreload(\"boom\");\n" + " llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 1.0]);\n" + " llOwnerSay(llXorBase64Strings(\"YQ==\", \"Yg==\"));\n" + " llTakeCamera(llGetOwner());\n" + "} }\n"); + CAPTURE(r.text); const auto named = [&r](const std::string& bare, const std::string& use) { return std::any_of(r.notes.begin(), r.notes.end(), [&](const ALScriptProblem& p) { return p.key == "SluaDeprecatedFor" && p.args.size() == 2 && p.args[0] == bare && p.args[1] == use; }); }; { - INFO("each, for what it names: " + r.text); + INFO("each, for what it names"); CHECK(named("MakeFire", "ll.ParticleSystem")); CHECK(named("SoundPreload", "ll.PreloadSound")); CHECK(named("SetPrimitiveParams", "ll.SetLinkPrimitiveParamsFast")); CHECK(named("TakeCamera", "ll.SetCameraParams")); } { - INFO("XorBase64Strings' own: " + r.text); + INFO("XorBase64Strings' own"); CHECK(noted(r, "SluaXorBase64Wrong")); CHECK_FALSE(named("XorBase64Strings", "ll.XorBase64")); } - UNSCOPED_INFO("said so: " + r.text); + UNSCOPED_INFO("said so"); CHECK(has(r, "-- LSL: SLua deprecates ll.MakeFire, for ll.ParticleSystem.\n")); - UNSCOPED_INFO("none said bare: " + r.text); + UNSCOPED_INFO("none said bare"); CHECK_FALSE(noted(r, "SluaDeprecated")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a find's place against a literal compared in SLua's count from 1", "[ALLSLToSLua]") @@ -3667,26 +3717,27 @@ namespace " llOwnerSay((string)at);\n" "}\n" "default { touch_start(integer n) { check(\"x\"); } }\n"; - const ALLSLToSLua::Result r = convert(lsl); - UNSCOPED_INFO("Tapple's: " + r.text); + const ALLSLToSLua::Result r = require_converted(lsl); + CAPTURE(r.text); + UNSCOPED_INFO("Tapple's"); CHECK(has(r, "if P ~= \"\" and ll.SubStringIndex(item, P) == 1 then")); - UNSCOPED_INFO("the number on the left: " + r.text); + UNSCOPED_INFO("the number on the left"); CHECK(has(r, "if ll.SubStringIndex(item, P) == 1 then print(\"b\")")); - UNSCOPED_INFO("~=: " + r.text); + UNSCOPED_INFO("~="); CHECK(has(r, "if ll.SubStringIndex(item, P) ~= 3 then")); - UNSCOPED_INFO("an order, nil read as LSL's -1: " + r.text); + UNSCOPED_INFO("an order, nil read as LSL's -1"); CHECK(has(r, "if (ll.SubStringIndex(item, P) or 0) > 1 then")); - UNSCOPED_INFO("an order the other way round: " + r.text); + UNSCOPED_INFO("an order the other way round"); CHECK(has(r, "if (ll.SubStringIndex(item, P) or 0) > 4 then")); - UNSCOPED_INFO("below -1, as written: " + r.text); + UNSCOPED_INFO("below -1, as written"); CHECK(has(r, "if (ll.SubStringIndex(item, P) or 0) - 1 == -2 then")); - UNSCOPED_INFO("under a not: " + r.text); + UNSCOPED_INFO("under a not"); CHECK(has(r, "if ll.SubStringIndex(item, P) ~= 1 then print(\"g\")")); - UNSCOPED_INFO("table.find's: " + r.text); + UNSCOPED_INFO("table.find's"); CHECK(has(r, "if table.find(L, item) == 1 then")); - UNSCOPED_INFO("a number, LSL's: " + r.text); + UNSCOPED_INFO("a number, LSL's"); CHECK(has(r, "local at = (ll.SubStringIndex(item, P) or 0) - 1")); - checksClean(r); + require_checks_clean(r); // table.find's alone, without SLua's ll: whether it found, against nil. ALLSLToSLua::Options idioms = ALLSLToSLua::Options::closeToLSL(); @@ -3698,64 +3749,67 @@ namespace " if (llListFindList(L, [n]) == 2) llOwnerSay(\"third\");\n" "} }\n", idioms); + CAPTURE(found.text); { - INFO("found, against nil: " + found.text); + INFO("found, against nil"); CHECK(found.converted); CHECK(has(found, "if table.find(L, n) == nil then")); CHECK(has(found, "if table.find(L, n) ~= nil then")); CHECK(has(found, "if table.find(L, n) == 3 then")); } - checksClean(found); + require_checks_clean(found); const ALLSLToSLua::Result close = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); + CAPTURE(close.text); { - INFO("llcompat's, LSL's count: " + close.text); + INFO("llcompat's, LSL's count"); CHECK(close.converted); CHECK(has(close, "llcompat.SubStringIndex(item, P) == 0")); } - checksClean(close); + require_checks_clean(close); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: blank lines between statements kept singly and not at a block's ends", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer a = 1;\n" - "integer b = 2;\n" - "\n" - "\n" - "integer c = 3;\n" - "integer preCheck(key avatar) {\n" - "\n" - " // only allow my owner to request contents\n" - " if (avatar != llGetOwner()) return FALSE;\n" - "\n" - " return TRUE;\n" - "}\n" - "default {\n" - " touch_start(integer n) {\n" - " if (preCheck(llDetectedKey(0))) {\n" - " llOwnerSay((string)a);\n" - "\n" - " llOwnerSay((string)b);\n" - "\n" - " }\n" - "\n" - " // said apart\n" - " llOwnerSay((string)c);\n" - " }\n" - "}\n"); - UNSCOPED_INFO("the globals: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer a = 1;\n" + "integer b = 2;\n" + "\n" + "\n" + "integer c = 3;\n" + "integer preCheck(key avatar) {\n" + "\n" + " // only allow my owner to request contents\n" + " if (avatar != llGetOwner()) return FALSE;\n" + "\n" + " return TRUE;\n" + "}\n" + "default {\n" + " touch_start(integer n) {\n" + " if (preCheck(llDetectedKey(0))) {\n" + " llOwnerSay((string)a);\n" + "\n" + " llOwnerSay((string)b);\n" + "\n" + " }\n" + "\n" + " // said apart\n" + " llOwnerSay((string)c);\n" + " }\n" + "}\n"); + CAPTURE(r.text); + UNSCOPED_INFO("the globals"); CHECK(has(r, "local a = 1\nlocal b = 2\n\nlocal c = 3\n")); - UNSCOPED_INFO("Tapple's: " + r.text); + UNSCOPED_INFO("Tapple's"); CHECK(has(r, " -- only allow my owner to request contents\n if avatar ~= ll.GetOwner() then return false end\n\n return true\nend")); - UNSCOPED_INFO("none at a block's start: " + r.text); + UNSCOPED_INFO("none at a block's start"); CHECK(has(r, "(avatar)\n -- only allow")); - UNSCOPED_INFO("in a block, none at its end: " + r.text); + UNSCOPED_INFO("in a block, none at its end"); CHECK(has(r, " print(tostring(a))\n\n print(tostring(b))\n end\n")); - UNSCOPED_INFO("one over a comment kept apart: " + r.text); + UNSCOPED_INFO("one over a comment kept apart"); CHECK(has(r, " end\n\n -- said apart\n print(tostring(c))\n")); - UNSCOPED_INFO("never two: " + r.text); + UNSCOPED_INFO("never two"); CHECK_FALSE(has(r, "\n\n\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: table braces spaced as asked as the formatter spaces them", "[ALLSLToSLua]") @@ -3774,25 +3828,27 @@ namespace ALLSLToSLua::Options typed; typed.types = true; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, typed); + CAPTURE(r.text); { - INFO("Tapple's: " + r.text); + INFO("Tapple's"); CHECK(r.converted); CHECK(has(r, "local params: { any } = ll.ParseStringKeepNulls(msg, { \"\\n\" }, {})")); } - UNSCOPED_INFO("a rule table: " + r.text); + UNSCOPED_INFO("a rule table"); CHECK(has(r, "ll.SetPrimMediaParams(0, { auto_play = true, whitelist = { \"a.com\", \"b.com\" } })")); - UNSCOPED_INFO("the states' type: " + r.text); + UNSCOPED_INFO("the states' type"); CHECK(has(r, "local states: { [string]: { [string]: (...any) -> () } } = {}")); - UNSCOPED_INFO("over lines: " + r.text); + UNSCOPED_INFO("over lines"); CHECK(has(r, "local gTwo: { any } = { 1, 2,\n 3 }\n")); - UNSCOPED_INFO("an interpolated string's: " + r.text); + UNSCOPED_INFO("an interpolated string's"); CHECK(has(r, "`n={#params} {ll.DumpList2String({ vector(1, 2, 3) }, \"\")}`")); - checksClean(r); + require_checks_clean(r); typed.braceSpaces = false; const ALLSLToSLua::Result none = ALLSLToSLua::convert(lsl, typed); + CAPTURE(none.text); { - INFO("none: " + none.text); + INFO("none"); CHECK(none.converted); CHECK(has(none, "local params: {any} = ll.ParseStringKeepNulls(msg, {\"\\n\"}, {})")); CHECK(has(none, "ll.SetPrimMediaParams(0, {auto_play = true, whitelist = {\"a.com\", \"b.com\"}})")); @@ -3800,7 +3856,7 @@ namespace CHECK(has(none, "local gTwo: {any} = {1, 2,\n 3}\n")); CHECK_FALSE(has(none, "{ ")); } - checksClean(none); + require_checks_clean(none); // As the formatter would leave them, either way. for (bool spaces : { true, false }) @@ -3809,7 +3865,7 @@ namespace formatter.lua = true; formatter.braceSpaces = spaces; const ALLSLToSLua::Result same = spaces ? r : none; - UNSCOPED_INFO((spaces ? "formatted, spaced" : "formatted, none")); + CAPTURE(spaces); REQUIRE(ALScriptFormatter::format(same.text, formatter) == same.text); } } @@ -3846,27 +3902,28 @@ namespace "}\n"; for (bool spaces : { true, false }) { + CAPTURE(spaces); ALLSLToSLua::Options options; options.braceSpaces = spaces; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, options); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); REQUIRE(r.converted); { - INFO("each after its own: " + r.text); + INFO("each after its own"); REQUIRE(has(r, " print(\"a\") -- after a\n elseif")); REQUIRE(has(r, " print(\"b\") -- after b\n else")); REQUIRE(has(r, " print(\"c\") -- after c\n end")); } - UNSCOPED_INFO("an empty body's, after the line that opens it: " + r.text); + UNSCOPED_INFO("an empty body's, after the line that opens it"); REQUIRE(has(r, " == 2 then -- an empty body\n else\n print(\"f\") -- after f\n")); - UNSCOPED_INFO("a loop's body's: " + r.text); + UNSCOPED_INFO("a loop's body's"); REQUIRE(has(r, " n -= 1 -- after the step\n end")); - UNSCOPED_INFO("between a block and its else, over what the else runs: " + r.text); + UNSCOPED_INFO("between a block and its else, over what the else runs"); REQUIRE(has(r, " else\n -- over the else\n print(\"e\")\n")); - UNSCOPED_INFO("lined up: " + r.text); + UNSCOPED_INFO("lined up"); REQUIRE(has(r, "local CHANNEL = 1 -- channel\nlocal OWNER_ONLY = 0 -- owner\nlocal TIMEOUT = 2.5 -- seconds\n")); { - INFO("one line break at the end: " + r.text); + INFO("one line break at the end"); REQUIRE(r.text.size() > 2); REQUIRE(r.text.compare(r.text.size() - 2, 2, "\n\n") != 0); REQUIRE(r.text.back() == '\n'); @@ -3874,9 +3931,8 @@ namespace ALScriptFormatter::Options formatter; formatter.lua = true; formatter.braceSpaces = spaces; - UNSCOPED_INFO((spaces ? "formatted, spaced" : "formatted, none")); REQUIRE(ALScriptFormatter::format(r.text, formatter) == r.text); - checksClean(r); + require_checks_clean(r); } } @@ -3889,112 +3945,115 @@ namespace "//*/\n" "default { state_entry() { if (allowAttachedAO) llOwnerSay(\"on\"); } }\n", options); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); - UNSCOPED_INFO("the toggle: " + r.text); + UNSCOPED_INFO("the toggle"); CHECK(has(r, "\n---[[ OVERRIDE\nlocal allowAttachedAO: boolean = false\n--]]\n")); UNSCOPED_INFO("the opener a line comment"); - CHECK(commentAt(r.text, "---[[") == std::string("---[[ OVERRIDE")); + CHECK(require_comment_at(r.text, "---[[") == std::string("---[[ OVERRIDE")); UNSCOPED_INFO("the closer one"); - CHECK(commentAt(r.text, "--]]") == std::string("--]]")); + CHECK(require_comment_at(r.text, "--]]") == std::string("--]]")); UNSCOPED_INFO("turned off"); - CHECK(toggled(r.text, "---[[") == std::string("--[[ OVERRIDE\nlocal allowAttachedAO: boolean = false\n--]]")); - checksClean(r); + CHECK(require_toggled(r.text, "---[[") == std::string("--[[ OVERRIDE\nlocal allowAttachedAO: boolean = false\n--]]")); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: half a toggle: an opener only before a closer and openers share it", "[ALLSLToSLua]") { - const ALLSLToSLua::Result lone = convert("integer a;\n" - "//* nothing closes this\n" - "integer b;\n" - "default { state_entry() { llOwnerSay((string)(a + b)); } }\n"); + const ALLSLToSLua::Result lone = require_converted("integer a;\n" + "//* nothing closes this\n" + "integer b;\n" + "default { state_entry() { llOwnerSay((string)(a + b)); } }\n"); + CAPTURE(lone.text); { - INFO("a lone opener a comment with a star: " + lone.text); + INFO("a lone opener a comment with a star"); CHECK(has(lone, "\n--* nothing closes this\nlocal b = 0\n")); CHECK_FALSE(has(lone, "---[")); } - checksClean(lone); - - const ALLSLToSLua::Result r = convert("//* not one */\n" - "//**************\n" - "integer c = 1;\n" - "//* one\n" - "integer d = 2;\n" - "//* two\n" - "integer e = 3;\n" - "//*/ // both\n" - "//*/\n" - "default { state_entry() { llOwnerSay((string)(c + d + e)); } }\n"); - UNSCOPED_INFO("closed on its line: " + r.text); + require_checks_clean(lone); + + const ALLSLToSLua::Result r = require_converted("//* not one */\n" + "//**************\n" + "integer c = 1;\n" + "//* one\n" + "integer d = 2;\n" + "//* two\n" + "integer e = 3;\n" + "//*/ // both\n" + "//*/\n" + "default { state_entry() { llOwnerSay((string)(c + d + e)); } }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("closed on its line"); CHECK(has(r, "\n--* not one */\n")); - UNSCOPED_INFO("a rule of stars: " + r.text); + UNSCOPED_INFO("a rule of stars"); CHECK(has(r, "\n--**************\n")); - UNSCOPED_INFO("two openers and a closer, then one alone: " + r.text); + UNSCOPED_INFO("two openers and a closer, then one alone"); CHECK(has(r, "\n---[[ one\nlocal d = 2\n---[[ two\nlocal e = 3\n--]] -- both\n--]]\n")); UNSCOPED_INFO("the first off"); - CHECK(toggled(r.text, "---[[ one") == std::string("--[[ one\nlocal d = 2\n---[[ two\nlocal e = 3\n--]]")); + CHECK(require_toggled(r.text, "---[[ one") == std::string("--[[ one\nlocal d = 2\n---[[ two\nlocal e = 3\n--]]")); UNSCOPED_INFO("the second off"); - CHECK(toggled(r.text, "---[[ two") == std::string("--[[ two\nlocal e = 3\n--]]")); + CHECK(require_toggled(r.text, "---[[ two") == std::string("--[[ two\nlocal e = 3\n--]]")); std::string off = r.text; off.erase(off.find("---[[ one"), 1); UNSCOPED_INFO("the closer's own comment after it, off"); - CHECK(commentAt(off, "-- both") == std::string("-- both")); - checksClean(r); + CHECK(require_comment_at(off, "-- both") == std::string("-- both")); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: the toggle the other way round as a block comment SLua can turn on", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("/* OVERRIDE\n" - "integer allowAttachedAO;\n" - "//*/\n" - "//* A\n" - "integer x = 1;\n" - "/* B\n" - "integer y = 2;\n" - "//*/\n" - "default { state_entry() { llOwnerSay((string)x); } }\n"); + const ALLSLToSLua::Result r = require_converted("/* OVERRIDE\n" + "integer allowAttachedAO;\n" + "//*/\n" + "//* A\n" + "integer x = 1;\n" + "/* B\n" + "integer y = 2;\n" + "//*/\n" + "default { state_entry() { llOwnerSay((string)x); } }\n"); UNSCOPED_INFO("one block comment"); - CHECK(commentAt(r.text, "--[[ OVERRIDE") == std::string("--[[ OVERRIDE\ninteger allowAttachedAO;\n--]]")); + CHECK(require_comment_at(r.text, "--[[ OVERRIDE") == std::string("--[[ OVERRIDE\ninteger allowAttachedAO;\n--]]")); // What it holds stays LSL: nothing says it is code. std::string on = r.text; on.insert(on.find("--[[ OVERRIDE"), "-"); UNSCOPED_INFO("turned on, the opener a line comment"); - CHECK(commentAt(on, "---[[ OVERRIDE") == std::string("---[[ OVERRIDE")); + CHECK(require_comment_at(on, "---[[ OVERRIDE") == std::string("---[[ OVERRIDE")); UNSCOPED_INFO("and the closer"); - CHECK(commentAt(on, "--]]") == std::string("--]]")); + CHECK(require_comment_at(on, "--]]") == std::string("--]]")); UNSCOPED_INFO("the opener over code off, to the closer of the one after it"); - CHECK(toggled(r.text, "---[[ A") == - std::string("--[[ A\nlocal x = 1\n\n--[[ B\ninteger y = 2;\n--]]")); - checksClean(r); + CHECK(require_toggled(r.text, "---[[ A") == + std::string("--[[ A\nlocal x = 1\n\n--[[ B\ninteger y = 2;\n--]]")); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a toggle's level is the fewest =s that nothing inside closes", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("//* OVERRIDE\n" - "string s = \"]]\";\n" - "//*/\n" - "default { state_entry() { llOwnerSay(s); } }\n"); - UNSCOPED_INFO("past a string's ]]: " + r.text); + const ALLSLToSLua::Result r = require_converted("//* OVERRIDE\n" + "string s = \"]]\";\n" + "//*/\n" + "default { state_entry() { llOwnerSay(s); } }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("past a string's ]]"); CHECK(has(r, "\n---[=[ OVERRIDE\nlocal s = \"]]\"\n--]=]\n")); - UNSCOPED_INFO("off"); - CHECK(toggled(r.text, "---[=[") == std::string("--[=[ OVERRIDE\nlocal s = \"]]\"\n--]=]")); - checksClean(r); - - const ALLSLToSLua::Result shared = convert("//* A\n" - "integer x = 1; /* one */\n" - "/* B\n" - "string s = \"]]\";\n" - "//*/\n" - "default { state_entry() { llOwnerSay((string)x); } }\n"); - { - INFO("one level for the pair: " + shared.text); + CHECK(require_toggled(r.text, "---[=[") == std::string("--[=[ OVERRIDE\nlocal s = \"]]\"\n--]=]")); + require_checks_clean(r); + + const ALLSLToSLua::Result shared = require_converted("//* A\n" + "integer x = 1; /* one */\n" + "/* B\n" + "string s = \"]]\";\n" + "//*/\n" + "default { state_entry() { llOwnerSay((string)x); } }\n"); + CAPTURE(shared.text); + { + INFO("one level for the pair"); CHECK(has(shared, "\n---[=[ A\nlocal x = 1 --[[ one ]]\n")); CHECK(has(shared, "\n--[=[ B\nstring s = \"]]\";\n--]=]\n")); } UNSCOPED_INFO("the opener off"); - CHECK(toggled(shared.text, "---[=[ A") == - std::string("--[=[ A\nlocal x = 1 --[[ one ]]\n\n--[=[ B\nstring s = \"]]\";\n--]=]")); - checksClean(shared); + CHECK(require_toggled(shared.text, "---[=[ A") == + std::string("--[=[ A\nlocal x = 1 --[[ one ]]\n\n--[=[ B\nstring s = \"]]\";\n--]=]")); + require_checks_clean(shared); std::string every = "// "; for (char c = 1; c < ' '; ++c) @@ -4004,56 +4063,57 @@ namespace every += c; } } - const ALLSLToSLua::Result unmarked = convert(every + "\ninteger y;\n//* A\ninteger x = 1;\n//*/\n" - "default { state_entry() { llOwnerSay((string)(x + y)); } }\n"); - UNSCOPED_INFO("paired still: " + unmarked.text); + const ALLSLToSLua::Result unmarked = require_converted(every + "\ninteger y;\n//* A\ninteger x = 1;\n//*/\n" + "default { state_entry() { llOwnerSay((string)(x + y)); } }\n"); + CAPTURE(unmarked.text); + UNSCOPED_INFO("paired still"); CHECK(has(unmarked, "\n---[[ A\nlocal x = 1\n--]]\n")); UNSCOPED_INFO("no byte of its own left in"); CHECK(count(unmarked, std::string(1, '\x01')) == size_t(1)); - checksClean(unmarked); + require_checks_clean(unmarked); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a toggle after code or in a handler or at the top above the helpers", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() {\n" - " integer a = 1; //* A\n" + const ALLSLToSLua::Result r = require_converted("default { state_entry() {\n" + " integer a = 1; //* A\n" " llOwnerSay((string)a); //*/\n" " //* DEBUG\n" " llOwnerSay(\"debug\");\n" " //*/\n" " llOwnerSay(\"done\");\n" "} }\n"); - UNSCOPED_INFO("after code: " + r.text); + CAPTURE(r.text); + UNSCOPED_INFO("after code"); CHECK(has(r, "\nlocal a = 1 ---[[ A\nprint(tostring(a)) --]]\n")); UNSCOPED_INFO("off from it"); - CHECK(toggled(r.text, "---[[ A") == std::string("--[[ A\nprint(tostring(a)) --]]")); - UNSCOPED_INFO("in a handler: " + r.text); + CHECK(require_toggled(r.text, "---[[ A") == std::string("--[[ A\nprint(tostring(a)) --]]")); + UNSCOPED_INFO("in a handler"); CHECK(has(r, "\n---[[ DEBUG\nprint(\"debug\")\n--]]\nprint(\"done\")\n")); - UNSCOPED_INFO("off"); - CHECK(toggled(r.text, "---[[ DEBUG") == std::string("--[[ DEBUG\nprint(\"debug\")\n--]]")); - checksClean(r); + CHECK(require_toggled(r.text, "---[[ DEBUG") == std::string("--[[ DEBUG\nprint(\"debug\")\n--]]")); + require_checks_clean(r); - const ALLSLToSLua::Result top = convert("//* DEBUG\n" - "\n" - "vector v = <1, 2, 3>;\n" - "//*/\n" - "default { state_entry() { llOwnerSay((string)llVecNorm(v)); } }\n"); + const ALLSLToSLua::Result top = require_converted("//* DEBUG\n" + "\n" + "vector v = <1, 2, 3>;\n" + "//*/\n" + "default { state_entry() { llOwnerSay((string)llVecNorm(v)); } }\n"); + CAPTURE(top.text); { - INFO("the helper over it: " + top.text); + INFO("the helper over it"); CHECK(has(top, "local function vecNorm(")); CHECK(top.text.find("local function vecNorm(") < top.text.find("---[[ DEBUG")); } - UNSCOPED_INFO("off"); - CHECK(toggled(top.text, "---[[ DEBUG") == std::string("--[[ DEBUG\nlocal v = vector(1, 2, 3)\n--]]")); - checksClean(top); + CHECK(require_toggled(top.text, "---[[ DEBUG") == std::string("--[[ DEBUG\nlocal v = vector(1, 2, 3)\n--]]")); + require_checks_clean(top); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a comment after an unbraced body trails the statement ending its line", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { touch_start(integer n) {\n" - " integer a; integer b;\n" - " if (n == 1)\n" - " llOwnerSay(\"a\"); // after a\n" + const ALLSLToSLua::Result r = require_converted("default { touch_start(integer n) {\n" + " integer a; integer b;\n" + " if (n == 1)\n" + " llOwnerSay(\"a\"); // after a\n" " else if (n == 2)\n" " llOwnerSay(\"b\"); // after b\n" " else\n" @@ -4068,210 +4128,219 @@ namespace " while (a) ; // an empty loop\n" " if (n == 5) llOwnerSay(\"h\"); // after h\n" "} }\n"); - UNSCOPED_INFO("a then's, an elseif's, an else's: " + r.text); + CAPTURE(r.text); + UNSCOPED_INFO("a then's, an elseif's, an else's"); CHECK(has(r, " if n == 1 then\n print(\"a\") -- after a\n elseif n == 2 then\n print(\"b\") -- after b\n" " else\n print(\"c\") -- after c\n end\n")); - UNSCOPED_INFO("an empty body's, the line that opens it: " + r.text); + UNSCOPED_INFO("an empty body's, the line that opens it"); CHECK(has(r, " == 2 then -- empty\n elseif b == 2 then -- empty too\n else\n a = true -- after a = 2\n end\n")); - UNSCOPED_INFO("an if on one line inside another: " + r.text); + UNSCOPED_INFO("an if on one line inside another"); CHECK(has(r, " if b ~= 0 then print(\"x\") end -- inner, one line\n")); { - INFO("a loop's body's: " + r.text); + INFO("a loop's body's"); CHECK(has(r, " n -= 1 -- after the step\n end\n")); CHECK(has(r, " while a do -- an empty loop\n end\n")); } - UNSCOPED_INFO("a one-line if, as before: " + r.text); + UNSCOPED_INFO("a one-line if, as before"); CHECK(has(r, " if n == 5 then print(\"h\") end -- after h\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a float cut to an integer is math.modf bracketed where its values spread", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("integer menuChannel;\n" - "vector gV;\n" - "integer cut(float f) { return (integer)f; }\n" - "default { touch_start(integer n) {\n" - " float f = llFrand(10);\n" - " menuChannel = (integer)llFrand(1000) - 10000;\n" - " integer x = (integer)f;\n" - " x = (integer)f;\n" - " x += (integer)f;\n" - " x = -(integer)f;\n" - " if ((integer)f == 3) llOwnerSay((string)((integer)f));\n" - " llOwnerSay(llList2CSV([(integer)f]));\n" - " gV.z = (integer)f;\n" - " llOwnerSay((string)(cut(f) + x + menuChannel) + (string)gV);\n" - "} }\n"); - UNSCOPED_INFO("Tapple's: " + r.text); + const ALLSLToSLua::Result r = require_converted("integer menuChannel;\n" + "vector gV;\n" + "integer cut(float f) { return (integer)f; }\n" + "default { touch_start(integer n) {\n" + " float f = llFrand(10);\n" + " menuChannel = (integer)llFrand(1000) - 10000;\n" + " integer x = (integer)f;\n" + " x = (integer)f;\n" + " x += (integer)f;\n" + " x = -(integer)f;\n" + " if ((integer)f == 3) llOwnerSay((string)((integer)f));\n" + " llOwnerSay(llList2CSV([(integer)f]));\n" + " gV.z = (integer)f;\n" + " llOwnerSay((string)(cut(f) + x + menuChannel) + (string)gV);\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("Tapple's"); CHECK(has(r, "menuChannel = math.modf(ll.Frand(1000)) - 10000\n")); - UNSCOPED_INFO("a local: " + r.text); + UNSCOPED_INFO("a local"); CHECK(has(r, "local x = math.modf(f)\n")); - UNSCOPED_INFO("assigned: " + r.text); + UNSCOPED_INFO("assigned"); CHECK(has(r, " x = math.modf(f)\n")); - UNSCOPED_INFO("added: " + r.text); + UNSCOPED_INFO("added"); CHECK(has(r, " x += math.modf(f)\n")); - UNSCOPED_INFO("its negative: " + r.text); + UNSCOPED_INFO("its negative"); CHECK(has(r, " x = -math.modf(f)\n")); - UNSCOPED_INFO("compared: " + r.text); + UNSCOPED_INFO("compared"); CHECK(has(r, "if math.modf(f) == 3 then")); - UNSCOPED_INFO("an argument: " + r.text); + UNSCOPED_INFO("an argument"); CHECK(has(r, "print(tostring((math.modf(f))))")); - UNSCOPED_INFO("a list's item: " + r.text); + UNSCOPED_INFO("a list's item"); CHECK(has(r, "{ (math.modf(f)) }")); - UNSCOPED_INFO("returned: " + r.text); + UNSCOPED_INFO("returned"); CHECK(has(r, "return (math.modf(f))")); - UNSCOPED_INFO("a vector's part, an argument: " + r.text); + UNSCOPED_INFO("a vector's part, an argument"); CHECK(has(r, "gV = vector(gV.x, gV.y, (math.modf(f)))")); - checksClean(r); + require_checks_clean(r); } + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a local whose Luau name would hide another is named apart", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("f1(string f2) {\n" - " llOwnerSay(f2);\n" - " f2();\n" - "}\n" - "\n" - "f2() {\n" - " llOwnerSay(\"world\");\n" - "}\n" - "\n" - "default { state_entry() {\n" - " f1(\"hello\");\n" - "} }\n"); - UNSCOPED_INFO("Tapple's, the parameter named apart: " + r.text); + const ALLSLToSLua::Result r = require_converted("f1(string f2) {\n" + " llOwnerSay(f2);\n" + " f2();\n" + "}\n" + "\n" + "f2() {\n" + " llOwnerSay(\"world\");\n" + "}\n" + "\n" + "default { state_entry() {\n" + " f1(\"hello\");\n" + "} }\n"); + CAPTURE(r.text); + UNSCOPED_INFO("Tapple's, the parameter named apart"); CHECK(has(r, "local function f1(f2_)\n print(f2_)\n f2()\nend\n")); { - INFO("the function as it was: " + r.text); + INFO("the function as it was"); CHECK(has(r, "local f2\n")); CHECK(has(r, "function f2()\n")); } - checksClean(r); - - const ALLSLToSLua::Result detected = convert("integer detected() {\n" - " return 5;\n" - "}\n" - "\n" - "default\n" - "{\n" - " touch_start(integer total_number)\n" - " {\n" - " string detected = \"fritz\";\n" - "\n" - " llOwnerSay(llDetectedName(0));\n" - " llOwnerSay(detected);\n" - " llOwnerSay((string)(detected() * detected()));\n" - " }\n" - "}\n"); - UNSCOPED_INFO("Tapple's detected, the function marked: " + detected.text); + require_checks_clean(r); + + const ALLSLToSLua::Result detected = require_converted("integer detected() {\n" + " return 5;\n" + "}\n" + "\n" + "default\n" + "{\n" + " touch_start(integer total_number)\n" + " {\n" + " string detected = \"fritz\";\n" + "\n" + " llOwnerSay(llDetectedName(0));\n" + " llOwnerSay(detected);\n" + " llOwnerSay((string)(detected() * detected()));\n" + " }\n" + "}\n"); + CAPTURE(detected.text); + UNSCOPED_INFO("Tapple's detected, the function marked"); CHECK(has(detected, "local function detected_()")); { - INFO("the local apart from it: " + detected.text); + INFO("the local apart from it"); CHECK(has(detected, "local detected_2 = \"fritz\"")); CHECK(has(detected, "print(detected_2)")); } - UNSCOPED_INFO("the function called: " + detected.text); + UNSCOPED_INFO("the function called"); CHECK(has(detected, "print(tostring(detected_() * detected_()))")); - checksClean(detected); - - const ALLSLToSLua::Result own = convert("integer fact(integer fact) {\n" - " if (fact <= 1) return 1;\n" - " return fact * fact(fact - 1);\n" - "}\n" - "string table = \"t\";\n" - "string table_ = \"u\";\n" - "f3(string f2) { llOwnerSay(f2); }\n" - "f2() { llOwnerSay(\"f2\"); }\n" - "f4() { f2(); string f2 = \"x\"; llOwnerSay(f2); }\n" - "default { touch_start(integer n) {\n" - " integer was = 1;\n" - " integer k = was++ + was;\n" - " llOwnerSay(table + table_ + (string)(fact(n) + k));\n" - " f3(\"a\"); f4();\n" - "} }\n"); - { - INFO("a parameter named as its function: " + own.text); + require_checks_clean(detected); + + const ALLSLToSLua::Result own = require_converted("integer fact(integer fact) {\n" + " if (fact <= 1) return 1;\n" + " return fact * fact(fact - 1);\n" + "}\n" + "string table = \"t\";\n" + "string table_ = \"u\";\n" + "f3(string f2) { llOwnerSay(f2); }\n" + "f2() { llOwnerSay(\"f2\"); }\n" + "f4() { f2(); string f2 = \"x\"; llOwnerSay(f2); }\n" + "default { touch_start(integer n) {\n" + " integer was = 1;\n" + " integer k = was++ + was;\n" + " llOwnerSay(table + table_ + (string)(fact(n) + k));\n" + " f3(\"a\"); f4();\n" + "} }\n"); + CAPTURE(own.text); + { + INFO("a parameter named as its function"); CHECK(has(own, "local function fact(fact_)")); CHECK(has(own, "return fact_ * fact(fact_ - 1)")); } { - INFO("a global the mark gave another's name: " + own.text); + INFO("a global the mark gave another's name"); CHECK(has(own, "local table_2 = \"t\"\n")); CHECK(has(own, "local table_ = \"u\"\n")); CHECK(has(own, "print(`{table_2}{table_}{fact(n) + k}`)")); } - UNSCOPED_INFO("nothing hidden, nothing renamed: " + own.text); + UNSCOPED_INFO("nothing hidden, nothing renamed"); CHECK(has(own, "local function f3(f2)\n print(f2)\n")); - UNSCOPED_INFO("a call before the local, not in its reach: " + own.text); + UNSCOPED_INFO("a call before the local, not in its reach"); CHECK(has(own, " f2()\n local f2 = \"x\"\n print(f2)\n")); - UNSCOPED_INFO("what a step was, under a name the script does not have: " + own.text); + UNSCOPED_INFO("what a step was, under a name the script does not have"); CHECK(has(own, "(function() local was2 = was; was += 1 return was2 end)() + was")); - checksClean(own); + require_checks_clean(own); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a state's handlers kept apart by blank lines only where the LSL had them", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("default { state_entry() { state collect; } }\n" - "state collect {\n" - " changed(integer change) {\n" - " if (change & CHANGED_INVENTORY) {\n" - " llOwnerSay(\"c\");\n" - " }\n" - " }\n" - "\n" - " on_rez(integer param) {\n" - " llOwnerSay(\"r\");\n" - " }\n" - " touch_start(integer n) { llOwnerSay(\"t\"); }\n" - "\n" - " // listening\n" - " listen(integer c, string name, key id, string msg) { llOwnerSay(msg); }\n" - "}\n"); - UNSCOPED_INFO("Tapple's, apart: " + r.text); + const ALLSLToSLua::Result r = require_converted("default { state_entry() { state collect; } }\n" + "state collect {\n" + " changed(integer change) {\n" + " if (change & CHANGED_INVENTORY) {\n" + " llOwnerSay(\"c\");\n" + " }\n" + " }\n" + "\n" + " on_rez(integer param) {\n" + " llOwnerSay(\"r\");\n" + " }\n" + " touch_start(integer n) { llOwnerSay(\"t\"); }\n" + "\n" + " // listening\n" + " listen(integer c, string name, key id, string msg) { llOwnerSay(msg); }\n" + "}\n"); + CAPTURE(r.text); + UNSCOPED_INFO("Tapple's, apart"); CHECK(has(r, " end\n end,\n\n on_rez = function(param)\n")); - UNSCOPED_INFO("together: " + r.text); + UNSCOPED_INFO("together"); CHECK(has(r, " print(\"r\")\n end,\n touch_start = function(detected)\n")); - UNSCOPED_INFO("one blank line over a comment over it: " + r.text); + UNSCOPED_INFO("one blank line over a comment over it"); CHECK(has(r, " end,\n\n -- listening\n listen = function(")); - UNSCOPED_INFO("none at the table's top: " + r.text); + UNSCOPED_INFO("none at the table's top"); CHECK(has(r, "states.collect = {\n changed = function(change)\n")); - checksClean(r); + require_checks_clean(r); } TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: LSL's right-side-first order noted only where it could show", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("key gSender;\n" - "string gCache;\n" - "string myInfo() { return (string)llGetKey(); }\n" - "string say() { llOwnerSay(\"x\"); return \"y\"; }\n" - "string cached() { return gCache; }\n" - "string fill() { gCache = \"z\"; return gCache; }\n" - "string other() { return \"o\"; }\n" - "default { touch_start(integer n) {\n" - " llOwnerSay(\"activeRequestor: \" + llKey2Name(gSender) + \"; llGetKey: \" + llKey2Name(myInfo()));\n" - " llOwnerSay(llKey2Name(gSender) + say());\n" - " llOwnerSay(cached() + fill());\n" - " llOwnerSay(other() + fill());\n" - "} }\n"); - { - INFO("Tapple's, two sides that only read: " + r.text); + const ALLSLToSLua::Result r = require_converted("key gSender;\n" + "string gCache;\n" + "string myInfo() { return (string)llGetKey(); }\n" + "string say() { llOwnerSay(\"x\"); return \"y\"; }\n" + "string cached() { return gCache; }\n" + "string fill() { gCache = \"z\"; return gCache; }\n" + "string other() { return \"o\"; }\n" + "default { touch_start(integer n) {\n" + " llOwnerSay(\"activeRequestor: \" + llKey2Name(gSender) + \"; llGetKey: \" + llKey2Name(myInfo()));\n" + " llOwnerSay(llKey2Name(gSender) + say());\n" + " llOwnerSay(cached() + fill());\n" + " llOwnerSay(other() + fill());\n" + "} }\n"); + CAPTURE(r.text); + { + INFO("Tapple's, two sides that only read"); CHECK_FALSE(noted(r, "SluaRightFirst")); CHECK_FALSE(noted(r, "SluaRightFirstWorld", "myInfo()")); } { - INFO("a call that changes the world, named with the other: " + r.text); + INFO("a call that changes the world, named with the other"); CHECK(noted(r, "SluaRightFirstWorld", "say()")); CHECK(has(r, "-- LSL: LSL ran the right side of this before the left, and Luau runs the left first: say() may change what " "llKey2Name() does or finds outside the script.\n print(ll.Key2Name(gSender) .. say())")); } { - INFO("a call that sets what the other's reads: " + r.text); + INFO("a call that sets what the other's reads"); CHECK(noted(r, "SluaRightFirstCall", "fill()")); CHECK(has(r, "fill() sets gCache, which the other side reads or sets too.\n print(cached() .. fill())")); } - UNSCOPED_INFO("noted twice, no more: " + r.text); + UNSCOPED_INFO("noted twice, no more"); CHECK(count(r, "LSL ran the right side") == 2); - checksClean(r); + require_checks_clean(r); } + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: with keyTextAny off text for a key passes bare and the checker marks it", "[ALLSLToSLua]") { const std::string lsl = "key inventorySender;\n" @@ -4283,17 +4352,17 @@ namespace ALLSLToSLua::Options o; o.keyTextAny = false; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, o); - UNSCOPED_INFO("converted"); + CAPTURE(r.text); CHECK(r.converted); { - INFO("as it is: " + r.text); + INFO("as it is"); CHECK(has(r, "ll.Key2Name(myInfo()))")); CHECK_FALSE(has(r, "myInfo() :: any")); } - UNSCOPED_INFO("a UUID written out, still a uuid: " + r.text); + UNSCOPED_INFO("a UUID written out, still a uuid"); CHECK(has(r, "ll.Key2Name(uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"))")); { - INFO("the note with the ways: " + r.text); + INFO("the note with the ways"); CHECK(noted(r, "SluaKeyAsTextBare")); CHECK_FALSE(noted(r, "SluaKeyAsText")); CHECK(has(r, "`:: any` quiets it, uuid() stops the script on text that is no UUID")); @@ -4310,7 +4379,7 @@ namespace // The line the text for a key is on, as marked says it. const auto lineOf = [](const std::string& slua) { const size_t at = slua.find("ll.Key2Name(myInfo())"); - UNSCOPED_INFO("written: " + slua); + CAPTURE(slua); REQUIRE(at != std::string::npos); return llformat("[%d] ", static_cast(std::count(slua.begin(), slua.begin() + at, '\n')) + 1); }; @@ -4324,87 +4393,92 @@ namespace const ALLSLToSLua::Result typed = ALLSLToSLua::convert(lsl, o); const std::string said = marked(typed.text); { - INFO("typed, the text for a key marked, alone:\n" + said + "---\n" + typed.text); + INFO("typed, the text for a key marked, alone"); + CAPTURE(said, typed.text); CHECK(said.rfind(lineOf(typed.text), 0) == 0); CHECK(std::count(said.begin(), said.end(), '\n') == 1); } const std::string strict = "--!strict\n" + r.text; const std::string loud = marked(strict); { - INFO("strict, the same:\n" + loud + "---\n" + strict); + INFO("strict, the same"); + CAPTURE(loud, strict); CHECK(loud.rfind(lineOf(strict), 0) == 0); CHECK(std::count(loud.begin(), loud.end(), '\n') == 1); } } } + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: a key given no text holds text as LSL's did; a uuid key compared as one", "[ALLSLToSLua]") { - const ALLSLToSLua::Result r = convert("key gUser = \"\";\n" - "default {\n" - " state_entry() {\n" - " key k = \"\";\n" - " llOwnerSay((string)(k == \"\"));\n" - " }\n" - " touch_start(integer n) {\n" - " if (gUser) llOwnerSay(\"busy\");\n" - " gUser = llDetectedKey(0);\n" - " llMessageLinked(LINK_SET, 0, \"\", gUser);\n" - " gUser = \"\";\n" - " llMessageLinked(LINK_SET, 0, \"\", gUser);\n" - " llOwnerSay(\"user: \" + (string)gUser);\n" - " }\n" - " link_message(integer s, integer num, string m, key id) {\n" - " if (id == \"\") llOwnerSay(\"nobody\");\n" - " if (id) llOwnerSay(\"someone\");\n" - " llOwnerSay(llList2CSV([id, gUser]));\n" - " key d;\n" - " if (d == \"\") llOwnerSay(\"no key yet\");\n" - " if (\"\" != d) llOwnerSay(\"some\");\n" - " if (d == \"a2e76fcd-9360-4f6d-a924-938f923df11d\") llOwnerSay(\"that one\");\n" - " if (d == \"none\") llOwnerSay(\"text\");\n" - " if (NULL_KEY != \"\") llOwnerSay(\"NULL_KEY has text\");\n" - " }\n" - "}\n"); - { - INFO("Tapple's, text: " + r.text); + const ALLSLToSLua::Result r = require_converted("key gUser = \"\";\n" + "default {\n" + " state_entry() {\n" + " key k = \"\";\n" + " llOwnerSay((string)(k == \"\"));\n" + " }\n" + " touch_start(integer n) {\n" + " if (gUser) llOwnerSay(\"busy\");\n" + " gUser = llDetectedKey(0);\n" + " llMessageLinked(LINK_SET, 0, \"\", gUser);\n" + " gUser = \"\";\n" + " llMessageLinked(LINK_SET, 0, \"\", gUser);\n" + " llOwnerSay(\"user: \" + (string)gUser);\n" + " }\n" + " link_message(integer s, integer num, string m, key id) {\n" + " if (id == \"\") llOwnerSay(\"nobody\");\n" + " if (id) llOwnerSay(\"someone\");\n" + " llOwnerSay(llList2CSV([id, gUser]));\n" + " key d;\n" + " if (d == \"\") llOwnerSay(\"no key yet\");\n" + " if (\"\" != d) llOwnerSay(\"some\");\n" + " if (d == \"a2e76fcd-9360-4f6d-a924-938f923df11d\") llOwnerSay(\"that one\");\n" + " if (d == \"none\") llOwnerSay(\"text\");\n" + " if (NULL_KEY != \"\") llOwnerSay(\"NULL_KEY has text\");\n" + " }\n" + "}\n"); + CAPTURE(r.text); + { + INFO("Tapple's, text"); CHECK(has(r, "local k = \"\"")); CHECK(has(r, "print(tostring(if k == \"\" then 1 else 0))")); } { - INFO("a global given no text, text: " + r.text); + INFO("a global given no text, text"); CHECK(has(r, "local gUser = \"\"")); CHECK(has(r, "gUser = tostring(detected[1]:getKey())")); } - UNSCOPED_INFO("sent as text, no text and all: " + r.text); + UNSCOPED_INFO("sent as text, no text and all"); CHECK(count(r, "ll.MessageLinked(LINK_SET, 0, \"\", gUser)") == 2); - UNSCOPED_INFO("made text as it is: " + r.text); + UNSCOPED_INFO("made text as it is"); CHECK(has(r, "print(\"user: \" .. gUser)")); - UNSCOPED_INFO("its truth as LSL's key's: " + r.text); + UNSCOPED_INFO("its truth as LSL's key's"); CHECK(has(r, "if (touuid(gUser) or NULL_KEY).istruthy then")); { - INFO("the id, text: " + r.text); + INFO("the id, text"); CHECK(has(r, "if id == \"\" then")); CHECK(has(r, "if (touuid(id) or NULL_KEY).istruthy then")); } { - INFO("keys held as text, in a list as text: " + r.text); + INFO("keys held as text, in a list as text"); CHECK(has(r, "{ id, gUser }")); CHECK_FALSE(has(r, "{ uuid(")); } { - INFO("a uuid against no text, a uuid: " + r.text); + INFO("a uuid against no text, a uuid"); CHECK(has(r, "local d = NULL_KEY")); CHECK(has(r, "if d == uuid(\"\") then")); CHECK(has(r, "if uuid(\"\") ~= d then")); } - UNSCOPED_INFO("a UUID written out: " + r.text); + UNSCOPED_INFO("a UUID written out"); CHECK(has(r, "if d == uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\") then")); - UNSCOPED_INFO("other text, as text: " + r.text); + UNSCOPED_INFO("other text, as text"); CHECK(has(r, "if tostring(d) == \"none\" then")); - UNSCOPED_INFO("NULL_KEY, as LSL's text: " + r.text); + UNSCOPED_INFO("NULL_KEY, as LSL's text"); CHECK(has(r, "if tostring(NULL_KEY) ~= \"\" then")); - checksClean(r); + require_checks_clean(r); } + TEST_CASE_METHOD(allsltoslua_data, "ALLSLToSLua: with uuidKeys off the script's own keys are held as text as in LSL", "[ALLSLToSLua]") { const std::string lsl = "key gOwner;\n" @@ -4431,65 +4505,68 @@ namespace o.uuidKeys = false; o.types = true; const ALLSLToSLua::Result r = ALLSLToSLua::convert(lsl, o); + CAPTURE(r.text); UNSCOPED_INFO("converted: " + (r.problems.empty() ? std::string() : r.problems.front().message)); CHECK(r.converted); { - INFO("Tapple's: " + r.text); + INFO("Tapple's"); CHECK(has(r, "local k: string = \"\"\n")); CHECK(has(r, "print(tostring(if k == \"\" then 1 else 0))")); } { - INFO("a global, LSL's default: " + r.text); + INFO("a global, LSL's default"); CHECK(has(r, "local gOwner: string = \"\"\n")); CHECK(has(r, "gOwner = tostring(ll.GetOwner())")); } - UNSCOPED_INFO("what a function gives: " + r.text); + UNSCOPED_INFO("what a function gives"); CHECK(has(r, "local function pick(): string\n return tostring(ll.GetOwner())")); { - INFO("a parameter: " + r.text); + INFO("a parameter"); CHECK(has(r, "local function mine(who: string): boolean")); CHECK(has(r, "who == gOwner")); } { - INFO("a cast, the text as it is: " + r.text); + INFO("a cast, the text as it is"); CHECK(has(r, "local cast: string = s\n")); CHECK(has(r, "print(cast .. \"\")")); CHECK_FALSE(has(r, "uuid(s)")); CHECK_FALSE(has(r, "uuid(\"\")")); } - UNSCOPED_INFO("its truth as LSL's: " + r.text); + UNSCOPED_INFO("its truth as LSL's"); CHECK(has(r, "if (touuid(gOwner) or NULL_KEY).istruthy then")); - UNSCOPED_INFO("given ll as it is: " + r.text); + UNSCOPED_INFO("given ll as it is"); CHECK(has(r, "ll.Dialog(gOwner :: any, \"hi\"")); - UNSCOPED_INFO("against NULL_KEY, as text: " + r.text); + UNSCOPED_INFO("against NULL_KEY, as text"); CHECK(has(r, "gOwner ~= tostring(NULL_KEY)")); { - INFO("a handler's key, made text as it begins: " + r.text); + INFO("a handler's key, made text as it begins"); CHECK(has(r, "function(c: number, n: string, idKey: uuid, m: string)")); CHECK(has(r, "local id: string = tostring(idKey)")); CHECK(has(r, "if id == gOwner then")); } { - INFO("in a list, text as it is: " + r.text); + INFO("in a list, text as it is"); CHECK(has(r, "local values: { any } = { odd, k, tostring(NULL_KEY) }")); CHECK_FALSE(has(r, "{ uuid(")); } - UNSCOPED_INFO("nothing said of NULL_KEY: " + r.text); + UNSCOPED_INFO("nothing said of NULL_KEY"); CHECK_FALSE(noted(r, "SluaKeyEmpty")); - checksClean(r); + require_checks_clean(r); const ALLSLToSLua::Result close = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); + CAPTURE(close.text); { - INFO("closeToLSL the same: " + close.text); + INFO("closeToLSL the same"); CHECK(close.converted); CHECK(has(close, "local gOwner = \"\"\n")); CHECK(has(close, "local cast = s\n")); } - checksClean(close); + require_checks_clean(close); - const ALLSLToSLua::Result uuids = convert(lsl); + const ALLSLToSLua::Result uuids = require_converted(lsl); + CAPTURE(uuids.text); { - INFO("uuids by default: " + uuids.text); + INFO("uuids by default"); CHECK(has(uuids, "local gOwner = NULL_KEY\n")); CHECK(has(uuids, "local cast = uuid(s)")); } diff --git a/indra/alscript/tests/alluauconfig_test.cpp b/indra/alscript/tests/alluauconfig_test.cpp index 7260e603ed..f26727fe6e 100644 --- a/indra/alscript/tests/alluauconfig_test.cpp +++ b/indra/alscript/tests/alluauconfig_test.cpp @@ -40,9 +40,7 @@ namespace { ALLuauConfig config; std::string error; - UNSCOPED_INFO("parses: " + error); REQUIRE(ALLuauConfig::parse("{\n \"languageMode\": \"strict\",\n \"aliases\": { \"Lib\": \"./lib\", \"shared\": \"../shared\", \"abs\": \"/opt/lua\" }\n}\n", config, error)); - UNSCOPED_INFO("three aliases"); REQUIRE(config.aliases.size() == size_t(3)); UNSCOPED_INFO("by lower-case name"); CHECK(config.aliases["lib"] == std::string("./lib")); @@ -50,7 +48,6 @@ namespace CHECK(config.aliases["shared"] == std::string("../shared")); UNSCOPED_INFO("an absolute one"); CHECK(config.aliases["abs"] == std::string("/opt/lua")); - UNSCOPED_INFO("the mode"); CHECK(config.mode == std::string("strict")); { INFO("no mode where none is said"); @@ -69,11 +66,9 @@ namespace TEST_CASE("alluauconfig: aliasOf reads the @ name up to the slash; absolute paths and studio aliases", "[alluauconfig]") { std::string alias, rest; - UNSCOPED_INFO("found"); REQUIRE(ALLuauConfig::aliasOf("@Lib/util/strings", alias, rest)); UNSCOPED_INFO("lower case"); CHECK(alias == std::string("lib")); - UNSCOPED_INFO("the rest"); CHECK(rest == std::string("util/strings")); { INFO("the alias alone"); @@ -128,14 +123,13 @@ namespace UNSCOPED_INFO("one that cannot be: lib"); CHECK(ALLuauConfig::studioAliasFor("self", {}) == std::string("lib")); } + TEST_CASE("alluauconfig: lints and globals come out as Luau reads them with its default lints", "[alluauconfig]") { ALLuauConfig config; std::string error; const ALLuauConfig defaults; - UNSCOPED_INFO("parses: " + error); REQUIRE(ALLuauConfig::parse("{\n \"lint\": { \"LocalUnused\": false, \"LocalShadow\": true },\n \"globals\": [\"Alchemy\", \"grid\"]\n}\n", config, error)); - UNSCOPED_INFO("two globals"); REQUIRE(config.globals.size() == size_t(2)); UNSCOPED_INFO("in order"); CHECK(config.globals[0] == std::string("Alchemy")); @@ -255,7 +249,6 @@ namespace const std::vector chain{ nearby, middle, broken, root }; ALLuauConfig merged; - UNSCOPED_INFO("parsed"); REQUIRE(ALLuauConfig::parseChain(chain, merged)); UNSCOPED_INFO("the nearest mode"); CHECK(merged.mode == std::string("nonstrict")); @@ -292,7 +285,7 @@ namespace } ALLuauConfig out; std::string error; - UNSCOPED_INFO("beside Luau's: " + error); + UNSCOPED_INFO("beside Luau's"); REQUIRE(ALLuauConfig::parse("{ \"languageMode\": \"strict\", \"lint\": { \"LocalUnused\": false, \"SlCompoundAssign\": false } }", out, error)); { INFO("the rest read"); @@ -302,30 +295,30 @@ namespace UNSCOPED_INFO("ours off"); CHECK((out.slLints & compound) == 0); { - INFO("alone: " + error); + INFO("alone"); CHECK(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false } }", out, error)); CHECK((out.slLints & compound) == 0); } { - INFO("first: " + error); + INFO("first"); CHECK(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false, \"LocalUnused\": false }, \"globals\": [\"g\"] }", out, error)); CHECK((out.slLints & compound) == 0); CHECK(out.globals == std::vector{ "g" }); CHECK((out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); } { - INFO("with // comments, over lines: " + error); + INFO("with // comments, over lines"); CHECK(ALLuauConfig::parse("{\n // c style\n \"lint\": {\n \"LocalUnused\": true,\n \"SlCompoundAssign\": false\n }\n}", out, error)); CHECK((out.slLints & compound) == 0); } { - INFO("* for ours too: " + error); + INFO("* for ours too"); CHECK(ALLuauConfig::parse("{ \"lint\": { \"*\": false } }", out, error)); CHECK(out.slLints == 0); CHECK((out.lints & ALLuauConfig::lintBit("LocalUnused")) == 0); } { - INFO("then one on: " + error); + INFO("then one on"); CHECK(ALLuauConfig::parse("{ \"lint\": { \"*\": false, \"SlCompoundAssign\": true } }", out, error)); CHECK(out.slLints == compound); } @@ -340,7 +333,7 @@ namespace CHECK(error == "Bad setting 'fatal'. Valid options are true and false"); } { - INFO("Luau's words on the rest keep their lines: " + error); + INFO("Luau's words on the rest keep their lines"); CHECK_FALSE(ALLuauConfig::parse("{ \"lint\": { \"SlCompoundAssign\": false },\n \"globals\": 3 }", out, error)); REQUIRE(error.find("line 2") != std::string::npos); } @@ -349,7 +342,7 @@ namespace base.slLints = 0; base.slFatalLints = compound; { - INFO("the base's where the file does not say: " + error); + INFO("the base's where the file does not say"); CHECK(ALLuauConfig::parse("{ \"languageMode\": \"strict\" }", out, error, &base)); CHECK(out.slLints == 0); CHECK(out.slFatalLints == compound); @@ -373,12 +366,10 @@ namespace " other = { tool = true },\n" "}\n"; std::string json, error; - UNSCOPED_INFO("ran: " + error); REQUIRE(ALLuauConfigScript::asLuaurc(source, json, error)); ALLuauConfig config; UNSCOPED_INFO("and parses as a .luaurc: " + error + " in " + json); REQUIRE(ALLuauConfig::parse(json, config, error)); - UNSCOPED_INFO("its mode"); CHECK(config.mode == std::string("strict")); { INFO("its aliases, by name in lower case"); @@ -415,7 +406,7 @@ namespace // A .luaurc's strings are taken byte for byte: a backslash is kept // as it is, and what one cannot hold is refused. { - INFO("a backslash kept: " + error); + INFO("a backslash kept"); CHECK(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { q = 'C:\\\\lua\\\\lib' } } }", json, error)); CHECK(ALLuauConfig::parse(json, config, error)); CHECK(config.aliases["q"] == "C:\\lua\\lib"); @@ -461,8 +452,7 @@ namespace UNSCOPED_INFO("not a table"); CHECK((ALLuauConfigScript::asLuaurc("return 'x'", json, error), error) == std::string("configuration did not return a table")); UNSCOPED_INFO("luau not a table"); - CHECK((ALLuauConfigScript::asLuaurc("return { luau = 3 }", json, error), error) == - std::string("configuration value for key \"luau\" must be a table")); + CHECK((ALLuauConfigScript::asLuaurc("return { luau = 3 }", json, error), error) == std::string("configuration value for key \"luau\" must be a table")); { INFO("a function where a value goes"); CHECK_FALSE(ALLuauConfigScript::asLuaurc("return { luau = { aliases = { x = print } } }", json, error)); @@ -506,29 +496,28 @@ namespace const std::string forever = "while true do end -- " + LLUUID::generateNewID().asString(); const auto started = std::chrono::steady_clock::now(); UNSCOPED_INFO("a loop that never ends, stopped"); - CHECK((ALLuauConfigScript::asLuaurc(forever, json, error), error) == - std::string("configuration execution timed out")); + CHECK((ALLuauConfigScript::asLuaurc(forever, json, error), error) == std::string("configuration execution timed out")); const F64 took = std::chrono::duration(std::chrono::steady_clock::now() - started).count(); { - INFO(llformat("stopped at its time, not long after: %.2f s", took)); + INFO("stopped at its time, not long after"); CHECK(took >= ALLuauConfigScript::MOST_SECONDS); CHECK(took < ALLuauConfigScript::MOST_SECONDS + 2.0); } UNSCOPED_INFO("asked again at once, kept for a while: no second wait"); CHECK((ALLuauConfigScript::asLuaurc(forever, json, error), - std::chrono::duration(std::chrono::steady_clock::now() - started).count() < took + 0.1)); + std::chrono::duration(std::chrono::steady_clock::now() - started).count() < took + 0.1)); // table.move's copy is a loop in C the deadline does not reach, and // nils moved into a table that holds none take no memory: held to so // many elements, at once. const auto moving = std::chrono::steady_clock::now(); { - INFO("two billion nils moved, refused at once: " + error); + INFO("two billion nils moved, refused at once"); CHECK_FALSE(ALLuauConfigScript::asLuaurc("table.move({}, 1, 2147483646, 2) return {}", json, error)); 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: " + error); + INFO("a move within it, as Luau's"); CHECK(ALLuauConfigScript::asLuaurc("return { luau = { globals = table.move({ 'a', 'b' }, 1, 2, 2, { 'z' }) } }", json, error)); CHECK(json == "{\"globals\": [\"z\", \"a\", \"b\"]}"); } @@ -546,14 +535,14 @@ 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: " + error); + INFO("no ares"); CHECK(ALLuauConfigScript::asLuaurc("return { luau = { languagemode = ares and 'strict' or 'nonstrict' } }", json, error)); REQUIRE(json.find("nonstrict") != std::string::npos); } { - INFO("Luau's libraries: " + error); + 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)); + json, error)); CHECK(json == "{\"globals\": [\"A\", \"2\", \"xy\", \"b\"]}"); } } @@ -581,7 +570,7 @@ namespace const char* const working = "return { luau = { languagemode = 'strict' } }"; error = "from before"; { - INFO("a success: " + json); + INFO("a success"); CHECK(ALLuauConfigScript::asLuaurc(working, json, error)); CHECK(json == "{\"languageMode\": \"strict\"}"); } diff --git a/indra/alscript/tests/alluaufragment_test.cpp b/indra/alscript/tests/alluaufragment_test.cpp index 819673e815..ffec37d75f 100644 --- a/indra/alscript/tests/alluaufragment_test.cpp +++ b/indra/alscript/tests/alluaufragment_test.cpp @@ -495,16 +495,17 @@ namespace } } - void report(const char* corpus, const Tally& tally, bool answers = true) + void require_answers_as_whole(const char* corpus, const Tally& tally, bool answers = true) { std::printf("alluaufragment, %s solver, %s: %zu places, %zu answered over a fragment; told apart by a type beside a name %zu, " "by which fit %zu, by a few names %zu, by a signature's parameters %zu\n", newSolver ? "new" : "old", corpus, tally.asked, tally.answered, tally.types, tally.fits, tally.few, tally.generics); - UNSCOPED_INFO(std::string(corpus) + ": the fragment answers as the whole script does:\n" + tally.faults); + CAPTURE(corpus); + UNSCOPED_INFO("the fragment answers as the whole script does:\n" + tally.faults); REQUIRE(tally.faults.empty()); if (answers) { - UNSCOPED_INFO(std::string(corpus) + ": most places answered over a fragment"); + UNSCOPED_INFO("most places answered over a fragment"); REQUIRE(tally.answered * 2 > tally.asked); } } @@ -527,7 +528,7 @@ namespace { ask((*it)["name"].asString(), probesOf((*it)["body"].asString(), 12), tally); } - report("the templates", tally); + require_answers_as_whole("the templates", tally); } TEST_CASE_METHOD(alluaufragment_data, "alluaufragment: over Tailslide's scripts as SLua the fragment answers as the whole does", "[alluaufragment]") @@ -560,7 +561,7 @@ namespace ask(name, probesOf(converted.text, 6), tally); } } - report("Tailslide's scripts", tally); + require_answers_as_whole("Tailslide's scripts", tally); } TEST_CASE_METHOD(alluaufragment_data, "alluaufragment: over an expanded script with modules the fragment answers as the whole does", "[alluaufragment]") @@ -600,13 +601,12 @@ namespace { modules.reaches.push_back({ resolved.from, resolved.name, resolved.path }); } - UNSCOPED_INFO("its modules"); REQUIRE(modules.modules.size() == 2); fragment.setModules(modules); whole.setModules(modules); Tally tally; ask("test_require", probesOf(made.apart.script.text, 12), tally); - report("an expanded script", tally); + require_answers_as_whole("an expanded script", tally); // A module changed since the last check: util, which had a function // since taken out. ALLuauService::Modules before = modules; @@ -627,7 +627,7 @@ namespace fragment.setModules(modules); ask("test_require, util changed", { probe }, changed, /*settled*/ true); } - report("an expanded script, a module changed since", changed, false); + require_answers_as_whole("an expanded script, a module changed since", changed, false); UNSCOPED_INFO("a module changed since: every place asked of the whole script"); CHECK(changed.answered == size_t(0)); fragment.setModules({}); @@ -685,9 +685,10 @@ namespace const std::string text = base + place.line + "\n"; fragment.check(base); fragment.warm(base); - UNSCOPED_INFO(std::string(place.line) + ": over a fragment"); + CAPTURE(place.line); + UNSCOPED_INFO("over a fragment"); REQUIRE(stubbed(fragment.complete(text, 1, place.column)) == place.offered); - UNSCOPED_INFO(std::string(place.line) + ": over the whole script"); + UNSCOPED_INFO("over the whole script"); REQUIRE(stubbed(whole.complete(text, 1, place.column)) == place.offered); } } @@ -745,11 +746,9 @@ namespace ask(above + place.checked, place.typed, [&](ALLuauService& service, const std::string& text, S32 line, const char* over) { const std::vector found = service.complete(text, line, place.column); const auto test = std::find_if(found.begin(), found.end(), [](const ALScriptCompletion& c) { return c.text == "test"; }); - { - INFO(llformat("round %d, %s, over %s: the number", round, place.typed, over)); - REQUIRE(test != found.end()); - REQUIRE(test->kind == ALScriptSymbolKind::Variable); - } + CAPTURE(round, place.typed, over); + REQUIRE(test != found.end()); + REQUIRE(test->kind == ALScriptSymbolKind::Variable); }); } // A function of two strings declared over one of a number: a @@ -757,11 +756,9 @@ namespace ask(above + "local function call(n: number)\nend\nlocal call = function(s: string, t: string)\nend\n", "call(\"x\", \"y\")", [&](ALLuauService& service, const std::string& text, S32 line, const char* over) { const ALScriptSignature found = service.signature(text, line, 5); - { - INFO(llformat("round %d, a call, over %s: the two strings, not %s", round, over, found.label.c_str())); - REQUIRE(found.found); - REQUIRE(found.parameters.size() == 2); - } + CAPTURE(round, over, found.label); + REQUIRE(found.found); + REQUIRE(found.parameters.size() == 2); }); } UNSCOPED_INFO("answered over a fragment"); diff --git a/indra/alscript/tests/alluaunavigation_test.cpp b/indra/alscript/tests/alluaunavigation_test.cpp index 0ebdf683b9..8857ddf2d3 100644 --- a/indra/alscript/tests/alluaunavigation_test.cpp +++ b/indra/alscript/tests/alluaunavigation_test.cpp @@ -139,12 +139,8 @@ namespace CHECK(loaded); open("a", true); ALScriptReferences refs = service.references(SCRIPT_A, 5, 12); // `twice` in `util.twice(2)` - { - INFO("found"); - REQUIRE(refs.found); - CHECK(refs.name == "twice"); - } - UNSCOPED_INFO(llformat("a function, not kind %d", (int)refs.kind)); + REQUIRE(refs.found); + CHECK(refs.name == "twice"); CHECK(refs.kind == ALScriptSymbolKind::Function); UNSCOPED_INFO("declared in the module"); CHECK(refs.definitionFile == std::string(UTIL)); @@ -168,8 +164,7 @@ namespace refs = service.references(SCRIPT_A, 6, 12); // `limit`, assigned UNSCOPED_INFO("a field assigned: declared where it is assigned"); - CHECK(refs.definitionFile + "@" + where(refs.definition) == - std::string(UTIL) + "@6:2"); + CHECK(refs.definitionFile + "@" + where(refs.definition) == std::string(UTIL) + "@6:2"); refs = service.references(SCRIPT_A, 5, 29); // `double`, a module that requires the other UNSCOPED_INFO("each module its own key"); CHECK(refs.definitionFile + "@" + where(refs.definition) == std::string(SHAPES) + "@2:11"); @@ -196,18 +191,13 @@ namespace CHECK(loaded); open("a", true); const ALScriptReferences refs = service.references(SCRIPT_A, 4, 15); // `Point` in `util.Point` - { - INFO("found"); - REQUIRE(refs.found); - CHECK(refs.name == "Point"); - } - UNSCOPED_INFO("a type"); + REQUIRE(refs.found); + CHECK(refs.name == "Point"); CHECK(refs.kind == ALScriptSymbolKind::Type); UNSCOPED_INFO("declared in the module"); CHECK(refs.definitionFile + "@" + where(refs.definition) == std::string(UTIL) + "@2:12"); UNSCOPED_INFO("each place: the script's, the module's"); CHECK(placesOf(refs) == std::string("4:14 ") + UTIL + "@2:12 " + UTIL + "@7:21"); - UNSCOPED_INFO("renamable"); CHECK(refs.renamable); // The local the module was required as is named in a type's prefix @@ -225,20 +215,16 @@ namespace open("a", true); ALScriptReferences refs = service.references(SCRIPT_A, 5, 12); UNSCOPED_INFO("the script's, the module's own, and the other module's"); - CHECK(placesOf(refs) == - std::string("5:11 ") + UTIL + "@3:11 " + SHAPES + "@3:16"); + CHECK(placesOf(refs) == std::string("5:11 ") + UTIL + "@3:11 " + SHAPES + "@3:16"); refs = service.references(SCRIPT_A, 6, 12); UNSCOPED_INFO("an assigned one the same"); CHECK(placesOf(refs) == std::string("6:11 ") + UTIL + "@6:2 " + SHAPES + "@3:32"); // The script's own table's field of the same name: the script's alone. refs = service.references(SCRIPT_A, 5, 46); - { - INFO("found"); - REQUIRE(refs.found); - CHECK(refs.name == "twice"); - REQUIRE(refs.definitionFile.empty()); - } + REQUIRE(refs.found); + CHECK(refs.name == "twice"); + REQUIRE(refs.definitionFile.empty()); UNSCOPED_INFO("declared in the script's constructor"); CHECK(where(refs.definition) == std::string("3:16")); UNSCOPED_INFO("and used once; none of the module's"); @@ -275,18 +261,13 @@ namespace CHECK(from_module.definitionFile == UTIL); } UNSCOPED_INFO("the script's use, through the require, and the module's declaration"); - CHECK(placesOf(from_module) == - std::string("1:11 ") + UTIL + "@3:11"); + CHECK(placesOf(from_module) == std::string("1:11 ") + UTIL + "@3:11"); // A module's local is its own alone. const ALScriptReferences local = service.references(SCRIPT_B, 1, 6, UTIL); // `M` in `local M = {}` - { - INFO("found"); - REQUIRE(local.found); - CHECK(local.name == "M"); - } + REQUIRE(local.found); + CHECK(local.name == "M"); UNSCOPED_INFO("the module's places alone"); - CHECK(placesOf(local) == - std::string(UTIL) + "@1:6 " + UTIL + "@3:9 " + UTIL + "@6:0 " + UTIL + "@7:9 " + UTIL + "@10:7"); + CHECK(placesOf(local) == std::string(UTIL) + "@1:6 " + UTIL + "@3:9 " + UTIL + "@6:0 " + UTIL + "@7:9 " + UTIL + "@10:7"); service.setDocument(""); } @@ -296,7 +277,6 @@ namespace CHECK(loaded); open("a", true); ALScriptHover hover = service.hover(SCRIPT_A, 4, 15); // `Point` in `util.Point` - UNSCOPED_INFO("found"); REQUIRE(hover.found); { INFO("the module's, by its name and fields: " + hover.label); @@ -304,8 +284,7 @@ namespace REQUIRE(hover.label.find("x: number") != std::string::npos); } UNSCOPED_INFO("declared in the module"); - CHECK(hover.definitionFile + "@" + llformat("%d:%d", hover.definitionLine, hover.definitionColumn) == - std::string(UTIL) + "@2:12"); + CHECK(hover.definitionFile + "@" + llformat("%d:%d", hover.definitionLine, hover.definitionColumn) == std::string(UTIL) + "@2:12"); const std::string own = "--!strict\n" // 0 "type Pair = { first: T, second: T }\n" // 1 @@ -344,27 +323,20 @@ namespace "print(t.name, t[\"name\"], t['size'])\n"; // 3 open("keys", false); ALScriptReferences refs = service.references(text, 2, 4); // `size` in `t["size"]` - { - INFO("found"); - REQUIRE(refs.found); - CHECK(refs.name == "size"); - } + REQUIRE(refs.found); + CHECK(refs.name == "size"); UNSCOPED_INFO("each place: the record's key, the strings' insides, the name"); CHECK(placesOf(refs) == std::string("1:12 2:3 2:14 3:28")); UNSCOPED_INFO("declared by the record"); CHECK(where(refs.definition) == std::string("1:12")); - { - INFO("renamable"); - CHECK(refs.hasDefinition); - CHECK(refs.renamable); - } + CHECK(refs.hasDefinition); + CHECK(refs.renamable); refs = service.references(text, 3, 9); // `name` in `t.name` UNSCOPED_INFO("a key in brackets declares it"); CHECK(placesOf(refs) == std::string("1:24 3:8 3:17")); UNSCOPED_INFO("there, inside its quotes"); CHECK(where(refs.definition) == std::string("1:24")); - UNSCOPED_INFO("renamable"); CHECK(refs.renamable); UNSCOPED_INFO("asked at that key: the same"); CHECK(placesOf(service.references(text, 1, 25)) == std::string("1:24 3:8 3:17")); @@ -388,18 +360,12 @@ namespace "print(a:deposit(5))\n"; // 10 open("account", false); ALScriptReferences refs = service.references(account, 6, 18); // `deposit` in `function Account:deposit` - { - INFO("found"); - REQUIRE(refs.found); - CHECK(refs.name == "deposit"); - } + REQUIRE(refs.found); + CHECK(refs.name == "deposit"); UNSCOPED_INFO("the declaration and the object's call"); CHECK(placesOf(refs) == std::string("6:17 10:8")); - { - INFO("renamable"); - CHECK(refs.hasDefinition); - CHECK(refs.renamable); - } + CHECK(refs.hasDefinition); + CHECK(refs.renamable); refs = service.references(account, 10, 9); // `deposit` in `a:deposit(5)` UNSCOPED_INFO("from the call: the same"); CHECK(placesOf(refs) == std::string("6:17 10:8")); @@ -415,11 +381,8 @@ namespace "print(Base.greet(), o.greet)\n"; // 6 open("proxy", false); refs = service.references(proxy, 2, 15); // `greet` in `function Base.greet` - { - INFO("found, and declared"); - REQUIRE(refs.found); - CHECK(refs.hasDefinition); - } + REQUIRE(refs.found); + CHECK(refs.hasDefinition); UNSCOPED_INFO("an object's field of the name, its __index a function: not to rename"); CHECK_FALSE(refs.renamable); service.setDocument(""); diff --git a/indra/alscript/tests/alluauservice_test.cpp b/indra/alscript/tests/alluauservice_test.cpp index 0576b18be3..789220f451 100644 --- a/indra/alscript/tests/alluauservice_test.cpp +++ b/indra/alscript/tests/alluauservice_test.cpp @@ -108,7 +108,6 @@ namespace REQUIRE_FALSE(definitions.empty()); UNSCOPED_INFO("definitions load: " + error); CHECK(loaded); - UNSCOPED_INFO("service says so"); CHECK(service.hasDefinitions()); } @@ -131,10 +130,8 @@ namespace CHECK(loaded); ALScriptProblems problems = service.check("ll.Say(\"zero\", 0)\n"); UNSCOPED_INFO("an error: " + said(problems)); - CHECK(errors(problems) > 0); - UNSCOPED_INFO("from the type checker"); + REQUIRE(errors(problems) > 0); CHECK(problems.front().source == ALScriptProblem::Source::Types); - UNSCOPED_INFO("on the first line"); CHECK(problems.front().line == 0); } @@ -189,15 +186,13 @@ namespace CHECK(loaded); ALScriptProblems problems = service.check("local x = \n"); UNSCOPED_INFO("an error: " + said(problems)); - CHECK(errors(problems) > 0); - UNSCOPED_INFO("from the parser"); + REQUIRE(errors(problems) > 0); CHECK(problems.front().source == ALScriptProblem::Source::Parser); } TEST_CASE_METHOD(alluauservice_data, "alluauservice: without the definitions ll is unknown", "[alluauservice]") { ALLuauService bare; - UNSCOPED_INFO("nothing loaded"); CHECK_FALSE(bare.hasDefinitions()); ALScriptProblems problems = bare.check("ll.Say(0, \"x\")\n"); UNSCOPED_INFO("an error: " + said(problems)); @@ -210,11 +205,8 @@ namespace { ALLuauService bare; std::string why; - UNSCOPED_INFO("refused"); REQUIRE_FALSE(bare.loadDefinitions("declare ll: {\n", why)); - UNSCOPED_INFO("with a reason"); REQUIRE_FALSE(why.empty()); - UNSCOPED_INFO("and nothing loaded"); CHECK_FALSE(bare.hasDefinitions()); } @@ -231,7 +223,6 @@ namespace if (c.text == "Say") { say = true; - UNSCOPED_INFO("a function"); REQUIRE(c.kind == ALScriptSymbolKind::Function); UNSCOPED_INFO("with a signature: " + c.detail); REQUIRE(c.detail.find("(") != std::string::npos); @@ -251,7 +242,6 @@ namespace } UNSCOPED_INFO("the local, typed"); CHECK(local); - UNSCOPED_INFO("a keyword"); CHECK(keyword); } @@ -263,18 +253,15 @@ namespace std::stringstream json; json << in.rdbuf(); std::string docs_error; - UNSCOPED_INFO("docs loaded: " + docs_error); CHECK(service.loadDocs(json.str(), docs_error)); const std::string script = "local count = 1\nll.Say(0, tostring(count))\n"; ALScriptHover hover = service.hover(script, 1, 4); // on `Say` - UNSCOPED_INFO("found"); REQUIRE(hover.found); { INFO("the name and its type: " + hover.label); REQUIRE(hover.label.find("Say") != std::string::npos); REQUIRE(hover.label.find("(") != std::string::npos); } - UNSCOPED_INFO("documented: " + hover.documentation); REQUIRE_FALSE(hover.documentation.empty()); UNSCOPED_INFO("not the script's own"); CHECK_FALSE(hover.hasDefinition); @@ -293,7 +280,7 @@ namespace hover = service.hover(script, 1, 0); // on `ll` itself UNSCOPED_INFO("the table is found"); REQUIRE(hover.found); - UNSCOPED_INFO(llformat("and said in a glance, not %d characters", (int)hover.label.size())); + UNSCOPED_INFO("and said in a glance"); REQUIRE(hover.label.size() < 1200); UNSCOPED_INFO("with how many more there are: " + hover.label); REQUIRE(hover.label.find("more") != std::string::npos); @@ -310,16 +297,12 @@ namespace CHECK(loaded); const std::string script = "ll.Say(0, \"hi\")\n"; ALScriptSignature sig = service.signature(script, 0, 7); // in the first argument - UNSCOPED_INFO("found"); REQUIRE(sig.found); UNSCOPED_INFO("the call: " + sig.label); REQUIRE(sig.label.find("Say") != std::string::npos); - UNSCOPED_INFO("two parameters"); REQUIRE(sig.parameters.size() == size_t(2)); - UNSCOPED_INFO("at the first"); CHECK(sig.active == 0); sig = service.signature(script, 0, 11); // in the second - UNSCOPED_INFO("at the second"); CHECK(sig.active == 1); UNSCOPED_INFO("none outside a call"); REQUIRE_FALSE(service.signature(script, 0, 0).found); @@ -339,18 +322,15 @@ namespace ALScriptReferences refs = service.references(script, 1, 20); // on `count` in the call UNSCOPED_INFO("the local is found"); REQUIRE(refs.found); - UNSCOPED_INFO("by name"); CHECK(refs.name == std::string("count")); - UNSCOPED_INFO(llformat("a variable, not kind %d", (int)refs.kind)); CHECK(refs.kind == ALScriptSymbolKind::Variable); { - INFO(llformat("bound on the first line, not %d:%d-%d", refs.definition.line, refs.definition.column, refs.definition.endColumn)); + INFO("bound on the first line"); CHECK(refs.hasDefinition); CHECK(refs.definition.line == 0); CHECK(refs.definition.column == 6); CHECK(refs.definition.endColumn == 11); } - UNSCOPED_INFO("five places"); REQUIRE(refs.references.size() == size_t(5)); UNSCOPED_INFO("the first is the binding"); CHECK(refs.references[0] == refs.definition); @@ -368,14 +348,12 @@ namespace REQUIRE(refs.found); CHECK(refs.name == "Say"); } - UNSCOPED_INFO("a function"); CHECK(refs.kind == ALScriptSymbolKind::Function); { INFO("not bound in the script"); CHECK_FALSE(refs.hasDefinition); CHECK_FALSE(refs.renamable); } - UNSCOPED_INFO("used once"); REQUIRE(refs.references.size() == size_t(1)); refs = service.references(script, 5, 2); // on `f` in `M.f(count)` @@ -392,7 +370,6 @@ namespace } UNSCOPED_INFO("declared and called"); REQUIRE(refs.references.size() == size_t(2)); - UNSCOPED_INFO("and renamable"); CHECK(refs.renamable); refs = service.references(script, 0, 6); // on the binding itself @@ -464,7 +441,6 @@ namespace UNSCOPED_INFO("definitions loaded: " + error); CHECK(loaded); ALScriptProblems problems = service.check("ll.ay(0, \"hi\")\n"); - UNSCOPED_INFO("one problem"); REQUIRE_FALSE(problems.empty()); const std::string& message = problems.front().message; { @@ -474,7 +450,7 @@ namespace } UNSCOPED_INFO("and suggests Say: " + message); REQUIRE(message.find("'Say'") != std::string::npos); - UNSCOPED_INFO(llformat("in a glance, not %d characters", (int)message.size())); + UNSCOPED_INFO("in a glance"); REQUIRE(message.size() < 100); } @@ -677,6 +653,7 @@ namespace UNSCOPED_INFO("a builtin by its signature: " + hover.label); CHECK(hover.label.rfind(newSolver ? "function print<" : "function print(", 0) == 0); } + TEST_CASE_METHOD(alluauservice_data, "alluauservice: the configuration adds globals and turns lints off until it is replaced", "[alluauservice]") { UNSCOPED_INFO("definitions loaded: " + error); @@ -689,7 +666,6 @@ namespace CHECK(mentions(plain, "unused")); ALLuauConfig config; std::string bad; - UNSCOPED_INFO("parses: " + bad); REQUIRE(ALLuauConfig::parse("{ \"globals\": [\"Alchemy\"], \"lint\": { \"LocalUnused\": false } }", config, bad)); service.setConfig(config); ALScriptProblems configured = service.check(script); @@ -738,6 +714,7 @@ namespace REQUIRE(keyed); } } + TEST_CASE_METHOD(alluauservice_data, "alluauservice: questions reuse a text's check and hints are the same whatever came first", "[alluauservice]") { UNSCOPED_INFO("definitions loaded: " + error); @@ -764,15 +741,12 @@ namespace CHECK(service.typeChecks() == checked); // A text asked about before a check has what a hover does. const std::vector after_hover = service.inlayHints(script, true, true); - UNSCOPED_INFO("as many hints"); REQUIRE(after_hover.size() == after_check.size()); for (size_t i = 0; i < after_hover.size(); ++i) { - { - INFO("the same hint: " + after_hover[i].text + " / " + after_check[i].text); - REQUIRE(after_hover[i].text == after_check[i].text); - REQUIRE(after_hover[i].line == after_check[i].line); - } + CAPTURE(i); + REQUIRE(after_hover[i].text == after_check[i].text); + REQUIRE(after_hover[i].line == after_check[i].line); } // Another configuration, or another text, is checked again: one // unlike either solver's default. @@ -795,11 +769,9 @@ namespace CHECK(loaded); const std::string script = "local T = {}\nfunction T:m(a: number, b: string) end\nT.m(T, 1, \"x\")\nT:m(1, \"x\")\n"; ALScriptSignature dotted = service.signature(script, 2, 12); // in "x" - UNSCOPED_INFO("found"); REQUIRE(dotted.found); UNSCOPED_INFO("self, a and b: " + dotted.label); REQUIRE(dotted.parameters.size() == size_t(3)); - UNSCOPED_INFO("at the third"); CHECK(dotted.active == 2); UNSCOPED_INFO("which is b: " + dotted.parameters[2]); CHECK(dotted.parameters[2].find("b") == 0); @@ -820,9 +792,7 @@ namespace for (const bool use : { true, false, true }) { std::string why; - UNSCOPED_INFO("switched: " + why); REQUIRE(service.setNewSolver(use, why)); - UNSCOPED_INFO("the one asked for"); REQUIRE(service.newSolver() == use); UNSCOPED_INFO("the definitions with it"); REQUIRE(service.hasDefinitions()); @@ -972,18 +942,12 @@ namespace CHECK(loaded); const std::string script = "local f: ((n: number) -> ()) & ((a: string, b: string) -> ()) = nil :: any\nf(\"x\", \"y\")\n"; const ALScriptSignature sig = service.signature(script, 1, 7); - UNSCOPED_INFO("found"); REQUIRE(sig.found); - UNSCOPED_INFO("both forms"); REQUIRE(sig.overloads.size() == size_t(2)); UNSCOPED_INFO("the second fits two arguments"); CHECK(sig.overload == 1); - { - INFO("shown"); - CHECK(sig.label == sig.overloads[1].label); - REQUIRE(sig.parameters.size() == 2); - } - UNSCOPED_INFO("at the second argument"); + CHECK(sig.label == sig.overloads[1].label); + REQUIRE(sig.parameters.size() == 2); CHECK(sig.active == 1); UNSCOPED_INFO("one form: none listed"); REQUIRE(service.signature("local function g(a: number) end\ng(1)\n", 1, 2).overloads.empty()); @@ -1042,11 +1006,8 @@ namespace REQUIRE(loose_hints.size() == size_t(2)); UNSCOPED_INFO("and strict"); REQUIRE(strict_hints.size() == size_t(2)); - { - INFO("the same"); - CHECK(loose_hints[0].text == strict_hints[0].text); - CHECK(loose_hints[1].text == strict_hints[1].text); - } + CHECK(loose_hints[0].text == strict_hints[0].text); + CHECK(loose_hints[1].text == strict_hints[1].text); { INFO("the problems of each told apart"); CHECK(said(service.check(loose)) == first); @@ -1071,11 +1032,8 @@ namespace UNSCOPED_INFO("what was checked stands"); CHECK(service.typeChecks() == checked); const std::string docs = "{\"@sl-slua/global/ll.Say\": {\"documentation\": \"Says it.\", \"learn_more_link\": \"x\"}}"; - { - INFO("docs loaded"); - CHECK(service.loadDocs(docs, why)); - CHECK(service.hasDocs()); - } + CHECK(service.loadDocs(docs, why)); + CHECK(service.hasDocs()); { INFO("and the same again"); CHECK(service.loadDocs(docs, why)); @@ -1118,7 +1076,7 @@ namespace { offered |= fix.title == std::string("Declare it as '") + type + "'"; } - UNSCOPED_INFO("line " + std::to_string(line) + ": declared as " + type); + CAPTURE(line, type); REQUIRE(offered); } } @@ -1326,7 +1284,6 @@ namespace CHECK(LLSDSerialize::fromXML(templates, in) != LLSDParser::PARSE_FAILURE); REQUIRE(templates.isArray()); } - UNSCOPED_INFO("some"); REQUIRE(templates.size() > 0); for (LLSD::array_const_iterator it = templates.beginArray(); it != templates.endArray(); ++it) { @@ -1340,7 +1297,7 @@ namespace wrong += llformat(" [%d:%d] %s", problem.line, problem.column, problem.message.c_str()); } } - UNSCOPED_INFO(name + ":" + wrong); + CAPTURE(name, wrong); REQUIRE(wrong.empty()); } } @@ -1459,15 +1416,11 @@ namespace "local m: number? = nil\n" "if m then print(m) end\n" "if n ~= 0 or not (n > 1) then print(b, t) end\n"); - UNSCOPED_INFO("four: " + llformat("%zu", said.size())); REQUIRE(said.size() == size_t(4)); - { - INFO("each: " + said[0] + "|" + said[1] + "|" + said[2] + "|" + said[3]); - CHECK(said[0] == "LuauLintSlNumberTruth n 1"); - CHECK(said[1] == "LuauLintSlNumberTruth n 2"); - CHECK(said[2] == "LuauLintSlNumberTruthNot n 3"); - CHECK(said[3] == "LuauLintSlNumberTruth n 4"); - } + CHECK(said[0] == "LuauLintSlNumberTruth n 1"); + CHECK(said[1] == "LuauLintSlNumberTruth n 2"); + CHECK(said[2] == "LuauLintSlNumberTruthNot n 3"); + CHECK(said[3] == "LuauLintSlNumberTruth n 4"); // A local by what it is given, where the old solver's nonstrict // mode says any: a copy of a number is one; one given a string, or // joined to one, or a parameter, is not. @@ -1608,17 +1561,14 @@ namespace "function LLEvents.touch_start(n: number) end\n"; const std::vector outline = service.outline(script); const std::vector shape = ALLuauService::shape(script); - UNSCOPED_INFO("as many"); REQUIRE(shape.size() == outline.size()); for (size_t i = 0; i < shape.size(); ++i) { - { - INFO("the same symbol, where it was"); - REQUIRE(shape[i].name == outline[i].name); - REQUIRE(shape[i].kind == outline[i].kind); - REQUIRE(shape[i].span == outline[i].span); - REQUIRE(shape[i].depth == outline[i].depth); - } + CAPTURE(i); + REQUIRE(shape[i].name == outline[i].name); + REQUIRE(shape[i].kind == outline[i].kind); + REQUIRE(shape[i].span == outline[i].span); + REQUIRE(shape[i].depth == outline[i].depth); UNSCOPED_INFO("no type"); REQUIRE(shape[i].detail.empty()); } @@ -1771,11 +1721,9 @@ namespace for (const ALScriptCompletion& c : found) { said += " " + c.text; - UNSCOPED_INFO("a word of the language: " + c.text); + CAPTURE(c.text); REQUIRE(c.kind == ALScriptSymbolKind::Keyword); - UNSCOPED_INFO("in a hot comment: " + c.text); REQUIRE(c.context == ALScriptCompletion::Context::HotComment); - UNSCOPED_INFO("one the studio offers: " + c.text); REQUIRE(ALScriptLexicon::isLuauHotComment(c.text)); } UNSCOPED_INFO("each of them:" + said); @@ -1797,12 +1745,11 @@ namespace return nullptr; }; std::string why; - UNSCOPED_INFO("docs loaded"); CHECK(service.loadDocs("{\"@sl-slua/global/ll.Say\": {\"documentation\": \"Says it.\", \"learn_more_link\": \"x\"}," - " \"@sl-slua/global/ll\": {\"documentation\": \"The library.\", \"learn_more_link\": \"x\"}," - " \"@sl-slua/global/vector\": {\"documentation\": \"Makes one.\", \"learn_more_link\": \"x\"}," - " \"@sl-slua/global/vector.magnitude\": {\"documentation\": \"Its length.\", \"learn_more_link\": \"x\"}}", - why)); + " \"@sl-slua/global/ll\": {\"documentation\": \"The library.\", \"learn_more_link\": \"x\"}," + " \"@sl-slua/global/vector\": {\"documentation\": \"Makes one.\", \"learn_more_link\": \"x\"}," + " \"@sl-slua/global/vector.magnitude\": {\"documentation\": \"Its length.\", \"learn_more_link\": \"x\"}}", + why)); const std::string base = "local count: number = 1\n" "local function twice(n: number): number return n * 2 end\n" "local t = { a = 1, b = \"x\" }\n"; @@ -1814,16 +1761,13 @@ namespace UNSCOPED_INFO("off: checked whole"); CHECK(service.typeChecks() > checks); UNSCOPED_INFO("a call of a field documented as the field, not what holds it"); - CHECK(service.signature(base + "ll.Say(0, )\n", 3, 10).documentation == - std::string("Says it.")); + CHECK(service.signature(base + "ll.Say(0, )\n", 3, 10).documentation == std::string("Says it.")); // `vector` is a table that is also called, whose fields Luau gives // no symbol of their own. UNSCOPED_INFO("and of a callable table's field"); - CHECK(service.signature(base + "local m = vector.magnitude()\n", 3, 27).documentation == - std::string("Its length.")); + CHECK(service.signature(base + "local m = vector.magnitude()\n", 3, 27).documentation == std::string("Its length.")); service.setFragments(true); - UNSCOPED_INFO("on"); CHECK(service.fragments()); service.check(base); service.complete(base, 0, 0); @@ -1849,9 +1793,7 @@ namespace ALScriptSignature said = service.signature(base + "ll.Say(0, )\n", 3, 10); UNSCOPED_INFO("a call's signature"); REQUIRE(said.found); - UNSCOPED_INFO("its label"); CHECK(said.label == std::string("ll.Say(channel: number, msg: string): ()")); - UNSCOPED_INFO("at its second argument"); CHECK(said.active == 1); UNSCOPED_INFO("documented as the field it calls"); CHECK(said.documentation == std::string("Says it.")); @@ -1867,8 +1809,7 @@ namespace UNSCOPED_INFO("no signature outside a call"); REQUIRE_FALSE(service.signature(base + "local q = 1\n", 3, 8).found); UNSCOPED_INFO("a callable table's field over the fragment too"); - CHECK(service.signature(base + "local m = vector.magnitude()\n", 3, 27).documentation == - std::string("Its length.")); + CHECK(service.signature(base + "local m = vector.magnitude()\n", 3, 27).documentation == std::string("Its length.")); UNSCOPED_INFO("none of it checked whole"); CHECK(service.typeChecks() == checks); UNSCOPED_INFO("each answered over its statement"); @@ -1886,7 +1827,6 @@ namespace size_t checks = service.typeChecks(); size_t parts = service.fragmentsChecked(); const std::string base = "local count: number = 1\n"; - UNSCOPED_INFO("answered"); REQUIRE_FALSE(service.complete(base + "local x = co\n", 1, 10).empty()); { INFO("checked whole"); @@ -1899,9 +1839,7 @@ namespace ALLuauService::Stop stop = ALLuauService::newStop(); ALLuauService::cancel(stop); service.setStop(stop); - UNSCOPED_INFO("nothing"); REQUIRE(service.complete(base + "local y = co\n", 1, 10).empty()); - UNSCOPED_INFO("stopped"); CHECK(service.stopped()); service.setStop(nullptr); checks = service.typeChecks(); @@ -1926,7 +1864,6 @@ namespace parts = service.fragmentsChecked(); const std::string changed = "local function twice(n: string): string return n end\nlocal function main()\n local count = 1\n local x = tw\nend\n"; const std::vector found = service.complete(changed, 3, 14); - UNSCOPED_INFO("answered"); CHECK(offers(found, "twice")); { INFO("whole"); @@ -1986,14 +1923,12 @@ namespace checks = service.typeChecks(); service.warm(base); UNSCOPED_INFO("the old solver's autocomplete module checked; the new solver's check is it"); - CHECK(service.typeChecks() - checks == - size_t(newSolver ? 0 : 1)); + CHECK(service.typeChecks() - checks == size_t(newSolver ? 0 : 1)); service.warm(base); UNSCOPED_INFO("and not again for the same text"); CHECK(service.typeChecks() - checks == size_t(newSolver ? 0 : 1)); checks = service.typeChecks(); const size_t parts = service.fragmentsChecked(); - UNSCOPED_INFO("answered"); CHECK(offers(service.complete(typed, 1, 18), "count")); UNSCOPED_INFO("over its statement"); CHECK(service.fragmentsChecked() - parts == size_t(1)); @@ -2415,11 +2350,8 @@ namespace return sig.found && sig.overloads.size() == 2 ? sig.overload : -1; }; const ALScriptSignature listen = service.signature(script, 0, 24); - { - INFO("found, both forms"); - REQUIRE(listen.found); - REQUIRE(listen.overloads.size() == 2); - } + REQUIRE(listen.found); + REQUIRE(listen.overloads.size() == 2); { INFO("listen, not a detected event: " + listen.label); CHECK(listen.overload == 1); @@ -2515,7 +2447,7 @@ namespace UNSCOPED_INFO("definitions loaded: " + error); CHECK(loaded); std::string why; - UNSCOPED_INFO("the other solver: " + why); + UNSCOPED_INFO("the other solver"); REQUIRE(service.setNewSolver(!newSolver, why)); ALScriptProblems problems = service.check("print(REZ_TORQUE)\n"); UNSCOPED_INFO("known there: " + said(problems)); @@ -2526,7 +2458,6 @@ namespace UNSCOPED_INFO("known again: " + said(problems)); CHECK_FALSE(unknownGlobal(problems, "REZ_TORQUE")); const ALScriptHover hover = service.hover("print(REZ_TORQUE)\n", 0, 8); - UNSCOPED_INFO("hover finds it"); REQUIRE(hover.found); UNSCOPED_INFO("a number: " + hover.label); REQUIRE(hover.label.find("number") != std::string::npos); diff --git a/indra/alscript/tests/almasterburst_test.cpp b/indra/alscript/tests/almasterburst_test.cpp index 8a0e2901d2..a28a2b9e25 100644 --- a/indra/alscript/tests/almasterburst_test.cpp +++ b/indra/alscript/tests/almasterburst_test.cpp @@ -64,11 +64,8 @@ namespace REQUIRE(burst.release(100.0).empty()); burst.changed("/s/door.lsl", 100.0); - { - INFO("it waits"); - CHECK(burst.waiting("/s/door.lsl")); - REQUIRE(burst.size() == 1); - } + CHECK(burst.waiting("/s/door.lsl")); + REQUIRE(burst.size() == 1); UNSCOPED_INFO("due a second on"); CHECK(*burst.dueAt() == 101.0); { @@ -76,13 +73,9 @@ namespace REQUIRE(burst.release(100.5).empty()); REQUIRE(burst.release(almasterburst_data::before(101.0)).empty()); } - UNSCOPED_INFO("then"); CHECK(almasterburst_data::joined(burst.release(101.0)) == std::string("/s/door.lsl")); - { - INFO("and is gone"); - REQUIRE(burst.empty()); - REQUIRE_FALSE(burst.dueAt()); - } + REQUIRE(burst.empty()); + REQUIRE_FALSE(burst.dueAt()); UNSCOPED_INFO("not given twice"); REQUIRE(burst.release(200.0).empty()); @@ -113,7 +106,6 @@ namespace UNSCOPED_INFO("none while it goes on"); REQUIRE(burst.release(last).empty()); } - UNSCOPED_INFO("all thirty wait"); REQUIRE(burst.size() == size_t(30)); UNSCOPED_INFO("due a second after the last"); CHECK(*burst.dueAt() == last + 1.0); @@ -121,11 +113,8 @@ namespace REQUIRE(burst.release(almasterburst_data::before(last + 1.0)).empty()); UNSCOPED_INFO("all of them, in the order heard"); CHECK(almasterburst_data::joined(burst.release(last + 1.0)) == expected); - { - INFO("nothing left"); - REQUIRE(burst.empty()); - REQUIRE(burst.release(last + 60.0).empty()); - } + REQUIRE(burst.empty()); + REQUIRE(burst.release(last + 60.0).empty()); } TEST_CASE_METHOD(almasterburst_data, "almasterburst: a file rewritten every half-second still goes at the burst's longest", "[almasterburst]") @@ -218,11 +207,8 @@ namespace burst.changed("/s/b.lsl", 0.25); burst.forget("/s/a.lsl"); burst.forget("/s/never.lsl"); - { - INFO("forgotten"); - CHECK_FALSE(burst.waiting("/s/a.lsl")); - REQUIRE(burst.size() == 1); - } + CHECK_FALSE(burst.waiting("/s/a.lsl")); + REQUIRE(burst.size() == 1); UNSCOPED_INFO("the rest goes"); CHECK(almasterburst_data::joined(burst.release(1.25)) == std::string("/s/b.lsl")); @@ -248,12 +234,9 @@ namespace burst.changed("/s/a.lsl", 200.0); burst.changed("/s/b.lsl", 200.0, 1.5); burst.clear(); - { - INFO("cleared"); - REQUIRE(burst.empty()); - REQUIRE_FALSE(burst.dueAt()); - REQUIRE(burst.release(300.0).empty()); - } + REQUIRE(burst.empty()); + REQUIRE_FALSE(burst.dueAt()); + REQUIRE(burst.release(300.0).empty()); } TEST_CASE_METHOD(almasterburst_data, "almasterburst: Windows paths match in any case and separator and go as last spelled", "[almasterburst]") diff --git a/indra/alscript/tests/almasterindex_test.cpp b/indra/alscript/tests/almasterindex_test.cpp index 6327e399c2..99370ed00b 100644 --- a/indra/alscript/tests/almasterindex_test.cpp +++ b/indra/alscript/tests/almasterindex_test.cpp @@ -169,7 +169,6 @@ namespace TEST_CASE_METHOD(almasterindex_data, "almasterindex: links go through the file and come back as they went", "[almasterindex]") { ALMasterIndex& made = open(); - UNSCOPED_INFO("none at first"); REQUIRE(made.empty()); ALMasterLink door = almasterindex_data::link(1, 2, "/s/door.lsl"); door.uses = { "disk:/s/util.lsl" }; @@ -185,30 +184,18 @@ namespace CHECK_FALSE(made.dirty()); ALMasterIndex& back = open(); - UNSCOPED_INFO("both back"); REQUIRE(back.size() == size_t(2)); const std::optional read = back.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); - UNSCOPED_INFO("the door's"); REQUIRE(read.has_value()); - UNSCOPED_INFO("its master"); CHECK(read->master == std::string("/s/door.lsl")); - UNSCOPED_INFO("its hash"); CHECK(read->hash == std::string("abc")); - UNSCOPED_INFO("its stamp"); CHECK(read->stamp == S64(1234)); - UNSCOPED_INFO("its base"); CHECK(read->base == almasterindex_data::key(9)); - UNSCOPED_INFO("its uses"); REQUIRE(read->uses.size() == size_t(1)); - UNSCOPED_INFO("its name"); CHECK(read->itemName == std::string("Door")); - UNSCOPED_INFO("the card's, a notecard's"); CHECK(back.linkOf(LLUUID::null, almasterindex_data::key(3))->notecard); - UNSCOPED_INFO("found by its master"); REQUIRE(back.mastering("/s/door.lsl").size() == size_t(1)); - UNSCOPED_INFO("and by what it read"); REQUIRE(back.affectedBy("disk:/s/util.lsl").size() == size_t(1)); - UNSCOPED_INFO("in its object"); REQUIRE(back.linksIn(almasterindex_data::key(1)).size() == size_t(1)); UNSCOPED_INFO("in the inventory"); REQUIRE(back.linksIn(LLUUID::null).size() == size_t(1)); @@ -222,7 +209,6 @@ namespace almasterindex_data::put(file, garbage); { ALMasterIndex& looked = open(); - UNSCOPED_INFO("no links"); REQUIRE(looked.empty()); UNSCOPED_INFO("nothing to write"); CHECK_FALSE(looked.dirty()); @@ -273,12 +259,9 @@ 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(2, 3, "/s/c.lsl") }); - UNSCOPED_INFO("three"); REQUIRE(made.size() == size_t(3)); - UNSCOPED_INFO("told once"); CHECK(told == 1); made.link(almasterindex_data::link(2, 4, "/s/d.lsl")); - UNSCOPED_INFO("and once more"); CHECK(told == 2); made.link(std::vector()); UNSCOPED_INFO("none, not told"); @@ -286,7 +269,6 @@ namespace made.link(almasterindex_data::link(1, 1, "/s/a2.lsl")); UNSCOPED_INFO("put over, not beside"); REQUIRE(made.size() == size_t(4)); - UNSCOPED_INFO("the new master"); CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))->master == std::string("/s/a2.lsl")); } @@ -295,11 +277,8 @@ namespace ALMasterIndex& made = open(); made.link({ almasterindex_data::link(1, 1, "/s/a.lsl"), almasterindex_data::link(1, 2, "/s/b.lsl") }); told = 0; - UNSCOPED_INFO("let go of"); CHECK(made.unlink(almasterindex_data::key(1), almasterindex_data::key(1))); - UNSCOPED_INFO("told"); CHECK(told == 1); - UNSCOPED_INFO("gone"); REQUIRE_FALSE(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1))); UNSCOPED_INFO("one not linked is not"); CHECK_FALSE(made.unlink(almasterindex_data::key(1), almasterindex_data::key(1))); @@ -309,9 +288,7 @@ 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)); - UNSCOPED_INFO("pending"); CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->state == ALMasterLink::State::Pending); - UNSCOPED_INFO("told"); CHECK(told == 2); UNSCOPED_INFO("none linked, none marked"); CHECK(made.markPending({ { almasterindex_data::key(5), almasterindex_data::key(5) } }) == size_t(0)); @@ -319,9 +296,7 @@ namespace CHECK(told == 2); index.reset(); const ALMasterLinks disk = onDisk(); - UNSCOPED_INFO("one on disk"); REQUIRE(disk.size() == size_t(1)); - UNSCOPED_INFO("pending there"); CHECK(disk.of(almasterindex_data::key(1), almasterindex_data::key(2))->state == ALMasterLink::State::Pending); } @@ -350,28 +325,21 @@ namespace CHECK_FALSE(made.dirty()); // A recompile: the same text, a new asset. - UNSCOPED_INFO("the same"); 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)); - UNSCOPED_INFO("the base moved"); CHECK(same->base == almasterindex_data::key(101)); - UNSCOPED_INFO("still active"); CHECK(same->state == ALMasterLink::State::Active); - UNSCOPED_INFO("nobody told"); CHECK(told == 0); UNSCOPED_INFO("to be written"); CHECK(made.dirty()); UNSCOPED_INFO("a notecard's by its text"); CHECK(made.heardSaved(almasterindex_data::key(1), almasterindex_data::key(3), almasterindex_data::key(201), "hello") == ALMasterIndex::Heard::Same); - UNSCOPED_INFO("something else"); 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)); - UNSCOPED_INFO("differing"); CHECK(other->state == ALMasterLink::State::Differing); UNSCOPED_INFO("its base as it was"); CHECK(other->base == almasterindex_data::key(101)); - UNSCOPED_INFO("told"); CHECK(told == 1); // Nothing sent through it yet: no hash to be the same as. @@ -388,33 +356,25 @@ namespace ALMasterLink sent = *made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2)); sent.hash = "h1"; sent.base = almasterindex_data::key(50); - UNSCOPED_INFO("put back"); CHECK(made.finished(almasterindex_data::key(1), almasterindex_data::key(2), sent)); UNSCOPED_INFO("with what the send left"); CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->hash == std::string("h1")); - UNSCOPED_INFO("told"); CHECK(told == 1); // Saved as another item: the link is that one's. ALMasterLink rekeyed = sent; rekeyed.item = almasterindex_data::key(3); rekeyed.hash = "h2"; - UNSCOPED_INFO("put"); 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))); - UNSCOPED_INFO("the new one's"); CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(3))->hash == std::string("h2")); - UNSCOPED_INFO("one link"); REQUIRE(made.size() == size_t(1)); - UNSCOPED_INFO("found by its master"); REQUIRE(made.mastering("/s/door.lsl").size() == size_t(1)); // Let go of while it was on its way: stays let go of. made.unlink(almasterindex_data::key(1), almasterindex_data::key(3)); - UNSCOPED_INFO("not put"); CHECK_FALSE(made.finished(almasterindex_data::key(1), almasterindex_data::key(3), rekeyed)); - UNSCOPED_INFO("none"); REQUIRE(made.empty()); } @@ -426,23 +386,16 @@ namespace CHECK(ALMasterIndex::knowsNothing(*made.linkOf(almasterindex_data::key(1), almasterindex_data::key(1)))); told = 0; - UNSCOPED_INFO("kept"); 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)); - UNSCOPED_INFO("what it read"); REQUIRE(a->uses.size() == size_t(1)); UNSCOPED_INFO("what went up, as the world holds it"); CHECK(a->hash == std::string("ha")); - UNSCOPED_INFO("the stamp with it"); CHECK(a->stamp == S64(77)); - UNSCOPED_INFO("found by what it read"); REQUIRE_FALSE(made.affectedBy("disk:/s/util.lsl").empty()); // The world holding something else: what it read, and no hash. - UNSCOPED_INFO("kept too"); CHECK(made.adopted(almasterindex_data::key(1), almasterindex_data::key(2), "/s/b.lsl", {}, true, std::string(), 0)); - UNSCOPED_INFO("missed"); CHECK(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->missed); - UNSCOPED_INFO("no hash"); REQUIRE(made.linkOf(almasterindex_data::key(1), almasterindex_data::key(2))->hash.empty()); UNSCOPED_INFO("not told yet"); CHECK(told == 0); @@ -558,7 +511,6 @@ namespace made.link({ almasterindex_data::link(1, 1, "/s/a.lsl"), almasterindex_data::link(1, 2, "/s/b.lsl") }); UNSCOPED_INFO("a link: handed over at once"); CHECK_FALSE(made.dirty()); - UNSCOPED_INFO("on disk"); REQUIRE(on_disk(1).has_value()); // A send's bookkeeping waits, and a crash now loses it. @@ -576,7 +528,6 @@ namespace made.markPending({ { almasterindex_data::key(1), almasterindex_data::key(2) } }); UNSCOPED_INFO("pending: at once"); CHECK_FALSE(made.dirty()); - UNSCOPED_INFO("pending on disk"); CHECK(on_disk(2)->state == ALMasterLink::State::Pending); UNSCOPED_INFO("and what waited with it"); CHECK(on_disk(1)->hash == std::string("h1")); @@ -587,7 +538,6 @@ namespace made.finished(almasterindex_data::key(1), almasterindex_data::key(1), sent); UNSCOPED_INFO("suspended: at once"); CHECK_FALSE(made.dirty()); - UNSCOPED_INFO("suspended on disk"); CHECK(on_disk(1)->state == ALMasterLink::State::Suspended); sent.state = ALMasterLink::State::Active; made.finished(almasterindex_data::key(1), almasterindex_data::key(1), sent); diff --git a/indra/alscript/tests/almasterlinks_test.cpp b/indra/alscript/tests/almasterlinks_test.cpp index 2de4c09535..1f3ea7e4c1 100644 --- a/indra/alscript/tests/almasterlinks_test.cpp +++ b/indra/alscript/tests/almasterlinks_test.cpp @@ -100,7 +100,6 @@ namespace links.put(almasterlinks_data::link(1, 2, "/s/door.lsl")); links.put(almasterlinks_data::link(0, 2, "/s/inventory.lsl")); links.put(almasterlinks_data::link(3, 4, "/s/lamp.lsl")); - UNSCOPED_INFO("three"); REQUIRE(links.size() == size_t(3)); UNSCOPED_INFO("by its object and item"); CHECK(links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))->master == std::string("/s/door.lsl")); @@ -131,7 +130,6 @@ namespace UNSCOPED_INFO("nothing to change of an item not linked"); REQUIRE_FALSE(links.find(almasterlinks_data::key(9), almasterlinks_data::key(9))); - UNSCOPED_INFO("removed"); CHECK(links.remove(almasterlinks_data::key(1), almasterlinks_data::key(2))); UNSCOPED_INFO("and gone"); REQUIRE_FALSE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(2))); @@ -247,7 +245,6 @@ namespace links.put(one); } - UNSCOPED_INFO("one pruned"); CHECK(links.prune(now) == size_t(1)); UNSCOPED_INFO("the old orphan"); REQUIRE_FALSE(links.of(almasterlinks_data::key(1), almasterlinks_data::key(10))); @@ -310,7 +307,6 @@ namespace } const LLSD llsd = links.toLLSD(); - UNSCOPED_INFO("version 1"); CHECK(llsd["version"].asInteger() == 1); UNSCOPED_INFO("every link"); REQUIRE(llsd["links"].size() == 5); @@ -319,7 +315,6 @@ namespace LLSDSerialize::toNotation(llsd, out); std::istringstream in(out.str()); LLSD back; - UNSCOPED_INFO("as notation"); REQUIRE(LLSDSerialize::fromNotation(back, in, static_cast(out.str().size())) > 0); const ALMasterLinks read = ALMasterLinks::fromLLSD(back); @@ -328,39 +323,26 @@ namespace const ALMasterLink* got = read.of(full.object, full.item); UNSCOPED_INFO("the full one"); REQUIRE(got != nullptr); - UNSCOPED_INFO("master"); CHECK(got->master == full.master); - UNSCOPED_INFO("made"); CHECK(got->made == full.made); - UNSCOPED_INFO("lua"); CHECK(got->lua); - UNSCOPED_INFO("target"); CHECK(got->target == full.target); - UNSCOPED_INFO("base"); CHECK(got->base == full.base); - UNSCOPED_INFO("hash"); CHECK(got->hash == full.hash); UNSCOPED_INFO("stamp, to the nanosecond"); CHECK(got->stamp == full.stamp); - UNSCOPED_INFO("uses"); CHECK(almasterlinks_data::joined(got->uses) == almasterlinks_data::joined(full.uses)); - UNSCOPED_INFO("object name"); CHECK(got->objectName == full.objectName); - UNSCOPED_INFO("item name"); CHECK(got->itemName == full.itemName); - UNSCOPED_INFO("region name"); CHECK(got->regionName == full.regionName); - UNSCOPED_INFO("linked"); CHECK(got->linked == full.linked); - UNSCOPED_INFO("state"); CHECK(got->state == full.state); - UNSCOPED_INFO("orphaned since"); CHECK(got->orphanedSince == full.orphanedSince); const ALMasterLink* plain = read.of(LLUUID::null, inventory.item); { INFO("the inventory's, with no object"); REQUIRE(plain); - REQUIRE(plain->object.isNull()); + CHECK(plain->object.isNull()); CHECK(plain->state == ALMasterLink::State::Differing); } { @@ -371,12 +353,10 @@ namespace for (const ALMasterLink::State state : { ALMasterLink::State::Active, ALMasterLink::State::Pending, ALMasterLink::State::Suspended }) { const ALMasterLink* one = read.of(almasterlinks_data::key(5), almasterlinks_data::key(10 + static_cast(state))); - { - INFO("each state"); - REQUIRE(one); - REQUIRE(one->state == state); - REQUIRE(one->made == ALMasterLink::Made::Hint); - } + CAPTURE(state); + REQUIRE(one); + REQUIRE(one->state == state); + REQUIRE(one->made == ALMasterLink::Made::Hint); } UNSCOPED_INFO("found by their masters"); REQUIRE(read.mastering("/s/x.lsl").size() == size_t(3)); @@ -448,7 +428,7 @@ namespace REQUIRE(plain != nullptr); { INFO("what it does not say is as a new link has it"); - REQUIRE(plain->made == ALMasterLink::Made::Picked); + CHECK(plain->made == ALMasterLink::Made::Picked); CHECK(plain->state == ALMasterLink::State::Active); } UNSCOPED_INFO("a stamp written as an integer"); @@ -459,7 +439,6 @@ namespace once["links"].append(good); const ALMasterLinks careful = ALMasterLinks::fromLLSD(once); const ALMasterLink* odd = careful.of(almasterlinks_data::key(1), almasterlinks_data::key(5)); - UNSCOPED_INFO("read"); REQUIRE(odd != nullptr); UNSCOPED_INFO("a way of making not known is a hint's"); CHECK(odd->made == ALMasterLink::Made::Hint); @@ -491,9 +470,8 @@ namespace for (int m = 0; m < MASTERS; ++m) { const std::vector found = links.mastering("/s/master" + std::to_string(m) + ".lsl"); - UNSCOPED_INFO("each master's"); + CAPTURE(m); REQUIRE(found.size() == size_t(LINKS / MASTERS)); - UNSCOPED_INFO("each the master's"); REQUIRE(found.front()->master == "/s/master" + std::to_string(m) + ".lsl"); } UNSCOPED_INFO("every user of the include they share"); @@ -534,7 +512,6 @@ namespace LLSDSerialize::toNotation(llsd, out); std::istringstream in(out.str()); LLSD back; - UNSCOPED_INFO("as notation"); REQUIRE(LLSDSerialize::fromNotation(back, in, static_cast(out.str().size())) > 0); const ALMasterLinks read = ALMasterLinks::fromLLSD(back); UNSCOPED_INFO("read back set"); diff --git a/indra/alscript/tests/almastermatch_test.cpp b/indra/alscript/tests/almastermatch_test.cpp index d847e3a9d1..ff03c3bbfa 100644 --- a/indra/alscript/tests/almastermatch_test.cpp +++ b/indra/alscript/tests/almastermatch_test.cpp @@ -233,7 +233,7 @@ namespace for (const char* elsewhere : { "\\\\host\\share\\door.lsl", "//host/share/door.lsl", "\\\\?\\UNC\\host\\share\\door.lsl", "C:\\door.lsl", "c:door.lsl" }) { - UNSCOPED_INFO(std::string("never asked of: ") + elsewhere); + CAPTURE(elsewhere); REQUIRE(almastermatch_data::resolved(elsewhere, blessed, false) == "- '" + std::string(elsewhere) + "' is outside the include folders"); } @@ -412,15 +412,10 @@ namespace TEST_CASE_METHOD(almastermatch_data, "almastermatch: a notecard's master is text or notecard or JSON in any case; never a script", "[almastermatch]") { const std::vector& notecards = ALMasterMatch::notecardExtensions(); - UNSCOPED_INFO("text"); CHECK(ALDiskIncludes::extensionOf("readme.txt", notecards) != 0); - UNSCOPED_INFO("a notecard"); CHECK(ALDiskIncludes::extensionOf("Config.NOTECARD", notecards) != 0); - UNSCOPED_INFO("JSON"); CHECK(ALDiskIncludes::extensionOf("data/settings.json", notecards) != 0); - UNSCOPED_INFO("not a script"); CHECK(ALDiskIncludes::extensionOf("door.lsl", notecards) == 0); - UNSCOPED_INFO("nor SLua"); CHECK(ALDiskIncludes::extensionOf("door.luau", notecards) == 0); } } diff --git a/indra/alscript/tests/almasterplan_test.cpp b/indra/alscript/tests/almasterplan_test.cpp index 2ad87a8c3c..a1d787774a 100644 --- a/indra/alscript/tests/almasterplan_test.cpp +++ b/indra/alscript/tests/almasterplan_test.cpp @@ -112,10 +112,7 @@ namespace CHECK(sizeof(table) / sizeof(table[0]) == size_t(32)); for (const Row& row : table) { - const std::string name = std::string(row.kind == Send::Direct ? "direct" : "derived") + (row.moved ? ", moved" : ", not moved") + - (row.same ? ", the same" : ", not the same") + (row.unchanged ? ", unchanged" : ", changed") + - (row.skip ? ", skipping" : ", not skipping"); - UNSCOPED_INFO(name); + CAPTURE(row.kind == Send::Direct, row.moved, row.same, row.unchanged, row.skip); REQUIRE(almasterplan_data::nameOf(ALMasterPlan::decide(row.kind, row.moved, row.same, row.unchanged, row.skip)) == almasterplan_data::nameOf(row.expected)); } @@ -147,9 +144,7 @@ namespace { const LLUUID door("00000001-0000-4000-8000-000000000000"); const LLUUID lamp("00000002-0000-4000-8000-000000000000"); - UNSCOPED_INFO("none"); CHECK(ALMasterPlan::objectsOf({}) == size_t(0)); - UNSCOPED_INFO("one script"); CHECK(ALMasterPlan::objectsOf({ door }) == size_t(1)); UNSCOPED_INFO("many scripts in one object"); CHECK(ALMasterPlan::objectsOf({ door, door, door, door, door }) == size_t(1)); @@ -162,7 +157,7 @@ namespace // two in two objects do at once. std::vector one(20, door); UNSCOPED_INFO("twenty in one object, over eight"); - REQUIRE(ALMasterPlan::askFirst(one.size(), ALMasterPlan::objectsOf(one), 8)); + CHECK(ALMasterPlan::askFirst(one.size(), ALMasterPlan::objectsOf(one), 8)); UNSCOPED_INFO("eight in one object, not"); CHECK_FALSE(ALMasterPlan::askFirst(8, ALMasterPlan::objectsOf(std::vector(8, door)), 8)); UNSCOPED_INFO("two in the inventory and an object"); diff --git a/indra/alscript/tests/almasterqueue_test.cpp b/indra/alscript/tests/almasterqueue_test.cpp index 6b31007e33..86eca7cdc6 100644 --- a/indra/alscript/tests/almasterqueue_test.cpp +++ b/indra/alscript/tests/almasterqueue_test.cpp @@ -60,45 +60,30 @@ namespace CHECK(queue.limit() == size_t(4)); for (const char* key : { "a", "b", "c", "d" }) { - UNSCOPED_INFO(std::string("starts: ") + key); + CAPTURE(key); REQUIRE(queue.ask(key, Send::Derived)); - { - INFO(std::string("is underway: ") + key); - REQUIRE(queue.underway(key)); - REQUIRE_FALSE(queue.waiting(key)); - } + REQUIRE(queue.underway(key)); + REQUIRE_FALSE(queue.waiting(key)); } - UNSCOPED_INFO("four busy"); CHECK(queue.busy() == size_t(4)); UNSCOPED_INFO("a fifth waits"); CHECK_FALSE(queue.ask("e", Send::Derived)); UNSCOPED_INFO("a sixth too"); CHECK_FALSE(queue.ask("f", Send::Direct)); - { - INFO("waiting, not underway"); - CHECK(queue.waiting("e")); - CHECK_FALSE(queue.underway("e")); - } - UNSCOPED_INFO("still four busy"); + CHECK(queue.waiting("e")); + CHECK_FALSE(queue.underway("e")); CHECK(queue.busy() == size_t(4)); UNSCOPED_INFO("one ends, the first waiting starts"); CHECK(almasterqueue_data::started(queue.finished("c")) == std::string("e*")); - { - INFO("the one ended is not underway"); - CHECK_FALSE(queue.underway("c")); - CHECK_FALSE(queue.waiting("c")); - } - { - INFO("the one started is"); - CHECK(queue.underway("e")); - CHECK_FALSE(queue.waiting("e")); - } + CHECK_FALSE(queue.underway("c")); + CHECK_FALSE(queue.waiting("c")); + CHECK(queue.underway("e")); + CHECK_FALSE(queue.waiting("e")); UNSCOPED_INFO("then the next"); CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("f")); UNSCOPED_INFO("then nothing waits"); CHECK(almasterqueue_data::started(queue.finished("b")) == std::string()); - UNSCOPED_INFO("three busy"); CHECK(queue.busy() == size_t(3)); UNSCOPED_INFO("one asked now starts at once"); CHECK(queue.ask("g", Send::Derived)); @@ -112,7 +97,6 @@ namespace { using Send = almasterqueue_data::Send; ALMasterQueue queue; - UNSCOPED_INFO("starts"); REQUIRE(queue.ask("a", Send::Direct)); UNSCOPED_INFO("asked again, waits"); CHECK_FALSE(queue.ask("a", Send::Direct)); @@ -134,11 +118,8 @@ namespace } UNSCOPED_INFO("ended: nothing more"); CHECK(almasterqueue_data::started(queue.finished("a")) == std::string()); - { - INFO("and gone"); - CHECK_FALSE(queue.underway("a")); - CHECK(queue.busy() == 0); - } + CHECK_FALSE(queue.underway("a")); + CHECK(queue.busy() == 0); } TEST_CASE_METHOD(almasterqueue_data, "almasterqueue: a send asked while it waits stays in its place", "[almasterqueue]") @@ -207,11 +188,8 @@ namespace queue.ask("a", Send::Direct); UNSCOPED_INFO("a ends, the first waiting goes, not a"); CHECK(almasterqueue_data::started(queue.finished("a")) == std::string("e*")); - { - INFO("a waits"); - CHECK(queue.waiting("a")); - CHECK_FALSE(queue.underway("a")); - } + CHECK(queue.waiting("a")); + CHECK_FALSE(queue.underway("a")); UNSCOPED_INFO("then f"); CHECK(almasterqueue_data::started(queue.finished("b")) == std::string("f*")); UNSCOPED_INFO("then a, as it was asked again"); @@ -271,7 +249,6 @@ namespace UNSCOPED_INFO("never more than four"); REQUIRE(queue.busy() <= 4); } - UNSCOPED_INFO("four started"); REQUIRE(underway.size() == size_t(4)); while (!underway.empty()) { @@ -300,12 +277,9 @@ namespace CHECK_FALSE(none.ask("b", Send::Derived)); none.clear(); - { - INFO("cleared"); - CHECK_FALSE(none.underway("a")); - CHECK_FALSE(none.waiting("b")); - CHECK(none.busy() == 0); - } + CHECK_FALSE(none.underway("a")); + CHECK_FALSE(none.waiting("b")); + CHECK(none.busy() == 0); UNSCOPED_INFO("and starts afresh"); CHECK(none.ask("b", Send::Derived)); UNSCOPED_INFO("with nothing behind it"); diff --git a/indra/alscript/tests/almasterwatch_test.cpp b/indra/alscript/tests/almasterwatch_test.cpp index 2ef10a4b73..84f65708c1 100644 --- a/indra/alscript/tests/almasterwatch_test.cpp +++ b/indra/alscript/tests/almasterwatch_test.cpp @@ -228,11 +228,8 @@ namespace UNSCOPED_INFO("not before the quiet"); REQUIRE(released.empty()); pass(0.2); - UNSCOPED_INFO("released once"); REQUIRE(released.size() == size_t(1)); - UNSCOPED_INFO("the master"); CHECK(almasterwatch_data::joined(released[0].masters) == std::string("door.lsl")); - UNSCOPED_INFO("no include"); REQUIRE(released[0].includes.empty()); looked(); pass(5.0); @@ -266,9 +263,7 @@ namespace UNSCOPED_INFO("the burst goes on: nothing yet"); REQUIRE(released.empty()); pass(0.2); - UNSCOPED_INFO("released once"); REQUIRE(released.size() == size_t(1)); - UNSCOPED_INFO("all ten together"); REQUIRE(released[0].masters.size() == size_t(10)); } @@ -284,11 +279,8 @@ namespace put(util, "b2", 200); looked(); pass(1.1); - UNSCOPED_INFO("released once"); REQUIRE(released.size() == size_t(1)); - UNSCOPED_INFO("the master"); CHECK(almasterwatch_data::joined(released[0].masters) == std::string("door.lsl")); - UNSCOPED_INFO("the include"); CHECK(almasterwatch_data::joined(released[0].includes) == std::string("util.lsl")); } @@ -325,7 +317,6 @@ namespace pass(0.2); UNSCOPED_INFO("then, before the emptied hold would have ended"); REQUIRE(released.size() == size_t(3)); - UNSCOPED_INFO("the file, once"); REQUIRE(released[2].masters.size() == size_t(1)); } @@ -369,7 +360,6 @@ namespace CHECK(watched.waiting(door)); CHECK_FALSE(watched.watching(door)); } - UNSCOPED_INFO("asked once"); REQUIRE(asked.size() == size_t(1)); // The studio writes it, then somebody else saves it, all before the @@ -399,11 +389,8 @@ namespace put(door, "studio", 200); watched.seen(door); answer(0, before); - { - INFO("both watched"); - CHECK(watched.watching(door)); - CHECK(watched.watching(lamp)); - } + CHECK(watched.watching(door)); + CHECK(watched.watching(lamp)); looked(); pass(5.0); UNSCOPED_INFO("the studio's write is no change"); @@ -420,14 +407,11 @@ namespace UNSCOPED_INFO("not waiting now"); CHECK_FALSE(watched.waiting(door)); answer(0); - UNSCOPED_INFO("not watched"); CHECK_FALSE(watched.watching(door)); - UNSCOPED_INFO("nothing at all"); REQUIRE(watched.size() == size_t(0)); put(door, "changed", 200); looked(); pass(5.0); - UNSCOPED_INFO("nothing heard"); REQUIRE(released.empty()); } @@ -466,12 +450,10 @@ namespace UNSCOPED_INFO("lamp waits on its look still: other alone asked"); REQUIRE(asked[3].paths.size() == size_t(1)); answer(3); - UNSCOPED_INFO("other watched"); CHECK(watched.watching(other)); UNSCOPED_INFO("lamp waits on its own"); CHECK(watched.waiting(lamp)); answer(2); - UNSCOPED_INFO("lamp watched"); CHECK(watched.watching(lamp)); } @@ -488,9 +470,7 @@ namespace return true; }); watch->watch({ almasterwatch_data::master(door), almasterwatch_data::include(util) }); - UNSCOPED_INFO("a master"); CHECK(*watch->periodOf(door) == ALMasterWatch::MASTER_PERIOD); - UNSCOPED_INFO("an include"); CHECK(*watch->periodOf(util) == ALMasterWatch::INCLUDE_PERIOD); UNSCOPED_INFO("both looked at first"); CHECK(looks == size_t(2)); @@ -501,11 +481,9 @@ namespace many.push_back(almasterwatch_data::master(in("m" + std::to_string(i) + ".lsl"))); } watch->watch(many); - UNSCOPED_INFO("257 watched"); REQUIRE(watch->size() == size_t(257)); UNSCOPED_INFO("past 256: a master every two seconds"); CHECK(*watch->periodOf(door) == ALMasterWatch::MANY_PERIOD); - UNSCOPED_INFO("and an include"); CHECK(*watch->periodOf(util) == ALMasterWatch::MANY_PERIOD); UNSCOPED_INFO("only the new ones looked at"); CHECK(looks == size_t(257)); @@ -513,7 +491,6 @@ namespace watch->watch({ almasterwatch_data::master(door), almasterwatch_data::include(util) }); UNSCOPED_INFO("fewer again: a master's own"); CHECK(*watch->periodOf(door) == ALMasterWatch::MASTER_PERIOD); - UNSCOPED_INFO("an include's own"); CHECK(*watch->periodOf(util) == ALMasterWatch::INCLUDE_PERIOD); UNSCOPED_INFO("nothing looked at again"); CHECK(looks == size_t(257)); diff --git a/indra/alscript/tests/almessagemap_test.cpp b/indra/alscript/tests/almessagemap_test.cpp index f07eec58a7..47a5dc8f80 100644 --- a/indra/alscript/tests/almessagemap_test.cpp +++ b/indra/alscript/tests/almessagemap_test.cpp @@ -47,10 +47,8 @@ namespace TEST_CASE_METHOD(almessagemap_data, "almessagemap: match gives the words between the literals in the marks' order", "[almessagemap]") { std::vector args; - UNSCOPED_INFO("matches"); REQUIRE(ALMessageMap::match("Passing [1] as argument [2] of `[3]' which is declared as `[4] [5]'.", "Passing string as argument 2 of `llSay' which is declared as `integer channel'.", args)); - UNSCOPED_INFO("the words"); CHECK(joined(args) == std::string("<2>")); UNSCOPED_INFO("the first literal must begin it"); CHECK_FALSE(ALMessageMap::match("Too many arguments to function `[1]'.", "Warning: Too many arguments to function `f'.", args)); @@ -87,7 +85,6 @@ namespace CHECK(joined(args) == std::string("")); UNSCOPED_INFO("quoted, a name is whole"); CHECK(ALMessageMap::match("Variable '[1]' is never used; prefix with '_' to silence", "Variable 'is' is never used; prefix with '_' to silence", args)); - UNSCOPED_INFO("the name"); CHECK(joined(args) == std::string("")); // The last literal is anchored at the end, so a word that holds // it is not cut there. @@ -100,11 +97,8 @@ namespace TEST_CASE_METHOD(almessagemap_data, "almessagemap: Tailslide's messages are keyed by code and Luau's lints by name", "[almessagemap]") { ALMessageMap::Match m; - UNSCOPED_INFO("10007"); REQUIRE(ALMessageMap::lsl(10007, "`llSay' is undeclared; did you mean llSay?", m)); - UNSCOPED_INFO("its key"); CHECK(m.key == std::string("LSLUndeclaredWithSuggestion")); - UNSCOPED_INFO("its words"); CHECK(joined(m.args) == std::string("")); UNSCOPED_INFO("a code with another message is not taken"); CHECK_FALSE(ALMessageMap::lsl(10007, "`llSay' is undeclared.", m)); @@ -112,13 +106,11 @@ namespace CHECK_FALSE(ALMessageMap::lsl(10020, "syntax error, unexpected ';'", m)); UNSCOPED_INFO("a lint by name"); CHECK(ALMessageMap::luauLint("LocalUnused", "Variable 'x' is never used; prefix with '_' to silence", m)); - UNSCOPED_INFO("its key"); CHECK(m.key == std::string("LuauLintLocalUnused")); UNSCOPED_INFO("the name is part of the match"); CHECK_FALSE(ALMessageMap::luauLint("FunctionUnused", "Variable 'x' is never used; prefix with '_' to silence", m)); UNSCOPED_INFO("the comparison lints have a mark for each operator"); CHECK(ALMessageMap::luauLint("ComparisonPrecedence", "X == Y == Z is equivalent to (X == Y) == Z; add parentheses to silence", m)); - UNSCOPED_INFO("four of them"); REQUIRE(m.args.size() == size_t(4)); } @@ -127,25 +119,20 @@ namespace ALMessageMap::Match m; UNSCOPED_INFO("plain"); REQUIRE(ALMessageMap::luauLint("DeprecatedApi", "Member 'table.getn' is deprecated", m)); - UNSCOPED_INFO("plain key"); CHECK(m.key == std::string("LuauLintDeprecatedMember")); UNSCOPED_INFO("the qualified name whole"); CHECK(joined(m.args) == std::string("")); UNSCOPED_INFO("with a replacement"); CHECK(ALMessageMap::luauLint("DeprecatedApi", "Member 'table.getn' is deprecated, use '#' instead", m)); - UNSCOPED_INFO("its key"); CHECK(m.key == std::string("LuauLintDeprecatedMemberUse")); - UNSCOPED_INFO("its words"); CHECK(joined(m.args) == std::string("<#>")); UNSCOPED_INFO("with a replacement and a reason"); CHECK(ALMessageMap::luauLint("DeprecatedApi", "Member 'a.b' is deprecated, use 'c' instead. It was slow", m)); UNSCOPED_INFO("the fuller key"); CHECK(m.key == std::string("LuauLintDeprecatedMemberUseReason")); - UNSCOPED_INFO("three words"); CHECK(joined(m.args) == std::string("")); UNSCOPED_INFO("a function"); CHECK(ALMessageMap::luauLint("DeprecatedApi", "Function 'f' is deprecated. Gone", m)); - UNSCOPED_INFO("its key"); CHECK(m.key == std::string("LuauLintDeprecatedFunctionReason")); } @@ -154,15 +141,12 @@ namespace ALMessageMap::Match m; UNSCOPED_INFO("a mismatch"); REQUIRE(ALMessageMap::luauError("Expected this to be 'number', but got 'string'", m)); - UNSCOPED_INFO("its key"); CHECK(m.key == std::string("LuauTypeMismatch")); UNSCOPED_INFO("wanted then given, quotes and all"); CHECK(joined(m.args) == std::string("<'number'><'string'>")); UNSCOPED_INFO("with a reason"); CHECK(ALMessageMap::luauError("Expected this to be 'number', but got 'string'; a table is not a number", m)); - UNSCOPED_INFO("the reason's key"); CHECK(m.key == std::string("LuauTypeMismatchReason")); - UNSCOPED_INFO("three words"); CHECK(joined(m.args) == std::string("<'number'><'string'>")); UNSCOPED_INFO("exactly"); CHECK(ALMessageMap::luauError("Expected this to be exactly 'number', but got 'string'", m)); @@ -170,11 +154,9 @@ namespace CHECK(m.key == std::string("LuauTypeMismatchExactly")); UNSCOPED_INFO("unreachable"); CHECK(ALMessageMap::luauError("Expected this to be unreachable, but got 'string'", m)); - UNSCOPED_INFO("its key"); CHECK(m.key == std::string("LuauTypeMismatchUnreachable")); UNSCOPED_INFO("a count"); CHECK(ALMessageMap::luauError("Argument count mismatch. Function 'll.Say' expects 2 arguments, but only 1 is specified", m)); - UNSCOPED_INFO("only one"); CHECK(m.key == std::string("LuauArgumentCountOnlyOne")); UNSCOPED_INFO("the name and the count"); CHECK(joined(m.args) == std::string("<2>")); @@ -184,34 +166,26 @@ namespace CHECK(m.key == std::string("LuauArgumentCountAtLeastOnlyOne")); UNSCOPED_INFO("a range"); CHECK(ALMessageMap::luauError("Argument count mismatch. Function 'f' expects 1 to 2 arguments, but none are specified", m)); - UNSCOPED_INFO("range, none"); CHECK(m.key == std::string("LuauArgumentCountRangeNone")); UNSCOPED_INFO("one wanted"); CHECK(ALMessageMap::luauError("Argument count mismatch. Function 'f' expects 1 argument, but 3 are specified", m)); - UNSCOPED_INFO("its key"); CHECK(m.key == std::string("LuauArgumentCountOne")); UNSCOPED_INFO("a key not found"); CHECK(ALMessageMap::luauError("Key 'b' not found in table '{ a: number }'. Did you mean 'a'?", m)); - UNSCOPED_INFO("did you mean"); CHECK(m.key == std::string("LuauMissingPropertyDidYouMean")); - UNSCOPED_INFO("its words"); CHECK(joined(m.args) == std::string("<{ a: number }>")); UNSCOPED_INFO("nothing the table knows"); CHECK_FALSE(ALMessageMap::luauError("Some words nobody wrote", m)); // The new solver's nonstrict mode. UNSCOPED_INFO("a checked call"); CHECK(ALMessageMap::luauError("the function 'string.len' expects to get a string as its 1st argument, but is being given a number", m)); - UNSCOPED_INFO("its key"); CHECK(m.key == std::string("LuauCheckedCall")); - UNSCOPED_INFO("and words"); CHECK(joined(m.args) == std::string("<1st>")); UNSCOPED_INFO("an argument that fails, in a function"); CHECK(ALMessageMap::luauError("in the function 'f', 'the argument 'x' is used in a way that will error at runtime", m)); - UNSCOPED_INFO("in a function"); CHECK(m.key + joined(m.args) == std::string("LuauFailsAtRuntimeIn")); UNSCOPED_INFO("and in none"); CHECK(ALMessageMap::luauError("the argument 'x' is used in a way that will error at runtime", m)); - UNSCOPED_INFO("in none"); CHECK(m.key + joined(m.args) == std::string("LuauFailsAtRuntime")); } TEST_CASE_METHOD(almessagemap_data, "almessagemap: unmarked templates and brackets are literal and fill puts words in as is", "[almessagemap]") diff --git a/indra/alscript/tests/almodulelook_test.cpp b/indra/alscript/tests/almodulelook_test.cpp index 00dd0b8ea2..199e7262eb 100644 --- a/indra/alscript/tests/almodulelook_test.cpp +++ b/indra/alscript/tests/almodulelook_test.cpp @@ -113,7 +113,7 @@ namespace { INFO("by its path from the folder, then its own name"); REQUIRE(file->names.size() >= 2); - REQUIRE(file->names[0] == "lib/util.lsl"); + CHECK(file->names[0] == "lib/util.lsl"); CHECK(file->names[1] == "util.lsl"); } UNSCOPED_INFO("what it declares"); @@ -138,7 +138,7 @@ namespace { INFO("by its stem, under the prefix"); REQUIRE(util); - REQUIRE(util->names[0] == "@lib/util"); + CHECK(util->names[0] == "@lib/util"); CHECK(util->exports == std::vector{ "clamp" }); } const ALModuleLook::Candidate* folder = named(found, "init"); @@ -206,7 +206,7 @@ namespace { INFO("listed, by its stem"); REQUIRE(one); - REQUIRE(one->names[0] == "@lib/Upper"); + CHECK(one->names[0] == "@lib/Upper"); CHECK(one->exports == std::vector{ "one" }); } diff --git a/indra/alscript/tests/alnotecardformat_test.cpp b/indra/alscript/tests/alnotecardformat_test.cpp index 7fc65e2ab2..0c237fb14c 100644 --- a/indra/alscript/tests/alnotecardformat_test.cpp +++ b/indra/alscript/tests/alnotecardformat_test.cpp @@ -62,7 +62,6 @@ namespace UNSCOPED_INFO("prose with a colon or two"); CHECK(ALNotecardFormat::guess("Note: read this.\nWelcome to the store\nAsk for help\nHave fun\n") == std::string("text")); - UNSCOPED_INFO("nothing"); CHECK(ALNotecardFormat::guess("") == std::string("text")); } @@ -93,8 +92,7 @@ namespace " \"on\": true\n" "}\n"; const std::vector entries = ALNotecardFormat::outline(text); - UNSCOPED_INFO("the keys"); - CHECK(said(entries) == + REQUIRE(said(entries) == std::string("name=\"Door\" speed=2.5 sounds={...} .open=\"creak\" .shut=\"bang\" stops=[...] .[2]={...} ..at=3 on=true")); const ALScriptOutlineEntry& sounds = entries[2]; { @@ -136,8 +134,8 @@ namespace { INFO("inside the string"); REQUIRE(named); - REQUIRE(named->name == "config"); - REQUIRE(named->begin == 30); + CHECK(named->name == "config"); + CHECK(named->begin == 30); CHECK(named->end == 38); } UNSCOPED_INFO("on its quote"); @@ -158,19 +156,12 @@ namespace " touch_start(integer n) { llGetNumberOfNotecardLines(\"config\"); llGetNotecardLine(\"other\", 0); }\n" "}\n"; const std::vector read = ALNotecardFormat::readersOf(script, "config"); - UNSCOPED_INFO("two readers of it"); REQUIRE(read.size() == size_t(2)); - { - INFO("the first"); - CHECK(read[0].line == 1); - CHECK(read[0].column == 36); - CHECK(read[0].endColumn == 44); - } - { - INFO("the second"); - CHECK(read[1].line == 2); - CHECK(read[1].column == 54); - } + CHECK(read[0].line == 1); + CHECK(read[0].column == 36); + CHECK(read[0].endColumn == 44); + CHECK(read[1].line == 2); + CHECK(read[1].column == 54); UNSCOPED_INFO("none of another"); REQUIRE(ALNotecardFormat::readersOf(script, "missing").empty()); } @@ -182,7 +173,6 @@ namespace " touch_start(integer n) { llGetNumberOfNotecardLines(\"config\"); }\n" "}\r"; const std::vector read = ALNotecardFormat::readersOf(script, "config"); - UNSCOPED_INFO("both"); REQUIRE(read.size() == size_t(2)); { INFO("the first on the second line"); diff --git a/indra/alscript/tests/alpreprocessor_test.cpp b/indra/alscript/tests/alpreprocessor_test.cpp index 3cbdc2bf43..ea92613d1f 100644 --- a/indra/alscript/tests/alpreprocessor_test.cpp +++ b/indra/alscript/tests/alpreprocessor_test.cpp @@ -198,16 +198,16 @@ namespace // The grid's builtins, which the optimizer and const folding read. A // test that needs them asks for them itself: Tailslide keeps them for // the process once loaded, so whichever asks first loads them. - static void ensureGridBuiltins() + static void requireGridBuiltins() { ALLSLService service; std::string error; - UNSCOPED_INFO("builtins: " + error); + INFO("builtins: " << error); REQUIRE(service.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error)); } // The same but the notes of a require nothing reads, which a script - // made to try something else may leave about (test 50 tries them). + // made to try something else may leave about (the last case here tries them). static std::string faults(const ALPreprocessor::Result& r) { ALPreprocessor::Result kept; @@ -242,12 +242,9 @@ namespace "#define TWICE(x) x + x\n" "default { state_entry() { llSay(0, GREETING); integer y = TWICE(2); string s = \"GREETING\"; /* GREETING */ } }"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("text"); CHECK(r.text == std::string("\n\ndefault { state_entry() { llSay(0, \"hello\"); integer y = 2 + 2; string s = \"GREETING\"; /* GREETING */ } }")); - UNSCOPED_INFO("not disabled"); CHECK_FALSE(r.disabled); // A function-like macro's name on its own is only a name, and its @@ -255,13 +252,11 @@ namespace r = ALPreprocessor::run("#define M(a, b) a+b\nM + 1\nM(1,\n 2)\n", options()); UNSCOPED_INFO("name alone"); CHECK(r.text == std::string("\nM + 1\n1+2\n")); - UNSCOPED_INFO("no problems"); CHECK(messages(r) == std::string()); // `#define` needs a name; an unknown directive is an error; a // redefinition is a warning; `#undef` takes it away. r = ALPreprocessor::run("#define\n#foo\n#define A 1\n#define A 2\nA\n#undef A\nA\n", options()); - UNSCOPED_INFO("errors"); CHECK(messages(r) == std::string("E 0: macro name missing in #define\nE 1: ill formed preprocessor directive '#foo'\nW 3: macro 'A' redefined\n")); UNSCOPED_INFO("undef"); CHECK(squeeze(r.text) == std::string("2\nA\n")); @@ -281,9 +276,7 @@ namespace "STR(__LINE__)\n" "TAIL(1) TAIL(1, 2, 3)\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("text"); CHECK(squeeze(r.text) == std::string("\"hello world\"\n" "\"\\\"q\\\\n\\\"\"\n" @@ -323,9 +316,7 @@ namespace "AB\n" "I(I)(3)\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("text"); CHECK(squeeze(r.text) == std::string("2*9*g\nfoo\na b AB\nI(3)\n")); } @@ -369,9 +360,7 @@ namespace "#endif\n" "end\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("text"); CHECK(squeeze(r.text) == std::string("yes\nyes2\nyes3\nyes4\nyes5\nyes6\nend\n")); std::istringstream in(r.text); std::string line; @@ -389,7 +378,6 @@ namespace CHECK(loc.line == 19); r = ALPreprocessor::run("#if 1\n#else\n#else\n#endif\n#endif\n#elif 1\n#if 1/0\n#endif\n#if 1 +\n#endif\n#if (1\n#endif\n#if 1\n", options()); - UNSCOPED_INFO("errors"); CHECK(messages(r) == std::string("E 2: #else after #else\n" "E 4: #endif without #if\n" @@ -418,31 +406,22 @@ namespace "#include NAME\n" "FROM_INC\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string("E 5: could not find include file 'missing.lsl'\n")); - UNSCOPED_INFO("text"); CHECK(r.text == std::string("\ninteger a = 1; string f = \"a.lsl\"; string id = \"aaaa\";\n" "string top = \"main.lsl\"; string tid = \"NOT_IN_WORLD\";\n" "\n\nonce\n" "\n\n\n\n\n\n7\n")); - UNSCOPED_INFO("includes"); REQUIRE(r.includes.size() == 3u); - UNSCOPED_INFO("first include"); CHECK(r.includes[0] == std::string("a.lsl")); - UNSCOPED_INFO("pending"); REQUIRE(r.pending.size() == 1u); - UNSCOPED_INFO("pending name"); CHECK(r.pending[0] == std::string("pending.lsl")); - UNSCOPED_INFO("files"); REQUIRE(r.map.files().size() == 4u); - UNSCOPED_INFO("main file"); CHECK(r.map.files()[0].name == std::string("main.lsl")); UNSCOPED_INFO("an include by its identity"); CHECK(r.map.fileOf("once.lsl") == 2); r = ALPreprocessor::run("#include \"deep.lsl\"\n", options()); - UNSCOPED_INFO("too deep"); REQUIRE(messages(r).find("nested too deeply") != std::string::npos); UNSCOPED_INFO("in the include"); CHECK(r.problems.front().file == "deep.lsl"); @@ -468,7 +447,6 @@ namespace const std::string off = "//fspreprocessor off\n#define X 1\nX\n"; r = ALPreprocessor::run(off, options()); - UNSCOPED_INFO("disabled"); CHECK(r.disabled); UNSCOPED_INFO("as it was"); CHECK(r.text == off); @@ -486,7 +464,6 @@ namespace r = ALPreprocessor::run("__LINE__ __FILE__ __SHORTFILE__ __ASSETID__\n__AGENTKEY__ __AGENTID__ __AGENTIDRAW__ __AGENTNAME__ __UNIXTIME__\n" "integer(1.5) list(1, 2) key(x)\n__DATE__ __TIME__\n", o); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("line one"); CHECK(r.text.substr(0, r.text.find('\n')) == std::string("1 \"main.lsl\" \"main.lsl\" \"asset-1\"")); @@ -529,11 +506,8 @@ namespace "default: llSay(0, \"other\");\n" "}\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("used"); CHECK(r.usedSwitches); - UNSCOPED_INFO("text"); CHECK(r.text == std::string("\n{if((x) == (1))jump _sw1_1;\n" "if((x) == (2))jump _sw1_2;\n" @@ -580,12 +554,9 @@ namespace "list l; integer i = (integer)l[2]; l[0] = 5; string s = (string)(l[(integer)l[1]]); rotation r = (rotation)l[0];\n" "x = (integer)(l[0]; f((float)l[0], l[1] = 2);\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("used"); CHECK(r.usedLazyLists); const std::string helper = "list lazy_list_set(list L, integer i, list v)\n{\n while (llGetListLength(L) < i)\n L = L + 0;\n return llListReplaceList(L, v, i, i);\n}\n\n"; - UNSCOPED_INFO("text"); CHECK(r.text == helper + "\nlist l; integer i = llList2Integer(l, 2); l = lazy_list_set(l,0,[5]); string s = llList2String(l, llList2Integer(l, 1)); rotation r = llList2Rot(l, 0);\n" "x = (integer)(l[0]; f(llList2Float(l, 0), l = lazy_list_set(l,1,[2]));\n"); @@ -604,9 +575,7 @@ namespace o.compress = true; ALPreprocessor::Result r = ALPreprocessor::run("// comment\ndefault { state_entry ( ) { integer a = - -1 ; /* x */ llSay( 0 , \"a b\" ) ; a ++ ; float f = 1 . 5; } }\n", o); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("text"); CHECK(r.text == std::string("default{state_entry(){integer a=- -1;llSay(0,\"a b\");a++;float f=1 . 5;}}\n")); r = ALPreprocessor::run("a\n\n\nb / /* c */ d\n", o); UNSCOPED_INFO("lines kept, not multiplied"); @@ -622,9 +591,7 @@ namespace "print(N)\n" "--#line 3\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options(true)); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("text"); CHECK(r.text == std::string("\nlocal s = [[N]] -- N\nlocal t = \"N\" .. #N_list .. 'N' .. `N{N}`\n--[==[ N\nN ]==]\nprint(3)\n--#line 3\n")); @@ -632,14 +599,11 @@ namespace add("b", "return 42\n"); add("c", "return require(\"c\")\n"); r = ALPreprocessor::run("local a = require(\"a\")\nlocal b = require('b')\nlocal x = require(name)\n", options(true)); - UNSCOPED_INFO("problems"); CHECK(faults(r) == std::string()); // Numbered as met: a, then what a requires. const std::string bundled = bundleOf({ "local b = require(2)\nreturn { b = b }\n", "return 42\n" }, "local a = require(1)\nlocal b = require(2)\nlocal x = require(name)\n"); - UNSCOPED_INFO("modules"); CHECK(r.text == bundled); - UNSCOPED_INFO("included"); 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")); @@ -663,11 +627,8 @@ namespace ALPreprocessor::run("local a = require(\"a\")\nlocal b = require('b')\nlocal x = require(name)\n", apart); UNSCOPED_INFO("the bundle as ever"); CHECK(split.text == bundled); - { - INFO("apart"); - CHECK(split.apart.valid); - REQUIRE(split.apart.modules.size() == 2); - } + CHECK(split.apart.valid); + REQUIRE(split.apart.modules.size() == 2); UNSCOPED_INFO("the script, its requires calls"); CHECK(split.apart.script.text == std::string("local a = require(\"a\")\nlocal b = require('b')\nlocal x = require(name)\n")); @@ -677,7 +638,6 @@ namespace CHECK(split.apart.modules[0].text == std::string("local b = require(\"b\")\nreturn { b = b }\n")); UNSCOPED_INFO("then what it reached"); CHECK(split.apart.modules[1].key == std::string("b")); - UNSCOPED_INFO("b"); CHECK(split.apart.modules[1].text == std::string("return 42\n")); const ALSourceMap::Loc in_a = split.apart.modules[0].map.toSource(1, 0); { @@ -706,12 +666,9 @@ namespace { add("inc", "x;\n"); ALPreprocessor::Result r = ALPreprocessor::run("#define X 10\ninteger a = X + 1;\n#include \"inc\"\ny;\n", options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("text"); CHECK(r.text == std::string("\ninteger a = 10 + 1;\n\nx;\ny;\n")); ALSourceMap::Loc loc = r.map.toSource(1, 0); - UNSCOPED_INFO("found"); REQUIRE(loc.found()); UNSCOPED_INFO("integer"); CHECK(loc.line * 1000 + loc.column == 1000); @@ -781,7 +738,7 @@ namespace const std::string stem = entry.path().stem().string(); const fs::path expected = dir / (stem + "_expected" + entry.path().extension().string()); ALPreprocessor::Result r = runGolden(entry.path(), "\n"); - UNSCOPED_INFO("problems in " + name); + CAPTURE(name); REQUIRE(messages(r) == std::string()); // With AL_PREPROCESSOR_WRITE_EXPECTED set, the run writes the // expected files instead of checking them: for a new golden @@ -793,9 +750,7 @@ namespace out << r.text; continue; } - UNSCOPED_INFO("expected file for " + name); REQUIRE(fs::exists(expected)); - UNSCOPED_INFO("output of " + name); REQUIRE(r.text == readFile(expected)); ++checked; } @@ -805,16 +760,14 @@ namespace TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: the optimizer runs on expanded text and its notes map to the source", "[alpreprocessor]") { - ensureGridBuiltins(); + requireGridBuiltins(); add("consts.lsl", "#define CHANNEL 7\ninteger unused = 1;\n"); ALPreprocessor::Options o = options(); o.optimize = true; o.compress = true; const std::string source = "#include \"consts.lsl\"\n#define TWICE(x) ((x) * 2)\ndefault\n{\n state_entry()\n {\n llSay(CHANNEL, (string)TWICE(21));\n }\n}\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, o); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); - UNSCOPED_INFO("text"); CHECK(r.text == std::string("default\n{\nstate_entry()\n{\nllSay(7,\"42\");\n}\n}\n")); // The unused global was in the include, and the note says so. bool noted = false; @@ -829,15 +782,12 @@ namespace REQUIRE(p.line == 1); } } - UNSCOPED_INFO("noted"); CHECK(noted); // The llSay is on output line 4, which came from source line 6. const ALSourceMap::Loc loc = r.map.toSource(4, 0); - UNSCOPED_INFO("found"); REQUIRE(loc.found()); UNSCOPED_INFO("main file"); CHECK(loc.file == 0); - UNSCOPED_INFO("line"); CHECK(loc.line == 6); } TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: break and continue become jumps; &= |= ^= <<= >>= become assignments", "[alpreprocessor]") @@ -845,9 +795,7 @@ namespace ALPreprocessor::Options o = options(); o.extensions = true; ALPreprocessor::Result r = ALPreprocessor::run("while (a) { if (b) break; if (c) continue; d++; }\n", o); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("used"); CHECK(r.usedExtensions); UNSCOPED_INFO("a while"); CHECK(r.text == std::string("while (a) { if (b) jump _brk1; if (c) jump _cnt1; d++; @_cnt1;}@_brk1;\n")); @@ -880,12 +828,9 @@ namespace r = ALPreprocessor::run("inline f() { }\ninline integer g(integer x) { return x; }\ninline = 3;\n", o); UNSCOPED_INFO("inline before a function is taken off and the name kept"); CHECK(r.text == std::string("f() { }\ninteger g(integer x) { return x; }\ninline = 3;\n")); - { - INFO("the names"); - REQUIRE(r.inlined.size() == 2); - CHECK(r.inlined[0] == "f"); - CHECK(r.inlined[1] == "g"); - } + REQUIRE(r.inlined.size() == 2); + CHECK(r.inlined[0] == "f"); + CHECK(r.inlined[1] == "g"); r = ALPreprocessor::run("integer break; while (a) { break = 1; x = continue; }\n", o); UNSCOPED_INFO("the words as names are errors, not rewrites"); CHECK(messages(r) == @@ -900,7 +845,6 @@ namespace ALPreprocessor::Options o = options(); o.extensions = true; ALPreprocessor::Result r = ALPreprocessor::run("while (a) { if (b) break; c++; }\n@out;\n", o); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("a label after the loop is the break's"); CHECK(r.text == std::string("while (a) { if (b) jump out; c++; }\n@out;\n")); @@ -925,11 +869,8 @@ namespace ALPreprocessor::Result r = ALPreprocessor::run("continue;\n#define f(a, a) a\n", o); // The directives go first, the extensions after: the macro's // problem is said before the loop's. - UNSCOPED_INFO("two problems"); REQUIRE(r.problems.size() == size_t(2)); - UNSCOPED_INFO("the words"); CHECK(r.problems[1].message == std::string("continue outside a loop")); - UNSCOPED_INFO("the key"); CHECK(r.problems[1].key == std::string("PreprocOutsideLoop")); { INFO("the word it was built with"); @@ -971,7 +912,6 @@ namespace REQUIRE_FALSE(r.problems.empty()); CHECK(r.problems.back().key == std::string("PreprocTooMuch")); } - UNSCOPED_INFO("an error"); CHECK(r.problems.back().severity == ALScriptProblem::Severity::Error); // A run within the budget is untouched by it. ALPreprocessor::Result fine = ALPreprocessor::run("#define A 1\ninteger n = A;\n", o); @@ -995,13 +935,10 @@ namespace ALPreprocessor::Options d = options(); d.expressionDepth = 16; ALPreprocessor::Result nested = ALPreprocessor::run(deep, d); - UNSCOPED_INFO("said"); REQUIRE_FALSE(nested.problems.empty()); - UNSCOPED_INFO("with its key"); CHECK(nested.problems.front().key == std::string("PreprocExpressionTooDeep")); // And one within the depth is answered. ALPreprocessor::Result shallow = ALPreprocessor::run("#if ((((1))))\ninteger n;\n#endif\n", d); - UNSCOPED_INFO("nothing said"); REQUIRE(shallow.problems.empty()); UNSCOPED_INFO("the group taken"); REQUIRE(shallow.text.find("integer n;") != std::string::npos); @@ -1019,7 +956,6 @@ namespace { deep = deep || p.key == "PreprocIncludeTooDeep"; } - UNSCOPED_INFO("said so"); CHECK(deep); UNSCOPED_INFO("each listed once"); REQUIRE(r.includes.size() == size_t(2)); @@ -1027,9 +963,7 @@ namespace files.clear(); add("c.lsl", "#pragma once\ninteger c;\n"); ALPreprocessor::Result twice = ALPreprocessor::run("#include \"c.lsl\"\n#include \"c.lsl\"\n", o); - UNSCOPED_INFO("nothing said"); REQUIRE(twice.problems.empty()); - UNSCOPED_INFO("listed once"); REQUIRE(twice.includes.size() == size_t(1)); UNSCOPED_INFO("and put in once"); CHECK(twice.text.find("integer c;") == twice.text.rfind("integer c;")); @@ -1052,7 +986,6 @@ namespace UNSCOPED_INFO("the expansion alone leaves it as it was"); REQUIRE(in_two.text.find("// comment") != std::string::npos); ALPreprocessor::finish(in_two, whole); - UNSCOPED_INFO("the same text"); CHECK(in_two.text == at_once.text); UNSCOPED_INFO("and the same way back"); CHECK(in_two.map.toSource(0, 0).line == at_once.map.toSource(0, 0).line); @@ -1063,13 +996,12 @@ namespace ALPreprocessor::Options opts = options(); opts.defines = { "DEBUG", "LEVEL=3", " SPACED = 7 ", "2BAD", "" }; ALPreprocessor::Result r = ALPreprocessor::run("#if DEBUG\nx = LEVEL;\n#endif\ny = SPACED;\n#undef LEVEL\nz = LEVEL;\n", opts); - UNSCOPED_INFO("no problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("DEBUG is 1, so the block is in: " + r.text); + UNSCOPED_INFO("DEBUG is 1, so the block is in: " << r.text); REQUIRE(r.text.find("x = 3;") != std::string::npos); - UNSCOPED_INFO("a value around blanks: " + r.text); + UNSCOPED_INFO("a value around blanks: " << r.text); REQUIRE(r.text.find("y = 7;") != std::string::npos); - UNSCOPED_INFO("undefined by the script: " + r.text); + UNSCOPED_INFO("undefined by the script: " << r.text); REQUIRE(r.text.find("z = LEVEL;") != std::string::npos); } TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a run is bounded through chained ?: and nested calls and self includes", "[alpreprocessor]") @@ -1088,7 +1020,7 @@ namespace } chain += "\nyes\n#endif\n"; ALPreprocessor::Result r = ALPreprocessor::run(chain, options()); - UNSCOPED_INFO("the chain is too deep: " + messages(r).substr(0, 200)); + UNSCOPED_INFO("the chain is too deep: " << messages(r).substr(0, 200)); REQUIRE(messages(r).find("nests too deeply") != std::string::npos); // A macro invoked in its own argument, deeper than the bound. @@ -1104,9 +1036,8 @@ namespace } nested += ";\n"; r = ALPreprocessor::run(nested, options()); - UNSCOPED_INFO("overran"); CHECK(r.overran); - UNSCOPED_INFO("and said why: " + messages(r).substr(0, 200)); + UNSCOPED_INFO("and said why: " << messages(r).substr(0, 200)); REQUIRE(messages(r).find("more deeply than this preprocessor follows") != std::string::npos); UNSCOPED_INFO("the text is the source as it was"); CHECK(r.text == nested); @@ -1114,7 +1045,7 @@ namespace // Within the bound, as deep as a person writes, it expands. r = ALPreprocessor::run("#define F(x) x\nF(F(F(F(F(1)))));\n", options()); { - INFO("five deep is nothing: " + messages(r)); + INFO("five deep is nothing: " << messages(r)); CHECK_FALSE(r.overran); REQUIRE(r.text.find("1;") != std::string::npos); } @@ -1134,7 +1065,7 @@ namespace ALPreprocessor::Options opts = options(); opts.fileName = "Say \"hi\" \\ there"; ALPreprocessor::Result r = ALPreprocessor::run("llSay(0, __FILE__);\nllSay(0, __SHORTFILE__);\n", opts); - UNSCOPED_INFO("the quote and the backslash escaped: " + r.text); + UNSCOPED_INFO("the quote and the backslash escaped: " << r.text); REQUIRE(r.text.find("\"Say \\\"hi\\\" \\\\ there\"") != std::string::npos); // A module found on a Windows disk: required by its number, and @@ -1145,12 +1076,11 @@ namespace module.path = "disk:C:\\Users\\me\\lib\\util.luau"; files["./util"] = module; r = ALPreprocessor::run("local util = require(\"./util\")\n", options(true)); - UNSCOPED_INFO("nothing wrong"); CHECK(faults(r) == std::string()); - UNSCOPED_INFO("required by its number: " + r.text); + UNSCOPED_INFO("required by its number: " << r.text); REQUIRE(r.text.find("local util = require(1)") != std::string::npos); { - INFO("no path: " + r.text); + INFO("no path: " << r.text); REQUIRE(r.text.find("disk:") == std::string::npos); REQUIRE(r.text.find("Users") == std::string::npos); } @@ -1168,7 +1098,7 @@ namespace } } { - INFO("the string whole, then what follows it: " + std::to_string(kinds.size())); + INFO("the string whole, then what follows it"); REQUIRE(kinds.size() >= 6); CHECK(kinds[3] == "`a{ \"{\" .. `{'}'}` }b`"); CHECK(kinds[4] == ".."); @@ -1177,7 +1107,7 @@ namespace // And the macro after it is expanded. ALPreprocessor::Result r = ALPreprocessor::run("--#define X 42\nlocal s = `{\"{\"}` .. X\n", options(true)); - UNSCOPED_INFO("the macro after the string expanded: " + r.text); + UNSCOPED_INFO("the macro after the string expanded: " << r.text); REQUIRE(r.text.find(".. 42") != std::string::npos); } @@ -1200,9 +1130,8 @@ namespace } blocks += "\n} }\n"; ALPreprocessor::Result r = ALPreprocessor::run(blocks, opts); - UNSCOPED_INFO("overran"); CHECK(r.overran); - UNSCOPED_INFO("and said why: " + messages(r).substr(0, 200)); + UNSCOPED_INFO("and said why: " << messages(r).substr(0, 200)); REQUIRE(messages(r).find("blocks nest more deeply") != std::string::npos); UNSCOPED_INFO("the text is the source as it was"); CHECK(r.text == blocks); @@ -1221,7 +1150,7 @@ namespace r = ALPreprocessor::run(switches, opts); UNSCOPED_INFO("switches overran"); CHECK(r.overran); - UNSCOPED_INFO("and said why: " + messages(r).substr(0, 200)); + UNSCOPED_INFO("and said why: " << messages(r).substr(0, 200)); REQUIRE(messages(r).find("blocks nest more deeply") != std::string::npos); // As deep as anybody writes, done. @@ -1238,11 +1167,11 @@ namespace loops += "\n} }\n"; r = ALPreprocessor::run(loops, opts); { - INFO("thirty deep is nothing: " + messages(r).substr(0, 200)); + INFO("thirty deep is nothing: " << messages(r).substr(0, 200)); CHECK_FALSE(r.overran); CHECK_FALSE(r.hasErrors()); } - UNSCOPED_INFO("and the loops were lowered: " + r.text.substr(0, 200)); + UNSCOPED_INFO("and the loops were lowered: " << r.text.substr(0, 200)); REQUIRE(r.text.find("jump _brk") != std::string::npos); // A loop governing a long chain of else-ifs, on a thread with what @@ -1257,7 +1186,7 @@ namespace std::thread worker([&]() { r = ALPreprocessor::run(chain, opts); }); worker.join(); { - INFO("the chain is no deeper for its length: " + messages(r).substr(0, 200)); + INFO("the chain is no deeper for its length: " << messages(r).substr(0, 200)); CHECK_FALSE(r.overran); CHECK_FALSE(r.hasErrors()); } @@ -1269,9 +1198,8 @@ namespace { const std::string source = "#define X 1\n/* one\n two\n three */ integer x = X;\n"; ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("nothing wrong"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("the comment on the lines it was on: " + r.text); + UNSCOPED_INFO("the comment on the lines it was on: " << r.text); REQUIRE(r.text.find("\n/* one\n two\n three */ integer x = 1;") != std::string::npos); ALSourceMap::Loc loc = r.map.toSource(2, 4); UNSCOPED_INFO("the comment's middle line maps"); @@ -1298,7 +1226,6 @@ namespace const std::string lua = "--#define X 2\nlocal s = [[one\ntwo\nthree]] .. X\n"; r = ALPreprocessor::run(lua, options(true)); - UNSCOPED_INFO("nothing wrong in SLua"); CHECK(messages(r) == std::string()); loc = r.map.toSource(2, 1); { @@ -1328,15 +1255,11 @@ namespace return t; }; std::string word; - UNSCOPED_INFO("switch"); CHECK(of({ " switch (x)" }, 0, &word) == T::Switch); - UNSCOPED_INFO("its word"); CHECK(word == std::string("switch")); UNSCOPED_INFO("switch not called"); CHECK(of({ "switch = 2;" }, 0) == T::None); - UNSCOPED_INFO("a case"); CHECK(of({ " case 1:" }, 0, &word) == T::Switch); - UNSCOPED_INFO("the case's word"); CHECK(word == std::string("case")); UNSCOPED_INFO("case assigned"); CHECK(of({ "case = 1;" }, 0) == T::None); @@ -1344,23 +1267,19 @@ namespace CHECK(of({ "case(1);" }, 0) == T::None); UNSCOPED_INFO("case with no colon"); CHECK(of({ "case 1" }, 0) == T::None); - UNSCOPED_INFO("break"); CHECK(of({ "break;" }, 0) == T::Extensions); UNSCOPED_INFO("break out of two"); CHECK(of({ "break 2;" }, 0) == T::Extensions); - UNSCOPED_INFO("continue"); CHECK(of({ "\tcontinue;" }, 0) == T::Extensions); UNSCOPED_INFO("break assigned"); CHECK(of({ "break = 3;" }, 0) == T::None); UNSCOPED_INFO("an inline function"); CHECK(of({ "inline f(integer a)" }, 0, &word) == T::Extensions); - UNSCOPED_INFO("inline's word"); CHECK(word == std::string("inline")); UNSCOPED_INFO("an inline function with its type"); CHECK(of({ "inline integer f(integer a)" }, 0) == T::Extensions); UNSCOPED_INFO("inline after the parameters"); CHECK(of({ "integer f(integer a) inline {" }, 0, &word) == T::Extensions); - UNSCOPED_INFO("its word"); CHECK(word == std::string("inline")); UNSCOPED_INFO("inline after them, the brace on the next line"); CHECK(of({ "f() inline" }, 0) == T::Extensions); @@ -1370,7 +1289,6 @@ namespace CHECK(of({ "inline = 2;" }, 0) == T::None); UNSCOPED_INFO("const before a type"); CHECK(of({ "const integer X = 1;" }, 0, &word) == T::Extensions); - UNSCOPED_INFO("const's word"); CHECK(word == std::string("const")); UNSCOPED_INFO("a const parameter"); CHECK(of({ "f(integer a, const vector v)" }, 0) == T::Extensions); @@ -1401,7 +1319,7 @@ namespace TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: verbatimSpan maps copied source back whole but not a macro's output", "[alpreprocessor]") { const ALPreprocessor::Result r = ALPreprocessor::run("#define GREET llOwnerSay(\"hi\")\nGREET;\n llSay(0, \"x\");\n", options()); - UNSCOPED_INFO("no problems: " + messages(r)); + UNSCOPED_INFO("no problems: " << messages(r)); REQUIRE(r.problems.empty()); // Where each call landed in the output. S32 greet_line = -1, greet_column = -1, say_line = -1, say_column = -1; @@ -1421,7 +1339,7 @@ namespace } } { - INFO("both in the output: " + r.text); + INFO("both in the output: " << r.text); CHECK(greet_line >= 0); CHECK(say_line >= 0); } @@ -1451,7 +1369,7 @@ namespace TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a weighed optimizer run says what it saved in all and per note", "[alpreprocessor]") { - ensureGridBuiltins(); + requireGridBuiltins(); const std::string source = "integer unused(integer n)\n" "{\n" " return n * 3;\n" @@ -1468,10 +1386,9 @@ namespace o.optimizer.target = ALLSLOptimizer::Target::LSO; o.weigh = true; const ALPreprocessor::Result r = ALPreprocessor::run(source, o); - UNSCOPED_INFO("optimized"); CHECK(r.optimized); { - INFO("weighed, and lighter: " + std::to_string(r.codeBefore) + " to " + std::to_string(r.codeAfter)); + INFO("weighed, and lighter"); CHECK(r.codeAfter > 0); CHECK(r.codeAfter < r.codeBefore); } @@ -1489,7 +1406,7 @@ namespace REQUIRE(removed); REQUIRE(removed->savedBytes.has_value()); } - UNSCOPED_INFO("its lines' code all saved: " + std::to_string(removed->savedBytes.value_or(0))); + UNSCOPED_INFO("its lines' code all saved"); CHECK(*removed->savedBytes > 0); { INFO("the fold noted, with what its line saved"); @@ -1529,7 +1446,7 @@ namespace TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a script that does not compile: the optimizer stands aside with a note", "[alpreprocessor]") { - ensureGridBuiltins(); + requireGridBuiltins(); // Nexii's library, whose ObjectLinksetSittingAvatars has a parameter // without its type, included as a save would include it. llifstream in(std::string(AL_ALSCRIPT_TEST_DIR) + "/preprocessor/include/linkset.lsl", std::ios::binary); @@ -1541,18 +1458,14 @@ namespace o.optimize = true; o.weigh = true; ALPreprocessor::Result r = ALPreprocessor::run(source, o); - UNSCOPED_INFO("no error of the preprocessor's: " + messages(r)); + UNSCOPED_INFO("no error of the preprocessor's: " << messages(r)); CHECK_FALSE(r.hasErrors()); UNSCOPED_INFO("one note, on the library's line"); CHECK(messages(r) == std::string("N linkset.lsl:110: not optimized: the script does not compile as it stands, and compiling it says why\n")); - { - INFO("keyed"); - REQUIRE(r.problems.size() == 1); - CHECK(r.problems[0].key == "OptimizerUncompiled"); - CHECK(r.problems[0].column == 33); - } - UNSCOPED_INFO("not optimized"); + REQUIRE(r.problems.size() == 1); + CHECK(r.problems[0].key == "OptimizerUncompiled"); + CHECK(r.problems[0].column == 33); CHECK_FALSE(r.optimized); ALPreprocessor::Options plain = options(); UNSCOPED_INFO("the text as expanded, for the compiler to read"); @@ -1566,7 +1479,6 @@ namespace "local b = t : require(\"x\")\n" "local c = t .. require(\"x\")\n", options(true)); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("only the concatenated call is a module's"); CHECK(r.text == @@ -1589,9 +1501,7 @@ namespace "local h = require([[h]])\n" "local i = x .. require(\"i\")\n"; // 10 const std::vector found = ALPreprocessor::requiresIn(source); - UNSCOPED_INFO("three"); REQUIRE(found.size() == size_t(3)); - UNSCOPED_INFO("a"); CHECK(found[0].name == std::string("a")); { INFO("a's stretch"); @@ -1600,7 +1510,6 @@ namespace CHECK(found[0].endLine == 0); CHECK(found[0].endColumn == 22); } - UNSCOPED_INFO("c"); CHECK(found[1].name == std::string("c")); { INFO("c's stretch, across lines"); @@ -1609,7 +1518,6 @@ namespace CHECK(found[1].endLine == 4); CHECK(found[1].endColumn == 1); } - UNSCOPED_INFO("i"); CHECK(found[2].name == std::string("i")); { INFO("i's line"); @@ -1623,7 +1531,6 @@ namespace add("c", "return 3\n"); add("i", "return 9\n"); const ALPreprocessor::Result r = ALPreprocessor::run(source, options(true)); - UNSCOPED_INFO("problems"); CHECK(faults(r) == std::string()); // Each module's function is put at the top once, and asked for by // its number where it was called for. @@ -1646,7 +1553,6 @@ namespace const ALPreprocessor::Result r = ALPreprocessor::run("#include \"lib/util.lsl\"\n#include \"lib/util.lsl\"\ndefault {}\n", options()); // Included twice, with no #pragma once: its own include asked for // twice as well. - UNSCOPED_INFO("four found"); REQUIRE(r.resolved.size() == size_t(4)); { INFO("the script's, by its name, as the identity found"); @@ -1689,7 +1595,7 @@ namespace const std::string many = "#define D(x) x x x x x x x x x x x x x x x x\nD(D(D(D(D(D(D(1)))))))\n"; ALPreprocessor::Result r = ALPreprocessor::run(many, options()); { - INFO("ran away: " + messages(r)); + INFO("ran away: " << messages(r)); CHECK(r.overran); CHECK(r.problems.back().key == std::string("PreprocTooMuch")); } @@ -1705,7 +1611,7 @@ namespace } r = ALPreprocessor::run("#define CAT(a) a##a\n#define D(a) CAT(a)\n" + call + ";\n", options()); { - INFO("pasting stopped: " + messages(r)); + INFO("pasting stopped: " << messages(r)); CHECK(r.overran); CHECK(r.problems.back().key == std::string("PreprocTokenTooLong")); } @@ -1718,7 +1624,7 @@ namespace } r = ALPreprocessor::run("#define S(x) #x\n#define X(x) S(x)\n" + call + ";\n", options()); { - INFO("stringizing stopped: " + messages(r)); + INFO("stringizing stopped: " << messages(r)); CHECK(r.overran); CHECK(r.problems.back().key == std::string("PreprocTokenTooLong")); } @@ -1729,13 +1635,13 @@ namespace small.byteBudget = 1000; r = ALPreprocessor::run("#define L \"" + std::string(600, 'a') + "\"\nL L\n", small); { - INFO("the bytes: " + messages(r)); + INFO("the bytes: " << messages(r)); CHECK(r.overran); CHECK(r.problems.back().key == std::string("PreprocTooMuch")); } r = ALPreprocessor::run("#define L \"" + std::string(100, 'a') + "\"\nL L\n", small); { - INFO("within them: " + messages(r)); + INFO("within them: " << messages(r)); CHECK_FALSE(r.overran); REQUIRE(r.text.find(std::string(100, 'a')) != std::string::npos); } @@ -1776,7 +1682,7 @@ namespace o.lazyLists = true; ALPreprocessor::Result r = ALPreprocessor::run(deep, o); { - INFO("stopped at the depth: " + messages(r)); + INFO("stopped at the depth: " << messages(r)); CHECK(r.overran); CHECK(r.problems.back().key == std::string("PreprocNestsTooDeep")); } @@ -1784,7 +1690,7 @@ namespace // Within it, each read is a call, the inner in the outer's place. r = ALPreprocessor::run("#define N0 0\n#define N1 (integer)l[N0]\n#define N2 (integer)l[N1]\n#define N3 (integer)l[N2]\nlist l;\ninteger x = N3;\n", o); { - INFO("nested reads each a call: " + r.text); + INFO("nested reads each a call: " << r.text); CHECK_FALSE(r.overran); REQUIRE(r.text.find("integer x = llList2Integer(l, llList2Integer(l, llList2Integer(l, 0)));") != std::string::npos); } @@ -1795,7 +1701,6 @@ namespace add("m", "return 1\n"); const ALPreprocessor::Result r = ALPreprocessor::run("-- @file vehicle/hovertext.luau\n--!strict\n--!nolint LocalUnused\nlocal m = require(\"m\")\n", options(true)); - UNSCOPED_INFO("problems"); CHECK(faults(r) == std::string()); UNSCOPED_INFO("the header first, whole"); CHECK(r.text.substr(0, r.text.find("do\n local modules")) == @@ -1858,14 +1763,12 @@ namespace add("guarded.lsl", "// the helpers\n#ifndef GUARDED\n#define GUARDED\ninteger helper() { return 1; }\n#endif\n"); const ALPreprocessor::Result r = ALPreprocessor::run( "#include \"guarded.lsl\"\n#include \"guarded.lsl\"\n#include \"guarded.lsl\"\ndefault { state_entry() { helper(); } }\n", options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); size_t count = 0; for (size_t at = r.text.find("integer helper()"); at != std::string::npos; at = r.text.find("integer helper()", at + 1)) { ++count; } - UNSCOPED_INFO("once"); CHECK(count == size_t(1)); UNSCOPED_INFO("listed once"); REQUIRE(r.map.files().size() == size_t(2)); @@ -1894,14 +1797,12 @@ namespace "TWO(1,\n2) TWO( (1, 2) ,\t3 )\n" "I\n(3)\n"; const ALPreprocessor::Result r = ALPreprocessor::run(source, options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("text"); CHECK(squeeze(r.text) == std::string("zero zero Z\n[1] I x I\n1+2 (1, 2)+3\n[3]\n")); } TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: a function marked inline in any form is listed and the mark taken off", "[alpreprocessor]") { - ensureGridBuiltins(); + requireGridBuiltins(); ALPreprocessor::Options o = options(); o.extensions = true; ALPreprocessor::Result r = ALPreprocessor::run("integer f(integer a) inline { return a; }\n" @@ -1909,14 +1810,13 @@ namespace "h() //pragma inline\n{\n}\n" "k() /* pragma inline */ { }\n", o); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("the four marked, in order"); CHECK(r.inlined == std::vector{ "f", "g", "h", "k" }); - UNSCOPED_INFO("the word after the parameters taken off: " + r.text); + UNSCOPED_INFO("the word after the parameters taken off: " << r.text); REQUIRE(r.text.find("integer f(integer a) { return a; }") != std::string::npos); { - INFO("a comment left as it is: " + r.text); + INFO("a comment left as it is: " << r.text); REQUIRE(r.text.find("g(string s) /*pragma inline*/ { llOwnerSay(s); }") != std::string::npos); REQUIRE(r.text.find("h() //pragma inline\n{") != std::string::npos); } @@ -1947,7 +1847,7 @@ namespace "default\n{\n state_entry()\n {\n say(\"a\");\n say(\"b\");\n }\n}\n", optimizing); { - INFO("both calls put in place: " + put.text); + INFO("both calls put in place: " << put.text); REQUIRE(put.text.find("say(") == std::string::npos); REQUIRE(put.text.find("llOwnerSay(\"a\")") != std::string::npos); REQUIRE(put.text.find("llOwnerSay(\"b\" + \"!\")") != std::string::npos); @@ -1956,7 +1856,7 @@ namespace TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: const before a type is taken off and each name said where it is", "[alpreprocessor]") { - ensureGridBuiltins(); + requireGridBuiltins(); const auto at = [](const ALPreprocessor::Result& r, size_t i, const std::string& name, S32 line, S32 column, bool function) { return i < r.consts.size() && r.consts[i].name == name && r.consts[i].line == line && r.consts[i].column == column && r.consts[i].function == function; @@ -1966,7 +1866,6 @@ namespace "const integer sq(const integer x) { return x * x; }\n" "default { state_entry() { const vector v = <1, 2, 3>; llOwnerSay((string)v); } }\n", options()); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("taken off"); CHECK(r.text == @@ -1974,7 +1873,6 @@ namespace "float Y = 10.0;\n" "integer sq(integer x) { return x * x; }\n" "default { state_entry() { vector v = <1, 2, 3>; llOwnerSay((string)v); } }\n")); - UNSCOPED_INFO("five"); REQUIRE(r.consts.size() == size_t(5)); UNSCOPED_INFO("a global"); CHECK(at(r, 0, "X", 0, 8, false)); @@ -2001,7 +1899,6 @@ namespace ALPreprocessor::Options o = options(); o.extensions = true; r = ALPreprocessor::run("#define FIXED const integer\nFIXED Z = 3;\n", o); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("a macro's"); CHECK(squeeze(r.text) == std::string("integer Z = 3;\n")); @@ -2021,7 +1918,6 @@ namespace // A line that starts with # goes on an expression: never a // directive, and nothing said of it. ALPreprocessor::Result r = ALPreprocessor::run("local n =\n #items\n#define N 3\nprint(N)\n", options(true)); - UNSCOPED_INFO("nothing wrong"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("# lines are Luau's"); CHECK(r.text == std::string("local n =\n #items\n#define N 3\nprint(N)\n")); @@ -2042,7 +1938,6 @@ namespace "--#endif\n" "--#line 40\n", options(true)); - UNSCOPED_INFO("nothing wrong"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("directives read, # as length"); CHECK(r.text == @@ -2059,7 +1954,6 @@ namespace "local z = 1 --#define Z 3\n" "print(X, Y, Z)\n"; r = ALPreprocessor::run(comments, options(true)); - UNSCOPED_INFO("nothing wrong"); CHECK(messages(r) == std::string()); UNSCOPED_INFO("all as written"); CHECK(r.text == comments); @@ -2067,7 +1961,6 @@ namespace // An include, as LSL has one. add("lib.luau", "--#define GREETING \"hi\"\nlocal lib = {}\n"); r = ALPreprocessor::run("--#include \"lib.luau\"\nprint(GREETING)\n", options(true)); - UNSCOPED_INFO("nothing wrong"); CHECK(messages(r) == std::string()); { INFO("the include's text in, and its macro"); @@ -2078,7 +1971,7 @@ namespace // Its errors are the directive's. r = ALPreprocessor::run("--#endif\n--#if\n--#endif\n", options(true)); { - INFO("an #endif without #if, and an #if without an expression: " + messages(r)); + INFO("an #endif without #if, and an #if without an expression: " << messages(r)); REQUIRE(messages(r).find("E 0:") != std::string::npos); REQUIRE(messages(r).find("E 1:") != std::string::npos); } @@ -2112,12 +2005,11 @@ namespace " print(x)\n" "SAY(\"hi\")\n"; ALPreprocessor::Result r = ALPreprocessor::run(lf, options(true)); - UNSCOPED_INFO("nothing wrong"); CHECK(messages(r) == std::string()); const std::string joined = r.text; const size_t used = joined.find("ll.OwnerSay(\"hi\")"); { - INFO("the body on the line that uses it: " + joined); + INFO("the body on the line that uses it: " << joined); REQUIRE(used != std::string::npos); CHECK(joined.find("print(\"hi\")") > used); CHECK(joined.find('\n', used) > joined.find("print(\"hi\")")); @@ -2155,7 +2047,7 @@ namespace r = ALPreprocessor::run("--#include \"say.luau\"\nSAY(1)\n", options(true)); UNSCOPED_INFO("an include with CRLF said nothing"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("and its macro goes on over its lines: " + r.text); + UNSCOPED_INFO("and its macro goes on over its lines: " << r.text); REQUIRE(r.text.find("print(1)") != std::string::npos); } @@ -2177,9 +2069,8 @@ namespace { const std::string said = ending[1] ? " with CRLF" : " with a lone CR"; ALPreprocessor::Result r = runGolden(entry.path(), ending); - UNSCOPED_INFO("problems in " + name + said); + INFO(name << said); REQUIRE(messages(r) == std::string()); - UNSCOPED_INFO("output of " + name + said); REQUIRE(r.text == want); } ++checked; @@ -2191,7 +2082,6 @@ namespace TEST_CASE_METHOD(alpreprocessor_data, "alpreprocessor: usesDirectives and wanted: why a script is preprocessed", "[alpreprocessor]") { typedef ALPreprocessor P; - UNSCOPED_INFO("#define"); CHECK(P::usesDirectives("#define X 1\ndefault { state_entry() { } }\n", false)); UNSCOPED_INFO("one further down, indented"); CHECK(P::usesDirectives("integer x;\n #include \"lib.lsl\"\n", false)); @@ -2251,7 +2141,6 @@ namespace return ALPreprocessor::Found::No; }; const ALPreprocessor::Result r = ALPreprocessor::run("local util = require('util')\nlocal other = require(\"other\")\n", o); - UNSCOPED_INFO("both said"); REQUIRE(r.problems.size() == size_t(2)); const ALScriptProblem& moved = r.problems[0]; UNSCOPED_INFO("where the search found it"); @@ -2272,11 +2161,8 @@ namespace CHECK(plain.edits[0].column == 21); CHECK(plain.edits[0].endColumn == 27); } - { - INFO("preferred"); - CHECK(plain.preferred); - CHECK(plain.key == "ScriptFixRequireAs"); - } + CHECK(plain.preferred); + CHECK(plain.key == "ScriptFixRequireAs"); const ALScriptFix& alias = moved.fixes[1]; { INFO("the alias's named in it, and not preferred"); @@ -2303,14 +2189,10 @@ namespace return ALPreprocessor::Found::Yes; }; const ALPreprocessor::Result r = ALPreprocessor::run("local both = require('./both')\nprint(both)\n", o); - UNSCOPED_INFO("one said"); REQUIRE(r.problems.size() == size_t(1)); const ALScriptProblem& said = r.problems[0]; - { - INFO("a warning"); - CHECK(said.severity == ALScriptProblem::Severity::Warning); - CHECK(said.key == "PreprocModuleBesideInit"); - } + CHECK(said.severity == ALScriptProblem::Severity::Warning); + CHECK(said.key == "PreprocModuleBesideInit"); UNSCOPED_INFO("naming both, each from the folder both are in"); CHECK(said.args == std::vector({ "./both", "both.luau", "both/init.luau" })); UNSCOPED_INFO("the module taken"); @@ -2325,7 +2207,6 @@ namespace return ALPreprocessor::Found::No; }; const ALPreprocessor::Result r = ALPreprocessor::run("require('./a')\nrequire('./b')\nrequire('./c')\n", o); - UNSCOPED_INFO("each said"); REQUIRE(r.problems.size() == size_t(3)); { INFO("keyed"); @@ -2371,7 +2252,6 @@ namespace "shared = own.inner == a\n" "ownSees = own.sees\n", options(true)); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); // Run in SLua's VM, its globals read back. @@ -2392,10 +2272,10 @@ namespace // module of the scripter's making. lua_setmemcat(L, LUA_FIRST_USER_MEMCAT); const std::string bytecode = Luau::compile(r.text); - UNSCOPED_INFO("loads: " + r.text); + UNSCOPED_INFO("loads: " << r.text); REQUIRE(luau_load(L, "=bundle", bytecode.data(), bytecode.size(), 0) == 0); const int status = lua_pcall(L, 0, 0, 0); - UNSCOPED_INFO(std::string("runs: ") + (status != 0 && lua_isstring(L, -1) ? lua_tostring(L, -1) : "") + "\n" + r.text); + UNSCOPED_INFO("runs: " << (status != 0 && lua_isstring(L, -1) ? lua_tostring(L, -1) : "") << "\n" << r.text); CHECK(status == 0); const auto truth = [L](const char* name) { lua_getglobal(L, name); @@ -2477,17 +2357,16 @@ namespace lua.lineComments = true; 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); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("the reader finds every line: " + why); + UNSCOPED_INFO("the reader finds every line: " << why); CHECK(readsTrue(r, "main.luau", why)); - UNSCOPED_INFO("the header still first: " + r.text); + 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); + UNSCOPED_INFO("a module's first: " << r.text); REQUIRE(r.text.find(" [1] = function()\n-- @line 1 \"util\"\nlocal M = {}\n") != std::string::npos); - UNSCOPED_INFO("one past a directive: " + r.text); + UNSCOPED_INFO("one past a directive: " << r.text); REQUIRE(r.text.find("-- @line 2 \"deep\"\nreturn { k = 2 }\n") != std::string::npos); - UNSCOPED_INFO("the script's after the modules: " + r.text); + UNSCOPED_INFO("the script's after the modules: " << r.text); REQUIRE(r.text.find("end\n-- @line 2 \"main.luau\"\nlocal util = require(1)\n") != std::string::npos); UNSCOPED_INFO("three, no more"); CHECK(std::count(r.text.begin(), r.text.end(), '@') == 3); @@ -2503,16 +2382,15 @@ namespace ALPreprocessor::Options lsl = options(); 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); - UNSCOPED_INFO("problems"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("the reader finds every line: " + why); + UNSCOPED_INFO("the reader finds every line: " << why); CHECK(readsTrue(r, "main.lsl", why)); - UNSCOPED_INFO("in LSL's comment: " + r.text); + UNSCOPED_INFO("in LSL's comment: " << r.text); REQUIRE(r.text.find("// @line 1 \"inc.lsl\"\ninteger x;\n") != std::string::npos); // Nothing moved: none at all. r = ALPreprocessor::run("integer a;\n\ndefault { state_entry() { } }\n", lsl); - UNSCOPED_INFO("nothing to say: " + r.text); + UNSCOPED_INFO("nothing to say: " << r.text); REQUIRE(r.text.find("@line") == std::string::npos); } @@ -2534,7 +2412,7 @@ namespace ALPreprocessor::Result r = ALPreprocessor::run("local util = require(\"util\")\nlocal used = require(\"used\")\nprint(used)\n", options(true)); std::vector said = notes(r); - UNSCOPED_INFO("one note: " + messages(r)); + UNSCOPED_INFO("one note: " << messages(r)); REQUIRE(said.size() == size_t(1)); { INFO("a note, at the require"); @@ -2548,7 +2426,7 @@ namespace REQUIRE(said[0]->args[0] == "util"); CHECK(said[0]->args[1] == "util"); } - UNSCOPED_INFO("with its weight: " + said[0]->args[2]); + UNSCOPED_INFO("with its weight: " << said[0]->args[2]); CHECK(std::atoi(said[0]->args[2].c_str()) > 0); UNSCOPED_INFO("still in the bundle"); REQUIRE(r.text.find("M.twice") != std::string::npos); @@ -2572,7 +2450,7 @@ namespace files["wrap"] = wrap; r = ALPreprocessor::run("local w = require(\"wrap\")\nprint(w)\n", options(true)); said = notes(r); - UNSCOPED_INFO("one, in the module: " + messages(r)); + UNSCOPED_INFO("one, in the module: " << messages(r)); REQUIRE(said.size() == size_t(1)); { INFO("in its file, at its require"); diff --git a/indra/alscript/tests/alpreprocessorc11_test.cpp b/indra/alscript/tests/alpreprocessorc11_test.cpp index 2c9bc1404d..b499894890 100644 --- a/indra/alscript/tests/alpreprocessorc11_test.cpp +++ b/indra/alscript/tests/alpreprocessorc11_test.cpp @@ -170,11 +170,9 @@ namespace std::string("string s = \"ab\\ncd\" ;")); { const ALPreprocessor::Result r = run("// a note \\\nint hidden;\nint shown;\n"); - { - INFO("a // comment goes on over the join: " + r.text); - REQUIRE(r.text.find("\nint hidden") == std::string::npos); - REQUIRE(r.text.find("int shown;") != std::string::npos); - } + INFO("a // comment goes on over the join: " + r.text); + CHECK(r.text.find("\nint hidden") == std::string::npos); + CHECK(r.text.find("int shown;") != std::string::npos); } UNSCOPED_INFO("the lines after keep their numbers"); CHECK(pp(std::string("#define A 1 \\\n" @@ -1000,9 +998,7 @@ namespace "--#if B or A ~= 2\ntwo\n--#endif\n" "--#if (A && !B) == 1\nthree\n--#endif\n" "--#if not A or B\nno\n--#else\nfour\n--#endif\n", lua); - UNSCOPED_INFO("said"); CHECK(messages(r) == std::string()); - UNSCOPED_INFO("taken"); CHECK(flat(r.text) == std::string("one two three four")); } } @@ -1024,9 +1020,9 @@ namespace const std::string joined = pp(lf); { INFO("the macro's lines are its body: " + joined); - REQUIRE(joined.find("PROBLEMS") == std::string::npos); - REQUIRE(joined.find("llSay") != std::string::npos); - REQUIRE(joined.find('\\') == std::string::npos); + CHECK(joined.find("PROBLEMS") == std::string::npos); + CHECK(joined.find("llSay") != std::string::npos); + CHECK(joined.find('\\') == std::string::npos); CHECK(joined.find("default") == 0); } UNSCOPED_INFO("CRLF"); @@ -1050,8 +1046,7 @@ namespace { const ALPreprocessor::Result r = run("// a note \\ \nint shown;\n"); UNSCOPED_INFO("a note's backslash and a blank take no line: " + r.text); - REQUIRE(r.text.find("int shown;") != std::string::npos); - UNSCOPED_INFO("nothing said"); + CHECK(r.text.find("int shown;") != std::string::npos); CHECK(messages(r) == std::string()); } } diff --git a/indra/alscript/tests/alscriptenvelope_test.cpp b/indra/alscript/tests/alscriptenvelope_test.cpp index a0e40038aa..56f5dd0e8f 100644 --- a/indra/alscript/tests/alscriptenvelope_test.cpp +++ b/indra/alscript/tests/alscriptenvelope_test.cpp @@ -122,23 +122,15 @@ namespace "//last_compiled 09/20/2026 10:11:12\n" "//mono\n" "default { state_entry() { llSay(0, (string)1); } }\n"; - UNSCOPED_INFO("looks wrapped"); CHECK(ALScriptEnvelope::looksWrapped(asset)); std::optional envelope = ALScriptEnvelope::parse(asset); - UNSCOPED_INFO("parsed"); REQUIRE(envelope.has_value()); - UNSCOPED_INFO("lsl"); CHECK_FALSE(envelope->lua); - UNSCOPED_INFO("the source"); CHECK(envelope->source == std::string("#define X 1\n/* a comment */\ndefault { state_entry() { llSay(0, (string)X); } }")); - UNSCOPED_INFO("the target"); CHECK(envelope->compileTarget == std::string("mono")); - UNSCOPED_INFO("the program"); CHECK(envelope->programVersion == std::string("Firestorm-Releasex64 7.1.11 (76496)")); - UNSCOPED_INFO("the date"); CHECK(envelope->lastCompiled == std::string("09/20/2026 10:11:12")); - UNSCOPED_INFO("the expanded code"); CHECK(envelope->expanded == std::string("default { state_entry() { llSay(0, (string)1); } }\n")); UNSCOPED_INFO("plain source is not an envelope"); @@ -161,13 +153,9 @@ namespace std::string("//start_unprocessed_text\n/*/|/ src\ndefault { }*/\n//end_unprocessed_text\n" "//nfo_preprocessor_version 0\n//program_version Alchemy 9.0.0\n//last_compiled now\n//lsl2\ndefault { }\n")); std::optional back = ALScriptEnvelope::parse(asset); - UNSCOPED_INFO("reads back"); REQUIRE(back.has_value()); - UNSCOPED_INFO("the source"); CHECK(back->source == envelope.source); - UNSCOPED_INFO("the expanded code"); CHECK(back->expanded == envelope.expanded); - UNSCOPED_INFO("the target"); CHECK(back->compileTarget == envelope.compileTarget); } @@ -186,35 +174,25 @@ namespace REQUIRE(asset.find("--[==[local t") != std::string::npos); REQUIRE(asset.find("]==]\n--end_unprocessed_text\n") != std::string::npos); } - UNSCOPED_INFO("looks wrapped"); CHECK(ALScriptEnvelope::looksWrapped(asset)); std::optional back = ALScriptEnvelope::parse(asset); - UNSCOPED_INFO("reads back"); REQUIRE(back.has_value()); - UNSCOPED_INFO("lua"); CHECK(back->lua); - UNSCOPED_INFO("the source"); CHECK(back->source == envelope.source); - UNSCOPED_INFO("the expanded code"); CHECK(back->expanded == envelope.expanded); - UNSCOPED_INFO("the target"); CHECK(back->compileTarget == std::string("luau")); } TEST_CASE_METHOD(alscriptenvelope_data, "alscriptenvelope: a directive is a line of its own", "[alscriptenvelope]") { - UNSCOPED_INFO("mono"); CHECK(ALScriptEnvelope::directiveOf("// x\n//mono\ndefault{}", false) == std::string("mono")); // As Firestorm has it: mono anywhere wins over lsl2 anywhere. UNSCOPED_INFO("mono over lsl2"); CHECK(ALScriptEnvelope::directiveOf("//lsl2\n//mono\n", false) == std::string("mono")); - UNSCOPED_INFO("lsl2"); CHECK(ALScriptEnvelope::directiveOf("//lsl2\n", false) == std::string("lsl2")); - UNSCOPED_INFO("none"); CHECK(ALScriptEnvelope::directiveOf("//monolith\n", false) == std::string()); UNSCOPED_INFO("not mid-line"); CHECK(ALScriptEnvelope::directiveOf("x //mono\n", false) == std::string()); - UNSCOPED_INFO("lua"); CHECK(ALScriptEnvelope::directiveOf("--luau\n", true) == std::string("luau")); UNSCOPED_INFO("not lua's in lsl"); CHECK(ALScriptEnvelope::directiveOf("--luau\n", false) == std::string()); @@ -252,20 +230,17 @@ namespace size_t level = 0; size_t body = 0; const size_t end = luaCommentEnd(asset, level, body); - UNSCOPED_INFO(c.name + ": the level"); + CAPTURE(c.name); REQUIRE(level == c.level); - UNSCOPED_INFO(c.name + ": the comment closes where the source ends"); + UNSCOPED_INFO("the comment closes where the source ends"); REQUIRE(end == body + c.source.size()); - UNSCOPED_INFO(c.name + ": and the end line follows the closer"); + UNSCOPED_INFO("and the end line follows the closer"); REQUIRE(asset.substr(end + level + 2, 24) == std::string("\n--end_unprocessed_text\n")); std::optional back = ALScriptEnvelope::parse(asset); - UNSCOPED_INFO(c.name + ": reads back"); REQUIRE(back.has_value()); - UNSCOPED_INFO(c.name + ": the source"); REQUIRE(back->source == c.source); - UNSCOPED_INFO(c.name + ": the expanded code"); REQUIRE(back->expanded == envelope.expanded); } } @@ -338,9 +313,9 @@ namespace from = nl + 1; } const int at = envelope.codeLine(); - UNSCOPED_INFO(std::string(lua ? "SLua" : "LSL") + ": the code's first line there"); + CAPTURE(lua); REQUIRE(lines[static_cast(at)] == std::string("first")); - UNSCOPED_INFO(std::string(lua ? "SLua" : "LSL") + ": after the source's lines and the envelope's own"); + UNSCOPED_INFO("after the source's lines and the envelope's own"); REQUIRE(at == 9); } } @@ -361,7 +336,7 @@ namespace envelope.compileTarget = target; envelope.programVersion = "Alchemy Test 1.2.3"; envelope.lastCompiled = "2026-09-27T12:00:00Z"; - UNSCOPED_INFO(std::string(lua ? "SLua " : "LSL ") + "'" + source + "' for '" + target + "'"); + CAPTURE(lua, source, target); REQUIRE(envelope.wrappedSize() == envelope.wrap().size()); } @@ -380,24 +355,20 @@ namespace lsl.header + lsl.expanded); for (const bool lua : { false, true }) { - const std::string name = lua ? "SLua: " : "LSL: "; + CAPTURE(lua); const ALScriptEnvelope written = alscriptenvelope_data::made(lua, true); std::optional back = ALScriptEnvelope::parse(written.wrap()); - UNSCOPED_INFO(name + "reads back"); REQUIRE(back.has_value()); - UNSCOPED_INFO(name + "the header"); REQUIRE(back->header == written.header); - UNSCOPED_INFO(name + "the code alone"); + UNSCOPED_INFO("the code alone"); REQUIRE(back->expanded == written.expanded); - UNSCOPED_INFO(name + "the source"); REQUIRE(back->source == written.source); - UNSCOPED_INFO(name + "the target"); REQUIRE(back->compileTarget == written.compileTarget); - UNSCOPED_INFO(name + "written again, the same"); + UNSCOPED_INFO("written again, the same"); REQUIRE(back->wrap() == written.wrap()); - UNSCOPED_INFO(name + "its size worked out"); + UNSCOPED_INFO("its size worked out"); REQUIRE(written.wrappedSize() == written.wrap().size()); - UNSCOPED_INFO(name + "and from the fields"); + UNSCOPED_INFO("and from the fields"); REQUIRE(ALScriptEnvelope::wrappedSize(lua, written.source, written.expanded, written.compileTarget, written.programVersion, written.lastCompiled, written.header) == written.wrap().size()); @@ -406,13 +377,10 @@ namespace // with nothing added. const ALScriptEnvelope plain = alscriptenvelope_data::made(lua, false); back = ALScriptEnvelope::parse(plain.wrap()); - UNSCOPED_INFO(name + "none: reads back"); REQUIRE(back.has_value()); - UNSCOPED_INFO(name + "none: no header"); REQUIRE(back->header == std::string()); - UNSCOPED_INFO(name + "none: the code"); REQUIRE(back->expanded == plain.expanded); - UNSCOPED_INFO(name + "none: the header's bytes and no more"); + UNSCOPED_INFO("without one: the header's bytes and no more"); REQUIRE(plain.wrap().size() + written.header.size() == written.wrap().size()); } @@ -431,7 +399,7 @@ namespace { INFO("untargeted: the header taken"); REQUIRE(back); - REQUIRE(back->header == untargeted.header); + CHECK(back->header == untargeted.header); CHECK(back->expanded == untargeted.expanded); } @@ -443,7 +411,7 @@ namespace { INFO("the plugin's taken"); REQUIRE(back); - REQUIRE(back->header == theirs.header); + CHECK(back->header == theirs.header); CHECK(back->expanded == theirs.expanded); } @@ -455,7 +423,7 @@ namespace { INFO("one header taken"); REQUIRE(back); - REQUIRE(back->header == twice.header); + CHECK(back->header == twice.header); CHECK(back->expanded == twice.expanded); } } @@ -464,13 +432,12 @@ namespace { for (const bool lua : { false, true }) { - const std::string name = lua ? "SLua: " : "LSL: "; + CAPTURE(lua); const ALScriptEnvelope without = alscriptenvelope_data::made(lua, false); const ALScriptEnvelope with = alscriptenvelope_data::made(lua, true); const int lines = static_cast(std::count(with.header.begin(), with.header.end(), '\n')); - UNSCOPED_INFO(name + "the header's lines"); REQUIRE(lines == 5); - UNSCOPED_INFO(name + "the code begins that much later"); + UNSCOPED_INFO("the code begins that much later"); REQUIRE(with.codeLine() == without.codeLine() + lines); // The asset's line codeLine() + n is the code's line n either @@ -480,16 +447,16 @@ namespace const std::vector code = alscriptenvelope_data::linesOf(with.expanded); for (size_t i = 0; i < code.size(); ++i) { - UNSCOPED_INFO(name + "code line " + std::to_string(i) + " without"); + CAPTURE(i); + UNSCOPED_INFO("without a header"); REQUIRE(plain[static_cast(without.codeLine()) + i] == code[i]); - UNSCOPED_INFO(name + "code line " + std::to_string(i) + " with"); + UNSCOPED_INFO("with a header"); REQUIRE(headed[static_cast(with.codeLine()) + i] == code[i]); } // And read back from the asset, as a save hears of it. const std::optional back = ALScriptEnvelope::parse(with.wrap()); - UNSCOPED_INFO(name + "reads back"); REQUIRE(back.has_value()); - UNSCOPED_INFO(name + "read back, the same line"); + UNSCOPED_INFO("read back, the same line"); REQUIRE(back->codeLine() == with.codeLine()); } } @@ -498,7 +465,7 @@ namespace { for (const bool lua : { false, true }) { - const std::string name = lua ? "SLua: " : "LSL: "; + CAPTURE(lua); const ALScriptEnvelope sent = alscriptenvelope_data::made(lua, true); ALScriptEnvelope later = alscriptenvelope_data::made(lua, true); ALUploadHeader again = *ALUploadHeader::parse(later.header, lua); @@ -507,27 +474,24 @@ namespace const std::optional held = ALScriptEnvelope::parse(sent.wrap()); const std::optional came = ALScriptEnvelope::parse(later.wrap()); - { - INFO(name + "both read"); - REQUIRE(held); - REQUIRE(came); - } - UNSCOPED_INFO(name + "the headers apart"); + REQUIRE(held); + REQUIRE(came); + UNSCOPED_INFO("the headers apart"); REQUIRE(held->header != came->header); // What the bridge holds up to tell an edit of the compiled half: // the code the same, whenever it went up. - UNSCOPED_INFO(name + "the code the same"); + UNSCOPED_INFO("the code the same"); REQUIRE(held->expanded == came->expanded); - UNSCOPED_INFO(name + "compiled from the expansion"); + UNSCOPED_INFO("compiled from the expansion"); REQUIRE(ALScriptEnvelope::compiledFrom(sent.expanded, came->expanded, lua)); // Even left in, a header is comments, which it passes over. - UNSCOPED_INFO(name + "a header in the code passed over"); + UNSCOPED_INFO("a header in the code passed over"); REQUIRE(ALScriptEnvelope::compiledFrom(sent.expanded, sent.header + sent.expanded, lua)); - UNSCOPED_INFO(name + "the source told as it was"); + UNSCOPED_INFO("the source told as it was"); 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. - UNSCOPED_INFO(name + "the hash again"); + UNSCOPED_INFO("the hash again"); REQUIRE(ALUploadHeader::hashOf(came->compileTarget, came->source, came->expanded) == ALUploadHeader::parse(came->header, lua)->hash); } diff --git a/indra/alscript/tests/alscriptfixes_test.cpp b/indra/alscript/tests/alscriptfixes_test.cpp index 044fa7c2a1..4ad1108daa 100644 --- a/indra/alscript/tests/alscriptfixes_test.cpp +++ b/indra/alscript/tests/alscriptfixes_test.cpp @@ -105,36 +105,33 @@ namespace // The problem keyed so, its preferred fix, the text with it made, // and that text checked again: the problem gone, and no error in // its place. - std::string fixed(const std::string& script, bool lua, const std::string& key, const std::string& title) + std::string require_fixed(const std::string& script, bool lua, const std::string& key, const std::string& title) { const ALScriptProblems problems = check(script, lua); const ALScriptProblem* problem = keyed(problems, key); - UNSCOPED_INFO("said: " + key + "\n" + said(problems)); + CAPTURE(key); + INFO("said:\n" + said(problems)); REQUIRE(problem != nullptr); - UNSCOPED_INFO("a fix: " + said(problems)); REQUIRE_FALSE(problem->fixes.empty()); const ALScriptFix& fix = problem->fixes.front(); - UNSCOPED_INFO("preferred"); REQUIRE(fix.preferred); - UNSCOPED_INFO("its words"); REQUIRE(fix.title == title); const std::optional made = ALScriptFixes::apply(script, fix); - UNSCOPED_INFO("applies"); REQUIRE(made.has_value()); const ALScriptProblems after = check(*made, lua); - UNSCOPED_INFO("gone from:\n" + *made + "\n" + said(after)); + INFO("made:\n" + *made + "\nsaid then:\n" + said(after)); REQUIRE(keyed(after, key) == nullptr); - UNSCOPED_INFO("no error in its place:\n" + *made + "\n" + said(after)); + UNSCOPED_INFO("no error in its place"); REQUIRE(errors(after) <= errors(problems) - (problem->severity == ALScriptProblem::Severity::Error ? 1 : 0)); return *made; } // Where a piece of the script is, zero-based: its first line and // column, and where it ends on that line. - static void place(const std::string& script, const std::string& piece, S32& line, S32& column, S32& end) + static void require_place(const std::string& script, const std::string& piece, S32& line, S32& column, S32& end) { const size_t at = script.find(piece); - UNSCOPED_INFO("in the script: " + piece); + CAPTURE(piece); REQUIRE(at != std::string::npos); const size_t start = script.rfind('\n', at); line = static_cast(std::count(script.begin(), script.begin() + at, '\n')); @@ -160,7 +157,7 @@ namespace // The text with the refactor titled so made, among those offered at // a place, and that text checked again: no error in it. Where none // is titled so, what was offered. - std::string acted(const std::string& script, bool lua, S32 line, S32 column, S32 endLine, S32 endColumn, const std::string& title) + std::string require_acted(const std::string& script, bool lua, S32 line, S32 column, S32 endLine, S32 endColumn, const std::string& title) { const std::vector offered = actions(script, lua, line, column, endLine, endColumn); for (const ALScriptFix& action : offered) @@ -169,13 +166,12 @@ namespace { continue; } - UNSCOPED_INFO("a refactor"); + CAPTURE(title); REQUIRE(action.kind == ALScriptFix::Kind::Refactor); const std::optional made = ALScriptFixes::apply(script, action); - UNSCOPED_INFO("applies"); REQUIRE(made.has_value()); const ALScriptProblems after = check(*made, lua); - UNSCOPED_INFO("no error in:\n" + *made + "\n" + said(after)); + INFO("made:\n" + *made + "\nsaid then:\n" + said(after)); REQUIRE(errors(after) == 0); return *made; } @@ -184,11 +180,11 @@ namespace // The same with the caret, or the stretch chosen, on a piece of the // script. - std::string actedOn(const std::string& script, bool lua, const std::string& piece, bool chosen, const std::string& title) + std::string require_acted_on(const std::string& script, bool lua, const std::string& piece, bool chosen, const std::string& title) { S32 line = 0, column = 0, end = 0; - place(script, piece, line, column, end); - return acted(script, lua, line, column, line, chosen ? end : column, title); + require_place(script, piece, line, column, end); + return require_acted(script, lua, line, column, line, chosen ? end : column, title); } }; @@ -196,7 +192,7 @@ namespace { UNSCOPED_INFO("builtins: " + error); CHECK(lslLoaded); - const std::string made = fixed("default\n{\n state_entry()\n {\n integer a = 1\n llOwnerSay((string)a);\n }\n}\n", false, + 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 ';'"); UNSCOPED_INFO("after the 1: " + made); REQUIRE(made.find("integer a = 1;\n") != std::string::npos); @@ -206,11 +202,11 @@ namespace { UNSCOPED_INFO("builtins: " + error); CHECK(lslLoaded); - std::string made = fixed("default\n{\n state_entry()\n {\n llownersay(\"hi\");\n }\n}\n", false, "LSLUndeclared", + std::string made = require_fixed("default\n{\n state_entry()\n {\n llownersay(\"hi\");\n }\n}\n", false, "LSLUndeclared", "Change 'llownersay' to 'llOwnerSay'"); UNSCOPED_INFO("the call: " + made); REQUIRE(made.find("llOwnerSay(\"hi\")") != std::string::npos); - made = fixed("default\n{\n state_entry()\n {\n integer count = 1;\n llOwnerSay((string)coutn);\n }\n}\n", false, + made = require_fixed("default\n{\n state_entry()\n {\n integer count = 1;\n llOwnerSay((string)coutn);\n }\n}\n", false, "LSLUndeclared", "Change 'coutn' to 'count'"); UNSCOPED_INFO("the local: " + made); REQUIRE(made.find("(string)count)") != std::string::npos); @@ -229,7 +225,7 @@ namespace { UNSCOPED_INFO("builtins: " + error); CHECK(lslLoaded); - const std::string made = fixed("default\n{\n state_entry()\n {\n llSoundPreload(\"boom\");\n }\n}\n", false, + 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'"); UNSCOPED_INFO("the call: " + made); REQUIRE(made.find("llPreloadSound(\"boom\");") != std::string::npos); @@ -246,8 +242,7 @@ namespace { UNSCOPED_INFO("definitions: " + error); CHECK(luauLoaded); - const std::string made = fixed("local unused = 1\n", true, "LuauLintLocalUnused", "Rename to '_unused'"); - UNSCOPED_INFO("marked"); + const std::string made = require_fixed("local unused = 1\n", true, "LuauLintLocalUnused", "Rename to '_unused'"); CHECK(made == std::string("local _unused = 1\n")); const ALScriptProblems problems = check("local function helper() end\n", true); const ALScriptProblem* problem = keyed(problems, "LuauLintFunctionUnused"); @@ -256,7 +251,6 @@ namespace REQUIRE(problem != nullptr); REQUIRE_FALSE(problem->fixes.empty()); } - UNSCOPED_INFO("safe"); CHECK(problem->fixes.front().safe); } @@ -271,8 +265,7 @@ namespace config.mode = "strict"; luau.setConfig(config); } - const std::string made = fixed("ll.OwnerSya(\"hi\")\n", true, "LuauKeyNotFoundDidYouMean", "Change 'OwnerSya' to 'OwnerSay'"); - UNSCOPED_INFO("the call"); + const std::string made = require_fixed("ll.OwnerSya(\"hi\")\n", true, "LuauKeyNotFoundDidYouMean", "Change 'OwnerSya' to 'OwnerSay'"); CHECK(made == std::string("ll.OwnerSay(\"hi\")\n")); } @@ -293,18 +286,15 @@ namespace REQUIRE(problem != nullptr); UNSCOPED_INFO("a fix: " + said(problems)); REQUIRE_FALSE(problem->fixes.empty()); - UNSCOPED_INFO("not safe"); CHECK_FALSE(problem->fixes.front().safe); const std::optional made = ALScriptFixes::apply("local n = ll.Abs(-1)\nprint(n)\n", problem->fixes.front()); - UNSCOPED_INFO("applies"); REQUIRE(made.has_value()); const ALScriptProblems after = check(*made, true); + INFO("made:\n" + *made + "\nsaid then:\n" + said(after)); for (const ALScriptProblem& each : after) { - UNSCOPED_INFO("no longer deprecated:\n" + *made + "\n" + said(after)); REQUIRE(each.code != "DeprecatedApi"); } - UNSCOPED_INFO("no errors:\n" + *made + "\n" + said(after)); CHECK(errors(after) == static_cast(0)); } @@ -313,7 +303,6 @@ namespace ALScriptFix fix; fix.edits.push_back({ 1, 0, 1, 3, "BAR" }); fix.edits.push_back({ 0, 0, 0, 0, ">" }); - UNSCOPED_INFO("both"); CHECK(ALScriptFixes::apply("foo\nbar\n", fix).value_or("refused") == std::string(">foo\nBAR\n")); fix.edits.push_back({ 1, 2, 1, 3, "x" }); @@ -385,36 +374,33 @@ namespace lint.severity = ALScriptProblem::Severity::Warning; lint.source = ALScriptProblem::Source::Lint; lint.code = "LocalUnused"; - auto made = [&lint](const std::string& line, bool lua) { + auto require_suppression = [&lint](const std::string& line, bool lua) { const std::optional fix = ALScriptFixes::suppression(lint, line, lua); - UNSCOPED_INFO("a fix for: " + line); + CAPTURE(line); REQUIRE(fix.has_value()); - { - INFO("a suppression"); - REQUIRE(fix->kind == ALScriptFix::Kind::Suppress); - REQUIRE_FALSE(fix->preferred); - REQUIRE(fix->safe); - } + REQUIRE(fix->kind == ALScriptFix::Kind::Suppress); + REQUIRE_FALSE(fix->preferred); + REQUIRE(fix->safe); const std::string after = ALScriptFixes::apply(line, *fix).value_or("refused"); UNSCOPED_INFO("suppresses: " + after); REQUIRE(ALScriptFixes::suppressed(lint, after, "", lua)); return after; }; UNSCOPED_INFO("its own"); - CHECK(made("local x = 1", true) == std::string("local x = 1 -- NOLINT(LocalUnused)")); + CHECK(require_suppression("local x = 1", true) == std::string("local x = 1 -- NOLINT(LocalUnused)")); UNSCOPED_INFO("the line's comment"); - CHECK(made("local x = 1 -- why", true) == std::string("local x = 1 -- why NOLINT(LocalUnused)")); + CHECK(require_suppression("local x = 1 -- why", true) == std::string("local x = 1 -- why NOLINT(LocalUnused)")); UNSCOPED_INFO("a NOLINT's list"); - CHECK(made("local x = 1 -- NOLINT(ShadowLocal)", true) == std::string("local x = 1 -- NOLINT(ShadowLocal, LocalUnused)")); + CHECK(require_suppression("local x = 1 -- NOLINT(ShadowLocal)", true) == std::string("local x = 1 -- NOLINT(ShadowLocal, LocalUnused)")); UNSCOPED_INFO("after a block comment"); - CHECK(made("local x = 1 --[[ why ]]", true) == std::string("local x = 1 --[[ why ]] -- NOLINT(LocalUnused)")); + 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(made(" integer x;", false) == std::string(" integer x; // NOLINT(DeclaredButNotUsed)")); + 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]") @@ -432,11 +418,9 @@ namespace } UNSCOPED_INFO("said: " + said(problems)); REQUIRE(problem != nullptr); - const std::string made = fixed("pirnt(\"hi\")\n", true, problem->key, "Change 'pirnt' to 'print'"); - UNSCOPED_INFO("the call"); + const std::string made = require_fixed("pirnt(\"hi\")\n", true, problem->key, "Change 'pirnt' to 'print'"); CHECK(made == std::string("print(\"hi\")\n")); - const std::string local = fixed("local count = 1\nprint(coutn)\n", true, problem->key, "Change 'coutn' to 'count'"); - UNSCOPED_INFO("a local"); + const std::string local = require_fixed("local count = 1\nprint(coutn)\n", true, problem->key, "Change 'coutn' to 'count'"); CHECK(local == std::string("local count = 1\nprint(count)\n")); } @@ -444,23 +428,23 @@ namespace { UNSCOPED_INFO("builtins: " + error); CHECK(lslLoaded); - std::string made = fixed("default\n{\n state_entry()\n {\n llOwnerSay(5 + 1);\n }\n}\n", false, "LSLArgumentWrongType", + std::string made = require_fixed("default\n{\n state_entry()\n {\n llOwnerSay(5 + 1);\n }\n}\n", false, "LSLArgumentWrongType", "Cast to string"); UNSCOPED_INFO("the argument: " + made); REQUIRE(made.find("llOwnerSay((string)(5 + 1));") != std::string::npos); - made = fixed("default\n{\n state_entry()\n {\n llSetText(\"a\", <1, 1, 1>, \"1\");\n }\n}\n", false, "LSLArgumentWrongType", + made = require_fixed("default\n{\n state_entry()\n {\n llSetText(\"a\", <1, 1, 1>, \"1\");\n }\n}\n", false, "LSLArgumentWrongType", "Cast to float"); UNSCOPED_INFO("the third, past a vector's commas: " + made); REQUIRE(made.find("<1, 1, 1>, (float)\"1\")") != std::string::npos); - made = fixed("default\n{\n state_entry()\n {\n string s = 5;\n llOwnerSay(s);\n }\n}\n", false, + made = require_fixed("default\n{\n state_entry()\n {\n string s = 5;\n llOwnerSay(s);\n }\n}\n", false, "LSLWrongTypeInAssignment", "Cast to string"); UNSCOPED_INFO("the declaration: " + made); REQUIRE(made.find("string s = (string)5;") != std::string::npos); - made = fixed("default\n{\n state_entry()\n {\n string s;\n s = 6;\n llOwnerSay(s);\n }\n}\n", false, + made = require_fixed("default\n{\n state_entry()\n {\n string s;\n s = 6;\n llOwnerSay(s);\n }\n}\n", false, "LSLInvalidOperator", "Cast to string"); UNSCOPED_INFO("the assignment: " + made); REQUIRE(made.find("s = (string)6;") != std::string::npos); - made = fixed("string f()\n{\n return 5;\n}\ndefault\n{\n state_entry()\n {\n llOwnerSay(f());\n }\n}\n", false, + made = require_fixed("string f()\n{\n return 5;\n}\ndefault\n{\n state_entry()\n {\n llOwnerSay(f());\n }\n}\n", false, "LSLBadReturnType", "Cast to string"); UNSCOPED_INFO("the return: " + made); REQUIRE(made.find("return (string)5;") != std::string::npos); @@ -480,13 +464,13 @@ namespace 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"; - std::string made = fixed(head + " a == 3;\n" + tail, false, "LSLEqAsStatement", "Assign with '='"); + std::string made = require_fixed(head + " a == 3;\n" + tail, false, "LSLEqAsStatement", "Assign with '='"); UNSCOPED_INFO("assigned: " + made); REQUIRE(made.find(" a = 3;\n") != std::string::npos); - made = fixed(head + " a *= 1.5;\n" + tail, false, "LSLIntFloatMulAssign", "Multiply as a float, then cast to integer"); + made = require_fixed(head + " a *= 1.5;\n" + tail, false, "LSLIntFloatMulAssign", "Multiply as a float, then cast to integer"); UNSCOPED_INFO("rewritten: " + made); REQUIRE(made.find("a = (integer)(a * 1.5);") != std::string::npos); - made = fixed(head + " state default;\n" + tail, false, "LSLChangeToCurrentState", "Write 'return' instead"); + made = require_fixed(head + " state default;\n" + tail, false, "LSLChangeToCurrentState", "Write 'return' instead"); UNSCOPED_INFO("returned: " + made); REQUIRE(made.find(" return;\n") != std::string::npos); @@ -500,18 +484,11 @@ namespace REQUIRE(problem != nullptr); REQUIRE(problem->fixes.size() >= 2); } - { - INFO("neither preferred"); - REQUIRE_FALSE(problem->fixes[0].preferred); - CHECK_FALSE(problem->fixes[1].preferred); - } - UNSCOPED_INFO("compared"); - REQUIRE((ALScriptFixes::apply(script, problem->fixes[0]).value_or("").find("if (a == 1)") != std::string::npos) == true); - { - INFO("bracketed, safely"); - REQUIRE(problem->fixes[1].safe); - REQUIRE(ALScriptFixes::apply(script, problem->fixes[1]).value_or("").find("if ((a = 1))") != std::string::npos); - } + REQUIRE_FALSE(problem->fixes[0].preferred); + CHECK_FALSE(problem->fixes[1].preferred); + REQUIRE(ALScriptFixes::apply(script, problem->fixes[0]).value_or("").find("if (a == 1)") != std::string::npos); + REQUIRE(problem->fixes[1].safe); + REQUIRE(ALScriptFixes::apply(script, problem->fixes[1]).value_or("").find("if ((a = 1))") != std::string::npos); const std::optional quiet = ALScriptFixes::apply(script, problem->fixes[1]); { INFO("and quiet after"); @@ -524,20 +501,20 @@ namespace { UNSCOPED_INFO("definitions: " + error); CHECK(luauLoaded); - std::string made = fixed("--!strict\nlocal n = 5\nlocal s: string = n\nprint(s)\n", true, "LuauTypeMismatch", "Wrap in tostring()"); + 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); REQUIRE(made.find("local s: string = tostring(n)") != std::string::npos); - made = fixed("--!strict\nlocal t = {}\nfunction t:m() return self end\nprint(t.m())\n", true, "LuauRequiresSelf", "Call with ':'"); + made = require_fixed("--!strict\nlocal t = {}\nfunction t:m() return self end\nprint(t.m())\n", true, "LuauRequiresSelf", "Call with ':'"); UNSCOPED_INFO("with a colon: " + made); REQUIRE(made.find("print(t:m())") != std::string::npos); - made = fixed("local function f()\n counter = 1\n return counter\nend\nprint(f())\n", true, "LuauLintGlobalUsedAsLocalFunction", + made = require_fixed("local function f()\n counter = 1\n return counter\nend\nprint(f())\n", true, "LuauLintGlobalUsedAsLocalFunction", "Make 'counter' local"); UNSCOPED_INFO("local: " + made); REQUIRE(made.find(" local counter = 1\n") != std::string::npos); - made = fixed("--!strict\nlocal x\nprint(x)\n", true, "LuauLintUninitializedLocal", "Initialize 'x' with nil"); + made = require_fixed("--!strict\nlocal x\nprint(x)\n", true, "LuauLintUninitializedLocal", "Initialize 'x' with nil"); UNSCOPED_INFO("given nil: " + made); REQUIRE(made.find("local x = nil\n") != std::string::npos); - made = fixed("--!nonstrickt\nprint(1)\n", true, "LuauLintDirectiveUnknownDidYouMean", "Change 'nonstrickt' to 'nonstrict'"); + made = require_fixed("--!nonstrickt\nprint(1)\n", true, "LuauLintDirectiveUnknownDidYouMean", "Change 'nonstrickt' to 'nonstrict'"); UNSCOPED_INFO("spelt: " + made); CHECK(made.compare(0, 12, "--!nonstrict") == 0); } @@ -547,13 +524,13 @@ namespace UNSCOPED_INFO("builtins: " + error); CHECK(lslLoaded); const std::string state = "default\n{\n state_entry()\n {\n llOwnerSay(\"x\");\n }\n}\n"; - std::string made = fixed("integer unused = 5;\n" + state, false, "LSLDeclaredButNotUsed", "Remove 'unused'"); + std::string made = require_fixed("integer unused = 5;\n" + state, false, "LSLDeclaredButNotUsed", "Remove 'unused'"); UNSCOPED_INFO("the global's line gone"); CHECK(made == state); - made = fixed("integer helper(integer x)\n{\n return x;\n}\n" + state, false, "LSLDeclaredButNotUsed", "Remove 'helper'"); + made = require_fixed("integer helper(integer x)\n{\n return x;\n}\n" + state, false, "LSLDeclaredButNotUsed", "Remove 'helper'"); UNSCOPED_INFO("the function's lines gone"); CHECK(made == state); - made = fixed("default\n{\n state_entry()\n {\n integer a = 1 + 2;\n llOwnerSay(\"x\");\n }\n}\n", false, + made = require_fixed("default\n{\n state_entry()\n {\n integer a = 1 + 2;\n llOwnerSay(\"x\");\n }\n}\n", false, "LSLDeclaredButNotUsed", "Remove 'a'"); UNSCOPED_INFO("the local's line gone"); CHECK(made == state); @@ -630,37 +607,37 @@ namespace CHECK(lslLoaded); const std::string head = "default\n{\n state_entry()\n {\n"; const std::string tail = " }\n}\n"; - std::string made = actedOn(head + " llOwnerSay((string)(llGetUnixTime() + 5));\n" + tail, false, "llGetUnixTime() + 5", true, + std::string made = require_acted_on(head + " llOwnerSay((string)(llGetUnixTime() + 5));\n" + tail, false, "llGetUnixTime() + 5", true, "Put it in a local, 'value'"); UNSCOPED_INFO("on its own line"); CHECK(made == head + " integer value = llGetUnixTime() + 5;\n llOwnerSay((string)(value));\n" + tail); // After another statement on the line, just before its own; and // named as nothing in the script is. - made = actedOn(head + " integer value = 1; llOwnerSay((string)(value * 2.5));\n" + tail, false, "value * 2.5", true, + made = require_acted_on(head + " integer value = 1; llOwnerSay((string)(value * 2.5));\n" + tail, false, "value * 2.5", true, "Put it in a local, 'value2'"); UNSCOPED_INFO("beside"); CHECK(made == head + " integer value = 1; float value2 = value * 2.5; llOwnerSay((string)(value2));\n" + tail); - made = actedOn(head + " llSetRot(llEuler2Rot(<0, 0, 1>));\n" + tail, false, "llEuler2Rot(<0, 0, 1>)", true, "Put it in a local, 'value'"); + made = require_acted_on(head + " llSetRot(llEuler2Rot(<0, 0, 1>));\n" + tail, false, "llEuler2Rot(<0, 0, 1>)", true, "Put it in a local, 'value'"); UNSCOPED_INFO("a rotation as LSL says it: " + made); REQUIRE(made.find("rotation value = llEuler2Rot(<0, 0, 1>);") != std::string::npos); // 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: " + actedOn(none, false, "i < 10", true, "Put it in a local, 'value'")); - CHECK(actedOn(none, false, "i < 10", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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); none = head + " integer i;\n if (i) llOwnerSay((string)(i + 1));\n" + tail; UNSCOPED_INFO("under an if"); - CHECK(actedOn(none, false, "i + 1", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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; - place(none, "i = 4", line, column, end); + require_place(none, "i = 4", line, column, end); UNSCOPED_INFO("assigned to"); - CHECK(acted(none, false, line, column, line, column + 1, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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(actedOn(none, false, "llOwnerSay((string)llGetUnixTime())", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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]") @@ -669,28 +646,28 @@ namespace CHECK(lslLoaded); const std::string head = "default\n{\n touch_start(integer n)\n {\n"; const std::string tail = " }\n}\n"; - std::string made = actedOn(head + " if (n == 1)\n {\n llOwnerSay(\"one\");\n }\n else\n {\n" + std::string made = require_acted_on(head + " if (n == 1)\n {\n llOwnerSay(\"one\");\n }\n else\n {\n" " llOwnerSay(\"many\");\n }\n" + tail, false, "if", false, "Invert the if"); UNSCOPED_INFO("braces"); CHECK(made == head + " if (n != 1)\n {\n llOwnerSay(\"many\");\n }\n else\n {\n" " llOwnerSay(\"one\");\n }\n" + tail); - made = actedOn(head + " if (!n) llOwnerSay(\"none\"); else llOwnerSay(\"some\");\n" + tail, false, "if", false, "Invert the if"); + made = require_acted_on(head + " if (!n) llOwnerSay(\"none\"); else llOwnerSay(\"some\");\n" + tail, false, "if", false, "Invert the if"); UNSCOPED_INFO("a not taken off"); CHECK(made == head + " if (n) llOwnerSay(\"some\"); else llOwnerSay(\"none\");\n" + tail); - made = actedOn(head + " if (n > 2) llOwnerSay(\"many\"); else llOwnerSay(\"few\");\n" + tail, false, "if", false, "Invert the if"); + made = require_acted_on(head + " if (n > 2) llOwnerSay(\"many\"); else llOwnerSay(\"few\");\n" + tail, false, "if", false, "Invert the if"); 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(actedOn(chain, false, "if", false, "Invert the if").rfind("offered", 0) == 0); + 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(actedOn(lone, false, "if", false, "Invert the if").rfind("offered", 0) == 0); + 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 = actedOn(head + " if (n) llOwnerSay(\"a\"); else while (n--) if (n == 3) llOwnerSay(\"c\");\n" + tail, false, "if", false, + made = require_acted_on(head + " if (n) llOwnerSay(\"a\"); else while (n--) if (n == 3) llOwnerSay(\"c\");\n" + tail, false, "if", false, "Invert the if"); UNSCOPED_INFO("braced"); CHECK(made == head + " if (!n) { while (n--) if (n == 3) llOwnerSay(\"c\"); } else llOwnerSay(\"a\");\n" + tail); @@ -702,7 +679,7 @@ namespace CHECK(lslLoaded); const std::string script = "default\n{\n state_entry()\n {\n llListen(0, \"\", NULL_KEY, \"\");\n llSetTimerEvent(1.0);\n" " }\n}\n"; - std::string made = actedOn(script, false, "llListen", false, "Add a handler for 'listen'"); + std::string made = require_acted_on(script, false, "llListen", false, "Add a handler for 'listen'"); UNSCOPED_INFO("before the brace, after a blank line"); CHECK(made == "default\n{\n state_entry()\n {\n llListen(0, \"\", NULL_KEY, \"\");\n llSetTimerEvent(1.0);\n }\n\n" @@ -715,14 +692,14 @@ namespace REQUIRE(actions(heard, false, 3, 8, 3, 8).empty()); // The brace where the state's first handler has it. const std::string same = "default\n{\n state_entry() {\n llSensorRepeat(\"\", NULL_KEY, AGENT, 10.0, PI, 5.0);\n }\n}\n"; - made = actedOn(same, false, "llSensorRepeat", false, "Add a handler for 'sensor'"); + made = require_acted_on(same, false, "llSensorRepeat", false, "Add a handler for 'sensor'"); UNSCOPED_INFO("on its line: " + made); REQUIRE(made.find("\n\n sensor(integer num_detected) {\n }\n}\n") != std::string::npos); // Asked for from a function a handler calls, and from one that one // calls: offered all the same. const std::string helper = "arm()\n{\n llSetTimerEvent(1.0);\n}\nstart()\n{\n arm();\n}\n" "default\n{\n state_entry()\n {\n start();\n }\n}\n"; - made = actedOn(helper, false, "start();\n }", false, "Add a handler for 'timer'"); + made = require_acted_on(helper, false, "start();\n }", false, "Add a handler for 'timer'"); UNSCOPED_INFO("through two calls: " + made); REQUIRE(made.find(" timer()\n {\n }\n") != std::string::npos); } @@ -731,61 +708,60 @@ namespace { UNSCOPED_INFO("definitions: " + error); CHECK(luauLoaded); - std::string made = actedOn("local function f(n: number)\n print(n * 2 + 1)\nend\nf(1)\n", true, "n * 2", true, "Put it in a local, 'value'"); + 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"); CHECK(made == "local function f(n: number)\n local value = n * 2\n print(value + 1)\nend\nf(1)\n"); - made = actedOn("local x = 1; print(x + 1)\n", true, "x + 1", true, "Put it in a local, 'value'"); + made = require_acted_on("local x = 1; print(x + 1)\n", true, "x + 1", true, "Put it in a local, 'value'"); 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(actedOn(skipped, true, "tostring(x)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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(actedOn(looped, true, "x < 10", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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(actedOn(elseif, true, "x + 1", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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(actedOn(whole, true, "print(math.abs(-1))", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); - UNSCOPED_INFO("its argument yes: " + actedOn(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'")); - CHECK(actedOn(whole, true, "math.abs(-1)", true, "Put it in a local, 'value'") == "local value = math.abs(-1)\nprint(value)\n"); + 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"); // 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(actedOn(two + "print(g(1))\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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(actedOn(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); + 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(actedOn(two + "local t = { g(1) }\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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(actedOn(two + "local a, b = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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(actedOn("for k, v in pairs({}) do end\n", true, "pairs({})", true, "Put it in a local, 'value'").rfind("offered", 0) == 0); + 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: " + actedOn(two + "print(g(1), 3)\n", true, "g(1)", true, "Put it in a local, 'value'")); - CHECK(actedOn(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: " + actedOn(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'")); - CHECK(actedOn(two + "local a = g(1)\n", true, "g(1)", true, "Put it in a local, 'value'").rfind("offered", 0) != 0); + 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); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a SLua if is inverted; a join interpolated; a local given its type", "[ALScriptFixes]") { UNSCOPED_INFO("definitions: " + error); CHECK(luauLoaded); - std::string made = actedOn("local x = 1\nif x == 1 then\n print(\"one\")\nelse\n print(\"other\")\nend\n", true, "if", false, "Invert the if"); - UNSCOPED_INFO("inverted"); + 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"); - made = actedOn("local x = 1\nif not (x > 1) then\n print(\"few\")\nelse\n print(\"many\")\nend\n", true, "if", false, "Invert the if"); + made = require_acted_on("local x = 1\nif not (x > 1) then\n print(\"few\")\nelse\n print(\"many\")\nend\n", true, "if", false, "Invert the if"); UNSCOPED_INFO("a not taken off"); CHECK(made == "local x = 1\nif x > 1 then\n print(\"many\")\nelse\n print(\"few\")\nend\n"); - made = actedOn("local x = 1\nprint(\"n = \" .. x .. \"!\")\n", true, "..", false, "Write it as an interpolated string"); + made = require_acted_on("local x = 1\nprint(\"n = \" .. x .. \"!\")\n", true, "..", false, "Write it as an interpolated string"); UNSCOPED_INFO("interpolated"); CHECK(made == "local x = 1\nprint(`n = {x}!`)\n"); - made = actedOn("local count = 5\nprint(count)\n", true, "count", false, "Declare it as 'number'"); + made = require_acted_on("local count = 5\nprint(count)\n", true, "count", false, "Declare it as 'number'"); UNSCOPED_INFO("annotated"); CHECK(made == "local count: number = 5\nprint(count)\n"); } @@ -802,17 +778,17 @@ namespace grid.mode = "nonstrict"; luau.setConfig(grid); const std::string script = "ll.Listen(0, \"\", ll.GetOwner(), \"\")\n"; - std::string made = actedOn(script, true, "Listen", false, "Add a handler for 'listen'"); + std::string made = require_acted_on(script, true, "Listen", false, "Add a handler for 'listen'"); UNSCOPED_INFO("at the end"); 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(actedOn(heard, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) == 0); + 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(actedOn(off, true, "Listen", false, "Add a handler for 'listen'").rfind("offered", 0) != 0); + 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]") @@ -832,7 +808,6 @@ namespace const ALPreprocessor::Result result = ALPreprocessor::run(script, options); ALScriptProblem held; const ALSourceMap::Loc carried = result.map.toExpanded(0, 5, 8); - UNSCOPED_INFO("carried"); REQUIRE(carried.found()); held.fixes.push_back(ALScriptFixes::titled("ScriptActionExtract", "Put it in a local, '[1]'", { "value" })); held.fixes.back().edits.push_back({ carried.line, 0, carried.line, 0, " integer value = 1;\n" }); @@ -841,7 +816,6 @@ namespace REQUIRE(held.fixes.size() == static_cast(1)); UNSCOPED_INFO("the source's line"); CHECK(held.fixes.front().edits.front().line == 5); - UNSCOPED_INFO("its start"); CHECK(held.fixes.front().edits.front().column == 0); } @@ -889,7 +863,6 @@ 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")); - UNSCOPED_INFO("its words"); 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) == @@ -897,7 +870,6 @@ 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")); - UNSCOPED_INFO("a field's words"); 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) == @@ -909,11 +881,8 @@ namespace ALScriptFixes::offerRequire(problem, "print(util)\n", "util", false); UNSCOPED_INFO("offered once"); REQUIRE(problem.fixes.size() == static_cast(1)); - { - INFO("neither preferred nor safe"); - CHECK_FALSE(problem.fixes.front().preferred); - CHECK_FALSE(problem.fixes.front().safe); - } + CHECK_FALSE(problem.fixes.front().preferred); + CHECK_FALSE(problem.fixes.front().safe); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: an LSL include exports its macros and top-level functions and globals", "[ALScriptFixes]") @@ -954,7 +923,6 @@ 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")); - UNSCOPED_INFO("its words"); 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") == @@ -976,7 +944,8 @@ namespace { said_so = said_so || (each.key == "LSLUndeclared" && each.args.size() == 1 && each.args[0] == name); } - UNSCOPED_INFO(std::string("undeclared: ") + name + "\n" + said(problems)); + CAPTURE(name); + INFO("said:\n" + said(problems)); REQUIRE(said_so); } } @@ -1046,7 +1015,7 @@ namespace "default\n{\n state_entry()\n {\n llOwnerSay(\"hello there\");\n" " llSetText(\"hello there\", <1.0, 1.0, 1.0>, 1.0);\n }\n" " touch_start(integer n)\n {\n llOwnerSay(\"hello there\");\n llOwnerSay(\"other\");\n }\n}\n"; - const std::string made = actedOn(script, false, "\"hello there\");", false, "Put the string in a global, 'gHelloThere', for its 3 uses"); + const std::string made = require_acted_on(script, false, "\"hello there\");", false, "Put the string in a global, 'gHelloThere', for its 3 uses"); UNSCOPED_INFO("declared after the globals, each use named, the global's own value kept"); CHECK(made == "string gGreeting = \"hello there\";\nstring gHelloThere = \"hello there\";\n" @@ -1055,7 +1024,7 @@ namespace " touch_start(integer n)\n {\n llOwnerSay(gHelloThere);\n llOwnerSay(\"other\");\n }\n}\n"); // A short one costs more as a global than it saves: the list says // so, weighing it. A long one, used as often, is lighter. - UNSCOPED_INFO("heavier on LSO: " + std::to_string(ALScriptWeigh::lso(made).total) + " from " + std::to_string(ALScriptWeigh::lso(script).total)); + UNSCOPED_INFO("heavier on LSO"); CHECK(ALScriptWeigh::lso(made).total >= ALScriptWeigh::lso(script).total); std::string long_script = script; std::string long_made = made; @@ -1066,15 +1035,14 @@ namespace text->replace(at, 11, "hello there, and a sentence of some length said to the owner"); } } - UNSCOPED_INFO("a long one lighter on LSO: " + std::to_string(ALScriptWeigh::lso(long_made).total) + " from " + - std::to_string(ALScriptWeigh::lso(long_script).total)); + 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(actedOn(script, false, "\"other\"", false, "Put the string in a global, 'gOther', for its 1 uses").rfind("offered", 0) == 0); + 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"; - const std::string placed = actedOn(bare, false, "\"hi", false, "Put the string in a global, 'gHi', for its 2 uses"); + const std::string placed = require_acted_on(bare, false, "\"hi", false, "Put the string in a global, 'gHi', for its 2 uses"); UNSCOPED_INFO("ahead of the state, a blank line after: " + placed); CHECK(placed.rfind("string gHi = \"hi\\n\";\n\ndefault\n", 0) == 0); } @@ -1087,18 +1055,18 @@ namespace "default\n{\n state_entry()\n {\n integer n = 5;\n" " list l = [n, \"two\", <3.0, 3.0, 3.0>, -4, n + 3, llGetListLength(gFixed)];\n" " llOwnerSay((string)[1, 2]);\n }\n}\n"; - std::string made = actedOn(script, false, "[n,", false, "Write the list as a sum"); + std::string made = require_acted_on(script, false, "[n,", false, "Write the list as a sum"); UNSCOPED_INFO("each where it would mean what it meant"); CHECK(made == "list gFixed = [1, 2];\n" "default\n{\n state_entry()\n {\n integer n = 5;\n" " list l = (list)n + \"two\" + <3.0, 3.0, 3.0> + (-4) + (n + 3) + llGetListLength(gFixed);\n" " llOwnerSay((string)[1, 2]);\n }\n}\n"); - made = actedOn(script, false, "[1, 2]);", false, "Write the list as a sum"); + 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(actedOn(script, false, "[1, 2];", false, "Write the list as a sum").rfind("offered", 0) == 0); + 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]") { @@ -1109,16 +1077,10 @@ namespace 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); const ALScriptProblem* unused = keyed(lsl, "LSLDeclaredButNotUsed"); - { - INFO("said"); - REQUIRE(unused != nullptr); - REQUIRE_FALSE(unused->fixes.empty()); - } - { - INFO("a removal is safe, and removes"); - REQUIRE(unused->fixes.front().safe); - CHECK(unused->fixes.front().removes); - } + REQUIRE(unused != nullptr); + REQUIRE_FALSE(unused->fixes.empty()); + REQUIRE(unused->fixes.front().safe); + CHECK(unused->fixes.front().removes); const ALScriptProblems lua = check("local function f()\n local unused = 1\nend\nf()\n", true); const ALScriptProblem* local = keyed(lua, "LuauLintLocalUnused"); { @@ -1126,11 +1088,8 @@ namespace REQUIRE(local != nullptr); REQUIRE_FALSE(local->fixes.empty()); } - { - INFO("a rename is safe, and removes nothing"); - REQUIRE(local->fixes.front().safe); - CHECK_FALSE(local->fixes.front().removes); - } + REQUIRE(local->fixes.front().safe); + CHECK_FALSE(local->fixes.front().removes); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: short names guess one edit away; a swap is one edit; ties not preferred", "[ALScriptFixes]") @@ -1172,11 +1131,8 @@ namespace REQUIRE(problem != nullptr); UNSCOPED_INFO("both: " + titles(problem->fixes)); REQUIRE(problem->fixes.size() == size_t(2)); - { - INFO("neither preferred"); - REQUIRE_FALSE(problem->fixes[0].preferred); - CHECK_FALSE(problem->fixes[1].preferred); - } + REQUIRE_FALSE(problem->fixes[0].preferred); + CHECK_FALSE(problem->fixes[1].preferred); problems = check("local cat, car = 1, 2\nprint(cat, car, cax)\n", true); problem = unknown(problems); UNSCOPED_INFO("said: " + said(problems)); @@ -1226,7 +1182,7 @@ namespace const ALScriptEdit put{ 0, 0, 0, 0, "local v = print(x)\n" }; const ALScriptEdit swap{ 0, 0, 0, 8, "v" }; fix.edits = insertion_first ? std::vector{ put, swap } : std::vector{ swap, put }; - UNSCOPED_INFO((insertion_first ? "put in first" : "replaced first")); + CAPTURE(insertion_first); REQUIRE(ALScriptFixes::apply("print(x)\n", fix).value_or("refused") == std::string("local v = print(x)\nv\n")); } @@ -1251,36 +1207,31 @@ namespace problem.column = 0; problem.endColumn = 1; ALScriptFixes::attachOptimizer(problem, "integer x;\n"); - UNSCOPED_INFO("nothing added"); REQUIRE(problem.fixes.size() == size_t(1)); - UNSCOPED_INFO("still preferred"); CHECK(problem.fixes.front().preferred); // Where it can, its own is added unpreferred. problem.line = problem.endLine = 0; problem.column = 8; problem.endColumn = 9; ALScriptFixes::attachOptimizer(problem, "integer x;\n"); - { - INFO("added: " + std::to_string(problem.fixes.size())); - REQUIRE(problem.fixes.size() == 2); - CHECK_FALSE(problem.fixes.back().preferred); - CHECK(problem.fixes.front().preferred); - } + REQUIRE(problem.fixes.size() == 2); + CHECK_FALSE(problem.fixes.back().preferred); + CHECK(problem.fixes.front().preferred); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: LSL's operators and zero indexes in SLua fixed; only ~= is safe", "[ALScriptFixes]") { UNSCOPED_INFO("~="); - CHECK(fixed("local a, b = 1, 2\nif a != b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '!=' to '~='") == + 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(fixed("local a, b = true, false\nif a&&b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '&&' to 'and'") == + 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(fixed("local a, b = true, false\nif a || b then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '||' to '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(fixed("local a = true\nif(!a) then print(1) end\n", true, "LuauUnexpectedDidYouMean", "Change '!' to 'not'") == + 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); @@ -1298,34 +1249,34 @@ namespace } UNSCOPED_INFO("an index from 0"); - CHECK(fixed("local t = {}\ntable.insert(t, 0, 5)\n", true, "LuauLintTableInsertZero", "Change '0' to '1'") == + 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(fixed("local t = {1, 2}\nfor i = 0, #t do print(t[i]) end\n", true, "LuauLintForRangeZero", "Change '0' to '1'") == + 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(fixed("default { state_entry() { integer x = llGetUnixTime(); if (x); llOwnerSay(\"a\"); } }", false, "LSLEmptyIf", + 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(fixed("default { touch_start(string n) { llOwnerSay((string)n); } }", false, "LSLArgumentWrongTypeEvent", + 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(fixed("default { touch_start() { } }", false, "LSLTooFewArgumentsEvent", "Write the parameters 'touch_start' takes") == + 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(fixed("default { touch_start(integer n, integer m) { } }", false, "LSLTooManyArgumentsEvent", "Write the parameters 'touch_start' takes") == + 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(fixed("default { touch_start(n) { } }", false, "LSLParameterUntyped", "Write the parameters 'touch_start' takes") == + 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(fixed("default { state_entry() { if (llGetUnixTime()) integer i = 1; } }", false, "LSLDeclarationInvalidHere", + 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; } } }")); @@ -1362,10 +1313,9 @@ namespace 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"; - const std::string made = fixed(script, false, "LSLSyntaxMissing", "Insert '}'"); + const std::string made = require_fixed(script, false, "LSLSyntaxMissing", "Insert '}'"); UNSCOPED_INFO("closed after the call, its own line: " + made); REQUIRE(made.find("\"Hello, Avatar!\");\n }\n\n touch_start(") != std::string::npos); - UNSCOPED_INFO("no error left"); CHECK(errors(check(made, false)) == 0); } @@ -1373,18 +1323,14 @@ namespace { UNSCOPED_INFO("definitions: " + error); CHECK(luauLoaded); - const std::string made = fixed("local x = 1\nx = x + 2 * x\nprint(x)\n", true, "LuauLintSlCompoundAssign", "Write it x += ..."); - UNSCOPED_INFO("the text"); + 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")); const ALScriptProblems problems = check("local x = 1\nx = x + 2 * x\nprint(x)\n", true); const ALScriptProblem* problem = keyed(problems, "LuauLintSlCompoundAssign"); - { - INFO("safe"); - REQUIRE(problem); - CHECK(problem->fixes.front().safe); - } + REQUIRE(problem); + CHECK(problem->fixes.front().safe); UNSCOPED_INFO("a field, joined"); - CHECK(fixed("local t = { n = \"a\" }\nt.n = t.n ..\"b\" .. \"c\"\nprint(t.n)\n", true, "LuauLintSlCompoundAssign", + 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")); } @@ -1397,8 +1343,7 @@ namespace " list l = [1, 2]; integer length = 3; integer i;\n" " for (i = 0; i < llGetListLength(l); ++i) llOwnerSay((string)(i + length));\n" "} }\n"; - const std::string made = fixed(script, false, "LSLSlLoopInvariantCall", "Work out llGetListLength once, before the loop"); - UNSCOPED_INFO("the text"); + const std::string made = require_fixed(script, false, "LSLSlLoopInvariantCall", "Work out llGetListLength once, before the loop"); CHECK(made == std::string("default { state_entry() {\n" " list l = [1, 2]; integer length = 3; integer i;\n" @@ -1407,11 +1352,8 @@ namespace "} }\n")); const ALScriptProblems problems = check(script, false); const ALScriptProblem* problem = keyed(problems, "LSLSlLoopInvariantCall"); - { - INFO("not safe"); - REQUIRE(problem); - CHECK_FALSE(problem->fixes.front().safe); - } + REQUIRE(problem); + CHECK_FALSE(problem->fixes.front().safe); // Not where the loop is all its if has: a declaration may not be. const ALScriptProblems in_if = check("default { state_entry() {\n" " list l; integer i;\n" @@ -1419,11 +1361,8 @@ namespace "} }\n", false); const ALScriptProblem* alone = keyed(in_if, "LSLSlLoopInvariantCall"); - { - INFO("said, with no fix"); - REQUIRE(alone); - REQUIRE(alone->fixes.empty()); - } + REQUIRE(alone); + REQUIRE(alone->fixes.empty()); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlNumberTruth's fixes: n ~= 0 bracketed where needed and not n as n == 0", "[ALScriptFixes]") @@ -1431,22 +1370,19 @@ namespace UNSCOPED_INFO("definitions: " + error); CHECK(luauLoaded); UNSCOPED_INFO("compared"); - CHECK(fixed("local n = 0\nif n then print(n) end\n", true, "LuauLintSlNumberTruth", "Compare it: n ~= 0") == + 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(fixed("local c = true\nif (if c then 1 else 2) then print(c) end\n", true, "LuauLintSlNumberTruth", + 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(fixed("local n = 0\nprint(not n)\n", true, "LuauLintSlNumberTruthNot", "Ask whether it is 0: n == 0") == + 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"); - { - INFO("not safe"); - REQUIRE(problem); - CHECK_FALSE(problem->fixes.front().safe); - } + REQUIRE(problem); + CHECK_FALSE(problem->fixes.front().safe); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: SlNilSentinel's fixes: nil for SLua's finds and -1 for llcompat's; safe", "[ALScriptFixes]") @@ -1454,32 +1390,29 @@ namespace UNSCOPED_INFO("definitions: " + error); CHECK(luauLoaded); UNSCOPED_INFO("never"); - CHECK(fixed("local l = {1}\nif ll.ListFindList(l, {2}) == -1 then print(l) end\n", true, "LuauLintSlNilSentinel", + 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(fixed("local i = table.find({1}, 1)\nprint(-1 ~= i)\n", true, "LuauLintSlNilSentinelAlways", "Write it i ~= nil") == + 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(fixed("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint((i) < 0)\n", true, "LuauLintSlNilSentinelOrder", + 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(fixed("local i = ll.SubStringIndex(\"ab\", \"b\")\nprint(0 <= i)\n", true, + 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(fixed("print(llcompat.ListFindList({1}, {1}) ~= nil)\n", true, "LuauLintSlNilSentinelAlways", + 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"); - { - INFO("safe"); - REQUIRE(problem); - REQUIRE_FALSE(problem->fixes.empty()); - CHECK(problem->fixes.front().safe); - } + REQUIRE(problem); + REQUIRE_FALSE(problem->fixes.empty()); + CHECK(problem->fixes.front().safe); } TEST_CASE_METHOD(alscriptfixes_data, "ALScriptFixes: a deprecated ll function is renamed only where the arguments match", "[ALScriptFixes]") @@ -1487,7 +1420,7 @@ namespace UNSCOPED_INFO("definitions: " + error); CHECK(luauLoaded); UNSCOPED_INFO("the same arguments"); - CHECK(fixed("ll.SetLinkPrimitiveParams(LINK_THIS, {})\n", true, "LuauLintDeprecatedMemberUse", + 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" }) @@ -1499,7 +1432,7 @@ namespace { for (const ALScriptFix& fix : problem.fixes) { - UNSCOPED_INFO("no replacement by name: " + std::string(call) + fix.title); + CAPTURE(call, fix.title); REQUIRE(fix.key != "ScriptFixUseInstead"); } } @@ -1531,7 +1464,7 @@ namespace CHECK(luauLoaded); // The fixture's statics named by its class: MSVC finds none of them // from a lambda, even one holding `this`. - const auto removal = [this](const std::string& script) -> const ALScriptFix* { + const auto require_removal = [this](const std::string& script) -> const ALScriptFix* { static ALScriptProblems problems; problems = check(script, true); const ALScriptProblem* problem = alscriptfixes_data::keyed(problems, "LuauLintImportUnused"); @@ -1552,28 +1485,23 @@ namespace return nullptr; }; const std::string script = "local util = require(\"util\")\nprint(1)\n"; - const ALScriptFix* fix = removal(script); - UNSCOPED_INFO("offered"); + const ALScriptFix* fix = require_removal(script); REQUIRE(fix != nullptr); - UNSCOPED_INFO("its words"); CHECK(fix->title == std::string("Remove the require of \"util\"")); - { - INFO("neither preferred nor safe"); - REQUIRE_FALSE(fix->preferred); - CHECK_FALSE(fix->safe); - } + REQUIRE_FALSE(fix->preferred); + CHECK_FALSE(fix->safe); UNSCOPED_INFO("its line gone"); CHECK(ALScriptFixes::apply(script, *fix).value_or("(not made)") == std::string("print(1)\n")); const std::string bare = "local util = require 'util'; print(1)\n"; - fix = removal(bare); + fix = require_removal(bare); UNSCOPED_INFO("without brackets, and its ;"); REQUIRE(fix != nullptr); 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(removal("local name = 'util'\nlocal util = require(name)\n") == nullptr); + 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]") @@ -1582,7 +1510,7 @@ namespace CHECK(luauLoaded); // The fixture's statics named by its class: MSVC finds none of them // from a lambda, even one holding `this`. - const auto deprecated = [this](const std::string& script) -> const ALScriptProblem* { + const auto require_deprecated = [this](const std::string& script) -> const ALScriptProblem* { static ALScriptProblems problems; problems = check(script, true); for (const ALScriptProblem& problem : problems) @@ -1592,54 +1520,50 @@ namespace return &problem; } } - UNSCOPED_INFO("said: " + alscriptfixes_data::said(problems)); - REQUIRE(false); + FAIL("no deprecation said:\n" + alscriptfixes_data::said(problems)); return nullptr; }; - const auto written = [&](const std::string& script) { - const ALScriptProblem* problem = deprecated(script); - UNSCOPED_INFO("a fix: " + script); + const auto require_written = [&](const std::string& script) { + CAPTURE(script); + const ALScriptProblem* problem = require_deprecated(script); REQUIRE_FALSE(problem->fixes.empty()); const ALScriptFix& fix = problem->fixes.front(); - { - INFO("preferred, not safe: " + fix.title); - REQUIRE(fix.preferred); - REQUIRE_FALSE(fix.safe); - } + CAPTURE(fix.title); + REQUIRE(fix.preferred); + REQUIRE_FALSE(fix.safe); const std::string made = ALScriptFixes::apply(script, fix).value_or("(not made)"); const ALScriptProblems after = check(made, true); + INFO("made:\n" + made + "\nsaid then:\n" + alscriptfixes_data::said(after)); for (const ALScriptProblem& each : after) { - UNSCOPED_INFO("no longer deprecated:\n" + made); REQUIRE(each.key.rfind("LuauLintDeprecated", 0) != 0); } - UNSCOPED_INFO("no errors:\n" + made + "\n" + alscriptfixes_data::said(after)); REQUIRE(alscriptfixes_data::errors(after) == static_cast(0)); 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(written(vectors + "print(ll.VecDist(a, ll.GetPos()))\n") == + 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(written(vectors + "print(ll.VecDist(a + b, b))\n") == + 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(written("local n = 2\nprint(ll.Round(n * 0.25))\n") == + 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(written("ll.SetPrimitiveParams({PRIM_SIZE, vector(1, 1, 1)})\n") == + 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(written("local t = {1, 2}\nprint(table.getn(t))\n") == + 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(written("print(ll.Abs(-1))\n") == std::string("Use 'math.abs' instead of 'll.Abs'\nprint(math.abs(-1))\n")); + 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 = deprecated("print(ll.VecNorm(vector(0, 0, 0)))\n"); + const ALScriptProblem* norm = require_deprecated("print(ll.VecNorm(vector(0, 0, 0)))\n"); { - INFO(("normalize offered: " + alscriptfixes_data::said({ *norm }))); + INFO("normalize offered: " + alscriptfixes_data::said({ *norm })); REQUIRE_FALSE(norm->fixes.empty()); CHECK(norm->fixes.front().title == "Use 'vector.normalize' instead of 'll.VecNorm'"); } @@ -1649,8 +1573,9 @@ namespace "local a = vector(1, 0, 0)\nprint(ll.VecDist(a,\n a))\n", "local f = ll.VecDist\nprint(f)\n", "print(ll.GetListEntryType({1}, 0))\n" }) { - const ALScriptProblem* problem = deprecated(script); - UNSCOPED_INFO(("no fix: " + std::string(script) + alscriptfixes_data::said({ *problem }))); + CAPTURE(script); + const ALScriptProblem* problem = require_deprecated(script); + INFO(alscriptfixes_data::said({ *problem })); REQUIRE(problem->fixes.empty()); } diff --git a/indra/alscript/tests/alscriptformatter_test.cpp b/indra/alscript/tests/alscriptformatter_test.cpp index a8527f361a..9b2153db23 100644 --- a/indra/alscript/tests/alscriptformatter_test.cpp +++ b/indra/alscript/tests/alscriptformatter_test.cpp @@ -82,34 +82,26 @@ namespace " }\n" " }\n" "}\n"; - UNSCOPED_INFO("lsl structure"); CHECK(lsl(in) == want); } // LSL spacing: operators, unary signs, vectors, casts, comments. TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: LSL spacing: operators; unary signs; vectors; casts; comments", "[alscriptformatter]") { - UNSCOPED_INFO("binary"); CHECK(lsl("x=a+b*c;\n") == std::string("x = a + b * c;\n")); - UNSCOPED_INFO("unary"); CHECK(lsl("x = -a * -b;\n") == std::string("x = -a * -b;\n")); - UNSCOPED_INFO("not"); CHECK(lsl("if(!x)y=~z;\n") == std::string("if (!x) y = ~z;\n")); - UNSCOPED_INFO("vector"); CHECK(lsl("v=<1,-2,3>*<0,0,1>;\n") == std::string("v = <1, -2, 3> * <0, 0, 1>;\n")); - UNSCOPED_INFO("comparison"); CHECK(lsl("if(ad)x++;\n") == std::string("if (a < b && c > d) x++;\n")); UNSCOPED_INFO("cast kept"); CHECK(lsl("x=(integer)y;\n") == std::string("x = (integer)y;\n")); UNSCOPED_INFO("cast spaced kept"); CHECK(lsl("x=(integer) y;\n") == std::string("x = (integer) y;\n")); - UNSCOPED_INFO("for"); CHECK(lsl("for(i=0;i = {}\n") == std::string("local x: Array = {}\n")); UNSCOPED_INFO("method kept"); CHECK(lua("obj:method(1)\n") == std::string("obj:method(1)\n")); - UNSCOPED_INFO("not"); CHECK(lua("if not(x)then end\n") == std::string("if not (x) then end\n")); UNSCOPED_INFO("long string kept"); CHECK(lua("s = [[\n a\n]]\nx=1\n") == std::string("s = [[\n a\n]]\nx = 1\n")); @@ -284,11 +274,9 @@ namespace } TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: a comment-only line is indented as a statement there would be", "[alscriptformatter]") { - UNSCOPED_INFO("LSL"); CHECK(lsl("// first\ndefault\n{\n// a note\nstate_entry()\n{\n/* over\n lines */\nif (x)\n// why\nllSay(0, \"a\");\n}\n}\n") == std::string("// first\ndefault\n{\n // a note\n state_entry()\n {\n /* over\n lines */\n if (x)\n" " // why\n llSay(0, \"a\");\n }\n}\n")); - UNSCOPED_INFO("Luau"); CHECK(lua("-- first\nlocal function f()\n-- a note\nreturn 1\nend\n--[[ over\n lines ]]\n") == std::string("-- first\nlocal function f()\n -- a note\n return 1\nend\n--[[ over\n lines ]]\n")); // Some lines asked for, as the studio asks for all of a script's: @@ -403,7 +391,6 @@ namespace ALScriptFormatter::Options tabs = options; tabs.tabs = true; const std::string call = "llSay(0, \"" + std::string(92 - 13, 'x') + "\");"; - UNSCOPED_INFO("a call of 92"); REQUIRE(call.size() == size_t(92)); const std::string tabbed = "default\n{\n state_entry()\n {\n " + call + "\n }\n}\n"; UNSCOPED_INFO("fits"); @@ -449,12 +436,10 @@ namespace TEST_CASE_METHOD(alscriptformatter_data, "alscriptformatter: a sign hugs the bracket after it and is spaced from a < or minus before it", "[alscriptformatter]") { - UNSCOPED_INFO("Luau"); CHECK(lua("local x = - (a)\nlocal y = # (t)\nlocal z = # {1}\nif i <#t and i <-1 and i > - (2) then end\n" "local w = not (a)\nlocal q = a - (b)\nreturn -(x)\nlocal d = #{1} -#{2} - -#t + -#t\n") == std::string("local x = -(a)\nlocal y = #(t)\nlocal z = #{ 1 }\nif i < #t and i < -1 and i > -(2) then end\n" "local w = not (a)\nlocal q = a - (b)\nreturn -(x)\nlocal d = #{ 1 } - #{ 2 } - -#t + -#t\n")); - UNSCOPED_INFO("LSL"); CHECK(lsl("default { state_entry() { integer x = - (a); vector v = < -1, 0, 0 >; if (i <-1) x = ! (b); } }\n") == std::string("default { state_entry() { integer x = -(a); vector v = <-1, 0, 0>; if (i < -1) x = !(b); } }\n")); UNSCOPED_INFO("a list of vectors"); @@ -513,7 +498,6 @@ namespace CHECK(lua(repeat) == repeat); UNSCOPED_INFO("flat"); CHECK(lua("if a then\nx()\n-- note\nend\n") == std::string("if a then\n x()\n -- note\nend\n")); - UNSCOPED_INFO("LSL"); CHECK(lsl("default\n{\n touch_start(integer n)\n {\n x();\n // about the end\n }\n}\n") == std::string("default\n{\n touch_start(integer n)\n {\n x();\n // about the end\n }\n}\n")); } diff --git a/indra/alscript/tests/alscriptjoblane_test.cpp b/indra/alscript/tests/alscriptjoblane_test.cpp index 0a2f3162c9..b3b4b4cc56 100644 --- a/indra/alscript/tests/alscriptjoblane_test.cpp +++ b/indra/alscript/tests/alscriptjoblane_test.cpp @@ -95,8 +95,8 @@ namespace lane.put(queued("check a2", "a", true), take); { INFO("the waiting one taken"); - REQUIRE(jobs[0]->taken); - REQUIRE_FALSE(jobs[0]->stopped); + CHECK(jobs[0]->taken); + CHECK_FALSE(jobs[0]->stopped); CHECK_FALSE(jobs[1]->taken); } UNSCOPED_INFO("and let go of"); @@ -112,8 +112,8 @@ namespace { INFO("the running one told to stop"); REQUIRE(jobs.back()->name == "check x2"); - REQUIRE(jobs[jobs.size() - 2]->taken); - REQUIRE(jobs[jobs.size() - 2]->stopped); + CHECK(jobs[jobs.size() - 2]->taken); + CHECK(jobs[jobs.size() - 2]->stopped); } UNSCOPED_INFO("and the later one ran after"); CHECK(ran.back() == "check x2"); diff --git a/indra/alscript/tests/alscriptlexicon_test.cpp b/indra/alscript/tests/alscriptlexicon_test.cpp index 0098b6e1ba..68459e2cf6 100644 --- a/indra/alscript/tests/alscriptlexicon_test.cpp +++ b/indra/alscript/tests/alscriptlexicon_test.cpp @@ -36,47 +36,32 @@ namespace using namespace ALScriptLexicon; for (const char* word : { "default", "state", "jump", "return", "if", "else", "for", "do", "while" }) { - { - INFO(std::string("control: ") + word); - REQUIRE(lslWord(word) == LSL_CONTROL); - REQUIRE(isLslKeyword(word)); - } + CAPTURE(word); + REQUIRE(lslWord(word) == LSL_CONTROL); + REQUIRE(isLslKeyword(word)); } for (const char* word : { "integer", "float", "string", "key", "vector", "rotation", "quaternion", "list" }) { - { - INFO(std::string("type: ") + word); - REQUIRE(lslWord(word) == LSL_TYPE); - REQUIRE(isLslType(word)); - REQUIRE(isLslKeyword(word)); - } - } - { - INFO("event and print are reserved"); - CHECK(lslWord("event") == LSL_RESERVED); - CHECK(lslWord("print") == LSL_RESERVED); - CHECK(isLslKeyword("print")); - } - { - INFO("TRUE and FALSE are constants, not keywords"); - CHECK(lslWord("TRUE") == LSL_CONSTANT); - CHECK(lslWord("FALSE") == LSL_CONSTANT); - CHECK_FALSE(isLslKeyword("TRUE")); - } + CAPTURE(word); + REQUIRE(lslWord(word) == LSL_TYPE); + REQUIRE(isLslType(word)); + REQUIRE(isLslKeyword(word)); + } + CHECK(lslWord("event") == LSL_RESERVED); + CHECK(lslWord("print") == LSL_RESERVED); + CHECK(isLslKeyword("print")); + CHECK(lslWord("TRUE") == LSL_CONSTANT); + CHECK(lslWord("FALSE") == LSL_CONSTANT); + CHECK_FALSE(isLslKeyword("TRUE")); for (const char* word : { "inline", "const", "break", "continue", "switch", "case" }) { - { - INFO(std::string("extension, not a keyword: ") + word); - REQUIRE(lslWord(word) == LSL_EXTENSION); - REQUIRE_FALSE(isLslKeyword(word)); - } - } - { - INFO("a name is none of them"); - CHECK(lslWord("count") == LSL_NAME); - CHECK(lslWord("llSay") == LSL_NAME); - CHECK(lslWord("") == LSL_NAME); + CAPTURE(word); + REQUIRE(lslWord(word) == LSL_EXTENSION); + REQUIRE_FALSE(isLslKeyword(word)); } + CHECK(lslWord("count") == LSL_NAME); + CHECK(lslWord("llSay") == LSL_NAME); + CHECK(lslWord("") == LSL_NAME); { INFO("case matters"); CHECK(lslWord("Integer") == LSL_NAME); @@ -90,22 +75,17 @@ namespace for (const char* word : { "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "continue", "export", "const" }) { - { - INFO(std::string("keyword: ") + word); - REQUIRE(isLuauKeyword(word)); - REQUIRE(isKeyword(true, word)); - } + CAPTURE(word); + REQUIRE(isLuauKeyword(word)); + REQUIRE(isKeyword(true, word)); } { INFO("type and typeof are called"); CHECK_FALSE(isLuauKeyword("type")); CHECK_FALSE(isLuauKeyword("typeof")); } - { - INFO("a name is not"); - CHECK_FALSE(isLuauKeyword("print")); - CHECK_FALSE(isLuauKeyword("ll")); - } + CHECK_FALSE(isLuauKeyword("print")); + CHECK_FALSE(isLuauKeyword("ll")); { INFO("the language decides"); CHECK(isKeyword(false, "state")); @@ -118,36 +98,24 @@ namespace TEST_CASE("alscriptlexicon: a name is ASCII letters and digits and underscores; no digit first", "[alscriptlexicon]") { using namespace ALScriptLexicon; - { - INFO("letters and an underscore"); - CHECK(isName("_count2")); - CHECK(isName("x")); - CHECK(isName("llSay")); - } - UNSCOPED_INFO("not a digit first"); + CHECK(isName("_count2")); + CHECK(isName("x")); + CHECK(isName("llSay")); CHECK_FALSE(isName("2x")); - { - INFO("not empty, nor with other bytes"); - CHECK_FALSE(isName("")); - CHECK_FALSE(isName("a-b")); - CHECK_FALSE(isName("a.b")); - CHECK_FALSE(isName("caf\xc3\xa9")); - } - { - INFO("the bytes"); - CHECK(isNameStart('_')); - CHECK_FALSE(isNameStart('7')); - CHECK(isNameByte('7')); - CHECK_FALSE(isNameByte('$')); - } + CHECK_FALSE(isName("")); + CHECK_FALSE(isName("a-b")); + CHECK_FALSE(isName("a.b")); + CHECK_FALSE(isName("caf\xc3\xa9")); + CHECK(isNameStart('_')); + CHECK_FALSE(isNameStart('7')); + CHECK(isNameByte('7')); + CHECK_FALSE(isNameByte('$')); } TEST_CASE("alscriptlexicon: a long bracket's level and where its closer of that level ends", "[alscriptlexicon]") { using namespace ALScriptLexicon; - UNSCOPED_INFO("[["); CHECK(longBracketLevel("x = [[a]]", 4) == 0); - UNSCOPED_INFO("[==["); CHECK(longBracketLevel("[==[a]==]", 0) == 2); UNSCOPED_INFO("not one"); CHECK(longBracketLevel("[=a", 0) == -1); @@ -157,10 +125,8 @@ namespace CHECK(longBracketLevel("[[", 5) == -1); UNSCOPED_INFO("the closer of its level, not another's"); CHECK(longBracketClose("a]=]b]]c", 0, 0) == size_t(7)); - UNSCOPED_INFO("level two"); CHECK(longBracketClose("a]]]=]]==]x", 0, 2) == size_t(10)); - UNSCOPED_INFO("none"); - REQUIRE(longBracketClose("a]=]", 0, 0) == std::string_view::npos); + CHECK(longBracketClose("a]=]", 0, 0) == std::string_view::npos); } TEST_CASE("alscriptlexicon: LSL: strings run past escapes and breaks; comments to the break or closer", "[alscriptlexicon]") @@ -168,11 +134,8 @@ namespace using namespace ALScriptLexicon; const std::string_view text = "a \"b\\\"c\nd\" // e\n/* f */ g /* h"; Stretch s = stretchAt(text, 0, false); - { - INFO("code, one byte"); - CHECK(s.kind == Kind::Code); - CHECK(s.end == 1); - } + CHECK(s.kind == Kind::Code); + CHECK(s.end == 1); s = stretchAt(text, 2, false); { INFO("the string, across the break"); @@ -194,26 +157,22 @@ namespace } const size_t block = text.find("/*"); s = stretchAt(text, block, false); - { - INFO("the block comment"); - CHECK(s.kind == Kind::Comment); - CHECK(s.closed); - CHECK(s.close == 2); - CHECK(text.substr(block, s.end - block) == "/* f */"); - } + CHECK(s.kind == Kind::Comment); + CHECK(s.closed); + CHECK(s.close == 2); + CHECK(text.substr(block, s.end - block) == "/* f */"); s = stretchAt(text, text.rfind("/*"), false); { INFO("a block comment the text runs out in"); CHECK(s.kind == Kind::Comment); CHECK_FALSE(s.closed); - REQUIRE(s.end == text.size()); + CHECK(s.end == text.size()); CHECK(s.close == 0); } UNSCOPED_INFO("no char constants"); CHECK(stretchAt("'x'", 0, false).kind == Kind::Code); UNSCOPED_INFO("no -- comment"); CHECK(stretchAt("--x", 0, false).kind == Kind::Code); - UNSCOPED_INFO("a string not closed"); CHECK_FALSE(stretchAt("\"abc", 0, false).closed); } @@ -223,12 +182,10 @@ namespace for (const char* quoted : { "\"a\\\"b\"", "'a\\'b'", "`a{b}c`" }) { const Stretch s = stretchAt(quoted, 0, true); - { - INFO(std::string("closed: ") + quoted); - REQUIRE(s.kind == Kind::String); - REQUIRE(s.closed); - REQUIRE(s.end == std::string_view(quoted).size()); - } + CAPTURE(quoted); + REQUIRE(s.kind == Kind::String); + REQUIRE(s.closed); + REQUIRE(s.end == std::string_view(quoted).size()); } Stretch s = stretchAt("'abc\nd'", 0, true); { @@ -244,31 +201,22 @@ namespace CHECK(s.end == 6); } s = stretchAt("[==[a]]b]==]c", 0, true); - { - INFO("a long string"); - CHECK(s.kind == Kind::String); - CHECK(s.closed); - CHECK(s.open == 4); - CHECK(s.close == 4); - CHECK(s.end == 12); - } + CHECK(s.kind == Kind::String); + CHECK(s.closed); + CHECK(s.open == 4); + CHECK(s.close == 4); + CHECK(s.end == 12); s = stretchAt("--[[a\nb]] c", 0, true); - { - INFO("a long comment"); - CHECK(s.kind == Kind::Comment); - CHECK(s.closed); - CHECK(s.open == 4); - CHECK(s.close == 2); - CHECK(s.end == 9); - } + CHECK(s.kind == Kind::Comment); + CHECK(s.closed); + CHECK(s.open == 4); + CHECK(s.close == 2); + CHECK(s.end == 9); s = stretchAt("-- a\nb", 0, true); - { - INFO("a line comment"); - CHECK(s.kind == Kind::Comment); - CHECK(s.closed); - CHECK(s.close == 0); - CHECK(s.end == 4); - } + CHECK(s.kind == Kind::Comment); + CHECK(s.closed); + CHECK(s.close == 0); + CHECK(s.end == 4); s = stretchAt("--[[ a", 0, true); { INFO("a long comment the text runs out in"); diff --git a/indra/alscript/tests/alscriptlintpass_test.cpp b/indra/alscript/tests/alscriptlintpass_test.cpp index 795fb705bc..3fd5938ac1 100644 --- a/indra/alscript/tests/alscriptlintpass_test.cpp +++ b/indra/alscript/tests/alscriptlintpass_test.cpp @@ -114,40 +114,36 @@ namespace // The problem keyed so, its preferred fix -- titled so, and safe or // not as said -- the text with it made, and that text checked again: // the problem gone, and no error in its place. - std::string fixed(const std::string& script, const std::string& key, const std::string& title, bool safe, bool lua = true) + std::string require_fixed(const std::string& script, const std::string& key, const std::string& title, bool safe, bool lua = true) { const ALScriptProblems problems = check(script, lua); const ALScriptProblem* problem = keyed(problems, key); - UNSCOPED_INFO("said: " + key + "\n" + said(problems)); + CAPTURE(key); + INFO("said:\n" + said(problems)); REQUIRE(problem != nullptr); - UNSCOPED_INFO("a fix: " + said(problems)); REQUIRE_FALSE(problem->fixes.empty()); const ALScriptFix& fix = problem->fixes.front(); - UNSCOPED_INFO("preferred"); REQUIRE(fix.preferred); - UNSCOPED_INFO("its words"); REQUIRE(fix.title == title); - UNSCOPED_INFO("safe or not"); REQUIRE(fix.safe == safe); const std::optional made = ALScriptFixes::apply(script, fix); - UNSCOPED_INFO("applies"); REQUIRE(made.has_value()); const ALScriptProblems after = check(*made, lua); - UNSCOPED_INFO("gone from:\n" + *made + "\n" + said(after)); + INFO("made:\n" + *made + "\nsaid then:\n" + said(after)); REQUIRE(keyed(after, key) == nullptr); - UNSCOPED_INFO("no error in its place:\n" + *made + "\n" + said(after)); + UNSCOPED_INFO("no error in its place"); REQUIRE(errors(after) <= errors(problems) - (problem->severity == ALScriptProblem::Severity::Error ? 1 : 0)); return *made; } // The problem keyed so is said, and offers no fix. - void unfixed(const std::string& script, const std::string& key, bool lua = true) + void require_unfixed(const std::string& script, const std::string& key, bool lua = true) { const ALScriptProblems problems = check(script, lua); const ALScriptProblem* problem = keyed(problems, key); - UNSCOPED_INFO("said: " + key + "\n" + said(problems)); + CAPTURE(key); + INFO("said:\n" + said(problems)); REQUIRE(problem != nullptr); - UNSCOPED_INFO("no fix: " + said(problems)); REQUIRE(problem->fixes.empty()); } }; @@ -169,7 +165,6 @@ namespace "local function f(l) return l ~= {} end\n" "if t == nil or t == d then print(f(t)) end\n", "SlTableCompare", ALScriptProblem::Severity::Error); - UNSCOPED_INFO("each"); CHECK(said == std::string("1 LuauLintSlTableCompare|t|#t == 0\n" "2 LuauLintSlTableCompareAlways|t|#t > 0\n" @@ -178,12 +173,12 @@ namespace "7 LuauLintSlTableCompareItems|s\n" "8 LuauLintSlTableCompareAlways|l|next(l) ~= nil\n")); UNSCOPED_INFO("a list"); - CHECK(fixed("local t = {1}\nif t == {} then print(t) end\n", "LuauLintSlTableCompare", "Write it #t == 0", false) == + 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(fixed("local d = {a = 1}\nprint({} ~= (d))\n", "LuauLintSlTableCompareAlways", "Write it next(d) ~= nil", false) == + 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")); - unfixed("local t = {1}\nprint(t == {1})\n", "LuauLintSlTableCompareItems"); + 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]") @@ -204,7 +199,6 @@ namespace "h[0] = 2\n" "print(d[0], h[0], llcompat.SubStringIndex(s, \"a\") == 0)\n", "SlZeroIndex", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("1 LuauLintSlZeroIndex|t\n" "2 LuauLintSlZeroIndexLoop|#t|t[i]|i\n" @@ -218,27 +212,27 @@ namespace "8 LuauLintSlZeroIndexFound|ll.SubStringIndex(...)|ll.SubStringIndex\n" "8 LuauLintSlZeroIndexFound|table.find(...)|table.find\n")); UNSCOPED_INFO("a list"); - CHECK(fixed("local t = {1}\nprint(t[0])\n", "LuauLintSlZeroIndex", "Write it t[1]", false) == + 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(fixed("local t = {1}\nfor i = 0, #t - 1 do print(t[i]) end\n", "LuauLintSlZeroIndexLoop", "Write it for i = 1, #t", false) == + 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(fixed("local s = \"abc\"\nprint(s:sub(0, #s - 1))\n", "LuauLintSlZeroIndexSub", "Write it s:sub(1, #s)", false) == + 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(fixed("print(string.sub(\"abc\", 0, 2))\n", "LuauLintSlZeroIndexSub", "Write it string.sub(\"abc\", 1, 3)", false) == + 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(fixed("print(ll.GetSubString(\"abc\", 0, -1))\n", "LuauLintSlZeroIndexArg", + 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(fixed("print(ll.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlZeroIndexFound", "Write it ll.SubStringIndex(...) == 1", false) == + 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")); - unfixed("local t = {1}\nfor i = 0, #t - 1 do print(i, t[i]) end\n", "LuauLintSlZeroIndexLoop"); - unfixed("local s = \"abc\"\nlocal n = 2\nprint(s:sub(0, n))\n", "LuauLintSlZeroIndexSub"); - unfixed("local s = \"abc\"\nprint(ll.GetSubString(s, 0, #s))\n", "LuauLintSlZeroIndexArg"); + 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"); } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlCompatCall: llcompat's where ll's means the same; not where it differs", "[ALScriptLintPass]") @@ -259,7 +253,6 @@ namespace "print(n, g, llcompat.ListFindList({1}, {1}) == -1)\n" "print(llcompat.List2Json(JSON_ARRAY, {s}))\n", "SlCompatCall", ALScriptProblem::Severity::Note); - UNSCOPED_INFO("each"); CHECK(said == std::string("1 LuauLintSlCompatCall|llcompat.Say|ll.Say(...)\n" "2 LuauLintSlCompatCall|llcompat.GetSubString|ll.GetSubString(s, 1, -1)\n" @@ -270,22 +263,22 @@ namespace "8 LuauLintSlCompatCall|llcompat.SameGroup|ll.SameGroup(...)\n" "12 LuauLintSlCompatCall|llcompat.List2Json|ll.List2Json(...)\n")); UNSCOPED_INFO("the same"); - CHECK(fixed("llcompat.Say(0, \"hi\")\n", "LuauLintSlCompatCall", "Write it ll.Say(...)", true) == + 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(fixed("print(llcompat.GetSubString(\"abc\", 0, 1))\n", "LuauLintSlCompatCall", "Write it ll.GetSubString(\"abc\", 1, 2)", true) == + 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(fixed("print(0 == llcompat.SameGroup(ll.GetOwner()))\n", "LuauLintSlCompatCall", "Write it not ll.SameGroup(...)", true) == + 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(fixed("print(llcompat.SameGroup(ll.GetOwner()) ~= 0)\n", "LuauLintSlCompatCall", "Write it ll.SameGroup(...)", true) == + 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(fixed("print(llcompat.SubStringIndex(\"ab\", \"a\") == 0)\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) == 1", true) == + 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(fixed("print(-1 < llcompat.SubStringIndex(\"ab\", \"a\"))\n", "LuauLintSlCompatCall", "Write it ll.SubStringIndex(...) ~= nil", true) == + 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")); } @@ -300,7 +293,6 @@ namespace "if (if on then 1 else 0) == 1 or (if n > 2 then 0 else 1) ~= 0 then print(3) end\n" "if n == 1 or (if on then 2 else 0) == 1 then print(4) end\n", "SlBooleanNumber", ALScriptProblem::Severity::Error); - UNSCOPED_INFO("each"); CHECK(said == std::string("2 LuauLintSlBooleanNumber|on|1\n" "2 LuauLintSlBooleanNumberAlways|on|0\n" @@ -309,15 +301,15 @@ namespace "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(fixed("local on = false\nprint(on == 1)\n", "LuauLintSlBooleanNumber", "Write it on", false) == + 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(fixed("print(0 == ll.SameGroup(ll.GetOwner()))\n", "LuauLintSlBooleanNumber", "Write it not ll.SameGroup(ll.GetOwner())", false) == + 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(fixed("local n = 1\nprint((if n > 2 then 0 else 1) ~= 0)\n", "LuauLintSlBooleanNumberChoice", "Write it not (n > 2)", false) == + 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")); - unfixed("local n = 1\nprint((n > 2) == 5)\n", "LuauLintSlBooleanNumber"); + 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]") @@ -338,7 +330,6 @@ namespace "print(bump, early, outer, shared, total, x, y)\n" "count = 1\n"; const std::string said = found(script, "SlGlobalAssign", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("0 LuauLintSlGlobalAssign|count (another severity)\n" "1 LuauLintSlGlobalInFunction|total (another severity)\n" @@ -362,21 +353,21 @@ namespace 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(fixed("count = 0\nprint(count)\n", "LuauLintSlGlobalAssign", "Write it local count", false) == + 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(fixed("local function early() return later end\nlater = 5\nprint(early())\n", "LuauLintSlGlobalAssign", + 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(fixed("function f() return 1 end\nprint(f())\n", "LuauLintSlGlobalFunction", "Write it local function f", false) == + 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(fixed("local function outer()\n function inner() return 1 end\n return inner()\nend\nprint(outer())\n", + 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")); - unfixed("local function outer()\n function shared() return 2 end\nend\nprint(outer, shared)\n", "LuauLintSlGlobalFunctionInScope"); - unfixed("print(_G)\ny = 2\n", "LuauLintSlGlobalAssign"); + 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]") @@ -391,7 +382,6 @@ namespace "if (x) and (x) then print(3) end\n" "if x then print(4) end\n", "SlParenCondition", ALScriptProblem::Severity::Note); - UNSCOPED_INFO("each"); CHECK(said == std::string("1 LuauLintSlParenCondition|if\n" "1 LuauLintSlParenCondition|elseif\n" @@ -399,10 +389,10 @@ namespace "3 LuauLintSlParenCondition|until\n" "4 LuauLintSlParenCondition|if\n")); UNSCOPED_INFO("spaced"); - CHECK(fixed("local x = true\nif (x) then print(1) end\n", "LuauLintSlParenCondition", "Take out the brackets", true) == + 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(fixed("local x = true\nwhile(x)do break end\n", "LuauLintSlParenCondition", "Take out the brackets", true) == + 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", @@ -424,12 +414,11 @@ namespace "print(a)\n" "b = a\n", "SlAlmostSwapped", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); 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(fixed("local a, b = 1, 2\na = b\nb = a\nprint(a, b)\n", "LuauLintSlAlmostSwapped", "Write it a, b = b, a", false) == + 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")); } @@ -450,7 +439,6 @@ namespace "math.random()\n" "print(ll.DeleteSubList(l, 1, 1))\n", "SlMustUse", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("2 LuauLintSlMustUse|ll.DeleteSubList\n" "3 LuauLintSlMustUse|string.upper\n" @@ -459,13 +447,13 @@ namespace "6 LuauLintSlMustUse|tostring\n" "7 LuauLintSlMustUse|llcompat.GetPos\n")); UNSCOPED_INFO("a list"); - CHECK(fixed("local l = {1, 2}\nll.DeleteSubList(l, 1, 1)\nprint(l)\n", "LuauLintSlMustUse", "Write it l = ll.DeleteSubList(...)", false) == + 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(fixed("local s = \"abc\"\ns:upper()\nprint(s)\n", "LuauLintSlMustUse", "Write it s = s:upper(...)", false) == + 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")); - unfixed("local s = \"abc\"\nstring.len(s)\n", "LuauLintSlMustUse"); - unfixed("string.upper(\"abc\")\n", "LuauLintSlMustUse"); + 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]") @@ -480,16 +468,15 @@ namespace "for k, v in walk(t) do print(k, v) end\n" "for k, v in t do print(k, v) end\n", "SlGeneralizedFor", ALScriptProblem::Severity::Note); - UNSCOPED_INFO("each"); CHECK(said == std::string("1 LuauLintSlGeneralizedFor|t|pairs(t)\n" "2 LuauLintSlGeneralizedForList|t|ipairs(t)\n" "3 LuauLintSlGeneralizedFor|t|next, t\n")); UNSCOPED_INFO("pairs"); - CHECK(fixed("local t = {1}\nfor k, v in pairs(t) do print(k, v) end\n", "LuauLintSlGeneralizedFor", "Write it in t", false) == + 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(fixed("local t = {1}\nfor k, v in next, t do print(k, v) end\n", "LuauLintSlGeneralizedFor", "Write it in t", false) == + 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")); } @@ -510,7 +497,6 @@ namespace "end\n" "while c do break end\n", "SlEmptyBlock", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("1 LuauLintSlEmptyBlock|if\n" "2 LuauLintSlEmptyBlock|if\n" @@ -521,13 +507,13 @@ namespace "7 LuauLintSlEmptyBlock|for\n" "8 LuauLintSlEmptyBlock|repeat\n")); UNSCOPED_INFO("turned round"); - CHECK(fixed("local c = true\nif c then else print(1) end\n", "LuauLintSlEmptyBlock", "Write it if not c then", false) == + 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(fixed("local c = true\nif c then print(3) else end\n", "LuauLintSlEmptyBlock", "Take out the empty else", true) == + 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(fixed("local c = true\nif c then\n print(3)\nelse\nend\n", "LuauLintSlEmptyBlock", "Take out the empty else", true) == + 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", @@ -546,14 +532,13 @@ namespace "local d = {a = 1}\n" "print(d[n / 2], string.rep(\"x\", n / 2))\n", "SlIndexDivision", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("2 LuauLintSlIndexDivision|n / 2|n // 2\n" "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(fixed("local t = {1}\nlocal n = #t\nprint(t[n / 2])\n", "LuauLintSlIndexDivision", "Write it n // 2", false) == + 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")); } @@ -569,7 +554,6 @@ namespace "local s = a * b\n" "print(a * 2, s, c)\n", "SlVectorProduct", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("2 LuauLintSlVectorProduct|a * b|a|b\n" "3 LuauLintSlVectorProduct|a * b|a|b\n" @@ -579,10 +563,10 @@ namespace 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(fixed("local a = vector(1, 0, 0)\nprint(a * a > 0.5)\n", "LuauLintSlVectorProduct", "Write it vector.dot(a, a)", false) == + 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(fixed("local a = vector(1, 0, 0)\nlocal c = a % a\nprint(c)\n", "LuauLintSlVectorCross", "Write it vector.cross(a, a)", false) == + 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")); } @@ -599,7 +583,6 @@ namespace "ll.SetLinkPrimitiveParamsFast(LINK_THIS, {})\n" "llcompat.SetPos(v)\n", "SlSleepingCall", ALScriptProblem::Severity::Note); - UNSCOPED_INFO("each"); CHECK(said == std::string("1 LuauLintSlSleepingCall|ll.SetPos|0.2|ll.SetLinkPrimitiveParamsFast\n" "2 LuauLintSlSleepingCallOften|ll.SetRot|0.2|ll.SetLinkPrimitiveParamsFast (another severity)\n" @@ -608,19 +591,19 @@ namespace "5 LuauLintSlSleepingCall|ll.SetLinkRenderMaterial|0.2|ll.SetLinkPrimitiveParamsFast\n" "7 LuauLintSlSleepingCall|llcompat.SetPos|0.2|llcompat.SetLinkPrimitiveParamsFast\n")); UNSCOPED_INFO("put in"); - CHECK(fixed("local v = vector(1, 2, 3)\nll.SetPos(v)\n", "LuauLintSlSleepingCall", + 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(fixed("LLTimers:every(1, function() ll.SetPrimitiveParams({}) end)\n", "LuauLintSlSleepingCallOften", + 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(fixed("ll.SetLinkRenderMaterial(LINK_THIS, \"m\", 0)\n", "LuauLintSlSleepingCall", + 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")); - unfixed("ll.SetTexture(\"x\", 0)\n", "LuauLintSlSleepingTexture"); - unfixed("ll.SetRenderMaterial(tostring(1), 0)\n", "LuauLintSlSleepingCall"); + 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]") @@ -640,7 +623,6 @@ namespace " }\n" "}\n", "SlSleepingCall", ALScriptProblem::Severity::Note, false); - UNSCOPED_INFO("each"); CHECK(said == std::string("2 LSLSlSleepingCall|llSetPos|0.2|llSetLinkPrimitiveParamsFast\n" "4 LSLSlSleepingCallOften|llSetRot|0.2|llSetLinkPrimitiveParamsFast (another severity)\n" @@ -648,14 +630,14 @@ namespace "6 LSLSlSleepingTexture|llSetTexture|0.2|PRIM_TEXTURE\n" "9 LSLSlSleepingCallOften|llSetPrimitiveParams|0.2|llSetLinkPrimitiveParamsFast (another severity)\n")); UNSCOPED_INFO("put in"); - CHECK(fixed("default { state_entry() { llSetPos( <1, 2, 3> /* here */ ); } }\n", "LSLSlSleepingCall", + 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(fixed("default { state_entry() { llSetLinkRenderMaterial(LINK_THIS, \"m\", ALL_SIDES); } }\n", "LSLSlSleepingCall", + 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")); - unfixed("default { state_entry() { llSetRenderMaterial(llGetInventoryName(INVENTORY_MATERIAL, 0), 0); } }\n", "LSLSlSleepingCall", false); + 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]") @@ -671,16 +653,15 @@ namespace "ll.SetPrimitiveParams({PRIM_GLOW, ALL_SIDES, 0})\n" "ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0})\n", "SlMergeablePrimParams", ALScriptProblem::Severity::Note); - UNSCOPED_INFO("each"); CHECK(said == std::string("0 LuauLintSlMergeablePrimParams|3|ll.SetLinkPrimitiveParamsFast\n")); UNSCOPED_INFO("a link changed"); - CHECK(fixed("ll.SetLinkPrimitiveParamsFast(LINK_THIS, {PRIM_GLOW, ALL_SIDES, 0.5})\nll.SetLinkPrimitiveParamsFast(2, {PRIM_GLOW, ALL_SIDES, 0})\n", + 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(fixed("ll.SetLinkPrimitiveParamsFast(1, {PRIM_LINK_TARGET, 2, PRIM_GLOW, ALL_SIDES, 1})\n" + 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, " @@ -702,10 +683,9 @@ namespace " }\n" "}\n", "SlMergeablePrimParams", ALScriptProblem::Severity::Note, false); - UNSCOPED_INFO("each"); CHECK(said == std::string("2 LSLSlMergeablePrimParams|2|llSetLinkPrimitiveParamsFast\n")); UNSCOPED_INFO("a link changed"); - CHECK(fixed("default { state_entry() {\n llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_GLOW, ALL_SIDES, 0.5]);\n" + 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])", @@ -713,7 +693,7 @@ namespace 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(fixed("default { state_entry() { llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.5]); llSetPrimitiveParams([PRIM_COLOR, " + 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) == @@ -787,17 +767,17 @@ namespace "5 LuauLintSlStringBuild|g\n" "7 LuauLintSlStringBuild|u\n")); UNSCOPED_INFO("in a table"); - CHECK(fixed("local s = \"\"\nfor i = 1, 3 do s ..= tostring(i) end\nprint(s)\n", "LuauLintSlStringBuild", + 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(fixed("local function f()\n local s = \"\"\n local sParts = 1\n for i = 1, 3 do\n s = s .. i\n end\n" + 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")); - unfixed("local t = \"\"\nfor i = 1, 3 do t = t .. i; print(t) end\n", "LuauLintSlStringBuild"); - 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", + 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"); UNSCOPED_INFO("LSL"); CHECK(found("default {\n" @@ -822,7 +802,6 @@ namespace { UNSCOPED_INFO("definitions: " + error); CHECK(loaded); - UNSCOPED_INFO("each"); CHECK(found("local function report()\n" " print(ll.GetOwner())\n" " if ll.GetOwner() == ll.GetKey() then print(ll.GetOwner()) end\n" @@ -836,7 +815,7 @@ namespace "2 LuauLintSlRepeatedCall|ll.GetKey()|2\n" "6 LuauLintSlRepeatedCall|ll.GetScriptName()|2\n")); UNSCOPED_INFO("kept"); - CHECK(fixed("local function report()\n print(ll.GetOwner())\n print(ll.GetOwner())\nend\nprint(report)\n", + 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")); } @@ -845,7 +824,6 @@ namespace { UNSCOPED_INFO("builtins: " + error); CHECK(lslLoaded); - UNSCOPED_INFO("each"); CHECK(found("default {\n" " touch_start(integer n) {\n" " string s = llDetectedName(0);\n" @@ -859,11 +837,11 @@ namespace std::string("3 LSLSlRepeatedCall|llGetOwner()|2\n" "4 LSLSlRepeatedCall|llToUpper(s)|2\n")); UNSCOPED_INFO("a steady one"); - CHECK(fixed("default { touch_start(integer n) {\n llOwnerSay((string)llGetOwner());\n llOwnerSay((string)llGetOwner());\n} }\n", + 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(fixed("default { state_entry() {\n string s = \"a\";\n llOwnerSay(llToUpper(s) + llToUpper(s));\n} }\n", + 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")); } @@ -917,12 +895,11 @@ namespace "local n = 1\n" "while n < 3 do n = n + 1 end\n", "SlForIndexAssign", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("5 LuauLintSlForIndexAssign|i\n" "7 LuauLintSlForIndexAssign|i\n" "8 LuauLintSlForIndexAssign|i\n")); - unfixed("for i = 1, 3 do i = 2 end\n", "LuauLintSlForIndexAssign"); + require_unfixed("for i = 1, 3 do i = 2 end\n", "LuauLintSlForIndexAssign"); } TEST_CASE_METHOD(alscriptlintpass_data, "ALScriptLintPass: SlGlobalAssign: a global set in a function and named elsewhere goes local", "[ALScriptLintPass]") @@ -941,7 +918,7 @@ namespace CHECK(found(shared, "SlGlobalAssign", ALScriptProblem::Severity::Note) == std::string("1 LuauLintSlGlobalInFunction|count\n")); UNSCOPED_INFO("declared before the first that names it"); - CHECK(fixed(shared, "LuauLintSlGlobalInFunction", "Declare local count before it is first named", false) == + 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) @@ -1012,7 +989,6 @@ namespace "local b = true\n" "if t or b or ll.GetOwner() then print(t) end\n", "SlNumberTruth", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("6 LuauLintSlUuidTruth|id|id.istruthy\n" "7 LuauLintSlVectorTruth|v|v ~= ZERO_VECTOR\n" @@ -1028,29 +1004,29 @@ namespace "11 LuauLintSlIntegerTruthNot|i|i == 0i\n" "19 LuauLintSlUuidTruth|ll.GetOwner()|ll.GetOwner().istruthy\n")); UNSCOPED_INFO("a uuid"); - CHECK(fixed("local id = ll.GetOwner()\nif id then print(id) end\n", "LuauLintSlUuidTruth", "Write it id.istruthy", false) == + 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(fixed("if not ll.GetOwner() then print(1) end\n", "LuauLintSlUuidTruthNot", "Write it not ll.GetOwner().istruthy", false) == + 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(fixed("local v = ll.GetPos()\nwhile v do break end\n", "LuauLintSlVectorTruth", "Write it v ~= ZERO_VECTOR", false) == + 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(fixed("local r = ll.GetRot()\nprint(not r)\n", "LuauLintSlQuaternionTruthNot", "Write it r == ZERO_ROTATION", false) == + 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(fixed("local c = true\nif (if c then \"a\" else \"\") then print(c) end\n", + 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(fixed("local l = {1}\nif l then print(l) end\n", "LuauLintSlListTruth", "Write it #l > 0", false) == + 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(fixed("local l = {1}\nprint(not l)\n", "LuauLintSlListTruthNot", "Write it #l == 0", false) == + 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(fixed("local i = 5i\nif i then print(i) end\n", "LuauLintSlIntegerTruth", "Write it i ~= 0i", false) == + 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", @@ -1077,7 +1053,6 @@ namespace "buffer.fill(b, 0, 0)\n" "print(integer.add(i, 1), buffer.readu8(b, 0), vector(1, 2, 3))\n", "SlMustUse", ALScriptProblem::Severity::Warning); - UNSCOPED_INFO("each"); CHECK(said == std::string("2 LuauLintSlMustUse|integer.add\n" "3 LuauLintSlMustUse|uuid\n" @@ -1090,8 +1065,8 @@ namespace "10 LuauLintSlMustUse|buffer.create\n" "11 LuauLintSlMustUse|table.create\n")); UNSCOPED_INFO("an integer's"); - CHECK(fixed("local i = 5i\ninteger.band(i, 3i)\nprint(i)\n", "LuauLintSlMustUse", "Write it i = integer.band(...)", false) == + 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")); - unfixed("vector(1, 2, 3)\n", "LuauLintSlMustUse"); + require_unfixed("vector(1, 2, 3)\n", "LuauLintSlMustUse"); } } diff --git a/indra/alscript/tests/alscriptmessages_test.cpp b/indra/alscript/tests/alscriptmessages_test.cpp index ea3936b87d..355b321273 100644 --- a/indra/alscript/tests/alscriptmessages_test.cpp +++ b/indra/alscript/tests/alscriptmessages_test.cpp @@ -35,16 +35,11 @@ namespace const ALScriptMessages::Place place = ALScriptMessages::readDiagnostic("(12, 4) : ERROR : Syntax error", false); UNSCOPED_INFO("the line as it came"); CHECK(place.line == 12); - UNSCOPED_INFO("the column too"); CHECK(place.column == 4); - UNSCOPED_INFO("a column was given"); CHECK(place.hasColumn); - UNSCOPED_INFO("the level"); CHECK(place.level == std::string("ERROR")); - UNSCOPED_INFO("the words"); CHECK(place.message == std::string("Syntax error")); const ALScriptMessages::Place warned = ALScriptMessages::readDiagnostic("(3, 0) : WARNING : Unused variable x", false); - UNSCOPED_INFO("a warning says so"); CHECK(warned.level == std::string("WARNING")); // Anything else is an error with no place: "Math Error" comes so. const ALScriptMessages::Place bare = ALScriptMessages::readDiagnostic("Math Error", false); @@ -67,14 +62,11 @@ namespace const ALScriptMessages::Place place = ALScriptMessages::readDiagnostic("script:14: unexpected symbol near 'end'", true); UNSCOPED_INFO("one off the line"); CHECK(place.line == 13); - UNSCOPED_INFO("no column"); CHECK_FALSE(place.hasColumn); - UNSCOPED_INFO("an error"); CHECK(place.level == std::string("ERROR")); UNSCOPED_INFO("the words past the colon"); CHECK(place.message == std::string("unexpected symbol near 'end'")); // Line one stays line zero rather than going negative. - UNSCOPED_INFO("the first line"); CHECK(ALScriptMessages::readDiagnostic("x:1: bad", true).line == 0); // A line of neither form is no place: the whole is the words. const ALScriptMessages::Place other = ALScriptMessages::readDiagnostic("Math Error", true); @@ -93,7 +85,6 @@ namespace errors.append("(1, 2) : ERROR : first"); errors.append("(3, 4) : WARNING : second"); const std::vector places = ALScriptMessages::readDiagnostics(errors, false); - UNSCOPED_INFO("both"); REQUIRE(places.size() == size_t(2)); UNSCOPED_INFO("in order"); CHECK(places[1].message == std::string("second")); @@ -116,9 +107,7 @@ namespace TEST_CASE("alscriptmessages: a run-time error's header names the object and script and marks what follows", "[alscriptmessages]") { ALScriptMessages::Header named; - UNSCOPED_INFO("a header"); REQUIRE(ALScriptMessages::readRuntimeHeader("Object [script:My Script] Script run-time error", named)); - UNSCOPED_INFO("the object"); CHECK(named.object == std::string("Object")); UNSCOPED_INFO("the script, spaces and all"); CHECK(named.script == std::string("My Script")); @@ -143,14 +132,12 @@ namespace CHECK(where.line == 6); UNSCOPED_INFO("no column"); CHECK(where.column == -1); - UNSCOPED_INFO("the words"); CHECK(where.message == std::string("attempt to index nil")); const std::vector lsl = { "Object [script:s] Script run-time error", "(5, 2) : ERROR : Stack-Heap Collision" }; UNSCOPED_INFO("LSL's"); CHECK(ALScriptMessages::readRuntimeLocation(lsl, false, where)); UNSCOPED_INFO("the line as it came"); CHECK(where.line == 5); - UNSCOPED_INFO("and the column"); CHECK(where.column == 2); UNSCOPED_INFO("the words past the level"); CHECK(where.message == std::string("Stack-Heap Collision")); @@ -169,7 +156,6 @@ namespace ALScriptMessages::Frame frame; UNSCOPED_INFO("a bare frame"); REQUIRE(ALScriptMessages::readStackFrame("s:7", frame)); - UNSCOPED_INFO("its chunk"); CHECK(frame.chunk == std::string("s")); UNSCOPED_INFO("its line, one off"); CHECK(frame.line == 6); @@ -177,13 +163,11 @@ namespace CHECK(ALScriptMessages::readStackFrame(" My Script:12 function counter", frame)); UNSCOPED_INFO("a script's name, spaces and all"); CHECK(frame.chunk == std::string("My Script")); - UNSCOPED_INFO("that line"); CHECK(frame.line == 11); UNSCOPED_INFO("as Lua quotes a chunk"); CHECK(ALScriptMessages::readStackFrame("[string \"My Script\"]:30: in function 'tick'", frame)); UNSCOPED_INFO("the quoted name, spaces and all"); CHECK(frame.chunk == std::string("My Script")); - UNSCOPED_INFO("and its line"); CHECK(frame.line == 29); UNSCOPED_INFO("the heading is no frame"); CHECK_FALSE(ALScriptMessages::readStackFrame("stack traceback:", frame)); @@ -211,16 +195,11 @@ namespace "... (+4 frames)", "lua_script:30" }; ALScriptMessages::Header named; - UNSCOPED_INFO("the header"); REQUIRE(ALScriptMessages::readRuntimeHeader(said[0], named)); - UNSCOPED_INFO("names the script"); CHECK(named.script == std::string("Counter")); ALScriptMessages::Location where; - UNSCOPED_INFO("where"); REQUIRE(ALScriptMessages::readRuntimeLocation(said, true, where)); - UNSCOPED_INFO("its line"); CHECK(where.line == 11); - UNSCOPED_INFO("its words"); CHECK(where.message == std::string("attempt to index nil with 'field'")); std::vector frames; for (size_t i = 1; i < said.size(); ++i) @@ -246,12 +225,12 @@ namespace for (const char* goes_on : { "lua_script:12: attempt to index nil with 'field'", "stack traceback:", "[C] function error", "lua_script:12 function tick", " lua_script:30", "... (+4 frames)", "(12, 3) : ERROR : Stack-Heap Collision" }) { - UNSCOPED_INFO(std::string("goes on: ") + goes_on); + CAPTURE(goes_on); REQUIRE(ALScriptMessages::continuesRuntimeError(goes_on)); } for (const char* besides : { "hello", "Counter: 12 visitors", "", " ", "Object [script:Counter] Script run-time error" }) { - UNSCOPED_INFO(std::string("its own: ") + besides); + CAPTURE(besides); REQUIRE_FALSE(ALScriptMessages::continuesRuntimeError(besides)); } } @@ -282,6 +261,7 @@ namespace const std::string said = "Line 320: WARN: Declaration of `activeRequest' in this scope shadows previous declaration at (345, 1)"; for (const bool lua : { false, true }) { + CAPTURE(lua); const ALScriptMessages::Place place = ALScriptMessages::readDiagnostic(said, lua); { INFO("a line named"); @@ -290,7 +270,6 @@ namespace } UNSCOPED_INFO("one off the line"); REQUIRE(place.line == 319); - UNSCOPED_INFO("its level"); REQUIRE(place.level == std::string("WARN")); UNSCOPED_INFO("the words past the level"); REQUIRE(place.message == std::string("Declaration of `activeRequest' in this scope shadows previous declaration at (345, 1)")); diff --git a/indra/alscript/tests/alscriptnameindex_test.cpp b/indra/alscript/tests/alscriptnameindex_test.cpp index 9dc7d20fcc..7298a1aba7 100644 --- a/indra/alscript/tests/alscriptnameindex_test.cpp +++ b/indra/alscript/tests/alscriptnameindex_test.cpp @@ -109,7 +109,6 @@ namespace index.changed(id(3), std::nullopt); std::map seen; index.forEach([&seen](const LLUUID& item, const std::string& name) { UNSCOPED_INFO("once each"); REQUIRE(seen.emplace(item, name).second); }); - UNSCOPED_INFO("what is listed"); REQUIRE(seen.size() == size_t(2)); { INFO("by its name now"); diff --git a/indra/alscript/tests/alscriptoutlinepairs_test.cpp b/indra/alscript/tests/alscriptoutlinepairs_test.cpp index f89add0759..c58402b3a7 100644 --- a/indra/alscript/tests/alscriptoutlinepairs_test.cpp +++ b/indra/alscript/tests/alscriptoutlinepairs_test.cpp @@ -58,7 +58,6 @@ namespace const std::vector right = { symbol("count", K::Variable, 0, 0), symbol("b", K::Function, 1, 5), symbol("made", K::Function, 6, 7), symbol("a", K::Function, 8, 12) }; const std::vector pairs = ALScriptOutlinePairs::pair(left, right); - UNSCOPED_INFO("two"); REQUIRE(pairs.size() == size_t(2)); UNSCOPED_INFO("a with a, in the left's order"); CHECK(pairs[0] == Pair{ 1, 4, 8, 12 }); @@ -80,7 +79,6 @@ namespace symbol("inner", K::Function, 16, 18, 1), symbol("touch_start", K::Event, 20, 21), }; const std::vector pairs = ALScriptOutlinePairs::pair(left, right); - UNSCOPED_INFO("six"); REQUIRE(pairs.size() == size_t(6)); UNSCOPED_INFO("default with default"); CHECK(pairs[0] == Pair{ 0, 9, 5, 12 }); diff --git a/indra/alscript/tests/alscriptsnapshot_test.cpp b/indra/alscript/tests/alscriptsnapshot_test.cpp index 035f15ac54..9c3995c715 100644 --- a/indra/alscript/tests/alscriptsnapshot_test.cpp +++ b/indra/alscript/tests/alscriptsnapshot_test.cpp @@ -54,7 +54,7 @@ namespace const ALPreprocessor::Result made = snapshot.run( "#include \"lib.lsl\"\n#include \"gone.lsl\"\n#include \"new.lsl\"\n#include \"new.lsl\"\ndefault { state_entry() { helper(); } }\n"); UNSCOPED_INFO("the answered one in"); - REQUIRE(made.text.find("integer helper()") != std::string::npos); + CHECK(made.text.find("integer helper()") != std::string::npos); UNSCOPED_INFO("the missing one said"); CHECK(std::any_of(made.problems.begin(), made.problems.end(), [](const ALScriptProblem& p) { return p.key == "PreprocIncludeNotFound"; })); const std::vector missed = snapshot.missed(); @@ -64,7 +64,7 @@ namespace CHECK(missed[0].name == "new.lsl"); } UNSCOPED_INFO("taken away by the asking"); - REQUIRE(snapshot.missed().empty()); + CHECK(snapshot.missed().empty()); } TEST_CASE_METHOD(alscriptsnapshot_data, "alscriptsnapshot: keyOf is the name and who asks and whether it is a require", "[alscriptsnapshot]") diff --git a/indra/alscript/tests/alscriptstack_test.cpp b/indra/alscript/tests/alscriptstack_test.cpp index ea56f77974..7d57bef1c7 100644 --- a/indra/alscript/tests/alscriptstack_test.cpp +++ b/indra/alscript/tests/alscriptstack_test.cpp @@ -63,7 +63,6 @@ namespace const std::thread::id asking = std::this_thread::get_id(); std::thread::id ran; alScriptOnLargeStack([&]() { ran = std::this_thread::get_id(); }); - UNSCOPED_INFO("on the same thread"); CHECK(ran == asking); bool caught = false; @@ -111,7 +110,7 @@ namespace { INFO("expanded, within the bound"); CHECK_FALSE(result.overran); - REQUIRE(result.text.find("1;") != std::string::npos); + CHECK(result.text.find("1;") != std::string::npos); } } diff --git a/indra/alscript/tests/alscripttempfiles_test.cpp b/indra/alscript/tests/alscripttempfiles_test.cpp index b16871f137..d7b9090ad7 100644 --- a/indra/alscript/tests/alscripttempfiles_test.cpp +++ b/indra/alscript/tests/alscripttempfiles_test.cpp @@ -69,7 +69,6 @@ namespace TEST_CASE_METHOD(alscripttempfiles_data, "alscripttempfiles: a copy's name is the cleaned script name and id and language's extension", "[alscripttempfiles]") { - UNSCOPED_INFO("LSL"); CHECK(ALScriptTempFiles::nameFor("/tmp", "My Script", "abc", false) == std::string("/tmp/sl_script_My Script_abc.lsl")); UNSCOPED_INFO("SLua, the name cleaned"); CHECK(ALScriptTempFiles::nameFor("/tmp/", "a/b:c?", "abc", true) == @@ -103,7 +102,6 @@ namespace // Held again after it went: written again, and listed once. std::shared_ptr again = files.claim(copy); put(copy); - UNSCOPED_INFO("there"); CHECK(there(copy)); } @@ -130,7 +128,6 @@ namespace std::shared_ptr held = running.claim(shared); ALScriptTempFiles sweeper(lists, "now"); - UNSCOPED_INFO("three went"); CHECK(sweeper.sweep() == size_t(3)); UNSCOPED_INFO("the crashed one's copy"); CHECK_FALSE(there(left)); @@ -174,13 +171,9 @@ namespace CHECK(there(lists + "clean.lock")); } } - UNSCOPED_INFO("no copy"); CHECK_FALSE(there(copy)); - { - INFO("no list, no lock"); - CHECK_FALSE(there(lists + "clean.list")); - CHECK_FALSE(there(lists + "clean.lock")); - } + CHECK_FALSE(there(lists + "clean.list")); + CHECK_FALSE(there(lists + "clean.lock")); // One that let go of the store first, a hold outliving it: the // lock is kept until the hold goes. diff --git a/indra/alscript/tests/alscripttextcache_test.cpp b/indra/alscript/tests/alscripttextcache_test.cpp index 1f3b2b9c25..cd2e51db0a 100644 --- a/indra/alscript/tests/alscripttextcache_test.cpp +++ b/indra/alscript/tests/alscripttextcache_test.cpp @@ -41,11 +41,10 @@ namespace std::string text; { INFO("nothing held"); - REQUIRE_FALSE(cache.take("inventory:a", one, text)); + CHECK_FALSE(cache.take("inventory:a", one, text)); CHECK_FALSE(cache.holds("inventory:a")); } cache.failed("inventory:a"); - UNSCOPED_INFO("failed"); CHECK(cache.hasFailed("inventory:a")); cache.put("inventory:a", one, "integer a;\n"); UNSCOPED_INFO("put: no longer failed"); @@ -67,7 +66,6 @@ namespace CHECK(cache.bytes() == size_t(11)); cache.failed("inventory:b"); cache.forgetFailure("inventory:b"); - UNSCOPED_INFO("forgotten"); CHECK_FALSE(cache.hasFailed("inventory:b")); } diff --git a/indra/alscript/tests/alscriptweight_test.cpp b/indra/alscript/tests/alscriptweight_test.cpp index 1a6b689415..af1b88a4c4 100644 --- a/indra/alscript/tests/alscriptweight_test.cpp +++ b/indra/alscript/tests/alscriptweight_test.cpp @@ -122,7 +122,7 @@ namespace // A shared start's edits made from the end back, as an editor's // replaceAll does. - static std::string applied(const std::string& script, const ALLuauSharedStart::Rewrite& rewrite) + static std::string require_applied(const std::string& script, const ALLuauSharedStart::Rewrite& rewrite) { std::vector lines; for (size_t from = 0; from <= script.size();) @@ -182,13 +182,9 @@ namespace CHECK(weight.compiled); UNSCOPED_INFO("against SLua's limit"); CHECK(weight.limit == size_t(131072)); - UNSCOPED_INFO("not an estimate"); CHECK_FALSE(weight.estimate); - { - INFO("something, and not much"); - CHECK(weight.total > 50); - CHECK(weight.total < 2000); - } + CHECK(weight.total > 50); + CHECK(weight.total < 2000); const ALScriptWeight::Part* greet = named(weight, "greet"); const ALScriptWeight::Part* count = named(weight, "count"); { @@ -206,18 +202,15 @@ namespace { INFO("a function that does nothing weighs least:" + listed(weight)); REQUIRE(nothing); - REQUIRE(nothing->bytes < greet->bytes); + CHECK(nothing->bytes < greet->bytes); CHECK(nothing->bytes < count->bytes); } - { - INFO("the script itself, and its strings"); - REQUIRE(named(weight, "script")); - REQUIRE(named(weight, "strings")); - } + CHECK(named(weight, "script")); + CHECK(named(weight, "strings")); const size_t parts = std::accumulate(weight.parts.begin(), weight.parts.end(), size_t(0), [](size_t sum, const ALScriptWeight::Part& p) { return sum + p.bytes; }); { - INFO("the parts are nearly the whole: " + std::to_string(parts) + " of " + std::to_string(weight.total)); + INFO("the parts are nearly the whole: " << parts << " of " << weight.total); CHECK(parts <= weight.total); CHECK(weight.total - parts < 16); } @@ -237,12 +230,8 @@ namespace const std::string words(2000, 'x'); const ALScriptWeight heavy = ALScriptWeigh::slua("local s = \"" + words + "\"\nprint(s)\n"); const ALScriptWeight light = ALScriptWeigh::slua("local s = \"x\"\nprint(s)\n"); - { - INFO("both compiled"); - CHECK(heavy.compiled); - CHECK(light.compiled); - } - UNSCOPED_INFO("the string weighs"); + CHECK(heavy.compiled); + CHECK(light.compiled); CHECK(heavy.total >= light.total + 2000); // Heavy, it is a part of its own, and no longer the table of // strings'. @@ -256,7 +245,7 @@ namespace { INFO("a part of its own:" + listed(heavy)); REQUIRE(constant); - REQUIRE(constant->bytes >= 2000); + CHECK(constant->bytes >= 2000); CHECK(constant->line == 1); } { @@ -354,11 +343,8 @@ namespace } const ALScriptWeight broken = ALScriptWeigh::slua("local function (\n"); - { - INFO("a script that does not compile"); - CHECK_FALSE(broken.compiled); - REQUIRE_FALSE(broken.error.empty()); - } + CHECK_FALSE(broken.compiled); + CHECK_FALSE(broken.error.empty()); } // LSO weighs its image to the top of its heap: the registers, the @@ -390,11 +376,8 @@ namespace CHECK(weight.compiled); UNSCOPED_INFO("against LSO's limit"); CHECK(weight.limit == size_t(16384)); - { - INFO("well under it"); - CHECK(weight.total > 0); - CHECK(weight.total < 4096); - } + CHECK(weight.total > 0); + CHECK(weight.total < 4096); const ALScriptWeight::Part* twice = named(weight, "twice"); const ALScriptWeight::Part* state = named(weight, "default"); const ALScriptWeight::Part* touch = named(weight, "touch_start"); @@ -403,8 +386,8 @@ namespace REQUIRE(twice); REQUIRE(state); REQUIRE(touch); - REQUIRE(named(weight, "state_entry")); - REQUIRE(named(weight, "registers")); + CHECK(named(weight, "state_entry")); + CHECK(named(weight, "registers")); } UNSCOPED_INFO("a function where it is"); CHECK(twice->line == 2); @@ -416,16 +399,12 @@ namespace CHECK(state->bytes == size_t(5 + 8 * 2)); const ALScriptWeight::Part* count = named(weight, "gCount"); const ALScriptWeight::Part* name = named(weight, "gName"); - { - INFO("the globals too"); - REQUIRE(count); - REQUIRE(name); - } + REQUIRE(count); + REQUIRE(name); UNSCOPED_INFO("a string global weighs its string"); CHECK(name->bytes > count->bytes); } - // What does not fit in 16 KB says so. TEST_CASE_METHOD(alscriptweight_data, "ALScriptWeight: what does not fit in 16 KB says so", "[ALScriptWeight]") { UNSCOPED_INFO("builtins: " + error); @@ -438,18 +417,15 @@ namespace script += "];\ndefault\n{\n state_entry()\n {\n llOwnerSay(llList2String(gWords, 0));\n }\n}\n"; const ALScriptWeight weight = ALScriptWeigh::lso(script); { - INFO("does not fit: " + weight.error + " " + std::to_string(weight.total)); + INFO("does not fit: " << weight.error << " " << weight.total); CHECK_FALSE(weight.compiled); - REQUIRE_FALSE(weight.error.empty()); + CHECK_FALSE(weight.error.empty()); } UNSCOPED_INFO("and weighs more than there is"); CHECK(weight.total > weight.limit); const ALScriptWeight broken = ALScriptWeigh::lso("default { state_entry() { integer x = ; } }\n"); - { - INFO("a script that does not parse"); - CHECK_FALSE(broken.compiled); - REQUIRE_FALSE(broken.error.empty()); - } + CHECK_FALSE(broken.compiled); + CHECK_FALSE(broken.error.empty()); } // Mono is an estimate: the IL sized by the opcode table with what it @@ -478,15 +454,11 @@ namespace const ALScriptWeight weight = ALScriptWeigh::mono(script); UNSCOPED_INFO("compiled: " + weight.error); CHECK(weight.compiled); - UNSCOPED_INFO("an estimate"); CHECK(weight.estimate); UNSCOPED_INFO("against Mono's limit"); CHECK(weight.limit == size_t(65536)); - { - INFO("over what an assembly costs, and under the limit"); - CHECK(weight.total > ALScriptWeigh::MONO_BASE_BYTES); - CHECK(weight.total < 8192); - } + CHECK(weight.total > ALScriptWeigh::MONO_BASE_BYTES); + CHECK(weight.total < 8192); const ALScriptWeight::Part* twice = named(weight, "twice"); const ALScriptWeight::Part* touch = named(weight, "touch_start"); const ALScriptWeight::Part* entry = named(weight, "state_entry"); @@ -495,20 +467,18 @@ namespace REQUIRE(twice); REQUIRE(touch); REQUIRE(entry); - REQUIRE(named(weight, "globals")); - REQUIRE(named(weight, "assembly")); + CHECK(named(weight, "globals")); + CHECK(named(weight, "assembly")); } UNSCOPED_INFO("a function where it is"); CHECK(twice->line == 1); UNSCOPED_INFO("a handler in its state"); CHECK(touch->within == std::string("default")); - UNSCOPED_INFO("where it is"); CHECK(touch->line == 11); UNSCOPED_INFO("a long string weighs:" + listed(weight)); CHECK(touch->bytes > entry->bytes); const size_t parts = std::accumulate(weight.parts.begin(), weight.parts.end(), size_t(0), [](size_t sum, const ALScriptWeight::Part& p) { return sum + p.bytes; }); - UNSCOPED_INFO("the parts are the whole"); CHECK(parts == weight.total); } @@ -540,26 +510,23 @@ namespace CHECK(weight.compiled); UNSCOPED_INFO("against Luau's limit"); CHECK(weight.limit == size_t(131072)); - UNSCOPED_INFO("not an estimate"); CHECK_FALSE(weight.estimate); { - INFO("something, and not much: " + std::to_string(weight.total) + listed(weight)); + INFO("parts:" + listed(weight)); CHECK(weight.total > 50); CHECK(weight.total < 4096); } const ALScriptWeight::Part* twice = named(weight, "twice"); UNSCOPED_INFO("a function a part:" + listed(weight)); REQUIRE(twice != nullptr); - UNSCOPED_INFO("where it is: " + std::to_string(twice->line)); CHECK(twice->line == 1); const ALScriptWeight::Part* touch = named(weight, "touch_start"); { INFO("a handler in its state:" + listed(weight)); REQUIRE(touch); - REQUIRE(touch->within == "default"); + CHECK(touch->within == "default"); CHECK(touch->kind == ALScriptWeight::Part::Kind::Handler); } - UNSCOPED_INFO("where it is"); CHECK(touch->line == 11); // Its lines, read off the compiler's lined bytecode, which the // server's is not: the total stays what the server charges. @@ -574,11 +541,8 @@ namespace UNSCOPED_INFO("nothing past the script's lines:" + lined(weight)); CHECK(within(weight, 16, 1000) == 0); const ALScriptWeight broken = ALScriptWeigh::lslLuau("default { state_entry() { integer x = ; } }\n"); - { - INFO("a script that does not compile"); - CHECK_FALSE(broken.compiled); - REQUIRE_FALSE(broken.error.empty()); - } + CHECK_FALSE(broken.compiled); + CHECK_FALSE(broken.error.empty()); } // LSO by line: what the compiler wrote while it stood at each @@ -592,8 +556,7 @@ namespace const ALScriptWeight weight = ALScriptWeigh::lso(byLine()); UNSCOPED_INFO("compiled: " + weight.error); CHECK(weight.compiled); - UNSCOPED_INFO("by line:" + lined(weight)); - REQUIRE_FALSE(weight.lines.empty()); + CHECK_FALSE(weight.lines.empty()); UNSCOPED_INFO("the loop's body" + lined(weight)); CHECK(at(weight, 13) > 0); UNSCOPED_INFO("the loop itself: its test, its step and its jump back" + lined(weight)); @@ -634,8 +597,7 @@ namespace const ALScriptWeight weight = ALScriptWeigh::mono(byLine()); UNSCOPED_INFO("compiled: " + weight.error); CHECK(weight.compiled); - UNSCOPED_INFO("by line:" + lined(weight)); - REQUIRE_FALSE(weight.lines.empty()); + CHECK_FALSE(weight.lines.empty()); UNSCOPED_INFO("the loop's body" + lined(weight)); CHECK(at(weight, 13) > 0); UNSCOPED_INFO("the loop itself" + lined(weight)); @@ -664,11 +626,8 @@ namespace { all += line.bytes; } - { - INFO("the lines are less than the whole"); - CHECK(all > 0); - CHECK(all < weight.total); - } + CHECK(all > 0); + CHECK(all < weight.total); } // In the source's places: a part in an include is in the include, at @@ -716,7 +675,6 @@ namespace UNSCOPED_INFO("compiled: " + weight.error); CHECK(weight.compiled); const ALScriptWeight placed = weight.inSource(mapped(true)); - UNSCOPED_INFO("the same weight"); CHECK(placed.total == weight.total); const ALScriptWeight::Part* twice = named(placed, "twice"); const ALScriptWeight::Part* entry = named(placed, "state_entry"); @@ -732,7 +690,7 @@ namespace UNSCOPED_INFO("to its end there"); CHECK(twice->endLine == 3); UNSCOPED_INFO("a handler in the script"); - REQUIRE(entry->file.empty()); + CHECK(entry->file.empty()); UNSCOPED_INFO("at the script's line"); CHECK(entry->line == 3); size_t in_include = 0, in_script = 0; @@ -778,7 +736,6 @@ namespace } script += "];\ndefault\n{\n state_entry()\n {\n llOwnerSay((string)llGetListLength(gNumbers));\n }\n}\n"; lsl.check(script); - UNSCOPED_INFO("it parses"); CHECK(lsl.parsed()); const ALScriptWeight weight = ALScriptWeigh::lso(script); UNSCOPED_INFO("and is weighed: " + weight.error); @@ -855,8 +812,8 @@ namespace { INFO("a local function, from its function to its end:" + listed(weight)); REQUIRE(twice); - REQUIRE(twice->line == 1); - REQUIRE(twice->endLine == 3); + CHECK(twice->line == 1); + CHECK(twice->endLine == 3); CHECK(twice->endColumn == 3); } UNSCOPED_INFO("not a handler"); @@ -869,17 +826,17 @@ namespace } { INFO("from its function to its end"); - REQUIRE(entry->line == 6); - REQUIRE(entry->column == 18); + CHECK(entry->line == 6); + CHECK(entry->column == 18); CHECK(entry->endLine == 8); } UNSCOPED_INFO("a state named by a string:" + listed(weight)); - REQUIRE(find("touch_start", "other place") != nullptr); + CHECK(find("touch_start", "other place") != nullptr); const ALScriptWeight::Part* touched = find("touch_start", ""); { INFO("LLEvents' handler by its event:" + listed(weight)); REQUIRE(touched); - REQUIRE(touched->kind == ALScriptWeight::Part::Kind::Handler); + CHECK(touched->kind == ALScriptWeight::Part::Kind::Handler); CHECK(touched->line == 11); } const ALScriptWeight::Part* tick = find("LLTimers:every", ""); @@ -895,9 +852,9 @@ namespace UNSCOPED_INFO("the long string on the line that says it:" + lined(weight)); CHECK(at(weight, 7) > 80); UNSCOPED_INFO("a short one on its own line:" + lined(weight)); - REQUIRE(at(weight, 12) > 4 + std::string("touched").size()); + CHECK(at(weight, 12) > 4 + std::string("touched").size()); const size_t lines = within(weight, 0, 100); - UNSCOPED_INFO("most of the whole on some line: " + std::to_string(lines) + " of " + std::to_string(weight.total)); + UNSCOPED_INFO("most of the whole on some line: " << lines << " of " << weight.total); CHECK(lines * 3 > weight.total * 2); } @@ -932,40 +889,40 @@ namespace { INFO("a value loaded twice"); REQUIRE(again); - REQUIRE(again->uses == 2); - REQUIRE(again->loads == 2); - REQUIRE(again->line == 6); + CHECK(again->uses == 2); + CHECK(again->loads == 2); + CHECK(again->line == 6); CHECK(again->bytes == 12); } const ALScriptWeight::String* colour = of("colour"); { INFO("a field's name: named, never loaded"); REQUIRE(colour); - REQUIRE(colour->uses >= 2); - REQUIRE(colour->loads == 0); + CHECK(colour->uses >= 2); + CHECK(colour->loads == 0); CHECK(colour->line == 4); } const ALScriptWeight::String* blue = of("blue"); { INFO("a comparison's: named, never loaded"); REQUIRE(blue); - REQUIRE(blue->uses == 1); + CHECK(blue->uses == 1); CHECK(blue->loads == 0); } const ALScriptWeight::String* say = of("OwnerSay"); { INFO("an import's path"); REQUIRE(say); - REQUIRE(say->uses >= 1); - REQUIRE(say->loads == 0); + CHECK(say->uses >= 1); + CHECK(say->loads == 0); CHECK(say->line == 1); } const ALScriptWeight::String* greet = of("greet"); { INFO("a function's name, at the line it starts on"); REQUIRE(greet); - REQUIRE(greet->name); - REQUIRE(greet->uses == 0); + CHECK(greet->name); + CHECK(greet->uses == 0); CHECK(greet->line == 0); } size_t bytes = 0; @@ -1023,7 +980,7 @@ namespace UNSCOPED_INFO("the start the four share"); CHECK(shared.start == start); UNSCOPED_INFO("the four, not the field's"); - REQUIRE(shared.strings.size() == rests.size()); + CHECK(shared.strings.size() == rests.size()); size_t before = 0; size_t after = 1 + start.size(); for (const std::string& rest : rests) @@ -1036,11 +993,8 @@ namespace CHECK(shared.saved == before - cost); for (const size_t i : shared.strings) { - { - INFO("each one of them"); - REQUIRE(w.strings[i].text.rfind(start, 0) == 0); - REQUIRE(w.strings[i].text != start + "zulu"); - } + REQUIRE(w.strings[i].text.rfind(start, 0) == 0); + REQUIRE(w.strings[i].text != start + "zulu"); } // The costs against the compiler's own: the four written whole, and @@ -1055,24 +1009,21 @@ namespace } const ALScriptWeight as_whole = ALScriptWeigh::slua(whole); const ALScriptWeight as_apart = ALScriptWeigh::slua(apart); - { - INFO("both compiled"); - CHECK(as_whole.compiled); - CHECK(as_apart.compiled); - REQUIRE(as_whole.sharedStarts.size() == 1); - } + CHECK(as_whole.compiled); + CHECK(as_apart.compiled); + REQUIRE(as_whole.sharedStarts.size() == 1); const S64 really = S64(as_whole.total) - S64(as_apart.total); - UNSCOPED_INFO(llformat("saves about what was said: %lld against %zu", (long long)really, as_whole.sharedStarts[0].saved)); + UNSCOPED_INFO("saves about what was said: " << really << " against " << as_whole.sharedStarts[0].saved); CHECK(std::abs(really - S64(as_whole.sharedStarts[0].saved)) <= 8); // Nothing where only two share a start: two loads cost more than // one start saves, until the start is long. UNSCOPED_INFO("two with a middling start: nothing"); - REQUIRE(ALScriptWeigh::slua("ll.OwnerSay(\"twenty bytes of start, then one\")\nll.OwnerSay(\"twenty bytes of start, then two\")\n").sharedStarts.empty()); + CHECK(ALScriptWeigh::slua("ll.OwnerSay(\"twenty bytes of start, then one\")\nll.OwnerSay(\"twenty bytes of start, then two\")\n").sharedStarts.empty()); UNSCOPED_INFO("two with a long one: worth it"); - REQUIRE(ALScriptWeigh::slua("ll.OwnerSay(\"a start long enough to be worth keeping once, then one\")\n" - "ll.OwnerSay(\"a start long enough to be worth keeping once, then two\")\n") - .sharedStarts.size() == 1); + CHECK(ALScriptWeigh::slua("ll.OwnerSay(\"a start long enough to be worth keeping once, then one\")\n" + "ll.OwnerSay(\"a start long enough to be worth keeping once, then two\")\n") + .sharedStarts.size() == 1); // Never inside a character. const ALScriptWeight cut = ALScriptWeigh::slua("ll.OwnerSay(\"a start long enough to be worth keeping once \xC3\xA9t\xC3\xA9\")\n" "ll.OwnerSay(\"a start long enough to be worth keeping once \xC3\xA8re\")\n"); @@ -1110,15 +1061,14 @@ namespace REQUIRE(before.sharedStarts.size() == size_t(1)); const std::vector strings = textsOf(before, before.sharedStarts[0]); UNSCOPED_INFO("not the require's path, which is no string to keep once"); - REQUIRE(std::find(strings.begin(), strings.end(), start + "x") == strings.end()); + CHECK(std::find(strings.begin(), strings.end(), start + "x") == strings.end()); ALLuauSharedStart::Rewrite rewrite; std::string error; const bool done = ALLuauSharedStart::rewrite(script, before.sharedStarts[0].start, strings, rewrite, error); UNSCOPED_INFO("rewritten: " + error); CHECK(done); - UNSCOPED_INFO("its name"); CHECK(rewrite.name == std::string("sharedStart")); - const std::string after = applied(script, rewrite); + const std::string after = require_applied(script, rewrite); const auto has = [&after](const std::string& text) { return after.find(text) != std::string::npos; }; UNSCOPED_INFO("the local past the --! comment: " + after); CHECK(after.rfind("--!strict\nlocal sharedStart\nsharedStart = \"" + start + "\"\n\n", 0) == 0); @@ -1144,8 +1094,7 @@ namespace // Nothing left to keep once: the start is the local's one value, // kept once already, and the require's path no literal to keep. UNSCOPED_INFO("nothing offered again"); - REQUIRE(weighed.sharedStarts.size() == size_t(0)); - UNSCOPED_INFO(llformat("lighter: %zu then %zu", before.total, weighed.total)); + CHECK(weighed.sharedStarts.size() == size_t(0)); CHECK(weighed.total < before.total); ALLuauService service; llifstream in(std::string(AL_LSL_DEFINITIONS_DIR) + "/secondlife.d.luau", std::ios::binary); @@ -1159,12 +1108,12 @@ namespace said += p.severity == ALScriptProblem::Severity::Error && p.message.find("require") == std::string::npos ? p.message + "\n" : std::string(); } UNSCOPED_INFO("checks, strict:\n" + said + "---\n" + after); - REQUIRE(said.empty()); + CHECK(said.empty()); { INFO("nothing to make of a script without them"); CHECK_FALSE(ALLuauSharedStart::rewrite("print(\"x\")\n", start, strings, rewrite, error)); - REQUIRE_FALSE(error.empty()); + CHECK_FALSE(error.empty()); } const bool taken = ALLuauSharedStart::rewrite("local sharedStart = 1\nprint(\"" + start + "alpha\")\n", start, { start + "alpha" }, rewrite, error); UNSCOPED_INFO("rewritten with a name taken: " + error); @@ -1221,8 +1170,8 @@ namespace UNSCOPED_INFO("what it saves, a capture for each closure"); CHECK(shared.saved == before - cost); const S64 really = S64(as_whole.total) - S64(as_apart.total); - UNSCOPED_INFO(llformat("saves about what was said: %lld against %zu", (long long)really, shared.saved)); - REQUIRE(std::abs(really - S64(shared.saved)) <= S64(8 + 2 * rests.size())); + UNSCOPED_INFO("saves about what was said: " << really << " against " << shared.saved); + CHECK(std::abs(really - S64(shared.saved)) <= S64(8 + 2 * rests.size())); } // Where the start's local goes: before the first of the script that is @@ -1244,14 +1193,14 @@ namespace const bool done = ALLuauSharedStart::rewrite(script, start, strings, rewrite, why); UNSCOPED_INFO("rewritten: " + why + "\n" + script); REQUIRE(done); - return alscriptweight_data::applied(script, rewrite); + return alscriptweight_data::require_applied(script, rewrite); }; const auto heads = [&](const std::string& head, const std::string& text) { return text.rfind(head + local, 0) == 0; }; std::string after = kept("-- Door script\n--!strict\n" + said); UNSCOPED_INFO("past an ordinary comment and the --! one: " + after); CHECK(heads("-- Door script\n--!strict\n", after)); UNSCOPED_INFO("and the strings kept: " + after); - REQUIRE(after.find("ll.OwnerSay(sharedStart .. \"alpha\")") != std::string::npos); + CHECK(after.find("ll.OwnerSay(sharedStart .. \"alpha\")") != std::string::npos); after = kept("\n--!strict\n" + said); UNSCOPED_INFO("past a blank first line: " + after); CHECK(heads("\n--!strict\n", after)); @@ -1262,7 +1211,7 @@ namespace { INFO("at the start of an indented line: " + after); CHECK(heads("--!strict\n", after)); - REQUIRE(after.find(local + " ll.OwnerSay") != std::string::npos); + CHECK(after.find(local + " ll.OwnerSay") != std::string::npos); } after = kept("--[[ The door ]] " + said); UNSCOPED_INFO("after a comment on the same line: " + after); @@ -1272,21 +1221,21 @@ namespace std::string error; { INFO("a call's string on a line of its own would be ambiguous bracketed"); - REQUIRE_FALSE(ALLuauSharedStart::rewrite("ll.OwnerSay\n\"" + start + "alpha\"\nll.OwnerSay(\"" + start + "bravo\")\n", start, strings, rewrite, error)); - REQUIRE(error.find("does not parse") != std::string::npos); + CHECK_FALSE(ALLuauSharedStart::rewrite("ll.OwnerSay\n\"" + start + "alpha\"\nll.OwnerSay(\"" + start + "bravo\")\n", start, strings, rewrite, error)); + CHECK(error.find("does not parse") != std::string::npos); } UNSCOPED_INFO("nothing made of it"); - REQUIRE(rewrite.edits.empty()); + CHECK(rewrite.edits.empty()); { INFO("a string not written out: " + error); CHECK_FALSE(ALLuauSharedStart::rewrite("ll.OwnerSay(\"" + start + "alpha\")\n", start, strings, rewrite, error)); - REQUIRE(error.find("not every one") != std::string::npos); + CHECK(error.find("not every one") != std::string::npos); } { 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)); - REQUIRE(error.find("none of these strings") != std::string::npos); + CHECK(error.find("none of these strings") != std::string::npos); } UNSCOPED_INFO("a byte of no character in hex, a whole one as it is"); @@ -1308,27 +1257,27 @@ namespace { const std::string start = "You have touched the object of the day: "; UNSCOPED_INFO("an interpolated string's pattern"); - REQUIRE(ALScriptWeigh::slua("local function said(name: string)\n" - " ll.OwnerSay(`" + start + "{name}, first`)\n" - " ll.OwnerSay(`" + start + "{name}, second`)\n" - " ll.OwnerSay(`" + start + "{name}, third`)\n" - "end\n" - "said(\"x\")\n") - .sharedStarts.empty()); + CHECK(ALScriptWeigh::slua("local function said(name: string)\n" + " ll.OwnerSay(`" + start + "{name}, first`)\n" + " ll.OwnerSay(`" + start + "{name}, second`)\n" + " ll.OwnerSay(`" + start + "{name}, third`)\n" + "end\n" + "said(\"x\")\n") + .sharedStarts.empty()); UNSCOPED_INFO("strings joined on a local's constant value"); - REQUIRE(ALScriptWeigh::slua("local greeting = \"" + start + "\"\n" - "ll.OwnerSay(greeting .. \"alpha\")\n" - "ll.OwnerSay(greeting .. \"bravo\")\n" - "ll.OwnerSay(greeting .. \"charlie\")\n") - .sharedStarts.empty()); + CHECK(ALScriptWeigh::slua("local greeting = \"" + start + "\"\n" + "ll.OwnerSay(greeting .. \"alpha\")\n" + "ll.OwnerSay(greeting .. \"bravo\")\n" + "ll.OwnerSay(greeting .. \"charlie\")\n") + .sharedStarts.empty()); const auto feelings = [&start](const std::string& type) { return "type Mood = " + type + "\nlocal function feel(mood: Mood) end\n" "feel(\"" + start + "happy\")\nfeel(\"" + start + "sad\")\nfeel(\"" + start + "bored\")\n"; }; UNSCOPED_INFO("given for a string"); - REQUIRE(ALScriptWeigh::slua(feelings("string")).sharedStarts.size() == size_t(1)); + CHECK(ALScriptWeigh::slua(feelings("string")).sharedStarts.size() == size_t(1)); UNSCOPED_INFO("given for a singleton"); - REQUIRE(ALScriptWeigh::slua(feelings("\"" + start + "happy\" | \"" + start + "sad\" | \"" + start + "bored\"")).sharedStarts.empty()); + CHECK(ALScriptWeigh::slua(feelings("\"" + start + "happy\" | \"" + start + "sad\" | \"" + start + "bored\"")).sharedStarts.empty()); // Kept once, with an interpolated string sharing the start beside // them: neither it nor the start's local is offered again. @@ -1346,17 +1295,17 @@ namespace REQUIRE(kept.sharedStarts.size() == 1); } UNSCOPED_INFO("the three written out, not the pattern"); - REQUIRE(kept.sharedStarts[0].strings.size() == size_t(3)); + CHECK(kept.sharedStarts[0].strings.size() == size_t(3)); ALLuauSharedStart::Rewrite rewrite; std::string error; bool done = ALLuauSharedStart::rewrite(kept_script, start, textsOf(kept, kept.sharedStarts[0]), rewrite, error); UNSCOPED_INFO("rewritten: " + error); CHECK(done); - const ALScriptWeight again = ALScriptWeigh::slua(applied(kept_script, rewrite)); + const ALScriptWeight again = ALScriptWeigh::slua(require_applied(kept_script, rewrite)); UNSCOPED_INFO("weighed again: " + again.error); CHECK(again.compiled); UNSCOPED_INFO("nothing offered again"); - REQUIRE(again.sharedStarts.size() == size_t(0)); + CHECK(again.sharedStarts.size() == size_t(0)); // Joined on to a name, each piece is loaded on its own. const std::string joined = "local function touched(name: string)\n" @@ -1375,15 +1324,12 @@ namespace done = ALLuauSharedStart::rewrite(joined, start, textsOf(pieces, pieces.sharedStarts[0]), rewrite, error); UNSCOPED_INFO("rewritten: " + error); CHECK(done); - const std::string pieces_after = applied(joined, rewrite); + const std::string pieces_after = require_applied(joined, rewrite); UNSCOPED_INFO("each piece kept in place: " + pieces_after); - REQUIRE(pieces_after.find("ll.OwnerSay(name .. sharedStart .. \"alpha\" .. \"!\")") != std::string::npos); + CHECK(pieces_after.find("ll.OwnerSay(name .. sharedStart .. \"alpha\" .. \"!\")") != std::string::npos); const ALScriptWeight lighter = ALScriptWeigh::slua(pieces_after); - { - INFO(llformat("lighter: %zu then %zu", pieces.total, lighter.total)); - CHECK(lighter.compiled); - CHECK(lighter.total < pieces.total); - } + CHECK(lighter.compiled); + CHECK(lighter.total < pieces.total); // A module's strings, in the source's places, are not the script's // to keep once: the first three lines are a module's. @@ -1417,12 +1363,12 @@ namespace { INFO("weighed: " + bundled.error); CHECK(bundled.compiled); - REQUIRE(bundled.sharedStarts.size() == 1); + CHECK(bundled.sharedStarts.size() == 1); } UNSCOPED_INFO("all the script's: kept"); - REQUIRE(bundled.inSource(mapped(0)).sharedStarts.size() == size_t(1)); + CHECK(bundled.inSource(mapped(0)).sharedStarts.size() == size_t(1)); UNSCOPED_INFO("two a module's: nothing"); - REQUIRE(bundled.inSource(mapped(3)).sharedStarts.size() == size_t(0)); + CHECK(bundled.inSource(mapped(3)).sharedStarts.size() == size_t(0)); } // LSL's constants are folded as the grid's compiler folds them: a @@ -1432,11 +1378,8 @@ namespace { const ALScriptWeight named = ALScriptWeigh::slua("local x = PRIM_SIZE\nprint(x)\n"); const ALScriptWeight written = ALScriptWeigh::slua("local x = 7\nprint(x)\n"); - { - INFO("both compiled"); - CHECK(named.compiled); - CHECK(written.compiled); - } + CHECK(named.compiled); + CHECK(written.compiled); UNSCOPED_INFO("the same as its value written out"); CHECK(named.total == written.total); bool looked_up = false; @@ -1464,8 +1407,8 @@ namespace REQUIRE(found != nullptr); { INFO("named on one line: " + found->name); - REQUIRE(found->name.rfind("\"a\\rb\\tc", 0) == 0); - REQUIRE(found->name.find('\r') == std::string::npos); + CHECK(found->name.rfind("\"a\\rb\\tc", 0) == 0); + CHECK(found->name.find('\r') == std::string::npos); } } } diff --git a/indra/alscript/tests/alsourcemap_test.cpp b/indra/alscript/tests/alsourcemap_test.cpp index 1cadd08022..e1ba8b7497 100644 --- a/indra/alscript/tests/alsourcemap_test.cpp +++ b/indra/alscript/tests/alsourcemap_test.cpp @@ -108,8 +108,8 @@ namespace CHECK(map.toSource(0, 20000).column == 2999); { INFO("a line with none: nowhere"); - REQUIRE_FALSE(map.toSource(1, 0).found()); - REQUIRE(map.toSource(2, 1).found()); + CHECK_FALSE(map.toSource(1, 0).found()); + CHECK(map.toSource(2, 1).found()); } } @@ -149,7 +149,7 @@ namespace CHECK(through.toSource(0, 11).column == 1); } UNSCOPED_INFO("a line the inner has nothing for: dropped"); - REQUIRE_FALSE(through.toSource(1, 0).found()); + CHECK_FALSE(through.toSource(1, 0).found()); ALSourceMap::Loc begin, end; { INFO("the copied stretches stay verbatim"); @@ -161,17 +161,11 @@ namespace TEST_CASE("alsourcemap: a text mapped to itself: every line its own even the last without a newline", "[alsourcemap]") { const ALSourceMap map = ALSourceMap::identity("one\ntwo three\nlast", "same"); - { - INFO("named"); - REQUIRE(map.files().size() == 1); - CHECK(map.files()[0].name == "same"); - } - { - INFO("each line"); - CHECK(map.toSource(1, 5).line == 1); - CHECK(map.toSource(1, 5).column == 5); - CHECK(map.toSource(2, 2).line == 2); - } + REQUIRE(map.files().size() == 1); + CHECK(map.files()[0].name == "same"); + CHECK(map.toSource(1, 5).line == 1); + CHECK(map.toSource(1, 5).column == 5); + CHECK(map.toSource(2, 2).line == 2); } TEST_CASE("alsourcemap: a verbatim stretch on a long line: the first segment to reach the column", "[alsourcemap]") { diff --git a/indra/alscript/tests/aluploadheader_test.cpp b/indra/alscript/tests/aluploadheader_test.cpp index 113f67a595..1c79a3208e 100644 --- a/indra/alscript/tests/aluploadheader_test.cpp +++ b/indra/alscript/tests/aluploadheader_test.cpp @@ -97,18 +97,18 @@ namespace { ALUploadHeader absolute = bare; absolute.file = where; - UNSCOPED_INFO(std::string("never absolute: ") + where); + INFO("never absolute: " << where); REQUIRE(absolute.write(false) == bare.write(false)); } ALUploadHeader aliased = bare; aliased.file = "@lib/util.luau"; UNSCOPED_INFO("an alias is no root"); - REQUIRE(aliased.write(true).find("-- @file @lib/util.luau\n") != std::string::npos); + CHECK(aliased.write(true).find("-- @file @lib/util.luau\n") != std::string::npos); ALUploadHeader broken = bare; broken.creator = "two\nlines\r"; UNSCOPED_INFO("a line break in a field stays in its comment"); - REQUIRE(broken.write(false).find("// @creator two lines \n") != std::string::npos); + CHECK(broken.write(false).find("// @creator two lines \n") != std::string::npos); } TEST_CASE_METHOD(aluploadheader_data, "aluploadheader: ours reads back and take() steps past it to the code", "[aluploadheader]") @@ -120,24 +120,17 @@ namespace const std::string text = written.write(lua) + code; std::string_view rest = text; const std::optional back = ALUploadHeader::take(rest, lua); - const std::string name = lua ? "SLua: " : "LSL: "; - UNSCOPED_INFO(name + "read"); + CAPTURE(lua); REQUIRE(back.has_value()); - UNSCOPED_INFO(name + "ours"); REQUIRE(back->ours); - UNSCOPED_INFO(name + "file"); REQUIRE(back->file == written.file); - UNSCOPED_INFO(name + "hash"); REQUIRE(back->hash == written.hash); - UNSCOPED_INFO(name + "date"); REQUIRE(back->date == written.date); - UNSCOPED_INFO(name + "creator"); REQUIRE(back->creator == written.creator); - UNSCOPED_INFO(name + "no key"); REQUIRE(back->creatorId == std::string()); - UNSCOPED_INFO(name + "the code is what is left"); + UNSCOPED_INFO("the code is what is left"); REQUIRE(std::string(rest) == code); - UNSCOPED_INFO(name + "not in the other language's comments"); + UNSCOPED_INFO("not in the other language's comments"); REQUIRE_FALSE(ALUploadHeader::parse(text, !lua).has_value()); } @@ -148,7 +141,6 @@ namespace REQUIRE(windows.has_value()); UNSCOPED_INFO("and none in a field"); CHECK(windows->hash == std::string("h")); - UNSCOPED_INFO("past it"); CHECK(std::string(crlf) == std::string("code")); } @@ -166,19 +158,13 @@ namespace "default { }\n"; std::string_view rest = text; const std::optional back = ALUploadHeader::take(rest, false); - UNSCOPED_INFO("read"); REQUIRE(back.has_value()); - UNSCOPED_INFO("not ours"); CHECK_FALSE(back->ours); - UNSCOPED_INFO("file"); CHECK(back->file == std::string("scripts/door.lsl")); UNSCOPED_INFO("its sha256, as it is"); CHECK(back->hash == std::string("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")); - UNSCOPED_INFO("date"); CHECK(back->date == std::string("2026-10-07 14:03:11")); - UNSCOPED_INFO("creator"); CHECK(back->creator == std::string("Rye Resident")); - UNSCOPED_INFO("key"); CHECK(back->creatorId == std::string("0b8f2ef9-1d4a-4f4c-9b62-7d3a1c4e5f60")); UNSCOPED_INFO("the code is what is left"); CHECK(std::string(rest) == std::string("default { }\n")); @@ -190,7 +176,7 @@ namespace { INFO("SLua's, a field to come passed over"); REQUIRE(slua.has_value()); - REQUIRE(slua->file == "a.luau"); + CHECK(slua->file == "a.luau"); CHECK_FALSE(slua->ours); } } @@ -216,9 +202,8 @@ namespace for (const char* text : texts) { std::string_view rest = text; - UNSCOPED_INFO(std::string("not a header: ") + text); + CAPTURE(text); REQUIRE_FALSE(ALUploadHeader::take(rest, false).has_value()); - UNSCOPED_INFO(std::string("and nothing taken: ") + text); REQUIRE(std::string(rest) == std::string(text)); } std::string runs_on = "// ================ alchemy meta ================\n"; @@ -228,7 +213,7 @@ namespace } runs_on += "// ====\n"; UNSCOPED_INFO("a block far longer than any header"); - REQUIRE_FALSE(ALUploadHeader::parse(runs_on, false).has_value()); + CHECK_FALSE(ALUploadHeader::parse(runs_on, false).has_value()); } TEST_CASE_METHOD(aluploadheader_data, "aluploadheader: the hash is XXH128 of v1 and the target and source and code split by noughts", "[aluploadheader]") @@ -238,7 +223,7 @@ namespace UNSCOPED_INFO("xxHash's own"); CHECK(hash == aluploadheader_data::xxhsum("v1\0mono\0#define X 1\nX\0001"s)); UNSCOPED_INFO("its scheme and 32 digits"); - REQUIRE(hash.size() == std::string("xxh128:").size() + 32); + CHECK(hash.size() == std::string("xxh128:").size() + 32); UNSCOPED_INFO("plain: the text alone"); CHECK(ALUploadHeader::hashOfPlain("luau", "print(1)") == aluploadheader_data::xxhsum("v1\0luau\0print(1)"s)); @@ -270,12 +255,12 @@ namespace for (const char* path : { "/a", "\\a", "\\\\host\\a", "C:\\a", "c:/a", "z:a", "~/a", "~rye/a" }) { - UNSCOPED_INFO(std::string("absolute: ") + path); + CAPTURE(path); REQUIRE(ALUploadHeader::absolute(path)); } for (const char* path : { "", "a", "net/door.lsl", "../shared/util.luau", "@lib/util.luau", "1:x", ":a" }) { - UNSCOPED_INFO(std::string("not: ") + path); + CAPTURE(path); REQUIRE_FALSE(ALUploadHeader::absolute(path)); } } diff --git a/indra/lscript/tests/allslreference_test.cpp b/indra/lscript/tests/allslreference_test.cpp index 51d3c90265..5b7ab80631 100644 --- a/indra/lscript/tests/allslreference_test.cpp +++ b/indra/lscript/tests/allslreference_test.cpp @@ -176,7 +176,7 @@ namespace "}\n"; const ALLSLReference::Result lso = ALLSLReference::compile(text, Target::LSO); UNSCOPED_INFO("LSO: " + lso.messages); - CHECK(lso.ok); + REQUIRE(lso.ok); // Events 35 to 43, bits 34 to 42. UNSCOPED_INFO("their bits"); CHECK(reg64(lso.image, NER) == (U64)0x1ff << 34); @@ -188,7 +188,7 @@ namespace "edefaultexperience_permissions_denied(", "edefaultlinkset_data(", "edefaultgame_control(", "edefaulton_death(", "edefaulton_damage(", "edefaultfinal_damage(" }) { - UNSCOPED_INFO(std::string(handler) + ": " + cil.cil); + INFO(handler << ": " << cil.cil); REQUIRE(has(cil.cil, handler)); } } @@ -251,7 +251,7 @@ namespace "default { state_entry() { llSay(0, (string)(twice(g) * 3 + 6)); } }\n"; const ALLSLReference::Result lso = ALLSLReference::compile(text, Target::LSO); UNSCOPED_INFO("LSO: " + lso.messages); - CHECK(lso.ok); + REQUIRE(lso.ok); // llSay is function 23: heard here for the run, as it was after. gSaid.clear(); @@ -266,13 +266,9 @@ namespace vm.runQuanta(FALSE, LLUUID::null, &error, 1.f, events, timer); } } - UNSCOPED_INFO("no fault: " + std::string(error ? error : "")); - REQUIRE_FALSE(error); - UNSCOPED_INFO("one handler run"); + CHECK_FALSE(error); CHECK(events == (U32)1); - UNSCOPED_INFO("said once"); REQUIRE(gSaid.size() == (size_t)1); - UNSCOPED_INFO("what it worked out"); CHECK(gSaid[0] == std::string("42")); } } diff --git a/indra/lscript/tests/allslreferencecheck_test.cpp b/indra/lscript/tests/allslreferencecheck_test.cpp index 57681e1ded..2ed9ed57c8 100644 --- a/indra/lscript/tests/allslreferencecheck_test.cpp +++ b/indra/lscript/tests/allslreferencecheck_test.cpp @@ -278,7 +278,6 @@ namespace } where += name + " from byte " + std::to_string(at) + ", in its " + regionOf(ll.image, at) + "\n"; } - UNSCOPED_INFO("most of them: " + std::to_string(same)); CHECK(same >= 75); UNSCOPED_INFO("the images that part: " + listed(parted) + "\n" + where); CHECK(parted == PARTED); @@ -330,10 +329,9 @@ namespace where += name + ": [" + (at < written.size() ? written[at] : "") + "] against LL's [" + (at < theirs.size() ? theirs[at] : "") + "]\n"; } } - UNSCOPED_INFO("most of them: " + std::to_string(both)); CHECK(both >= 75); UNSCOPED_INFO("the same as LL's:\n" + where); - REQUIRE(parted.empty()); + CHECK(parted.empty()); UNSCOPED_INFO("LL's compiles alone: " + listed(only_ll)); CHECK(only_ll == ONLY_LL); UNSCOPED_INFO("Tailslide compiles alone: " + listed(only_tailslide)); @@ -370,10 +368,9 @@ namespace wrong += name + ": LL's weighs " + std::to_string(theirs.total) + ", Tailslide's " + std::to_string(tailslide.total) + "\n"; } } - UNSCOPED_INFO("most of them: " + std::to_string(both)); CHECK(both >= 75); UNSCOPED_INFO(wrong); - REQUIRE(wrong.empty()); + CHECK(wrong.empty()); } TEST_CASE_METHOD(allslreferencecheck_data, "allslreferencecheck: integer literals past 32 bits read -1 as on 32-bit hosts; both make one image", "[allslreferencecheck]") @@ -388,11 +385,9 @@ namespace std::vector ours; std::string why; UNSCOPED_INFO("Tailslide compiles it: " + why); - REQUIRE(ALScriptWeigh::tailslideLSO(text, ours, why)); + CHECK(ALScriptWeigh::tailslideLSO(text, ours, why)); const ALLSLReference::Result ll = ALLSLReference::compile(text, Target::LSO); - UNSCOPED_INFO("LL's compiles it"); CHECK(ll.ok); - UNSCOPED_INFO("the same image"); CHECK(ours == ll.image); } } From f5f03ef1aa1e02d8582a3cbb00038329c9b4aea5 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 01:41:36 -0400 Subject: [PATCH 6/7] An LSL service that loads builtins the process already holds has them Tailslide's builtins table is the process's and only added to, so ALLSLService::loadBuiltins, after the first load, loads only the names a file adds. Where it added none -- the same file again, in a second service -- it returned before marking the service as having builtins, and hasBuiltins() said false for a load that succeeded. Nothing in the viewer asks hasBuiltins(); the optimizer and the weigher ask the process-wide builtinsLoaded(). But allslservice's first case asserted it, and so passed only as the process's first load: in one process with the other alscript tests, it failed. The service is marked as soon as its file opens. A new allslservice case loads the grid's file into a second service, and fails without the change. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/lsl/allslservice.cpp | 4 +++- indra/alscript/tests/allslservice_test.cpp | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/indra/alscript/lsl/allslservice.cpp b/indra/alscript/lsl/allslservice.cpp index 95a75403d5..2d14b5b0c7 100644 --- a/indra/alscript/lsl/allslservice.cpp +++ b/indra/alscript/lsl/allslservice.cpp @@ -1359,6 +1359,9 @@ bool ALLSLService::loadBuiltins(const std::string& path, std::string& error) added += '\n'; } } + // This service has them, whatever the table already held: a file + // all of whose names an earlier one loaded is loaded all the same. + mImpl->builtins = true; error.clear(); if (added.empty()) { @@ -1392,7 +1395,6 @@ bool ALLSLService::loadBuiltins(const std::string& path, std::string& error) const int index = Luau::lslEventIndex(symbol->getName()); symbol->setEventIndex(index > 0 ? index : ++last); } - mImpl->builtins = true; return true; } // Read here and handed over as text: a path Tailslide opened itself diff --git a/indra/alscript/tests/allslservice_test.cpp b/indra/alscript/tests/allslservice_test.cpp index 10291907b8..9268fd8310 100644 --- a/indra/alscript/tests/allslservice_test.cpp +++ b/indra/alscript/tests/allslservice_test.cpp @@ -88,6 +88,19 @@ namespace CHECK(service.hasBuiltins()); } + TEST_CASE_METHOD(allslservice_data, "allslservice: a second service loading the same builtins has them too", "[allslservice]") + { + // Every name it declares is in Tailslide's table already, so none + // is loaded again; the service has them all the same. + REQUIRE(loaded); + ALLSLService again; + std::string why; + const bool read = again.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", why); + CAPTURE(why); + CHECK(read); + CHECK(again.hasBuiltins()); + } + TEST_CASE_METHOD(allslservice_data, "allslservice: a script that is right checks clean", "[allslservice]") { UNSCOPED_INFO("builtins load: " + error); From 7312a9e90273779ef51ae1546165a7c313c87d7c Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 02:03:17 -0400 Subject: [PATCH 7/7] alscript's tests start as the viewer does, and the state guard watches the script engines A listener in every binary of alscript's tests, alscript_test_checks, sets the process up once before the first case, as the viewer has it before it checks a script: - ALLuauService::setUpProcess(): Luau's flags as the grid's SLua needs them. A .config.luau run before any service was made saw no integer library, so alluauconfig's cases depended on whether another file had made one first; - the grid's builtins, loaded first, so every builtins file a test loads after them only adds to them, in whatever order the files run. The state guard checks, after every case: - Luau's flags, one a line, so a case that flips one shows which; - that no thread is left inside Tailslide, where the next case's would count as a second thread in at once, as allsloptimizer's clash count assumes there is none; - how many names Tailslide's builtins table holds. allslservice's three cases that load files of their own tell the guard: the table is the process's and only added to, so what they load stays. alscript_tests and lscript_tests take llcommon's checks too. I broke a case each way, and each check named it. The plan expected alpreprocessor 39 and 40 and alscriptweight to need this; each passes alone already, through alpreprocessor's own builtins helper and the weigher's own setUpProcess(). alscript_tests, run whole, passes 10 random orders and 20 repeats with no guard report, and so do lscript_tests and the six new-solver files together under the new solver. Co-Authored-By: Claude Opus 5.5 --- indra/alscript/CMakeLists.txt | 12 ++ indra/alscript/tests/allslservice_test.cpp | 11 ++ indra/alscript/tests/alscript_test_checks.cpp | 118 ++++++++++++++++++ indra/lscript/CMakeLists.txt | 1 + 4 files changed, 142 insertions(+) create mode 100644 indra/alscript/tests/alscript_test_checks.cpp diff --git a/indra/alscript/CMakeLists.txt b/indra/alscript/CMakeLists.txt index a96651b029..e6706a1eca 100644 --- a/indra/alscript/CMakeLists.txt +++ b/indra/alscript/CMakeLists.txt @@ -237,6 +237,17 @@ if(AL_BUILD_TESTS) templates_define "AL_SCRIPT_TEMPLATES_DIR=\"${INDRA_SOURCE_DIR}/newview/app_settings/script_templates\"" ) + # What every binary of alscript's tests sets up once, as the viewer has it + # before it checks a script, and the state guard's checks on what the + # script engines keep for the process. + add_library(alscript_test_checks OBJECT tests/alscript_test_checks.cpp) + target_link_libraries( + alscript_test_checks + PRIVATE al::flags al_test_support alscript llcommon ll::tailslide + ) + target_compile_definitions(alscript_test_checks PRIVATE ${definitions_defines}) + set_target_properties(alscript_test_checks PROPERTIES FOLDER "Tests/alscript") + # One Catch2 binary for alscript's tests, each test file run in a process # of its own. The SLua checks, and LSL written again as SLua, run again # with Luau's new type solver, which a scripter may choose @@ -288,6 +299,7 @@ if(AL_BUILD_TESTS) tests/alscriptweight_test.cpp tests/alsourcemap_test.cpp tests/aluploadheader_test.cpp + SUPPORT alscript_test_checks llcommon_test_checks LIBRARIES alscript llcommon ll::slua ll::tailslide DEFINES ${definitions_defines} diff --git a/indra/alscript/tests/allslservice_test.cpp b/indra/alscript/tests/allslservice_test.cpp index 9268fd8310..667f12bdf2 100644 --- a/indra/alscript/tests/allslservice_test.cpp +++ b/indra/alscript/tests/allslservice_test.cpp @@ -27,6 +27,8 @@ #include "../lsl/allslservice.h" #include "../lint/alscriptfixes.h" +#include "alteststateguard.h" + #include #include "llsdserialize.h" @@ -792,6 +794,9 @@ namespace } std::string why; const bool read = 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"); std::error_code gone; std::filesystem::remove(fsyspath(path), gone); UNSCOPED_INFO("loaded: " + why); @@ -823,6 +828,9 @@ namespace } std::string why; const bool read = 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"); std::error_code gone; std::filesystem::remove_all(dir, gone); UNSCOPED_INFO("loaded: " + why); @@ -930,6 +938,9 @@ namespace out << text << "integer llBrandNewThing( integer Value )\n"; } const bool loaded = service.loadBuiltins(path, error); + // 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); diff --git a/indra/alscript/tests/alscript_test_checks.cpp b/indra/alscript/tests/alscript_test_checks.cpp new file mode 100644 index 0000000000..2783173505 --- /dev/null +++ b/indra/alscript/tests/alscript_test_checks.cpp @@ -0,0 +1,118 @@ +/** + * @file alscript_test_checks.cpp + * @brief What every binary of alscript's tests sets up for the process once, + * as the viewer has it before any script is checked, and the state + * guard's checks on what the script engines keep for the process. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "../core/alscriptengine.h" +#include "../lsl/allslservice.h" +#include "../luau/alluauservice.h" + +#include "alteststateguard.h" + +#include "Luau/Common.h" + +#include + +#include +#include + +#include +#include +#include + +namespace +{ + // Every one of Luau's flags, name=value a line, sorted. + std::string luauFlags() + { + std::vector lines; + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + lines.push_back(std::string(flag->name) + "=" + (flag->value ? "true" : "false")); + } + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + lines.push_back(std::string(flag->name) + "=" + std::to_string(flag->value)); + } + std::sort(lines.begin(), lines.end()); + std::string text; + for (const std::string& line : lines) + { + text += line + "\n"; + } + return text; + } + + // Whether a thread is still inside Tailslide. + std::string engineOwner() + { + return al_script_engine::owner().load() == std::thread::id() ? "free" : "held by a thread"; + } + + // How many names Tailslide's builtins table holds: one table for the + // process, only ever added to. + std::string builtinCount() + { + AL_SCRIPT_ENGINE_HELD; + Tailslide::ScopedScriptParser probe(nullptr); + Tailslide::LSLSymbolTable* table = probe.context.builtins; + return table ? std::to_string(table->getMap().size()) : std::string("none"); + } + + class ALScriptChecks : public Catch::EventListenerBase + { + public: + using Catch::EventListenerBase::EventListenerBase; + + void testRunStarting(Catch::TestRunInfo const&) override + { + // The process as the viewer has it before it checks a script: + // Luau's flags set as the grid's SLua needs them, which a + // .config.luau run before any service was made would not see; + // and the grid's builtins loaded first, so that every file of + // builtins a test loads after them only adds to them, whatever + // order the test files run in. + ALLuauService::setUpProcess(); + ALLSLService lsl; + std::string error; + if (!lsl.loadBuiltins(std::string(AL_LSL_DEFINITIONS_DIR) + "/builtins.txt", error)) + { + LL_WARNS() << "The grid's builtins did not load: " << error << LL_ENDL; + } + + // A case flips a flag of Luau's only for itself. + ll_test::StateGuard::add("Luau's flags", luauFlags); + // A case leaves no thread inside Tailslide, where the next + // case's would count as a second thread in at once. + ll_test::StateGuard::add("Tailslide's engine", engineOwner); + // A case that loads builtins of its own says so: they stay for + // every case after it. + ll_test::StateGuard::add("Tailslide's builtins", builtinCount); + } + }; +} + +CATCH_REGISTER_LISTENER(ALScriptChecks) diff --git a/indra/lscript/CMakeLists.txt b/indra/lscript/CMakeLists.txt index 97957452eb..78f731c67f 100755 --- a/indra/lscript/CMakeLists.txt +++ b/indra/lscript/CMakeLists.txt @@ -180,6 +180,7 @@ al_add_test_binary( lscript_tests PROJECT lscript SOURCES tests/allslreference_test.cpp tests/allslreferencecheck_test.cpp + SUPPORT alscript_test_checks llcommon_test_checks LIBRARIES lscript alscript llcommon DEFINES "AL_LSL_DEFINITIONS_DIR=\"${LSL_DEFINITIONS_DIR}\""