diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index d9724dbb60..24ddb265ad 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -295,89 +295,100 @@ target_link_libraries( ) if(AL_BUILD_TESTS) - # No unit tests: the harness itself links llcommon, so a test of one of its - # files could not stand alone. Every test here links the library. - foreach( - test - alfilewrite - alrecoverystore - alregex - alsavehistory - alserialworker - alwatchedfile - apply - bitpack - classic_callback - commonmisc - lazyeventapi - llapp - llbase64 - llcond - lldate - lldeadmantimer - lldependencies - lldoubledispatch - llerror - lleventcoro - lleventdispatcher - lleventfilter - llevents - llfile - llframetimer - llhttpdate - llheteromap - llinitparam - llinstancetracker - llmainthreadtask - llmutex - llpounceable - llprocessor - llprocinfo - llptrto - llrand - llsd - llsdjson - llsdutil - llsingleton - llstreamtools - llstring - llstring_utf - lltimer - lltrace - lltreeiterators - llunits - lluri - lluuid - stringize - threadsafeschedule - tuple - workqueue + # The state guard's checks on what llcommon keeps for the process, and the + # event pumps' end after every case, in every binary of llcommon's tests + # that shares a process between files. + add_library(llcommon_test_checks OBJECT tests/llcommon_test_checks.cpp) + target_link_libraries(llcommon_test_checks PRIVATE al::flags al_test_support llcommon) + set_target_properties(llcommon_test_checks PROPERTIES FOLDER "Tests/llcommon") + + # One Catch2 binary for llcommon's tests, each test file run in a process + # of its own. None of them may delete every singleton: the files will + # share a process. + al_add_test_binary( + llcommon_tests + PROJECT llcommon + SOURCES + tests/alfilewrite_test.cpp + tests/alrecoverystore_test.cpp + tests/alregex_test.cpp + tests/alsavehistory_test.cpp + tests/alserialworker_test.cpp + tests/alsimd_test.cpp + tests/alwatchedfile_test.cpp + tests/apply_test.cpp + tests/bitpack_test.cpp + tests/classic_callback_test.cpp + tests/commonmisc_test.cpp + tests/lazyeventapi_test.cpp + tests/llbase64_test.cpp + tests/llcond_test.cpp + tests/lldate_test.cpp + tests/lldeadmantimer_test.cpp + tests/lldependencies_test.cpp + tests/lldoubledispatch_test.cpp + tests/llerror_test.cpp + tests/lleventcoro_test.cpp + tests/lleventdispatcher_test.cpp + tests/lleventfilter_test.cpp + tests/llevents_test.cpp + tests/llfile_test.cpp + tests/llframetimer_test.cpp + tests/llheteromap_test.cpp + tests/llhttpdate_test.cpp + tests/llinitparam_test.cpp + tests/llinstancetracker_test.cpp + tests/llmainthreadtask_test.cpp + tests/llmutex_test.cpp + tests/llpounceable_test.cpp + tests/llprocessor_test.cpp + tests/llprocinfo_test.cpp + tests/llptrto_test.cpp + tests/llrand_test.cpp + tests/llsd_test.cpp + tests/llsdjson_test.cpp + tests/llsdserialize_test.cpp + tests/llsdutil_test.cpp + tests/llstreamtools_test.cpp + tests/llstring_test.cpp + tests/llstring_utf_test.cpp + tests/lltimer_test.cpp + tests/lltrace_test.cpp + tests/lltreeiterators_test.cpp + tests/llunits_test.cpp + tests/lluri_test.cpp + tests/lluuid_test.cpp + tests/stringize_test.cpp + tests/threadsafeschedule_test.cpp + tests/tuple_test.cpp + tests/workqueue_test.cpp + SUPPORT llcommon_test_checks + LIBRARIES llcommon ) - al_add_test(${test} PROJECT llcommon LIBRARIES llcommon) - endforeach() # These drive a Python peer: LEAP's protocol is to an external script, - # llprocess spawns one, and llsdserialize round-trips through the llsd - # package as an independent implementation. A peer that imports a script a - # test wrote to the temp folder would leave its bytecode there. - foreach(test llleap llprocess llsdserialize) - al_add_test( - ${test} - PROJECT llcommon - LIBRARIES llcommon - PYTHON - ENVIRONMENT PYTHONDONTWRITEBYTECODE=1 - ) - endforeach() + # llprocess spawns one, and llsdserialize_python round-trips through the + # llsd package as an independent implementation. A peer that imports a + # script a test wrote to the temp folder would leave its bytecode there. + al_add_test_binary( + llcommon_python_tests + PROJECT llcommon + SOURCES tests/llleap_test.cpp tests/llprocess_test.cpp tests/llsdserialize_python_test.cpp + SUPPORT llcommon_test_checks + LIBRARIES llcommon + PYTHON + ENVIRONMENT PYTHONDONTWRITEBYTECODE=1 + ) - # The SIMD ops layer, at the tree's tier and, where the tiers differ, at - # each of them; a variant the host cannot run skips. - al_add_test(alsimd PROJECT llcommon LIBRARIES llcommon) - if(BUILD_TARGET_IS_X86_64 AND NOT DARWIN) - foreach(tier baseline v2 v3 v4) - al_add_test(alsimd PROJECT llcommon LIBRARIES llcommon ISA_TIER ${tier}) - endforeach() - endif() + # Alone: llsingleton's tests delete every singleton, and llapp's make and + # destroy the process's LLApp, by design. + al_add_test_binary( + llsingleton_test + PROJECT llcommon + SOURCES tests/llsingleton_test.cpp + LIBRARIES llcommon + ) + al_add_test_binary(llapp_test PROJECT llcommon SOURCES tests/llapp_test.cpp LIBRARIES llcommon) # The ops layer's assembly, one function per op, compiled with the tree's # flags and read by AlSimdAsmCheck.cmake for a table lookup, a call or a diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index df82276cd2..acbabdcede 100644 --- a/indra/llcommon/lluri.cpp +++ b/indra/llcommon/lluri.cpp @@ -752,7 +752,7 @@ std::string LLURI::mapToQueryString(const LLSD& queryMap) return query_string; } -bool operator!=(const LLURI& first, const LLURI& second) +bool operator==(const LLURI& first, const LLURI& second) { - return (first.asString() != second.asString()); + return first.asString() == second.asString(); } diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index 37ee0a0ac7..c6e881e2fd 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -187,7 +187,7 @@ class LL_COMMON_API LLURI std::string mEscapedQuery; }; -// this operator required for tut -LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second); +// Two URIs are equal when their strings are. +LL_COMMON_API bool operator==(const LLURI& first, const LLURI& second); #endif // LL_LLURI_H diff --git a/indra/llcommon/tests/alfilewrite_test.cpp b/indra/llcommon/tests/alfilewrite_test.cpp index 88c9aa61ea..1ac1e58096 100644 --- a/indra/llcommon/tests/alfilewrite_test.cpp +++ b/indra/llcommon/tests/alfilewrite_test.cpp @@ -30,7 +30,7 @@ #include "../llfile.h" #include "../lluuid.h" -#include "../test/lltut.h" +#include #include #include @@ -41,7 +41,7 @@ #include #endif -namespace tut +namespace { namespace fs = std::filesystem; @@ -94,99 +94,92 @@ namespace tut } }; - typedef test_group alfilewrite_group; - typedef alfilewrite_group::object alfilewrite_object; - alfilewrite_group alfilewrite_instance("alfilewrite"); - - template<> template<> - void alfilewrite_object::test<1>() + TEST_CASE_METHOD(alfilewrite_data, "alfilewrite: whole writes a new file or over an old and leaves nothing beside it", "[alfilewrite]") { - set_test_name("a file is written whole, a new one or over an old, and nothing is left beside it"); const std::string file = in("script.lsl"); - ensure("a new file", ALFileWrite::whole(file, "default {}\n")); - ensure_equals("its text", read(file), std::string("default {}\n")); - ensure("over it", ALFileWrite::whole(file, "x")); - ensure_equals("the new text, all of the old gone", read(file), std::string("x")); - ensure("empty is a text too", ALFileWrite::whole(file, "")); - ensure_equals("and the file is empty", read(file), std::string()); + CHECK(ALFileWrite::whole(file, "default {}\n")); + CHECK(read(file) == std::string("default {}\n")); + CHECK(ALFileWrite::whole(file, "x")); + UNSCOPED_INFO("the new text, all of the old gone"); + CHECK(read(file) == std::string("x")); + UNSCOPED_INFO("empty is a text too"); + CHECK(ALFileWrite::whole(file, "")); + CHECK(read(file) == std::string()); std::error_code ec; - ensure("nothing left beside it", !fs::exists(fsyspath(ALFileWrite::besideOf(file)), ec)); + CHECK_FALSE(fs::exists(fsyspath(ALFileWrite::besideOf(file)), ec)); } - template<> template<> - void alfilewrite_object::test<2>() + TEST_CASE_METHOD(alfilewrite_data, "alfilewrite: a file that could not be written in place is left as it was", "[alfilewrite]") { - set_test_name("a file that could not be written in place is left as it was"); if (root()) { - skip("root writes what it likes"); + SKIP("root writes what it likes"); } const std::string file = in("readonly.lsl"); put(file, "what was there"); std::error_code ec; fs::permissions(fsyspath(file), fs::perms::owner_read, fs::perm_options::replace, ec); - ensure("refused", !ALFileWrite::whole(file, "new text")); - ensure_equals("the old text, whole", read(file), std::string("what was there")); - ensure("nothing left beside it", !fs::exists(fsyspath(ALFileWrite::besideOf(file)), ec)); + CHECK_FALSE(ALFileWrite::whole(file, "new text")); + UNSCOPED_INFO("the old text, whole"); + CHECK(read(file) == std::string("what was there")); + CHECK_FALSE(fs::exists(fsyspath(ALFileWrite::besideOf(file)), ec)); } - template<> template<> - void alfilewrite_object::test<3>() + TEST_CASE_METHOD(alfilewrite_data, "alfilewrite: a file with no room beside it is written in place and the folder stays", "[alfilewrite]") { - set_test_name("where nothing can be put beside a file, it is written in place, and what is there by that name stays"); const std::string file = in("keep.lsl"); put(file, "what was there"); // Where the text would go beside it is a folder, which no file can // be written as. std::error_code ec; fs::create_directories(fsyspath(ALFileWrite::besideOf(file)), ec); - ensure("written", ALFileWrite::whole(file, "new text")); - ensure_equals("in place", read(file), std::string("new text")); - ensure("the folder by that name left", fs::is_directory(fsyspath(ALFileWrite::besideOf(file)), ec)); + CHECK(ALFileWrite::whole(file, "new text")); + UNSCOPED_INFO("in place"); + CHECK(read(file) == std::string("new text")); + CHECK(fs::is_directory(fsyspath(ALFileWrite::besideOf(file)), ec)); } - template<> template<> - void alfilewrite_object::test<4>() + TEST_CASE_METHOD(alfilewrite_data, "alfilewrite: a file keeps its permissions and a link is written through", "[alfilewrite]") { - set_test_name("a file keeps its permissions, and a link is written through and stays a link"); #if LL_WINDOWS - skip("permissions and links are the Posix file systems' to test"); + SKIP("permissions and links are the Posix file systems' to test"); #else const std::string file = in("mine.lsl"); put(file, "old"); std::error_code ec; fs::permissions(fsyspath(file), fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace, ec); - ensure("written", ALFileWrite::whole(file, "new")); - ensure_equals("the new text", read(file), std::string("new")); - ensure("its permissions as they were", fs::status(fsyspath(file)).permissions() == (fs::perms::owner_read | fs::perms::owner_write)); + CHECK(ALFileWrite::whole(file, "new")); + CHECK(read(file) == std::string("new")); + UNSCOPED_INFO("its permissions as they were"); + CHECK(fs::status(fsyspath(file)).permissions() == (fs::perms::owner_read | fs::perms::owner_write)); const std::string link = in("link.lsl"); fs::create_symlink(fsyspath(file), fsyspath(link), ec); if (ec) { - skip("no links here"); + SKIP("no links here"); } - ensure("through the link", ALFileWrite::whole(link, "through")); - ensure("still a link", fs::is_symlink(fsyspath(link))); - ensure_equals("the file it names written", read(file), std::string("through")); + CHECK(ALFileWrite::whole(link, "through")); + CHECK(fs::is_symlink(fsyspath(link))); + UNSCOPED_INFO("the file it names written"); + CHECK(read(file) == std::string("through")); const std::string dangling = in("dangling.lsl"); fs::create_symlink(fsyspath(in("nowhere.lsl")), fsyspath(dangling), ec); - ensure("a link to nothing has nowhere to write", !ALFileWrite::whole(dangling, "lost")); - ensure("and is left a link", fs::is_symlink(fsyspath(dangling))); + UNSCOPED_INFO("a link to nothing has nowhere to write"); + CHECK_FALSE(ALFileWrite::whole(dangling, "lost")); + CHECK(fs::is_symlink(fsyspath(dangling))); #endif } - template<> template<> - void alfilewrite_object::test<5>() + TEST_CASE_METHOD(alfilewrite_data, "alfilewrite: a file in a folder only its files may be written in is written in place", "[alfilewrite]") { - set_test_name("a file in a folder only its files may be written in is written in place"); #if LL_WINDOWS - skip("a folder's permissions are the Posix file systems' to test"); + SKIP("a folder's permissions are the Posix file systems' to test"); #else if (root()) { - skip("root writes what it likes"); + SKIP("root writes what it likes"); } const std::string sub = in("closed"); std::error_code ec; @@ -196,59 +189,66 @@ namespace tut fs::permissions(fsyspath(sub), fs::perms::owner_read | fs::perms::owner_exec, fs::perm_options::replace, ec); const bool written = ALFileWrite::whole(file, "new"); fs::permissions(fsyspath(sub), fs::perms::owner_all, fs::perm_options::replace, ec); - ensure("written", written); - ensure_equals("in place", read(file), std::string("new")); + CHECK(written); + UNSCOPED_INFO("in place"); + CHECK(read(file) == std::string("new")); #endif } - template<> template<> - void alfilewrite_object::test<6>() + TEST_CASE_METHOD(alfilewrite_data, "alfilewrite: a durable write is whole or nothing and never in place", "[alfilewrite]") { - set_test_name("a durable write is whole or nothing: where nothing can be put beside the file, it is not written in place"); const std::string file = in("journal.llsd"); - ensure("a new one", ALFileWrite::whole(file, "one", /*durable*/ true)); - ensure("over it", ALFileWrite::whole(file, "two", /*durable*/ true)); - ensure_equals("the new text", read(file), std::string("two")); + CHECK(ALFileWrite::whole(file, "one", /*durable*/ true)); + CHECK(ALFileWrite::whole(file, "two", /*durable*/ true)); + CHECK(read(file) == std::string("two")); std::error_code ec; - ensure("nothing left beside it", !fs::exists(fsyspath(ALFileWrite::besideOf(file)), ec)); + REQUIRE_FALSE(fs::exists(fsyspath(ALFileWrite::besideOf(file)), ec)); fs::create_directories(fsyspath(ALFileWrite::besideOf(file)), ec); - ensure("refused", !ALFileWrite::whole(file, "three", /*durable*/ true)); - ensure_equals("the old text, whole", read(file), std::string("two")); + CHECK_FALSE(ALFileWrite::whole(file, "three", /*durable*/ true)); + UNSCOPED_INFO("the old text, whole"); + CHECK(read(file) == std::string("two")); } - template<> template<> - void alfilewrite_object::test<7>() + TEST_CASE_METHOD(alfilewrite_data, "alfilewrite: ALFileRead reads an ordinary file up to a limit and no folder or pipe", "[alfilewrite]") { - set_test_name("the one reader: an ordinary file whole, up to a limit; not a folder, and not a pipe, which it does not wait on"); const std::string file = in("include.lsl"); put(file, "integer x;\n"); std::string text = "left over"; - ensure("read", ALFileRead::whole(file, text, 64)); - ensure_equals("whole", text, std::string("integer x;\n")); - ensure("at the limit", ALFileRead::whole(file, text, 11) && text.size() == 11); - ensure("past it", !ALFileRead::whole(file, text, 10) && text.empty()); - ensure("a folder is not read", !ALFileRead::whole(folder, text, 64)); - ensure("nothing is not read", !ALFileRead::whole(in("gone.lsl"), text, 64)); + CHECK(ALFileRead::whole(file, text, 64)); + CHECK(text == std::string("integer x;\n")); + { + INFO("at the limit"); + CHECK(ALFileRead::whole(file, text, 11)); + REQUIRE(text.size() == 11); + } + { + INFO("past it"); + CHECK_FALSE(ALFileRead::whole(file, text, 10)); + REQUIRE(text.empty()); + } + CHECK_FALSE(ALFileRead::whole(folder, text, 64)); + UNSCOPED_INFO("a missing file is not read"); + CHECK_FALSE(ALFileRead::whole(in("gone.lsl"), text, 64)); #if !LL_WINDOWS const std::string pipe = in("pipe"); if (mkfifo(pipe.c_str(), 0600) == 0) { - ensure("a pipe is not read", !ALFileRead::whole(pipe, text, 64)); + CHECK_FALSE(ALFileRead::whole(pipe, text, 64)); } #endif } - template<> template<> - void alfilewrite_object::test<8>() + TEST_CASE_METHOD(alfilewrite_data, "alfilewrite: temp writes in place for the user alone and never through a link", "[alfilewrite]") { - set_test_name("a temp file is written in place, the user's alone, and never through a link"); const std::string file = in("sl_script_x_id.lsl"); - ensure("written", ALFileWrite::temp(file, "default {}")); - ensure_equals("its text", read(file), std::string("default {}")); - ensure("again, shorter", ALFileWrite::temp(file, "x")); - ensure_equals("all of the old gone", read(file), std::string("x")); + CHECK(ALFileWrite::temp(file, "default {}")); + CHECK(read(file) == std::string("default {}")); + CHECK(ALFileWrite::temp(file, "x")); + UNSCOPED_INFO("all of the old gone"); + CHECK(read(file) == std::string("x")); #if !LL_WINDOWS - ensure("the user's alone", fs::status(fsyspath(file)).permissions() == (fs::perms::owner_read | fs::perms::owner_write)); + UNSCOPED_INFO("the user's alone"); + CHECK(fs::status(fsyspath(file)).permissions() == (fs::perms::owner_read | fs::perms::owner_write)); const std::string target = in("elsewhere.txt"); put(target, "somebody's"); const std::string link = in("sl_script_y_id.lsl"); @@ -256,8 +256,9 @@ namespace tut fs::create_symlink(fsyspath(target), fsyspath(link), ec); if (!ec) { - ensure("not through a link", !ALFileWrite::temp(link, "mine")); - ensure_equals("what it names untouched", read(target), std::string("somebody's")); + CHECK_FALSE(ALFileWrite::temp(link, "mine")); + UNSCOPED_INFO("what it names untouched"); + CHECK(read(target) == std::string("somebody's")); } #endif } diff --git a/indra/llcommon/tests/alrecoverystore_test.cpp b/indra/llcommon/tests/alrecoverystore_test.cpp index 015d06ee43..b120767e53 100644 --- a/indra/llcommon/tests/alrecoverystore_test.cpp +++ b/indra/llcommon/tests/alrecoverystore_test.cpp @@ -31,15 +31,34 @@ #include "llsdserialize.h" #include "lltimer.h" -#include "../test/lltut.h" +#include +#include "alteststateguard.h" +#include "altestteardown.h" #include #include #include #include -namespace tut +namespace { + // The stores write on one thread for the process, ALRecoveryWriter's, + // which the viewer stops in its cleanup. Here it is made once, before the + // first case that writes would make it, and stopped by the harness's + // teardown: that it then runs is no change a case made. + void makeRecoveryWriter() + { + static bool sMade = false; + if (!sMade) + { + sMade = true; + ALRecoveryWriter::getInstance(); + ll_test::Teardown::add([] { ALRecoveryWriter::deleteSingleton(); }); + ll_test::StateGuard::accept("the live thread pools"); + ll_test::StateGuard::accept("the live work queues"); + } + } + struct alrecoverystore_data { // A folder of its own for each test, gone after it. @@ -47,6 +66,7 @@ namespace tut alrecoverystore_data() { + makeRecoveryWriter(); folder = fsyspath(std::filesystem::temp_directory_path() / fsyspath("alrecoverystore_" + LLUUID::generateNewID().asString())).string(); std::error_code ec; std::filesystem::create_directories(fsyspath(folder), ec); @@ -75,19 +95,21 @@ namespace tut // Entries as a listing reads them, read whole: what they say of // their text is only there then. - static std::vector whole(const ALRecoveryStore& store, std::vector entries) + static std::vector require_whole(const ALRecoveryStore& store, std::vector entries) { for (ALRecoveryEntry& one : entries) { - ensure("read whole: " + one.key, store.load(one)); + CAPTURE(one.key); + REQUIRE(store.load(one)); } return entries; } - static std::optional whole(const ALRecoveryStore& store, std::optional one) + static std::optional require_whole(const ALRecoveryStore& store, std::optional one) { if (one) { - ensure("read whole: " + one->key, store.load(*one)); + CAPTURE(one->key); + REQUIRE(store.load(*one)); } return one; } @@ -101,28 +123,22 @@ namespace tut return one; } }; - typedef test_group alrecoverystore_group; - typedef alrecoverystore_group::object alrecoverystore_object; - tut::alrecoverystore_group alrecoverystore_instance("alrecoverystore"); - template<> template<> - void alrecoverystore_object::test<1>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: keyOf tells an object's item from the inventory's and a file's", "[alrecoverystore]") { - set_test_name("a key says whose text it is: an object's item, the inventory's, or a file's"); const LLUUID object = LLUUID::generateNewID(); const LLUUID item = LLUUID::generateNewID(); const std::string task = ALRecoveryStore::keyOf(object, item, std::string()); const std::string inventory = ALRecoveryStore::keyOf(LLUUID::null, item, std::string()); const std::string file = ALRecoveryStore::keyOf(LLUUID::null, LLUUID::null, "/scripts/lib.lsl"); - ensure("an object's item and the inventory's differ", task != inventory); - ensure("a file is its path", file == "disk:/scripts/lib.lsl"); - ensure_equals("and a key is the same asked twice", ALRecoveryStore::keyOf(object, item, std::string()), task); + CHECK(task != inventory); + CHECK(file == "disk:/scripts/lib.lsl"); + UNSCOPED_INFO("and a key is the same asked twice"); + CHECK(ALRecoveryStore::keyOf(object, item, std::string()) == task); } - template<> template<> - void alrecoverystore_object::test<2>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: an entry reads back as it was written", "[alrecoverystore]") { - set_test_name("an entry reads back as it was written"); ALRecoveryEntry was = entry("task:a:b", "default\n{\n state_entry() { llSay(0, \"<&>\"); }\n}\n"); was.session = "one"; was.state = ALRecoveryEntry::State::Kept; @@ -137,237 +153,281 @@ namespace tut was.compileTarget = "luau"; was.baseAsset = LLUUID::generateNewID(); ALRecoveryEntry back; - ensure("it reads", ALRecoveryEntry::fromLLSD(was.asLLSD(), back)); - ensure_equals("the text, whatever is in it", back.text, was.text); - ensure("kept", back.state == ALRecoveryEntry::State::Kept); - ensure_equals("when", back.when.secondsSinceEpoch(), was.when.secondsSinceEpoch()); - ensure("the object and the item", back.object == was.object && back.item == was.item); - ensure("where it was", back.objectName == "Door" && back.region == "Ahern"); - ensure("what it is", back.lua && !back.notecard && back.wrapped && back.compileTarget == "luau" && back.baseAsset == was.baseAsset); - ensure("no history said, none read", !back.history.isDefined() && back.caretLine == -1); + REQUIRE(ALRecoveryEntry::fromLLSD(was.asLLSD(), back)); + UNSCOPED_INFO("the text, whatever is in it"); + CHECK(back.text == was.text); + CHECK(back.state == ALRecoveryEntry::State::Kept); + CHECK(back.when.secondsSinceEpoch() == was.when.secondsSinceEpoch()); + CHECK(back.object == was.object); + CHECK(back.item == was.item); + CHECK(back.objectName == "Door"); + CHECK(back.region == "Ahern"); + CHECK(back.lua); + CHECK_FALSE(back.notecard); + CHECK(back.wrapped); + CHECK(back.compileTarget == "luau"); + CHECK(back.baseAsset == was.baseAsset); + { + INFO("no history said, none read"); + REQUIRE_FALSE(back.history.isDefined()); + CHECK(back.caretLine == -1); + } LLSD history; history["undo"] = LLSD::emptyArray().with(0, LLSD().with("label", "rename")); history["saved"] = 0; was.history = history; was.caretLine = 4; was.caretColumn = 7; - ensure("it reads again", ALRecoveryEntry::fromLLSD(was.asLLSD(), back)); - ensure("the history as it was written", back.history["undo"][0]["label"].asString() == "rename" && back.history["saved"].asInteger() == 0); - ensure("and where the caret stood", back.caretLine == 4 && back.caretColumn == 7); - ensure("something that is no entry is not read as one", !ALRecoveryEntry::fromLLSD(LLSD("text"), back)); - } - - template<> template<> - void alrecoverystore_object::test<7>() - { - set_test_name("a notecard keeps its items with its text, in order, through the disk and back"); - ALRecoveryStore store(folder, "mine"); - ALRecoveryEntry card = entry("item:card", std::string("Look: ") + "\xF4\x80\x80\x80" + " and " + "\xF4\x80\x80\x81"); - card.notecard = true; - LLSD first; - first["item_id"] = LLUUID::generateNewID(); - first["name"] = "Sunset"; - LLSD second; - second["item_id"] = LLUUID::generateNewID(); - second["name"] = "Home"; - card.embedded = LLSD::emptyArray().with(0, first).with(1, second); - ensure("written", store.write(card)); - const std::vector all = whole(store, store.list()); - ensure_equals("one", all.size(), size_t(1)); - ensure("a notecard", all.front().notecard); - ensure_equals("its text, the placeholders in it byte for byte", all.front().text, card.text); - ensure_equals("both items", all.front().embedded.size(), 2); - ensure("in the order the text stands them", - all.front().embedded[0]["name"].asString() == "Sunset" && all.front().embedded[1]["name"].asString() == "Home"); - ensure("each whole", all.front().embedded[1]["item_id"].asUUID() == second["item_id"].asUUID()); - // Discarded, it keeps them too. - ensure("discarded", store.discard(all.front())); - const std::vector thrown = whole(store, store.list()); - ensure("the items go with it", thrown.size() == 1 && thrown.front().embedded.size() == 2); - // A script has none, and says so as an empty list. - ALRecoveryEntry script; - ensure("read", ALRecoveryEntry::fromLLSD(entry("item:s", "x").asLLSD(), script)); - ensure("no items", script.embedded.isArray() && script.embedded.size() == 0); + CHECK(ALRecoveryEntry::fromLLSD(was.asLLSD(), back)); + CHECK(back.history["undo"][0]["label"].asString() == "rename"); + CHECK(back.history["saved"].asInteger() == 0); + CHECK(back.caretLine == 4); + CHECK(back.caretColumn == 7); + UNSCOPED_INFO("something that is no entry is not read as one"); + CHECK_FALSE(ALRecoveryEntry::fromLLSD(LLSD("text"), back)); } - template<> template<> - void alrecoverystore_object::test<3>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a session's entry is written whole then over by its next and forgotten", "[alrecoverystore]") { - set_test_name("a session's entry is written whole, written over by its next, and forgotten"); ALRecoveryStore store(folder, "session-a"); - ensure("written", store.write(entry("item:x", "first"))); - ensure("written again", store.write(entry("item:x", "second"))); - const std::vector all = whole(store, store.list()); - ensure_equals("one entry for one key", all.size(), size_t(1)); - ensure_equals("the last text", all.front().text, std::string("second")); - ensure_equals("this session's", all.front().session, std::string("session-a")); - ensure("unsaved", all.front().state == ALRecoveryEntry::State::Unsaved); + CHECK(store.write(entry("item:x", "first"))); + CHECK(store.write(entry("item:x", "second"))); + const std::vector all = require_whole(store, store.list()); + UNSCOPED_INFO("one entry for one key"); + REQUIRE(all.size() == size_t(1)); + CHECK(all.front().text == std::string("second")); + CHECK(all.front().session == std::string("session-a")); + CHECK(all.front().state == ALRecoveryEntry::State::Unsaved); for (const std::string& name : files()) { - ensure("nothing half written left beside it: " + name, name.find(".tmp") == std::string::npos); + CAPTURE(name); + REQUIRE(name.find(".tmp") == std::string::npos); } - ensure("this session's own is nothing to offer", !store.hasOffers()); - ensure("nor left by another", store.left().empty()); + UNSCOPED_INFO("this session's own is nothing to offer"); + CHECK_FALSE(store.hasOffers()); + REQUIRE(store.left().empty()); store.forget("item:x"); - ensure("forgotten", store.list().empty()); + REQUIRE(store.list().empty()); } - template<> template<> - void alrecoverystore_object::test<4>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: typing in a script another session left keeps what it left", "[alrecoverystore]") { - set_test_name("one session's entries are apart from another's: typing in a script another session left does not write over what it left"); ALRecoveryStore before(folder, "before"); ALRecoveryStore now(folder, "now"); - ensure("the crashed session's", before.write(entry("task:o:i", "left behind"))); - ensure("this session's", now.write(entry("task:o:i", "typed since"))); - ensure_equals("both kept", now.list().size(), size_t(2)); - const std::vector left = whole(now, now.left()); - ensure_equals("one left by another session", left.size(), size_t(1)); - ensure_equals("its text", left.front().text, std::string("left behind")); - const std::optional found = whole(now, now.leftFor("task:o:i")); - ensure("found by its key", found.has_value() && found->text == "left behind"); - ensure("nothing for another key", !now.leftFor("task:o:j").has_value()); - ensure("something to offer", now.hasOffers()); + UNSCOPED_INFO("the crashed session's"); + CHECK(before.write(entry("task:o:i", "left behind"))); + CHECK(now.write(entry("task:o:i", "typed since"))); + UNSCOPED_INFO("both kept"); + REQUIRE(now.list().size() == size_t(2)); + const std::vector left = require_whole(now, now.left()); + REQUIRE(left.size() == size_t(1)); + CHECK(left.front().text == std::string("left behind")); + const std::optional found = require_whole(now, now.leftFor("task:o:i")); + REQUIRE(found.has_value()); + CHECK(found->text == "left behind"); + REQUIRE_FALSE(now.leftFor("task:o:j").has_value()); + CHECK(now.hasOffers()); now.forget("task:o:i"); - ensure("this session forgetting its own leaves the other's", now.leftFor("task:o:i").has_value()); + UNSCOPED_INFO("this session forgetting its own leaves the other's"); + REQUIRE(now.leftFor("task:o:i").has_value()); now.remove(*found); - ensure("taken up, it is gone", !now.leftFor("task:o:i").has_value() && !now.hasOffers()); + { + INFO("taken up, it is gone"); + REQUIRE_FALSE(now.leftFor("task:o:i").has_value()); + CHECK_FALSE(now.hasOffers()); + } } - template<> template<> - void alrecoverystore_object::test<5>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a discarded entry is pruned once old but an unsaved one never is", "[alrecoverystore]") { - set_test_name("a discarded entry is kept apart a while, then let go of; an unsaved one never is"); ALRecoveryStore store(folder, "mine"); ALRecoveryStore other(folder, "theirs"); - ensure("written", store.write(entry("item:gone", "thrown away"))); - ensure("and another session's", other.write(entry("item:kept", "never thrown away"))); + CHECK(store.write(entry("item:gone", "thrown away"))); + CHECK(other.write(entry("item:kept", "never thrown away"))); { const std::vector written = store.list(); const auto mine = std::find_if(written.begin(), written.end(), [](const ALRecoveryEntry& one) { return one.key == "item:gone"; }); - ensure("discarded", mine != written.end() && store.discard(*mine)); + REQUIRE(mine != written.end()); + CHECK(store.discard(*mine)); } - std::vector all = whole(store, store.list()); - ensure_equals("both still listed", all.size(), size_t(2)); + std::vector all = require_whole(store, store.list()); + UNSCOPED_INFO("both still listed"); + REQUIRE(all.size() == size_t(2)); const auto discarded = std::find_if(all.begin(), all.end(), [](const ALRecoveryEntry& one) { return one.key == "item:gone"; }); - ensure("the discarded one", discarded != all.end() && discarded->state == ALRecoveryEntry::State::Discarded); - ensure_equals("with its text", discarded->text, std::string("thrown away")); - ensure_equals("in the folder beside", files("/discarded").size(), size_t(1)); - ensure("not among what was left unsaved", store.leftFor("item:gone") == std::nullopt); - ensure("but offered", store.hasOffers()); + REQUIRE(discarded != all.end()); + CHECK(discarded->state == ALRecoveryEntry::State::Discarded); + CHECK(discarded->text == std::string("thrown away")); + REQUIRE(files("/discarded").size() == size_t(1)); + UNSCOPED_INFO("not among what was left unsaved"); + CHECK(store.leftFor("item:gone") == std::nullopt); + UNSCOPED_INFO("but offered"); + CHECK(store.hasOffers()); store.prune(60.0, LLDate(LLDate::now().secondsSinceEpoch() + 30.0)); - ensure_equals("young, it stays", files("/discarded").size(), size_t(1)); + UNSCOPED_INFO("young, it stays"); + REQUIRE(files("/discarded").size() == size_t(1)); store.prune(60.0, LLDate(LLDate::now().secondsSinceEpoch() + 120.0)); - ensure("old, it goes", files("/discarded").empty()); - ensure("and the unsaved one does not", store.leftFor("item:kept").has_value()); + UNSCOPED_INFO("old, it goes"); + REQUIRE(files("/discarded").empty()); + UNSCOPED_INFO("and the unsaved one does not"); + REQUIRE(store.leftFor("item:kept").has_value()); // Another session's, discarded from the list of what was left. const std::optional theirs = store.leftFor("item:kept"); - ensure("discarded too", store.discard(*theirs)); - ensure("gone from what was left", !store.leftFor("item:kept").has_value()); - ensure_equals("into the folder beside", files("/discarded").size(), size_t(1)); + CHECK(store.discard(*theirs)); + REQUIRE_FALSE(store.leftFor("item:kept").has_value()); + REQUIRE(files("/discarded").size() == size_t(1)); } - template<> template<> - void alrecoverystore_object::test<6>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a file that cannot be read is passed over and left in place", "[alrecoverystore]") { - set_test_name("a file that cannot be read is passed over and left where it is, for a person to look at"); ALRecoveryStore store(folder, "mine"); - ensure("written", store.write(entry("item:ok", "fine"))); + CHECK(store.write(entry("item:ok", "fine"))); { LLFILE* broken = LLFile::fopen(folder + "/0000.someone.llsd", LLFILE_MODE("wb")); - ensure("made", broken != nullptr); + REQUIRE(broken != nullptr); fputs("texthalf", broken); fclose(broken); } const std::vector all = store.list(); - ensure_equals("the one that reads", all.size(), size_t(1)); - ensure_equals("is the good one", all.front().key, std::string("item:ok")); - ensure("the other is still there", LLFile::isfile(folder + "/0000.someone.llsd")); + UNSCOPED_INFO("the one that reads"); + REQUIRE(all.size() == size_t(1)); + CHECK(all.front().key == std::string("item:ok")); + UNSCOPED_INFO("the other is still there"); + CHECK(LLFile::isfile(folder + "/0000.someone.llsd")); + } + + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a notecard keeps its items in order with its text through the disk", "[alrecoverystore]") + { + ALRecoveryStore store(folder, "mine"); + ALRecoveryEntry card = entry("item:card", std::string("Look: ") + "\xF4\x80\x80\x80" + " and " + "\xF4\x80\x80\x81"); + card.notecard = true; + LLSD first; + first["item_id"] = LLUUID::generateNewID(); + first["name"] = "Sunset"; + LLSD second; + second["item_id"] = LLUUID::generateNewID(); + second["name"] = "Home"; + card.embedded = LLSD::emptyArray().with(0, first).with(1, second); + CHECK(store.write(card)); + const std::vector all = require_whole(store, store.list()); + REQUIRE(all.size() == size_t(1)); + CHECK(all.front().notecard); + UNSCOPED_INFO("its text, the placeholders in it byte for byte"); + CHECK(all.front().text == card.text); + REQUIRE(all.front().embedded.size() == 2); + CHECK(all.front().embedded[0]["name"].asString() == "Sunset"); + CHECK(all.front().embedded[1]["name"].asString() == "Home"); + UNSCOPED_INFO("each whole"); + CHECK(all.front().embedded[1]["item_id"].asUUID() == second["item_id"].asUUID()); + // Discarded, it keeps them too. + CHECK(store.discard(all.front())); + const std::vector thrown = require_whole(store, store.list()); + { + INFO("the items go with it"); + REQUIRE(thrown.size() == 1); + REQUIRE(thrown.front().embedded.size() == 2); + } + // A script has none, and says so as an empty list. + ALRecoveryEntry script; + REQUIRE(ALRecoveryEntry::fromLLSD(entry("item:s", "x").asLLSD(), script)); + REQUIRE(script.embedded.isArray()); + REQUIRE(script.embedded.size() == 0); } - template<> template<> - void alrecoverystore_object::test<8>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: setAside leaves its source; prune sweeps halves; an XML entry reads", "[alrecoverystore]") { - set_test_name("a text set aside leaves what it came from; half-written files and old discards go at a prune; an XML entry still reads"); ALRecoveryStore store(folder, "mine"); ALRecoveryStore other(folder, "theirs"); - ensure("another session's", other.write(entry("item:a", "left"))); - ensure("set aside", store.setAside(entry("item:a", "thrown"))); - ensure_equals("among the discarded", files("/discarded").size(), size_t(1)); - const std::optional left = whole(store, store.leftFor("item:a")); - ensure("what it came from still left", left.has_value() && left->text == "left"); - ensure_equals("and only that is left", store.left().size(), size_t(1)); - const std::vector all = whole(store, store.list()); + CHECK(other.write(entry("item:a", "left"))); + CHECK(store.setAside(entry("item:a", "thrown"))); + REQUIRE(files("/discarded").size() == size_t(1)); + const std::optional left = require_whole(store, store.leftFor("item:a")); + { + INFO("what it came from still left"); + REQUIRE(left.has_value()); + CHECK(left->text == "left"); + } + UNSCOPED_INFO("and only that is left"); + REQUIRE(store.left().size() == size_t(1)); + const std::vector all = require_whole(store, store.list()); const auto thrown = std::find_if(all.begin(), all.end(), [](const ALRecoveryEntry& one) { return one.text == "thrown"; }); - ensure("set aside by this session", thrown != all.end() && thrown->session == "mine" && thrown->state == ALRecoveryEntry::State::Discarded); + REQUIRE(thrown != all.end()); + REQUIRE(thrown->session == "mine"); + CHECK(thrown->state == ALRecoveryEntry::State::Discarded); // Written half by a session that went, and by this one. for (const char* name : { "/0001.theirs.llsd.tmp", "/0002.mine.llsd.tmp" }) { LLFILE* half = LLFile::fopen(folder + name, LLFILE_MODE("wb")); - ensure("made", half != nullptr); + REQUIRE(half != nullptr); fputs("\n{'key':'ite", half); fclose(half); } store.prune(60.0); - ensure("the gone session's half gone", !LLFile::isfile(folder + "/0001.theirs.llsd.tmp")); - ensure("this session's own left", LLFile::isfile(folder + "/0002.mine.llsd.tmp")); - ensure_equals("the young discard stays", files("/discarded").size(), size_t(1)); + UNSCOPED_INFO("the gone session's half gone"); + CHECK_FALSE(LLFile::isfile(folder + "/0001.theirs.llsd.tmp")); + UNSCOPED_INFO("this session's own left"); + CHECK(LLFile::isfile(folder + "/0002.mine.llsd.tmp")); + UNSCOPED_INFO("the young discard stays"); + REQUIRE(files("/discarded").size() == size_t(1)); // An entry as the XML it was once written as. { ALRecoveryEntry was = entry("item:old", "from before"); was.session = "older"; LLFILE* xml = LLFile::fopen(folder + "/0003.older.llsd", LLFILE_MODE("wb")); - ensure("made", xml != nullptr); + REQUIRE(xml != nullptr); std::ostringstream text; LLSDSerialize::toPrettyXML(was.asLLSD(), text); fputs(text.str().c_str(), xml); fclose(xml); } - const std::vector now = whole(store, store.left()); - ensure("read", std::any_of(now.begin(), now.end(), [](const ALRecoveryEntry& one) { return one.text == "from before"; })); + const std::vector now = require_whole(store, store.left()); + CHECK(std::any_of(now.begin(), now.end(), [](const ALRecoveryEntry& one) { return one.text == "from before"; })); } - template<> template<> - void alrecoverystore_object::test<9>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: letGo of a changed tab sets its text aside and drops what it took up", "[alrecoverystore]") { - set_test_name("a changed tab let go of has its text set aside, its own entry forgotten, and the entry it took up let go of"); ALRecoveryStore store(folder, "mine"); ALRecoveryStore other(folder, "theirs"); - ensure("left by another session", other.write(entry("item:x", "left before"))); + CHECK(other.write(entry("item:x", "left before"))); const std::optional took = store.leftFor("item:x"); - ensure("found", took.has_value()); - ensure("kept by this one", store.write(entry("item:x", "typed since"))); + REQUIRE(took.has_value()); + CHECK(store.write(entry("item:x", "typed since"))); ALRecoveryStore::Parting parting; parting.key = "item:x"; parting.unsaved = entry("item:x", "typed since, then closed"); parting.tookUp = took; parting.settled = true; - ensure("let go of", store.letGo(parting)); + CHECK(store.letGo(parting)); - const std::vector all = whole(store, store.list()); - ensure_equals("one entry", all.size(), size_t(1)); - ensure("set aside, as it stood", all.front().state == ALRecoveryEntry::State::Discarded && all.front().text == "typed since, then closed"); - ensure("by this session", all.front().session == "mine"); - ensure("what it took up gone", !store.leftFor("item:x").has_value() && store.left().empty()); + const std::vector all = require_whole(store, store.list()); + REQUIRE(all.size() == size_t(1)); + { + INFO("set aside, as it stood"); + CHECK(all.front().state == ALRecoveryEntry::State::Discarded); + CHECK(all.front().text == "typed since, then closed"); + } + CHECK(all.front().session == "mine"); + { + INFO("what it took up gone"); + REQUIRE_FALSE(store.leftFor("item:x").has_value()); + REQUIRE(store.left().empty()); + } } - template<> template<> - void alrecoverystore_object::test<10>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: where the text cannot be set aside letGo lets go of nothing", "[alrecoverystore]") { - set_test_name("where the text cannot be set aside, nothing is let go of: its own entry and the one it took up both stay"); ALRecoveryStore store(folder, "mine"); ALRecoveryStore other(folder, "theirs"); - ensure("left by another session", other.write(entry("item:x", "left before"))); + CHECK(other.write(entry("item:x", "left before"))); const std::optional took = store.leftFor("item:x"); - ensure("kept by this one", store.write(entry("item:x", "typed since"))); + CHECK(store.write(entry("item:x", "typed since"))); // A file where the folder of the discarded would be: nothing can be // written there. { LLFILE* blocking = LLFile::fopen(folder + "/discarded", LLFILE_MODE("wb")); - ensure("made", blocking != nullptr); + REQUIRE(blocking != nullptr); fclose(blocking); } @@ -376,103 +436,118 @@ namespace tut parting.unsaved = entry("item:x", "typed since, then closed"); parting.tookUp = took; parting.settled = true; - ensure("said to have failed", !store.letGo(parting)); + CHECK_FALSE(store.letGo(parting)); - const std::vector all = whole(store, store.list()); + const std::vector all = require_whole(store, store.list()); const auto own = std::find_if(all.begin(), all.end(), [](const ALRecoveryEntry& one) { return one.session == "mine"; }); - ensure("its own entry still there", own != all.end() && own->text == "typed since"); - ensure("and the one it took up", store.leftFor("item:x").has_value()); - ensure("a set-aside that fails is said", !store.setAside(entry("item:y", "anything"))); + { + INFO("its own entry still there"); + REQUIRE(own != all.end()); + CHECK(own->text == "typed since"); + } + UNSCOPED_INFO("and the one it took up"); + REQUIRE(store.leftFor("item:x").has_value()); + CHECK_FALSE(store.setAside(entry("item:y", "anything"))); } - template<> template<> - void alrecoverystore_object::test<11>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a clean tab drops what it took up only where a save could reach it", "[alrecoverystore]") { - set_test_name("a clean tab forgets its own entry, and lets go of what it took up only where a save could reach its text"); ALRecoveryStore store(folder, "mine"); ALRecoveryStore other(folder, "theirs"); - ensure("left by another session", other.write(entry("item:x", "left before"))); + CHECK(other.write(entry("item:x", "left before"))); // Holding the kept text where nothing can save it -- the script may // no longer be changed, or never loaded: left to be offered again. - ensure("kept by this one", store.write(entry("item:x", "typed since"))); + CHECK(store.write(entry("item:x", "typed since"))); ALRecoveryStore::Parting stuck; stuck.key = "item:x"; stuck.tookUp = store.leftFor("item:x"); stuck.settled = false; - ensure("let go of", store.letGo(stuck)); + CHECK(store.letGo(stuck)); const std::vector after = store.list(); - ensure("its own forgotten", std::none_of(after.begin(), after.end(), [](const ALRecoveryEntry& one) { return one.session == "mine"; })); - ensure("what it took up still offered", store.leftFor("item:x").has_value()); - ensure("nothing set aside", files("/discarded").empty()); + UNSCOPED_INFO("its own forgotten"); + CHECK(std::none_of(after.begin(), after.end(), [](const ALRecoveryEntry& one) { return one.session == "mine"; })); + UNSCOPED_INFO("what it took up still offered"); + REQUIRE(store.leftFor("item:x").has_value()); + UNSCOPED_INFO("nothing set aside"); + REQUIRE(files("/discarded").empty()); // Loaded and clean: the text it took up is what was saved. ALRecoveryStore::Parting clean; clean.key = "item:x"; clean.tookUp = store.leftFor("item:x"); clean.settled = true; - ensure("let go of", store.letGo(clean)); - ensure("what it took up gone", !store.leftFor("item:x").has_value()); - ensure("and nothing set aside", files("/discarded").empty()); + CHECK(store.letGo(clean)); + UNSCOPED_INFO("what it took up gone"); + REQUIRE_FALSE(store.leftFor("item:x").has_value()); + UNSCOPED_INFO("and nothing set aside"); + REQUIRE(files("/discarded").empty()); } - template<> template<> - void alrecoverystore_object::test<12>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a carrying tab sets aside text with no file and leaves an entry on disk", "[alrecoverystore]") { - set_test_name("a tab let go of before what it carried was put in: a text with no file of its own set aside, an entry on disk left as it was"); ALRecoveryStore store(folder, "mine"); ALRecoveryStore other(folder, "theirs"); // Moved from another window, or loading its item under what it held: // the text it carried is the only copy of what was typed, and this // session's entry for it was written by the tab it came from. - ensure("kept by the tab it came from", store.write(entry("item:moved", "typed in the other window"))); + UNSCOPED_INFO("kept by the tab it came from"); + CHECK(store.write(entry("item:moved", "typed in the other window"))); ALRecoveryStore::Parting moved; moved.key = "item:moved"; moved.tookUp = entry("item:moved", "typed in the other window"); moved.carrying = true; - ensure("let go of", store.letGo(moved)); - const std::vector all = whole(store, store.list()); - ensure_equals("one entry", all.size(), size_t(1)); - ensure("the carried text set aside", all.front().state == ALRecoveryEntry::State::Discarded && all.front().text == "typed in the other window"); + CHECK(store.letGo(moved)); + const std::vector all = require_whole(store, store.list()); + REQUIRE(all.size() == size_t(1)); + { + INFO("the carried text set aside"); + CHECK(all.front().state == ALRecoveryEntry::State::Discarded); + CHECK(all.front().text == "typed in the other window"); + } // Opened to take up another session's entry, and closed before it // loaded: that entry is still what it was, to be offered again. - ensure("left by another session", other.write(entry("item:left", "left before"))); + CHECK(other.write(entry("item:left", "left before"))); ALRecoveryStore::Parting waiting; waiting.key = "item:left"; waiting.tookUp = store.leftFor("item:left"); waiting.carrying = true; - ensure("let go of", store.letGo(waiting)); - ensure("the entry left", store.leftFor("item:left").has_value()); - ensure_equals("and nothing more set aside", files("/discarded").size(), size_t(1)); + CHECK(store.letGo(waiting)); + UNSCOPED_INFO("the entry left"); + REQUIRE(store.leftFor("item:left").has_value()); + UNSCOPED_INFO("and nothing more set aside"); + REQUIRE(files("/discarded").size() == size_t(1)); // A new item opened with a text of its own: nothing to keep. - ensure("kept", store.write(entry("item:new", "template"))); + CHECK(store.write(entry("item:new", "template"))); ALRecoveryStore::Parting fresh; fresh.key = "item:new"; fresh.carrying = true; - ensure("let go of", store.letGo(fresh)); + CHECK(store.letGo(fresh)); const std::vector after = store.list(); - ensure("forgotten", std::none_of(after.begin(), after.end(), [](const ALRecoveryEntry& one) { return one.key == "item:new"; })); + CHECK(std::none_of(after.begin(), after.end(), [](const ALRecoveryEntry& one) { return one.key == "item:new"; })); } - template<> template<> - void alrecoverystore_object::test<13>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a failed load is retried with delays tripling and a few times only", "[alrecoverystore]") { - set_test_name("a failed load is tried again a moment later, three times as long after each failure since, and a few times only"); - ensure_equals("after the first", ALRecoveryRetry::delayAfter(1), ALRecoveryRetry::FIRST); - ensure_equals("after the second", ALRecoveryRetry::delayAfter(2), ALRecoveryRetry::FIRST * 3.0); - ensure_equals("after the third", ALRecoveryRetry::delayAfter(3), ALRecoveryRetry::FIRST * 9.0); - ensure_equals("none yet is as the first", ALRecoveryRetry::delayAfter(0), ALRecoveryRetry::FIRST); - ensure("tried while there have been fewer failures than the tries", ALRecoveryRetry::mayTry(0) && ALRecoveryRetry::mayTry(ALRecoveryRetry::TRIES - 1)); - ensure("and then waits to be asked", !ALRecoveryRetry::mayTry(ALRecoveryRetry::TRIES)); + CHECK(ALRecoveryRetry::delayAfter(1) == ALRecoveryRetry::FIRST); + CHECK(ALRecoveryRetry::delayAfter(2) == ALRecoveryRetry::FIRST * 3.0); + CHECK(ALRecoveryRetry::delayAfter(3) == ALRecoveryRetry::FIRST * 9.0); + UNSCOPED_INFO("none yet is as the first"); + CHECK(ALRecoveryRetry::delayAfter(0) == ALRecoveryRetry::FIRST); + { + INFO("tried while there have been fewer failures than the tries"); + CHECK(ALRecoveryRetry::mayTry(0)); + CHECK(ALRecoveryRetry::mayTry(ALRecoveryRetry::TRIES - 1)); + } + UNSCOPED_INFO("and then waits to be asked"); + CHECK_FALSE(ALRecoveryRetry::mayTry(ALRecoveryRetry::TRIES)); } - template<> template<> - void alrecoverystore_object::test<14>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: written as notation; setAside under the session named; newest left first", "[alrecoverystore]") { - set_test_name("written as notation, whatever the text and history hold; set aside by the session named, a moment apart kept apart; the newest left first"); ALRecoveryStore store(folder, "mine"); ALRecoveryEntry awkward = entry("item:a", "tab\there, 'quoted' \"twice\", back\\slash,\r\nlines, \xC3\xA9, \xF4\x80\x80\x80"); LLSD history; @@ -481,78 +556,90 @@ namespace tut history["redo"] = LLSD::emptyArray(); history["saved"] = -1; awkward.history = history; - ensure("written", store.write(awkward)); + CHECK(store.write(awkward)); { const std::vector names = files(); - ensure_equals("one file", names.size(), size_t(1)); + REQUIRE(names.size() == size_t(1)); std::error_code ec; const std::string written = LLFile::getContents(fsyspath(folder + "/" + names.front()), ec); // A line of what a listing reads, then a line of the rest, each // LLSD notation. const size_t line = written.find('\n'); - ensure("as notation, a line of what a listing reads", written.rfind("{", 0) == 0 && line != std::string::npos); - ensure("which is not the text", written.substr(0, line).find("'text'") == std::string::npos); - ensure("then the rest", written.compare(line + 1, 8, "{'caret'") == 0 || written.compare(line + 1, 10, "{'history'") == 0 || - written.compare(line + 1, 7, "{'text'") == 0); + { + INFO("as notation, a line of what a listing reads"); + CHECK(written.rfind("{", 0) == 0); + REQUIRE(line != std::string::npos); + } + UNSCOPED_INFO("which is not the text"); + REQUIRE(written.substr(0, line).find("'text'") == std::string::npos); + UNSCOPED_INFO("then the rest"); + CHECK((written.compare(line + 1, 8, "{'caret'") == 0 || written.compare(line + 1, 10, "{'history'") == 0 || + written.compare(line + 1, 7, "{'text'") == 0)); } - const std::vector back = whole(store, store.list()); - ensure("the text as it was", back.size() == 1 && back.front().text == awkward.text); - ensure("the history as it was", back.front().history["undo"][0]["edits"][0][5].asString() == awkward.text && - back.front().history["undo"][0]["label"].asString() == "it's"); + const std::vector back = require_whole(store, store.list()); + REQUIRE(back.size() == 1); + CHECK(back.front().text == awkward.text); + CHECK(back.front().history["undo"][0]["edits"][0][5].asString() == awkward.text); + CHECK(back.front().history["undo"][0]["label"].asString() == "it's"); // Set aside for another session, and two a moment apart. ALRecoveryEntry theirs = entry("item:b", "theirs"); theirs.session = "theirs"; - ensure("set aside", store.setAside(theirs)); + CHECK(store.setAside(theirs)); // Apart by more than the centisecond a date is written to. ms_sleep(20); - ensure("and another", store.setAside(entry("item:b", "mine, later"))); - ensure_equals("both kept", files("/discarded").size(), size_t(2)); - const std::vector all = whole(store, store.list()); - ensure("the session it was set aside for", std::any_of(all.begin(), all.end(), [](const ALRecoveryEntry& one) { return one.text == "theirs" && one.session == "theirs"; })); - ensure("this one where none was named", std::any_of(all.begin(), all.end(), [](const ALRecoveryEntry& one) { return one.text == "mine, later" && one.session == "mine"; })); + CHECK(store.setAside(entry("item:b", "mine, later"))); + UNSCOPED_INFO("both kept"); + REQUIRE(files("/discarded").size() == size_t(2)); + const std::vector all = require_whole(store, store.list()); + UNSCOPED_INFO("the session it was set aside for"); + CHECK(std::any_of(all.begin(), all.end(), [](const ALRecoveryEntry& one) { return one.text == "theirs" && one.session == "theirs"; })); + UNSCOPED_INFO("this one where none was named"); + CHECK(std::any_of(all.begin(), all.end(), [](const ALRecoveryEntry& one) { return one.text == "mine, later" && one.session == "mine"; })); // Left by other sessions: the newest first. ALRecoveryStore older(folder, "older"); ALRecoveryStore newer(folder, "newer"); - ensure("older", older.write(entry("item:c", "older"))); + CHECK(older.write(entry("item:c", "older"))); // Apart by more than the centisecond a date is written to. ms_sleep(20); - ensure("newer", newer.write(entry("item:d", "newer"))); - const std::vector left = whole(store, store.left()); - ensure("newest first", left.size() == 2 && left.front().text == "newer" && left.back().text == "older"); + CHECK(newer.write(entry("item:d", "newer"))); + const std::vector left = require_whole(store, store.left()); + REQUIRE(left.size() == 2); + CHECK(left.front().text == "newer"); + CHECK(left.back().text == "older"); } - template<> template<> - void alrecoverystore_object::test<15>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a discard whose name says no time is pruned by what it says inside", "[alrecoverystore]") { - set_test_name("a discarded file whose name does not say when is pruned by what it says inside"); ALRecoveryStore store(folder, "mine"); - ensure("set aside", store.setAside(entry("item:a", "old"))); + CHECK(store.setAside(entry("item:a", "old"))); // Renamed to say nothing of when, and then to say it badly. const std::vector names = files("/discarded"); - ensure_equals("one", names.size(), size_t(1)); + REQUIRE(names.size() == size_t(1)); const std::string stamped = folder + "/discarded/" + names.front(); const std::string bare = folder + "/discarded/0000.mine.llsd"; - ensure("renamed", LLFile::rename(stamped, bare) == 0); + CHECK(LLFile::rename(stamped, bare) == 0); store.prune(60.0, LLDate(LLDate::now().secondsSinceEpoch() + 30.0)); - ensure("young by what it says, it stays", LLFile::isfile(bare)); + UNSCOPED_INFO("young by what it says, it stays"); + CHECK(LLFile::isfile(bare)); store.prune(60.0, LLDate(LLDate::now().secondsSinceEpoch() + 120.0)); - ensure("old by what it says, it goes", !LLFile::isfile(bare)); + UNSCOPED_INFO("old by what it says, it goes"); + CHECK_FALSE(LLFile::isfile(bare)); - ensure("set aside", store.setAside(entry("item:b", "old"))); + CHECK(store.setAside(entry("item:b", "old"))); const std::string garbled = folder + "/discarded/0001.mine.soon.llsd"; - ensure("renamed", LLFile::rename(folder + "/discarded/" + files("/discarded").front(), garbled) == 0); + CHECK(LLFile::rename(folder + "/discarded/" + files("/discarded").front(), garbled) == 0); store.prune(60.0, LLDate(LLDate::now().secondsSinceEpoch() + 30.0)); - ensure("a stamp that is no number read past", LLFile::isfile(garbled)); + UNSCOPED_INFO("a stamp that is no number read past"); + CHECK(LLFile::isfile(garbled)); store.prune(60.0, LLDate(LLDate::now().secondsSinceEpoch() + 120.0)); - ensure("and pruned by what it says", !LLFile::isfile(garbled)); + UNSCOPED_INFO("and pruned by what it says"); + CHECK_FALSE(LLFile::isfile(garbled)); } - template<> template<> - void alrecoverystore_object::test<16>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: writeSoon writes on the store's thread and the newest for a key wins", "[alrecoverystore]") { - set_test_name("what typing writes goes on the store's thread, the newest for a key winning, and whatever else is asked waits for it"); { ALRecoveryStore store(folder, "session-a"); for (int i = 0; i < 20; ++i) @@ -561,16 +648,19 @@ namespace tut } store.writeSoon(entry("item:y", "other")); store.flush(); - const std::vector all = whole(store, store.list()); - ensure_equals("one entry a key", all.size(), size_t(2)); + const std::vector all = require_whole(store, store.list()); + UNSCOPED_INFO("one entry a key"); + REQUIRE(all.size() == size_t(2)); for (const ALRecoveryEntry& one : all) { - ensure("the newest of x: " + one.text, one.key != "item:x" || one.text == "typed 19"); - ensure("this session's", one.session == "session-a"); + CAPTURE(one.key, one.text); + REQUIRE((one.key != "item:x" || one.text == "typed 19")); + REQUIRE(one.session == "session-a"); } for (const std::string& name : files()) { - ensure("nothing half written: " + name, name.find(".tmp") == std::string::npos); + CAPTURE(name); + REQUIRE(name.find(".tmp") == std::string::npos); } // Forgotten right after it was asked to be written: the write // goes first, and the file does not come back after. @@ -578,18 +668,23 @@ namespace tut store.forget("item:x"); store.flush(); const std::vector after = store.list(); - ensure("x gone: " + std::to_string(after.size()), after.size() == 1 && after.front().key == "item:y"); - ensure("nothing failed", store.takeFailures().empty()); + { + INFO("x gone"); + REQUIRE(after.size() == 1); + CHECK(after.front().key == "item:y"); + } + REQUIRE(store.takeFailures().empty()); // Left waiting as the store goes: written all the same. store.writeSoon(entry("item:z", "as it went")); } ALRecoveryStore again(folder, "session-a"); bool z = false; - for (const ALRecoveryEntry& one : whole(again, again.list())) + for (const ALRecoveryEntry& one : require_whole(again, again.list())) { z |= one.key == "item:z" && one.text == "as it went"; } - ensure("written as the store went", z); + UNSCOPED_INFO("written as the store went"); + CHECK(z); // Where the folder cannot be written, said by key. const std::string blocked = folder + "/blocked"; { @@ -600,139 +695,213 @@ namespace tut nowhere.writeSoon(entry("item:w", "lost")); nowhere.flush(); const std::vector failed = nowhere.takeFailures(); - ensure("failed, by key", failed.size() == 1 && failed.front() == "item:w"); - ensure("and said once", nowhere.takeFailures().empty()); + REQUIRE(failed.size() == 1); + CHECK(failed.front() == "item:w"); + UNSCOPED_INFO("and said once"); + REQUIRE(nowhere.takeFailures().empty()); // Each asking of its own key leaves another's. nowhere.writeSoon(entry("item:u", "lost")); nowhere.writeSoon(entry("item:v", "lost")); nowhere.flush(); - ensure("one key's own", nowhere.takeFailure("item:u") && !nowhere.takeFailure("item:u")); - ensure("the other's left for it", nowhere.takeFailures() == std::vector({ "item:v" })); + { + INFO("one key's own"); + CHECK(nowhere.takeFailure("item:u")); + CHECK_FALSE(nowhere.takeFailure("item:u")); + } + UNSCOPED_INFO("the other's left for it"); + CHECK(nowhere.takeFailures() == std::vector({ "item:v" })); } - template<> template<> - void alrecoverystore_object::test<17>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: whenSaid gives the day and time an entry was written", "[alrecoverystore]") { - set_test_name("when an entry was written, said as a day and a time"); ALRecoveryEntry one; one.when = LLDate(1758772800.0); const std::string said = one.whenSaid(); // The day and the hour are the local zone's; the shape is not. - ensure_equals("YYYY-MM-DD HH:MM: " + said, said.size(), size_t(16)); - ensure("dashes and a colon: " + said, said[4] == '-' && said[7] == '-' && said[10] == ' ' && said[13] == ':'); - ensure("the year: " + said, said.starts_with("2025-09-2")); + CAPTURE(said); + UNSCOPED_INFO("YYYY-MM-DD HH:MM"); + REQUIRE(said.size() == size_t(16)); + CHECK(said[4] == '-'); + CHECK(said[7] == '-'); + CHECK(said[10] == ' '); + CHECK(said[13] == ':'); + CHECK(said.starts_with("2025-09-2")); } - template<> template<> - void alrecoverystore_object::test<18>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: reclaim sets this session's own entry aside keeping when it was written", "[alrecoverystore]") { - set_test_name("this session's own entry, reclaimed where no tab holds it, is set aside keeping when it was written, and out of the way of forget"); ALRecoveryStore store(folder, "now"); ALRecoveryEntry went = entry("task:a:b", "typed, then its window went"); store.write(went); const LLDate written = store.list().front().when; - ensure("not left by another session", !store.leftFor("task:a:b")); + UNSCOPED_INFO("not left by another session"); + REQUIRE_FALSE(store.leftFor("task:a:b")); const std::optional back = store.reclaim("task:a:b"); - ensure("reclaimed", back.has_value()); - ensure_equals("its text", back->text, went.text); - ensure("set aside", back->state == ALRecoveryEntry::State::Discarded); - ensure_equals("when it was written, not when it was set aside", back->when.secondsSinceEpoch(), written.secondsSinceEpoch()); - ensure("its own file gone, and it among the discarded", files().empty() && files("/discarded").size() == 1); - ensure("where it is now", back->path.find("discarded") != std::string::npos); + REQUIRE(back.has_value()); + CHECK(back->text == went.text); + CHECK(back->state == ALRecoveryEntry::State::Discarded); + UNSCOPED_INFO("when it was written, not when it was set aside"); + CHECK(back->when.secondsSinceEpoch() == written.secondsSinceEpoch()); + { + INFO("its own file gone, and it among the discarded"); + REQUIRE(files().empty()); + REQUIRE(files("/discarded").size() == 1); + } + REQUIRE(back->path.find("discarded") != std::string::npos); store.forget("task:a:b"); - ensure("a tab opened clean forgets nothing of it", files("/discarded").size() == 1); - const std::vector listed = whole(store, store.list()); - ensure("offered under Recover Unsaved Changes", listed.size() == 1 && listed.front().text == went.text); - ensure("reclaimed once", !store.reclaim("task:a:b")); + UNSCOPED_INFO("a tab opened clean forgets nothing of it"); + REQUIRE(files("/discarded").size() == 1); + const std::vector listed = require_whole(store, store.list()); + { + INFO("offered under Recover Unsaved Changes"); + REQUIRE(listed.size() == 1); + CHECK(listed.front().text == went.text); + } + UNSCOPED_INFO("reclaimed once"); + REQUIRE_FALSE(store.reclaim("task:a:b")); ALRecoveryStore other(folder, "earlier"); other.write(entry("task:c:d", "another session's")); - ensure("another session's is not this one's to reclaim", !store.reclaim("task:c:d") && store.leftFor("task:c:d")); + { + INFO("another session's is not this one's to reclaim"); + REQUIRE_FALSE(store.reclaim("task:c:d")); + REQUIRE(store.leftFor("task:c:d")); + } store.remove(*back); - ensure("taken up, gone", files("/discarded").empty()); + UNSCOPED_INFO("taken up, gone"); + REQUIRE(files("/discarded").empty()); } - template<> template<> - void alrecoverystore_object::test<19>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: a listing reads only what it shows and load reads the rest", "[alrecoverystore]") { - set_test_name("a listing reads what it shows and no more; the rest is read as it is taken up; a history written as notation reads back as one"); ALRecoveryStore store(folder, "mine"); ALRecoveryStore other(folder, "theirs"); ALRecoveryEntry big = entry("item:big", std::string(200000, 'x')); big.caretLine = 3; big.caretColumn = 4; big.historyWritten = "{'version':i2,'undo':[{'label':'it\\'s','edits':[]}],'redo':[],'saved':i-1}"; - ensure("written", other.write(big)); + CHECK(other.write(big)); const std::vector listed = store.left(); - ensure_equals("listed", listed.size(), size_t(1)); - ensure("with what it shows", listed.front().name == "My Script" && listed.front().key == "item:big" && listed.front().session == "theirs"); - ensure("and not its text", !listed.front().whole && listed.front().text.empty() && listed.front().caretLine < 0); + REQUIRE(listed.size() == size_t(1)); + { + INFO("with what it shows"); + CHECK(listed.front().name == "My Script"); + CHECK(listed.front().key == "item:big"); + CHECK(listed.front().session == "theirs"); + } + { + INFO("and not its text"); + CHECK_FALSE(listed.front().whole); + REQUIRE(listed.front().text.empty()); + CHECK(listed.front().caretLine < 0); + } ALRecoveryEntry taken = listed.front(); - ensure("read whole", store.load(taken)); - ensure("its text", taken.whole && taken.text == big.text); - ensure("its caret", taken.caretLine == 3 && taken.caretColumn == 4); - ensure("its history, written as notation, read back", taken.history["undo"][0]["label"].asString() == "it's" && taken.history["saved"].asInteger() == -1); - ensure("whole already, read again as it is", store.load(taken) && taken.text == big.text); + CHECK(store.load(taken)); + CHECK(taken.whole); + CHECK(taken.text == big.text); + CHECK(taken.caretLine == 3); + CHECK(taken.caretColumn == 4); + { + INFO("its history, written as notation, read back"); + CHECK(taken.history["undo"][0]["label"].asString() == "it's"); + CHECK(taken.history["saved"].asInteger() == -1); + } + { + INFO("whole already, read again as it is"); + CHECK(store.load(taken)); + CHECK(taken.text == big.text); + } store.remove(taken); ALRecoveryEntry gone = listed.front(); - ensure("its file gone, not read", !store.load(gone)); + UNSCOPED_INFO("its file gone, not read"); + CHECK_FALSE(store.load(gone)); // Set aside from a listing, it is read whole first: nothing of its // text is lost among the discarded. - ensure("written again", other.write(big)); + CHECK(other.write(big)); const std::optional again = store.leftFor("item:big"); - ensure("found, as a listing reads it", again && !again->whole); - ensure("discarded", store.discard(*again)); - const std::vector thrown = whole(store, store.list()); - ensure("with its text", thrown.size() == 1 && thrown.front().text == big.text && thrown.front().history["undo"].size() == 1); + { + INFO("found, as a listing reads it"); + REQUIRE(again); + CHECK_FALSE(again->whole); + } + CHECK(store.discard(*again)); + const std::vector thrown = require_whole(store, store.list()); + REQUIRE(thrown.size() == 1); + CHECK(thrown.front().text == big.text); + REQUIRE(thrown.front().history["undo"].size() == 1); } - template<> template<> - void alrecoverystore_object::test<20>() + TEST_CASE_METHOD(alrecoverystore_data, "alrecoverystore: offers expire; discards are capped; offers are counted once a moment", "[alrecoverystore]") { - set_test_name("what was offered and left goes a while after; the discarded are held to so many and so much; offers are counted once a moment"); ALRecoveryStore store(folder, "mine"); ALRecoveryStore other(folder, "theirs"); - ensure("nothing to offer", !store.hasOffers()); - ensure("left by another session", other.write(entry("item:a", "left"))); - ensure("counted a moment ago: not yet seen", !store.hasOffers()); + CHECK_FALSE(store.hasOffers()); + CHECK(other.write(entry("item:a", "left"))); + UNSCOPED_INFO("counted a moment ago: not yet seen"); + CHECK_FALSE(store.hasOffers()); store.forget("item:none"); - ensure("seen once this store changes anything", store.hasOffers()); + UNSCOPED_INFO("seen once this store changes anything"); + CHECK(store.hasOffers()); // Offered: marked when, still offered, and gone a while after. const LLDate offered = LLDate::now(); store.markOffered(store.left(), offered); const std::vector names = files(); - ensure("renamed to say when", names.size() == 1 && names.front().find(std::to_string(static_cast(offered.secondsSinceEpoch() * 1000.0))) != std::string::npos); - ensure("still offered", store.hasOffers() && store.left().size() == 1 && store.leftFor("item:a").has_value()); - ensure("and read", whole(store, store.leftFor("item:a"))->text == "left"); + { + INFO("renamed to say when"); + REQUIRE(names.size() == 1); + REQUIRE(names.front().find(std::to_string(static_cast(offered.secondsSinceEpoch() * 1000.0))) != std::string::npos); + } + { + INFO("still offered"); + CHECK(store.hasOffers()); + REQUIRE(store.left().size() == 1); + REQUIRE(store.leftFor("item:a").has_value()); + } + CHECK(require_whole(store, store.leftFor("item:a"))->text == "left"); store.markOffered(store.left(), LLDate(offered.secondsSinceEpoch() + 100.0)); - ensure("marked once", files().front() == names.front()); + UNSCOPED_INFO("marked once"); + CHECK(files().front() == names.front()); store.prune(60.0, LLDate(offered.secondsSinceEpoch() + 30.0)); - ensure("young, it stays", files().size() == 1); + UNSCOPED_INFO("young, it stays"); + REQUIRE(files().size() == 1); store.prune(60.0, LLDate(offered.secondsSinceEpoch() + 120.0)); - ensure("left alone a while, it goes", files().empty() && !store.hasOffers()); - ensure("this session's own is never offered", store.write(entry("item:mine", "mine"))); + { + INFO("left alone a while, it goes"); + REQUIRE(files().empty()); + CHECK_FALSE(store.hasOffers()); + } + // This session's own is never offered, nor marked. + CHECK(store.write(entry("item:mine", "mine"))); store.markOffered(store.list()); - ensure("nor marked", files().size() == 1 && files().front().find(".mine.llsd") != std::string::npos); + { + INFO("this session's own not marked"); + REQUIRE(files().size() == 1); + REQUIRE(files().front().find(".mine.llsd") != std::string::npos); + } // The discarded held to three, then to what two of them weigh. store.limitDiscarded(3, 1024 * 1024); for (int i = 0; i < 5; ++i) { - ensure("set aside", store.setAside(entry("item:d" + std::to_string(i), "discarded " + std::to_string(i)))); + REQUIRE(store.setAside(entry("item:d" + std::to_string(i), "discarded " + std::to_string(i)))); // Apart by more than the millisecond their names say. ms_sleep(5); } - std::vector kept = whole(store, store.list()); - ensure_equals("three kept", files("/discarded").size(), size_t(3)); - ensure("the newest", std::any_of(kept.begin(), kept.end(), [](const ALRecoveryEntry& one) { return one.text == "discarded 4"; }) && - std::none_of(kept.begin(), kept.end(), [](const ALRecoveryEntry& one) { return one.text == "discarded 1"; })); + std::vector kept = require_whole(store, store.list()); + REQUIRE(files("/discarded").size() == size_t(3)); + { + INFO("the newest"); + CHECK(std::any_of(kept.begin(), kept.end(), [](const ALRecoveryEntry& one) { return one.text == "discarded 4"; })); + CHECK(std::none_of(kept.begin(), kept.end(), [](const ALRecoveryEntry& one) { return one.text == "discarded 1"; })); + } std::error_code ec; const uintmax_t one = std::filesystem::file_size(fsyspath(folder + "/discarded/" + files("/discarded").front()), ec); store.limitDiscarded(100, static_cast(one * 2 + one / 2)); store.prune(60.0); - ensure_equals("two, by what they weigh", files("/discarded").size(), size_t(2)); + UNSCOPED_INFO("two, by what they weigh"); + REQUIRE(files("/discarded").size() == size_t(2)); } } diff --git a/indra/llcommon/tests/alregex_test.cpp b/indra/llcommon/tests/alregex_test.cpp index 6cb46241a5..cc60691995 100644 --- a/indra/llcommon/tests/alregex_test.cpp +++ b/indra/llcommon/tests/alregex_test.cpp @@ -27,126 +27,159 @@ #include "../alregex.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { - struct alregex_data + TEST_CASE("alregex: a pattern compiles or says why not and a failed one matches nothing", "[alregex]") { - }; - - typedef test_group alregex_group; - typedef alregex_group::object alregex_object; - alregex_group alregex_instance("alregex"); - - template<> template<> - void alregex_object::test<1>() - { - set_test_name("a pattern compiles or says why not, and one that did not, or none, matches nothing"); const ALRegex good("a(b)c"); - ensure("compiles", good.ok()); - ensure("no error", good.error().empty()); - ensure_equals("the pattern as given", good.pattern(), std::string("a(b)c")); - ensure_equals("its groups", good.groups(), 1); + CHECK(good.ok()); + REQUIRE(good.error().empty()); + CHECK(good.pattern() == std::string("a(b)c")); + CHECK(good.groups() == 1); const ALRegex ahead("x(?=y)"); - ensure("RE2 has no lookahead", !ahead.ok()); - ensure("and says so", !ahead.error().empty()); - ensure_equals("the pattern is still the one given", ahead.pattern(), std::string("x(?=y)")); - ensure("it matches nothing", !ahead.search("xy") && !ahead.match("x")); - ensure_equals("and has no groups", ahead.groups(), 0); + UNSCOPED_INFO("RE2 has no lookahead"); + CHECK_FALSE(ahead.ok()); + REQUIRE_FALSE(ahead.error().empty()); + UNSCOPED_INFO("the pattern is still the one given"); + CHECK(ahead.pattern() == std::string("x(?=y)")); + CHECK_FALSE(ahead.search("xy")); + CHECK_FALSE(ahead.match("x")); + CHECK(ahead.groups() == 0); const ALRegex none; - ensure("none is not ok", !none.ok()); - ensure("and says why", !none.error().empty()); - ensure("and matches nothing, not even nothing", !none.match("") && !none.search("")); + CHECK_FALSE(none.ok()); + REQUIRE_FALSE(none.error().empty()); + { + INFO("and matches nothing, not even nothing"); + CHECK_FALSE(none.match("")); + CHECK_FALSE(none.search("")); + } std::string text = "abc"; - ensure_equals("and replaces nothing", none.replaceAll(text, "x"), size_t(0)); - ensure_equals("text as it was", text, std::string("abc")); + CHECK(none.replaceAll(text, "x") == size_t(0)); + CHECK(text == std::string("abc")); const ALRegex copy = good; - ensure("a copy matches as the original", copy.ok() && copy.match("abc")); + CHECK(copy.ok()); + CHECK(copy.match("abc")); } - template<> template<> - void alregex_object::test<2>() + TEST_CASE("alregex: dot takes line breaks and ^ $ match per line unless told; case as asked", "[alregex]") { - set_test_name("as Boost.Regex's Perl syntax: . takes a line break and ^ $ are at every line's ends, unless told otherwise; case as asked"); - ensure("dot takes a break", ALRegex("a.b").match("a\nb")); - ensure("not when told", !ALRegex("a.b", ALRegex::NO_DOT_NL).match("a\nb")); - ensure("$ before a break", ALRegex("x$").search("x\ny")); - ensure("^ after one", ALRegex("^y").search("x\ny")); - ensure("not when told", !ALRegex("x$", ALRegex::NO_MULTILINE).search("x\ny") && !ALRegex("^y", ALRegex::NO_MULTILINE).search("x\ny")); - ensure("the text's own ends either way", ALRegex("^x$", ALRegex::NO_MULTILINE).match("x")); - ensure("case matters", !ALRegex("abc").match("ABC")); - ensure("unless it does not", ALRegex("abc", ALRegex::ICASE).match("aBc")); - ensure("a class too", ALRegex("[a-c]+", ALRegex::ICASE).match("CAB")); + CHECK(ALRegex("a.b").match("a\nb")); + CHECK_FALSE(ALRegex("a.b", ALRegex::NO_DOT_NL).match("a\nb")); + UNSCOPED_INFO("$ before a break"); + CHECK(ALRegex("x$").search("x\ny")); + UNSCOPED_INFO("^ after a break"); + CHECK(ALRegex("^y").search("x\ny")); + CHECK_FALSE(ALRegex("x$", ALRegex::NO_MULTILINE).search("x\ny")); + CHECK_FALSE(ALRegex("^y", ALRegex::NO_MULTILINE).search("x\ny")); + UNSCOPED_INFO("the text's own ends either way"); + CHECK(ALRegex("^x$", ALRegex::NO_MULTILINE).match("x")); + CHECK_FALSE(ALRegex("abc").match("ABC")); + CHECK(ALRegex("abc", ALRegex::ICASE).match("aBc")); + UNSCOPED_INFO("a class ignores case too"); + CHECK(ALRegex("[a-c]+", ALRegex::ICASE).match("CAB")); } - template<> template<> - void alregex_object::test<3>() + TEST_CASE("alregex: text is bytes as it was to Boost.Regex unless told it is UTF-8", "[alregex]") { - set_test_name("the text is bytes, as it was to Boost.Regex; told it is UTF-8, a byte that is no part of a character stops . and a negated class"); const std::string bad("a\xE9z"); - ensure("any byte is a character", ALRegex("a.z").match(bad)); - ensure("to a negated class too", ALRegex("a[^x]z").match(bad)); - ensure("and \\S", ALRegex("\\S+").match(bad)); - ensure("not as UTF-8", !ALRegex("a.z", ALRegex::UTF8).match(bad)); + UNSCOPED_INFO("any byte is a character"); + CHECK(ALRegex("a.z").match(bad)); + UNSCOPED_INFO("to a negated class too"); + CHECK(ALRegex("a[^x]z").match(bad)); + CHECK(ALRegex("\\S+").match(bad)); + CHECK_FALSE(ALRegex("a.z", ALRegex::UTF8).match(bad)); const std::string good("a\xC3\xA9z"); - ensure("where a character is two bytes, the dot is one of them", !ALRegex("a.z").match(good) && ALRegex("a..z").match(good)); - ensure("as UTF-8, the dot is the character", ALRegex("a.z", ALRegex::UTF8).match(good)); - ensure("a name with an accent finds itself", ALRegex("\\bJos\xC3\xA9 Smith\\b", ALRegex::ICASE).search("hi Jos\xC3\xA9 SMITH!")); + { + INFO("where a character is two bytes, the dot is one of them"); + CHECK_FALSE(ALRegex("a.z").match(good)); + CHECK(ALRegex("a..z").match(good)); + } + UNSCOPED_INFO("as UTF-8, the dot is the character"); + CHECK(ALRegex("a.z", ALRegex::UTF8).match(good)); + UNSCOPED_INFO("a name with an accent finds itself"); + CHECK(ALRegex("\\bJos\xC3\xA9 Smith\\b", ALRegex::ICASE).search("hi Jos\xC3\xA9 SMITH!")); } - template<> template<> - void alregex_object::test<4>() + TEST_CASE("alregex: match takes the whole text and search finds from a place or only there", "[alregex]") { - set_test_name("match is the whole text; search the first match from a place, where what comes before is still read, or only there"); const ALRegex word("\\bfoo\\b"); - ensure("the whole", word.match("foo")); - ensure("not a part", !word.match("foo bar")); - ensure("search finds a part", word.search("a foo b")); + CHECK(word.match("foo")); + CHECK_FALSE(word.match("foo bar")); + CHECK(word.search("a foo b")); ALRegexMatch found; - ensure("from a place", word.search("foo foo", &found, 1)); - ensure_equals("the second", found.begin(), size_t(4)); - ensure("what comes before a place is read for \\b", !word.search("xfoo", &found, 1)); - ensure("where it is not a word", word.search(" foo", &found, 1) && found.begin() == 1); - ensure("anchored there, only there", !word.search("a foo", &found, 1, true) && word.search("a foo", &found, 2, true)); - ensure("^ after a break, from a place", ALRegex("^b").search("a\nb", nullptr, 2, true)); - ensure("but not mid-line", !ALRegex("^b").search("ab", nullptr, 1, true)); - ensure("past the end is nothing", !word.search("foo", &found, 4)); - ensure("an empty text", ALRegex("x*").match("") && ALRegex("x*").match(std::string_view())); + REQUIRE(word.search("foo foo", &found, 1)); + UNSCOPED_INFO("the second"); + CHECK(found.begin() == size_t(4)); + UNSCOPED_INFO("what comes before a place is read for \\b"); + CHECK_FALSE(word.search("xfoo", &found, 1)); + { + INFO("where it is not a word"); + CHECK(word.search(" foo", &found, 1)); + CHECK(found.begin() == 1); + } + { + INFO("anchored there, only there"); + CHECK_FALSE(word.search("a foo", &found, 1, true)); + CHECK(word.search("a foo", &found, 2, true)); + } + UNSCOPED_INFO("^ after a break, from a place"); + CHECK(ALRegex("^b").search("a\nb", nullptr, 2, true)); + UNSCOPED_INFO("but not mid-line"); + CHECK_FALSE(ALRegex("^b").search("ab", nullptr, 1, true)); + UNSCOPED_INFO("past the end is nothing"); + CHECK_FALSE(word.search("foo", &found, 4)); + CHECK(ALRegex("x*").match("")); + CHECK(ALRegex("x*").match(std::string_view())); } - template<> template<> - void alregex_object::test<5>() + TEST_CASE("alregex: groups say where each is and whether it took part however many", "[alregex]") { - set_test_name("groups: where each is, whether it took part, and as many as a pattern has, or as asked for"); const ALRegex date("([0-9]{1,2})/([0-9]{1,2})/([0-9]{4})( ([AP]M))?"); ALRegexMatch found; - ensure("matches", date.search("on 12/25/2026 then", &found)); - ensure_equals("four groups and the whole", found.size(), size_t(6)); - ensure_equals("the whole", found.str(), std::string("12/25/2026")); - ensure_equals("where", found.begin(), size_t(3)); - ensure_equals("to", found.end(), size_t(13)); - ensure_equals("the month", found.str(1), std::string("12")); - ensure_equals("the year's place", found.begin(3), size_t(9)); - ensure_equals("its length", found.length(3), size_t(4)); - ensure("an optional group that was not there", !found.matched(4) && !found.matched(5)); - ensure_equals("is no place", found.begin(4), ALRegexMatch::npos); - ensure("and nothing", found.str(4).empty() && found.length(4) == 0); - ensure("past the groups is nothing", !found.matched(9)); + REQUIRE(date.search("on 12/25/2026 then", &found)); + REQUIRE(found.size() == size_t(6)); + CHECK(found.str() == std::string("12/25/2026")); + CHECK(found.begin() == size_t(3)); + CHECK(found.end() == size_t(13)); + UNSCOPED_INFO("the month"); + CHECK(found.str(1) == std::string("12")); + UNSCOPED_INFO("the year's place"); + CHECK(found.begin(3) == size_t(9)); + REQUIRE(found.length(3) == size_t(4)); + { + INFO("an optional group that was not there"); + CHECK_FALSE(found.matched(4)); + CHECK_FALSE(found.matched(5)); + } + UNSCOPED_INFO("a group not there has no place"); + CHECK(found.begin(4) == ALRegexMatch::npos); + REQUIRE(found.str(4).empty()); + REQUIRE(found.length(4) == 0); + UNSCOPED_INFO("past the groups is nothing"); + CHECK_FALSE(found.matched(9)); - ensure("the whole only, as asked", date.search("12/25/2026", &found, 0, false, 0)); - ensure_equals("one", found.size(), size_t(1)); - ensure("and the groups not", !found.matched(1)); + UNSCOPED_INFO("the whole only, as asked"); + CHECK(date.search("12/25/2026", &found, 0, false, 0)); + REQUIRE(found.size() == size_t(1)); + CHECK_FALSE(found.matched(1)); - ensure("an empty group took part", ALRegex("a()b").match("ab", &found) && found.matched(1) && found.length(1) == 0 && found.begin(1) == 1); + { + INFO("an empty group took part"); + CHECK(ALRegex("a()b").match("ab", &found)); + CHECK(found.matched(1)); + REQUIRE(found.length(1) == 0); + CHECK(found.begin(1) == 1); + } // More groups than are kept in place. std::string pattern, text; @@ -156,113 +189,145 @@ namespace tut text += static_cast('a' + i % 26); } const ALRegex many(pattern); - ensure_equals("thirty groups", many.groups(), 30); - ensure("matches", many.match(text, &found)); - ensure_equals("all of them", found.size(), size_t(31)); - ensure_equals("the last", found.str(30), std::string(1, text[29])); - ensure_equals("and where", found.begin(30), size_t(29)); + CHECK(many.groups() == 30); + CHECK(many.match(text, &found)); + REQUIRE(found.size() == size_t(31)); + CHECK(found.str(30) == std::string(1, text[29])); + CHECK(found.begin(30) == size_t(29)); - ensure("a miss leaves nothing", !date.search("none", &found) && found.size() == 0 && !found.matched()); + { + INFO("a miss leaves nothing"); + CHECK_FALSE(date.search("none", &found)); + REQUIRE(found.size() == 0); + CHECK_FALSE(found.matched()); + } } - template<> template<> - void alregex_object::test<6>() + TEST_CASE("alregex: forEach and replaceEach visit every match and replaceAll is literal", "[alregex]") { - set_test_name("every match in turn, stopped when asked; each replaced by what is made of it, or by a literal as it is written"); const ALRegex num("[0-9]+"); std::vector seen; - ensure_equals("three", num.forEach("a1 b22 c333", [&](const ALRegexMatch& m) { seen.push_back(m.str()); return true; }), size_t(3)); - ensure("in order", seen == std::vector{ "1", "22", "333" }); + CHECK(num.forEach("a1 b22 c333", [&](const ALRegexMatch& m) { seen.push_back(m.str()); return true; }) == size_t(3)); + CHECK(seen == std::vector{ "1", "22", "333" }); seen.clear(); num.forEach("a1 b22 c333", [&](const ALRegexMatch& m) { seen.push_back(m.str()); return seen.size() < 2; }); - ensure_equals("stopped at two", seen.size(), size_t(2)); + REQUIRE(seen.size() == size_t(2)); std::string text = "a1 b22 c333"; - ensure_equals("each", num.replaceEach(text, [](const ALRegexMatch& m) { return "<" + m.str() + ">"; }), size_t(3)); - ensure_equals("made over", text, std::string("a<1> b<22> c<333>")); + CHECK(num.replaceEach(text, [](const ALRegexMatch& m) { return "<" + m.str() + ">"; }) == size_t(3)); + CHECK(text == std::string("a<1> b<22> c<333>")); text = "a1 b22"; - ensure_equals("literal", num.replaceAll(text, "\\1$&"), size_t(2)); - ensure_equals("as it is written", text, std::string("a\\1$& b\\1$&")); + CHECK(num.replaceAll(text, "\\1$&") == size_t(2)); + UNSCOPED_INFO("as it is written"); + CHECK(text == std::string("a\\1$& b\\1$&")); text = "none here"; - ensure_equals("no match, no change", num.replaceEach(text, [](const ALRegexMatch&) { return std::string("x"); }), size_t(0)); - ensure_equals("as it was", text, std::string("none here")); + UNSCOPED_INFO("no match, no change"); + CHECK(num.replaceEach(text, [](const ALRegexMatch&) { return std::string("x"); }) == size_t(0)); + CHECK(text == std::string("none here")); text = "ab"; - ensure_equals("empty matches: one at each place", ALRegex("x*").replaceEach(text, [](const ALRegexMatch&) { return std::string("-"); }), size_t(3)); - ensure_equals("and the text between", text, std::string("-a-b-")); + UNSCOPED_INFO("empty matches: one at each place"); + CHECK(ALRegex("x*").replaceEach(text, [](const ALRegexMatch&) { return std::string("-"); }) == size_t(3)); + CHECK(text == std::string("-a-b-")); size_t count = ALRegex("x*").forEach("ab", [](const ALRegexMatch&) { return true; }); - ensure_equals("and in turn", count, size_t(3)); + UNSCOPED_INFO("forEach sees each empty match"); + CHECK(count == size_t(3)); } - template<> template<> - void alregex_object::test<7>() + TEST_CASE("alregex: escape makes a text match itself and nothing else whatever it holds", "[alregex]") { - set_test_name("a text escaped matches itself and nothing else, whatever it holds"); const std::string name = "Bob (Bobby) [x].* $1 \\n ^|?+{2}"; const ALRegex exact("\\b" + ALRegex::escape(name), ALRegex::ICASE); - ensure("compiles", exact.ok()); + CHECK(exact.ok()); // The match views the text, which outlives it here. const std::string text = "hi " + name + " there"; ALRegexMatch found; - ensure("finds it", exact.search(text, &found)); - ensure_equals("all of it", found.str(), name); - ensure("and not what it would mean", !exact.search("hi Bob Bobby")); + REQUIRE(exact.search(text, &found)); + CHECK(found.str() == name); + UNSCOPED_INFO("and not what it would mean"); + CHECK_FALSE(exact.search("hi Bob Bobby")); } - template<> template<> - void alregex_object::test<8>() + TEST_CASE("alregex: firstByteRange gives the bytes a match can begin with or none", "[alregex]") { - set_test_name("the bytes a match can begin with, before and after a word, and none where it could be any"); U8 lo = 0, hi = 0; - ensure("a hexadecimal number", ALRegex("0[xX][0-9a-fA-F]+").firstByteRange(lo, hi)); - ensure("begins with 0", lo == '0' && hi <= '1'); - ensure("a number or a point", ALRegex("[0-9]+|\\.[0-9]+").firstByteRange(lo, hi)); - ensure("from the point to 9", lo == '.' && hi >= '9' && hi <= ':'); - ensure("anything is no range", !ALRegex(".*x").firstByteRange(lo, hi)); - ensure("nor is a pattern that did not compile", !ALRegex("(?=x)").firstByteRange(lo, hi)); + CHECK(ALRegex("0[xX][0-9a-fA-F]+").firstByteRange(lo, hi)); + CHECK(lo == '0'); + CHECK(hi <= '1'); + CHECK(ALRegex("[0-9]+|\\.[0-9]+").firstByteRange(lo, hi)); + CHECK(lo == '.'); + CHECK(hi >= '9'); + CHECK(hi <= ':'); + UNSCOPED_INFO("anything is no range"); + CHECK_FALSE(ALRegex(".*x").firstByteRange(lo, hi)); + UNSCOPED_INFO("nor is a pattern that did not compile"); + CHECK_FALSE(ALRegex("(?=x)").firstByteRange(lo, hi)); // After a word, \b is before what is not one. - ensure("a boundary before a non-letter", ALRegex("\\b[^a-z]x").firstByteRange(lo, hi) == false || (lo <= '!' && hi >= '_')); - ensure("a label", ALRegex("@[A-Za-z_][A-Za-z0-9_]*").firstByteRange(lo, hi)); - ensure("begins with @", lo == '@' && hi <= 'A'); + UNSCOPED_INFO("a boundary before a non-letter"); + CHECK((ALRegex("\\b[^a-z]x").firstByteRange(lo, hi) == false || (lo <= '!' && hi >= '_'))); + CHECK(ALRegex("@[A-Za-z_][A-Za-z0-9_]*").firstByteRange(lo, hi)); + CHECK(lo == '@'); + CHECK(hi <= 'A'); } - template<> template<> - void alregex_object::test<9>() + TEST_CASE("alregex: a regex streams as its pattern and keeps its flags", "[alregex]") { - set_test_name("a regex written out is its pattern, and keeps its flags"); std::ostringstream out; out << ALRegex("a|b", ALRegex::ICASE); - ensure_equals("the pattern", out.str(), std::string("a|b")); - ensure_equals("the flags", ALRegex("a", ALRegex::ICASE | ALRegex::NO_DOT_NL).flags(), U32(ALRegex::ICASE | ALRegex::NO_DOT_NL)); + CHECK(out.str() == std::string("a|b")); + CHECK(ALRegex("a", ALRegex::ICASE | ALRegex::NO_DOT_NL).flags() == U32(ALRegex::ICASE | ALRegex::NO_DOT_NL)); } - template<> template<> - void alregex_object::test<10>() + TEST_CASE("alregex: ALRegexSet says which of its patterns match a text in one pass", "[alregex]") { - set_test_name("a set says which of its patterns match anywhere in a text, in one pass, read as a regex reads them"); ALRegexSet set(ALRegex::ICASE); - ensure("an empty set is not made", !set.ok() && !set.compile()); - ensure_equals("the first", set.add("https?://\\S+"), 0); - ensure_equals("the second", set.add("[0-9]+"), 1); - ensure_equals("the third", set.add("^b$"), 2); + { + INFO("an empty set is not made"); + CHECK_FALSE(set.ok()); + CHECK_FALSE(set.compile()); + } + CHECK(set.add("https?://\\S+") == 0); + CHECK(set.add("[0-9]+") == 1); + CHECK(set.add("^b$") == 2); std::string error; - ensure_equals("one that does not compile is not added", set.add("x(?=y)", &error), -1); - ensure("and says why", !error.empty()); - ensure_equals("three", set.size(), size_t(3)); + UNSCOPED_INFO("one that does not compile is not added"); + CHECK(set.add("x(?=y)", &error) == -1); + REQUIRE_FALSE(error.empty()); + REQUIRE(set.size() == size_t(3)); std::vector hits; - ensure("not made, it cannot say", !set.match("123", hits) && hits.empty()); + { + INFO("not made, it cannot say"); + REQUIRE_FALSE(set.match("123", hits)); + REQUIRE(hits.empty()); + } - ensure("made", set.compile() && set.ok()); - ensure("nothing is added after", set.add("z") == -1); - ensure("a pass", set.match("see HTTP://x.com at 10", hits)); + CHECK(set.compile()); + CHECK(set.ok()); + UNSCOPED_INFO("nothing is added after"); + CHECK(set.add("z") == -1); + CHECK(set.match("see HTTP://x.com at 10", hits)); std::sort(hits.begin(), hits.end()); - ensure("the Url in any case, and the number", hits == std::vector{ 0, 1 }); - ensure("^ and $ are a line's, as a regex's are", set.match("a\nb\nc", hits) && hits == std::vector{ 2 }); - ensure("none, said", set.match("nothing", hits) && hits.empty()); + UNSCOPED_INFO("the Url in any case, and the number"); + CHECK(hits == std::vector{ 0, 1 }); + { + INFO("^ and $ are a line's, as a regex's are"); + CHECK(set.match("a\nb\nc", hits)); + CHECK(hits == std::vector{ 2 }); + } + { + INFO("none, said"); + CHECK(set.match("nothing", hits)); + REQUIRE(hits.empty()); + } ALRegexSet moved = std::move(set); - ensure("moved, it goes on", moved.ok() && moved.match("7", hits) && hits == std::vector{ 1 }); + { + INFO("moved, it goes on"); + CHECK(moved.ok()); + CHECK(moved.match("7", hits)); + CHECK(hits == std::vector{ 1 }); + } } } diff --git a/indra/llcommon/tests/alsavehistory_test.cpp b/indra/llcommon/tests/alsavehistory_test.cpp index e649af4b5a..5cb00f8145 100644 --- a/indra/llcommon/tests/alsavehistory_test.cpp +++ b/indra/llcommon/tests/alsavehistory_test.cpp @@ -30,12 +30,12 @@ #include "fsyspath.h" #include "llfile.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct alsavehistory_data { @@ -70,61 +70,66 @@ namespace tut } // A key's texts, newest first, each read whole. - static std::string texts(const ALSaveHistory& history, const std::string& key) + static std::string require_texts(const ALSaveHistory& history, const std::string& key) { + CAPTURE(key); std::string out; for (ALSavedText one : history.list(key)) { - ensure("read whole", history.load(one)); + REQUIRE(history.load(one)); out += (out.empty() ? "" : "|") + one.text; } return out; } }; - typedef test_group alsavehistory_group; - typedef alsavehistory_group::object alsavehistory_object; - tut::alsavehistory_group alsavehistory_test("ALSaveHistory"); - - template<> template<> - void alsavehistory_object::test<1>() + TEST_CASE_METHOD(alsavehistory_data, "ALSaveHistory: saves list newest first per key and the text is read on asking", "[ALSaveHistory]") { - set_test_name("kept, and listed newest first as far as a listing reads; the text read on asking; each key its own"); ALSaveHistory history(folder); // Within the age: dated from now. const F64 now = LLDate::now().secondsSinceEpoch(); - ensure("kept", history.keep(saved("item:a", "one", now - 60.0))); - ensure("kept", history.keep(saved("item:a", "two", now - 30.0))); - ensure("another's", history.keep(saved("item:b", "other", now - 45.0))); + CHECK(history.keep(saved("item:a", "one", now - 60.0))); + CHECK(history.keep(saved("item:a", "two", now - 30.0))); + CHECK(history.keep(saved("item:b", "other", now - 45.0))); const std::vector listed = history.list("item:a"); - ensure_equals("two of its own", listed.size(), 2U); - ensure("newest first", listed[0].when.secondsSinceEpoch() > listed[1].when.secondsSinceEpoch()); - ensure("what a listing reads", listed[0].name == "Door" && listed[0].objectName == "House" && listed[0].region == "Ahern" && - listed[0].asset == LLUUID::generateNewID("two") && listed[0].bytes == 3); - ensure("and not the text", !listed[0].whole && listed[0].text.empty()); - ensure_equals("read on asking", texts(history, "item:a"), std::string("two|one")); - ensure_equals("the other key's", texts(history, "item:b"), std::string("other")); - ensure("none for a key never saved", history.list("item:c").empty()); + REQUIRE(listed.size() == 2U); + CHECK(listed[0].when.secondsSinceEpoch() > listed[1].when.secondsSinceEpoch()); + { + INFO("what a listing reads"); + CHECK(listed[0].name == "Door"); + CHECK(listed[0].objectName == "House"); + CHECK(listed[0].region == "Ahern"); + CHECK(listed[0].asset == LLUUID::generateNewID("two")); + CHECK(listed[0].bytes == 3); + } + { + INFO("and not the text"); + CHECK_FALSE(listed[0].whole); + REQUIRE(listed[0].text.empty()); + } + UNSCOPED_INFO("read on asking"); + CHECK(require_texts(history, "item:a") == std::string("two|one")); + CHECK(require_texts(history, "item:b") == std::string("other")); + UNSCOPED_INFO("none for a key never saved"); + REQUIRE(history.list("item:c").empty()); } - template<> template<> - void alsavehistory_object::test<2>() + TEST_CASE_METHOD(alsavehistory_data, "ALSaveHistory: the text kept last is not kept again but a change back is", "[ALSaveHistory]") { - set_test_name("the text kept last is not kept again; one kept before that is, since it is a change back"); ALSaveHistory history(folder); const F64 now = LLDate::now().secondsSinceEpoch(); - ensure("kept", history.keep(saved("item:a", "one", now - 60.0))); - ensure("not again", !history.keep(saved("item:a", "one", now - 50.0))); - ensure("kept", history.keep(saved("item:a", "two", now - 40.0))); - ensure("back to one is a change", history.keep(saved("item:a", "one", now - 30.0))); - ensure_equals("three", texts(history, "item:a"), std::string("one|two|one")); - ensure("no key, nothing", !history.keep(saved("", "x", now))); + CHECK(history.keep(saved("item:a", "one", now - 60.0))); + CHECK_FALSE(history.keep(saved("item:a", "one", now - 50.0))); + CHECK(history.keep(saved("item:a", "two", now - 40.0))); + UNSCOPED_INFO("back to one is a change"); + CHECK(history.keep(saved("item:a", "one", now - 30.0))); + CHECK(require_texts(history, "item:a") == std::string("one|two|one")); + UNSCOPED_INFO("no key, nothing"); + CHECK_FALSE(history.keep(saved("", "x", now))); } - template<> template<> - void alsavehistory_object::test<3>() + TEST_CASE_METHOD(alsavehistory_data, "ALSaveHistory: the oldest go past so many for a key or past the age at a prune", "[ALSaveHistory]") { - set_test_name("past so many for a key, the oldest go as the next is kept; past the age, as it is pruned"); ALSaveHistory history(folder); history.limit(3, ALSaveHistory::MAX_AGE, ALSaveHistory::MAX_BYTES); const F64 now = LLDate::now().secondsSinceEpoch(); @@ -132,25 +137,27 @@ namespace tut { history.keep(saved("item:a", std::to_string(i), now - 100.0 + i)); } - ensure_equals("the newest three", texts(history, "item:a"), std::string("5|4|3")); + CHECK(require_texts(history, "item:a") == std::string("5|4|3")); ALSaveHistory aged(folder + "/aged"); aged.limit(50, 10.0 * DAY, ALSaveHistory::MAX_BYTES); aged.keep(saved("item:a", "old", THEN)); aged.keep(saved("item:a", "new", THEN + 8.0 * DAY)); aged.prune(LLDate(THEN + 9.0 * DAY)); - ensure_equals("both within the age", texts(aged, "item:a"), std::string("new|old")); + UNSCOPED_INFO("both within the age"); + CHECK(require_texts(aged, "item:a") == std::string("new|old")); aged.prune(LLDate(THEN + 12.0 * DAY)); - ensure_equals("the old one past it", texts(aged, "item:a"), std::string("new")); + UNSCOPED_INFO("the old one past it"); + CHECK(require_texts(aged, "item:a") == std::string("new")); aged.prune(LLDate(THEN + 30.0 * DAY)); - ensure("and then the other", aged.list("item:a").empty()); - ensure("its folder gone with it", std::filesystem::is_empty(fsyspath(folder + "/aged"))); + UNSCOPED_INFO("and then the other"); + REQUIRE(aged.list("item:a").empty()); + UNSCOPED_INFO("its folder gone with it"); + CHECK(std::filesystem::is_empty(fsyspath(folder + "/aged"))); } - template<> template<> - void alsavehistory_object::test<4>() + TEST_CASE_METHOD(alsavehistory_data, "ALSaveHistory: past so much in all the oldest of any key go first", "[ALSaveHistory]") { - set_test_name("past so much in all, the oldest of any key go first"); ALSaveHistory history(folder); const F64 now = LLDate::now().secondsSinceEpoch(); history.keep(saved("item:a", "aaaaaaaaaa", now - 40.0)); @@ -161,34 +168,35 @@ namespace tut const size_t room = size(history.list("item:a").front()) + size(history.list("item:b").front()); history.limit(50, 1e12, room); history.prune(); - ensure_equals("the oldest gone", texts(history, "item:a"), std::string("cccccccccc")); - ensure_equals("another key's newer one kept", texts(history, "item:b"), std::string("bbbbbbbbbb")); + UNSCOPED_INFO("the oldest gone"); + CHECK(require_texts(history, "item:a") == std::string("cccccccccc")); + UNSCOPED_INFO("another key's newer one kept"); + CHECK(require_texts(history, "item:b") == std::string("bbbbbbbbbb")); } - template<> template<> - void alsavehistory_object::test<5>() + TEST_CASE_METHOD(alsavehistory_data, "ALSaveHistory: rekey moves a key's saves behind another's under the new key", "[ALSaveHistory]") { - set_test_name("a key's saves moved to another, behind what that has, saying the new key; the old one empty"); ALSaveHistory history(folder); const F64 now = LLDate::now().secondsSinceEpoch(); history.keep(saved("task:p:old", "one", now - 60.0)); history.keep(saved("task:p:old", "two", now - 50.0)); history.keep(saved("task:p:new", "three", now - 40.0)); - ensure("moved", history.rekey("task:p:old", "task:p:new")); - ensure("none left", history.list("task:p:old").empty()); - ensure_equals("all under the new, in time's order", texts(history, "task:p:new"), std::string("three|two|one")); + CHECK(history.rekey("task:p:old", "task:p:new")); + REQUIRE(history.list("task:p:old").empty()); + UNSCOPED_INFO("all under the new, in time's order"); + CHECK(require_texts(history, "task:p:new") == std::string("three|two|one")); for (const ALSavedText& one : history.list("task:p:new")) { - ensure_equals("saying the new key", one.key, std::string("task:p:new")); + REQUIRE(one.key == std::string("task:p:new")); } - ensure("nothing to move", !history.rekey("task:p:gone", "task:p:new")); - ensure("not to itself", !history.rekey("task:p:new", "task:p:new")); + UNSCOPED_INFO("nothing to move"); + CHECK_FALSE(history.rekey("task:p:gone", "task:p:new")); + UNSCOPED_INFO("not to itself"); + CHECK_FALSE(history.rekey("task:p:new", "task:p:new")); } - template<> template<> - void alsavehistory_object::test<6>() + TEST_CASE_METHOD(alsavehistory_data, "ALSaveHistory: an unreadable file is passed over and a half write is swept", "[ALSaveHistory]") { - set_test_name("a file that cannot be read is passed over, and what a write cut short is swept"); ALSaveHistory history(folder); const F64 now = LLDate::now().secondsSinceEpoch(); history.keep(saved("item:a", "one", now - 60.0)); @@ -202,39 +210,40 @@ namespace tut llofstream half(dir + "2.llsd" + std::string(ALFileWrite::BESIDE)); half << "{'key':'item:a'"; } - ensure_equals("the one readable", history.list("item:a").size(), 1U); + UNSCOPED_INFO("the one readable"); + REQUIRE(history.list("item:a").size() == 1U); history.prune(); - ensure("the half-written swept", !LLFile::isfile(dir + "2.llsd" + std::string(ALFileWrite::BESIDE))); - ensure_equals("the save still there", texts(history, "item:a"), std::string("one")); + UNSCOPED_INFO("the half-written swept"); + CHECK_FALSE(LLFile::isfile(dir + "2.llsd" + std::string(ALFileWrite::BESIDE))); + UNSCOPED_INFO("the save still there"); + CHECK(require_texts(history, "item:a") == std::string("one")); } - template<> template<> - void alsavehistory_object::test<7>() + TEST_CASE_METHOD(alsavehistory_data, "ALSaveHistory: count counts a key's saves by file name and not half writes", "[ALSaveHistory]") { - set_test_name("a key's saves counted by their files' names, each key its own; what is no save of the name not counted"); ALSaveHistory history(folder); const F64 now = LLDate::now().secondsSinceEpoch(); - ensure_equals("none for a key never saved", history.count("item:a"), 0U); - ensure_equals("none for no key", history.count(""), 0U); + UNSCOPED_INFO("none for a key never saved"); + REQUIRE(history.count("item:a") == 0U); + REQUIRE(history.count("") == 0U); history.keep(saved("item:a", "one", now - 60.0)); history.keep(saved("item:a", "two", now - 50.0)); history.keep(saved("item:b", "other", now - 40.0)); - ensure_equals("as many as listed", history.count("item:a"), history.list("item:a").size()); - ensure_equals("two", history.count("item:a"), 2U); - ensure_equals("the other key's", history.count("item:b"), 1U); + REQUIRE(history.count("item:a") == history.list("item:a").size()); + REQUIRE(history.count("item:a") == 2U); + REQUIRE(history.count("item:b") == 1U); const std::string kept = history.list("item:a").front().path; const std::string dir = kept.substr(0, kept.find_last_of('/') + 1); { llofstream half(dir + "3.llsd" + std::string(ALFileWrite::BESIDE)); half << "{'key':'item:a'"; } - ensure_equals("what a write cut short is none", history.count("item:a"), 2U); + UNSCOPED_INFO("what a write cut short is none"); + REQUIRE(history.count("item:a") == 2U); } - template<> template<> - void alsavehistory_object::test<8>() + TEST_CASE_METHOD(alsavehistory_data, "ALSaveHistory: holds finds a text by its asset or its text among a key's newest", "[ALSaveHistory]") { - set_test_name("a text kept already, by the asset its save made or by the text, among a key's newest; not past them, nor another key's"); ALSaveHistory history(folder); const F64 now = LLDate::now().secondsSinceEpoch(); history.keep(saved("item:a", "one", now - 60.0)); @@ -242,19 +251,30 @@ namespace tut history.keep(saved("item:a", "three", now - 40.0)); history.keep(saved("item:b", "other", now - 30.0)); const LLUUID elsewhere = LLUUID::generateNewID("elsewhere"); - ensure("by the asset its save made, whatever the text", history.holds("item:a", LLUUID::generateNewID("two"), "not it", 10)); + UNSCOPED_INFO("by the asset its save made, whatever the text"); + CHECK(history.holds("item:a", LLUUID::generateNewID("two"), "not it", 10)); // The same text sent again made another asset, and was not kept // again: found by the text. - ensure("by the text, under an asset no save made", history.holds("item:a", elsewhere, "one", 10)); - ensure("a text of the same length that is not one", !history.holds("item:a", elsewhere, "owe", 10)); - ensure("nor one never kept", !history.holds("item:a", elsewhere, "four", 10)); - ensure("nor without an asset", !history.holds("item:a", LLUUID::null, "four", 10)); + UNSCOPED_INFO("by the text, under an asset no save made"); + CHECK(history.holds("item:a", elsewhere, "one", 10)); + UNSCOPED_INFO("a text of the same length that is not one"); + CHECK_FALSE(history.holds("item:a", elsewhere, "owe", 10)); + UNSCOPED_INFO("nor one never kept"); + CHECK_FALSE(history.holds("item:a", elsewhere, "four", 10)); + UNSCOPED_INFO("nor without an asset"); + CHECK_FALSE(history.holds("item:a", LLUUID::null, "four", 10)); // Among the newest two only: "one" is the third. - ensure("the newest", history.holds("item:a", elsewhere, "three", 2)); - ensure("past the newest, by the text", !history.holds("item:a", elsewhere, "one", 2)); - ensure("past the newest, by the asset", !history.holds("item:a", LLUUID::generateNewID("one"), "x", 2)); - ensure("another key's is not this one's", !history.holds("item:a", LLUUID::generateNewID("other"), "other", 10)); - ensure("nothing for a key never saved", !history.holds("item:c", LLUUID::generateNewID("one"), "one", 10)); - ensure("nor for no key", !history.holds("", LLUUID::generateNewID("one"), "one", 10)); + UNSCOPED_INFO("the newest"); + CHECK(history.holds("item:a", elsewhere, "three", 2)); + UNSCOPED_INFO("past the newest, by the text"); + CHECK_FALSE(history.holds("item:a", elsewhere, "one", 2)); + UNSCOPED_INFO("past the newest, by the asset"); + CHECK_FALSE(history.holds("item:a", LLUUID::generateNewID("one"), "x", 2)); + UNSCOPED_INFO("another key's is not this one's"); + CHECK_FALSE(history.holds("item:a", LLUUID::generateNewID("other"), "other", 10)); + UNSCOPED_INFO("nothing for a key never saved"); + CHECK_FALSE(history.holds("item:c", LLUUID::generateNewID("one"), "one", 10)); + UNSCOPED_INFO("nor for no key"); + CHECK_FALSE(history.holds("", LLUUID::generateNewID("one"), "one", 10)); } } diff --git a/indra/llcommon/tests/alserialworker_test.cpp b/indra/llcommon/tests/alserialworker_test.cpp index 0d6ba7aab1..ebf8df2572 100644 --- a/indra/llcommon/tests/alserialworker_test.cpp +++ b/indra/llcommon/tests/alserialworker_test.cpp @@ -30,7 +30,7 @@ #include "../llevents.h" #include "../llsdutil.h" -#include "../test/lltut.h" +#include #include #include @@ -38,7 +38,7 @@ #include #include -namespace tut +namespace { struct alserialworker_data { @@ -71,31 +71,28 @@ namespace tut return false; } }; - typedef test_group alserialworker_group; - typedef alserialworker_group::object alserialworker_object; - alserialworker_group alserialworker_instance("alserialworker"); - template<> template<> - void alserialworker_object::test<1>() + TEST_CASE_METHOD(alserialworker_data, "alserialworker: jobs run in order on one thread of their own", "[alserialworker]") { - set_test_name("jobs run one after another, on a thread of their own"); ALSerialWorker worker("alserialworker_test_1"); for (int n = 0; n < 50; ++n) { - ensure("taken", worker.post([this, n] { note(n); })); + REQUIRE(worker.post([this, n] { note(n); })); } - ensure("all done", waitFor(50)); + REQUIRE(waitFor(50)); for (int n = 0; n < 50; ++n) { - ensure("in the order posted", done[n] == n); - ensure("on one thread, not this one", where[n] == where[0] && where[n] != std::this_thread::get_id()); + REQUIRE(done[n] == n); + { + INFO("on one thread, not this one"); + REQUIRE(where[n] == where[0]); + REQUIRE(where[n] != std::this_thread::get_id()); + } } } - template<> template<> - void alserialworker_object::test<2>() + TEST_CASE_METHOD(alserialworker_data, "alserialworker: close stops the running job and drops waiting and later ones", "[alserialworker]") { - set_test_name("closed: the running job is stopped by the owner's stop, what waits is passed over, and a job after is refused"); std::atomic running{ false }; std::atomic stopped{ false }; ALSerialWorker worker("alserialworker_test_2", [&stopped] { stopped = true; }); @@ -116,27 +113,34 @@ namespace tut { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - ensure("the first is running", running.load()); + UNSCOPED_INFO("the first is running"); + CHECK(running.load()); worker.close(); - ensure("told to stop", stopped.load() && worker.closing()); - ensure_equals("it returned, and nothing waiting ran", done.size(), size_t(1)); + { + INFO("told to stop"); + CHECK(stopped.load()); + CHECK(worker.closing()); + } + UNSCOPED_INFO("it returned, and nothing waiting ran"); + REQUIRE(done.size() == size_t(1)); bool ran = false; - ensure("refused after", !worker.post([&ran] { ran = true; }) && !ran); + CHECK_FALSE(worker.post([&ran] { ran = true; })); + CHECK_FALSE(ran); worker.close(); - ensure("closed twice is closed", worker.closing()); + UNSCOPED_INFO("closed twice is closed"); + CHECK(worker.closing()); } - template<> template<> - void alserialworker_object::test<3>() + TEST_CASE_METHOD(alserialworker_data, "alserialworker: the viewer starting to quit closes it at once", "[alserialworker]") { - set_test_name("the viewer starting to quit closes it at once"); ALSerialWorker worker("alserialworker_test_3"); - ensure("taken", worker.post([this] { note(1); })); - ensure("done", waitFor(1)); + CHECK(worker.post([this] { note(1); })); + CHECK(waitFor(1)); LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", "running")); - ensure("running is nothing", !worker.closing()); + UNSCOPED_INFO("running is nothing"); + CHECK_FALSE(worker.closing()); LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", "quitting")); - ensure("closed", worker.closing()); - ensure("refused", !worker.post([this] { note(2); })); + CHECK(worker.closing()); + CHECK_FALSE(worker.post([this] { note(2); })); } } diff --git a/indra/llcommon/tests/alsimd_test.cpp b/indra/llcommon/tests/alsimd_test.cpp index 15629e19d4..d722e83246 100644 --- a/indra/llcommon/tests/alsimd_test.cpp +++ b/indra/llcommon/tests/alsimd_test.cpp @@ -34,7 +34,7 @@ #include "linden_common.h" #include "../alsimd.h" -#include "../test/lltut.h" +#include #include #include @@ -46,8 +46,6 @@ using namespace alsimd; -namespace tut -{ namespace { struct Lanes @@ -106,20 +104,21 @@ namespace return (U32)(d < 0 ? -d : d); } - void ensure_lanes_bits(const std::string& what, f32x4 got, U32 x, U32 y, U32 z, U32 w) + void require_lanes_bits(const std::string& what, f32x4 got, U32 x, U32 y, U32 z, U32 w) { const Lanes l = lanes_of(got); const U32 expected[4] = {x, y, z, w}; for (int i = 0; i < 4; ++i) { - ensure_equals(what + " lane " + std::to_string(i), bits_of(l.v[i]), expected[i]); + INFO(what << " lane " << i); + REQUIRE(bits_of(l.v[i]) == expected[i]); } } #if AL_SIMD_DISTINCT_MASK - void ensure_lanes_bits(const std::string& what, mask4 got, U32 x, U32 y, U32 z, U32 w) + void require_lanes_bits(const std::string& what, mask4 got, U32 x, U32 y, U32 z, U32 w) { - ensure_lanes_bits(what, as_f32(got), x, y, z, w); + require_lanes_bits(what, as_f32(got), x, y, z, w); } #endif @@ -129,9 +128,9 @@ namespace return as_i32(set(std::bit_cast(x), std::bit_cast(y), std::bit_cast(z), std::bit_cast(w))); } - void ensure_lanes(const std::string& what, f32x4 got, F32 x, F32 y, F32 z, F32 w) + void require_lanes(const std::string& what, f32x4 got, F32 x, F32 y, F32 z, F32 w) { - ensure_lanes_bits(what, got, bits_of(x), bits_of(y), bits_of(z), bits_of(w)); + require_lanes_bits(what, got, bits_of(x), bits_of(y), bits_of(z), bits_of(w)); } std::string precise(double v) @@ -144,15 +143,15 @@ namespace // The error is measured in double, so that the fast floating-point // contract the test itself is built under cannot approximate the // division that measures an approximation. - void ensure_lanes_relative(const std::string& what, f32x4 got, const double* expected, double relative) + void require_lanes_relative(const std::string& what, f32x4 got, const double* expected, double relative) { const Lanes l = lanes_of(got); for (int i = 0; i < 4; ++i) { const double err = std::fabs((double)l.v[i] - expected[i]) / std::fabs(expected[i]); - ensure(what + " lane " + std::to_string(i) + ": " + precise(l.v[i]) + " is off " + - precise(err) + " relative from " + precise(expected[i]), - err <= relative); + INFO(what << " lane " << i << ": " << precise(l.v[i]) << " is off " << precise(err) << " relative from " + << precise(expected[i])); + REQUIRE(err <= relative); } } @@ -163,10 +162,9 @@ namespace const F32 NAN_QUIET = std::numeric_limits::quiet_NaN(); const F32 DENORMAL = std::numeric_limits::denorm_min(); const F32 DENORMAL_BIG = float_of(0x007fffffu); -} -} // namespace tut +} // anonymous namespace -namespace tut +namespace { struct alsimd_data { @@ -179,78 +177,67 @@ namespace tut f32x4 a() const { return set(float_of(pa[0]), float_of(pa[1]), float_of(pa[2]), float_of(pa[3])); } f32x4 b() const { return set(float_of(pb[0]), float_of(pb[1]), float_of(pb[2]), float_of(pb[3])); } }; - typedef test_group alsimd_test; - typedef alsimd_test::object alsimd_object; - tut::alsimd_test alsimd_testcase("alsimd"); - // The register is sixteen bytes at sixteen, on every target. - template<> template<> - void alsimd_object::test<1>() + TEST_CASE_METHOD(alsimd_data, "alsimd: the register is sixteen bytes at sixteen on every target", "[alsimd]") { - ensure_equals("sizeof f32x4", sizeof(f32x4), (size_t)16); - ensure_equals("alignof f32x4", alignof(f32x4), (size_t)16); - ensure_equals("sizeof mask4", sizeof(mask4), (size_t)16); - ensure_equals("sizeof i32x4", sizeof(i32x4), (size_t)16); + CHECK(sizeof(f32x4) == (size_t)16); + CHECK(alignof(f32x4) == (size_t)16); + CHECK(sizeof(mask4) == (size_t)16); + CHECK(sizeof(i32x4) == (size_t)16); } - // Loads and stores move bits. - template<> template<> - void alsimd_object::test<2>() + TEST_CASE_METHOD(alsimd_data, "alsimd: loads and stores move bits", "[alsimd]") { alignas(16) F32 src[5] = {float_of(pa[0]), float_of(pa[1]), float_of(pa[2]), float_of(pa[3]), 99.f}; - ensure_lanes_bits("load", load(src), pa[0], pa[1], pa[2], pa[3]); - ensure_lanes_bits("loadu", loadu(src + 1), pa[1], pa[2], pa[3], bits_of(99.f)); - ensure_lanes_bits("load3", load3(src), pa[0], pa[1], pa[2], 0u); - ensure_lanes_bits("load3 offset", load3(src + 1), pa[1], pa[2], pa[3], 0u); + require_lanes_bits("load", load(src), pa[0], pa[1], pa[2], pa[3]); + require_lanes_bits("loadu", loadu(src + 1), pa[1], pa[2], pa[3], bits_of(99.f)); + require_lanes_bits("load3", load3(src), pa[0], pa[1], pa[2], 0u); + require_lanes_bits("load3 offset", load3(src + 1), pa[1], pa[2], pa[3], 0u); alignas(16) F32 dst[8] = {}; store(dst, a()); - ensure_equals("store", std::memcmp(dst, src, 16), 0); + CHECK(std::memcmp(dst, src, 16) == 0); storeu(dst + 1, a()); - ensure_equals("storeu", std::memcmp(dst + 1, src, 16), 0); + CHECK(std::memcmp(dst + 1, src, 16) == 0); - ensure_lanes_bits("set", a(), pa[0], pa[1], pa[2], pa[3]); - ensure_lanes("set1", set1(-2.5f), -2.5f, -2.5f, -2.5f, -2.5f); - ensure_lanes("zero", zero(), 0.f, 0.f, 0.f, 0.f); + require_lanes_bits("set", a(), pa[0], pa[1], pa[2], pa[3]); + require_lanes("set1", set1(-2.5f), -2.5f, -2.5f, -2.5f, -2.5f); + require_lanes("zero", zero(), 0.f, 0.f, 0.f, 0.f); } - // Lanes and permutations are bit moves. - template<> template<> - void alsimd_object::test<3>() + TEST_CASE_METHOD(alsimd_data, "alsimd: lanes and permutations are bit moves", "[alsimd]") { - ensure_equals("lane<0>", bits_of(lane<0>(a())), pa[0]); - ensure_equals("lane<1>", bits_of(lane<1>(a())), pa[1]); - ensure_equals("lane<2>", bits_of(lane<2>(a())), pa[2]); - ensure_equals("lane<3>", bits_of(lane<3>(a())), pa[3]); - - ensure_lanes_bits("splat<0>", splat<0>(a()), pa[0], pa[0], pa[0], pa[0]); - ensure_lanes_bits("splat<1>", splat<1>(a()), pa[1], pa[1], pa[1], pa[1]); - ensure_lanes_bits("splat<2>", splat<2>(a()), pa[2], pa[2], pa[2], pa[2]); - ensure_lanes_bits("splat<3>", splat<3>(a()), pa[3], pa[3], pa[3], pa[3]); - - ensure_lanes_bits("shuffle yzxw", shuffle<1, 2, 0, 3>(a()), pa[1], pa[2], pa[0], pa[3]); - ensure_lanes_bits("shuffle zxyw", shuffle<2, 0, 1, 3>(a()), pa[2], pa[0], pa[1], pa[3]); - ensure_lanes_bits("shuffle yxwz", shuffle<1, 0, 3, 2>(a()), pa[1], pa[0], pa[3], pa[2]); - ensure_lanes_bits("shuffle zwxy", shuffle<2, 3, 0, 1>(a()), pa[2], pa[3], pa[0], pa[1]); - ensure_lanes_bits("shuffle wwwx", shuffle<3, 3, 3, 0>(a()), pa[3], pa[3], pa[3], pa[0]); - ensure_lanes_bits("shuffle xyyx", shuffle<0, 1, 1, 0>(a()), pa[0], pa[1], pa[1], pa[0]); - ensure_lanes_bits("shuffle zzyw", shuffle<2, 2, 1, 3>(a()), pa[2], pa[2], pa[1], pa[3]); - ensure_lanes_bits("shuffle wzyx", shuffle<3, 2, 1, 0>(a()), pa[3], pa[2], pa[1], pa[0]); - - ensure_lanes_bits("shuffle2", shuffle2<0, 2, 1, 3>(a(), b()), pa[0], pa[2], pb[1], pb[3]); - ensure_lanes_bits("shuffle2 movelh", shuffle2<0, 1, 0, 1>(a(), b()), pa[0], pa[1], pb[0], pb[1]); - ensure_lanes_bits("shuffle2 high", shuffle2<2, 3, 2, 3>(a(), b()), pa[2], pa[3], pb[2], pb[3]); - ensure_lanes_bits("shuffle2 wxzy", shuffle2<3, 0, 2, 1>(a(), b()), pa[3], pa[0], pb[2], pb[1]); - - ensure_lanes_bits("movelh", movelh(a(), b()), pa[0], pa[1], pb[0], pb[1]); - ensure_lanes_bits("movehl", movehl(a(), b()), pb[2], pb[3], pa[2], pa[3]); - ensure_lanes_bits("unpacklo", unpacklo(a(), b()), pa[0], pb[0], pa[1], pb[1]); - ensure_lanes_bits("unpackhi", unpackhi(a(), b()), pa[2], pb[2], pa[3], pb[3]); + CHECK(bits_of(lane<0>(a())) == pa[0]); + CHECK(bits_of(lane<1>(a())) == pa[1]); + CHECK(bits_of(lane<2>(a())) == pa[2]); + CHECK(bits_of(lane<3>(a())) == pa[3]); + + require_lanes_bits("splat<0>", splat<0>(a()), pa[0], pa[0], pa[0], pa[0]); + require_lanes_bits("splat<1>", splat<1>(a()), pa[1], pa[1], pa[1], pa[1]); + require_lanes_bits("splat<2>", splat<2>(a()), pa[2], pa[2], pa[2], pa[2]); + require_lanes_bits("splat<3>", splat<3>(a()), pa[3], pa[3], pa[3], pa[3]); + + require_lanes_bits("shuffle yzxw", shuffle<1, 2, 0, 3>(a()), pa[1], pa[2], pa[0], pa[3]); + require_lanes_bits("shuffle zxyw", shuffle<2, 0, 1, 3>(a()), pa[2], pa[0], pa[1], pa[3]); + require_lanes_bits("shuffle yxwz", shuffle<1, 0, 3, 2>(a()), pa[1], pa[0], pa[3], pa[2]); + require_lanes_bits("shuffle zwxy", shuffle<2, 3, 0, 1>(a()), pa[2], pa[3], pa[0], pa[1]); + require_lanes_bits("shuffle wwwx", shuffle<3, 3, 3, 0>(a()), pa[3], pa[3], pa[3], pa[0]); + require_lanes_bits("shuffle xyyx", shuffle<0, 1, 1, 0>(a()), pa[0], pa[1], pa[1], pa[0]); + require_lanes_bits("shuffle zzyw", shuffle<2, 2, 1, 3>(a()), pa[2], pa[2], pa[1], pa[3]); + require_lanes_bits("shuffle wzyx", shuffle<3, 2, 1, 0>(a()), pa[3], pa[2], pa[1], pa[0]); + + require_lanes_bits("shuffle2", shuffle2<0, 2, 1, 3>(a(), b()), pa[0], pa[2], pb[1], pb[3]); + require_lanes_bits("shuffle2 movelh", shuffle2<0, 1, 0, 1>(a(), b()), pa[0], pa[1], pb[0], pb[1]); + require_lanes_bits("shuffle2 high", shuffle2<2, 3, 2, 3>(a(), b()), pa[2], pa[3], pb[2], pb[3]); + require_lanes_bits("shuffle2 wxzy", shuffle2<3, 0, 2, 1>(a(), b()), pa[3], pa[0], pb[2], pb[1]); + + require_lanes_bits("movelh", movelh(a(), b()), pa[0], pa[1], pb[0], pb[1]); + require_lanes_bits("movehl", movehl(a(), b()), pb[2], pb[3], pa[2], pa[3]); + require_lanes_bits("unpacklo", unpacklo(a(), b()), pa[0], pb[0], pa[1], pb[1]); + require_lanes_bits("unpackhi", unpackhi(a(), b()), pa[2], pb[2], pa[3], pb[3]); } - // Arithmetic is one rounding per lane. - template<> template<> - void alsimd_object::test<4>() + TEST_CASE_METHOD(alsimd_data, "alsimd: arithmetic is one rounding per lane", "[alsimd]") { const F32 xs[4] = {1.5f, -3.25f, 1e-3f, 65535.f}; const F32 ys[4] = {2.75f, 0.125f, 3.f, -7.f}; @@ -258,6 +245,7 @@ namespace tut const f32x4 y = loadu(ys); for (int i = 0; i < 4; ++i) { + CAPTURE(i); volatile F32 sum = xs[i] + ys[i]; volatile F32 diff = xs[i] - ys[i]; volatile F32 prod = xs[i] * ys[i]; @@ -266,31 +254,32 @@ namespace tut const Lanes d = lanes_of(sub(x, y)); const Lanes p = lanes_of(mul(x, y)); const Lanes q = lanes_of(div(x, y)); - ensure_equals("add", bits_of(s.v[i]), bits_of(sum)); - ensure_equals("sub", bits_of(d.v[i]), bits_of(diff)); - ensure_equals("mul", bits_of(p.v[i]), bits_of(prod)); - ensure("div within an ulp", ulps_between(q.v[i], quot) <= 1); + REQUIRE(bits_of(s.v[i]) == bits_of(sum)); + REQUIRE(bits_of(d.v[i]) == bits_of(diff)); + REQUIRE(bits_of(p.v[i]) == bits_of(prod)); + REQUIRE(ulps_between(q.v[i], quot) <= 1); } // A negative zero as its bits, in and out: /fp:fast lets the compiler // take -0.f for 0.f, and MSVC for arm64 does. const f32x4 sign = set1_bits(0x80000000u); - ensure_lanes_bits("neg", neg(or_(set(1.f, -2.f, 0.f, 0.f), and_(sign, mask_lane<3>()))), + require_lanes_bits("neg", neg(or_(set(1.f, -2.f, 0.f, 0.f), and_(sign, mask_lane<3>()))), bits_of(-1.f), bits_of(2.f), 0x80000000u, 0u); - ensure_lanes_bits("abs", abs(or_(set(1.f, -2.f, 0.f, -INF), and_(sign, mask_lane<2>()))), + require_lanes_bits("abs", abs(or_(set(1.f, -2.f, 0.f, -INF), and_(sign, mask_lane<2>()))), bits_of(1.f), bits_of(2.f), 0u, bits_of(INF)); - ensure_equals("neg keeps a NaN payload", bits_of(lane<2>(neg(a()))), pa[2] ^ 0x80000000u); - ensure_equals("abs keeps a NaN payload", bits_of(lane<2>(abs(neg(a())))), pa[2]); + UNSCOPED_INFO("neg keeps a NaN payload"); + CHECK(bits_of(lane<2>(neg(a()))) == (pa[2] ^ 0x80000000u)); + UNSCOPED_INFO("abs keeps a NaN payload"); + CHECK(bits_of(lane<2>(abs(neg(a())))) == pa[2]); // Denormals pass through: the ops do not rely on flush-to-zero. - ensure_lanes("denormal mul", mul(set(DENORMAL, DENORMAL_BIG, 0.f, 0.f), set1(1.f)), DENORMAL, DENORMAL_BIG, 0.f, 0.f); - ensure_lanes("denormal add", add(set(DENORMAL, 0.f, 0.f, 0.f), set(DENORMAL, 0.f, 0.f, 0.f)), 2 * DENORMAL, 0.f, 0.f, 0.f); + require_lanes("denormal mul", mul(set(DENORMAL, DENORMAL_BIG, 0.f, 0.f), set1(1.f)), DENORMAL, DENORMAL_BIG, 0.f, 0.f); + require_lanes("denormal add", add(set(DENORMAL, 0.f, 0.f, 0.f), set(DENORMAL, 0.f, 0.f, 0.f)), 2 * DENORMAL, 0.f, 0.f, 0.f); } // Fused multiply-add: exact against fma where the machine fuses, exact // against two roundings where it does not. - template<> template<> - void alsimd_object::test<5>() + TEST_CASE_METHOD(alsimd_data, "alsimd: fused multiply-add matches fma or two roundings as the machine does", "[alsimd]") { const F32 as[4] = {1.5f, -3.25f, 1e-3f, 65535.f}; const F32 bs[4] = {2.75f, 0.125f, 3.f, -7.f}; @@ -304,6 +293,7 @@ namespace tut const Lanes r_lane = lanes_of(fmadd_lane<2>(a, b, c)); for (int i = 0; i < 4; ++i) { + CAPTURE(i); F32 e_fmadd, e_fmsub, e_fnmadd, e_lane; if constexpr (AL_SIMD_FMA) { @@ -321,36 +311,35 @@ namespace tut e_fnmadd = cs[i] - p; e_lane = pl + cs[i]; } - ensure_equals("fmadd", bits_of(r_fmadd.v[i]), bits_of(e_fmadd)); - ensure_equals("fmsub", bits_of(r_fmsub.v[i]), bits_of(e_fmsub)); - ensure_equals("fnmadd", bits_of(r_fnmadd.v[i]), bits_of(e_fnmadd)); - ensure_equals("fmadd_lane", bits_of(r_lane.v[i]), bits_of(e_lane)); + REQUIRE(bits_of(r_fmadd.v[i]) == bits_of(e_fmadd)); + REQUIRE(bits_of(r_fmsub.v[i]) == bits_of(e_fmsub)); + REQUIRE(bits_of(r_fnmadd.v[i]) == bits_of(e_fnmadd)); + REQUIRE(bits_of(r_lane.v[i]) == bits_of(e_lane)); } } // min, max and sqrt on ordinary values. A NaN or a zero of either sign // in min and max is the backend's to decide and is not tested. - template<> template<> - void alsimd_object::test<6>() + TEST_CASE_METHOD(alsimd_data, "alsimd: min and max and sqrt on ordinary values", "[alsimd]") { const f32x4 x = set(1.f, -2.f, 3.5f, -INF); const f32x4 y = set(0.5f, -1.f, 3.5f, 4.f); - ensure_lanes("min", min(x, y), 0.5f, -2.f, 3.5f, -INF); - ensure_lanes("max", max(x, y), 1.f, -1.f, 3.5f, 4.f); + require_lanes("min", min(x, y), 0.5f, -2.f, 3.5f, -INF); + require_lanes("max", max(x, y), 1.f, -1.f, 3.5f, 4.f); const F32 sq[4] = {4.f, 2.f, 1e-6f, 1e12f}; const f32x4 s = sqrt(loadu(sq)); const Lanes l = lanes_of(s); for (int i = 0; i < 4; ++i) { - ensure_equals("sqrt", bits_of(l.v[i]), bits_of(std::sqrt(sq[i]))); + CAPTURE(i); + REQUIRE(bits_of(l.v[i]) == bits_of(std::sqrt(sq[i]))); } } // The estimates, within what each promises, across the magnitudes a // length or a quantization range takes. - template<> template<> - void alsimd_object::test<7>() + TEST_CASE_METHOD(alsimd_data, "alsimd: the estimates are within what each promises", "[alsimd]") { const F32 xs[4] = {1.f, 0.0001f, 12345.678f, 3e20f}; double e_rsqrt[4], e_rcp[4]; @@ -365,10 +354,10 @@ namespace tut // arithmetic around it adds its own few ulps. const double fast = 1.0 / 2048.0; const double full = 1.0 / 1048576.0; - ensure_lanes_relative("rsqrt_fast", rsqrt_fast(x), e_rsqrt, fast); - ensure_lanes_relative("rsqrt", rsqrt(x), e_rsqrt, full); - ensure_lanes_relative("rcp_fast", rcp_fast(x), e_rcp, fast); - ensure_lanes_relative("rcp", rcp(x), e_rcp, full); + require_lanes_relative("rsqrt_fast", rsqrt_fast(x), e_rsqrt, fast); + require_lanes_relative("rsqrt", rsqrt(x), e_rsqrt, full); + require_lanes_relative("rcp_fast", rcp_fast(x), e_rcp, fast); + require_lanes_relative("rcp", rcp(x), e_rcp, full); const F32 small[4] = {1e-10f, 2.f, 1e-20f, 7.f}; double e_small_rsqrt[4], e_small_rcp[4]; @@ -377,16 +366,15 @@ namespace tut e_small_rsqrt[i] = 1.0 / std::sqrt((double)small[i]); e_small_rcp[i] = 1.0 / (double)small[i]; } - ensure_lanes_relative("rsqrt small", rsqrt(loadu(small)), e_small_rsqrt, full); - ensure_lanes_relative("rcp small", rcp(loadu(small)), e_small_rcp, full); + require_lanes_relative("rsqrt small", rsqrt(loadu(small)), e_small_rsqrt, full); + require_lanes_relative("rcp small", rcp(loadu(small)), e_small_rcp, full); } // Rounding: nearest with ties to even, floor and ceiling, including the // halves, the negatives and the integers already there. A result of zero // keeps the sign of its input from SSE4 up and on NEON; the SSE2 forms // below that go through an integer and lose it. - template<> template<> - void alsimd_object::test<8>() + TEST_CASE_METHOD(alsimd_data, "alsimd: round to nearest even and floor and ceil including the halves", "[alsimd]") { const F32 xs[8] = {2.5f, 3.5f, -2.5f, -3.5f, 0.25f, -1.75f, 1234567.f, -7.f}; for (int base = 0; base < 8; base += 4) @@ -400,97 +388,95 @@ namespace tut for (int i = 0; i < 4; ++i) { const F32 v = xs[base + i]; - ensure_equals("round " + std::to_string(v), bits_of(r.v[i]), bits_of(std::nearbyint(v))); - ensure_equals("floor " + std::to_string(v), bits_of(f.v[i]), bits_of(std::floor(v))); - ensure_equals("ceil " + std::to_string(v), bits_of(c.v[i]), bits_of(std::ceil(v))); - ensure_equals("cvt_round " + std::to_string(v), int_lane(cr, i), (S32)std::nearbyint(v)); - ensure_equals("cvt_trunc " + std::to_string(v), int_lane(ct, i), (S32)v); + CAPTURE(v); + REQUIRE(bits_of(r.v[i]) == bits_of(std::nearbyint(v))); + REQUIRE(bits_of(f.v[i]) == bits_of(std::floor(v))); + REQUIRE(bits_of(c.v[i]) == bits_of(std::ceil(v))); + REQUIRE(int_lane(cr, i) == (S32)std::nearbyint(v)); + REQUIRE(int_lane(ct, i) == (S32)v); } } const f32x4 near_zero = set(-0.f, -0.25f, 0.f, -0.5f); if constexpr (AL_SIMD_SSE4 || AL_SIMD_NEON) { - ensure_lanes("round keeps the sign of zero", round(near_zero), -0.f, -0.f, 0.f, -0.f); - ensure_lanes("ceil keeps the sign of zero", ceil(near_zero), -0.f, -0.f, 0.f, -0.f); - ensure_lanes("floor keeps the sign of zero", floor(set(-0.f, 0.f, 0.25f, 0.f)), -0.f, 0.f, 0.f, 0.f); + require_lanes("round keeps the sign of zero", round(near_zero), -0.f, -0.f, 0.f, -0.f); + require_lanes("ceil keeps the sign of zero", ceil(near_zero), -0.f, -0.f, 0.f, -0.f); + require_lanes("floor keeps the sign of zero", floor(set(-0.f, 0.f, 0.25f, 0.f)), -0.f, 0.f, 0.f, 0.f); } else { - ensure_lanes("round to zero below SSE4", round(near_zero), 0.f, 0.f, 0.f, 0.f); - ensure_lanes("ceil to zero below SSE4", ceil(near_zero), 0.f, 0.f, 0.f, 0.f); + require_lanes("round to zero below SSE4", round(near_zero), 0.f, 0.f, 0.f, 0.f); + require_lanes("ceil to zero below SSE4", ceil(near_zero), 0.f, 0.f, 0.f, 0.f); } - ensure_lanes("to_float", to_float(ints(0, -1, 16777216, -12345678)), 0.f, -1.f, 16777216.f, -12345678.f); + require_lanes("to_float", to_float(ints(0, -1, 16777216, -12345678)), 0.f, -1.f, 16777216.f, -12345678.f); const Lanes back = lanes_of(cvt_trunc(set(0.f, -1.f, 16777216.f, -12345678.f))); - ensure_equals("cvt_trunc round trip", int_lane(back, 3), -12345678); + UNSCOPED_INFO("cvt_trunc round trip"); + CHECK(int_lane(back, 3) == -12345678); } - // Bit operations move bits. - template<> template<> - void alsimd_object::test<9>() + TEST_CASE_METHOD(alsimd_data, "alsimd: bit operations move bits", "[alsimd]") { - ensure_lanes_bits("and", and_(a(), b()), pa[0] & pb[0], pa[1] & pb[1], pa[2] & pb[2], pa[3] & pb[3]); - ensure_lanes_bits("andnot", andnot(a(), b()), ~pa[0] & pb[0], ~pa[1] & pb[1], ~pa[2] & pb[2], ~pa[3] & pb[3]); - ensure_lanes_bits("or", or_(a(), b()), pa[0] | pb[0], pa[1] | pb[1], pa[2] | pb[2], pa[3] | pb[3]); - ensure_lanes_bits("xor", xor_(a(), b()), pa[0] ^ pb[0], pa[1] ^ pb[1], pa[2] ^ pb[2], pa[3] ^ pb[3]); - ensure_lanes_bits("as_i32 round trip", as_f32(as_i32(a())), pa[0], pa[1], pa[2], pa[3]); - ensure_lanes_bits("as_mask round trip", as_f32(as_mask(a())), pa[0], pa[1], pa[2], pa[3]); + require_lanes_bits("and", and_(a(), b()), pa[0] & pb[0], pa[1] & pb[1], pa[2] & pb[2], pa[3] & pb[3]); + require_lanes_bits("andnot", andnot(a(), b()), ~pa[0] & pb[0], ~pa[1] & pb[1], ~pa[2] & pb[2], ~pa[3] & pb[3]); + require_lanes_bits("or", or_(a(), b()), pa[0] | pb[0], pa[1] | pb[1], pa[2] | pb[2], pa[3] | pb[3]); + require_lanes_bits("xor", xor_(a(), b()), pa[0] ^ pb[0], pa[1] ^ pb[1], pa[2] ^ pb[2], pa[3] ^ pb[3]); + require_lanes_bits("as_i32 round trip", as_f32(as_i32(a())), pa[0], pa[1], pa[2], pa[3]); + require_lanes_bits("as_mask round trip", as_f32(as_mask(a())), pa[0], pa[1], pa[2], pa[3]); } // Masks: the constants, the comparisons with a NaN in play, and what // reads them. - template<> template<> - void alsimd_object::test<10>() + TEST_CASE_METHOD(alsimd_data, "alsimd: mask constants and comparisons with a NaN and what reads them", "[alsimd]") { - ensure_lanes_bits("mask_none", mask_none(), MASK_OFF, MASK_OFF, MASK_OFF, MASK_OFF); - ensure_lanes_bits("mask_all", mask_all(), MASK_ON, MASK_ON, MASK_ON, MASK_ON); - ensure_lanes_bits("mask_lane<0>", mask_lane<0>(), MASK_ON, MASK_OFF, MASK_OFF, MASK_OFF); - ensure_lanes_bits("mask_lane<2>", mask_lane<2>(), MASK_OFF, MASK_OFF, MASK_ON, MASK_OFF); - ensure_lanes_bits("mask_lane<3>", mask_lane<3>(), MASK_OFF, MASK_OFF, MASK_OFF, MASK_ON); - ensure_lanes_bits("mask_xyz", mask_xyz(), MASK_ON, MASK_ON, MASK_ON, MASK_OFF); - ensure_lanes_bits("mask_not", mask_not(mask_lane<1>()), MASK_ON, MASK_OFF, MASK_ON, MASK_ON); + require_lanes_bits("mask_none", mask_none(), MASK_OFF, MASK_OFF, MASK_OFF, MASK_OFF); + require_lanes_bits("mask_all", mask_all(), MASK_ON, MASK_ON, MASK_ON, MASK_ON); + require_lanes_bits("mask_lane<0>", mask_lane<0>(), MASK_ON, MASK_OFF, MASK_OFF, MASK_OFF); + require_lanes_bits("mask_lane<2>", mask_lane<2>(), MASK_OFF, MASK_OFF, MASK_ON, MASK_OFF); + require_lanes_bits("mask_lane<3>", mask_lane<3>(), MASK_OFF, MASK_OFF, MASK_OFF, MASK_ON); + require_lanes_bits("mask_xyz", mask_xyz(), MASK_ON, MASK_ON, MASK_ON, MASK_OFF); + require_lanes_bits("mask_not", mask_not(mask_lane<1>()), MASK_ON, MASK_OFF, MASK_ON, MASK_ON); const f32x4 x = set(1.f, 2.f, NAN_QUIET, -0.f); const f32x4 y = set(2.f, 2.f, 2.f, 0.f); - ensure_lanes_bits("cmplt", cmplt(x, y), MASK_ON, MASK_OFF, MASK_OFF, MASK_OFF); - ensure_lanes_bits("cmple", cmple(x, y), MASK_ON, MASK_ON, MASK_OFF, MASK_ON); - ensure_lanes_bits("cmpgt", cmpgt(x, y), MASK_OFF, MASK_OFF, MASK_OFF, MASK_OFF); - ensure_lanes_bits("cmpge", cmpge(x, y), MASK_OFF, MASK_ON, MASK_OFF, MASK_ON); - ensure_lanes_bits("cmpeq", cmpeq(x, y), MASK_OFF, MASK_ON, MASK_OFF, MASK_ON); - ensure_lanes_bits("cmpne", cmpne(x, y), MASK_ON, MASK_OFF, MASK_ON, MASK_OFF); + require_lanes_bits("cmplt", cmplt(x, y), MASK_ON, MASK_OFF, MASK_OFF, MASK_OFF); + require_lanes_bits("cmple", cmple(x, y), MASK_ON, MASK_ON, MASK_OFF, MASK_ON); + require_lanes_bits("cmpgt", cmpgt(x, y), MASK_OFF, MASK_OFF, MASK_OFF, MASK_OFF); + require_lanes_bits("cmpge", cmpge(x, y), MASK_OFF, MASK_ON, MASK_OFF, MASK_ON); + require_lanes_bits("cmpeq", cmpeq(x, y), MASK_OFF, MASK_ON, MASK_OFF, MASK_ON); + require_lanes_bits("cmpne", cmpne(x, y), MASK_ON, MASK_OFF, MASK_ON, MASK_OFF); const f32x4 n = set(INF, -INF, NAN_QUIET, std::numeric_limits::max()); - ensure_lanes_bits("nonfinite", nonfinite(n), MASK_ON, MASK_ON, MASK_ON, MASK_OFF); + require_lanes_bits("nonfinite", nonfinite(n), MASK_ON, MASK_ON, MASK_ON, MASK_OFF); const f32x4 f = set(0.f, -0.f, DENORMAL, -1.f); - ensure_lanes_bits("nonfinite on finite", nonfinite(f), MASK_OFF, MASK_OFF, MASK_OFF, MASK_OFF); + require_lanes_bits("nonfinite on finite", nonfinite(f), MASK_OFF, MASK_OFF, MASK_OFF, MASK_OFF); const mask4 m = cmplt(x, y); - ensure_lanes_bits("select", select(m, a(), b()), pa[0], pb[1], pb[2], pb[3]); - ensure_lanes_bits("select inverted", select(mask_not(m), a(), b()), pb[0], pa[1], pa[2], pa[3]); - ensure_lanes_bits("select xyz", select(mask_xyz(), a(), b()), pa[0], pa[1], pa[2], pb[3]); - - ensure_equals("bits none", bits(mask_none()), 0u); - ensure_equals("bits all", bits(mask_all()), 0xFu); - ensure_equals("bits lane 2", bits(mask_lane<2>()), 4u); - ensure_equals("bits xyz", bits(mask_xyz()), 7u); - ensure_equals("bits cmpge", bits(cmpge(x, y)), 0xAu); - - ensure("any none", !any(mask_none())); - ensure("any lane 3", any(mask_lane<3>())); - ensure("all all", all(mask_all())); - ensure("all xyz is not all", !all(mask_xyz())); - ensure("any3 lane 3 is not any3", !any3(mask_lane<3>())); - ensure("any3 lane 1", any3(mask_lane<1>())); - ensure("all3 xyz", all3(mask_xyz())); - ensure("all3 with lane 1 clear", !all3(mask_not(mask_lane<1>()))); - ensure("all3 with only lane 3 clear", all3(mask_not(mask_lane<3>()))); + require_lanes_bits("select", select(m, a(), b()), pa[0], pb[1], pb[2], pb[3]); + require_lanes_bits("select inverted", select(mask_not(m), a(), b()), pb[0], pa[1], pa[2], pa[3]); + require_lanes_bits("select xyz", select(mask_xyz(), a(), b()), pa[0], pa[1], pa[2], pb[3]); + + CHECK(bits(mask_none()) == 0u); + CHECK(bits(mask_all()) == 0xFu); + CHECK(bits(mask_lane<2>()) == 4u); + CHECK(bits(mask_xyz()) == 7u); + CHECK(bits(cmpge(x, y)) == 0xAu); + + CHECK_FALSE(any(mask_none())); + CHECK(any(mask_lane<3>())); + CHECK(all(mask_all())); + CHECK_FALSE(all(mask_xyz())); + CHECK_FALSE(any3(mask_lane<3>())); + CHECK(any3(mask_lane<1>())); + CHECK(all3(mask_xyz())); + CHECK_FALSE(all3(mask_not(mask_lane<1>()))); + CHECK(all3(mask_not(mask_lane<3>()))); } // Dot products sum rounded products in one order on every backend, so // they are exact against that order; the cross product rounds one // product and fuses the other where the machine fuses. - template<> template<> - void alsimd_object::test<11>() + TEST_CASE_METHOD(alsimd_data, "alsimd: dot products sum in one order and cross3 fuses where the machine does", "[alsimd]") { const F32 as[4] = {1.5f, -3.25f, 0.001f, 4.f}; const F32 bs[4] = {2.75f, 0.125f, 3.f, -7.f}; @@ -505,8 +491,8 @@ namespace tut volatile F32 zw = p2 + p3; const F32 d3 = xy + p2; const F32 d4 = xy + zw; - ensure_lanes("dot3", dot3(a, b), d3, d3, d3, d3); - ensure_lanes("dot4", dot4(a, b), d4, d4, d4, d4); + require_lanes("dot3", dot3(a, b), d3, d3, d3, d3); + require_lanes("dot4", dot4(a, b), d4, d4, d4, d4); // a.yzx * b.zxy - a.zxy * b.yzx volatile F32 first[3] = {as[1] * bs[2], as[2] * bs[0], as[0] * bs[1]}; @@ -529,49 +515,57 @@ namespace tut const Lanes c = lanes_of(cross3(a, b)); for (int i = 0; i < 3; ++i) { - ensure_equals("cross3 lane " + std::to_string(i), bits_of(c.v[i]), bits_of(expected[i])); + CAPTURE(i); + REQUIRE(bits_of(c.v[i]) == bits_of(expected[i])); } - ensure_equals("cross3 w", c.v[3], 0.f); + CHECK(c.v[3] == 0.f); // The cross product of two axes is the third. - ensure_lanes("x cross y", cross3(set(1.f, 0.f, 0.f, 0.f), set(0.f, 1.f, 0.f, 0.f)), 0.f, 0.f, 1.f, 0.f); - ensure_lanes("y cross z", cross3(set(0.f, 1.f, 0.f, 0.f), set(0.f, 0.f, 1.f, 0.f)), 1.f, 0.f, 0.f, 0.f); + require_lanes("x cross y", cross3(set(1.f, 0.f, 0.f, 0.f), set(0.f, 1.f, 0.f, 0.f)), 0.f, 0.f, 1.f, 0.f); + require_lanes("y cross z", cross3(set(0.f, 1.f, 0.f, 0.f), set(0.f, 0.f, 1.f, 0.f)), 1.f, 0.f, 0.f, 0.f); } - // The feature macros describe the build they came from. - template<> template<> - void alsimd_object::test<12>() + TEST_CASE_METHOD(alsimd_data, "alsimd: the feature macros describe the build they came from", "[alsimd]") { - ensure("one architecture", AL_SIMD_X86 + AL_SIMD_NEON == 1); - ensure("a width", AL_SIMD_WIDTH == 4 || AL_SIMD_WIDTH == 8 || AL_SIMD_WIDTH == 16); + UNSCOPED_INFO("one architecture"); + CHECK(AL_SIMD_X86 + AL_SIMD_NEON == 1); + CHECK((AL_SIMD_WIDTH == 4 || AL_SIMD_WIDTH == 8 || AL_SIMD_WIDTH == 16)); if constexpr (AL_SIMD_AVX512) { - ensure("AVX-512 implies AVX2", AL_SIMD_AVX2 == 1); - ensure("AVX-512 is sixteen wide", AL_SIMD_WIDTH == 16); + UNSCOPED_INFO("AVX-512 implies AVX2"); + CHECK(AL_SIMD_AVX2 == 1); + UNSCOPED_INFO("AVX-512 is sixteen wide"); + CHECK(AL_SIMD_WIDTH == 16); } if constexpr (AL_SIMD_AVX2) { - ensure("AVX2 implies AVX", AL_SIMD_AVX == 1); - ensure("AVX2 implies FMA", AL_SIMD_FMA == 1); + UNSCOPED_INFO("AVX2 implies AVX"); + CHECK(AL_SIMD_AVX == 1); + UNSCOPED_INFO("AVX2 implies FMA"); + CHECK(AL_SIMD_FMA == 1); } if constexpr (AL_SIMD_AVX) { - ensure("AVX implies SSE4", AL_SIMD_SSE4 == 1); - ensure("AVX is at least eight wide", AL_SIMD_WIDTH >= 8); + UNSCOPED_INFO("AVX implies SSE4"); + CHECK(AL_SIMD_SSE4 == 1); + UNSCOPED_INFO("AVX is at least eight wide"); + CHECK(AL_SIMD_WIDTH >= 8); } if constexpr (AL_SIMD_NEON) { - ensure("NEON has FMA", AL_SIMD_FMA == 1); - ensure("NEON is four wide", AL_SIMD_WIDTH == 4); + UNSCOPED_INFO("NEON has FMA"); + CHECK(AL_SIMD_FMA == 1); + UNSCOPED_INFO("NEON is four wide"); + CHECK(AL_SIMD_WIDTH == 4); } - ensure("the level is not above the flags", AL_ISA_LEVEL <= 2 || (AL_ISA_LEVEL == 3 && AL_SIMD_AVX2) || (AL_ISA_LEVEL == 4 && AL_SIMD_AVX512)); + UNSCOPED_INFO("the level is not above the flags"); + CHECK((AL_ISA_LEVEL <= 2 || (AL_ISA_LEVEL == 3 && AL_SIMD_AVX2) || (AL_ISA_LEVEL == 4 && AL_SIMD_AVX512))); } // The aligned copy moves every byte and no other, at every size the // three loops can be entered with, both ends at every sixteen-byte // offset within a line, and at the sizes the slabs come in. - template<> template<> - void alsimd_object::test<13>() + TEST_CASE_METHOD(alsimd_data, "alsimd: copy_aligned16 moves every byte and no other at every entry size", "[alsimd]") { constexpr size_t LARGEST = 4 * 1024 * 1024; constexpr size_t GUARD = 64; @@ -607,10 +601,10 @@ namespace tut unsigned char* dst = dst_line + dst_off; std::memset(dst - 16, 0xEE, size + 32); copy_aligned16(reinterpret_cast(dst), reinterpret_cast(src), size); - const std::string what = "copy of " + std::to_string(size) + " at " + std::to_string(src_off) + "/" + std::to_string(dst_off); - ensure(what + " moved every byte", std::memcmp(dst, src, size) == 0); - ensure(what + " left the byte before", dst[-1] == 0xEE); - ensure(what + " left the byte after", dst[size] == 0xEE); + CAPTURE(size, src_off, dst_off); + REQUIRE(std::memcmp(dst, src, size) == 0); + REQUIRE(dst[-1] == 0xEE); + REQUIRE(dst[size] == 0xEE); } } } diff --git a/indra/llcommon/tests/alwatchedfile_test.cpp b/indra/llcommon/tests/alwatchedfile_test.cpp index 852488ccc9..c2c9401146 100644 --- a/indra/llcommon/tests/alwatchedfile_test.cpp +++ b/indra/llcommon/tests/alwatchedfile_test.cpp @@ -33,7 +33,7 @@ #include "../lluuid.h" #include "../workqueue.h" -#include "../test/lltut.h" +#include #include #include @@ -42,7 +42,7 @@ #include #include -namespace tut +namespace { namespace fs = std::filesystem; @@ -138,116 +138,112 @@ namespace tut } }; - typedef test_group alwatchedfile_group; - typedef alwatchedfile_group::object alwatchedfile_object; - alwatchedfile_group alwatchedfile_instance("alwatchedfile"); - - template<> template<> - void alwatchedfile_object::test<1>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: a write is heard once when it holds still; what was there is not", "[alwatchedfile]") { - set_test_name("what is there when the watch begins is seen, and a write is heard once, after it holds still"); put("print(1)\n", 100); ALWatchedFile watched(file, hear()); - ensure("nothing yet", !looked(watched)); - ensure("nobody told", heard.empty()); + UNSCOPED_INFO("nothing yet"); + CHECK_FALSE(looked(watched)); + REQUIRE(heard.empty()); put("print(2)\n", 200); - ensure("the first look finds it moving", !watched.check()); - ensure("the next finds it still", watched.check()); - ensure_equals("told once", heard.size(), size_t(1)); - ensure_equals("of the file", heard[0], file); - ensure_equals("which held what was written", held[0], std::string("print(2)\n")); - ensure("and not again", !looked(watched)); + UNSCOPED_INFO("the first look finds it moving"); + CHECK_FALSE(watched.check()); + UNSCOPED_INFO("the next finds it still"); + CHECK(watched.check()); + UNSCOPED_INFO("told once"); + REQUIRE(heard.size() == size_t(1)); + CHECK(heard[0] == file); + CHECK(held[0] == std::string("print(2)\n")); + UNSCOPED_INFO("and not again"); + CHECK_FALSE(looked(watched)); } - template<> template<> - void alwatchedfile_object::test<2>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: two writes in one second are two changes of either size", "[alwatchedfile]") { - set_test_name("two writes in one second are two changes, of one size or another"); put("a = 1\n", 100); ALWatchedFile watched(file, hear()); put("a = 2\n", 300); - ensure("the first", looked(watched)); + CHECK(looked(watched)); // The same size, the same second: the time alone tells them apart. put("a = 3\n", 500); - ensure("the second", looked(watched)); + CHECK(looked(watched)); // The same time, a size of its own. put("a = 40\n", 500); - ensure("the third", looked(watched)); - ensure_equals("each heard", heard.size(), size_t(3)); - ensure_equals("the last as it was written", held[2], std::string("a = 40\n")); + CHECK(looked(watched)); + REQUIRE(heard.size() == size_t(3)); + CHECK(held[2] == std::string("a = 40\n")); } - template<> template<> - void alwatchedfile_object::test<3>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: the owner's own write is not heard but the next one from outside is", "[alwatchedfile]") { - set_test_name("a write of the owner's own is seen, and the next write from outside is heard all the same"); put("one\n", 100); ALWatchedFile watched(file, hear()); put("two\n", 200); - ensure("from outside", looked(watched)); + UNSCOPED_INFO("from outside"); + CHECK(looked(watched)); // The owner's own, in the same second: a flag waiting on the next // change would never be taken by a write a time by the second // missed, and would take the editor's save after it. put("three\n", 300); watched.seen(); - ensure("the owner's own is no change", !looked(watched)); + UNSCOPED_INFO("the owner's own is no change"); + CHECK_FALSE(looked(watched)); put("four\n", 400); - ensure("the editor's save after it is heard", looked(watched)); - ensure_equals("two heard", heard.size(), size_t(2)); - ensure_equals("the editor's", held[1], std::string("four\n")); + UNSCOPED_INFO("the editor's save after it is heard"); + CHECK(looked(watched)); + REQUIRE(heard.size() == size_t(2)); + CHECK(held[1] == std::string("four\n")); } - template<> template<> - void alwatchedfile_object::test<4>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: a file caught half written is not read until it holds still", "[alwatchedfile]") { - set_test_name("a file caught half written is not read until it holds still"); put("local x = 1\n", 100); ALWatchedFile watched(file, hear()); // An editor saving in two steps: emptied, then filled. put("", 200); - ensure("emptied: not still yet", !watched.check()); + UNSCOPED_INFO("emptied: not still yet"); + CHECK_FALSE(watched.check()); put("local x = 2\n", 300); - ensure("filled: moved again", !watched.check()); - ensure("still", watched.check()); - ensure_equals("heard once", heard.size(), size_t(1)); - ensure_equals("filled", held[0], std::string("local x = 2\n")); + UNSCOPED_INFO("filled: moved again"); + CHECK_FALSE(watched.check()); + CHECK(watched.check()); + REQUIRE(heard.size() == size_t(1)); + CHECK(held[0] == std::string("local x = 2\n")); } - template<> template<> - void alwatchedfile_object::test<5>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: a file gone or come back or put back older is each a change", "[alwatchedfile]") { - set_test_name("a file gone is a change, one come back is another, and one put back older is a third"); put("kept\n", 500); ALWatchedFile watched(file, hear()); std::error_code ec; fs::remove(fsyspath(file), ec); - ensure("gone", looked(watched)); + UNSCOPED_INFO("gone"); + CHECK(looked(watched)); put("back\n", 600); - ensure("back", looked(watched)); + UNSCOPED_INFO("back"); + CHECK(looked(watched)); // Older than it was: a backup put back, which a later time only // would not see. fs::last_write_time(fsyspath(file), second, ec); - ensure("older", looked(watched)); - ensure_equals("three heard", heard.size(), size_t(3)); + UNSCOPED_INFO("older"); + CHECK(looked(watched)); + REQUIRE(heard.size() == size_t(3)); } - template<> template<> - void alwatchedfile_object::test<6>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: a file that is not there yet is heard when it comes", "[alwatchedfile]") { - set_test_name("a file that is not there yet is heard when it comes"); ALWatchedFile watched(file, hear()); - ensure("nothing there, nothing heard", !looked(watched)); + UNSCOPED_INFO("nothing there, nothing heard"); + CHECK_FALSE(looked(watched)); put("made\n", 100); - ensure("made", looked(watched)); - ensure_equals("its text", held.at(0), std::string("made\n")); + CHECK(looked(watched)); + CHECK(held.at(0) == std::string("made\n")); } - template<> template<> - void alwatchedfile_object::test<7>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: the owner may let go of the watch as it is told", "[alwatchedfile]") { - set_test_name("the owner may let go of the watch as it is told"); put("before\n", 100); std::unique_ptr watched; std::string told; @@ -258,16 +254,17 @@ namespace tut }); put("after\n", 200); ALWatchedFile& watch = *watched; - ensure("moving", !watch.check()); - ensure("heard, and let go", watch.check()); - ensure("gone", !watched); - ensure_equals("told of the file all the same", told, file); + UNSCOPED_INFO("moving"); + REQUIRE_FALSE(watch.check()); + UNSCOPED_INFO("heard, and let go"); + CHECK(watch.check()); + REQUIRE_FALSE(watched); + UNSCOPED_INFO("told of the file all the same"); + CHECK(told == file); } - template<> template<> - void alwatchedfile_object::test<8>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: a polled file is looked at off thread and told on the main loop", "[alwatchedfile]") { - set_test_name("a polled file is looked at on the watcher's thread, and its owner told on the main loop"); put("print(1)\n", 100); const std::thread::id here = std::this_thread::get_id(); std::thread::id told_on; @@ -278,19 +275,19 @@ namespace tut }); watched.poll(0.05f); put("print(2)\n", 200); - ensure("heard", pumped([&]() { return !heard.empty(); })); - ensure_equals("what was written", held.at(0), std::string("print(2)\n")); - ensure("told on the main loop's thread", told_on == here); + REQUIRE(pumped([&]() { return !heard.empty(); })); + CHECK(held.at(0) == std::string("print(2)\n")); + UNSCOPED_INFO("told on the main loop's thread"); + CHECK(told_on == here); // Still: not told again, however many looks go by. LLTimer timer; pumped([&]() { return timer.getElapsedTimeF32() > 0.5f; }); - ensure_equals("told once", heard.size(), size_t(1)); + UNSCOPED_INFO("told once"); + REQUIRE(heard.size() == size_t(1)); } - template<> template<> - void alwatchedfile_object::test<9>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: a watch let go of is not told; the last may be let go of as it is told", "[alwatchedfile]") { - set_test_name("a file let go of is not told, and the others are; the owner may let go of the last as it is told"); const std::string other = fsyspath(fsyspath(folder) / fsyspath("sl_script_other.lsl")).string(); put("one\n", 100); { @@ -311,60 +308,64 @@ namespace tut llofstream out(other, std::ios::binary | std::ios::trunc); out << "default { state_entry() {} }\n"; } - ensure("heard", pumped([&]() { return !heard.empty(); })); - ensure("and let go", !kept); + REQUIRE(pumped([&]() { return !heard.empty(); })); + REQUIRE_FALSE(kept); LLTimer timer; pumped([&]() { return timer.getElapsedTimeF32() > 0.4f; }); - ensure_equals("the one kept, only", heard.size(), size_t(1)); - ensure_equals("which is the file", heard[0], file); + UNSCOPED_INFO("the one kept, only"); + REQUIRE(heard.size() == size_t(1)); + CHECK(heard[0] == file); } - template<> template<> - void alwatchedfile_object::test<10>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: ALFileStamp holds a file's size and time and nothing for a folder", "[alwatchedfile]") { - set_test_name("a stamp: a file's size and time, and nothing for a folder or nothing at all"); put("12345", 250); const ALFileStamp stamp = ALFileStamp::of(file); - ensure("there", stamp.exists); - ensure_equals("its size", stamp.size, std::uintmax_t(5)); + CHECK(stamp.exists); + REQUIRE(stamp.size == std::uintmax_t(5)); put("12345", 251); - ensure("a millisecond later is another time", !(ALFileStamp::of(file) == stamp)); - ensure("a folder is no file", !ALFileStamp::of(folder).exists); - ensure("nothing is nothing", !ALFileStamp::of(file + ".gone").exists); + UNSCOPED_INFO("a millisecond later is another time"); + CHECK_FALSE(ALFileStamp::of(file) == stamp); + UNSCOPED_INFO("a folder is no file"); + CHECK_FALSE(ALFileStamp::of(folder).exists); + CHECK_FALSE(ALFileStamp::of(file + ".gone").exists); } - template<> template<> - void alwatchedfile_object::test<11>() + TEST_CASE_METHOD(alwatchedfile_data, "alwatchedfile: a watch made from an earlier stamp starts from what it found", "[alwatchedfile]") { - set_test_name("a watch made from a look made before takes what that look found, and asks the disk nothing itself"); put("print(1)\n", 100); const ALFileStamp before = ALFileStamp::of(file); // Written after the look and before the watch is made: heard, since // the watch is from the look, not from the file as it is now. put("print(2)\n", 200); ALWatchedFile watched(file, before, hear()); - ensure("as the look found it", watched.stamp() == before); - ensure("what came after it heard", looked(watched)); - ensure_equals("once", heard.size(), size_t(1)); - ensure_equals("as it is now", held.at(0), std::string("print(2)\n")); + UNSCOPED_INFO("as the look found it"); + CHECK(watched.stamp() == before); + UNSCOPED_INFO("what came after it heard"); + CHECK(looked(watched)); + REQUIRE(heard.size() == size_t(1)); + CHECK(held.at(0) == std::string("print(2)\n")); // From a look that found the file as it is: nothing to hear. ALWatchedFile current(file, ALFileStamp::of(file), hear()); - ensure("nothing", !looked(current)); + CHECK_FALSE(looked(current)); // From a look that found nothing there -- the stamp of no file, // which a look of its own here would not have found: the file there // now is heard, which shows the watch took the look it was given. ALWatchedFile none(file, ALFileStamp(), hear()); - ensure("nothing there at the look", !none.there()); - ensure("the file there now heard", looked(none)); - ensure_equals("twice in all", heard.size(), size_t(2)); + UNSCOPED_INFO("nothing there at the look"); + CHECK_FALSE(none.there()); + UNSCOPED_INFO("the file there now heard"); + CHECK(looked(none)); + REQUIRE(heard.size() == size_t(2)); // And seen() takes the file as it is, whatever look it began from. put("print(3)\n", 300); ALWatchedFile owned(file, before, hear()); owned.seen(); - ensure("the owner's own is no change", !looked(owned)); - ensure_equals("still twice", heard.size(), size_t(2)); + UNSCOPED_INFO("the owner's own is no change"); + CHECK_FALSE(looked(owned)); + REQUIRE(heard.size() == size_t(2)); } } diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp index 6bb4e23b00..d028b2ea1c 100644 --- a/indra/llcommon/tests/apply_test.cpp +++ b/indra/llcommon/tests/apply_test.cpp @@ -25,12 +25,12 @@ // for ensure_equals(std::vector): must come BEFORE lltut.h #include "StringVec.h" -#include "../test/lltut.h" +#include /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { namespace statics { @@ -69,46 +69,46 @@ namespace tut const LLSD::URI& uri, const LLSD::Binary& bin) { called = true; - ensure_equals( "b mismatch", b, statics::b); - ensure_equals( "i mismatch", i, statics::i); - ensure_equals( "f mismatch", f, statics::f); - ensure_equals( "s mismatch", s, statics::s); - ensure_equals( "uu mismatch", uu, statics::uu); - ensure_equals( "dt mismatch", dt, statics::dt); - ensure_equals("uri mismatch", uri, statics::uri); - ensure_equals("bin mismatch", bin, statics::bin); + REQUIRE(b == statics::b); + REQUIRE(i == statics::i); + REQUIRE(f == statics::f); + REQUIRE(s == statics::s); + REQUIRE(uu == statics::uu); + REQUIRE(dt == statics::dt); + REQUIRE(uri.asString() == statics::uri.asString()); + REQUIRE(bin == statics::bin); } static void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4) { called = true; - ensure_equals("s0 mismatch", s0, statics::quick[0]); - ensure_equals("s1 mismatch", s1, statics::quick[1]); - ensure_equals("s2 mismatch", s2, statics::quick[2]); - ensure_equals("s3 mismatch", s3, statics::quick[3]); - ensure_equals("s4 mismatch", s4, statics::quick[4]); + REQUIRE(s0 == statics::quick[0]); + REQUIRE(s1 == statics::quick[1]); + REQUIRE(s2 == statics::quick[2]); + REQUIRE(s3 == statics::quick[3]); + REQUIRE(s4 == statics::quick[4]); } static void ints(int i0, int i1, int i2, int i3, int i4) { called = true; - ensure_equals("i0 mismatch", i0, statics::fibs[0]); - ensure_equals("i1 mismatch", i1, statics::fibs[1]); - ensure_equals("i2 mismatch", i2, statics::fibs[2]); - ensure_equals("i3 mismatch", i3, statics::fibs[3]); - ensure_equals("i4 mismatch", i4, statics::fibs[4]); + REQUIRE(i0 == statics::fibs[0]); + REQUIRE(i1 == statics::fibs[1]); + REQUIRE(i2 == statics::fibs[2]); + REQUIRE(i3 == statics::fibs[3]); + REQUIRE(i4 == statics::fibs[4]); } static void sdfunc(const LLSD& sd) { called = true; - ensure_equals("sd mismatch", sd.asInteger(), statics::i); + REQUIRE(sd.asInteger() == statics::i); } static void intfunc(int i) { called = true; - ensure_equals("i mismatch", i, statics::i); + REQUIRE(i == statics::i); } static void voidfunc() @@ -140,78 +140,59 @@ namespace tut statics::collected.clear(); } }; - typedef test_group apply_group; - typedef apply_group::object object; - apply_group applygrp("apply"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(apply_data, "apply: apply(tuple) passes each element as an argument", "[apply]") { - set_test_name("apply(tuple)"); LL::apply(statics::various, std::make_tuple(statics::b, statics::i, statics::f, statics::s, statics::uu, statics::dt, statics::uri, statics::bin)); - ensure("apply(tuple) failed", statics::called); + CHECK(statics::called); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(apply_data, "apply: apply(array) passes each element as an argument", "[apply]") { - set_test_name("apply(array)"); LL::apply(statics::ints, statics::fibs); - ensure("apply(array) failed", statics::called); + CHECK(statics::called); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(apply_data, "apply: apply(vector) passes each element as an argument", "[apply]") { - set_test_name("apply(vector)"); LL::apply(statics::strings, statics::quick); - ensure("apply(vector) failed", statics::called); + CHECK(statics::called); } // The various apply(LLSD) tests exercise only the success cases because // the failure cases trigger assert() fail, which is hard to catch. - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(apply_data, "apply: apply(LLSD()) calls a function of no arguments", "[apply]") { - set_test_name("apply(LLSD())"); LL::apply(statics::voidfunc, LLSD()); - ensure("apply(LLSD()) failed", statics::called); + CHECK(statics::called); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(apply_data, "apply: apply with an LLSD scalar converts it for an int parameter", "[apply]") { - set_test_name("apply(fn(int), LLSD scalar)"); LL::apply(statics::intfunc, LLSD(statics::i)); - ensure("apply(fn(int), LLSD scalar) failed", statics::called); + CHECK(statics::called); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(apply_data, "apply: apply with an LLSD scalar passes it to an LLSD parameter", "[apply]") { - set_test_name("apply(fn(LLSD), LLSD scalar)"); // This test verifies that LLSDParam doesn't send the compiler // into infinite recursion when the target is itself LLSD. LL::apply(statics::sdfunc, LLSD(statics::i)); - ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called); + CHECK(statics::called); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(apply_data, "apply: apply(LLSD array) converts each element to its parameter", "[apply]") { - set_test_name("apply(LLSD array)"); LL::apply(statics::various, llsd::array(statics::b, statics::i, statics::f, statics::s, statics::uu, statics::dt, statics::uri, statics::bin)); - ensure("apply(LLSD array) failed", statics::called); + CHECK(statics::called); } - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(apply_data, "apply: VAPPLY() expands an array into a variadic call", "[apply]") { - set_test_name("VAPPLY()"); // Make a std::array from statics::quick. We can't call a // variadic function with a data structure of dynamic length. std::array strray; @@ -222,7 +203,7 @@ namespace tut // LL::apply(statics::collect, strray); // That's what VAPPLY() is for. VAPPLY(statics::collect, strray); - ensure("VAPPLY() failed", statics::called); - ensure_equals("collected mismatch", statics::collected, statics::quick); + CHECK(statics::called); + CHECK(statics::collected == statics::quick); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/bitpack_test.cpp b/indra/llcommon/tests/bitpack_test.cpp index 2810f926af..c80289b812 100644 --- a/indra/llcommon/tests/bitpack_test.cpp +++ b/indra/llcommon/tests/bitpack_test.cpp @@ -30,21 +30,13 @@ #include "../llbitpack.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" -namespace tut +namespace { - struct bit_pack - { - }; - typedef test_group bit_pack_t; - typedef bit_pack_t::object bit_pack_object_t; - tut::bit_pack_t tut_bit_pack("LLBitPack"); - - // pack -> unpack - template<> template<> - void bit_pack_object_t::test<1>() + TEST_CASE("LLBitPack: a string packed then unpacked comes back unchanged", "[LLBitPack]") { U8 packbuffer[255]; U8 unpackbuffer[255]; @@ -60,13 +52,11 @@ namespace tut LLBitPack bitunpack(packbuffer, pack_bufsize*8); unpack_bufsize = bitunpack.bitUnpack(unpackbuffer, len*8); - ensure("bitPack: unpack size should be same as string size prior to pack", len == unpack_bufsize); - ensure_memory_matches("str->bitPack->bitUnpack should be equal to string", str, len, unpackbuffer, unpack_bufsize); + CHECK(len == unpack_bufsize); + CHECK_THAT(ll_test::Memory(str, len), ll_test::MemoryMatches(unpackbuffer, unpack_bufsize)); } - // pack large, unpack in individual bytes - template<> template<> - void bit_pack_object_t::test<2>() + TEST_CASE("LLBitPack: a packed string unpacks a byte at a time", "[LLBitPack]") { U8 packbuffer[255]; U8 unpackbuffer[255]; @@ -81,24 +71,22 @@ namespace tut LLBitPack bitunpack(packbuffer, pack_bufsize*8); bitunpack.bitUnpack(&unpackbuffer[0], 8); - ensure("bitPack: individual unpack: 0", unpackbuffer[0] == (U8) str[0]); + CHECK(unpackbuffer[0] == (U8) str[0]); bitunpack.bitUnpack(&unpackbuffer[0], 8); - ensure("bitPack: individual unpack: 1", unpackbuffer[0] == (U8) str[1]); + CHECK(unpackbuffer[0] == (U8) str[1]); bitunpack.bitUnpack(&unpackbuffer[0], 8); - ensure("bitPack: individual unpack: 2", unpackbuffer[0] == (U8) str[2]); + CHECK(unpackbuffer[0] == (U8) str[2]); bitunpack.bitUnpack(&unpackbuffer[0], 8); - ensure("bitPack: individual unpack: 3", unpackbuffer[0] == (U8) str[3]); + CHECK(unpackbuffer[0] == (U8) str[3]); bitunpack.bitUnpack(&unpackbuffer[0], 8); - ensure("bitPack: individual unpack: 4", unpackbuffer[0] == (U8) str[4]); + CHECK(unpackbuffer[0] == (U8) str[4]); bitunpack.bitUnpack(&unpackbuffer[0], 8); - ensure("bitPack: individual unpack: 5", unpackbuffer[0] == (U8) str[5]); + CHECK(unpackbuffer[0] == (U8) str[5]); bitunpack.bitUnpack(unpackbuffer, 8*4); // Life - ensure_memory_matches("bitPack: 4 bytes unpack:", unpackbuffer, 4, str+6, 4); + CHECK_THAT(ll_test::Memory(unpackbuffer, 4), ll_test::MemoryMatches(str+6, 4)); } - // U32 packing - template<> template<> - void bit_pack_object_t::test<3>() + TEST_CASE("LLBitPack: a U32 packed then unpacked comes back unchanged", "[LLBitPack]") { U8 packbuffer[255]; int pack_bufsize = 0; @@ -113,6 +101,6 @@ namespace tut // since packing and unpacking is done on same machine in the unit test run, // endianness should not matter bitunpack.bitUnpack((U8*) &res, sizeof(res)*8); - ensure("U32->bitPack->bitUnpack->U32 should be equal", num == res); + CHECK(num == res); } } diff --git a/indra/llcommon/tests/classic_callback_test.cpp b/indra/llcommon/tests/classic_callback_test.cpp index ec255ed1c7..c960980933 100644 --- a/indra/llcommon/tests/classic_callback_test.cpp +++ b/indra/llcommon/tests/classic_callback_test.cpp @@ -19,7 +19,7 @@ // std headers // external library headers // other Linden headers -#include "../test/lltut.h" +#include namespace { @@ -91,19 +91,10 @@ struct OtherCallback /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { - struct classic_callback_data - { - }; - typedef test_group classic_callback_group; - typedef classic_callback_group::object object; - classic_callback_group classic_callbackgrp("classic_callback"); - - template<> template<> - void object::test<1>() + TEST_CASE("classic_callback: ClassicCallback adapts C++ callables to a C callback", "[classic_callback]") { - set_test_name("ClassicCallback"); // engage someAPI(MyCallback()) auto ccb{ makeClassicCallback(MyCallback()) }; someAPI(ccb.get_callback(), ccb.get_userdata()); @@ -113,13 +104,15 @@ namespace tut MyCallback mcb; ClassicCallback ccb2(mcb); someAPI(ccb2.get_callback(), ccb2.get_userdata()); - ensure_equals("failed to call through ClassicCallback", mcb.mMsg, "called"); + UNSCOPED_INFO("failed to call through ClassicCallback"); + CHECK(mcb.mMsg == "called"); // try with HeapClassicCallback mcb.mMsg.clear(); auto hcbp{ makeHeapClassicCallback(mcb) }; someAPI(hcbp->get_callback(), hcbp->get_userdata()); - ensure_equals("failed to call through HeapClassicCallback", mcb.mMsg, "called"); + UNSCOPED_INFO("failed to call through HeapClassicCallback"); + CHECK(mcb.mMsg == "called"); // lambda // The tricky thing here is that a lambda is an unspecified type, so @@ -130,7 +123,8 @@ namespace tut [&mcb](const char* msg, void*) { mcb.callback_with_extra("extra", msg); })); someAPI(xcb.get_callback(), xcb.get_userdata()); - ensure_equals("failed to call lambda", mcb.mMsg, "extra called"); + UNSCOPED_INFO("failed to call lambda"); + CHECK(mcb.mMsg == "extra called"); // engage otherAPI(OtherCallback()) OtherCallback ocb; @@ -141,9 +135,9 @@ namespace tut // effect. auto occb{ makeClassicCallback(std::move(ocb)) }; std::string result{ otherAPI(occb.get_callback(), occb.get_userdata()) }; - ensure_equals("failed to return callback result", result, "hello back!"); - ensure_equals("failed to set int", sData.mi, 17); - ensure_equals("failed to set string", sData.ms, "hello world"); - ensure_equals("failed to set double", sData.mf, 3.0); + CHECK(result == "hello back!"); + CHECK(sData.mi == 17); + CHECK(sData.ms == "hello world"); + CHECK(sData.mf == 3.0); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/commonmisc_test.cpp b/indra/llcommon/tests/commonmisc_test.cpp index 80be26dfeb..e6102076f8 100644 --- a/indra/llcommon/tests/commonmisc_test.cpp +++ b/indra/llcommon/tests/commonmisc_test.cpp @@ -44,19 +44,71 @@ #include "../u64.h" #include "../llhash.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { - struct sd_data +/* + template<> template<> + void sd_object::test<16>() { - }; - typedef test_group sd_test; - typedef sd_test::object sd_object; - tut::sd_test sd("LLSD"); + } +*/ +} - template<> template<> - void sd_object::test<1>() +#if 0 +'{\'task_id\':u1fd77b79-a8e7-25a5-9454-02a4d948ba1c}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t00082000\n\t\tcreator_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t00000000-0000-0000-0000-000000000000\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t10284\n\ttotal_crc\t35\n\ttype\t1\n\ttask_valid\t2\n\ttravel_access\t21\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t0\t0\t0\n\toldpos\t0\t0\t0\n\trotation\t4.371139183945160766597837e-08\t1\t4.371139183945160766597837e-08\t0\n\tvelocity\t0\t0\t0\n\tangvel\t0\t0\t0\n\tscale\t0.2816932\t0.2816932\t0.2816932\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t80\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t6\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t1132625972249870\n\tbirthtime\t1132625953120694\n\treztime\t1132625953120694\n\tparceltime\t1132625953120694\n\ttax_rate\t1.01615\n\tnamevalue\tAttachmentOrientation VEC3 RW DS -3.141593, 0.000000, -3.141593\n\tnamevalue\tAttachmentOffset VEC3 RW DS 0.000000, 0.000000, 0.000000\n\tnamevalue\tAttachPt U32 RW S 5\n\tnamevalue\tAttachItemID STRING RW SV 1f9975c0-2951-1b93-dd83-46e2b932fcc8\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\torig_asset_id\t52019cdd-b464-ba19-e66d-3da751fef9da\n\torig_item_id\t1f9975c0-2951-1b93-dd83-46e2b932fcc8\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n' +#endif + +namespace +{ +} + +namespace +{ + // llstrtou64 + // seems to be deprecated - could not find it being used + // anywhere in the tarball - skipping unit tests for now +} + + +namespace +{ + TEST_CASE("LLHash: llhash depends on the string and not on its pointer", "[LLHash]") + { + const char * str1 = "test string one"; + const char * same_as_str1 = "test string one"; + + size_t hash1 = llhash(str1); + size_t same_as_hash1 = llhash(same_as_str1); + + + CHECK(hash1 == same_as_hash1); + + char str[100]; + strcpy( str, "Another test" ); + + size_t hash2 = llhash(str); + + strcpy( str, "Different string, same pointer" ); + + size_t hash3 = llhash(str); + + CHECK(hash2 != hash3); + } + + TEST_CASE("LLMemoryStream: operator>> reads whitespace-separated words", "[LLMemoryStream]") + { + const char HELLO_WORLD[] = "hello world"; + LLMemoryStream mem((U8*)&HELLO_WORLD[0], static_cast(strlen(HELLO_WORLD))); /* Flawfinder: ignore */ + std::string hello; + std::string world; + mem >> hello >> world; + CHECK(hello == std::string("hello")); + CHECK(world == std::string("world")); + } + + TEST_CASE("LLSD: fromNotation parses a map from an LLMemoryStream", "[LLSD]") { std::ostringstream resp; resp << "{'connect':true, 'position':[r128,r128,r128], 'look_at':[r0,r1,r0], 'agent_access':'M', 'region_x':i8192, 'region_y':i8192}"; @@ -64,17 +116,16 @@ namespace tut LLMemoryStream mstr((U8*)str.c_str(), static_cast(str.size())); LLSD response; S32 count = LLSDSerialize::fromNotation(response, mstr, str.size()); - ensure("stream parsed", response.isDefined()); - ensure_equals("stream parse count", count, 13); - ensure_equals("sd type", response.type(), LLSD::TypeMap); - ensure_equals("map element count", response.size(), 6); - ensure_equals("value connect", response["connect"].asBoolean(), true); - ensure_equals("value region_x", response["region_x"].asInteger(),8192); - ensure_equals("value region_y", response["region_y"].asInteger(),8192); + CHECK(response.isDefined()); + CHECK(count == 13); + CHECK(response.type() == LLSD::TypeMap); + CHECK(response.size() == 6); + CHECK(response["connect"].asBoolean()); + CHECK(response["region_x"].asInteger() == 8192); + CHECK(response["region_y"].asInteger() == 8192); } - template<> template<> - void sd_object::test<2>() + TEST_CASE("LLSD: binary writes to notation as b(6) and parses back", "[LLSD]") { const std::string decoded("random"); //const std::string encoded("cmFuZG9t\n"); @@ -89,25 +140,22 @@ namespace tut sd = buf; std::stringstream str; S32 count = LLSDSerialize::toNotation(sd, str); - ensure_equals("output count", count, 1); + CHECK(count == 1); std::string actual(str.str()); - ensure_equals("formatted binary encoding", actual, streamed); + CHECK(actual == streamed); sd.clear(); LLSDSerialize::fromNotation(sd, str, str.str().size()); std::vector after; after = sd.asBinary(); - ensure_equals("binary decoded size", after.size(), decoded.size()); - ensure("binary decoding", (0 == memcmp( - &after[0], - decoded.c_str(), - decoded.size()))); + REQUIRE(after.size() == decoded.size()); + CHECK(memcmp(&after[0], decoded.c_str(), decoded.size()) == 0); } - template<> template<> - void sd_object::test<3>() + TEST_CASE("LLSD: random binary round-trips through notation", "[LLSD]") { for(S32 i = 0; i < 100; ++i) { + CAPTURE(i); // gen up a starting point typedef std::vector buf_t; buf_t source; @@ -121,22 +169,17 @@ namespace tut std::stringstream str; S32 count = LLSDSerialize::toNotation(sd, str); sd.clear(); - ensure_equals("format count", count, 1); + REQUIRE(count == 1); LLSD sd2; count = LLSDSerialize::fromNotation(sd2, str, str.str().size()); - ensure_equals("parse count", count, 1); + REQUIRE(count == 1); buf_t dest = sd2.asBinary(); - str.str(""); - str << "binary encoding size " << i; - ensure_equals(str.str().c_str(), dest.size(), source.size()); - str.str(""); - str << "binary encoding " << i; - ensure(str.str().c_str(), (source == dest)); + REQUIRE(dest.size() == source.size()); + REQUIRE(source == dest); } } - template<> template<> - void sd_object::test<4>() + TEST_CASE("LLSD: an escaped string with newlines and quotes parses back", "[LLSD]") { std::ostringstream ostr; ostr << "{'task_id':u1fd77b79-a8e7-25a5-9454-02a4d948ba1c}\n" @@ -150,15 +193,15 @@ namespace tut sd, serialized, serialized.str().size()); - ensure_equals("parse count", count, 1); - ensure_equals("String streaming", sd.asString(), expected); + CHECK(count == 1); + CHECK(sd.asString() == expected); } - template<> template<> - void sd_object::test<5>() + TEST_CASE("LLSD: random raw binary in b(size) notation parses", "[LLSD]") { for(S32 i = 0; i < 100; ++i) { + CAPTURE(i); // gen up a starting point typedef std::vector buf_t; buf_t source; @@ -174,15 +217,14 @@ namespace tut str << "\""; LLSD sd; S32 count = LLSDSerialize::fromNotation(sd, str, str.str().size()); - ensure_equals("binary parse", count, 1); + REQUIRE(count == 1); buf_t actual = sd.asBinary(); - ensure_equals("binary size", actual.size(), (size_t)size); - ensure("binary data", (0 == memcmp(&source[0], &actual[0], size))); + REQUIRE(actual.size() == (size_t)size); + REQUIRE(memcmp(&source[0], &actual[0], size) == 0); } } - template<> template<> - void sd_object::test<6>() + TEST_CASE("LLSD: a raw string in s(size) notation keeps quotes and whitespace", "[LLSD]") { std::string expected("'{\"task_id\":u1fd77b79-a8e7-25a5-9454-02a4d948ba1c}'\t\n\t\t"); std::stringstream str; @@ -191,14 +233,13 @@ namespace tut str << "'"; LLSD sd; S32 count = LLSDSerialize::fromNotation(sd, str, str.str().size()); - ensure_equals("parse count", count, 1); + CHECK(count == 1); std::string actual = sd.asString(); - ensure_equals("string sizes", actual.size(), expected.size()); - ensure_equals("string content", actual, expected); + CHECK(actual.size() == expected.size()); + CHECK(actual == expected); } - template<> template<> - void sd_object::test<7>() + TEST_CASE("LLSD: fromNotation parses a map of mixed scalar and array values", "[LLSD]") { std::string msg("come on in"); std::stringstream stream; @@ -211,19 +252,18 @@ namespace tut sd, stream, stream.str().size()); - ensure_equals("parse count", count, 12); - ensure_equals("bool value", sd["connect"].asBoolean(), true); - ensure_equals("message value", sd["message"].asString(), msg); - ensure_equals("pos x", sd["position"][0].asReal(), 45.65); - ensure_equals("pos y", sd["position"][1].asReal(), 100.1); - ensure_equals("pos z", sd["position"][2].asReal(), 25.5); - ensure_equals("look x", sd["look_at"][0].asReal(), 0.0); - ensure_equals("look y", sd["look_at"][1].asReal(), 1.0); - ensure_equals("look z", sd["look_at"][2].asReal(), 0.0); + CHECK(count == 12); + CHECK(sd["connect"].asBoolean()); + CHECK(sd["message"].asString() == msg); + CHECK(sd["position"][0].asReal() == 45.65); + CHECK(sd["position"][1].asReal() == 100.1); + CHECK(sd["position"][2].asReal() == 25.5); + CHECK(sd["look_at"][0].asReal() == 0.0); + CHECK(sd["look_at"][1].asReal() == 1.0); + CHECK(sd["look_at"][2].asReal() == 0.0); } - template<> template<> - void sd_object::test<8>() + TEST_CASE("LLSD: one-character and empty strings parse in a map", "[LLSD]") { std::stringstream resp; resp << "{'label':'short string test', 'singlechar':'a', 'empty':'', 'endoftest':'end' }"; @@ -232,15 +272,14 @@ namespace tut response, resp, resp.str().size()); - ensure_equals("parse count", count, 5); - ensure_equals("sd type", response.type(), LLSD::TypeMap); - ensure_equals("map element count", response.size(), 4); - ensure_equals("singlechar", response["singlechar"].asString(), "a"); - ensure_equals("empty", response["empty"].asString(), ""); + CHECK(count == 5); + CHECK(response.type() == LLSD::TypeMap); + CHECK(response.size() == 4); + CHECK(response["singlechar"].asString() == "a"); + CHECK(response["empty"].asString() == ""); } - template<> template<> - void sd_object::test<9>() + TEST_CASE("LLSD: one-byte binary and raw string values parse in a map", "[LLSD]") { std::ostringstream resp; resp << "{'label':'short binary test', 'singlebinary':b(1)\"A\", 'singlerawstring':s(1)\"A\", 'endoftest':'end' }"; @@ -248,28 +287,23 @@ namespace tut LLSD sd; LLMemoryStream mstr((U8*)str.c_str(), static_cast(str.size())); S32 count = LLSDSerialize::fromNotation(sd, mstr, str.size()); - ensure_equals("parse count", count, 5); - ensure("sd created", sd.isDefined()); - ensure_equals("sd type", sd.type(), LLSD::TypeMap); - ensure_equals("map element count", sd.size(), 4); - ensure_equals( - "label", - sd["label"].asString(), + CHECK(count == 5); + REQUIRE(sd.isDefined()); + CHECK(sd.type() == LLSD::TypeMap); + REQUIRE(sd.size() == 4); + CHECK(sd["label"].asString() == "short binary test"); std::vector bin = sd["singlebinary"].asBinary(); std::vector expected; expected.resize(1); expected[0] = 'A'; - ensure("single binary", (0 == memcmp(&bin[0], &expected[0], 1))); - ensure_equals( - "single string", - sd["singlerawstring"].asString(), + CHECK(memcmp(&bin[0], &expected[0], 1) == 0); + CHECK(sd["singlerawstring"].asString() == std::string("A")); - ensure_equals("end", sd["endoftest"].asString(), "end"); + CHECK(sd["endoftest"].asString() == "end"); } - template<> template<> - void sd_object::test<10>() + TEST_CASE("LLSD: escapeString escapes single quotes so a message parses back", "[LLSD]") { std::string message("parcel '' is naughty."); @@ -278,44 +312,42 @@ namespace tut << "'}"; std::string expected_str("{'message':'parcel \\'\\' is naughty.'}"); std::string actual_str = str.str(); - ensure_equals("stream contents", actual_str, expected_str); + CHECK(actual_str == expected_str); LLSD sd; S32 count = LLSDSerialize::fromNotation(sd, str, actual_str.size()); - ensure_equals("parse count", count, 2); - ensure("valid parse", sd.isDefined()); + CHECK(count == 2); + CHECK(sd.isDefined()); std::string actual = sd["message"].asString(); - ensure_equals("message contents", actual, message); + CHECK(actual == message); } - template<> template<> - void sd_object::test<11>() + TEST_CASE("LLSD: a string of single and double quotes escapes and parses back", "[LLSD]") { std::string expected("\"\"\"\"''''''\""); std::stringstream str; str << "'" << LLSDNotationFormatter::escapeString(expected) << "'"; LLSD sd; S32 count = LLSDSerialize::fromNotation(sd, str, str.str().size()); - ensure_equals("parse count", count, 1); - ensure_equals("string value", sd.asString(), expected); + CHECK(count == 1); + CHECK(sd.asString() == expected); } - template<> template<> - void sd_object::test<12>() + TEST_CASE("LLSD: a string ending in a backslash escapes and parses back", "[LLSD]") { std::string expected("mytest\\"); std::stringstream str; str << "'" << LLSDNotationFormatter::escapeString(expected) << "'"; LLSD sd; S32 count = LLSDSerialize::fromNotation(sd, str, str.str().size()); - ensure_equals("parse count", count, 1); - ensure_equals("string value", sd.asString(), expected); + CHECK(count == 1); + CHECK(sd.asString() == expected); } - template<> template<> - void sd_object::test<13>() + TEST_CASE("LLSD: random strings escape and parse back", "[LLSD]") { for(S32 i = 0; i < 1000; ++i) { + CAPTURE(i); // gen up a starting point std::string expected; srand(1337 + i); /* Flawfinder: ignore */ @@ -328,7 +360,7 @@ namespace tut str << "'" << LLSDNotationFormatter::escapeString(expected) << "'"; LLSD sd; S32 count = LLSDSerialize::fromNotation(sd, str, expected.size()); - ensure_equals("parse count", count, 1); + REQUIRE(count == 1); std::string actual = sd.asString(); /* if(actual != expected) @@ -359,12 +391,11 @@ namespace tut LL_WARNS() << "actual: " << a_str.str() << LL_ENDL; } */ - ensure_equals("string value", actual, expected); + REQUIRE(actual == expected); } } - template<> template<> - void sd_object::test<14>() + TEST_CASE("LLSD: an instant message parameter array parses", "[LLSD]") { //#if LL_WINDOWS && _MSC_VER >= 1400 // skip_fail("Fails on VS2005 due to broken LLSDSerialize::fromNotation() parser."); @@ -374,103 +405,56 @@ namespace tut istr.str(param); LLSD param_sd; LLSDSerialize::fromNotation(param_sd, istr, param.size()); - ensure_equals("parsed type", param_sd.type(), LLSD::TypeArray); + CHECK(param_sd.type() == LLSD::TypeArray); LLSD version_sd = param_sd[0]; - ensure_equals("version type", version_sd.type(), LLSD::TypeMap); - ensure("has version", version_sd.has("version")); - ensure_equals("version number", version_sd["version"].asInteger(), 1); + CHECK(version_sd.type() == LLSD::TypeMap); + CHECK(version_sd.has("version")); + CHECK(version_sd["version"].asInteger() == 1); LLSD src_sd = param_sd[1]; - ensure_equals("src type", src_sd.type(), LLSD::TypeMap); + CHECK(src_sd.type() == LLSD::TypeMap); LLSD dst_sd = param_sd[2]; - ensure_equals("dst type", dst_sd.type(), LLSD::TypeMap); + CHECK(dst_sd.type() == LLSD::TypeMap); } - template<> template<> - void sd_object::test<15>() + TEST_CASE("LLSD: an undefined value parses in a map in an array", "[LLSD]") { std::string val = "[{'failures':!,'successfuls':[u3c115e51-04f4-523c-9fa6-98aff1034730]}]"; std::istringstream istr; istr.str(val); LLSD sd; LLSDSerialize::fromNotation(sd, istr, val.size()); - ensure_equals("parsed type", sd.type(), LLSD::TypeArray); - ensure_equals("parsed size", sd.size(), 1); + CHECK(sd.type() == LLSD::TypeArray); + REQUIRE(sd.size() == 1); LLSD failures = sd[0]["failures"]; - ensure("no failures.", failures.isUndefined()); + CHECK(failures.isUndefined()); LLSD success = sd[0]["successfuls"]; - ensure_equals("success type", success.type(), LLSD::TypeArray); - ensure_equals("success size", success.size(), 1); - ensure_equals("success instance type", success[0].type(), LLSD::TypeUUID); + CHECK(success.type() == LLSD::TypeArray); + REQUIRE(success.size() == 1); + CHECK(success[0].type() == LLSD::TypeUUID); } - template<> template<> - void sd_object::test<16>() + TEST_CASE("LLSD: booleans parse from f t 0 and 1 in an array and a map", "[LLSD]") { std::string val = "[f,t,0,1,{'foo':t,'bar':f}]"; std::istringstream istr; istr.str(val); LLSD sd; LLSDSerialize::fromNotation(sd, istr, val.size()); - ensure_equals("parsed type", sd.type(), LLSD::TypeArray); - ensure_equals("parsed size", sd.size(), 5); - ensure_equals("element 0 false", sd[0].asBoolean(), false); - ensure_equals("element 1 true", sd[1].asBoolean(), true); - ensure_equals("element 2 false", sd[2].asBoolean(), false); - ensure_equals("element 3 true", sd[3].asBoolean(), true); + CHECK(sd.type() == LLSD::TypeArray); + REQUIRE(sd.size() == 5); + CHECK_FALSE(sd[0].asBoolean()); + CHECK(sd[1].asBoolean()); + CHECK_FALSE(sd[2].asBoolean()); + CHECK(sd[3].asBoolean()); LLSD map = sd[4]; - ensure_equals("element 4 type", map.type(), LLSD::TypeMap); - ensure_equals("map foo type", map["foo"].type(), LLSD::TypeBoolean); - ensure_equals("map foo value", map["foo"].asBoolean(), true); - ensure_equals("map bar type", map["bar"].type(), LLSD::TypeBoolean); - ensure_equals("map bar value", map["bar"].asBoolean(), false); - } - -/* - template<> template<> - void sd_object::test<16>() - { - } -*/ -} - -#if 0 -'{\'task_id\':u1fd77b79-a8e7-25a5-9454-02a4d948ba1c}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t00082000\n\t\tcreator_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t00000000-0000-0000-0000-000000000000\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t10284\n\ttotal_crc\t35\n\ttype\t1\n\ttask_valid\t2\n\ttravel_access\t21\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t0\t0\t0\n\toldpos\t0\t0\t0\n\trotation\t4.371139183945160766597837e-08\t1\t4.371139183945160766597837e-08\t0\n\tvelocity\t0\t0\t0\n\tangvel\t0\t0\t0\n\tscale\t0.2816932\t0.2816932\t0.2816932\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t80\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t6\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\t89556747-24cb-43ed-920b-47caed15465f\n\t\tcolors\t1 1 1 1\n\t\tscales\t0.56\n\t\tscalet\t0.56\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t1132625972249870\n\tbirthtime\t1132625953120694\n\treztime\t1132625953120694\n\tparceltime\t1132625953120694\n\ttax_rate\t1.01615\n\tnamevalue\tAttachmentOrientation VEC3 RW DS -3.141593, 0.000000, -3.141593\n\tnamevalue\tAttachmentOffset VEC3 RW DS 0.000000, 0.000000, 0.000000\n\tnamevalue\tAttachPt U32 RW S 5\n\tnamevalue\tAttachItemID STRING RW SV 1f9975c0-2951-1b93-dd83-46e2b932fcc8\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\torig_asset_id\t52019cdd-b464-ba19-e66d-3da751fef9da\n\torig_item_id\t1f9975c0-2951-1b93-dd83-46e2b932fcc8\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n' -#endif - -namespace tut -{ - struct mem_data - { - }; - typedef test_group mem_test; - typedef mem_test::object mem_object; - tut::mem_test mem_stream("LLMemoryStream"); - - template<> template<> - void mem_object::test<1>() - { - const char HELLO_WORLD[] = "hello world"; - LLMemoryStream mem((U8*)&HELLO_WORLD[0], static_cast(strlen(HELLO_WORLD))); /* Flawfinder: ignore */ - std::string hello; - std::string world; - mem >> hello >> world; - ensure_equals("first word", hello, std::string("hello")); - ensure_equals("second word", world, std::string("world")); + CHECK(map.type() == LLSD::TypeMap); + CHECK(map["foo"].type() == LLSD::TypeBoolean); + CHECK(map["foo"].asBoolean()); + CHECK(map["bar"].type() == LLSD::TypeBoolean); + CHECK_FALSE(map["bar"].asBoolean()); } -} - -namespace tut -{ - struct U64_data - { - }; - typedef test_group U64_test; - typedef U64_test::object U64_object; - tut::U64_test U64_testcase("U64_conversion"); - // U64_to_str - template<> template<> - void U64_object::test<1>() + TEST_CASE("U64_conversion: U64_to_str writes the digits and truncates to the buffer", "[U64_conversion]") { U64 val; std::string val_str; @@ -482,184 +466,141 @@ namespace tut U64_to_str(val, result, sizeof(result)); result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.1", val_str, result_str); + CHECK(val_str == result_str); val = 0; val_str = "0"; U64_to_str(val, result, sizeof(result)); result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.2", val_str, result_str); + CHECK(val_str == result_str); val = U64L(18446744073709551615); // 0xFFFFFFFFFFFFFFFF val_str = "18446744073709551615"; U64_to_str(val, result, sizeof(result)); result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.3", val_str, result_str); + CHECK(val_str == result_str); // overflow - will result in warning at compile time val = U64L(18446744073709551615) + 1; // overflow 0xFFFFFFFFFFFFFFFF + 1 == 0 val_str = "0"; U64_to_str(val, result, sizeof(result)); result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.4", val_str, result_str); + CHECK(val_str == result_str); val = U64(-1); // 0xFFFFFFFFFFFFFFFF == 18446744073709551615 val_str = "18446744073709551615"; U64_to_str(val, result, sizeof(result)); result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.5", val_str, result_str); + CHECK(val_str == result_str); val = U64L(10000000000000000000); // testing preserving of 0s val_str = "10000000000000000000"; U64_to_str(val, result, sizeof(result)); result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.6", val_str, result_str); + CHECK(val_str == result_str); val = 1; // testing no leading 0s val_str = "1"; U64_to_str(val, result, sizeof(result)); result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.7", val_str, result_str); + CHECK(val_str == result_str); val = U64L(18446744073709551615); // testing exact sized buffer for result val_str = "18446744073709551615"; memset(result, 'A', sizeof(result)); // initialize buffer with all 'A' U64_to_str(val, result, sizeof("18446744073709551615")); //pass in the exact size result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.8", val_str, result_str); + CHECK(val_str == result_str); val = U64L(18446744073709551615); // testing smaller sized buffer for result val_str = "1844"; memset(result, 'A', sizeof(result)); // initialize buffer with all 'A' U64_to_str(val, result, 5); //pass in a size of 5. should only copy first 4 integers and add a null terminator result_str = (const char*) result; - ensure_equals("U64_to_str converted 1.9", val_str, result_str); + CHECK(val_str == result_str); } - // str_to_U64 - template<> template<> - void U64_object::test<2>() + TEST_CASE("U64_conversion: str_to_U64 reads the first run of digits and wraps on overflow", "[U64_conversion]") { U64 val; U64 result; val = U64L(18446744073709551610); // slightly less than MAX_U64 result = str_to_U64("18446744073709551610"); - ensure_equals("str_to_U64 converted 2.1", val, result); + CHECK(val == result); val = U64L(0); // empty string result = str_to_U64(LLStringUtil::null); - ensure_equals("str_to_U64 converted 2.2", val, result); + CHECK(val == result); val = U64L(0); // 0 result = str_to_U64("0"); - ensure_equals("str_to_U64 converted 2.3", val, result); + CHECK(val == result); val = U64L(18446744073709551615); // 0xFFFFFFFFFFFFFFFF result = str_to_U64("18446744073709551615"); - ensure_equals("str_to_U64 converted 2.4", val, result); + CHECK(val == result); // overflow - will result in warning at compile time val = U64L(18446744073709551615) + 1; // overflow 0xFFFFFFFFFFFFFFFF + 1 == 0 result = str_to_U64("18446744073709551616"); - ensure_equals("str_to_U64 converted 2.5", val, result); + CHECK(val == result); val = U64L(1234); // process till first non-integral character result = str_to_U64("1234A5678"); - ensure_equals("str_to_U64 converted 2.6", val, result); + CHECK(val == result); val = U64L(5678); // skip all non-integral characters result = str_to_U64("ABCD5678"); - ensure_equals("str_to_U64 converted 2.7", val, result); + CHECK(val == result); // should it skip negative sign and process // rest of string or return 0 val = U64L(1234); // skip initial negative sign result = str_to_U64("-1234"); - ensure_equals("str_to_U64 converted 2.8", val, result); + CHECK(val == result); val = U64L(5678); // stop at negative sign in the middle result = str_to_U64("5678-1234"); - ensure_equals("str_to_U64 converted 2.9", val, result); + CHECK(val == result); val = U64L(0); // no integers result = str_to_U64("AaCD"); - ensure_equals("str_to_U64 converted 2.10", val, result); + CHECK(val == result); } - // U64_to_F64 - template<> template<> - void U64_object::test<3>() + TEST_CASE("U64_conversion: U64_to_F64 converts values from zero to the largest U64", "[U64_conversion]") { F64 val; F64 result; result = 18446744073709551610.0; val = U64_to_F64(U64L(18446744073709551610)); - ensure_equals("U64_to_F64 converted 3.1", val, result); + CHECK(val == result); result = 18446744073709551615.0; // 0xFFFFFFFFFFFFFFFF val = U64_to_F64(U64L(18446744073709551615)); - ensure_equals("U64_to_F64 converted 3.2", val, result); + CHECK(val == result); result = 0.0; // overflow 0xFFFFFFFFFFFFFFFF + 1 == 0 // overflow - will result in warning at compile time val = U64_to_F64(U64L(18446744073709551615)+1); - ensure_equals("U64_to_F64 converted 3.3", val, result); + CHECK(val == result); result = 0.0; // 0 val = U64_to_F64(U64L(0)); - ensure_equals("U64_to_F64 converted 3.4", val, result); + CHECK(val == result); result = 1.0; // odd val = U64_to_F64(U64L(1)); - ensure_equals("U64_to_F64 converted 3.5", val, result); + CHECK(val == result); result = 2.0; // even val = U64_to_F64(U64L(2)); - ensure_equals("U64_to_F64 converted 3.6", val, result); + CHECK(val == result); result = F64(0x7FFFFFFFFFFFFFFF) * 1.0; // 0x7FFFFFFFFFFFFFFF val = U64_to_F64(U64L(0x7FFFFFFFFFFFFFFF)); - ensure_equals("U64_to_F64 converted 3.7", val, result); - } - - // llstrtou64 - // seems to be deprecated - could not find it being used - // anywhere in the tarball - skipping unit tests for now -} - - -namespace tut -{ - struct hash_data - { - }; - typedef test_group hash_test; - typedef hash_test::object hash_object; - tut::hash_test hash_tester("LLHash"); - - template<> template<> - void hash_object::test<1>() - { - const char * str1 = "test string one"; - const char * same_as_str1 = "test string one"; - - size_t hash1 = llhash(str1); - size_t same_as_hash1 = llhash(same_as_str1); - - - ensure("Hashes from identical strings should be equal", hash1 == same_as_hash1); - - char str[100]; - strcpy( str, "Another test" ); - - size_t hash2 = llhash(str); - - strcpy( str, "Different string, same pointer" ); - - size_t hash3 = llhash(str); - - ensure("Hashes from same pointer but different string should not be equal", hash2 != hash3); + CHECK(val == result); } } diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp index f2c2bf168e..6e172e10dd 100644 --- a/indra/llcommon/tests/lazyeventapi_test.cpp +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -17,7 +17,8 @@ // std headers // external library headers // other Linden headers -#include "../test/lltut.h" +#include +#include #include "llevents.h" #include "llsdutil.h" @@ -69,7 +70,7 @@ class MyRegistrar: public LL::LazyEventAPI /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct lazyeventapi_data { @@ -84,36 +85,27 @@ namespace tut LLEventPumps::deleteSingleton(); } }; - typedef test_group lazyeventapi_group; - typedef lazyeventapi_group::object object; - lazyeventapi_group lazyeventapigrp("lazyeventapi"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(lazyeventapi_data, "lazyeventapi: a LazyEventAPI creates its listener when its pump is used", "[lazyeventapi]") { - set_test_name("LazyEventAPI"); // this is where the magic (should) happen // 'register' still a keyword until C++17 MyRegistrar regster; LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey")); - ensure_equals("failed to set data", data.asString(), "hey"); + CHECK(data.asString() == "hey"); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(lazyeventapi_data, "lazyeventapi: without a LazyEventAPI no listener answers its pump", "[lazyeventapi]") { - set_test_name("No LazyEventAPI"); // Because the MyRegistrar declaration in test<1>() is local, because // it has been destroyed, we fully expect NOT to reach a MyListener // instance with this post. LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot")); - ensure("accidentally set data", ! data.isDefined()); + CHECK_FALSE(data.isDefined()); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(lazyeventapi_data, "lazyeventapi: instance_snapshot finds a LazyEventAPI and its metadata", "[lazyeventapi]") { - set_test_name("LazyEventAPI metadata"); MyRegistrar regster; // Of course we have 'regster' in hand; we don't need to search for // it. But this next test verifies that we can find (all) LazyEventAPI @@ -124,19 +116,20 @@ namespace tut for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot()) if ((found = dynamic_cast(®istrar))) break; - ensure("Failed to find MyRegistrar via LLInstanceTracker", found); + UNSCOPED_INFO("Failed to find MyRegistrar via LLInstanceTracker"); + REQUIRE(found); - ensure_equals("wrong API name", found->getName(), "Test"); - ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI"); - ensure_equals("wrong API field", found->getDispatchKey(), "op"); + CHECK(found->getName() == "Test"); + CHECK_THAT(found->getDesc(), Catch::Matchers::ContainsSubstring("test LLEventAPI")); + CHECK(found->getDispatchKey() == "op"); // Normally we'd just iterate over *found. But for test purposes, // actually capture the range of NameDesc pairs in a vector. std::vector ops{ found->begin(), found->end() }; - ensure_equals("failed to find operations", ops.size(), 1); - ensure_equals("wrong operation name", ops[0].first, "set"); - ensure_contains("wrong operation desc", ops[0].second, "set operation"); + REQUIRE(ops.size() == 1); + CHECK(ops[0].first == "set"); + CHECK_THAT(ops[0].second, Catch::Matchers::ContainsSubstring("set operation")); LLSD metadata{ found->getMetadata(ops[0].first) }; - ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first); - ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second); + CHECK(metadata["name"].asString() == ops[0].first); + CHECK(metadata["desc"].asString() == ops[0].second); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llapp_test.cpp b/indra/llcommon/tests/llapp_test.cpp index adcbedee4b..964d6c6b3c 100644 --- a/indra/llcommon/tests/llapp_test.cpp +++ b/indra/llcommon/tests/llapp_test.cpp @@ -25,14 +25,15 @@ * $/LicenseInfo$ */ -#include +#include #include "linden_common.h" #include "llapp.h" -#include "lltut.h" +#include "alteststateguard.h" -namespace tut + +namespace { struct application { @@ -51,15 +52,16 @@ namespace tut ~application() { delete mApp; + // An LLApp ends LLCommon with it, the APR pool and LLTrace's + // master recorder, and this binary is alone so that one can: + // the next case starts from what this one leaves. + ll_test::StateGuard::accept("the LLApp"); + ll_test::StateGuard::accept("the LLTrace master recorder"); + ll_test::StateGuard::accept("the APR pool"); } }; - typedef test_group application_t; - typedef application_t::object application_object_t; - tut::application_t tut_application("application"); - - template<> template<> - void application_object_t::test<1>() + TEST_CASE_METHOD(application, "application: getOption returns the default option data", "[application]") { LLSD defaults; defaults["template"] = "../../../scripts/messages/message_template.msg"; @@ -68,20 +70,18 @@ namespace tut mApp->setOptionData(LLApp::PRIORITY_DEFAULT, defaults); LLSD datadir_sd = mApp->getOption("datadir"); - ensure_equals("data type", datadir_sd.type(), LLSD::TypeString); - ensure_equals( - "data value", datadir_sd.asString(), std::string("data")); + CHECK(datadir_sd.type() == LLSD::TypeString); + CHECK(datadir_sd.asString() == std::string("data")); } - template<> template<> - void application_object_t::test<2>() + TEST_CASE_METHOD(application, "application: a runtime override beats the general configuration", "[application]") { LLSD options; options["boolean-test"] = true; mApp->setOptionData(LLApp::PRIORITY_GENERAL_CONFIGURATION, options); - ensure("bool set", mApp->getOption("boolean-test").asBoolean()); + CHECK(mApp->getOption("boolean-test").asBoolean()); options["boolean-test"] = false; mApp->setOptionData(LLApp::PRIORITY_RUNTIME_OVERRIDE, options); - ensure("bool unset", !mApp->getOption("boolean-test").asBoolean()); + CHECK_FALSE(mApp->getOption("boolean-test").asBoolean()); } } diff --git a/indra/llcommon/tests/llbase64_test.cpp b/indra/llcommon/tests/llbase64_test.cpp index 69b062fc0c..0a3af78e8b 100644 --- a/indra/llcommon/tests/llbase64_test.cpp +++ b/indra/llcommon/tests/llbase64_test.cpp @@ -32,46 +32,34 @@ #include "../llbase64.h" #include "../lluuid.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { - struct base64_data - { - }; - typedef test_group base64_test; - typedef base64_test::object base64_object; - tut::base64_test base64("LLBase64"); - - template<> template<> - void base64_object::test<1>() + TEST_CASE("LLBase64: encode of no bytes and of a null and a set UUID", "[LLBase64]") { std::string result; result = LLBase64::encode(NULL, 0); - ensure("encode nothing", (result == "") ); + CHECK(result == ""); LLUUID nothing; result = LLBase64::encode(¬hing.mData[0], UUID_BYTES); - ensure("encode blank uuid", - (result == "AAAAAAAAAAAAAAAAAAAAAA==") ); + CHECK(result == "AAAAAAAAAAAAAAAAAAAAAA=="); LLUUID id("526a1e07-a19d-baed-84c4-ff08a488d15e"); result = LLBase64::encode(&id.mData[0], UUID_BYTES); - ensure("encode random uuid", - (result == "UmoeB6Gduu2ExP8IpIjRXg==") ); + CHECK(result == "UmoeB6Gduu2ExP8IpIjRXg=="); } - template<> template<> - void base64_object::test<2>() + TEST_CASE("LLBase64: encode of 40 arbitrary bytes", "[LLBase64]") { std::string result; U8 blob[40] = { 115, 223, 172, 255, 140, 70, 49, 125, 236, 155, 45, 199, 101, 17, 164, 131, 230, 19, 80, 64, 112, 53, 135, 98, 237, 12, 26, 72, 126, 14, 145, 143, 118, 196, 11, 177, 132, 169, 195, 134 }; result = LLBase64::encode(&blob[0], 40); - ensure("encode 40 bytes", - (result == "c9+s/4xGMX3smy3HZRGkg+YTUEBwNYdi7QwaSH4OkY92xAuxhKnDhg==") ); + CHECK(result == "c9+s/4xGMX3smy3HZRGkg+YTUEBwNYdi7QwaSH4OkY92xAuxhKnDhg=="); } } diff --git a/indra/llcommon/tests/llcommon_test_checks.cpp b/indra/llcommon/tests/llcommon_test_checks.cpp new file mode 100644 index 0000000000..ec4b618ee2 --- /dev/null +++ b/indra/llcommon/tests/llcommon_test_checks.cpp @@ -0,0 +1,88 @@ +/** + * @file llcommon_test_checks.cpp + * @brief The state guard's checks on what llcommon keeps for the process, and + * the event pumps' end after every test case. + * + * $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 "alteststateguard.h" +#include "llerrorcontrol.h" +#include "llevents.h" +#include "threadpool.h" +#include "workqueue.h" + +#include +#include + +#include +#include +#include + +namespace +{ + // The names a tracked class's live instances go by, sorted. + template + std::string liveNames() + { + std::vector names; + for (const std::string& name : typename TRACKED::key_snapshot()) + { + names.push_back(name); + } + std::sort(names.begin(), names.end()); + std::string text; + for (const std::string& name : names) + { + text += (text.empty() ? "" : ", ") + name; + } + return text; + } + + class LLCommonChecks : public Catch::EventListenerBase + { + public: + using Catch::EventListenerBase::EventListenerBase; + + void testRunStarting(Catch::TestRunInfo const&) override + { + // A case that starts worker threads stops them before it ends: + // a thread still running would be working, and counting LLSD + // objects, through every later case. + ll_test::StateGuard::add("the live thread pools", liveNames); + ll_test::StateGuard::add("the live work queues", liveNames); + // A case that turns logging up or down for itself puts it back. + ll_test::StateGuard::add("LLError's default level", + [] { return std::to_string(LLError::getDefaultLevel()); }); + } + + // Each case starts with no event pumps: a pump a case made, and the + // listeners still on it, would otherwise reach the cases after it, + // as a file's own cases always reached each other under TUT. + void testCaseEnded(Catch::TestCaseStats const&) override + { + LLEventPumps::deleteSingleton(); + } + }; +} + +CATCH_REGISTER_LISTENER(LLCommonChecks) diff --git a/indra/llcommon/tests/llcond_test.cpp b/indra/llcommon/tests/llcond_test.cpp index f2a302ed13..399f14a6a5 100644 --- a/indra/llcommon/tests/llcond_test.cpp +++ b/indra/llcommon/tests/llcond_test.cpp @@ -17,51 +17,44 @@ // std headers // external library headers // other Linden headers -#include "../test/lltut.h" +#include #include "llcoros.h" /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct llcond_data { LLScalarCond cond{0}; }; - typedef test_group llcond_group; - typedef llcond_group::object object; - llcond_group llcondgrp("llcond"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(llcond_data, "llcond: a wait for the value it already holds returns at once", "[llcond]") { - set_test_name("Immediate gratification"); cond.set_one(1); - ensure("wait_for_equal() failed", - cond.wait_for_equal(F32Milliseconds(1), 1)); - ensure("wait_for_unequal() should have failed", - ! cond.wait_for_unequal(F32Milliseconds(1), 1)); + CHECK(cond.wait_for_equal(F32Milliseconds(1), 1)); + CHECK_FALSE(cond.wait_for_unequal(F32Milliseconds(1), 1)); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(llcond_data, "llcond: two coroutines take turns through set_all and wait_equal", "[llcond]") { - set_test_name("Simple two-coroutine test"); LLCoros::instance().launch( "test<2>", [this]() { // Lambda immediately entered -- control comes here first. - ensure_equals(cond.get(), 0); + // CHECKs: a REQUIRE that failed would end the coroutine before + // it set what the test waits for, and the case would hang. + CHECK(cond.get() == 0); cond.set_all(1); cond.wait_equal(2); - ensure_equals(cond.get(), 2); + CHECK(cond.get() == 2); cond.set_all(3); }); // Main coroutine is resumed only when the lambda waits. - ensure_equals(cond.get(), 1); + CHECK(cond.get() == 1); cond.set_all(2); cond.wait_equal(3); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/lldate_test.cpp b/indra/llcommon/tests/lldate_test.cpp index 9a4116e4a4..bee9279b72 100644 --- a/indra/llcommon/tests/lldate_test.cpp +++ b/indra/llcommon/tests/lldate_test.cpp @@ -31,7 +31,7 @@ #include "../llstring.h" #include "../lldate.h" -#include "../test/lltut.h" +#include #define VALID_DATE "2003-04-30T04:00:00Z" @@ -65,14 +65,6 @@ namespace tut { - struct date_test - { - - }; - typedef test_group date_test_t; - typedef date_test_t::object date_test_object_t; - tut::date_test_t tut_date_test("LLDate"); - // Compile-time verification that LLDate's default ctor, accessors, and // comparison operators are constexpr. static_assert(LLDate{}.isNull(), "default LLDate is null at compile time"); @@ -84,58 +76,65 @@ namespace tut static_assert(LLDate{} >= LLDate{}, "default LLDate >= itself"); /* format validation */ - template<> template<> - void date_test_object_t::test<1>() + TEST_CASE("LLDate: fromString accepts valid dates and rejects malformed ones", "[LLDate]") { LLDate date(VALID_DATE); std::string expected_string; bool result; expected_string = VALID_DATE; - ensure_equals("Valid Date failed" , expected_string, date.asString()); + CHECK(expected_string == date.asString()); result = date.fromString(VALID_DATE_LEAP); expected_string = VALID_DATE_LEAP; - ensure_equals("VALID_DATE_LEAP failed" , expected_string, date.asString()); + CHECK(expected_string == date.asString()); result = date.fromString(VALID_DATE_HOUR_BOUNDARY); expected_string = VALID_DATE_HOUR_BOUNDARY; - ensure_equals("VALID_DATE_HOUR_BOUNDARY failed" , expected_string, date.asString()); + CHECK(expected_string == date.asString()); result = date.fromString(VALID_DATE_FRACTIONAL_SECS); expected_string = VALID_DATE_FRACTIONAL_SECS; - ensure_equals("VALID_DATE_FRACTIONAL_SECS failed" , expected_string, date.asString()); + CHECK(expected_string == date.asString()); result = date.fromString(INVALID_DATE_MISSING_YEAR); - ensure_equals("INVALID_DATE_MISSING_YEAR should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MISSING_YEAR should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MISSING_MONTH); - ensure_equals("INVALID_DATE_MISSING_MONTH should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MISSING_MONTH should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MISSING_DATE); - ensure_equals("INVALID_DATE_MISSING_DATE should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MISSING_DATE should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MISSING_T); - ensure_equals("INVALID_DATE_MISSING_T should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MISSING_T should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MISSING_HOUR); - ensure_equals("INVALID_DATE_MISSING_HOUR should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MISSING_HOUR should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MISSING_MIN); - ensure_equals("INVALID_DATE_MISSING_MIN should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MISSING_MIN should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MISSING_SEC); - ensure_equals("INVALID_DATE_MISSING_SEC should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MISSING_SEC should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MISSING_Z); - ensure_equals("INVALID_DATE_MISSING_Z should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MISSING_Z should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_EMPTY); - ensure_equals("INVALID_DATE_EMPTY should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_EMPTY should have failed"); + CHECK_FALSE(result); } /* Invalid Value Handling */ - template<> template<> - void date_test_object_t::test<2>() + TEST_CASE("LLDate: fromString rejects out-of-range field values", "[LLDate]") { #if LL_DATE_PARSER_CHECKS_BOUNDARY LLDate date; @@ -143,72 +142,76 @@ namespace tut bool result; result = date.fromString(INVALID_DATE_24HOUR_BOUNDARY); - ensure_equals("INVALID_DATE_24HOUR_BOUNDARY should have failed" , result, false); - ensure_equals("INVALID_DATE_24HOUR_BOUNDARY date still set to old value on failure!" , date.secondsSinceEpoch(), 0); + UNSCOPED_INFO("INVALID_DATE_24HOUR_BOUNDARY should have failed"); + CHECK_FALSE(result); + UNSCOPED_INFO("a failed parse leaves the date unchanged"); + CHECK(date.secondsSinceEpoch() == 0); result = date.fromString(INVALID_DATE_LEAP); - ensure_equals("INVALID_DATE_LEAP should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_LEAP should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_HOUR); - ensure_equals("INVALID_DATE_HOUR should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_HOUR should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MIN); - ensure_equals("INVALID_DATE_MIN should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MIN should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_SEC); - ensure_equals("INVALID_DATE_SEC should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_SEC should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_YEAR); - ensure_equals("INVALID_DATE_YEAR should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_YEAR should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_MONTH); - ensure_equals("INVALID_DATE_MONTH should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_MONTH should have failed"); + CHECK_FALSE(result); result = date.fromString(INVALID_DATE_DAY); - ensure_equals("INVALID_DATE_DAY should have failed" , result, false); + UNSCOPED_INFO("INVALID_DATE_DAY should have failed"); + CHECK_FALSE(result); #endif } /* API checks */ - template<> template<> - void date_test_object_t::test<3>() + TEST_CASE("LLDate: fromStream reads a date", "[LLDate]") { LLDate date; std::istringstream stream(VALID_DATE); std::string expected_string = VALID_DATE; date.fromStream(stream); - ensure_equals("fromStream failed", date.asString(), expected_string); + CHECK(date.asString() == expected_string); } - template<> template<> - void date_test_object_t::test<4>() + TEST_CASE("LLDate: the copy constructor copies the date", "[LLDate]") { LLDate date1(VALID_DATE); LLDate date2(date1); - ensure_equals("LLDate(const LLDate& date) constructor failed", date1.asString(), date2.asString()); + CHECK(date1.asString() == date2.asString()); } - template<> template<> - void date_test_object_t::test<5>() + TEST_CASE("LLDate: a date built from secondsSinceEpoch equals the original", "[LLDate]") { LLDate date1(VALID_DATE); LLDate date2(date1.secondsSinceEpoch()); - ensure_equals("secondsSinceEpoch not equal",date1.secondsSinceEpoch(), date2.secondsSinceEpoch()); - ensure_equals("LLDate created using secondsSinceEpoch not equal", date1.asString(), date2.asString()); + CHECK(date1.secondsSinceEpoch() == date2.secondsSinceEpoch()); + CHECK(date1.asString() == date2.asString()); } - template<> template<> - void date_test_object_t::test<6>() + TEST_CASE("LLDate: operator<< writes the date string", "[LLDate]") { LLDate date(VALID_DATE); std::ostringstream stream; stream << date; std::string expected_str = VALID_DATE; - ensure_equals("ostringstream failed", expected_str, stream.str()); + CHECK(expected_str == stream.str()); } - template<> template<> - void date_test_object_t::test<7>() + TEST_CASE("LLDate: operator>> reads a date that operator<< writes back the same", "[LLDate]") { LLDate date; std::istringstream stream(VALID_DATE); @@ -217,60 +220,55 @@ namespace tut std::ostringstream out_stream; out_stream << date; - ensure_equals("<< failed", date.asString(),expected_str); - ensure_equals("<< to >> failed", stream.str(),out_stream.str()); + CHECK(date.asString() == expected_str); + CHECK(stream.str() == out_stream.str()); } // Comparison operators (now constexpr in the header) — runtime coverage. - template<> template<> - void date_test_object_t::test<8>() + TEST_CASE("LLDate: comparison operators order dates by their seconds", "[LLDate]") { LLDate earlier(1000.0); LLDate later(2000.0); LLDate also_earlier(1000.0); - ensure("earlier < later", earlier < later); - ensure("later > earlier", later > earlier); - ensure("earlier <= later", earlier <= later); - ensure("later >= earlier", later >= earlier); - ensure("equal <=", earlier <= also_earlier); - ensure("equal >=", earlier >= also_earlier); - ensure("equal ==", earlier == also_earlier); - ensure("differ !=", earlier != later); - ensure("!(equal <)", !(earlier < also_earlier)); - ensure("!(equal >)", !(earlier > also_earlier)); + CHECK(earlier < later); + CHECK(later > earlier); + CHECK(earlier <= later); + CHECK(later >= earlier); + CHECK(earlier <= also_earlier); + CHECK(earlier >= also_earlier); + CHECK(earlier == also_earlier); + CHECK(earlier != later); + CHECK_FALSE(earlier < also_earlier); + CHECK_FALSE(earlier > also_earlier); } // isNull / notNull / setter round-trip. - template<> template<> - void date_test_object_t::test<9>() + TEST_CASE("LLDate: isNull and notNull follow the seconds set", "[LLDate]") { LLDate d; - ensure("default isNull", d.isNull()); - ensure("default !notNull", !d.notNull()); + CHECK(d.isNull()); + CHECK_FALSE(d.notNull()); d.secondsSinceEpoch(1234.5); - ensure("after set, !isNull", !d.isNull()); - ensure("after set, notNull", d.notNull()); - ensure_equals("getter round-trips", d.secondsSinceEpoch(), 1234.5); + CHECK_FALSE(d.isNull()); + CHECK(d.notNull()); + CHECK(d.secondsSinceEpoch() == 1234.5); d.secondsSinceEpoch(0.0); - ensure("zero -> isNull again", d.isNull()); + CHECK(d.isNull()); } // toLocalDateString / toHTTPDateString must not crash on the epoch value // (regression for the thread-safe localtime_utc/gmtime_utc rewrite). - template<> template<> - void date_test_object_t::test<10>() + TEST_CASE("LLDate: the date string formatters give output for the epoch value", "[LLDate]") { LLDate d; - ensure("toLocalDateString does not throw", - d.toLocalDateString(std::string("%Y-%m-%d")).size() > 0); - ensure("toHTTPDateString does not throw", - d.toHTTPDateString(std::string("%Y-%m-%d")).size() > 0); + CHECK(d.toLocalDateString(std::string("%Y-%m-%d")).size() > 0); + CHECK(d.toHTTPDateString(std::string("%Y-%m-%d")).size() > 0); // RFC1123 wraps toHTTPDateString; verifies the day-of-week token // is present (a strftime success indicator that doesn't pin locale). std::string rfc = d.asRFC1123(); - ensure("asRFC1123 produces output", rfc.size() > 0); + CHECK(rfc.size() > 0); } } diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp index 01e6e8e2f7..06cb890e99 100644 --- a/indra/llcommon/tests/lldeadmantimer_test.cpp +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -30,7 +30,8 @@ #include "../llsd.h" #include "../lltimer.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" // Convert between floating point time deltas and U64 time deltas. // Reflects an implementation detail inside lldeadmantimer.cpp @@ -46,7 +47,7 @@ static F64 u64_time_to_float(LLDeadmanTimer::time_type delta) } -namespace tut +namespace { struct deadmantimer_test @@ -58,13 +59,8 @@ struct deadmantimer_test } }; -typedef test_group deadmantimer_group_t; -typedef deadmantimer_group_t::object deadmantimer_object_t; -tut::deadmantimer_group_t deadmantimer_instance("LLDeadmanTimer"); - // Basic construction test and isExpired() call -template<> template<> -void deadmantimer_object_t::test<1>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: a new timer is not expired and isExpired changes nothing", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -72,10 +68,10 @@ void deadmantimer_object_t::test<1>() U64 count(U64L(8)); LLDeadmanTimer timer(10.0, false); - ensure_equals("WOCM isExpired() returns false after ctor()", timer.isExpired(0, started, stopped, count), false); - ensure_approximately_equals("WOCM t1 - isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("WOCM t1 - isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("WOCM t1 - isExpired() does not modify count", count, U64L(8)); + CHECK_FALSE(timer.isExpired(0, started, stopped, count)); + CHECK_THAT(started, ll_test::WithinBits(F64(42.0), 2)); + CHECK_THAT(stopped, ll_test::WithinBits(F64(97.0), 2)); + CHECK(count == U64L(8)); } { @@ -84,19 +80,18 @@ void deadmantimer_object_t::test<1>() U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(10.0, true); - ensure_equals("WCM isExpired() returns false after ctor()", timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false); - ensure_approximately_equals("WCM t1 - isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("WCM t1 - isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("WCM t1 - isExpired() does not modify count", count, U64L(8)); - ensure_equals("WCM t1 - isExpired() does not modify user_cpu", user_cpu, U64L(29000)); - ensure_equals("WCM t1 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000)); + CHECK_FALSE(timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu)); + CHECK_THAT(started, ll_test::WithinBits(F64(42.0), 2)); + CHECK_THAT(stopped, ll_test::WithinBits(F64(97.0), 2)); + CHECK(count == U64L(8)); + CHECK(user_cpu == U64L(29000)); + CHECK(sys_cpu == U64L(57000)); } } // Construct with zero horizon - not useful generally but will be useful in testing -template<> template<> -void deadmantimer_object_t::test<2>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: an unstarted zero-horizon timer is not expired", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -104,8 +99,7 @@ void deadmantimer_object_t::test<2>() U64 count(U64L(8)); LLDeadmanTimer timer(0.0, false); // Zero is pre-expired - ensure_equals("WOCM isExpired() still returns false with 0.0 time ctor()", - timer.isExpired(0, started, stopped, count), false); + CHECK_FALSE(timer.isExpired(0, started, stopped, count)); } { @@ -114,16 +108,14 @@ void deadmantimer_object_t::test<2>() U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(0.0, true); // Zero is pre-expired - ensure_equals("WCM isExpired() still returns false with 0.0 time ctor()", - timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false); + CHECK_FALSE(timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu)); } } // "pre-expired" timer - starting a timer with a 0.0 horizon will result in // expiration on first test. -template<> template<> -void deadmantimer_object_t::test<3>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: a started zero-horizon timer expires at the first test", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -132,9 +124,9 @@ void deadmantimer_object_t::test<3>() LLDeadmanTimer timer(0.0, false); timer.start(0); - ensure_equals("WOCM isExpired() returns true with 0.0 horizon time", - timer.isExpired(0, started, stopped, count), true); - ensure_approximately_equals("WOCM expired timer with no bell ringing has stopped == started", started, stopped, 8); + CHECK(timer.isExpired(0, started, stopped, count)); + UNSCOPED_INFO("with no bell rung, stopped stays equal to started"); + CHECK_THAT(started, ll_test::WithinBits(stopped, 8)); } { // With cpu metrics @@ -143,16 +135,15 @@ void deadmantimer_object_t::test<3>() LLDeadmanTimer timer(0.0, true); timer.start(0); - ensure_equals("WCM isExpired() returns true with 0.0 horizon time", - timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), true); - ensure_approximately_equals("WCM expired timer with no bell ringing has stopped == started", started, stopped, 8); + CHECK(timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu)); + UNSCOPED_INFO("with no bell rung, stopped stays equal to started"); + CHECK_THAT(started, ll_test::WithinBits(stopped, 8)); } } // "pre-expired" timer - bell rings are ignored as we're already expired. -template<> template<> -void deadmantimer_object_t::test<4>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: a pre-expired timer ignores the bell", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -162,9 +153,9 @@ void deadmantimer_object_t::test<4>() timer.start(0); timer.ringBell(LLDeadmanTimer::getNow() + float_time_to_u64(1000.0), 1); - ensure_equals("WOCM isExpired() returns true with 0.0 horizon time after bell ring", - timer.isExpired(0, started, stopped, count), true); - ensure_approximately_equals("WOCM ringBell has no impact on expired timer leaving stopped == started", started, stopped, 8); + CHECK(timer.isExpired(0, started, stopped, count)); + UNSCOPED_INFO("the bell leaves stopped equal to started"); + CHECK_THAT(started, ll_test::WithinBits(stopped, 8)); } { // With cpu metrics @@ -174,16 +165,15 @@ void deadmantimer_object_t::test<4>() timer.start(0); timer.ringBell(LLDeadmanTimer::getNow() + float_time_to_u64(1000.0), 1); - ensure_equals("WCM isExpired() returns true with 0.0 horizon time after bell ring", - timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), true); - ensure_approximately_equals("WCM ringBell has no impact on expired timer leaving stopped == started", started, stopped, 8); + CHECK(timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu)); + UNSCOPED_INFO("the bell leaves stopped equal to started"); + CHECK_THAT(started, ll_test::WithinBits(stopped, 8)); } } // start(0) test - unexpired timer reports unexpired -template<> template<> -void deadmantimer_object_t::test<5>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: start(0) leaves a timer unexpired and isExpired changes nothing", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -192,11 +182,10 @@ void deadmantimer_object_t::test<5>() LLDeadmanTimer timer(10.0, false); timer.start(0); - ensure_equals("WOCM isExpired() returns false after starting with 10.0 horizon time", - timer.isExpired(0, started, stopped, count), false); - ensure_approximately_equals("WOCM t5 - isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("WOCM t5 - isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("WOCM t5 - isExpired() does not modify count", count, U64L(8)); + CHECK_FALSE(timer.isExpired(0, started, stopped, count)); + CHECK_THAT(started, ll_test::WithinBits(F64(42.0), 2)); + CHECK_THAT(stopped, ll_test::WithinBits(F64(97.0), 2)); + CHECK(count == U64L(8)); } { // With cpu metrics @@ -205,20 +194,18 @@ void deadmantimer_object_t::test<5>() LLDeadmanTimer timer(10.0, true); timer.start(0); - ensure_equals("WCM isExpired() returns false after starting with 10.0 horizon time", - timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false); - ensure_approximately_equals("WCM t5 - isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("WCM t5 - isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("WCM t5 - isExpired() does not modify count", count, U64L(8)); - ensure_equals("WCM t5 - isExpired() does not modify user_cpu", user_cpu, U64L(29000)); - ensure_equals("WCM t5 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000)); + CHECK_FALSE(timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu)); + CHECK_THAT(started, ll_test::WithinBits(F64(42.0), 2)); + CHECK_THAT(stopped, ll_test::WithinBits(F64(97.0), 2)); + CHECK(count == U64L(8)); + CHECK(user_cpu == U64L(29000)); + CHECK(sys_cpu == U64L(57000)); } } // start() test - start in the past but not beyond 1 horizon -template<> template<> -void deadmantimer_object_t::test<6>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: a timer started less than a horizon ago is not expired", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -233,11 +220,10 @@ void deadmantimer_object_t::test<6>() LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); LLDeadmanTimer::time_type now(the_past + float_time_to_u64(5.0)); timer.start(the_past); - ensure_equals("WOCM t6 - isExpired() returns false with 10.0 horizon time starting 5.0 in past", - timer.isExpired(now, started, stopped, count), false); - ensure_approximately_equals("WOCM t6 - isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("WOCM t6 - isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("WOCM t6 - isExpired() does not modify count", count, U64L(8)); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); + CHECK_THAT(started, ll_test::WithinBits(F64(42.0), 2)); + CHECK_THAT(stopped, ll_test::WithinBits(F64(97.0), 2)); + CHECK(count == U64L(8)); } { // With cpu metrics @@ -252,20 +238,18 @@ void deadmantimer_object_t::test<6>() LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); LLDeadmanTimer::time_type now(the_past + float_time_to_u64(5.0)); timer.start(the_past); - ensure_equals("WCM t6 - isExpired() returns false with 10.0 horizon time starting 5.0 in past", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); - ensure_approximately_equals("WCM t6 - isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("WCM t6 - isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("t6 - isExpired() does not modify count", count, U64L(8)); - ensure_equals("WCM t6 - isExpired() does not modify user_cpu", user_cpu, U64L(29000)); - ensure_equals("WCM t6 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000)); + CHECK_FALSE(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); + CHECK_THAT(started, ll_test::WithinBits(F64(42.0), 2)); + CHECK_THAT(stopped, ll_test::WithinBits(F64(97.0), 2)); + CHECK(count == U64L(8)); + CHECK(user_cpu == U64L(29000)); + CHECK(sys_cpu == U64L(57000)); } } // start() test - start in the past but well beyond 1 horizon -template<> template<> -void deadmantimer_object_t::test<7>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: a timer started more than a horizon ago is expired", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -280,9 +264,9 @@ void deadmantimer_object_t::test<7>() LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0)); timer.start(the_past); - ensure_equals("WOCM t7 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", - timer.isExpired(now,started, stopped, count), true); - ensure_approximately_equals("WOCM t7 - starting before horizon still gives equal started / stopped", started, stopped, 8); + CHECK(timer.isExpired(now,started, stopped, count)); + UNSCOPED_INFO("starting before the horizon still gives equal started and stopped"); + CHECK_THAT(started, ll_test::WithinBits(stopped, 8)); } { // With cpu metrics @@ -297,16 +281,15 @@ void deadmantimer_object_t::test<7>() LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0)); timer.start(the_past); - ensure_equals("WCM t7 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", - timer.isExpired(now,started, stopped, count, user_cpu, sys_cpu), true); - ensure_approximately_equals("WOCM t7 - starting before horizon still gives equal started / stopped", started, stopped, 8); + CHECK(timer.isExpired(now,started, stopped, count, user_cpu, sys_cpu)); + UNSCOPED_INFO("starting before the horizon still gives equal started and stopped"); + CHECK_THAT(started, ll_test::WithinBits(stopped, 8)); } } // isExpired() test - results are read-once. Probes after first true are false. -template<> template<> -void deadmantimer_object_t::test<8>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: isExpired reports an expiry once and then changes nothing", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -321,17 +304,15 @@ void deadmantimer_object_t::test<8>() LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0)); timer.start(the_past); - ensure_equals("WOCM t8 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", - timer.isExpired(now, started, stopped, count), true); + CHECK(timer.isExpired(now, started, stopped, count)); started = 42.0; stopped = 97.0; count = U64L(8); - ensure_equals("WOCM t8 - second isExpired() returns false after true", - timer.isExpired(now, started, stopped, count), false); - ensure_approximately_equals("WOCM t8 - 2nd isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("WOCM t8 - 2nd isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("WOCM t8 - 2nd isExpired() does not modify count", count, U64L(8)); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); + CHECK_THAT(started, ll_test::WithinBits(F64(42.0), 2)); + CHECK_THAT(stopped, ll_test::WithinBits(F64(97.0), 2)); + CHECK(count == U64L(8)); } { // With cpu metrics @@ -346,28 +327,25 @@ void deadmantimer_object_t::test<8>() LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0)); timer.start(the_past); - ensure_equals("WCM t8 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true); + CHECK(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); started = 42.0; stopped = 97.0; count = U64L(8); user_cpu = 29000; sys_cpu = 57000; - ensure_equals("WCM t8 - second isExpired() returns false after true", - timer.isExpired(now, started, stopped, count), false); - ensure_approximately_equals("WCM t8 - 2nd isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("WCM t8 - 2nd isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("WCM t8 - 2nd isExpired() does not modify count", count, U64L(8)); - ensure_equals("WCM t8 - 2nd isExpired() does not modify user_cpu", user_cpu, U64L(29000)); - ensure_equals("WCM t8 - 2nd isExpired() does not modify sys_cpu", sys_cpu, U64L(57000)); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); + CHECK_THAT(started, ll_test::WithinBits(F64(42.0), 2)); + CHECK_THAT(stopped, ll_test::WithinBits(F64(97.0), 2)); + CHECK(count == U64L(8)); + CHECK(user_cpu == U64L(29000)); + CHECK(sys_cpu == U64L(57000)); } } // ringBell() test - see that we can keep a timer from expiring -template<> template<> -void deadmantimer_object_t::test<9>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: ringing the bell keeps a timer from expiring", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -399,18 +377,17 @@ void deadmantimer_object_t::test<9>() timer.ringBell(now, 1); now += float_time_to_u64(1.0); timer.ringBell(now, 1); - ensure_equals("WOCM t9 - 5.0 horizon timer has not timed out after 10 1-second bell rings", - timer.isExpired(now, started, stopped, count), false); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); F64 last_good_ring(u64_time_to_float(now)); // Jump forward and expire now += float_time_to_u64(10.0); - ensure_equals("WOCM t9 - 5.0 horizon timer expires on 10-second jump", - timer.isExpired(now, started, stopped, count), true); - ensure_approximately_equals("WOCM t9 - started matches start() time", started, real_start, 4); - ensure_approximately_equals("WOCM t9 - stopped matches last ringBell() time", stopped, last_good_ring, 4); - ensure_equals("WOCM t9 - 10 good ringBell()s", count, U64L(10)); - ensure_equals("WOCM t9 - single read only", timer.isExpired(now, started, stopped, count), false); + CHECK(timer.isExpired(now, started, stopped, count)); + CHECK_THAT(started, ll_test::WithinBits(real_start, 4)); + CHECK_THAT(stopped, ll_test::WithinBits(last_good_ring, 4)); + CHECK(count == U64L(10)); + UNSCOPED_INFO("an expiry is reported once"); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); } { // With cpu metrics @@ -442,25 +419,23 @@ void deadmantimer_object_t::test<9>() timer.ringBell(now, 1); now += float_time_to_u64(1.0); timer.ringBell(now, 1); - ensure_equals("WCM t9 - 5.0 horizon timer has not timed out after 10 1-second bell rings", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + CHECK_FALSE(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); F64 last_good_ring(u64_time_to_float(now)); // Jump forward and expire now += float_time_to_u64(10.0); - ensure_equals("WCM t9 - 5.0 horizon timer expires on 10-second jump", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true); - ensure_approximately_equals("WCM t9 - started matches start() time", started, real_start, 4); - ensure_approximately_equals("WCM t9 - stopped matches last ringBell() time", stopped, last_good_ring, 4); - ensure_equals("WCM t9 - 10 good ringBell()s", count, U64L(10)); - ensure_equals("WCM t9 - single read only", timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + CHECK(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); + CHECK_THAT(started, ll_test::WithinBits(real_start, 4)); + CHECK_THAT(stopped, ll_test::WithinBits(last_good_ring, 4)); + CHECK(count == U64L(10)); + UNSCOPED_INFO("an expiry is reported once"); + CHECK_FALSE(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); } } // restart after expiration test - verify that restarts behave well -template<> template<> -void deadmantimer_object_t::test<10>() +TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: a timer restarted after expiring times the new run", "[LLDeadmanTimer]") { { // Without cpu metrics @@ -492,18 +467,17 @@ void deadmantimer_object_t::test<10>() timer.ringBell(now, 1); now += float_time_to_u64(1.0); timer.ringBell(now, 1); - ensure_equals("WOCM t10 - 5.0 horizon timer has not timed out after 10 1-second bell rings", - timer.isExpired(now, started, stopped, count), false); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); F64 last_good_ring(u64_time_to_float(now)); // Jump forward and expire now += float_time_to_u64(10.0); - ensure_equals("WOCM t10 - 5.0 horizon timer expires on 10-second jump", - timer.isExpired(now, started, stopped, count), true); - ensure_approximately_equals("WOCM t10 - started matches start() time", started, real_start, 4); - ensure_approximately_equals("WOCM t10 - stopped matches last ringBell() time", stopped, last_good_ring, 4); - ensure_equals("WOCM t10 - 10 good ringBell()s", count, U64L(10)); - ensure_equals("WOCM t10 - single read only", timer.isExpired(now, started, stopped, count), false); + CHECK(timer.isExpired(now, started, stopped, count)); + CHECK_THAT(started, ll_test::WithinBits(real_start, 4)); + CHECK_THAT(stopped, ll_test::WithinBits(last_good_ring, 4)); + CHECK(count == U64L(10)); + UNSCOPED_INFO("an expiry is reported once"); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); // Jump forward and restart now += float_time_to_u64(1.0); @@ -527,19 +501,17 @@ void deadmantimer_object_t::test<10>() timer.ringBell(now, 1); now += float_time_to_u64(1.0); timer.ringBell(now, 1); - ensure_equals("WOCM t10 - 5.0 horizon timer has not timed out after 8 1-second bell rings", - timer.isExpired(now, started, stopped, count), false); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); last_good_ring = u64_time_to_float(now); // Jump forward and expire now += float_time_to_u64(10.0); - ensure_equals("WOCM t10 - 5.0 horizon timer expires on 8-second jump", - timer.isExpired(now, started, stopped, count), true); - ensure_approximately_equals("WOCM t10 - 2nd started matches start() time", started, real_start, 4); - ensure_approximately_equals("WOCM t10 - 2nd stopped matches last ringBell() time", stopped, last_good_ring, 4); - ensure_equals("WOCM t10 - 8 good ringBell()s", count, U64L(8)); - ensure_equals("WOCM t10 - single read only - 2nd start", - timer.isExpired(now, started, stopped, count), false); + CHECK(timer.isExpired(now, started, stopped, count)); + CHECK_THAT(started, ll_test::WithinBits(real_start, 4)); + CHECK_THAT(stopped, ll_test::WithinBits(last_good_ring, 4)); + CHECK(count == U64L(8)); + UNSCOPED_INFO("an expiry is reported once after the restart"); + CHECK_FALSE(timer.isExpired(now, started, stopped, count)); } { // With cpu metrics @@ -572,18 +544,17 @@ void deadmantimer_object_t::test<10>() timer.ringBell(now, 1); now += float_time_to_u64(1.0); timer.ringBell(now, 1); - ensure_equals("WCM t10 - 5.0 horizon timer has not timed out after 10 1-second bell rings", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + CHECK_FALSE(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); F64 last_good_ring(u64_time_to_float(now)); // Jump forward and expire now += float_time_to_u64(10.0); - ensure_equals("WCM t10 - 5.0 horizon timer expires on 10-second jump", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true); - ensure_approximately_equals("WCM t10 - started matches start() time", started, real_start, 4); - ensure_approximately_equals("WCM t10 - stopped matches last ringBell() time", stopped, last_good_ring, 4); - ensure_equals("WCM t10 - 10 good ringBell()s", count, U64L(10)); - ensure_equals("WCM t10 - single read only", timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + CHECK(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); + CHECK_THAT(started, ll_test::WithinBits(real_start, 4)); + CHECK_THAT(stopped, ll_test::WithinBits(last_good_ring, 4)); + CHECK(count == U64L(10)); + UNSCOPED_INFO("an expiry is reported once"); + CHECK_FALSE(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); // Jump forward and restart now += float_time_to_u64(1.0); @@ -607,22 +578,20 @@ void deadmantimer_object_t::test<10>() timer.ringBell(now, 1); now += float_time_to_u64(1.0); timer.ringBell(now, 1); - ensure_equals("WCM t10 - 5.0 horizon timer has not timed out after 8 1-second bell rings", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + CHECK_FALSE(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); last_good_ring = u64_time_to_float(now); // Jump forward and expire now += float_time_to_u64(10.0); - ensure_equals("WCM t10 - 5.0 horizon timer expires on 8-second jump", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true); - ensure_approximately_equals("WCM t10 - 2nd started matches start() time", started, real_start, 4); - ensure_approximately_equals("WCM t10 - 2nd stopped matches last ringBell() time", stopped, last_good_ring, 4); - ensure_equals("WCM t10 - 8 good ringBell()s", count, U64L(8)); - ensure_equals("WCM t10 - single read only - 2nd start", - timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + CHECK(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); + CHECK_THAT(started, ll_test::WithinBits(real_start, 4)); + CHECK_THAT(stopped, ll_test::WithinBits(last_good_ring, 4)); + CHECK(count == U64L(8)); + UNSCOPED_INFO("an expiry is reported once after the restart"); + CHECK_FALSE(timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu)); } } -} // end namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/lldependencies_test.cpp b/indra/llcommon/tests/lldependencies_test.cpp index b13576a985..4bf0149a44 100644 --- a/indra/llcommon/tests/lldependencies_test.cpp +++ b/indra/llcommon/tests/lldependencies_test.cpp @@ -57,7 +57,7 @@ std::ostream& display(std::ostream& out, const T& value) // ...but display std::string enclosed in double quotes. template<> -std::ostream& display(std::ostream& out, const std::string& value) +[[maybe_unused]] std::ostream& display(std::ostream& out, const std::string& value) { out << '"' << value << '"'; return out; @@ -105,7 +105,7 @@ std::ostream& operator<<(std::ostream& out, const std::set& set) /***************************************************************************** * Now we can #include lltut.h *****************************************************************************/ -#include "../test/lltut.h" +#include /***************************************************************************** * Other helpers @@ -155,49 +155,42 @@ bool is_empty(const RANGE& range) /***************************************************************************** * tut test group *****************************************************************************/ -namespace tut +namespace { - struct deps_data - { - }; - typedef test_group deps_group; - typedef deps_group::object deps_object; - tut::deps_group depsgr("LLDependencies"); - - template<> template<> - void deps_object::test<1>() + TEST_CASE("LLDependencies: sort orders keys by their before and after constraints", "[LLDependencies]") { StringDeps deps; StringList empty; // The quick brown fox jumps over the lazy yellow dog. // (note, "The" and "the" are distinct, else this test wouldn't work) deps.add("lazy"); - ensure_equals(sorted_keys(deps), StringList{"lazy"}); + CHECK(sorted_keys(deps) == StringList{"lazy"}); deps.add("jumps"); - ensure("found lazy", deps.get("lazy")); - ensure("not found dog.", ! deps.get("dog.")); + CHECK(deps.get("lazy")); + CHECK_FALSE(deps.get("dog.")); // NOTE: Maybe it's overkill to test each of these intermediate // results before all the interdependencies have been specified. My // thought is simply that if the order changes, I'd like to know why. // A change to the implementation of boost::topological_sort() would // be an acceptable reason, and you can simply update the expected // test output. - ensure_equals(sorted_keys(deps), StringList{ "lazy", "jumps" }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "jumps" }); deps.add("The", 0, empty, { "fox", "dog." }); // Test key accessors - ensure("empty before deps for missing key", is_empty(deps.get_before_range("bogus"))); - ensure("empty before deps for jumps", is_empty(deps.get_before_range("jumps"))); - ensure_equals(instance_from_range< std::set >(deps.get_before_range("The")), std::set{ "dog.", "fox" }); + UNSCOPED_INFO("empty before deps for missing key"); + CHECK(is_empty(deps.get_before_range("bogus"))); + CHECK(is_empty(deps.get_before_range("jumps"))); + CHECK(instance_from_range< std::set >(deps.get_before_range("The")) == std::set{ "dog.", "fox" }); // resume building dependencies - ensure_equals(sorted_keys(deps), StringList{ "lazy", "jumps", "The" }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "jumps", "The" }); deps.add("the", 0, { "The" }); - ensure_equals(sorted_keys(deps), StringList{ "lazy", "jumps", "The", "the" }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "jumps", "The", "the" }); deps.add("fox", 0, { "The" }, { "jumps" }); - ensure_equals(sorted_keys(deps), StringList{ "lazy", "The", "the", "fox", "jumps" }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "The", "the", "fox", "jumps" }); deps.add("the", 0, { "The" }); // same, see if cache works - ensure_equals(sorted_keys(deps), StringList{ "lazy", "The", "the", "fox", "jumps" }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "The", "the", "fox", "jumps" }); deps.add("jumps", 0, empty, { "over" }); // update jumps deps - ensure_equals(sorted_keys(deps), StringList{ "lazy", "The", "the", "fox", "jumps" }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "The", "the", "fox", "jumps" }); /*==========================================================================*| // It drives me nuts that this test doesn't work in the test // framework, because -- for reasons unknown -- running the test @@ -223,27 +216,26 @@ namespace tut } |*==========================================================================*/ deps.add("dog.", 0, { "yellow", "lazy" }); - ensure_equals(instance_from_range< std::set >(deps.get_after_range("dog.")), std::set{ "lazy", "yellow" }); - ensure_equals(sorted_keys(deps), StringList{ "lazy", "The", "the", "fox", "jumps", "dog." }); + CHECK(instance_from_range< std::set >(deps.get_after_range("dog.")) == std::set{ "lazy", "yellow" }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "The", "the", "fox", "jumps", "dog." }); deps.add("quick", 0, { "The" }, { "fox", "brown" }); - ensure_equals(sorted_keys(deps), StringList{ "lazy", "The", "the", "quick", "fox", "jumps", "dog." }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "The", "the", "quick", "fox", "jumps", "dog." }); deps.add("over", 0, { "jumps" }, { "yellow", "the" }); - ensure_equals(sorted_keys(deps), StringList{ "lazy", "The", "quick", "fox", "jumps", "over", "the", "dog." }); + CHECK(sorted_keys(deps) == StringList{ "lazy", "The", "quick", "fox", "jumps", "over", "the", "dog." }); deps.add("yellow", 0, { "the" }, { "lazy" }); - ensure_equals(sorted_keys(deps), StringList{ "The", "quick", "fox", "jumps", "over", "the", "yellow", "lazy", "dog." }); + CHECK(sorted_keys(deps) == StringList{ "The", "quick", "fox", "jumps", "over", "the", "yellow", "lazy", "dog." }); deps.add("brown"); // By now the dependencies are pretty well in place. A change to THIS // order should be viewed with suspicion. - ensure_equals(sorted_keys(deps), StringList{ "The", "quick", "brown", "fox", "jumps", "over", "the", "yellow", "lazy", "dog." }); + CHECK(sorted_keys(deps) == StringList{ "The", "quick", "brown", "fox", "jumps", "over", "the", "yellow", "lazy", "dog." }); StringList keys(StringList{ "The", "brown", "dog.", "fox", "jumps", "lazy", "over", "quick", "the", "yellow" }); - ensure_equals(instance_from_range(deps.get_key_range()), keys); - ensure_equals(instance_from_range(make_transform_range(deps.get_range(), extract_key)), keys); + CHECK(instance_from_range(deps.get_key_range()) == keys); + CHECK(instance_from_range(make_transform_range(deps.get_range(), extract_key)) == keys); // blow off get_node_range() because they're all LLDependenciesEmpty instances } - template<> template<> - void deps_object::test<2>() + TEST_CASE("LLDependencies: range and iterator accessors reach each key and its int node", "[LLDependencies]") { typedef LLDependencies NameIndexDeps; NameIndexDeps nideps; @@ -252,43 +244,44 @@ namespace tut nideps.add("ghi", 3); nideps.add("abc", 1, { "def" }); NameIndexDeps::range range(nideps.get_range()); - ensure_equals(range.begin()->first, "abc"); - ensure_equals(range.begin()->second, 1); + CHECK(range.begin()->first == "abc"); + CHECK(range.begin()->second == 1); range.begin()->second = 0; range.begin()->second = 1; NameIndexDeps::const_range const_range(const_nideps.get_range()); NameIndexDeps::const_iterator const_iterator(const_range.begin()); ++const_iterator; - ensure_equals(const_iterator->first, "def"); - ensure_equals(const_iterator->second, 2); + CHECK(const_iterator->first == "def"); + CHECK(const_iterator->second == 2); // NameIndexDeps::node_range node_range(nideps.get_node_range()); // ensure_equals(instance_from_range >(node_range), make< std::vector >(list_of(1,2,3))); // *node_range.begin() = 0; // *node_range.begin() = 1; NameIndexDeps::const_node_range const_node_range(const_nideps.get_node_range()); - ensure_equals(instance_from_range>(const_node_range), std::vector{ 1, 2, 3 }); + CHECK(instance_from_range>(const_node_range) == std::vector{ 1, 2, 3 }); NameIndexDeps::const_key_range const_key_range(const_nideps.get_key_range()); - ensure_equals(instance_from_range(const_key_range), StringList{ "abc", "def", "ghi" }); + CHECK(instance_from_range(const_key_range) == StringList{ "abc", "def", "ghi" }); NameIndexDeps::sorted_range sorted(const_nideps.sort()); NameIndexDeps::sorted_iterator sortiter(sorted.begin()); - ensure_equals(sortiter->first, "ghi"); - ensure_equals(sortiter->second, 3); + CHECK(sortiter->first == "ghi"); + CHECK(sortiter->second == 3); // test all iterator-flavored versions of get_after_range() StringList def{"def"}; - ensure("empty abc before list", is_empty(nideps.get_before_range(nideps.get_range().begin()))); - ensure_equals(instance_from_range(nideps.get_after_range(nideps.get_range().begin())), + UNSCOPED_INFO("abc has nothing before it"); + CHECK(is_empty(nideps.get_before_range(nideps.get_range().begin()))); + CHECK(instance_from_range(nideps.get_after_range(nideps.get_range().begin())) == def); - ensure_equals(instance_from_range(const_nideps.get_after_range(const_nideps.get_range().begin())), + CHECK(instance_from_range(const_nideps.get_after_range(const_nideps.get_range().begin())) == def); // ensure_equals(instance_from_range(nideps.get_after_range(nideps.get_node_range().begin())), // def); - ensure_equals(instance_from_range(const_nideps.get_after_range(const_nideps.get_node_range().begin())), + CHECK(instance_from_range(const_nideps.get_after_range(const_nideps.get_node_range().begin())) == def); - ensure_equals(instance_from_range(nideps.get_after_range(nideps.get_key_range().begin())), + CHECK(instance_from_range(nideps.get_after_range(nideps.get_key_range().begin())) == def); // advance from "ghi" to "def", which must come after "ghi" ++sortiter; - ensure_equals(instance_from_range(const_nideps.get_after_range(sortiter)), StringList{ "ghi" }); + CHECK(instance_from_range(const_nideps.get_after_range(sortiter)) == StringList{ "ghi" }); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/lldoubledispatch_test.cpp b/indra/llcommon/tests/lldoubledispatch_test.cpp index bafa7a7404..06c5acb979 100644 --- a/indra/llcommon/tests/lldoubledispatch_test.cpp +++ b/indra/llcommon/tests/lldoubledispatch_test.cpp @@ -41,7 +41,7 @@ #include // external library headers // other Linden headers -#include "lltut.h" +#include namespace { @@ -123,7 +123,7 @@ std::string asteroidStation(Asteroid& rock, SpaceStation& dock) } // anonymous namespace /*------------------------------- Test code --------------------------------*/ -namespace tut +namespace { struct dispatch_data { @@ -152,12 +152,8 @@ namespace tut CommercialShip dummyCommercial; SpaceStation dummyStation; }; - typedef test_group dispatch_group; - typedef dispatch_group::object dispatch_object; - tut::dispatch_group ddgr("double dispatch"); - template<> template<> - void dispatch_object::test<1>() + TEST_CASE_METHOD(dispatch_data, "double dispatch: symmetric Type entries match subclasses in either order", "[double_dispatch]") { // Describe param types using explicit DD::Type objects // (order-sensitive add() variant) @@ -168,24 +164,23 @@ namespace tut dispatcher.add(DD::Type(), DD::Type(), asteroidStation, true); // Try colliding them. - ensure_equals(dispatcher(*home, *tug), // reverse params, SpaceShip subclass + CHECK(dispatcher(*home, *tug) == // reverse params, SpaceShip subclass "shipStation"); - ensure_equals(dispatcher(*patrol, *home), // forward params, SpaceShip subclass + CHECK(dispatcher(*patrol, *home) == // forward params, SpaceShip subclass "shipStation"); - ensure_equals(dispatcher(*obstacle, *home), // forward params + CHECK(dispatcher(*obstacle, *home) == // forward params "asteroidStation"); - ensure_equals(dispatcher(*home, *obstacle), // reverse params + CHECK(dispatcher(*home, *obstacle) == // reverse params "asteroidStation"); - ensure_equals(dispatcher(*tug, *obstacle), // forward params, SpaceShip subclass + CHECK(dispatcher(*tug, *obstacle) == // forward params, SpaceShip subclass "shipAsteroid"); - ensure_equals(dispatcher(*obstacle, *patrol), // reverse params, SpaceShip subclass + CHECK(dispatcher(*obstacle, *patrol) == // reverse params, SpaceShip subclass // won't use militaryShipAsteroid() because it was added // in wrong order "shipAsteroid"); } - template<> template<> - void dispatch_object::test<2>() + TEST_CASE_METHOD(dispatch_data, "double dispatch: a specific Type entry added before a general one wins", "[double_dispatch]") { // Describe param types using explicit DD::Type objects // (order-sensitive add() variant) @@ -195,12 +190,11 @@ namespace tut dispatcher.add(DD::Type(), DD::Type(), shipStation, true); dispatcher.add(DD::Type(), DD::Type(), asteroidStation, true); - ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); - ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + CHECK(dispatcher(*patrol, *obstacle) == "militaryShipAsteroid"); + CHECK(dispatcher(*tug, *obstacle) == "shipAsteroid"); } - template<> template<> - void dispatch_object::test<3>() + TEST_CASE_METHOD(dispatch_data, "double dispatch: prototype entries match the most specific pair in their order only", "[double_dispatch]") { // Describe param types with actual prototype instances // (order-insensitive add() variant) @@ -209,13 +203,12 @@ namespace tut dispatcher.add(dummyShip, dummyStation, shipStation); dispatcher.add(dummyAsteroid, dummyStation, asteroidStation); - ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); - ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); - ensure_equals(dispatcher(*obstacle, *patrol), ""); + CHECK(dispatcher(*patrol, *obstacle) == "militaryShipAsteroid"); + CHECK(dispatcher(*tug, *obstacle) == "shipAsteroid"); + CHECK(dispatcher(*obstacle, *patrol) == ""); } - template<> template<> - void dispatch_object::test<4>() + TEST_CASE_METHOD(dispatch_data, "double dispatch: prototype entries match the most specific in any add order", "[double_dispatch]") { // Describe param types with actual prototype instances // (order-insensitive add() variant) @@ -226,19 +219,18 @@ namespace tut dispatcher.add(dummyShip, dummyStation, shipStation); dispatcher.add(dummyAsteroid, dummyStation, asteroidStation); - ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); - ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + CHECK(dispatcher(*patrol, *obstacle) == "militaryShipAsteroid"); + CHECK(dispatcher(*tug, *obstacle) == "shipAsteroid"); } - template<> template<> - void dispatch_object::test<5>() + TEST_CASE_METHOD(dispatch_data, "double dispatch: the templated add matches the most specific in any add order", "[double_dispatch]") { dispatcher.add(shipAsteroid); dispatcher.add(militaryShipAsteroid); dispatcher.add(shipStation); dispatcher.add(asteroidStation); - ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); - ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + CHECK(dispatcher(*patrol, *obstacle) == "militaryShipAsteroid"); + CHECK(dispatcher(*tug, *obstacle) == "shipAsteroid"); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index ce48c34a5a..bf7064d68a 100644 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -35,7 +35,8 @@ #include "../llerrorcontrol.h" #include "../llsd.h" -#include "../test/lltut.h" +#include +#include namespace { @@ -126,10 +127,7 @@ namespace tut std::string message(int n) { - std::ostringstream test_name; - test_name << "testing message " << n << ", not enough messages"; - - tut::ensure(test_name.str(), n < countMessages()); + REQUIRE(n < countMessages()); return mMessages[n]; } @@ -186,16 +184,14 @@ namespace tut return std::dynamic_pointer_cast(mRecorder)->message(n); } - void ensure_message_count(int expectedCount) + void require_message_count(int expectedCount) { - ensure_equals("message count", countMessages(), expectedCount); + REQUIRE(countMessages() == expectedCount); } std::string message_field(int msgnum, LogFieldIndex fieldnum) { - std::ostringstream test_name; - test_name << "testing message " << msgnum << ", not enough messages"; - tut::ensure(test_name.str(), msgnum < countMessages()); + REQUIRE(msgnum < countMessages()); std::string msg(message(msgnum)); @@ -242,37 +238,27 @@ namespace tut return msg.substr(start_field, fieldlen); } - void ensure_message_field_equals(int msgnum, LogFieldIndex fieldnum, const std::string& expectedText) + void require_message_field(int msgnum, LogFieldIndex fieldnum, const std::string& expectedText) { - std::ostringstream test_name; - test_name << "testing message " << msgnum << " field " << FieldName[fieldnum] << "\n message: \"" << message(msgnum) << "\"\n "; - - ensure_equals(test_name.str(), message_field(msgnum, fieldnum), expectedText); + INFO("message " << msgnum << " field " << FieldName[fieldnum] << ": \"" << message(msgnum) << "\""); + REQUIRE(message_field(msgnum, fieldnum) == expectedText); } - void ensure_message_does_not_contain(int n, const std::string& expectedText) + void require_message_lacks(int n, const std::string& expectedText) { - std::ostringstream test_name; - test_name << "testing message " << n; - - ensure_does_not_contain(test_name.str(), message(n), expectedText); + CAPTURE(n); + REQUIRE_THAT(message(n), !Catch::Matchers::ContainsSubstring(expectedText)); } }; - typedef test_group ErrorTestGroup; - typedef ErrorTestGroup::object ErrorTestObject; - - ErrorTestGroup errorTestGroup("error"); - - template<> template<> - void ErrorTestObject::test<1>() + TEST_CASE_METHOD(ErrorTestData, "error: messages reach the recorder in order", "[error]") // basic test of output { LL_INFOS() << "test" << LL_ENDL; LL_INFOS() << "bob" << LL_ENDL; - ensure_message_field_equals(0, MSG_FIELD, "test"); - ensure_message_field_equals(1, MSG_FIELD, "bob"); + require_message_field(0, MSG_FIELD, "test"); + require_message_field(1, MSG_FIELD, "bob"); } } @@ -289,69 +275,66 @@ namespace namespace tut { - template<> template<> - void ErrorTestObject::test<2>() + TEST_CASE_METHOD(ErrorTestData, "error: the default level filters messages", "[error]") // messages are filtered based on default level { #ifdef LL_DISABLE_DEBUG_LOGGING - skip("Debug messages disabled"); + SKIP("Debug messages disabled"); #endif LLError::setDefaultLevel(LLError::LEVEL_DEBUG); writeSome(); - ensure_message_field_equals(0, MSG_FIELD, "one"); - ensure_message_field_equals(0, LEVEL_FIELD, "DEBUG"); - ensure_message_field_equals(0, TAGS_FIELD, "#WriteTag#AnotherTag#"); - ensure_message_field_equals(1, MSG_FIELD, "two"); - ensure_message_field_equals(1, LEVEL_FIELD, "INFO"); - ensure_message_field_equals(1, TAGS_FIELD, "#WriteTag#"); - ensure_message_field_equals(2, MSG_FIELD, "three"); - ensure_message_field_equals(2, LEVEL_FIELD, "WARNING"); - ensure_message_field_equals(2, TAGS_FIELD, "#WriteTag#"); - ensure_message_field_equals(3, MSG_FIELD, "four"); - ensure_message_field_equals(3, LEVEL_FIELD, "ERROR"); - ensure_message_field_equals(3, TAGS_FIELD, "#WriteTag#"); - ensure_message_count(4); + require_message_field(0, MSG_FIELD, "one"); + require_message_field(0, LEVEL_FIELD, "DEBUG"); + require_message_field(0, TAGS_FIELD, "#WriteTag#AnotherTag#"); + require_message_field(1, MSG_FIELD, "two"); + require_message_field(1, LEVEL_FIELD, "INFO"); + require_message_field(1, TAGS_FIELD, "#WriteTag#"); + require_message_field(2, MSG_FIELD, "three"); + require_message_field(2, LEVEL_FIELD, "WARNING"); + require_message_field(2, TAGS_FIELD, "#WriteTag#"); + require_message_field(3, MSG_FIELD, "four"); + require_message_field(3, LEVEL_FIELD, "ERROR"); + require_message_field(3, TAGS_FIELD, "#WriteTag#"); + require_message_count(4); LLError::setDefaultLevel(LLError::LEVEL_INFO); writeSome(); - ensure_message_field_equals(4, MSG_FIELD, "two"); - ensure_message_field_equals(5, MSG_FIELD, "three"); - ensure_message_field_equals(6, MSG_FIELD, "four"); - ensure_message_count(7); + require_message_field(4, MSG_FIELD, "two"); + require_message_field(5, MSG_FIELD, "three"); + require_message_field(6, MSG_FIELD, "four"); + require_message_count(7); LLError::setDefaultLevel(LLError::LEVEL_WARN); writeSome(); - ensure_message_field_equals(7, MSG_FIELD, "three"); - ensure_message_field_equals(8, MSG_FIELD, "four"); - ensure_message_count(9); + require_message_field(7, MSG_FIELD, "three"); + require_message_field(8, MSG_FIELD, "four"); + require_message_count(9); LLError::setDefaultLevel(LLError::LEVEL_ERROR); writeSome(); - ensure_message_field_equals(9, MSG_FIELD, "four"); - ensure_message_count(10); + require_message_field(9, MSG_FIELD, "four"); + require_message_count(10); LLError::setDefaultLevel(LLError::LEVEL_NONE); writeSome(); - ensure_message_count(10); + require_message_count(10); } - template<> template<> - void ErrorTestObject::test<3>() + TEST_CASE_METHOD(ErrorTestData, "error: the output names the level of each message", "[error]") // error type string in output { #ifdef LL_DISABLE_DEBUG_LOGGING - skip("Debug messages disabled"); + SKIP("Debug messages disabled"); #endif writeSome(); - ensure_message_field_equals(0, LEVEL_FIELD, "DEBUG"); - ensure_message_field_equals(1, LEVEL_FIELD, "INFO"); - ensure_message_field_equals(2, LEVEL_FIELD, "WARNING"); - ensure_message_field_equals(3, LEVEL_FIELD, "ERROR"); - ensure_message_count(4); + require_message_field(0, LEVEL_FIELD, "DEBUG"); + require_message_field(1, LEVEL_FIELD, "INFO"); + require_message_field(2, LEVEL_FIELD, "WARNING"); + require_message_field(3, LEVEL_FIELD, "ERROR"); + require_message_count(4); } - template<> template<> - void ErrorTestObject::test<4>() + TEST_CASE_METHOD(ErrorTestData, "error: abbreviateFile strips the source root and leaves other paths", "[error]") // file abbreviation { std::string prev, abbreviateFile = __FILE__; @@ -370,12 +353,9 @@ namespace tut // argument unchanged, THEN check. } while (abbreviateFile != prev); - ensure_ends_with("file name abbreviation", - abbreviateFile, - "llcommon/tests/llerror_test.cpp" - ); - ensure_does_not_contain("file name abbreviation", - abbreviateFile, "indra"); + CHECK_THAT(abbreviateFile, + Catch::Matchers::EndsWith("llcommon/tests/llerror_test.cpp")); + CHECK_THAT(abbreviateFile, !Catch::Matchers::ContainsSubstring("indra")); std::string someFile = #if LL_WINDOWS @@ -386,8 +366,7 @@ namespace tut ; std::string someAbbreviation = LLError::abbreviateFile(someFile); - ensure_equals("non-indra file abbreviation", - someAbbreviation, someFile); + CHECK(someAbbreviation == someFile); } } @@ -496,21 +475,19 @@ namespace std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); } std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); } - void ensure_has(const std::string& message, + void require_has(const std::string& message, const std::string& actual, const std::string& expected) { std::string::size_type n1 = actual.find(expected); if (n1 == std::string::npos) { - std::stringstream ss; - ss << message << ": " << "expected to find a copy of '" << expected - << "' in actual '" << actual << "'"; - throw tut::failure(ss.str().c_str()); + FAIL(message << ": " << "expected to find a copy of '" << expected + << "' in actual '" << actual << "'"); } } typedef std::string (*LogFromFunction)(bool); - void testLogName(LLError::RecorderPtr recorder, LogFromFunction f, + void require_log_name(LLError::RecorderPtr recorder, LogFromFunction f, const std::string& class_name = "") { std::dynamic_pointer_cast(recorder)->clearMessages(); @@ -520,16 +497,16 @@ namespace std::string messageWithoutName = std::dynamic_pointer_cast(recorder)->message(0); std::string messageWithName = std::dynamic_pointer_cast(recorder)->message(1); - ensure_has(name + " logged without name", + require_has(name + " logged without name", messageWithoutName, name); - ensure_has(name + " logged with name", + require_has(name + " logged with name", messageWithName, name); if (!class_name.empty()) { - ensure_has(name + "logged without name", + require_has(name + " logged without name", messageWithoutName, class_name); - ensure_has(name + "logged with name", + require_has(name + " logged with name", messageWithName, class_name); } } @@ -551,38 +528,36 @@ namespace namespace tut { - template<> template<> - void ErrorTestObject::test<5>() + TEST_CASE_METHOD(ErrorTestData, "error: multiline output leaves backslashes and line breaks unescaped", "[error]") // backslash, return, and newline are not escaped with backslashes { #ifdef LL_DISABLE_DEBUG_LOGGING - skip("Debug messages disabled"); + SKIP("Debug messages disabled"); #endif LLError::setDefaultLevel(LLError::LEVEL_DEBUG); setWantsMultiline(true); writeMsgNeedsEscaping(); // but should not be now - ensure_message_field_equals(0, MSG_FIELD, "backslash\\"); - ensure_message_field_equals(1, MSG_FIELD, "newline\nafternewline"); - ensure_message_field_equals(2, MSG_FIELD, "return\rafterreturn"); - ensure_message_field_equals(3, MSG_FIELD, "backslash\\backslash\\"); - ensure_message_field_equals(4, MSG_FIELD, "backslash\\newline\nanothernewline\nafternewline"); - ensure_message_field_equals(5, MSG_FIELD, "backslash\\returnnewline\r\n\\afterbackslash"); - ensure_message_count(6); + require_message_field(0, MSG_FIELD, "backslash\\"); + require_message_field(1, MSG_FIELD, "newline\nafternewline"); + require_message_field(2, MSG_FIELD, "return\rafterreturn"); + require_message_field(3, MSG_FIELD, "backslash\\backslash\\"); + require_message_field(4, MSG_FIELD, "backslash\\newline\nanothernewline\nafternewline"); + require_message_field(5, MSG_FIELD, "backslash\\returnnewline\r\n\\afterbackslash"); + require_message_count(6); } } namespace tut { - template<> template<> - // class/function information in output - void ErrorTestObject::test<6>() + // class/function information in output + TEST_CASE_METHOD(ErrorTestData, "error: messages name the function and class they came from", "[error]") { - testLogName(mRecorder, logFromGlobal); - testLogName(mRecorder, logFromStatic); - testLogName(mRecorder, logFromAnon); - testLogName(mRecorder, logFromNamespace); - testLogName(mRecorder, logFromClassWithLogTypeMember, "ClassWithLogType"); - testLogName(mRecorder, logFromClassWithLogTypeStatic, "ClassWithLogType"); + require_log_name(mRecorder, logFromGlobal); + require_log_name(mRecorder, logFromStatic); + require_log_name(mRecorder, logFromAnon); + require_log_name(mRecorder, logFromNamespace); + require_log_name(mRecorder, logFromClassWithLogTypeMember, "ClassWithLogType"); + require_log_name(mRecorder, logFromClassWithLogTypeStatic, "ClassWithLogType"); } } @@ -623,32 +598,30 @@ namespace namespace tut { - template<> template<> - // handle nested logging - void ErrorTestObject::test<7>() + // handle nested logging + TEST_CASE_METHOD(ErrorTestData, "error: logging while building a message logs both", "[error]") { outerLogger(); - ensure_message_field_equals(0, MSG_FIELD, "inside"); - ensure_message_field_equals(1, MSG_FIELD, "outside(moo)"); - ensure_message_count(2); + require_message_field(0, MSG_FIELD, "inside"); + require_message_field(1, MSG_FIELD, "outside(moo)"); + require_message_count(2); metaLogger(); - ensure_message_field_equals(2, MSG_FIELD, "logging"); - ensure_message_field_equals(3, MSG_FIELD, "meta(baz)"); - ensure_message_count(4); + require_message_field(2, MSG_FIELD, "logging"); + require_message_field(3, MSG_FIELD, "meta(baz)"); + require_message_count(4); } - template<> template<> - // special handling of LL_ERRS() calls - void ErrorTestObject::test<8>() + // special handling of LL_ERRS() calls + TEST_CASE_METHOD(ErrorTestData, "error: LL_ERRS logs its location and calls the fatal function", "[error]") { std::string location = errorReturningLocation(); - ensure_message_field_equals(0, LOCATION_FIELD, location); - ensure_message_field_equals(0, MSG_FIELD, "die"); - ensure_message_count(1); + require_message_field(0, LOCATION_FIELD, location); + require_message_field(0, MSG_FIELD, "die"); + require_message_count(1); - ensure("fatal callback called", fatalWasCalled); + CHECK(fatalWasCalled); } } @@ -667,26 +640,24 @@ namespace namespace tut { - template<> template<> - // time in output (for recorders that need it) - void ErrorTestObject::test<9>() + // time in output (for recorders that need it) + TEST_CASE_METHOD(ErrorTestData, "error: a recorder gets the time only when it asks for it", "[error]") { LLError::setTimeFunction(roswell); setWantsTime(false); ufoSighting(); - ensure_message_field_equals(0, MSG_FIELD, "ufo"); - ensure_message_does_not_contain(0, roswell()); + require_message_field(0, MSG_FIELD, "ufo"); + require_message_lacks(0, roswell()); setWantsTime(true); ufoSighting(); - ensure_message_field_equals(1, MSG_FIELD, "ufo"); - ensure_message_field_equals(1, TIME_FIELD, roswell()); + require_message_field(1, MSG_FIELD, "ufo"); + require_message_field(1, TIME_FIELD, roswell()); } - template<> template<> - // output order - void ErrorTestObject::test<10>() + // output order + TEST_CASE_METHOD(ErrorTestData, "error: fields come in time level tags location function message order", "[error]") { LLError::setTimeFunction(roswell); setWantsTime(true); @@ -695,23 +666,21 @@ namespace tut function; writeReturningLocationAndFunction(location, function); - ensure_equals("order is time level tags location function message", - message(0), + CHECK(message(0) == roswell() + " INFO " + "# " /* no tag */ + location + " " + function + " : " + "apple"); } - template<> template<> - // multiple recorders - void ErrorTestObject::test<11>() + // multiple recorders + TEST_CASE_METHOD(ErrorTestData, "error: every recorder gets each message with its own time setting", "[error]") { LLError::RecorderPtr altRecorder(new TestRecorder()); LLError::addRecorder(altRecorder); LL_INFOS() << "boo" << LL_ENDL; - ensure_message_field_equals(0, MSG_FIELD, "boo"); - ensure_equals("alt recorder count", std::dynamic_pointer_cast(altRecorder)->countMessages(), 1); - ensure_contains("alt recorder message 0", std::dynamic_pointer_cast(altRecorder)->message(0), "boo"); + require_message_field(0, MSG_FIELD, "boo"); + CHECK(std::dynamic_pointer_cast(altRecorder)->countMessages() == 1); + CHECK_THAT(std::dynamic_pointer_cast(altRecorder)->message(0), Catch::Matchers::ContainsSubstring("boo")); LLError::setTimeFunction(roswell); @@ -723,11 +692,11 @@ namespace tut std::string when = roswell(); - ensure_message_does_not_contain(1, when); - ensure_equals("alt recorder count", std::dynamic_pointer_cast(altRecorder)->countMessages(), 2); - ensure_does_not_contain("alt recorder message 1", std::dynamic_pointer_cast(altRecorder)->message(1), when); - ensure_equals("another recorder count", std::dynamic_pointer_cast(anotherRecorder)->countMessages(), 1); - ensure_contains("another recorder message 0", std::dynamic_pointer_cast(anotherRecorder)->message(0), when); + require_message_lacks(1, when); + CHECK(std::dynamic_pointer_cast(altRecorder)->countMessages() == 2); + CHECK_THAT(std::dynamic_pointer_cast(altRecorder)->message(1), !Catch::Matchers::ContainsSubstring(when)); + CHECK(std::dynamic_pointer_cast(anotherRecorder)->countMessages() == 1); + CHECK_THAT(std::dynamic_pointer_cast(anotherRecorder)->message(0), Catch::Matchers::ContainsSubstring(when)); LLError::removeRecorder(altRecorder); LLError::removeRecorder(anotherRecorder); @@ -758,9 +727,8 @@ class TestBeta namespace tut { - template<> template<> - // filtering by class - void ErrorTestObject::test<12>() + // filtering by class + TEST_CASE_METHOD(ErrorTestData, "error: a class level overrides the default level", "[error]") { LLError::setDefaultLevel(LLError::LEVEL_WARN); LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO); @@ -768,17 +736,16 @@ namespace tut TestAlpha::doAll(); TestBeta::doAll(); - ensure_message_field_equals(0, MSG_FIELD, "aim west"); - ensure_message_field_equals(1, MSG_FIELD, "ate eels"); - ensure_message_field_equals(2, MSG_FIELD, "buy iron"); - ensure_message_field_equals(3, MSG_FIELD, "bad word"); - ensure_message_field_equals(4, MSG_FIELD, "big easy"); - ensure_message_count(5); + require_message_field(0, MSG_FIELD, "aim west"); + require_message_field(1, MSG_FIELD, "ate eels"); + require_message_field(2, MSG_FIELD, "buy iron"); + require_message_field(3, MSG_FIELD, "bad word"); + require_message_field(4, MSG_FIELD, "big easy"); + require_message_count(5); } - template<> template<> - // filtering by function, and that it will override class filtering - void ErrorTestObject::test<13>() + // filtering by function, and that it will override class filtering + TEST_CASE_METHOD(ErrorTestData, "error: a function level overrides the class level", "[error]") { LLError::setDefaultLevel(LLError::LEVEL_DEBUG); LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN); @@ -786,15 +753,14 @@ namespace tut LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE); TestBeta::doAll(); - ensure_message_field_equals(0, MSG_FIELD, "buy iron"); - ensure_message_field_equals(1, MSG_FIELD, "bad word"); - ensure_message_count(2); + require_message_field(0, MSG_FIELD, "buy iron"); + require_message_field(1, MSG_FIELD, "bad word"); + require_message_count(2); } - template<> template<> - // filtering by file - // and that it is overridden by both class and function filtering - void ErrorTestObject::test<14>() + // filtering by file + // and that it is overridden by both class and function filtering + TEST_CASE_METHOD(ErrorTestData, "error: a file level yields to class and function levels", "[error]") { LLError::setDefaultLevel(LLError::LEVEL_DEBUG); LLError::setFileLevel(LLError::abbreviateFile(__FILE__), @@ -807,45 +773,43 @@ namespace tut TestAlpha::doAll(); TestBeta::doAll(); - ensure_message_field_equals(0, MSG_FIELD, "any idea"); - ensure_message_field_equals(1, MSG_FIELD, "aim west"); - ensure_message_field_equals(2, MSG_FIELD, "bad word"); - ensure_message_count(3); + require_message_field(0, MSG_FIELD, "any idea"); + require_message_field(1, MSG_FIELD, "aim west"); + require_message_field(2, MSG_FIELD, "bad word"); + require_message_count(3); } - template<> template<> - // proper cached, efficient lookup of filtering - void ErrorTestObject::test<15>() + // proper cached, efficient lookup of filtering + TEST_CASE_METHOD(ErrorTestData, "error: filter lookups are cached until the levels change", "[error]") { LLError::setDefaultLevel(LLError::LEVEL_NONE); TestAlpha::doInfo(); - ensure_message_count(0); - ensure_equals("first check", LLError::shouldLogCallCount(), 1); + require_message_count(0); + CHECK(LLError::shouldLogCallCount() == 1); TestAlpha::doInfo(); - ensure_message_count(0); - ensure_equals("second check", LLError::shouldLogCallCount(), 1); + require_message_count(0); + CHECK(LLError::shouldLogCallCount() == 1); LLError::setClassLevel("TestAlpha", LLError::LEVEL_DEBUG); TestAlpha::doInfo(); - ensure_message_count(1); - ensure_equals("third check", LLError::shouldLogCallCount(), 2); + require_message_count(1); + CHECK(LLError::shouldLogCallCount() == 2); TestAlpha::doInfo(); - ensure_message_count(2); - ensure_equals("fourth check", LLError::shouldLogCallCount(), 2); + require_message_count(2); + CHECK(LLError::shouldLogCallCount() == 2); LLError::setClassLevel("TestAlpha", LLError::LEVEL_WARN); TestAlpha::doInfo(); - ensure_message_count(2); - ensure_equals("fifth check", LLError::shouldLogCallCount(), 3); + require_message_count(2); + CHECK(LLError::shouldLogCallCount() == 3); TestAlpha::doInfo(); - ensure_message_count(2); - ensure_equals("sixth check", LLError::shouldLogCallCount(), 3); + require_message_count(2); + CHECK(LLError::shouldLogCallCount() == 3); } - template<> template<> - // configuration from LLSD - void ErrorTestObject::test<16>() + // configuration from LLSD + TEST_CASE_METHOD(ErrorTestData, "error: configure sets levels from LLSD and can be run again", "[error]") { LLSD config; config["print-location"] = true; @@ -872,10 +836,10 @@ namespace tut TestAlpha::doAll(); TestBeta::doAll(); - ensure_message_field_equals(0, MSG_FIELD, "any idea"); - ensure_message_field_equals(1, MSG_FIELD, "aim west"); - ensure_message_field_equals(2, MSG_FIELD, "bad word"); - ensure_message_count(3); + require_message_field(0, MSG_FIELD, "any idea"); + require_message_field(1, MSG_FIELD, "aim west"); + require_message_field(2, MSG_FIELD, "bad word"); + require_message_count(3); // make sure reconfiguring works LLSD config2; @@ -885,32 +849,31 @@ namespace tut TestAlpha::doAll(); TestBeta::doAll(); - ensure_message_field_equals(3, MSG_FIELD, "aim west"); - ensure_message_field_equals(4, MSG_FIELD, "ate eels"); - ensure_message_field_equals(5, MSG_FIELD, "bad word"); - ensure_message_field_equals(6, MSG_FIELD, "big easy"); - ensure_message_count(7); + require_message_field(3, MSG_FIELD, "aim west"); + require_message_field(4, MSG_FIELD, "ate eels"); + require_message_field(5, MSG_FIELD, "bad word"); + require_message_field(6, MSG_FIELD, "big easy"); + require_message_count(7); } } namespace tut { - template<> template<> - void ErrorTestObject::test<17>() + TEST_CASE_METHOD(ErrorTestData, "error: single-line output escapes backslashes and line breaks", "[error]") // backslash, return, and newline are escaped with backslashes { #ifdef LL_DISABLE_DEBUG_LOGGING - skip("Debug messages disabled"); + SKIP("Debug messages disabled"); #endif LLError::setDefaultLevel(LLError::LEVEL_DEBUG); writeMsgNeedsEscaping(); - ensure_message_field_equals(0, MSG_FIELD, "backslash\\\\"); - ensure_message_field_equals(1, MSG_FIELD, "newline\\nafternewline"); - ensure_message_field_equals(2, MSG_FIELD, "return\\rafterreturn"); - ensure_message_field_equals(3, MSG_FIELD, "backslash\\\\backslash\\\\"); - ensure_message_field_equals(4, MSG_FIELD, "backslash\\\\newline\\nanothernewline\\nafternewline"); - ensure_message_field_equals(5, MSG_FIELD, "backslash\\\\returnnewline\\r\\n\\\\afterbackslash"); - ensure_message_count(6); + require_message_field(0, MSG_FIELD, "backslash\\\\"); + require_message_field(1, MSG_FIELD, "newline\\nafternewline"); + require_message_field(2, MSG_FIELD, "return\\rafterreturn"); + require_message_field(3, MSG_FIELD, "backslash\\\\backslash\\\\"); + require_message_field(4, MSG_FIELD, "backslash\\\\newline\\nanothernewline\\nafternewline"); + require_message_field(5, MSG_FIELD, "backslash\\\\returnnewline\\r\\n\\\\afterbackslash"); + require_message_count(6); } } @@ -925,21 +888,20 @@ namespace namespace tut { - template<> template<> - void ErrorTestObject::test<18>() + TEST_CASE_METHOD(ErrorTestData, "error: a space in a tag is an error", "[error]") // space character is not allowed in a tag { #ifdef LL_DISABLE_DEBUG_LOGGING - skip("Debug messages disabled"); + SKIP("Debug messages disabled"); #endif LLError::setDefaultLevel(LLError::LEVEL_DEBUG); fatalWasCalled = false; std::string location = writeTagWithSpaceReturningLocation(); std::string expected = "Space is not allowed in a log tag at " + location; - ensure_message_field_equals(0, LEVEL_FIELD, "ERROR"); - ensure_message_field_equals(0, MSG_FIELD, expected); - ensure("fatal callback called", fatalWasCalled); + require_message_field(0, LEVEL_FIELD, "ERROR"); + require_message_field(0, MSG_FIELD, expected); + CHECK(fatalWasCalled); } } diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index a16acb3307..6de805ba98 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -37,8 +37,10 @@ #include #include -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../test/lltestapp.h" +#include "alteststateguard.h" #include "llsd.h" #include "llsdutil.h" #include "llevents.h" @@ -93,7 +95,8 @@ class ImmediateAPI /// ran ~LLApp after each, and that ends LLCommon -- the APR pool, the LLTrace /// master recorder -- for every test after it in the process. So there is one /// app for the process, never destroyed, which each test sees start running -/// and stop. +/// and stop. Making it changes the LLApp and LLTrace's master recorder on +/// purpose, so the state guard takes those as they are then. class CoroTestApp: public LLTestApp { public: @@ -110,7 +113,13 @@ class CoroTestApp: public LLTestApp private: static CoroTestApp& app() { - static CoroTestApp* sApp = new CoroTestApp; + static CoroTestApp* sApp = nullptr; + if (!sApp) + { + sApp = new CoroTestApp; + ll_test::StateGuard::accept("the LLApp"); + ll_test::StateGuard::accept("the LLTrace master recorder"); + } return *sApp; } }; @@ -120,7 +129,7 @@ class CoroTestApp: public LLTestApp /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct lleventcoro_data { @@ -137,9 +146,6 @@ namespace tut void postAndWait1(); void coroPumpPost(); }; - typedef test_group coroutine_group; - typedef coroutine_group::object object; - coroutine_group coroutinegrp("coroutine"); void lleventcoro_data::explicit_wait(std::shared_ptr>& cbp) { @@ -164,15 +170,15 @@ namespace tut debug("about to suspend"); stringdata = future.get(); mSync.bump(); - ensure_equals("Got it", stringdata, "received"); + // A CHECK: a REQUIRE's throw would leave this coroutine for + // LLCoros to rethrow in whichever case came next. + CHECK(stringdata == "received"); } END } - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(lleventcoro_data, "coroutine: a coroutine suspended on a future wakes when its promise is set", "[coroutine]") { - set_test_name("explicit_wait"); DEBUG; // Construct the coroutine instance that will run explicit_wait. @@ -188,7 +194,7 @@ namespace tut // but give it a chance to wake up mSync.yield(); // ensure the coroutine ran and woke up again with the intended result - ensure_equals(stringdata, "received"); + CHECK(stringdata == "received"); } void lleventcoro_data::waitForEventOn1() @@ -202,10 +208,8 @@ namespace tut END } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(lleventcoro_data, "coroutine: suspendUntilEventOn() wakes on a post to the named pump", "[coroutine]") { - set_test_name("waitForEventOn1"); DEBUG; LLCoros::instance().launch("test<2>", [this](){ waitForEventOn1(); }); mSync.bump(); @@ -214,7 +218,7 @@ namespace tut // give waitForEventOn1() a chance to run mSync.yield(); debug("back from send"); - ensure_equals(result.asString(), "received"); + CHECK(result.asString() == "received"); } void lleventcoro_data::coroPump() @@ -230,10 +234,8 @@ namespace tut END } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(lleventcoro_data, "coroutine: LLCoroEventPump::suspend() wakes on a post to its pump", "[coroutine]") { - set_test_name("coroPump"); DEBUG; LLCoros::instance().launch("test<3>", [this](){ coroPump(); }); mSync.bump(); @@ -242,7 +244,7 @@ namespace tut // give coroPump() a chance to run mSync.yield(); debug("back from send"); - ensure_equals(result.asString(), "received"); + CHECK(result.asString() == "received"); } void lleventcoro_data::postAndWait1() @@ -259,13 +261,11 @@ namespace tut END } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(lleventcoro_data, "coroutine: postAndSuspend() gets an immediate reply", "[coroutine]") { - set_test_name("postAndWait1"); DEBUG; LLCoros::instance().launch("test<4>", [this](){ postAndWait1(); }); - ensure_equals(result.asInteger(), 18); + CHECK(result.asInteger() == 18); } void lleventcoro_data::coroPumpPost() @@ -281,13 +281,11 @@ namespace tut END } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(lleventcoro_data, "coroutine: LLCoroEventPump::postAndSuspend() gets an immediate reply", "[coroutine]") { - set_test_name("coroPumpPost"); DEBUG; LLCoros::instance().launch("test<5>", [this](){ coroPumpPost(); }); - ensure_equals(result.asInteger(), 18); + CHECK(result.asInteger() == 18); } // A coroutine consumes what a PUMP holds for it, one value a wait. @@ -302,7 +300,8 @@ namespace tut LL_DEBUGS() << "test() posting first" << LL_ENDL; LLSD first{LLSDMap("desc", "first")("value", 0)}; bool consumed = pump.post(first); - ensure("should not have consumed first", ! consumed); + UNSCOPED_INFO("should not have consumed first"); + CHECK_FALSE(consumed); // now launch the coro LL_DEBUGS() << "test() launching listener coro" << LL_ENDL; running = true; @@ -330,7 +329,8 @@ namespace tut LL_DEBUGS() << "test() posting second" << LL_ENDL; LLSD second{llsd::map("desc", "second", "value", 1)}; consumed = pump.post(second); - ensure("should have consumed second", consumed); + UNSCOPED_INFO("should have consumed second"); + CHECK(consumed); // This is a key point: even though we've post()ed the value for which // the coroutine is waiting, it's actually still suspended until we // pause for some other reason. The coroutine will only pick up one @@ -339,7 +339,8 @@ namespace tut LL_DEBUGS() << "test() posting third" << LL_ENDL; LLSD third{llsd::map("desc", "third", "value", 2)}; consumed = pump.post(third); - ensure("should NOT yet have consumed third", ! consumed); + UNSCOPED_INFO("should NOT yet have consumed third"); + CHECK_FALSE(consumed); // now just wait for coro to finish -- which it eventually will, given // that all its suspend calls have short timeouts. while (running) @@ -348,22 +349,17 @@ namespace tut llcoro::suspendUntilTimeout(0.1f); } // okay, verify expected results - ensure_equals("should have received three values", data, - llsd::array(first, second, third)); + CHECK_THAT(data, ll_test::LLSDEquals(llsd::array(first, second, third))); LL_DEBUGS() << "test() done" << LL_ENDL; } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(lleventcoro_data, "coroutine: LLEventMailDrop gives a coroutine one value a wait", "[coroutine]") { - set_test_name("LLEventMailDrop"); check_coro_consumes(); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(lleventcoro_data, "coroutine: LLEventLogProxyFor gives a coroutine one value a wait", "[coroutine]") { - set_test_name("LLEventLogProxyFor"); check_coro_consumes< LLEventLogProxyFor >(); } } diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index ada009b99b..98c9974756 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -17,7 +17,8 @@ // std headers // external library headers // other Linden headers -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "lleventfilter.h" #include "llsd.h" #include "llsdutil.h" @@ -324,10 +325,10 @@ void freenb(NPARAMSb) *****************************************************************************/ namespace tut { - static void ensure_has(const std::string& outer, const std::string& inner) + static void require_contains(const std::string& outer, const std::string& inner) { - ensure(stringize("'", outer, "' does not contain '", inner, "'"), - outer.find(inner) != std::string::npos); + CAPTURE(outer, inner); + REQUIRE(outer.find(inner) != std::string::npos); } template @@ -335,7 +336,7 @@ namespace tut { std::string what = catch_what(std::forward(func)); - ensure_has(what, exc_frag); + require_contains(what, exc_frag); return what; } @@ -635,13 +636,11 @@ namespace tut DescMap forgotten(descs.begin(), descs.end()); for (LLEventDispatcher::NameDesc nd: work) { + CAPTURE(nd.first); DescMap::iterator found = forgotten.find(nd.first); - ensure(stringize("LLEventDispatcher records function '", nd.first, - "' we didn't enter"), - found != forgotten.end()); - ensure_equals(stringize("LLEventDispatcher desc '", nd.second, - "' doesn't match what we entered: '", found->second, "'"), - nd.second, found->second); + UNSCOPED_INFO("LLEventDispatcher records a function we didn't enter"); + REQUIRE(found != forgotten.end()); + REQUIRE(nd.second == found->second); // found in our map the name from LLEventDispatcher, good, erase // our map entry forgotten.erase(found); @@ -656,15 +655,16 @@ namespace tut out << delim << fme.first; delim = ", "; } - throw failure(out.str()); + FAIL(out.str()); } } Vars* varsfor(const std::string& name) { + CAPTURE(name); VarsMap::const_iterator found = funcvars.find(name); - ensure(stringize("No Vars* for ", name), found != funcvars.end()); - ensure(stringize("NULL Vars* for ", name), found->second); + REQUIRE(found != funcvars.end()); + REQUIRE(found->second); return found->second; } @@ -692,8 +692,9 @@ namespace tut LLSD getMetadata(const std::string& name) { + CAPTURE(name); LLSD meta(work.getMetadata(name)); - ensure(stringize("No metadata for ", name), meta.isDefined()); + REQUIRE(meta.isDefined()); return meta; } @@ -711,29 +712,12 @@ namespace tut return map; } - // If I call this ensure_equals(), it blocks visibility of all other - // ensure_equals() overloads. Normally I could say 'using - // baseclass::ensure_equals;' and fix that, but I don't know what the - // base class is! - void ensure_llsd(const std::string& msg, const LLSD& actual, const LLSD& expected, U32 bits) + void require_llsd_equals(const LLSD& actual, const LLSD& expected, U32 bits) { - std::ostringstream out; - if (! msg.empty()) - { - out << msg << ": "; - } - out << "expected " << expected << ", actual " << actual; - ensure(out.str(), llsd_equals(actual, expected, bits)); - } - - void ensure_llsd(const LLSD& actual, const LLSD& expected, U32 bits) - { - ensure_llsd("", actual, expected, bits); + CAPTURE(expected, actual); + REQUIRE(llsd_equals(actual, expected, bits)); } }; - typedef test_group lleventdispatcher_group; - typedef lleventdispatcher_group::object object; - lleventdispatcher_group lleventdispatchergrp("lleventdispatcher"); // Call cases: // - (try_call | call) (explicit name | event key) (real | bogus) name @@ -756,10 +740,8 @@ namespace tut // - (Free function | non-static method), map style, (no | arbitrary) params, // (empty | full | partial (array | map)) defaults - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: map-style registration with non-array params", "[lleventdispatcher]") { - set_test_name("map-style registration with non-array params"); // Pass "param names" as scalar or as map LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2))); for (LLSD ae: inArray(attempts)) @@ -767,97 +749,81 @@ namespace tut std::string threw = catch_what([this, &ae](){ work.add("freena_err", "freena", freena, ae); }); - ensure_has(threw, "must be an array"); + require_contains(threw, "must be an array"); } } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: map-style registration with badly-formed defaults", "[lleventdispatcher]") { - set_test_name("map-style registration with badly-formed defaults"); std::string threw = catch_what([this](){ work.add("freena_err", "freena", freena, llsd::array("a", "b"), 17); }); - ensure_has(threw, "must be a map or an array"); + require_contains(threw, "must be a map or an array"); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: map-style registration with too many array defaults", "[lleventdispatcher]") { - set_test_name("map-style registration with too many array defaults"); std::string threw = catch_what([this](){ work.add("freena_err", "freena", freena, llsd::array("a", "b"), llsd::array(17, 0.9, "gack")); }); - ensure_has(threw, "shorter than"); + require_contains(threw, "shorter than"); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: map-style registration with too many map defaults", "[lleventdispatcher]") { - set_test_name("map-style registration with too many map defaults"); std::string threw = catch_what([this](){ work.add("freena_err", "freena", freena, llsd::array("a", "b"), LLSDMap("b", 17)("foo", 3.14)("bar", "sinister")); }); - ensure_has(threw, "nonexistent params"); - ensure_has(threw, "foo"); - ensure_has(threw, "bar"); + require_contains(threw, "nonexistent params"); + require_contains(threw, "foo"); + require_contains(threw, "bar"); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: query all lists every registered name and desc", "[lleventdispatcher]") { - set_test_name("query all"); verify_descs(); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: query all after remove() omits only the removed name", "[lleventdispatcher]") { - set_test_name("query all with remove()"); - ensure("remove('bogus') returned true", ! work.remove("bogus")); - ensure("remove('real') returned false", work.remove("free1")); + CHECK_FALSE(work.remove("bogus")); + CHECK(work.remove("free1")); // Of course, remove that from 'descs' too... descs.erase("free1"); verify_descs(); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: getDispatchKey() returns the key it was built with", "[lleventdispatcher]") { - set_test_name("getDispatchKey()"); - ensure_equals(work.getDispatchKey(), "op"); + CHECK(work.getDispatchKey() == "op"); } - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: query Callables with/out required params", "[lleventdispatcher]") { - set_test_name("query Callables with/out required params"); LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1")); for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); - ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(metadata["desc"].asString(), descs[nm]); - ensure("should not have required structure", metadata["required"].isUndefined()); - ensure("should not have optional", metadata["optional"].isUndefined()); + REQUIRE_THAT(metadata["name"], ll_test::LLSDEquals(nm)); + REQUIRE(metadata["desc"].asString() == descs[nm]); + REQUIRE(metadata["required"].isUndefined()); + REQUIRE(metadata["optional"].isUndefined()); std::string name_req(nm.asString() + "_req"); metadata = getMetadata(name_req); - ensure_equals(metadata["name"].asString(), name_req); - ensure_equals(metadata["desc"].asString(), descs[name_req]); - ensure_equals("required mismatch", required, metadata["required"]); - ensure("should not have optional", metadata["optional"].isUndefined()); + REQUIRE(metadata["name"].asString() == name_req); + REQUIRE(metadata["desc"].asString() == descs[name_req]); + REQUIRE_THAT(required, ll_test::LLSDEquals(metadata["required"])); + REQUIRE(metadata["optional"].isUndefined()); } } - template<> template<> - void object::test<9>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: query array-style functions/methods", "[lleventdispatcher]") { - set_test_name("query array-style functions/methods"); // Associate each registered name with expected arity. LLSD expected(llsd::array (llsd::array @@ -875,39 +841,33 @@ namespace tut req[arity - 1] = LLSD(); for (LLSD nm: inArray(names)) { + CAPTURE(nm); LLSD metadata(getMetadata(nm)); - ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(metadata["desc"].asString(), descs[nm]); - ensure_equals(stringize("mismatched required for ", nm.asString()), - metadata["required"], req); - ensure("should not have optional", metadata["optional"].isUndefined()); + REQUIRE_THAT(metadata["name"], ll_test::LLSDEquals(nm)); + REQUIRE(metadata["desc"].asString() == descs[nm]); + REQUIRE_THAT(metadata["required"], ll_test::LLSDEquals(req)); + REQUIRE(metadata["optional"].isUndefined()); } } } - template<> template<> - void object::test<10>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: query map-style no-params functions/methods", "[lleventdispatcher]") { - set_test_name("query map-style no-params functions/methods"); // - (Free function | non-static method), map style, no params (ergo // no defaults) LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map")); for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); - ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(metadata["desc"].asString(), descs[nm]); - ensure("should not have required", - (metadata["required"].isUndefined() || metadata["required"].size() == 0)); - ensure("should not have optional", metadata["optional"].isUndefined()); + REQUIRE_THAT(metadata["name"], ll_test::LLSDEquals(nm)); + REQUIRE(metadata["desc"].asString() == descs[nm]); + REQUIRE((metadata["required"].isUndefined() || metadata["required"].size() == 0)); + REQUIRE(metadata["optional"].isUndefined()); } } - template<> template<> - void object::test<11>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: full array defaults and full map defaults give the same metadata", "[lleventdispatcher]") { - set_test_name("query map-style arbitrary-params functions/methods: " - "full array defaults vs. full map defaults"); // With functions registered with no defaults ("_allreq" suffixes), // there is of course no difference between array defaults and map // defaults. (We don't even bother registering with LLSD::emptyArray() @@ -926,26 +886,23 @@ namespace tut { LLSD adft(eq[0]); LLSD mdft(eq[1]); + CAPTURE(adft, mdft); // We can't just compare the results of the two getMetadata() // calls, because they contain ["name"], which are different. So // capture them, verify that each ["name"] is as expected, then // remove for comparing the rest. LLSD ameta(getMetadata(adft)); LLSD mmeta(getMetadata(mdft)); - ensure_equals("adft name", adft, ameta["name"]); - ensure_equals("mdft name", mdft, mmeta["name"]); + REQUIRE_THAT(adft, ll_test::LLSDEquals(ameta["name"])); + REQUIRE_THAT(mdft, ll_test::LLSDEquals(mmeta["name"])); ameta.erase("name"); mmeta.erase("name"); - ensure_equals(stringize("metadata for ", adft.asString(), - " vs. ", mdft.asString()), - ameta, mmeta); + REQUIRE_THAT(ameta, ll_test::LLSDEquals(mmeta)); } } - template<> template<> - void object::test<12>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: query map-style arbitrary-params functions/methods", "[lleventdispatcher]") { - set_test_name("query map-style arbitrary-params functions/methods"); // - (Free function | non-static method), map style, arbitrary params, // (empty | full | partial (array | map)) defaults @@ -1043,47 +1000,40 @@ namespace tut // Loop through 'names' for (LLSD nm: inArray(names)) { + CAPTURE(nm); LLSD metadata(getMetadata(nm)); - ensure_equals("name mismatch", metadata["name"], nm); - ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]); - ensure_equals(stringize(nm, " required mismatch"), - metadata["required"], required); - ensure_equals(stringize(nm, " optional mismatch"), - metadata["optional"], optional); + REQUIRE_THAT(metadata["name"], ll_test::LLSDEquals(nm)); + REQUIRE(metadata["desc"].asString() == descs[nm]); + REQUIRE_THAT(metadata["required"], ll_test::LLSDEquals(required)); + REQUIRE_THAT(metadata["optional"], ll_test::LLSDEquals(optional)); } } } - template<> template<> - void object::test<13>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: try_call() returns whether it found the name", "[lleventdispatcher]") { - set_test_name("try_call()"); - ensure("try_call(bogus name, LLSD()) returned true", ! work.try_call("freek", LLSD())); - ensure("try_call(bogus name) returned true", ! work.try_call(LLSDMap("op", "freek"))); - ensure("try_call(real name, LLSD()) returned false", work.try_call("free0_array", LLSD())); - ensure("try_call(real name) returned false", work.try_call(LLSDMap("op", "free0_map"))); + CHECK_FALSE(work.try_call("freek", LLSD())); + CHECK_FALSE(work.try_call(LLSDMap("op", "freek"))); + CHECK(work.try_call("free0_array", LLSD())); + CHECK(work.try_call(LLSDMap("op", "free0_map"))); } - template<> template<> - void object::test<14>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: call with bad name", "[lleventdispatcher]") { - set_test_name("call with bad name"); call_exc("freek", LLSD(), "not found"); std::string threw = call_exc("", LLSDMap("op", "freek"), "bad"); - ensure_has(threw, "op"); - ensure_has(threw, "freek"); + require_contains(threw, "op"); + require_contains(threw, "freek"); } - template<> template<> - void object::test<15>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: call with event key", "[lleventdispatcher]") { - set_test_name("call with event key"); // We don't need a separate test for operator()(string, LLSD) with // valid name, because all the rest of the tests exercise that case. // The one we don't exercise elsewhere is operator()(LLSD) with valid // name, so here it is. work(LLSDMap("op", "free0_map")); - ensure_equals(g.i, 17); + CHECK(g.i == 17); } // Cannot be defined inside function body... remind me again why we use C++... :-P @@ -1096,10 +1046,8 @@ namespace tut }; } // anonymous namespace - template<> template<> - void object::test<16>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: call Callables", "[lleventdispatcher]") { - set_test_name("call Callables"); CallablesTriple tests[] = { { "free1", "free1_req", g.llsd }, @@ -1119,22 +1067,19 @@ namespace tut // Should be able to pass 'answer' to Callables registered // without 'required'. work(tr.name, answer); - ensure_equals("answer mismatch", tr.llsd, answer); + REQUIRE_THAT(tr.llsd, ll_test::LLSDEquals(answer)); // Should NOT be able to pass 'answer' to Callables registered // with 'required'. call_logerr(tr.name_req, answer, "bad request"); // But SHOULD be able to pass 'matching' to Callables registered // with 'required'. work(tr.name_req, matching); - ensure_equals("matching mismatch", tr.llsd, matching); + REQUIRE_THAT(tr.llsd, ll_test::LLSDEquals(matching)); } } - template<> template<> - void object::test<17>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: passing wrong args to (map | array)-style registrations", "[lleventdispatcher]") { - set_test_name("passing wrong args to (map | array)-style registrations"); - // Pass scalar/map to array-style functions, scalar/array to map-style // functions. It seems pointless to repeat this with every variation: // (free function | non-static method), (no | arbitrary) args. We @@ -1155,10 +1100,8 @@ namespace tut // call_exc("free0_map", llsd::array("a", "b"), map_exc); } - template<> template<> - void object::test<18>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: call no-args functions", "[lleventdispatcher]") { - set_test_name("call no-args functions"); LLSD names(llsd::array ("free0_array", "free0_map", "smethod0_array", "smethod0_map", @@ -1172,10 +1115,10 @@ namespace tut // calling these things several different times in the same // test method, manually reset the Vars between each. *vars = Vars(); - ensure_equals(vars->i, 0); + REQUIRE(vars->i == 0); // call function with empty array (or LLSD(), should be equivalent) work(name, LLSD()); - ensure_equals(vars->i, 17); + REQUIRE(vars->i == 17); } } @@ -1185,10 +1128,8 @@ namespace tut LLSDMap("a", "smethodna_array")("b", "smethodnb_array"), LLSDMap("a", "methodna_array") ("b", "methodnb_array"))); - template<> template<> - void object::test<19>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: call array-style functions with wrong-length arrays", "[lleventdispatcher]") { - set_test_name("call array-style functions with wrong-length arrays"); // Could have different wrong-length arrays for *na and for *nb, but // since they both take 5 params... LLSD tooshort(llsd::array("this", "array", "too", "short")); @@ -1207,10 +1148,8 @@ namespace tut } } - template<> template<> - void object::test<20>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: call array-style functions with right-size arrays", "[lleventdispatcher]") { - set_test_name("call array-style functions with right-size arrays"); std::vector binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) { @@ -1235,34 +1174,33 @@ namespace tut { for (LLSD::String a: ab) { + CAPTURE(funcsab[a], a); // Reset the Vars instance before each call Vars* vars(varsfor(funcsab[a])); *vars = Vars(); work(funcsab[a], args[a]); - ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"), - vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits + require_llsd_equals(vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits } } } - template<> template<> - void object::test<21>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: passing LLSD() to a const char* param sends NULL", "[lleventdispatcher]") { - set_test_name("verify that passing LLSD() to const char* sends NULL"); - - ensure_equals("Vars::cp init", v.cp, ""); + UNSCOPED_INFO("Vars::cp init"); + CHECK(v.cp == ""); work("methodna_map_mdft", LLSDMap("cp", LLSD())); - ensure_equals("passing LLSD()", v.cp, "NULL"); + UNSCOPED_INFO("passing LLSD()"); + CHECK(v.cp == "NULL"); work("methodna_map_mdft", LLSDMap("cp", "")); - ensure_equals("passing \"\"", v.cp, "''"); + UNSCOPED_INFO("passing \"\""); + CHECK(v.cp == "''"); work("methodna_map_mdft", LLSDMap("cp", "non-NULL")); - ensure_equals("passing \"non-NULL\"", v.cp, "'non-NULL'"); + UNSCOPED_INFO("passing \"non-NULL\""); + CHECK(v.cp == "'non-NULL'"); } - template<> template<> - void object::test<22>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: call map-style functions with (full | oversized) (arrays | maps)", "[lleventdispatcher]") { - set_test_name("call map-style functions with (full | oversized) (arrays | maps)"); const char binary[] = "\x99\x88\x77\x66\x55"; LLSD array_full(LLSDMap ("a", llsd::array(false, 255, 98.6, 1024.5, "pointer")) @@ -1276,11 +1214,11 @@ namespace tut "array_overfull: ", array_overfull); // We rather hope that LLDate::now() will generate a timestamp // distinct from the one it generated in the constructor, moments ago. - ensure_not_equals("Timestamps too close", - array_full["b"][2].asDate(), dft_array_full["b"][2].asDate()); + UNSCOPED_INFO("Timestamps too close"); + CHECK(array_full["b"][2].asDate() != dft_array_full["b"][2].asDate()); // We /insist/ that LLUUID::generateNewID() do so. - ensure_not_equals("UUID collision", - array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID()); + UNSCOPED_INFO("UUID collision"); + CHECK(array_full["b"][1].asUUID() != dft_array_full["b"][1].asUUID()); LLSD map_full, map_overfull; for (LLSD::String a: ab) { @@ -1329,12 +1267,12 @@ namespace tut { for (LLSD::String name: inArray(names[a])) { + CAPTURE(name, a); // Reset the Vars instance Vars* vars(varsfor(name)); *vars = Vars(); work(name, args[a]); - ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"), - vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits + require_llsd_equals(vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits // intercept LL_WARNS for the two overfull cases? } } @@ -1379,65 +1317,51 @@ namespace tut }; } // anonymous namespace - template<> template<> - void object::test<23>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: string result", "[lleventdispatcher]") { - set_test_name("string result"); DispatchResult service; LLSD result{ service("strfunc", "a string") }; - ensure_equals("strfunc() mismatch", result.asString(), "got a string"); + CHECK(result.asString() == "got a string"); } - template<> template<> - void object::test<24>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: void result", "[lleventdispatcher]") { - set_test_name("void result"); DispatchResult service; LLSD result{ service("voidfunc", LLSD()) }; - ensure("voidfunc() returned defined", result.isUndefined()); + CHECK(result.isUndefined()); } - template<> template<> - void object::test<25>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: Integer result", "[lleventdispatcher]") { - set_test_name("Integer result"); DispatchResult service; LLSD result{ service("intfunc", -17) }; - ensure_equals("intfunc() mismatch", result.asInteger(), 17); + CHECK(result.asInteger() == 17); } - template<> template<> - void object::test<26>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: LLSD echo", "[lleventdispatcher]") { - set_test_name("LLSD echo"); DispatchResult service; LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) }; - ensure_equals("llsdfunc() mismatch", result, - llsd::map("op", "llsdfunc", "reqid", 17, "with", "string")); + CHECK_THAT(result, + ll_test::LLSDEquals(llsd::map("op", "llsdfunc", "reqid", 17, "with", "string"))); } - template<> template<> - void object::test<27>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: map LLSD result", "[lleventdispatcher]") { - set_test_name("map LLSD result"); DispatchResult service; LLSD result{ service("mapfunc", llsd::array(-12, "value")) }; - ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value")); + CHECK_THAT(result, ll_test::LLSDEquals(llsd::map("i", 12, "str", "got value"))); } - template<> template<> - void object::test<28>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: array LLSD result", "[lleventdispatcher]") { - set_test_name("array LLSD result"); DispatchResult service; LLSD result{ service("arrayfunc", llsd::array(-8, "word")) }; - ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word")); + CHECK_THAT(result, ll_test::LLSDEquals(llsd::array(8, "got word"))); } - template<> template<> - void object::test<29>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: a listener error without a reply pump throws", "[lleventdispatcher]") { - set_test_name("listener error, no reply"); DispatchResult service; tut::call_exc( [&service]() @@ -1445,23 +1369,19 @@ namespace tut "nosuchfunc"); } - template<> template<> - void object::test<30>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: listener error with reply", "[lleventdispatcher]") { - set_test_name("listener error with reply"); DispatchResult service; LLCaptureListener result; service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; - ensure("no reply", reply.isDefined()); - ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); - ensure_has(reply["error"].asString(), "nosuchfunc"); + REQUIRE(reply.isDefined()); + CHECK(reply["reqid"].asInteger() == 17); + require_contains(reply["error"].asString(), "nosuchfunc"); } - template<> template<> - void object::test<31>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: listener call to void function", "[lleventdispatcher]") { - set_test_name("listener call to void function"); DispatchResult service; LLCaptureListener result; result.set("non-empty"); @@ -1471,14 +1391,13 @@ namespace tut "op", func, "reqid", 17, "reply", result.getName())); - ensure_equals("reply from " + func, result.get().asString(), "non-empty"); + CAPTURE(func); + REQUIRE(result.get().asString() == "non-empty"); } } - template<> template<> - void object::test<32>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: listener call to string function", "[lleventdispatcher]") { - set_test_name("listener call to string function"); DispatchResult service; LLCaptureListener result; service.post(llsd::map( @@ -1487,14 +1406,12 @@ namespace tut "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; - ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); - ensure_equals("bad reply from strfunc", reply["data"].asString(), "got a string"); + CHECK(reply["reqid"].asInteger() == 17); + CHECK(reply["data"].asString() == "got a string"); } - template<> template<> - void object::test<33>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: listener call to map function", "[lleventdispatcher]") { - set_test_name("listener call to map function"); DispatchResult service; LLCaptureListener result; service.post(llsd::map( @@ -1503,15 +1420,13 @@ namespace tut "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; - ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); - ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7); - ensure_equals("bad str from mapfunc", reply["str"], "got value"); + CHECK(reply["reqid"].asInteger() == 17); + CHECK(reply["i"].asInteger() == 7); + CHECK(reply["str"] == "got value"); } - template<> template<> - void object::test<34>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: batched map success", "[lleventdispatcher]") { - set_test_name("batched map success"); DispatchResult service; LLCaptureListener result; service.post(llsd::map( @@ -1523,22 +1438,18 @@ namespace tut "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; - ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + CHECK(reply["reqid"].asInteger() == 17); reply.erase("reqid"); - ensure_equals( - "bad map batch", - reply, - llsd::map( + CHECK_THAT(reply, + ll_test::LLSDEquals(llsd::map( "strfunc", "got some string", "intfunc", -2, "voidfunc", LLSD(), - "arrayfunc", llsd::array(5, "got other string"))); + "arrayfunc", llsd::array(5, "got other string")))); } - template<> template<> - void object::test<35>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: batched map error", "[lleventdispatcher]") { - set_test_name("batched map error"); DispatchResult service; LLCaptureListener result; service.post(llsd::map( @@ -1552,26 +1463,22 @@ namespace tut "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; - ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + CHECK(reply["reqid"].asInteger() == 17); reply.erase("reqid"); auto error{ reply["error"].asString() }; reply.erase("error"); - ensure_has(error, "badfunc"); - ensure_has(error, "missing"); - ensure_equals( - "bad partial batch", - reply, - llsd::map( + require_contains(error, "badfunc"); + require_contains(error, "missing"); + CHECK_THAT(reply, + ll_test::LLSDEquals(llsd::map( "strfunc", "got some string", "intfunc", -2, "voidfunc", LLSD(), - "arrayfunc", llsd::array(5, "got other string"))); + "arrayfunc", llsd::array(5, "got other string")))); } - template<> template<> - void object::test<36>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: batched map exception", "[lleventdispatcher]") { - set_test_name("batched map exception"); DispatchResult service; auto error = tut::call_exc( [&service]() @@ -1588,13 +1495,11 @@ namespace tut // no "reply" }, "badfunc"); - ensure_has(error, "missing"); + require_contains(error, "missing"); } - template<> template<> - void object::test<37>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: batched array success", "[lleventdispatcher]") { - set_test_name("batched array success"); DispatchResult service; LLCaptureListener result; service.post(llsd::map( @@ -1612,23 +1517,19 @@ namespace tut "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; - ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + CHECK(reply["reqid"].asInteger() == 17); reply.erase("reqid"); - ensure_equals( - "bad array batch", - reply, - llsd::map( + CHECK_THAT(reply, + ll_test::LLSDEquals(llsd::map( "data", llsd::array( "got some string", -2, llsd::array(5, "got other string"), - LLSD()))); + LLSD())))); } - template<> template<> - void object::test<38>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: batched array error", "[lleventdispatcher]") { - set_test_name("batched array error"); DispatchResult service; LLCaptureListener result; service.post(llsd::map( @@ -1646,20 +1547,18 @@ namespace tut "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; - ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + CHECK(reply["reqid"].asInteger() == 17); reply.erase("reqid"); auto error{ reply["error"] }; reply.erase("error"); - ensure_has(error, "[1]"); - ensure_has(error, "unsupported"); - ensure_equals("bad array batch", reply, - llsd::map("data", llsd::array("got some string"))); + require_contains(error, "[1]"); + require_contains(error, "unsupported"); + CHECK_THAT(reply, + ll_test::LLSDEquals(llsd::map("data", llsd::array("got some string")))); } - template<> template<> - void object::test<39>() + TEST_CASE_METHOD(lleventdispatcher_data, "lleventdispatcher: batched array exception", "[lleventdispatcher]") { - set_test_name("batched array exception"); DispatchResult service; auto error = tut::call_exc( [&service]() @@ -1680,6 +1579,6 @@ namespace tut // no "reply" }, "[1]"); - ensure_has(error, "unsupported"); + require_contains(error, "unsupported"); } } // namespace tut diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index e8dd37ee60..3bcc4d44fb 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -34,8 +34,9 @@ // std headers // external library headers // other Linden headers -#include "../test/lltut.h" -#include "stringize.h" +#include +#include +#include "altestmatchers.h" #include "llsdutil.h" #include "listener.h" #include "tests/wrapllerrs.h" @@ -160,7 +161,7 @@ class TestEventThrottle: public LLEventThrottleBase /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct filter_data { @@ -177,20 +178,15 @@ namespace tut Listener listener0; Listener listener1; - void check_listener(const std::string& desc, const Listener& listener, const LLSD& got) + void require_last_event(const std::string& desc, const Listener& listener, const LLSD& got) { - ensure_equals(STRINGIZE(listener << ' ' << desc), - listener.getLastEvent(), got); + INFO(listener << ' ' << desc); + REQUIRE_THAT(listener.getLastEvent(), ll_test::LLSDEquals(got)); } }; - typedef test_group filter_group; - typedef filter_group::object filter_object; - filter_group filtergrp("lleventfilter"); - template<> template<> - void filter_object::test<1>() + TEST_CASE_METHOD(filter_data, "lleventfilter: LLEventMatching passes only events that match its pattern", "[lleventfilter]") { - set_test_name("LLEventMatching"); LLEventPump& driver(pumps.obtain("driver")); listener0.reset(0); // Listener isn't derived from LLEventTrackable specifically to test @@ -210,21 +206,19 @@ namespace tut LLTempBoundListener temp2( listener1.listenTo(filter)); driver.post(1); - check_listener("direct", listener0, LLSD(1)); - check_listener("filtered", listener1, LLSD(0)); + require_last_event("direct", listener0, LLSD(1)); + require_last_event("filtered", listener1, LLSD(0)); // Okay, construct an LLSD map matching the pattern LLSD data; data["foo"] = "bar"; data["random"] = 17; driver.post(data); - check_listener("direct", listener0, data); - check_listener("filtered", listener1, data); + require_last_event("direct", listener0, data); + require_last_event("filtered", listener1, data); } - template<> template<> - void filter_object::test<2>() + TEST_CASE_METHOD(filter_data, "lleventfilter: LLEventTimeout::actionAfter() acts only when it times out", "[lleventfilter]") { - set_test_name("LLEventTimeout::actionAfter()"); LLEventPump& driver(pumps.obtain("driver")); TestEventTimeout filter(driver); listener0.reset(0); @@ -240,15 +234,15 @@ namespace tut // when we pump mainloop. Do that right now to take the logic path // before either the anticipated event arrives or the timer expires. mainloop.post(17); - check_listener("no timeout 1", listener1, LLSD(0)); + require_last_event("no timeout 1", listener1, LLSD(0)); // Expected event arrives... driver.post(1); - check_listener("event passed thru", listener0, LLSD(1)); + require_last_event("event passed thru", listener0, LLSD(1)); // Should have canceled the timer. Verify that by asserting that the // time has expired, then pumping mainloop again. filter.forceTimeout(); mainloop.post(17); - check_listener("no timeout 2", listener1, LLSD(0)); + require_last_event("no timeout 2", listener1, LLSD(0)); // Verify chained actionAfter() calls, that is, that a second // actionAfter() resets the timer established by the first // actionAfter(). @@ -267,35 +261,33 @@ namespace tut boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); // Look ma, no timeout! mainloop.post(17); - check_listener("no timeout 3", listener1, LLSD(0)); + require_last_event("no timeout 3", listener1, LLSD(0)); // Now let the updated actionAfter() timer expire. filter.forceTimeout(); // Notice the timeout. mainloop.post(17); - check_listener("timeout", listener1, LLSD("timeout")); + require_last_event("timeout", listener1, LLSD("timeout")); // Timing out cancels the timer. Verify that. listener1.reset(0); filter.forceTimeout(); mainloop.post(17); - check_listener("no timeout 4", listener1, LLSD(0)); + require_last_event("no timeout 4", listener1, LLSD(0)); // Reset the timer and then cancel() it. filter.actionAfter(20, boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); // neither expired nor satisified mainloop.post(17); - check_listener("no timeout 5", listener1, LLSD(0)); + require_last_event("no timeout 5", listener1, LLSD(0)); // cancel filter.cancel(); // timeout! filter.forceTimeout(); mainloop.post(17); - check_listener("no timeout 6", listener1, LLSD(0)); + require_last_event("no timeout 6", listener1, LLSD(0)); } - template<> template<> - void filter_object::test<3>() + TEST_CASE_METHOD(filter_data, "lleventfilter: LLEventTimeout::eventAfter() posts only when it times out", "[lleventfilter]") { - set_test_name("LLEventTimeout::eventAfter()"); LLEventPump& driver(pumps.obtain("driver")); TestEventTimeout filter(driver); listener0.reset(0); @@ -306,33 +298,31 @@ namespace tut // when we pump mainloop. Do that right now to take the logic path // before either the anticipated event arrives or the timer expires. mainloop.post(17); - check_listener("no timeout 1", listener0, LLSD(0)); + require_last_event("no timeout 1", listener0, LLSD(0)); // Expected event arrives... driver.post(1); - check_listener("event passed thru", listener0, LLSD(1)); + require_last_event("event passed thru", listener0, LLSD(1)); // Should have canceled the timer. Verify that by asserting that the // time has expired, then pumping mainloop again. filter.forceTimeout(); mainloop.post(17); - check_listener("no timeout 2", listener0, LLSD(1)); + require_last_event("no timeout 2", listener0, LLSD(1)); // Set timer again. filter.eventAfter(20, LLSD("timeout")); // Now let the timer expire. filter.forceTimeout(); // Notice the timeout. mainloop.post(17); - check_listener("timeout", listener0, LLSD("timeout")); + require_last_event("timeout", listener0, LLSD("timeout")); // Timing out cancels the timer. Verify that. listener0.reset(0); filter.forceTimeout(); mainloop.post(17); - check_listener("no timeout 3", listener0, LLSD(0)); + require_last_event("no timeout 3", listener0, LLSD(0)); } - template<> template<> - void filter_object::test<4>() + TEST_CASE_METHOD(filter_data, "lleventfilter: LLEventTimeout::errorAfter() raises an error only on timeout", "[lleventfilter]") { - set_test_name("LLEventTimeout::errorAfter()"); WrapLLErrs capture; LLEventPump& driver(pumps.obtain("driver")); TestEventTimeout filter(driver); @@ -344,15 +334,15 @@ namespace tut // when we pump mainloop. Do that right now to take the logic path // before either the anticipated event arrives or the timer expires. mainloop.post(17); - check_listener("no timeout 1", listener0, LLSD(0)); + require_last_event("no timeout 1", listener0, LLSD(0)); // Expected event arrives... driver.post(1); - check_listener("event passed thru", listener0, LLSD(1)); + require_last_event("event passed thru", listener0, LLSD(1)); // Should have canceled the timer. Verify that by asserting that the // time has expired, then pumping mainloop again. filter.forceTimeout(); mainloop.post(17); - check_listener("no timeout 2", listener0, LLSD(1)); + require_last_event("no timeout 2", listener0, LLSD(1)); // Set timer again. filter.errorAfter(20, "timeout"); // Now let the timer expire. @@ -361,18 +351,16 @@ namespace tut std::string threw = capture.catch_llerrs([this](){ mainloop.post(17); }); - ensure_contains("errorAfter() timeout exception", threw, "timeout"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("timeout")); // Timing out cancels the timer. Verify that. listener0.reset(0); filter.forceTimeout(); mainloop.post(17); - check_listener("no timeout 3", listener0, LLSD(0)); + require_last_event("no timeout 3", listener0, LLSD(0)); } - template<> template<> - void filter_object::test<5>() + TEST_CASE_METHOD(filter_data, "lleventfilter: LLEventThrottle delivers at most one event an interval", "[lleventfilter]") { - set_test_name("LLEventThrottle"); TestEventThrottle throttle(3); Concat cat; throttle.listen("concat", boost::ref(cat)); @@ -381,38 +369,38 @@ namespace tut // 1: post(): event immediately passed to listeners, next no sooner than 4 throttle.advance(1); throttle.post("1"); - ensure_equals("1", cat.result, "1"); // delivered immediately + CHECK(cat.result == "1"); // delivered immediately // 2: post(): deferred: waiting for 3 seconds to elapse throttle.advance(1); throttle.post("2"); - ensure_equals("2", cat.result, "1"); // "2" not yet delivered + CHECK(cat.result == "1"); // "2" not yet delivered // 3: post(): deferred throttle.advance(1); throttle.post("3"); - ensure_equals("3", cat.result, "1"); // "3" not yet delivered + CHECK(cat.result == "1"); // "3" not yet delivered // 4: no post() call, but event delivered to listeners; next no sooner than 7 throttle.advance(1); - ensure_equals("4", cat.result, "13"); // "3" delivered + CHECK(cat.result == "13"); // "3" delivered // 6: post(): deferred throttle.advance(2); throttle.post("6"); - ensure_equals("6", cat.result, "13"); // "6" not yet delivered + CHECK(cat.result == "13"); // "6" not yet delivered // 7: no post() call, but event delivered; next no sooner than 10 throttle.advance(1); - ensure_equals("7", cat.result, "136"); // "6" delivered + CHECK(cat.result == "136"); // "6" delivered // 12: post(): immediately passed to listeners, next no sooner than 15 throttle.advance(5); throttle.post(";12"); - ensure_equals("12", cat.result, "136;12"); // "12" delivered + CHECK(cat.result == "136;12"); // "12" delivered // 17: post(): immediately passed to listeners, next no sooner than 20 throttle.advance(5); throttle.post(";17"); - ensure_equals("17", cat.result, "136;12;17"); // "17" delivered + CHECK(cat.result == "136;12;17"); // "17" delivered } // A PUMP keeps what no listener consumed, and replays it on the next listen(). template - static void check_mail_drop_replay() + static void require_mail_drop_replay() { PUMP pump(typeid(PUMP).name()); LLSD data{LLSD::emptyArray()}; @@ -433,7 +421,7 @@ namespace tut pump.post("first"); } // first post() should certainly be received by listener - ensure_equals("first", data, llsd::array("first")); + REQUIRE_THAT(data, ll_test::LLSDEquals(llsd::array("first"))); // the question is, since consumed was true, did it queue the value? data = LLSD::emptyArray(); { @@ -441,7 +429,7 @@ namespace tut // listen() call LLTempBoundListener conn = pump.listen("lambda", lambda); } - ensure_equals("empty1", data, LLSD::emptyArray()); + REQUIRE_THAT(data, ll_test::LLSDEquals(LLSD::emptyArray())); data = LLSD::emptyArray(); // now let's NOT consume the posted data consumed = false; @@ -451,7 +439,7 @@ namespace tut pump.post("third"); } // the two events still arrive - ensure_equals("second,third1", data, llsd::array("second", "third")); + REQUIRE_THAT(data, ll_test::LLSDEquals(llsd::array("second", "third"))); data = LLSD::emptyArray(); { // when we reconnect, these should be delivered again @@ -460,29 +448,25 @@ namespace tut LLTempBoundListener conn = pump.listen("lambda", lambda); } // unconsumed events were delivered again - ensure_equals("second,third2", data, llsd::array("second", "third")); + REQUIRE_THAT(data, ll_test::LLSDEquals(llsd::array("second", "third"))); data = LLSD::emptyArray(); { // when we reconnect this time, no more unconsumed events LLTempBoundListener conn = pump.listen("lambda", lambda); } - ensure_equals("empty2", data, LLSD::emptyArray()); + REQUIRE_THAT(data, ll_test::LLSDEquals(LLSD::emptyArray())); } - template<> template<> - void filter_object::test<6>() + TEST_CASE_METHOD(filter_data, "lleventfilter: LLEventMailDrop replays unconsumed events on the next listen", "[lleventfilter]") { - set_test_name("LLEventMailDrop"); - check_mail_drop_replay(); + require_mail_drop_replay(); } - template<> template<> - void filter_object::test<7>() + TEST_CASE_METHOD(filter_data, "lleventfilter: LLEventLogProxyFor replays unconsumed events", "[lleventfilter]") { - set_test_name("LLEventLogProxyFor"); - check_mail_drop_replay< LLEventLogProxyFor >(); + require_mail_drop_replay< LLEventLogProxyFor >(); } -} // namespace tut +} // anonymous namespace // (llsdutil symbols formerly pulled in via #include "llsdutil.cpp" now come from // the linked llcommon library -- including the .cpp here double-defines them and diff --git a/indra/llcommon/tests/llevents_test.cpp b/indra/llcommon/tests/llevents_test.cpp index 4232efd9ca..f06de5ead1 100644 --- a/indra/llcommon/tests/llevents_test.cpp +++ b/indra/llcommon/tests/llevents_test.cpp @@ -46,9 +46,9 @@ #include // other Linden headers #include "tests/listener.h" // must PRECEDE lltut.h -#include "lltut.h" +#include +#include #include "catch_and_store_what_in.h" -#include "stringize.h" template static T make(const T& value) @@ -59,7 +59,7 @@ static T make(const T& value) /***************************************************************************** * tut test group *****************************************************************************/ -namespace tut +namespace { struct events_data { @@ -90,30 +90,25 @@ struct events_data Listener listener0; Listener listener1; - void check_listener(const std::string& desc, const Listener& listener, LLSD::Integer got) + void require_last_event(const std::string& desc, const Listener& listener, LLSD::Integer got) { - ensure_equals(STRINGIZE(listener << ' ' << desc), - listener.getLastEvent().asInteger(), got); + INFO(listener << ' ' << desc); + REQUIRE(listener.getLastEvent().asInteger() == got); } }; -typedef test_group events_group; -typedef events_group::object events_object; -tut::events_group evgr("events"); -template<> template<> -void events_object::test<1>() +TEST_CASE_METHOD(events_data, "events: basic operations", "[events]") { - set_test_name("basic operations"); // Having to modify this to track the statically- // constructed pumps in other TUT modules in this giant monolithic test // executable isn't such a hot idea. // ensure_equals("initial pump", pumps.mPumpMap.size(), 1); size_t initial_pumps(pumps.mPumpMap.size()); LLEventPump& per_frame(pumps.obtain("per-frame")); - ensure_equals("first explicit pump", pumps.mPumpMap.size(), initial_pumps + 1); + CHECK(pumps.mPumpMap.size() == initial_pumps + 1); // Verify that per_frame was instantiated as an LLEventStream. - ensure("LLEventStream leaf class", dynamic_cast (&per_frame)); - ensure("enabled", per_frame.enabled()); + CHECK(dynamic_cast (&per_frame)); + CHECK(per_frame.enabled()); // Trivial test, but posting an event to an EventPump with no // listeners should not blow up. The test is relevant because defining // a boost::signal with a non-void return signature, using the default @@ -122,27 +117,27 @@ void events_object::test<1>() // last listener, which is meaningless if there were no listeners. per_frame.post(0); LLBoundListener connection = listener0.listenTo(per_frame); - ensure("connected", connection.connected()); - ensure("not blocked", !connection.blocked()); + CHECK(connection.connected()); + CHECK_FALSE(connection.blocked()); per_frame.post(1); - check_listener("received", listener0, 1); + require_last_event("received", listener0, 1); { // block the connection LLEventPump::Blocker block(connection); - ensure("blocked", connection.blocked()); + CHECK(connection.blocked()); per_frame.post(2); - check_listener("not updated", listener0, 1); + require_last_event("not updated", listener0, 1); } // unblock - ensure("unblocked", !connection.blocked()); + CHECK_FALSE(connection.blocked()); per_frame.post(3); - check_listener("unblocked", listener0, 3); + require_last_event("unblocked", listener0, 3); LLBoundListener sameConnection = per_frame.getListener(listener0.getName()); - ensure("still connected", sameConnection.connected()); - ensure("still not blocked", !sameConnection.blocked()); + CHECK(sameConnection.connected()); + CHECK_FALSE(sameConnection.blocked()); { // block it again LLEventPump::Blocker block(sameConnection); - ensure("re-blocked", sameConnection.blocked()); + CHECK(sameConnection.blocked()); per_frame.post(4); - check_listener("re-blocked", listener0, 3); + require_last_event("re-blocked", listener0, 3); } // unblock std::string threw = catch_what( [&per_frame, this](){ @@ -153,7 +148,7 @@ void events_object::test<1>() per_frame.listen(listener0.getName(), // note bug, dup name boost::bind(&Listener::call, boost::ref(listener1), _1)); }); - ensure_equals(threw, + CHECK(threw == std::string("DupListenerName: " "Attempt to register duplicate listener name '") + listener0.getName() + "' on " + typeid(per_frame).name() + @@ -161,31 +156,29 @@ void events_object::test<1>() // do it right this time listener1.listenTo(per_frame); per_frame.post(5); - check_listener("got", listener0, 5); - check_listener("got", listener1, 5); + require_last_event("got", listener0, 5); + require_last_event("got", listener1, 5); per_frame.enable(false); per_frame.post(6); - check_listener("didn't get", listener0, 5); - check_listener("didn't get", listener1, 5); + require_last_event("didn't get", listener0, 5); + require_last_event("didn't get", listener1, 5); per_frame.enable(); per_frame.post(7); - check_listener("got", listener0, 7); - check_listener("got", listener1, 7); + require_last_event("got", listener0, 7); + require_last_event("got", listener1, 7); per_frame.stopListening(listener0.getName()); - ensure("disconnected 0", ! connection.connected()); - ensure("disconnected 1", ! sameConnection.connected()); + CHECK_FALSE(connection.connected()); + CHECK_FALSE(sameConnection.connected()); per_frame.post(8); - check_listener("disconnected", listener0, 7); - check_listener("still connected", listener1, 8); + require_last_event("disconnected", listener0, 7); + require_last_event("still connected", listener1, 8); per_frame.stopListening(listener1.getName()); per_frame.post(9); - check_listener("disconnected", listener1, 8); + require_last_event("disconnected", listener1, 8); } -template<> template<> -void events_object::test<2>() +TEST_CASE_METHOD(events_data, "events: callstop() returning true", "[events]") { - set_test_name("callstop() returning true"); LLEventPump& per_frame(pumps.obtain("per-frame")); listener0.reset(0); listener1.reset(0); @@ -193,21 +186,19 @@ void events_object::test<2>() LLBoundListener bound1 = listener1.listenTo(per_frame, &Listener::call, // after listener0 make(LLEventPump::NameList{ listener0.getName() })); - ensure("enabled", per_frame.enabled()); - ensure("connected 0", bound0.connected()); - ensure("unblocked 0", !bound0.blocked()); - ensure("connected 1", bound1.connected()); - ensure("unblocked 1", !bound1.blocked()); + CHECK(per_frame.enabled()); + CHECK(bound0.connected()); + CHECK_FALSE(bound0.blocked()); + CHECK(bound1.connected()); + CHECK_FALSE(bound1.blocked()); per_frame.post(1); - check_listener("got", listener0, 1); + require_last_event("got", listener0, 1); // Because listener0.callstop() returns true, control never reaches listener1.call(). - check_listener("got", listener1, 0); + require_last_event("got", listener1, 0); } -template<> template<> -void events_object::test<3>() +TEST_CASE_METHOD(events_data, "events: an explicit LLEventStream registers itself until destroyed", "[events]") { - set_test_name("explicitly-instantiated LLEventStream"); // Explicitly instantiate an LLEventStream, and verify that it // self-registers with LLEventPumps size_t registered = pumps.mPumpMap.size(); @@ -217,36 +208,32 @@ void events_object::test<3>() LLEventStream myEventStream("stream"); localInstance = &myEventStream; LLEventPump& stream(pumps.obtain("stream")); - ensure("found named LLEventStream instance", &stream == localInstance); - ensure_equals("registered new instance", pumps.mPumpMap.size(), registered + 1); - ensure_equals("explicit instance not owned", pumps.mOurPumps.size(), owned); + CHECK(&stream == localInstance); + CHECK(pumps.mPumpMap.size() == registered + 1); + CHECK(pumps.mOurPumps.size() == owned); } // destroy myEventStream -- should unregister - ensure_equals("destroyed instance unregistered", pumps.mPumpMap.size(), registered); - ensure_equals("destroyed instance not owned", pumps.mOurPumps.size(), owned); + CHECK(pumps.mPumpMap.size() == registered); + CHECK(pumps.mOurPumps.size() == owned); LLEventPump& stream(pumps.obtain("stream")); - ensure("new LLEventStream instance", &stream != localInstance); - ensure_equals("obtain()ed instance registered", pumps.mPumpMap.size(), registered + 1); - ensure_equals("obtain()ed instance owned", pumps.mOurPumps.size(), owned + 1); + CHECK(&stream != localInstance); + CHECK(pumps.mPumpMap.size() == registered + 1); + CHECK(pumps.mOurPumps.size() == owned + 1); } -template<> template<> -void events_object::test<4>() +TEST_CASE_METHOD(events_data, "events: stopListening() frees the name and a bogus name has no connection", "[events]") { - set_test_name("stopListening()"); LLEventPump& login(pumps.obtain("login")); listener0.listenTo(login); login.stopListening(listener0.getName()); // should not throw because stopListening() should have removed name listener0.listenTo(login, &Listener::callstop); LLBoundListener wrong = login.getListener("bogus"); - ensure("bogus connection disconnected", !wrong.connected()); - ensure("bogus connection blocked", wrong.blocked()); + CHECK_FALSE(wrong.connected()); + CHECK(wrong.blocked()); } -template<> template<> -void events_object::test<5>() +TEST_CASE_METHOD(events_data, "events: chaining LLEventPump instances", "[events]") { - set_test_name("chaining LLEventPump instances"); LLEventPump& upstream(pumps.obtain("upstream")); // One potentially-useful construct is to chain LLEventPumps together. // Among other things, this allows you to turn subsets of listeners on @@ -260,18 +247,16 @@ void events_object::test<5>() listener0.reset(0); listener1.reset(0); upstream.post(1); - check_listener("got unfiltered", listener0, 1); - check_listener("got unfiltered", listener1, 1); + require_last_event("got unfiltered", listener0, 1); + require_last_event("got unfiltered", listener1, 1); filter0.enable(false); upstream.post(2); - check_listener("didn't get filtered", listener0, 1); - check_listener("got filtered", listener1, 2); + require_last_event("didn't get filtered", listener0, 1); + require_last_event("got filtered", listener1, 2); } -template<> template<> -void events_object::test<6>() +TEST_CASE_METHOD(events_data, "events: listener dependency order", "[events]") { - set_test_name("listener dependency order"); typedef LLEventPump::NameList NameList; LLEventPump& button(pumps.obtain("button")); Collect collector; @@ -286,7 +271,7 @@ void events_object::test<6>() button.listen("spot", boost::bind(&Collect::add, boost::ref(collector), "spot", _1)); button.post(1); - ensure_equals(collector.result, make(StringVec{ "spot", "checked", "Mary" })); + CHECK(collector.result == make(StringVec{ "spot", "checked", "Mary" })); collector.clear(); button.stopListening("Mary"); button.listen("Mary", @@ -295,7 +280,7 @@ void events_object::test<6>() // now "Mary" must come before "spot" make(NameList{ "spot" })); button.post(2); - ensure_equals(collector.result, make(StringVec{ "Mary", "spot", "checked" })); + CHECK(collector.result == make(StringVec{ "Mary", "spot", "checked" })); collector.clear(); button.stopListening("spot"); std::string threw = catch_what( @@ -309,23 +294,23 @@ void events_object::test<6>() // change; go ahead and change the test to match. // Establish that it contains: // - the name and runtime type of the LLEventPump - ensure_contains("LLEventPump type", threw, typeid(button).name()); - ensure_contains("LLEventPump name", threw, "'button'"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring(typeid(button).name())); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("'button'")); // - the name of the new listener that caused the problem - ensure_contains("new listener name", threw, "'spot'"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("'spot'")); // - a synopsis of the problematic dependencies. - ensure_contains("cyclic dependencies", threw, - "\"Mary\" -> before (\"spot\")"); - ensure_contains("cyclic dependencies", threw, - "after (\"spot\") -> \"checked\""); - ensure_contains("cyclic dependencies", threw, - "after (\"Mary\", \"checked\") -> \"spot\""); + CHECK_THAT(threw, + Catch::Matchers::ContainsSubstring("\"Mary\" -> before (\"spot\")")); + CHECK_THAT(threw, + Catch::Matchers::ContainsSubstring("after (\"spot\") -> \"checked\"")); + CHECK_THAT(threw, + Catch::Matchers::ContainsSubstring("after (\"Mary\", \"checked\") -> \"spot\"")); button.listen("yellow", boost::bind(&Collect::add, boost::ref(collector), "yellow", _1), make(NameList{ "checked" })); button.listen("shoelaces", boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1), make(NameList{ "checked" })); button.post(3); - ensure_equals(collector.result, make(StringVec{ "Mary", "checked", "yellow", "shoelaces" })); + CHECK(collector.result == make(StringVec{ "Mary", "checked", "yellow", "shoelaces" })); collector.clear(); threw = catch_what( [&button, &collector](){ @@ -337,21 +322,21 @@ void events_object::test<6>() // Same remarks about the specific wording of the exception. Just // ensure that it contains enough information to clarify the // problem and what must be done to resolve it. - ensure_contains("LLEventPump type", threw, typeid(button).name()); - ensure_contains("LLEventPump name", threw, "'button'"); - ensure_contains("new listener name", threw, "'of'"); - ensure_contains("prev listener name", threw, "'yellow'"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring(typeid(button).name())); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("'button'")); + UNSCOPED_INFO("new listener name"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("'of'")); + UNSCOPED_INFO("prev listener name"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("'yellow'")); // std::cout << "Thrown Exception: " << threw << std::endl; - ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces"); - ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("was: Mary, checked, yellow, shoelaces")); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("now: Mary, checked, shoelaces, of, yellow")); button.post(4); - ensure_equals(collector.result, make(StringVec{ "Mary", "checked", "yellow", "shoelaces" })); + CHECK(collector.result == make(StringVec{ "Mary", "checked", "yellow", "shoelaces" })); } -template<> template<> -void events_object::test<7>() +TEST_CASE_METHOD(events_data, "events: tweaked and untweaked LLEventPump instance names", "[events]") { - set_test_name("tweaked and untweaked LLEventPump instance names"); { // nested scope // Hand-instantiate an LLEventStream... LLEventStream bob("bob"); @@ -360,17 +345,17 @@ void events_object::test<7>() // then another with a duplicate name. LLEventStream bob2("bob"); }); - ensure("Caught DupPumpName", !threw.empty()); + CHECK_FALSE(threw.empty()); } // delete first 'bob' LLEventStream bob("bob"); // should work, previous one unregistered LLEventStream bob1("bob", true);// allowed to tweak name - ensure_equals("tweaked LLEventStream name", bob1.getName(), "bob1"); + CHECK(bob1.getName() == "bob1"); std::vector > streams; for (int i = 2; i <= 10; ++i) { streams.push_back(std::shared_ptr(new LLEventStream("bob", true))); } - ensure_equals("last tweaked LLEventStream name", streams.back()->getName(), "bob10"); + CHECK(streams.back()->getName() == "bob10"); } // Define a function that accepts an LLListenerOrPumpName @@ -380,31 +365,27 @@ static void eventSource(const LLListenerOrPumpName& listener) listener(17); } -template<> template<> -void events_object::test<8>() +TEST_CASE_METHOD(events_data, "events: LLListenerOrPumpName calls a listener or posts to a named pump", "[events]") { - set_test_name("LLListenerOrPumpName"); // Passing a boost::bind() expression to LLListenerOrPumpName listener0.reset(0); eventSource(boost::bind(&Listener::call, boost::ref(listener0), _1)); - check_listener("got by listener", listener0, 17); + require_last_event("got by listener", listener0, 17); // Passing a string LLEventPump name to LLListenerOrPumpName listener0.reset(0); LLEventStream random("random"); listener0.listenTo(random); eventSource("random"); - check_listener("got by pump name", listener0, 17); + require_last_event("got by pump name", listener0, 17); std::string threw = catch_what( [](){ LLListenerOrPumpName empty; empty(17); }); - ensure("threw Empty", !threw.empty()); + CHECK_FALSE(threw.empty()); } -namespace -{ class TempListener: public Listener { @@ -424,12 +405,9 @@ class TempListener: public Listener bool& mLiveFlag; }; -} // anonymous namespace -template<> template<> -void events_object::test<9>() +TEST_CASE_METHOD(events_data, "events: a destroyed plain listener still looks connected", "[events]") { - set_test_name("listen(boost::bind(...TempListener...))"); // listen() can't do anything about a plain TempListener instance: // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass bool live = false; @@ -437,26 +415,27 @@ void events_object::test<9>() LLBoundListener connection; { TempListener tempListener("temp", live); - ensure("TempListener constructed", live); + UNSCOPED_INFO("TempListener constructed"); + CHECK(live); connection = heaptest.listen(tempListener.getName(), boost::bind(&Listener::call, boost::ref(tempListener), _1)); heaptest.post(1); - check_listener("received", tempListener, 1); + require_last_event("received", tempListener, 1); } // presumably this will make newListener go away? // verify that - ensure("TempListener destroyed", !live); + UNSCOPED_INFO("TempListener destroyed"); + CHECK_FALSE(live); // This is the case against which we can't defend. Don't even try to // post to heaptest -- that would engage Undefined Behavior. // Cautiously inspect connection... - ensure("misleadingly connected", connection.connected()); + UNSCOPED_INFO("misleadingly connected"); + CHECK(connection.connected()); // then disconnect by hand. heaptest.stopListening("temp"); } -namespace -{ class TempTrackableListener: public TempListener, public LLEventTrackable { @@ -466,54 +445,55 @@ class TempTrackableListener: public TempListener, public LLEventTrackable {} }; -} // anonymous namespace -template<> template<> -void events_object::test<10>() +TEST_CASE_METHOD(events_data, "events: destroying a bound LLEventTrackable disconnects it", "[events]") { - set_test_name("listen(boost::bind(...TempTrackableListener ref...))"); bool live = false; LLEventPump& heaptest(pumps.obtain("heaptest")); LLBoundListener connection; { TempTrackableListener tempListener("temp", live); - ensure("TempTrackableListener constructed", live); + UNSCOPED_INFO("TempTrackableListener constructed"); + CHECK(live); connection = heaptest.listen(tempListener.getName(), boost::bind(&TempTrackableListener::call, boost::ref(tempListener), _1)); heaptest.post(1); - check_listener("received", tempListener, 1); + require_last_event("received", tempListener, 1); } // presumably this will make tempListener go away? // verify that - ensure("TempTrackableListener destroyed", ! live); - ensure("implicit disconnect", ! connection.connected()); + UNSCOPED_INFO("TempTrackableListener destroyed"); + CHECK_FALSE(live); + UNSCOPED_INFO("implicit disconnect"); + CHECK_FALSE(connection.connected()); // now just make sure we don't blow up trying to access a freed object! heaptest.post(2); } -template<> template<> -void events_object::test<11>() +TEST_CASE_METHOD(events_data, "events: deleting a bound LLEventTrackable pointer disconnects it", "[events]") { - set_test_name("listen(boost::bind(...TempTrackableListener pointer...))"); bool live = false; LLEventPump& heaptest(pumps.obtain("heaptest")); LLBoundListener connection; { TempTrackableListener* newListener(new TempTrackableListener("temp", live)); - ensure("TempTrackableListener constructed", live); + UNSCOPED_INFO("TempTrackableListener constructed"); + CHECK(live); connection = heaptest.listen(newListener->getName(), boost::bind(&TempTrackableListener::call, newListener, _1)); heaptest.post(1); - check_listener("received", *newListener, 1); + require_last_event("received", *newListener, 1); // explicitly destroy newListener delete newListener; } // verify that - ensure("TempTrackableListener destroyed", ! live); - ensure("implicit disconnect", ! connection.connected()); + UNSCOPED_INFO("TempTrackableListener destroyed"); + CHECK_FALSE(live); + UNSCOPED_INFO("implicit disconnect"); + CHECK_FALSE(connection.connected()); // now just make sure we don't blow up trying to access a freed object! heaptest.post(2); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llfile_test.cpp b/indra/llcommon/tests/llfile_test.cpp index 46ea565b45..dcba540256 100644 --- a/indra/llcommon/tests/llfile_test.cpp +++ b/indra/llcommon/tests/llfile_test.cpp @@ -26,13 +26,13 @@ * $/LicenseInfo$ */ -#include -#include "lltut.h" +#include +#include "altestmatchers.h" #include "linden_common.h" #include "llfile.h" #include "lluuid.h" -namespace tut +namespace { static void clear_entire_dir(const std::filesystem::path& dir_path) { @@ -73,56 +73,55 @@ namespace tut clear_entire_dir(get_testdir_unicode(tempdir, unique)); } }; - typedef test_group llfile_test_t; - typedef llfile_test_t::object llfile_test_object_t; - tut::llfile_test_t tut_llfile_test("llfile_test"); - template<> template<> - void llfile_test_object_t::test<1>() + TEST_CASE_METHOD(llfile_test, "llfile_test: exists isdir and isfile follow mkdir write and remove", "[llfile_test]") { // Test creating directories and files and deleting them and checking if the // relevant status functions work as expected - ensure("LLFile::tmpdir() empty", !tempdir.empty()); - ensure("LLFile::tmpdir() doesn't exist", LLFile::exists(tempdir)); - ensure("LLFile::tmpdir() is not a directory", LLFile::isdir(tempdir)); - ensure("LLFile::tmpdir() should not be a file", !LLFile::isfile(tempdir)); + REQUIRE_FALSE(tempdir.empty()); + CHECK(LLFile::exists(tempdir)); + CHECK(LLFile::isdir(tempdir)); + CHECK_FALSE(LLFile::isfile(tempdir)); // Make sure there is nothing left from a previous test run clear_entire_dir(testdir); - ensure("llfile_test should not exist anymore", !LLFile::exists(testdir)); + CHECK_FALSE(LLFile::exists(testdir)); int rc = LLFile::mkdir(testdir); - ensure("LLFile::mkdir() failed", rc == 0); - ensure("llfile_test should be a directory", LLFile::isdir(testdir)); + UNSCOPED_INFO("mkdir"); + CHECK(rc == 0); + CHECK(LLFile::isdir(testdir)); rc = LLFile::mkdir(testdir); - ensure("LLFile::mkdir() should not fail when the directory already exists", rc == 0); + UNSCOPED_INFO("mkdir of a directory that exists"); + CHECK(rc == 0); std::filesystem::path testfile1 = testdir; testfile1.append("llfile_test.dat"); - ensure("llfile_test1.dat should not yet exist", !LLFile::exists(testfile1)); + CHECK_FALSE(LLFile::exists(testfile1)); const char* testdata = "testdata"; S64 bytes = LLFile::write(testfile1, testdata, 0, sizeof(testdata)); - ensure("LLFile::write() did not write correctly", bytes == sizeof(testdata)); + CHECK(bytes == sizeof(testdata)); rc = LLFile::remove(testfile1); - ensure("LLFile::remove() for file test_file.dat", rc == 0); - ensure("llfile_test.dat should not exist anymore", !LLFile::exists(testfile1)); - ensure("llfile_test.dat should not be a file", !LLFile::isfile(testfile1)); - ensure("llfile_test.dat should not be a directory", !LLFile::isdir(testfile1)); - ensure("llfile_test.dat should not be a symlink", !LLFile::islink(testfile1)); + UNSCOPED_INFO("remove of the file"); + CHECK(rc == 0); + CHECK_FALSE(LLFile::exists(testfile1)); + CHECK_FALSE(LLFile::isfile(testfile1)); + CHECK_FALSE(LLFile::isdir(testfile1)); + CHECK_FALSE(LLFile::islink(testfile1)); rc = LLFile::remove(testdir); - ensure("LLFile::remove() for directory llfile_test failed", rc == 0); - ensure("llfile_test should not exist anymore", !LLFile::exists(testdir)); + UNSCOPED_INFO("remove of the directory"); + CHECK(rc == 0); + CHECK_FALSE(LLFile::exists(testdir)); } - template<> template<> - void llfile_test_object_t::test<2>() + TEST_CASE_METHOD(llfile_test, "llfile_test: the static write size getContents and read agree on a file", "[llfile_test]") { // High level static file IO functions to read and write data files LLFile::mkdir(testdir); - ensure("llfile_test should exist", LLFile::isdir(testdir)); + CHECK(LLFile::isdir(testdir)); std::filesystem::path testfile1 = testdir; testfile1.append("llfile_test.dat"); @@ -131,42 +130,48 @@ namespace tut std::string testdata2("datateststuff"); std::time_t current = time(nullptr); S64 bytes = LLFile::write(testfile1, testdata1.c_str(), 0, testdata1.length()); - ensure("LLFile::write() did not write correctly", bytes == testdata1.length()); - ensure("llfile_test.dat should exist", LLFile::exists(testfile1)); - ensure("llfile_test.dat should be a file", LLFile::isfile(testfile1)); - ensure("llfile_test.dat should not be a directory", !LLFile::isdir(testfile1)); + UNSCOPED_INFO("write"); + CHECK(bytes == testdata1.length()); + CHECK(LLFile::exists(testfile1)); + CHECK(LLFile::isfile(testfile1)); + CHECK_FALSE(LLFile::isdir(testfile1)); bytes = LLFile::size(testfile1); - ensure("LLFile::size() did not return the correct size", bytes == testdata1.length()); + UNSCOPED_INFO("size"); + CHECK(bytes == testdata1.length()); std::string data = LLFile::getContents(testfile1); - ensure("LLFile::getContents() did not return the correct size data", data.length() == testdata1.length()); - ensure_memory_matches("LLFile::getContents() did not read correct data", testdata1.c_str(), (U32)testdata1.length(), data.c_str(), (U32)data.length()); + CHECK(data.length() == testdata1.length()); + CHECK_THAT(ll_test::Memory(testdata1.c_str(), (U32)testdata1.length()), ll_test::MemoryMatches(data.c_str(), (U32)data.length())); char buffer[1024]; bytes = LLFile::read(testfile1, buffer, 0, testdata1.length()); - ensure("LLFile:read() did not return the correct size", bytes == testdata1.length()); - ensure_memory_matches("LLFile::read() did not read correct data", testdata1.c_str(), (U32)bytes, buffer, (U32)bytes); + UNSCOPED_INFO("read"); + REQUIRE(bytes == testdata1.length()); + CHECK_THAT(ll_test::Memory(testdata1.c_str(), (U32)bytes), ll_test::MemoryMatches(buffer, (U32)bytes)); // What if we try to read more data than there is in the file? bytes = LLFile::read(testfile1, buffer, 0, bytes + 10); - ensure("LLFile:read() did not correctly stop on eof", bytes == testdata1.length()); - ensure_memory_matches("LLFile::read() did not read correct data", testdata1.c_str(), (U32)bytes, buffer, (U32)bytes); + UNSCOPED_INFO("read past the end of the file"); + REQUIRE(bytes == testdata1.length()); + CHECK_THAT(ll_test::Memory(testdata1.c_str(), (U32)bytes), ll_test::MemoryMatches(buffer, (U32)bytes)); // Let's append more data bytes = LLFile::write(testfile1, testdata2.c_str(), -1, testdata2.length()); - ensure("LLFile::write() did not write correctly", bytes == testdata2.length()); + UNSCOPED_INFO("append"); + CHECK(bytes == testdata2.length()); bytes = LLFile::size(testfile1); - ensure("LLFile::size() did not return the correct size", bytes == testdata1.length() + testdata2.length()); + UNSCOPED_INFO("size after the append"); + REQUIRE(bytes == testdata1.length() + testdata2.length()); bytes = LLFile::read(testfile1, buffer, 0, bytes); - ensure("LLFile:read() did not read correct number of bytes", bytes == testdata1.length() + testdata2.length()); - ensure_memory_matches("LLFile:read() did not read correct testdata1", testdata1.c_str(), (U32)testdata1.length(), buffer, (U32)testdata1.length()); - ensure_memory_matches("LLFile:read() did not read correct testdata2", testdata2.c_str(), (U32)testdata2.length(), buffer + testdata1.length(), (U32)testdata2.length()); + UNSCOPED_INFO("read after the append"); + CHECK(bytes == testdata1.length() + testdata2.length()); + CHECK_THAT(ll_test::Memory(testdata1.c_str(), (U32)testdata1.length()), ll_test::MemoryMatches(buffer, (U32)testdata1.length())); + CHECK_THAT(ll_test::Memory(testdata2.c_str(), (U32)testdata2.length()), ll_test::MemoryMatches(buffer + testdata1.length(), (U32)testdata2.length())); } - template<> template<> - void llfile_test_object_t::test<3>() + TEST_CASE_METHOD(llfile_test, "llfile_test: an LLFile writes reads seeks and tells", "[llfile_test]") { const size_t numints = 1024; @@ -183,59 +188,74 @@ namespace tut std::error_code ec; LLFile fileout(testfile, LLFile::out, ec); - ensure("LLFile constructor did not open correctly", (bool)fileout); - ensure("error_code from LLFile constructor should not indicate an error", !ec); + REQUIRE((bool)fileout); + UNSCOPED_INFO("constructor"); + CHECK_FALSE(ec); if (fileout) { S64 length = fileout.size(ec); - ensure("freshly created file should be empty", length == 0); - ensure("error_code from LLFile::size() should not indicate an error", !ec); + CHECK(length == 0); + UNSCOPED_INFO("size"); + CHECK_FALSE(ec); S64 bytes = fileout.write(data, sizeof(data), ec); - ensure("LLFile::write() did not write correctly", bytes == sizeof(data)); - ensure("error_code from LLFile::write() should not indicate an error", !ec); + UNSCOPED_INFO("write"); + CHECK(bytes == sizeof(data)); + UNSCOPED_INFO("write"); + CHECK_FALSE(ec); bytes = fileout.write(data, sizeof(data), ec); - ensure("LLFile::write() did not write correctly", bytes == sizeof(data)); - ensure("error_code from LLFile::write() should not indicate an error", !ec); + UNSCOPED_INFO("second write"); + CHECK(bytes == sizeof(data)); + UNSCOPED_INFO("second write"); + CHECK_FALSE(ec); bytes = fileout.size(ec); - ensure("LLFile::size() returned wrong size", bytes == 2 * sizeof(data)); - ensure("error_code from LLFile::size() should not indicate an error", !ec); + UNSCOPED_INFO("size after the writes"); + CHECK(bytes == 2 * sizeof(data)); + UNSCOPED_INFO("size after the writes"); + CHECK_FALSE(ec); fileout.close(); } LLFile filein(testfile, LLFile::in, ec); - ensure("LLFile constructor did not open correctly", (bool)filein); - ensure("error_code from LLFile constructor should not indicate an error", !ec); + REQUIRE((bool)filein); + UNSCOPED_INFO("constructor"); + CHECK_FALSE(ec); if (filein) { S64 length = filein.size(ec); - ensure("LLFile::size() returned wrong size", length == 2 * sizeof(data)); - ensure("error_code from LLFile::size() should not indicate an error", !ec); + REQUIRE(length == 2 * sizeof(data)); + UNSCOPED_INFO("size"); + CHECK_FALSE(ec); char* buffer = (char*)malloc(length); S64 bytes = filein.read(buffer, length, ec); - ensure("LLFile::read() did not read correctly", bytes == length); - ensure("error_code from LLFile::read() should not indicate an error", !ec); - ensure_memory_matches("LLFile:read() did not read correct data1", data, (U32)sizeof(data), buffer, (U32)sizeof(data)); - ensure_memory_matches("LLFile:read() did not read correct data2", data, (U32)sizeof(data), buffer + sizeof(data), (U32)sizeof(data)); + UNSCOPED_INFO("read"); + CHECK(bytes == length); + UNSCOPED_INFO("read"); + CHECK_FALSE(ec); + CHECK_THAT(ll_test::Memory(data, (U32)sizeof(data)), ll_test::MemoryMatches(buffer, (U32)sizeof(data))); + CHECK_THAT(ll_test::Memory(data, (U32)sizeof(data)), ll_test::MemoryMatches(buffer + sizeof(data), (U32)sizeof(data))); S64 offset = filein.tell(ec); - ensure("LLFile::tell() returned a bad offset", offset == length); - ensure("error_code from LLFile::read() should not indicate an error", !ec); + CHECK(offset == length); + UNSCOPED_INFO("tell"); + CHECK_FALSE(ec); offset = sizeof(data) / 2; int rc = filein.seek(offset, ec); - ensure("LLFile::seek() indicated an error", rc == 0); - ensure("error_code from LLFile::seek() should not indicate an error", !ec); + CHECK(rc == 0); + UNSCOPED_INFO("seek"); + CHECK_FALSE(ec); bytes = filein.read(buffer, 2 * sizeof(data), ec); - ensure("LLFile::read() did not read correctly", bytes == sizeof(data) + offset); - ensure("error_code from LLFile::read() should not indicate an error", !ec); - ensure_memory_matches("LLFile:read() did not read correct data3", (char*)data + offset, (U32)offset, buffer, (U32)offset); - ensure_memory_matches("LLFile:read() did not read correct data4", (char*)data, (U32)sizeof(data), buffer + offset, (U32)sizeof(data)); + UNSCOPED_INFO("read from the middle"); + CHECK(bytes == sizeof(data) + offset); + UNSCOPED_INFO("read from the middle"); + CHECK_FALSE(ec); + CHECK_THAT(ll_test::Memory((char*)data + offset, (U32)offset), ll_test::MemoryMatches(buffer, (U32)offset)); + CHECK_THAT(ll_test::Memory((char*)data, (U32)sizeof(data)), ll_test::MemoryMatches(buffer + offset, (U32)sizeof(data))); filein.close(); free(buffer); } } - template<> template<> - void llfile_test_object_t::test<4>() + TEST_CASE_METHOD(llfile_test, "llfile_test: LLFile refuses conflicting flags and missing paths", "[llfile_test]") { // Testing the LLFile class implementation with wrong paths and parameters std::filesystem::path testfile = testdir; @@ -248,29 +268,32 @@ namespace tut std::error_code ec; LLFile file(testfile, LLFile::out | LLFile::noreplace, ec); - ensure("LLFile constructor should not have opened the already existing file", !file); - ensure("error_code from LLFile constructor should indicate an error", (bool)ec); + UNSCOPED_INFO("noreplace on a file that exists"); + CHECK_FALSE(file); + CHECK((bool)ec); LLFile::remove(testfile); file = LLFile(testfile, LLFile::out | LLFile::app | LLFile::trunc, ec); - ensure("LLFile constructor should not have opened the file with conflicting flags", !file); - ensure("error_code from LLFile constructor should indicate an error", (bool)ec); + UNSCOPED_INFO("app with trunc"); + CHECK_FALSE(file); + CHECK((bool)ec); file = LLFile(testfile, LLFile::out | LLFile::app | LLFile::noreplace, ec); - ensure("LLFile constructor should not have opened the file with conflicting flags", !file); - ensure("error_code from LLFile constructor should indicate an error", (bool)ec); + UNSCOPED_INFO("app with noreplace"); + CHECK_FALSE(file); + CHECK((bool)ec); testfile = testdir; testfile.append("llfile_test"); testfile.append("llfile_test.bin"); file = LLFile(testfile, LLFile::in, ec); - ensure("LLFile constructor should not have been able to open the file in the non-existing directory", !file); - ensure("error_code from LLFile constructor should indicate an error", (bool)ec); + UNSCOPED_INFO("a directory that does not exist"); + CHECK_FALSE(file); + CHECK((bool)ec); } - template<> template<> - void llfile_test_object_t::test<5>() + TEST_CASE_METHOD(llfile_test, "llfile_test: mkdir write read and remove work with Unicode names", "[llfile_test]") { // Test file and directory operations with Unicode paths and filenames std::filesystem::path testdir_unicode = get_testdir_unicode(tempdir, unique); @@ -282,150 +305,162 @@ namespace tut // Clean up any previous test artifacts clear_entire_dir(testdir_unicode); - ensure("Unicode test directory should not exist", !LLFile::exists(testdir_unicode)); + CHECK_FALSE(LLFile::exists(testdir_unicode)); // Create the Unicode directory int rc = LLFile::mkdir(testdir_unicode); - ensure("LLFile::mkdir() failed for Unicode directory", rc == 0); - ensure("Unicode test directory should exist", LLFile::isdir(testdir_unicode)); + UNSCOPED_INFO("mkdir"); + CHECK(rc == 0); + CHECK(LLFile::isdir(testdir_unicode)); - ensure("Unicode test file should not exist", !LLFile::exists(testfile_unicode)); + CHECK_FALSE(LLFile::exists(testfile_unicode)); // Write to the Unicode file const char* testdata = "unicode_testdata"; S64 bytes = LLFile::write(testfile_unicode, testdata, 0, strlen(testdata)); - ensure("Unicode test file should exist", LLFile::exists(testfile_unicode)); - ensure("Unicode test file should be a file", LLFile::isfile(testfile_unicode)); - ensure("LLFile::write() did not write correctly to Unicode file", bytes == (S64)strlen(testdata)); + CHECK(LLFile::exists(testfile_unicode)); + CHECK(LLFile::isfile(testfile_unicode)); + UNSCOPED_INFO("write"); + CHECK(bytes == (S64)strlen(testdata)); // Read back the data char buffer[64] = {}; bytes = LLFile::read(testfile_unicode, buffer, 0, sizeof(buffer)); - ensure("LLFile::read() did not read correctly from Unicode file", bytes == (S64)strlen(testdata)); - ensure_memory_matches("LLFile::read() did not read correct Unicode data", testdata, (U32)bytes, buffer, (U32)bytes); + UNSCOPED_INFO("read"); + REQUIRE(bytes == (S64)strlen(testdata)); + CHECK_THAT(ll_test::Memory(testdata, (U32)bytes), ll_test::MemoryMatches(buffer, (U32)bytes)); // Remove the file and directory rc = LLFile::remove(testfile_unicode); - ensure("LLFile::remove() failed for Unicode file", rc == 0); - ensure("Unicode test file should not exist after removal", !LLFile::exists(testfile_unicode)); + UNSCOPED_INFO("remove of the file"); + CHECK(rc == 0); + CHECK_FALSE(LLFile::exists(testfile_unicode)); rc = LLFile::remove(testdir_unicode); - ensure("LLFile::remove() failed for Unicode directory", rc == 0); - ensure("Unicode test directory should not exist after removal", !LLFile::exists(testdir_unicode)); + UNSCOPED_INFO("remove of the directory"); + CHECK(rc == 0); + CHECK_FALSE(LLFile::exists(testdir_unicode)); } - template<> template<> - void llfile_test_object_t::test<6>() + TEST_CASE_METHOD(llfile_test, "llfile_test: isSafeFileName accepts ordinary file names", "[llfile_test]") { // Ordinary file names pass isSafeFileName() - ensure("xml", LLFile::isSafeFileName("lsl_keywords.xml")); - ensure("two extensions", LLFile::isSafeFileName("secondlife.d.luau")); - ensure("no extension", LLFile::isSafeFileName("keywords")); - ensure("inner dots", LLFile::isSafeFileName("a..b")); - ensure("inner space", LLFile::isSafeFileName("my file.txt")); - ensure("hidden", LLFile::isSafeFileName(".luaurc")); - ensure("leading dots", LLFile::isSafeFileName("..name")); - ensure("unicode", LLFile::isSafeFileName("\xE3\x83\x95\xE3\x82\xA1\xE3\x82\xA4\xE3\x83\xAB.bin")); + CHECK(LLFile::isSafeFileName("lsl_keywords.xml")); + CHECK(LLFile::isSafeFileName("secondlife.d.luau")); + CHECK(LLFile::isSafeFileName("keywords")); + CHECK(LLFile::isSafeFileName("a..b")); + CHECK(LLFile::isSafeFileName("my file.txt")); + UNSCOPED_INFO("hidden"); + CHECK(LLFile::isSafeFileName(".luaurc")); + CHECK(LLFile::isSafeFileName("..name")); + UNSCOPED_INFO("unicode"); + CHECK(LLFile::isSafeFileName("\xE3\x83\x95\xE3\x82\xA1\xE3\x82\xA4\xE3\x83\xAB.bin")); } - template<> template<> - void llfile_test_object_t::test<7>() + TEST_CASE_METHOD(llfile_test, "llfile_test: isSafeFileName refuses empty and dot names and ones Windows trims", "[llfile_test]") { // Nothing, the directory itself or its parent, and names Windows would // shorten when creating them - ensure("empty", !LLFile::isSafeFileName("")); - ensure("dot", !LLFile::isSafeFileName(".")); - ensure("dot dot", !LLFile::isSafeFileName("..")); - ensure("three dots", !LLFile::isSafeFileName("...")); - ensure("trailing dot", !LLFile::isSafeFileName("name.")); - ensure("trailing space", !LLFile::isSafeFileName("name.xml ")); + CHECK_FALSE(LLFile::isSafeFileName("")); + CHECK_FALSE(LLFile::isSafeFileName(".")); + CHECK_FALSE(LLFile::isSafeFileName("..")); + CHECK_FALSE(LLFile::isSafeFileName("...")); + CHECK_FALSE(LLFile::isSafeFileName("name.")); + UNSCOPED_INFO("trailing space"); + CHECK_FALSE(LLFile::isSafeFileName("name.xml ")); } - template<> template<> - void llfile_test_object_t::test<8>() + TEST_CASE_METHOD(llfile_test, "llfile_test: isSafeFileName refuses path separators of either platform", "[llfile_test]") { // Separators of either platform, at either end or inside - ensure("parent posix", !LLFile::isSafeFileName("../../evil.xml")); - ensure("parent windows", !LLFile::isSafeFileName("..\\..\\evil.xml")); - ensure("absolute posix", !LLFile::isSafeFileName("/etc/evil")); - ensure("absolute windows", !LLFile::isSafeFileName("\\Windows\\evil.dll")); - ensure("unc", !LLFile::isSafeFileName("\\\\host\\share\\evil")); - ensure("subdirectory posix", !LLFile::isSafeFileName("sub/evil.xml")); - ensure("subdirectory windows", !LLFile::isSafeFileName("sub\\evil.xml")); - ensure("trailing separator", !LLFile::isSafeFileName("evil/")); + CHECK_FALSE(LLFile::isSafeFileName("../../evil.xml")); + CHECK_FALSE(LLFile::isSafeFileName("..\\..\\evil.xml")); + CHECK_FALSE(LLFile::isSafeFileName("/etc/evil")); + CHECK_FALSE(LLFile::isSafeFileName("\\Windows\\evil.dll")); + UNSCOPED_INFO("unc"); + CHECK_FALSE(LLFile::isSafeFileName("\\\\host\\share\\evil")); + CHECK_FALSE(LLFile::isSafeFileName("sub/evil.xml")); + CHECK_FALSE(LLFile::isSafeFileName("sub\\evil.xml")); + CHECK_FALSE(LLFile::isSafeFileName("evil/")); } - template<> template<> - void llfile_test_object_t::test<9>() + TEST_CASE_METHOD(llfile_test, "llfile_test: isSafeFileName refuses colons reserved characters and controls", "[llfile_test]") { // Drive and stream colons, the rest of what Windows refuses in a name, // and control characters, a NUL above all: a C string would cut a // harmless prefix off a harmful name there - ensure("drive absolute", !LLFile::isSafeFileName("C:\\evil.dll")); - ensure("drive relative", !LLFile::isSafeFileName("C:evil.dll")); - ensure("stream", !LLFile::isSafeFileName("name.xml:evil")); - ensure("star", !LLFile::isSafeFileName("a*b")); - ensure("question", !LLFile::isSafeFileName("a?b")); - ensure("quote", !LLFile::isSafeFileName("a\"b")); - ensure("less", !LLFile::isSafeFileName("ab")); - ensure("pipe", !LLFile::isSafeFileName("a|b")); - ensure("tab", !LLFile::isSafeFileName("a\tb")); - ensure("newline", !LLFile::isSafeFileName("a\nb")); - ensure("unit separator", !LLFile::isSafeFileName("a\x1F" "b")); - ensure("only nul", !LLFile::isSafeFileName(std::string(1, '\0'))); - ensure("inner nul", !LLFile::isSafeFileName(std::string("name.xml\0evil", 13))); - ensure("trailing nul", !LLFile::isSafeFileName(std::string("name.xml\0", 9))); + UNSCOPED_INFO("drive absolute"); + CHECK_FALSE(LLFile::isSafeFileName("C:\\evil.dll")); + UNSCOPED_INFO("drive relative"); + CHECK_FALSE(LLFile::isSafeFileName("C:evil.dll")); + UNSCOPED_INFO("stream"); + CHECK_FALSE(LLFile::isSafeFileName("name.xml:evil")); + CHECK_FALSE(LLFile::isSafeFileName("a*b")); + CHECK_FALSE(LLFile::isSafeFileName("a?b")); + CHECK_FALSE(LLFile::isSafeFileName("a\"b")); + CHECK_FALSE(LLFile::isSafeFileName("ab")); + CHECK_FALSE(LLFile::isSafeFileName("a|b")); + CHECK_FALSE(LLFile::isSafeFileName("a\tb")); + CHECK_FALSE(LLFile::isSafeFileName("a\nb")); + UNSCOPED_INFO("unit separator"); + CHECK_FALSE(LLFile::isSafeFileName("a\x1F" "b")); + CHECK_FALSE(LLFile::isSafeFileName(std::string(1, '\0'))); + CHECK_FALSE(LLFile::isSafeFileName(std::string("name.xml\0evil", 13))); + CHECK_FALSE(LLFile::isSafeFileName(std::string("name.xml\0", 9))); } - template<> template<> - void llfile_test_object_t::test<10>() + TEST_CASE_METHOD(llfile_test, "llfile_test: isSafeFileName allows up to 255 bytes", "[llfile_test]") { // The length limit is inclusive - ensure("at limit", LLFile::isSafeFileName(std::string(255, 'a'))); - ensure("over limit", !LLFile::isSafeFileName(std::string(256, 'a'))); + CHECK(LLFile::isSafeFileName(std::string(255, 'a'))); + CHECK_FALSE(LLFile::isSafeFileName(std::string(256, 'a'))); } - template<> template<> - void llfile_test_object_t::test<11>() + TEST_CASE_METHOD(llfile_test, "llfile_test: isSafeFileName refuses Windows device names", "[llfile_test]") { // Windows device names, in any case, with or without an extension - ensure("con", !LLFile::isSafeFileName("CON")); - ensure("prn", !LLFile::isSafeFileName("PRN")); - ensure("aux", !LLFile::isSafeFileName("AUX")); - ensure("nul", !LLFile::isSafeFileName("NUL")); - ensure("conin", !LLFile::isSafeFileName("CONIN$")); - ensure("conout", !LLFile::isSafeFileName("CONOUT$")); - ensure("com0", !LLFile::isSafeFileName("COM0")); - ensure("com9", !LLFile::isSafeFileName("COM9")); - ensure("lpt0", !LLFile::isSafeFileName("LPT0")); - ensure("lpt9", !LLFile::isSafeFileName("LPT9")); - ensure("com superscript one", !LLFile::isSafeFileName("COM\xC2\xB9")); - ensure("lpt superscript three", !LLFile::isSafeFileName("LPT\xC2\xB3")); - - ensure("lower case", !LLFile::isSafeFileName("con")); - ensure("mixed case", !LLFile::isSafeFileName("cOm1")); - ensure("lower case conout", !LLFile::isSafeFileName("conout$")); - - ensure("extension", !LLFile::isSafeFileName("NUL.xml")); - ensure("two extensions", !LLFile::isSafeFileName("nul.tar.gz")); - ensure("superscript with extension", !LLFile::isSafeFileName("com\xC2\xB2.txt")); - ensure("spaces before extension", !LLFile::isSafeFileName("CON .xml")); + CHECK_FALSE(LLFile::isSafeFileName("CON")); + CHECK_FALSE(LLFile::isSafeFileName("PRN")); + CHECK_FALSE(LLFile::isSafeFileName("AUX")); + CHECK_FALSE(LLFile::isSafeFileName("NUL")); + CHECK_FALSE(LLFile::isSafeFileName("CONIN$")); + CHECK_FALSE(LLFile::isSafeFileName("CONOUT$")); + CHECK_FALSE(LLFile::isSafeFileName("COM0")); + CHECK_FALSE(LLFile::isSafeFileName("COM9")); + CHECK_FALSE(LLFile::isSafeFileName("LPT0")); + CHECK_FALSE(LLFile::isSafeFileName("LPT9")); + UNSCOPED_INFO("com superscript one"); + CHECK_FALSE(LLFile::isSafeFileName("COM\xC2\xB9")); + UNSCOPED_INFO("lpt superscript three"); + CHECK_FALSE(LLFile::isSafeFileName("LPT\xC2\xB3")); + + CHECK_FALSE(LLFile::isSafeFileName("con")); + CHECK_FALSE(LLFile::isSafeFileName("cOm1")); + CHECK_FALSE(LLFile::isSafeFileName("conout$")); + + CHECK_FALSE(LLFile::isSafeFileName("NUL.xml")); + CHECK_FALSE(LLFile::isSafeFileName("nul.tar.gz")); + UNSCOPED_INFO("superscript with extension"); + CHECK_FALSE(LLFile::isSafeFileName("com\xC2\xB2.txt")); + UNSCOPED_INFO("spaces before extension"); + CHECK_FALSE(LLFile::isSafeFileName("CON .xml")); } - template<> template<> - void llfile_test_object_t::test<12>() + TEST_CASE_METHOD(llfile_test, "llfile_test: isSafeFileName accepts names that only look like devices", "[llfile_test]") { // Names that only look like devices are files - ensure("longer word", LLFile::isSafeFileName("CONSOLE.xml")); - ensure("two digits", LLFile::isSafeFileName("COM10")); - ensure("no digit", LLFile::isSafeFileName("LPT")); - ensure("device prefix", LLFile::isSafeFileName("nul_keywords.xml")); - ensure("device suffix", LLFile::isSafeFileName("icon.xml")); - ensure("device extension", LLFile::isSafeFileName("keywords.con")); - ensure("leading space", LLFile::isSafeFileName(" CON")); - ensure("only spaces before extension", LLFile::isSafeFileName(" .xml")); - ensure("hidden device", LLFile::isSafeFileName(".con")); + CHECK(LLFile::isSafeFileName("CONSOLE.xml")); + CHECK(LLFile::isSafeFileName("COM10")); + CHECK(LLFile::isSafeFileName("LPT")); + CHECK(LLFile::isSafeFileName("nul_keywords.xml")); + CHECK(LLFile::isSafeFileName("icon.xml")); + CHECK(LLFile::isSafeFileName("keywords.con")); + UNSCOPED_INFO("leading space"); + CHECK(LLFile::isSafeFileName(" CON")); + UNSCOPED_INFO("only spaces before extension"); + CHECK(LLFile::isSafeFileName(" .xml")); + CHECK(LLFile::isSafeFileName(".con")); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llframetimer_test.cpp b/indra/llcommon/tests/llframetimer_test.cpp index 2af7be4fe5..8461502b13 100644 --- a/indra/llcommon/tests/llframetimer_test.cpp +++ b/indra/llcommon/tests/llframetimer_test.cpp @@ -30,9 +30,10 @@ #include "../llframetimer.h" #include "../llsd.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" -namespace tut +namespace { struct frametimer_test { @@ -41,48 +42,36 @@ namespace tut LLFrameTimer::updateFrameTime(); } }; - typedef test_group frametimer_group_t; - typedef frametimer_group_t::object frametimer_object_t; - tut::frametimer_group_t frametimer_instance("LLFrameTimer"); - template<> template<> - void frametimer_object_t::test<1>() + TEST_CASE_METHOD(frametimer_test, "LLFrameTimer: expiresAt returns the time setExpiryAt set", "[LLFrameTimer]") { F64 seconds_since_epoch = LLFrameTimer::getTotalSeconds(); LLFrameTimer timer; timer.setExpiryAt(seconds_since_epoch); F64 expires_at = timer.expiresAt(); - ensure_distance( - "set expiry matches get expiry", - expires_at, - seconds_since_epoch, - 0.001); + CHECK_THAT(expires_at, + ll_test::WithinDistance(seconds_since_epoch, + 0.001)); } - template<> template<> - void frametimer_object_t::test<2>() + TEST_CASE_METHOD(frametimer_test, "LLFrameTimer: setExpiryAt moves an expiry already set", "[LLFrameTimer]") { F64 seconds_since_epoch = LLFrameTimer::getTotalSeconds(); seconds_since_epoch += 10.0; LLFrameTimer timer; timer.setExpiryAt(seconds_since_epoch); F64 expires_at = timer.expiresAt(); - ensure_distance( - "set expiry matches get expiry 1", - expires_at, - seconds_since_epoch, - 0.001); + CHECK_THAT(expires_at, + ll_test::WithinDistance(seconds_since_epoch, + 0.001)); seconds_since_epoch += 10.0; timer.setExpiryAt(seconds_since_epoch); expires_at = timer.expiresAt(); - ensure_distance( - "set expiry matches get expiry 2", - expires_at, - seconds_since_epoch, - 0.001); + CHECK_THAT(expires_at, + ll_test::WithinDistance(seconds_since_epoch, + 0.001)); } - template<> template<> - void frametimer_object_t::test<3>() + TEST_CASE_METHOD(frametimer_test, "LLFrameTimer: an expiry two seconds out passes within ten 200 ms sleeps", "[LLFrameTimer]") { clock_t t1 = clock(); ms_sleep(200); @@ -109,27 +98,25 @@ namespace tut LLFrameTimer::updateFrameTime(); iterations_until_expiration++; } - ensure("timer took too long to expire", iterations_until_expiration <= 10); + CHECK(iterations_until_expiration <= 10); } - template<> template<> - void frametimer_object_t::test<4>() + TEST_CASE_METHOD(frametimer_test, "LLFrameTimer: an expiry counts from now and not from the timer's last reset", "[LLFrameTimer]") { - set_test_name("an expiry counts from now, not from the timer's last reset"); LLFrameTimer timer; // Longer than the expiry passes between the timer starting and the // expiry being set. ms_sleep(300); LLFrameTimer::updateFrameTime(); timer.setTimerExpirySec(0.2f); - ensure("not expired the moment it is set", !timer.hasExpired()); - ensure("the whole expiry still to run", timer.getTimeToExpireF32() > 0.19f); + UNSCOPED_INFO("not expired the moment it is set"); + CHECK_FALSE(timer.hasExpired()); + UNSCOPED_INFO("the whole expiry still to run"); + CHECK(timer.getTimeToExpireF32() > 0.19f); } - template<> template<> - void frametimer_object_t::test<5>() + TEST_CASE_METHOD(frametimer_test, "LLFrameTimer: a stopped timer keeps the time it ran and resumes from it", "[LLFrameTimer]") { - set_test_name("a stopped timer keeps the time it ran, and resumes from it"); // Far enough from the start of the run that the absolute start time // cannot pass for the time run. ms_sleep(300); @@ -140,19 +127,20 @@ namespace tut LLFrameTimer::updateFrameTime(); const F32 ran = timer.getElapsedTimeF32(); timer.stop(); - ensure("stopped", !timer.getStarted()); - ensure_approximately_equals("the time it ran", timer.getElapsedTimeF32(), ran, 12); + CHECK_FALSE(timer.getStarted()); + UNSCOPED_INFO("the time it ran"); + CHECK_THAT(timer.getElapsedTimeF32(), ll_test::WithinBits(ran, 12)); ms_sleep(100); LLFrameTimer::updateFrameTime(); - ensure_approximately_equals("not running while stopped", timer.getElapsedTimeF32(), ran, 12); + UNSCOPED_INFO("not running while stopped"); + CHECK_THAT(timer.getElapsedTimeF32(), ll_test::WithinBits(ran, 12)); timer.unpause(); - ensure_approximately_equals("resumed from where it stopped", timer.getElapsedTimeF32(), ran, 12); + UNSCOPED_INFO("resumed from where it stopped"); + CHECK_THAT(timer.getElapsedTimeF32(), ll_test::WithinBits(ran, 12)); } - template<> template<> - void frametimer_object_t::test<6>() + TEST_CASE_METHOD(frametimer_test, "LLFrameTimer: a reset while paused zeroes the time run and leaves it paused", "[LLFrameTimer]") { - set_test_name("a reset while paused zeroes the time run and leaves it paused"); ms_sleep(300); LLFrameTimer::updateFrameTime(); LLFrameTimer timer; @@ -161,16 +149,20 @@ namespace tut LLFrameTimer::updateFrameTime(); timer.pause(); timer.reset(); - ensure("still paused", !timer.getStarted()); - ensure_approximately_equals("no time run", timer.getElapsedTimeF32(), 0.f, 12); + CHECK_FALSE(timer.getStarted()); + UNSCOPED_INFO("no time run"); + CHECK_THAT(timer.getElapsedTimeF32(), ll_test::WithinBits(0.f, 12)); ms_sleep(100); LLFrameTimer::updateFrameTime(); - ensure_approximately_equals("not running while paused", timer.getElapsedTimeF32(), 0.f, 12); + UNSCOPED_INFO("not running while paused"); + CHECK_THAT(timer.getElapsedTimeF32(), ll_test::WithinBits(0.f, 12)); timer.unpause(); - ensure_approximately_equals("runs on from zero", timer.getElapsedTimeF32(), 0.f, 12); + UNSCOPED_INFO("runs on from zero"); + CHECK_THAT(timer.getElapsedTimeF32(), ll_test::WithinBits(0.f, 12)); timer.pause(); timer.start(); - ensure("started", timer.getStarted()); - ensure_approximately_equals("start() runs from zero, paused or not", timer.getElapsedTimeF32(), 0.f, 12); + CHECK(timer.getStarted()); + UNSCOPED_INFO("start() runs from zero, paused or not"); + CHECK_THAT(timer.getElapsedTimeF32(), ll_test::WithinBits(0.f, 12)); } } diff --git a/indra/llcommon/tests/llheteromap_test.cpp b/indra/llcommon/tests/llheteromap_test.cpp index 7d7a3f96f0..4953a326df 100644 --- a/indra/llcommon/tests/llheteromap_test.cpp +++ b/indra/llcommon/tests/llheteromap_test.cpp @@ -21,7 +21,7 @@ // (pacify clang) static std::ostream& operator<<(std::ostream& out, const std::set& strset); // other Linden headers -#include "../test/lltut.h" +#include static std::string clog; static std::set dlog; @@ -108,7 +108,7 @@ struct Chowdah /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct llheteromap_data { @@ -118,15 +118,9 @@ namespace tut dlog.clear(); } }; - typedef test_group llheteromap_group; - typedef llheteromap_group::object object; - llheteromap_group llheteromapgrp("llheteromap"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(llheteromap_data, "llheteromap: obtain creates each type once and the map destroys them all", "[llheteromap]") { - set_test_name("create, get, delete"); - { LLHeteroMap map; @@ -145,24 +139,24 @@ namespace tut { // verify each instance Chalk& chalk = map.obtain(); - ensure_equals(chalk.name, "Chalk"); + CHECK(chalk.name == "Chalk"); Cheese& cheese = map.obtain(); - ensure_equals(cheese.name, "Cheese"); + CHECK(cheese.name == "Cheese"); Chowdah& chowdah = map.obtain(); - ensure_equals(chowdah.name, "Chowdah"); + CHECK(chowdah.name == "Chowdah"); } } // destroy map // Chalk, Cheese and Chowdah should have been created in specific order - ensure_equals(clog, "aeo"); + CHECK(clog == "aeo"); // We don't care what order they're destroyed in, as long as each is // appropriately destroyed. std::set dtorset; for (const char* cp = "aeo"; *cp; ++cp) dtorset.insert(std::string(1, *cp)); - ensure_equals(dlog, dtorset); + CHECK(dlog == dtorset); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llhttpdate_test.cpp b/indra/llcommon/tests/llhttpdate_test.cpp index 023987f171..76c20afb7b 100644 --- a/indra/llcommon/tests/llhttpdate_test.cpp +++ b/indra/llcommon/tests/llhttpdate_test.cpp @@ -26,7 +26,8 @@ */ #include "linden_common.h" -#include "lltut.h" +#include +#include #include "lldate.h" #include "llcommon.h" @@ -35,7 +36,7 @@ #include #include -namespace tut +namespace { struct httpdate_data { @@ -47,30 +48,24 @@ namespace tut } LLDate some_date; }; - typedef test_group httpdate_test; - typedef httpdate_test::object httpdate_object; - tut::httpdate_test httpdate("httpdate"); - template<> template<> - void httpdate_object::test<1>() + TEST_CASE_METHOD(httpdate_data, "httpdate: asRFC1123 formats the epoch", "[httpdate]") { static std::string epoch_expected = "Thursday, 01 Jan 1970 00:00:00 GMT" ; - ensure("Check Epoch in RFC 1123", ( epoch_expected == some_date.asRFC1123())); + CHECK(epoch_expected == some_date.asRFC1123()); } - template<> template<> - void httpdate_object::test<2>() + TEST_CASE_METHOD(httpdate_data, "httpdate: asRFC1123 formats a timestamp", "[httpdate]") { static std::string expected = "Wednesday, 18 Jul 2007 22:17:24 GMT" ; some_date = LLDate(1184797044.037586); - ensure("Check some timestamp in RFC 1123", ( expected == some_date.asRFC1123())); + CHECK(expected == some_date.asRFC1123()); } // This test of course most generic.. runs off current time - template<> template<> - void httpdate_object::test<3>() + TEST_CASE_METHOD(httpdate_data, "httpdate: asRFC1123 of the current time matches strftime", "[httpdate]") { //F64 sometime = LLFrameTimer::getTotalSeconds(); @@ -96,41 +91,37 @@ namespace tut strftime(expected, 255, "%A, %d %b %Y %H:%M:%S GMT", result); actual = some_date.asRFC1123(); // probably not a good idea to use strcmp but this is just a unit test - ensure("Current time in RFC 1123", (strcmp(expected, actual.c_str()) == 0)); + CHECK(std::string_view(expected) == std::string_view(actual.c_str())); } - namespace + // LC_TIME, LLStringUtil's locale and the one LLDate last set LC_TIME + // to, put back as they were however the test ends. LLDate sets LC_TIME + // itself when the locale it saw last changes, so one date formatted in + // the restored locale brings it up to date before LC_TIME is restored. + struct TimeLocaleRestore { - // LC_TIME, LLStringUtil's locale and the one LLDate last set LC_TIME - // to, put back as they were however the test ends. LLDate sets LC_TIME - // itself when the locale it saw last changes, so one date formatted in - // the restored locale brings it up to date before LC_TIME is restored. - struct TimeLocaleRestore + std::string mLocale{ LLStringUtil::getLocale() }; + std::string mTimeLocale{ setlocale(LC_TIME, NULL) }; + + ~TimeLocaleRestore() { - std::string mLocale{ LLStringUtil::getLocale() }; - std::string mTimeLocale{ setlocale(LC_TIME, NULL) }; - - ~TimeLocaleRestore() - { - LLStringUtil::setLocale(mLocale); - struct tm any{}; - LLDate::toHTTPDateString(&any, "%Y"); - setlocale(LC_TIME, mTimeLocale.c_str()); - } - }; - } // anonymous namespace - - static void test_date_string(const std::string &locale, struct tm *t, - const std::string &fmt, const std::string &expected) + LLStringUtil::setLocale(mLocale); + struct tm any{}; + LLDate::toHTTPDateString(&any, "%Y"); + setlocale(LC_TIME, mTimeLocale.c_str()); + } + }; + + static void require_http_date_string(const std::string &locale, struct tm *t, + const std::string &fmt, const std::string &expected) { std::string result = LLDate::toHTTPDateString(t, fmt); LLStringUtil::toLower(result); - std::string label = std::string("toHTTPDateString - ") + locale; - ensure_equals(label.c_str(), result, expected); + CAPTURE(locale, fmt); + REQUIRE(result == expected); } - template<> template<> - void httpdate_object::test<4>() + TEST_CASE_METHOD(httpdate_data, "httpdate: toHTTPDateString formats in English and French locales", "[httpdate]") { // test localization of http dates const char *en_locale = "en_US.UTF-8"; @@ -143,34 +134,34 @@ namespace tut setlocale(LC_TIME, en_locale); if (strcmp(setlocale(LC_TIME, NULL), en_locale) != 0) { - skip("Cannot set English locale"); + SKIP("Cannot set English locale"); } LLStringUtil::setLocale(en_locale); - test_date_string(en_locale, t, "%d %B %Y - %H:%M", "08 september 2009 - 01:40"); - test_date_string(en_locale, t, "%H", "01"); - test_date_string(en_locale, t, "%M", "40"); - test_date_string(en_locale, t, "%I", "01"); - test_date_string(en_locale, t, "%d", "08"); - test_date_string(en_locale, t, "%Y", "2009"); - test_date_string(en_locale, t, "%p", "am"); - test_date_string(en_locale, t, "%A", "tuesday"); - test_date_string(en_locale, t, "%B", "september"); + require_http_date_string(en_locale, t, "%d %B %Y - %H:%M", "08 september 2009 - 01:40"); + require_http_date_string(en_locale, t, "%H", "01"); + require_http_date_string(en_locale, t, "%M", "40"); + require_http_date_string(en_locale, t, "%I", "01"); + require_http_date_string(en_locale, t, "%d", "08"); + require_http_date_string(en_locale, t, "%Y", "2009"); + require_http_date_string(en_locale, t, "%p", "am"); + require_http_date_string(en_locale, t, "%A", "tuesday"); + require_http_date_string(en_locale, t, "%B", "september"); setlocale(LC_TIME, fr_locale); if (strcmp(setlocale(LC_TIME, NULL), fr_locale) != 0) { - skip("Cannot set French locale"); + SKIP("Cannot set French locale"); } LLStringUtil::setLocale(fr_locale); - test_date_string(fr_locale, t, "%d %B %Y - %H:%M", "08 septembre 2009 - 01:40"); - test_date_string(fr_locale, t, "%H", "01"); - test_date_string(fr_locale, t, "%M", "40"); - test_date_string(fr_locale, t, "%I", "01"); - test_date_string(fr_locale, t, "%d", "08"); - test_date_string(fr_locale, t, "%Y", "2009"); - test_date_string(fr_locale, t, "%A", "mardi"); - test_date_string(fr_locale, t, "%B", "septembre"); + require_http_date_string(fr_locale, t, "%d %B %Y - %H:%M", "08 septembre 2009 - 01:40"); + require_http_date_string(fr_locale, t, "%H", "01"); + require_http_date_string(fr_locale, t, "%M", "40"); + require_http_date_string(fr_locale, t, "%I", "01"); + require_http_date_string(fr_locale, t, "%d", "08"); + require_http_date_string(fr_locale, t, "%Y", "2009"); + require_http_date_string(fr_locale, t, "%A", "mardi"); + require_http_date_string(fr_locale, t, "%B", "septembre"); } } diff --git a/indra/llcommon/tests/llinitparam_test.cpp b/indra/llcommon/tests/llinitparam_test.cpp index 0096b98fe3..41e2737fe3 100644 --- a/indra/llcommon/tests/llinitparam_test.cpp +++ b/indra/llcommon/tests/llinitparam_test.cpp @@ -34,7 +34,7 @@ #include "llsd.h" #include "llsdparam.h" -#include "../test/lltut.h" +#include // // Types under test. These live at namespace scope because a block's parameter @@ -129,10 +129,10 @@ struct ChoiceOfThings : public LLInitParam::ChoiceBlock {} }; -// The same, built only by test 9. A choice block starts with its first -// alternative chosen only when it is the first of its type, the one whose -// construction builds the type's descriptor: with a type of its own, test 9 -// sees that whatever ran before it. +// The same, built only by the case on choosing one alternative. A choice block +// starts with its first alternative chosen only when it is the first of its +// type, the one whose construction builds the type's descriptor: with a type of +// its own, that case sees that whatever ran before it. struct FirstChoiceOfThings : public LLInitParam::ChoiceBlock { Alternative number; @@ -235,132 +235,107 @@ struct PointChoice : public LLInitParam::ChoiceBlock } // anonymous namespace -namespace tut +namespace { - struct llinitparam_data - { - }; - typedef test_group llinitparam_group; - typedef llinitparam_group::object object; - llinitparam_group llinitparamgrp("llinitparam"); - - template<> template<> - void object::test<1>() + TEST_CASE("llinitparam: fresh block reports defaults and nothing provided", "[llinitparam]") { - set_test_name("fresh block reports defaults and nothing provided"); - SimpleBlock block; - ensure("nothing provided yet", !block.name.isProvided()); - ensure("nothing provided yet", !block.count.isProvided()); - ensure_equals("declared default survives", block.count(), 7); - ensure_equals("declared default survives", block.flag(), false); - ensure("block itself is not provided", !block.isProvided()); + CHECK_FALSE(block.name.isProvided()); + CHECK_FALSE(block.count.isProvided()); + CHECK(block.count() == 7); + CHECK_FALSE(block.flag()); + CHECK_FALSE(block.isProvided()); } - template<> template<> - void object::test<2>() + TEST_CASE("llinitparam: assignment marks a parameter provided", "[llinitparam]") { - set_test_name("assignment marks a parameter provided"); - SimpleBlock block; block.count = 42; - ensure("assignment provides", block.count.isProvided()); - ensure_equals("assignment stores", block.count(), 42); - ensure("sibling untouched", !block.flag.isProvided()); - ensure("block became provided", block.isProvided()); + CHECK(block.count.isProvided()); + CHECK(block.count() == 42); + CHECK_FALSE(block.flag.isProvided()); + CHECK(block.isProvided()); } - template<> template<> - void object::test<3>() + TEST_CASE("llinitparam: Mandatory gates validation and Optional does not", "[llinitparam]") { - set_test_name("Mandatory gates validation, Optional does not"); - SimpleBlock block; - ensure("missing mandatory fails validation", !block.validateBlock(false)); + UNSCOPED_INFO("missing mandatory fails validation"); + CHECK_FALSE(block.validateBlock(false)); block.name = std::string("widget"); - ensure("supplied mandatory passes validation", block.validateBlock(false)); + CHECK(block.validateBlock(false)); } - template<> template<> - void object::test<4>() + TEST_CASE("llinitparam: derived block sees its own and its base's parameters", "[llinitparam]") { - set_test_name("derived block sees its own and its base's parameters"); - DerivedBlock block; block.count = 3; // declared on SimpleBlock block.scale = 2.f; // declared on DerivedBlock - ensure_equals("base parameter readable", block.count(), 3); - ensure_equals("derived parameter readable", block.scale(), 2.f); + CHECK(block.count() == 3); + CHECK(block.scale() == 2.f); // The base's Mandatory is aggregated into the derived table, so it // still gates the derived block's validation. - ensure("inherited mandatory still required", !block.validateBlock(false)); + UNSCOPED_INFO("inherited mandatory still required"); + CHECK_FALSE(block.validateBlock(false)); block.name = std::string("derived"); - ensure("inherited mandatory satisfied", block.validateBlock(false)); + CHECK(block.validateBlock(false)); } - template<> template<> - void object::test<5>() + TEST_CASE("llinitparam: named values resolve by name and by number", "[llinitparam]") { - set_test_name("named values resolve by name and by number"); - S32 value = 0; - ensure("known name resolves", TestNames::getValueFromName("two", value)); - ensure_equals("known name resolves to its value", value, 2); - ensure("unknown name does not resolve", !TestNames::getValueFromName("nine", value)); + CHECK(TestNames::getValueFromName("two", value)); + CHECK(value == 2); + CHECK_FALSE(TestNames::getValueFromName("nine", value)); SimpleBlock block; block.named = std::string("one"); - ensure_equals("assignment by name sets the value", block.named(), 1); - ensure_equals("assignment by name records the name", block.named.getValueName(), std::string("one")); + CHECK(block.named() == 1); + CHECK(block.named.getValueName() == std::string("one")); block.named = 2; - ensure_equals("assignment by value sets the value", block.named(), 2); - ensure("assignment by value clears the name", block.named.getValueName().empty()); + CHECK(block.named() == 2); + CHECK(block.named.getValueName().empty()); } - template<> template<> - void object::test<6>() + TEST_CASE("llinitparam: Multiple counts elements and enforces its range", "[llinitparam]") { - set_test_name("Multiple counts elements and enforces its range"); - OuterBlock block; - ensure("empty list is below AtLeast<2>", !block.list.isValid()); - ensure_equals("empty list has no elements", block.list.size(), size_t(0)); + UNSCOPED_INFO("empty list is below AtLeast<2>"); + CHECK_FALSE(block.list.isValid()); + CHECK(block.list.size() == size_t(0)); block.list.add(10); - ensure("one element is still below AtLeast<2>", !block.list.isValid()); + UNSCOPED_INFO("one element is still below AtLeast<2>"); + CHECK_FALSE(block.list.isValid()); block.list.add(20); - ensure("two elements satisfy AtLeast<2>", block.list.isValid()); - ensure_equals("elements are kept in order", block.list()[0](), 10); - ensure_equals("elements are kept in order", block.list()[1](), 20); + UNSCOPED_INFO("two elements satisfy AtLeast<2>"); + REQUIRE(block.list.isValid()); + CHECK(block.list()[0]() == 10); + CHECK(block.list()[1]() == 20); } - template<> template<> - void object::test<7>() + TEST_CASE("llinitparam: a parameter that is a block is provided by its children", "[llinitparam]") { - set_test_name("a parameter that is a block is provided by its children"); - OuterBlock block; - ensure("untouched sub-block is not provided", !block.inner.isProvided()); + CHECK_FALSE(block.inner.isProvided()); block.inner.a = 5; - ensure("setting a child provides the sub-block", block.inner.isProvided()); - ensure("and provides the enclosing block", block.isProvided()); - ensure_equals("child value readable", block.inner.a(), 5); - ensure_equals("sibling child keeps its default", block.inner.b(), 0); + CHECK(block.inner.isProvided()); + CHECK(block.isProvided()); + CHECK(block.inner.a() == 5); + UNSCOPED_INFO("sibling child keeps its default"); + CHECK(block.inner.b() == 0); } - template<> template<> - void object::test<8>() + TEST_CASE("llinitparam: fillFrom yields to what is already provided and overwriteFrom does not", "[llinitparam]") { - set_test_name("fillFrom yields to what is already provided, overwriteFrom does not"); - SimpleBlock source; source.count = 100; source.flag = true; @@ -369,37 +344,33 @@ namespace tut destination.count = 1; destination.fillFrom(source); - ensure_equals("fillFrom leaves a provided value alone", destination.count(), 1); - ensure_equals("fillFrom supplies an absent value", destination.flag(), true); + CHECK(destination.count() == 1); + UNSCOPED_INFO("fillFrom supplies an absent value"); + CHECK(destination.flag()); destination.overwriteFrom(source); - ensure_equals("overwriteFrom replaces a provided value", destination.count(), 100); + CHECK(destination.count() == 100); } - template<> template<> - void object::test<9>() + TEST_CASE("llinitparam: choosing one alternative unprovides the other", "[llinitparam]") { - set_test_name("choosing one alternative unprovides the other"); - FirstChoiceOfThings choice; - ensure("first declared alternative is chosen initially", choice.number.isChosen()); + UNSCOPED_INFO("first declared alternative is chosen initially"); + CHECK(choice.number.isChosen()); choice.text = std::string("hello"); - ensure("assignment chooses", choice.text.isChosen()); - ensure("and unchooses the previous alternative", !choice.number.isChosen()); - ensure("the displaced alternative is no longer provided", !choice.number.isProvided()); - ensure_equals("the chosen alternative reads back", choice.text(), std::string("hello")); + CHECK(choice.text.isChosen()); + CHECK_FALSE(choice.number.isChosen()); + CHECK_FALSE(choice.number.isProvided()); + CHECK(choice.text() == std::string("hello")); choice.number = 3; - ensure("choosing back works", choice.number.isChosen()); - ensure("and displaces the other", !choice.text.isProvided()); + CHECK(choice.number.isChosen()); + CHECK_FALSE(choice.text.isProvided()); } - template<> template<> - void object::test<10>() + TEST_CASE("llinitparam: scalars survive an LLSD round-trip", "[llinitparam]") { - set_test_name("scalars survive an LLSD round-trip"); - SimpleBlock written; written.name = std::string("round"); written.count = 55; @@ -409,24 +380,21 @@ namespace tut LLParamSDParser parser; parser.writeSD(sd, written); - ensure("provided scalars are written", sd.has("count")); - ensure_equals("written value is the one set", sd["count"].asInteger(), 55); + REQUIRE(sd.has("count")); + CHECK(sd["count"].asInteger() == 55); SimpleBlock read; LLParamSDParser reader; reader.readSD(sd, read); - ensure_equals("string survives", read.name(), std::string("round")); - ensure_equals("integer survives", read.count(), 55); - ensure_equals("boolean survives", read.flag(), true); - ensure("reading marks parameters provided", read.count.isProvided()); + CHECK(read.name() == std::string("round")); + CHECK(read.count() == 55); + CHECK(read.flag()); + CHECK(read.count.isProvided()); } - template<> template<> - void object::test<11>() + TEST_CASE("llinitparam: nested blocks and lists survive an LLSD round-trip", "[llinitparam]") { - set_test_name("nested blocks and lists survive an LLSD round-trip"); - OuterBlock written; written.inner.a = 11; written.inner.b = 22; @@ -438,24 +406,22 @@ namespace tut LLParamSDParser parser; parser.writeSD(sd, written); - ensure("sub-block is written under its own name", sd.has("inner")); - ensure_equals("sub-block child is written", sd["inner"]["a"].asInteger(), 11); + REQUIRE(sd.has("inner")); + CHECK(sd["inner"]["a"].asInteger() == 11); OuterBlock read; LLParamSDParser reader; reader.readSD(sd, read); - ensure_equals("nested value survives", read.inner.a(), 11); - ensure_equals("nested value survives", read.inner.b(), 22); - ensure_equals("list length survives", read.list.size(), size_t(3)); - ensure_equals("list order survives", read.list()[2](), 3); + CHECK(read.inner.a() == 11); + CHECK(read.inner.b() == 22); + REQUIRE(read.list.size() == size_t(3)); + UNSCOPED_INFO("list order survives"); + CHECK(read.list()[2]() == 3); } - template<> template<> - void object::test<12>() + TEST_CASE("llinitparam: a named value round-trips as its name and not its number", "[llinitparam]") { - set_test_name("a named value round-trips as its name, not its number"); - SimpleBlock written; written.named = std::string("two"); @@ -463,21 +429,18 @@ namespace tut LLParamSDParser parser; parser.writeSD(sd, written); - ensure_equals("the name is what gets written", sd["named"].asString(), std::string("two")); + CHECK(sd["named"].asString() == std::string("two")); SimpleBlock read; LLParamSDParser reader; reader.readSD(sd, read); - ensure_equals("the name resolves back to its value", read.named(), 2); - ensure_equals("and the name is remembered", read.named.getValueName(), std::string("two")); + CHECK(read.named() == 2); + CHECK(read.named.getValueName() == std::string("two")); } - template<> template<> - void object::test<13>() + TEST_CASE("llinitparam: a synonym reaches the same parameter", "[llinitparam]") { - set_test_name("a synonym reaches the same parameter"); - LLSD sd; sd["w"] = 64; @@ -485,15 +448,12 @@ namespace tut LLParamSDParser parser; parser.readSD(sd, block); - ensure("the synonym provided the parameter", block.width.isProvided()); - ensure_equals("through to the same storage", block.width(), 64); + CHECK(block.width.isProvided()); + CHECK(block.width() == 64); } - template<> template<> - void object::test<14>() + TEST_CASE("llinitparam: an Ignored parameter is accepted and discarded", "[llinitparam]") { - set_test_name("an Ignored parameter is accepted and discarded"); - LLSD sd; sd["kept"] = 5; sd["gone"] = 6; @@ -502,33 +462,29 @@ namespace tut LLParamSDParser parser; parser.readSD(sd, block); - ensure_equals("the live parameter is read", block.kept(), 5); - ensure("the block is valid despite the ignored key", block.validateBlock(false)); + CHECK(block.kept() == 5); + UNSCOPED_INFO("the block is valid despite the ignored key"); + CHECK(block.validateBlock(false)); } - template<> template<> - void object::test<15>() + TEST_CASE("llinitparam: a custom value can be set whole or by component", "[llinitparam]") { - set_test_name("a custom value can be set whole or by component"); - PointBlock whole; whole.point.set(TestPoint(3, 4)); - ensure_equals("whole assignment reaches the value", whole.point().mX, 3); - ensure_equals("whole assignment reaches the value", whole.point().mY, 4); - ensure_equals("and splats out to the components", whole.point.x(), 3); + CHECK(whole.point().mX == 3); + CHECK(whole.point().mY == 4); + UNSCOPED_INFO("whole assignment splats out to the components"); + CHECK(whole.point.x() == 3); PointBlock components; components.point.x = 8; components.point.y = 9; - ensure_equals("component assignment builds the value", components.point().mX, 8); - ensure_equals("component assignment builds the value", components.point().mY, 9); + CHECK(components.point().mX == 8); + CHECK(components.point().mY == 9); } - template<> template<> - void object::test<16>() + TEST_CASE("llinitparam: a custom value round-trips through its components", "[llinitparam]") { - set_test_name("a custom value round-trips through its components"); - PointBlock written; written.point.x = 12; written.point.y = 34; @@ -541,24 +497,21 @@ namespace tut LLParamSDParser reader; reader.readSD(sd, read); - ensure_equals("x survives", read.point().mX, 12); - ensure_equals("y survives", read.point().mY, 34); + CHECK(read.point().mX == 12); + CHECK(read.point().mY == 34); } - template<> template<> - void object::test<17>() + TEST_CASE("llinitparam: a Lazy sub-block stays absent until something fills it", "[llinitparam]") { - set_test_name("a Lazy sub-block stays absent until something fills it"); - // Lazy<> keeps its block on the heap rather than deriving from it, so // an untouched one costs a pointer and serialises to nothing at all. OuterBlock untouched; - ensure("lazy sub-block starts unprovided", !untouched.lazy_inner.isProvided()); + CHECK_FALSE(untouched.lazy_inner.isProvided()); LLSD empty_sd; LLParamSDParser empty_parser; empty_parser.writeSD(empty_sd, untouched); - ensure("an unfilled lazy block writes nothing", !empty_sd.has("lazy_inner")); + CHECK_FALSE(empty_sd.has("lazy_inner")); LLSD sd; sd["lazy_inner"]["a"] = 77; @@ -567,63 +520,30 @@ namespace tut LLParamSDParser parser; parser.readSD(sd, filled); - ensure("parsing into it provides it", filled.lazy_inner.isProvided()); - ensure_equals("and the value is reachable", filled.lazy_inner().a(), 77); + CHECK(filled.lazy_inner.isProvided()); + CHECK(filled.lazy_inner().a() == 77); } - template<> template<> - void object::test<18>() + TEST_CASE("llinitparam: copying a block carries values and provided-ness", "[llinitparam]") { - set_test_name("copying a block carries values and provided-ness"); - SimpleBlock original; original.name = std::string("copy me"); original.count = 99; SimpleBlock copy(original); - ensure_equals("values copy", copy.count(), 99); - ensure_equals("values copy", copy.name(), std::string("copy me")); - ensure("provided-ness copies", copy.count.isProvided()); - ensure("unprovided stays unprovided", !copy.flag.isProvided()); + CHECK(copy.count() == 99); + CHECK(copy.name() == std::string("copy me")); + CHECK(copy.count.isProvided()); + CHECK_FALSE(copy.flag.isProvided()); // The copy must own its storage: changing it must not disturb the original. copy.count = 1; - ensure_equals("the original is untouched", original.count(), 99); + CHECK(original.count() == 99); } - template<> template<> - void object::test<20>() + TEST_CASE("llinitparam: the descriptor registry sees every block that has been built", "[llinitparam]") { - set_test_name("merging reports whether it actually took anything"); - - // The return value is what tells a caller its block changed. A - // Multiple<> used to answer yes unconditionally, so any block holding - // one looked modified by a merge that moved nothing. - OuterBlock empty_source; - OuterBlock destination; - ensure("filling from an empty block changes nothing", - !destination.fillFrom(empty_source)); - ensure_equals("and leaves the list alone", destination.list.size(), size_t(0)); - - OuterBlock source; - source.list.add(1); - source.list.add(2); - ensure("filling from a block with values does change it", - destination.fillFrom(source)); - ensure_equals("which arrive in the destination", destination.list.size(), size_t(2)); - - // Already-filled destination, still-empty source. - ensure("an empty source still changes nothing", - !destination.fillFrom(empty_source)); - ensure_equals("and does not disturb what is there", destination.list.size(), size_t(2)); - } - - template<> template<> - void object::test<19>() - { - set_test_name("the descriptor registry sees every block that has been built"); - // Constructing a block is what registers its parameters, so touch one // of each kind declared above before counting. SimpleBlock simple; @@ -635,32 +555,50 @@ namespace tut const std::vector& all = LLInitParam::BlockDescriptor::getAllDescriptors(); - ensure("every constructed block type registered", all.size() >= 5); + UNSCOPED_INFO("every constructed block type registered"); + CHECK(all.size() >= 5); size_t named_entries = 0; for (const LLInitParam::BlockDescriptor* descriptor : all) { named_entries += descriptor->namedParams().size(); } - ensure("the tables hold the parameters we declared", named_entries >= 12); + UNSCOPED_INFO("the tables hold the parameters we declared"); + CHECK(named_entries >= 12); // DerivedBlock aggregates SimpleBlock's four parameters on top of its // own, which is the duplication the census exists to measure. - ensure_equals("a derived block carries its base's parameters too", - DerivedBlock::getBlockDescriptor().namedParams().size(), - SimpleBlock::getBlockDescriptor().namedParams().size() + 1); + CHECK(DerivedBlock::getBlockDescriptor().namedParams().size() == + SimpleBlock::getBlockDescriptor().namedParams().size() + 1); const std::string report = LLInitParam::BlockDescriptor::getStatsReport(); - ensure("the census reports something", !report.empty()); - ensure("the census names what it counted", - report.find("block types registered") != std::string::npos); + CHECK_FALSE(report.empty()); + CHECK(report.find("block types registered") != std::string::npos); } - template<> template<> - void object::test<21>() + TEST_CASE("llinitparam: merging reports whether it actually took anything", "[llinitparam]") { - set_test_name("a value filling in its own components is not a choice"); + // The return value is what tells a caller its block changed. A + // Multiple<> used to answer yes unconditionally, so any block holding + // one looked modified by a merge that moved nothing. + OuterBlock empty_source; + OuterBlock destination; + CHECK_FALSE(destination.fillFrom(empty_source)); + CHECK(destination.list.size() == size_t(0)); + + OuterBlock source; + source.list.add(1); + source.list.add(2); + CHECK(destination.fillFrom(source)); + CHECK(destination.list.size() == size_t(2)); + // Already-filled destination, still-empty source. + CHECK_FALSE(destination.fillFrom(empty_source)); + CHECK(destination.list.size() == size_t(2)); + } + + TEST_CASE("llinitparam: a value filling in its own components is not a choice", "[llinitparam]") + { // TestPoint fills x and y from its value as it is built. That must // read as the block keeping itself consistent, not as someone // supplying a point. @@ -674,33 +612,27 @@ namespace tut PointChoice choice; - ensure("nothing has been chosen yet", !choice.point.isChosen()); - ensure("nothing has been chosen yet", !choice.scalar.isChosen()); + CHECK_FALSE(choice.point.isChosen()); + CHECK_FALSE(choice.scalar.isChosen()); } - template<> template<> - void object::test<22>() + TEST_CASE("llinitparam: providing an alternative still chooses it", "[llinitparam]") { - set_test_name("providing an alternative still chooses it"); - PointChoice choice; choice.scalar = 5; - ensure("the provided alternative is the choice", choice.scalar.isChosen()); - ensure_equals("and reads back", choice.scalar(), 5); + CHECK(choice.scalar.isChosen()); + CHECK(choice.scalar() == 5); // Reaching into the components of the other alternative counts as // providing it, the same way assigning the whole value would. choice.point.x = 3; - ensure("the later alternative takes over", choice.point.isChosen()); - ensure("and the earlier one steps down", !choice.scalar.isChosen()); - ensure_equals("the component drives the value", choice.point().mX, 3); + CHECK(choice.point.isChosen()); + CHECK_FALSE(choice.scalar.isChosen()); + CHECK(choice.point().mX == 3); } - template<> template<> - void object::test<23>() + TEST_CASE("llinitparam: merging does not hand the choice to a value with components", "[llinitparam]") { - set_test_name("merging does not hand the choice to a value with components"); - // A choice merges as a unit: the destination takes the source's // choice, and an alternative the source never provided is passed over // rather than visited -- which matters here because visiting the point @@ -712,8 +644,8 @@ namespace tut PointChoice destination; destination.fillFrom(source); - ensure("the source's choice carried over", destination.scalar.isChosen()); - ensure("the untouched alternative did not seize it", !destination.point.isChosen()); - ensure_equals("and the value came with it", destination.scalar(), 5); + CHECK(destination.scalar.isChosen()); + CHECK_FALSE(destination.point.isChosen()); + CHECK(destination.scalar() == 5); } } diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index ae315e45c4..d3207614b4 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -38,7 +38,7 @@ #include // std headers // other Linden headers -#include "../test/lltut.h" +#include namespace { @@ -75,66 +75,59 @@ struct Unkeyed: public LLInstanceTracker } // anonymous namespace /***************************************************************************** -* TUT +* Tests *****************************************************************************/ -namespace tut +namespace { - struct llinstancetracker_data - { - }; - typedef test_group llinstancetracker_group; - typedef llinstancetracker_group::object object; - llinstancetracker_group llinstancetrackergrp("llinstancetracker"); - - template<> template<> - void object::test<1>() + TEST_CASE("llinstancetracker: Keyed instances are found by key and forgotten when destroyed", "[llinstancetracker]") { - ensure_equals(Keyed::instanceCount(), 0); + CHECK(Keyed::instanceCount() == 0); { Keyed one("one"); - ensure_equals(Keyed::instanceCount(), 1); + CHECK(Keyed::instanceCount() == 1); auto found = Keyed::getInstance("one"); - ensure("couldn't find stack Keyed", bool(found)); - ensure_equals("found wrong Keyed instance", found.get(), &one); + UNSCOPED_INFO("couldn't find stack Keyed"); + CHECK(bool(found)); + CHECK(found.get() == &one); { std::unique_ptr two(new Keyed("two")); - ensure_equals(Keyed::instanceCount(), 2); + CHECK(Keyed::instanceCount() == 2); auto found = Keyed::getInstance("two"); - ensure("couldn't find heap Keyed", bool(found)); - ensure_equals("found wrong Keyed instance", found.get(), two.get()); + UNSCOPED_INFO("couldn't find heap Keyed"); + CHECK(bool(found)); + CHECK(found.get() == two.get()); } - ensure_equals(Keyed::instanceCount(), 1); + CHECK(Keyed::instanceCount() == 1); } auto found = Keyed::getInstance("one"); - ensure("Keyed key lives too long", ! found); - ensure_equals(Keyed::instanceCount(), 0); + UNSCOPED_INFO("Keyed key lives too long"); + CHECK_FALSE(found); + CHECK(Keyed::instanceCount() == 0); } - template<> template<> - void object::test<2>() + TEST_CASE("llinstancetracker: Unkeyed instances are counted and their weak pointers expire", "[llinstancetracker]") { - ensure_equals(Unkeyed::instanceCount(), 0); + CHECK(Unkeyed::instanceCount() == 0); std::weak_ptr dangling; { Unkeyed one; - ensure_equals(Unkeyed::instanceCount(), 1); + CHECK(Unkeyed::instanceCount() == 1); std::weak_ptr found = one.getWeak(); - ensure(! found.expired()); + CHECK_FALSE(found.expired()); { std::unique_ptr two(new Unkeyed); - ensure_equals(Unkeyed::instanceCount(), 2); + CHECK(Unkeyed::instanceCount() == 2); } - ensure_equals(Unkeyed::instanceCount(), 1); + CHECK(Unkeyed::instanceCount() == 1); // store a weak pointer to a temp Unkeyed instance dangling = found; } // make that instance vanish // check the now-invalid pointer to the destroyed instance - ensure("weak_ptr failed to track destruction", dangling.expired()); - ensure_equals(Unkeyed::instanceCount(), 0); + CHECK(dangling.expired()); + CHECK(Unkeyed::instanceCount() == 0); } - template<> template<> - void object::test<3>() + TEST_CASE("llinstancetracker: key_snapshot and instance_snapshot list each Keyed once", "[llinstancetracker]") { Keyed one("one"), two("two"), three("three"); // We don't want to rely on the underlying container delivering keys @@ -146,12 +139,13 @@ namespace tut StringVector keys(snap.begin(), snap.end()); std::sort(keys.begin(), keys.end()); StringVector::const_iterator ki(keys.begin()); - ensure_equals(*ki++, "one"); - ensure_equals(*ki++, "three"); - ensure_equals(*ki++, "two"); + CHECK(*ki++ == "one"); + CHECK(*ki++ == "three"); + CHECK(*ki++ == "two"); // Use ensure() here because ensure_equals would want to display // mismatched values, and frankly that wouldn't help much. - ensure("didn't reach end", ki == keys.end()); + UNSCOPED_INFO("didn't reach end"); + CHECK(ki == keys.end()); // Use a somewhat different approach to order independence with // instance_snapshot(): explicitly capture the instances we know in a @@ -163,13 +157,14 @@ namespace tut instances.insert(&three); for (auto& ref : Keyed::instance_snapshot()) { - ensure_equals("spurious instance", instances.erase(&ref), 1); + UNSCOPED_INFO("spurious instance"); + REQUIRE(instances.erase(&ref) == 1); } - ensure_equals("unreported instance", instances.size(), 0); + UNSCOPED_INFO("unreported instance"); + CHECK(instances.size() == 0); } - template<> template<> - void object::test<4>() + TEST_CASE("llinstancetracker: instance_snapshot lists each Unkeyed once", "[llinstancetracker]") { Unkeyed one, two, three; typedef std::set KeySet; @@ -181,17 +176,17 @@ namespace tut for (auto& ref : Unkeyed::instance_snapshot()) { - ensure_equals("spurious instance", instances.erase(&ref), 1); + UNSCOPED_INFO("spurious instance"); + REQUIRE(instances.erase(&ref) == 1); } - ensure_equals("unreported instance", instances.size(), 0); + UNSCOPED_INFO("unreported instance"); + CHECK(instances.size() == 0); } - template<> template<> - void object::test<5>() + TEST_CASE("llinstancetracker: delete Keyed with outstanding instance_snapshot", "[llinstancetracker]") { std::string desc("delete Keyed with outstanding instance_snapshot"); - set_test_name(desc); Keyed* keyed = new Keyed(desc); // capture a snapshot but do not yet traverse it auto snapshot = Keyed::instance_snapshot(); @@ -200,14 +195,12 @@ namespace tut // traversing the snapshot should reflect the deletion // avoid ensure_equals() because it requires the ability to stream the // two values to std::ostream - ensure(snapshot.begin() == snapshot.end()); + CHECK(snapshot.begin() == snapshot.end()); } - template<> template<> - void object::test<6>() + TEST_CASE("llinstancetracker: delete Keyed with outstanding key_snapshot", "[llinstancetracker]") { std::string desc("delete Keyed with outstanding key_snapshot"); - set_test_name(desc); Keyed* keyed = new Keyed(desc); // capture a snapshot but do not yet traverse it auto snapshot = Keyed::key_snapshot(); @@ -216,13 +209,11 @@ namespace tut // traversing the snapshot should reflect the deletion // avoid ensure_equals() because it requires the ability to stream the // two values to std::ostream - ensure(snapshot.begin() == snapshot.end()); + CHECK(snapshot.begin() == snapshot.end()); } - template<> template<> - void object::test<7>() + TEST_CASE("llinstancetracker: delete Unkeyed with outstanding instance_snapshot", "[llinstancetracker]") { - set_test_name("delete Unkeyed with outstanding instance_snapshot"); std::string what; Unkeyed* unkeyed = new Unkeyed; // capture a snapshot but do not yet traverse it @@ -232,13 +223,11 @@ namespace tut // traversing the snapshot should reflect the deletion // avoid ensure_equals() because it requires the ability to stream the // two values to std::ostream - ensure(snapshot.begin() == snapshot.end()); + CHECK(snapshot.begin() == snapshot.end()); } - template<> template<> - void object::test<8>() + TEST_CASE("llinstancetracker: a throwing subclass constructor leaves no instance behind", "[llinstancetracker]") { - set_test_name("exception in subclass ctor"); typedef std::set InstanceSet; InstanceSet existing; // We can't use the iterator-range InstanceSet constructor because @@ -271,7 +260,8 @@ namespace tut // its constructor exception. for (auto& ref : Unkeyed::instance_snapshot()) { - ensure("failed to remove instance", existing.find(&ref) != existing.end()); + UNSCOPED_INFO("failed to remove instance"); + REQUIRE(existing.find(&ref) != existing.end()); } } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index d833e6185f..394f4e4efc 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -18,9 +18,12 @@ #include // external library headers // other Linden headers -#include "../test/lltut.h" +#include +#include +#include "altestname.h" #include "../test/namedtempfile.h" #include "../test/catch_and_store_what_in.h" +#include "altestfailure.h" #include "wrapllerrs.h" // CaptureLog #include "llevents.h" #include "llprocess.h" @@ -75,10 +78,9 @@ static void waitfor(const LLLeapVector& instances, int timeout=60) sleep(1); LLEventPumps::instance().obtain("mainloop").post(LLSD()); } - tut::ensure(STRINGIZE("at least 1 of " << instances.size() - << " LLLeap instances timed out (" - << timeout << " seconds) without terminating"), - i < timeout); + INFO("at least 1 of " << instances.size() + << " LLLeap instances timed out (" << timeout << " seconds) without terminating"); + REQUIRE(i < timeout); } static void waitfor(LLLeap* instance, int timeout=60) @@ -193,27 +195,23 @@ namespace tut reader.getPath().string().substr(0, reader.getPath().string().length()-3))), PYTHON(LLStringUtil::getenv("PYTHON")) { - ensure("Set PYTHON to interpreter pathname", !PYTHON.empty()); + UNSCOPED_INFO("Set PYTHON to interpreter pathname"); + REQUIRE_FALSE(PYTHON.empty()); } NamedExtTempFile reader; const std::string reader_module; const std::string PYTHON; }; - typedef test_group llleap_group; - typedef llleap_group::object object; - llleap_group llleapgrp("llleap"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(llleap_data, "llleap: two LLLeap instances coexist and both terminate", "[llleap]") { - set_test_name("multiple LLLeap instances"); NamedExtTempFile script("py", "import time\n" "time.sleep(1)\n"); LLLeapVector instances; - instances.push_back(LLLeap::create(get_test_name(), + instances.push_back(LLLeap::create(ll_test::currentTestName(), StringVec{PYTHON, script.getPath().string()})->getWeak()); - instances.push_back(LLLeap::create(get_test_name(), + instances.push_back(LLLeap::create(ll_test::currentTestName(), StringVec{PYTHON, script.getPath().string()})->getWeak()); // In this case we're simply establishing that two LLLeap instances // can coexist without throwing exceptions or bombing in any other @@ -221,90 +219,76 @@ namespace tut waitfor(instances); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(llleap_data, "llleap: a plugin's stderr lines go to the log", "[llleap]") { - set_test_name("stderr to log"); NamedExtTempFile script("py", "import sys\n" "sys.stderr.write('''Hello from Python!\n" "note partial line''')\n"); StringVec vcommand{ PYTHON, script.getPath().string() }; CaptureLog log(LLError::LEVEL_INFO); - waitfor(LLLeap::create(get_test_name(), vcommand)); + waitfor(LLLeap::create(ll_test::currentTestName(), vcommand)); log.messageWith("Hello from Python!"); log.messageWith("note partial line"); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(llleap_data, "llleap: stdout without a length prefix is logged as bad protocol", "[llleap]") { - set_test_name("bad stdout protocol"); NamedExtTempFile script("py", "print('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); - waitfor(LLLeap::create(get_test_name(), + waitfor(LLLeap::create(ll_test::currentTestName(), StringVec{PYTHON, script.getPath().string()})); - ensure_contains("error log line", - log.messageWith("invalid protocol"), "Hello from Python!"); + CHECK_THAT(log.messageWith("invalid protocol"), Catch::Matchers::ContainsSubstring("Hello from Python!")); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(llleap_data, "llleap: partial stdout left at exit is logged", "[llleap]") { - set_test_name("leftover stdout"); NamedExtTempFile script("py", "import sys\n" // note lack of newline "sys.stdout.write('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); - waitfor(LLLeap::create(get_test_name(), + waitfor(LLLeap::create(ll_test::currentTestName(), StringVec{PYTHON, script.getPath().string()})); - ensure_contains("error log line", - log.messageWith("Discarding"), "Hello from Python!"); + CHECK_THAT(log.messageWith("Discarding"), Catch::Matchers::ContainsSubstring("Hello from Python!")); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(llleap_data, "llleap: a non-numeric length prefix is logged", "[llleap]") { - set_test_name("bad stdout len prefix"); NamedExtTempFile script("py", "import sys\n" "sys.stdout.write('5a2:something')\n"); CaptureLog log(LLError::LEVEL_WARN); - waitfor(LLLeap::create(get_test_name(), + waitfor(LLLeap::create(ll_test::currentTestName(), StringVec{PYTHON, script.getPath().string()})); - ensure_contains("error log line", - log.messageWith("invalid protocol"), "5a2:"); + CHECK_THAT(log.messageWith("invalid protocol"), Catch::Matchers::ContainsSubstring("5a2:")); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(llleap_data, "llleap: create with an empty command throws or returns null", "[llleap]") { - set_test_name("empty plugin vector"); std::string threw = catch_what([](){ LLLeap::create("empty", StringVec()); }); - ensure_contains("LLLeap::Error", threw, "no plugin"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("no plugin")); // try the suppress-exception variant - ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false)); + CHECK_FALSE(LLLeap::create("empty", StringVec(), false)); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(llleap_data, "llleap: create with a missing executable throws or returns null", "[llleap]") { - set_test_name("bad launch"); // Synthesize bogus executable name std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x"); CaptureLog log; std::string threw = catch_what([&BADPYTHON](){ LLLeap::create("bad exe", BADPYTHON); }); - ensure_contains("LLLeap::create() didn't throw", threw, "failed"); + UNSCOPED_INFO("LLLeap::create() didn't throw"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("failed")); log.messageWith("failed"); log.messageWith(BADPYTHON); // try the suppress-exception variant - ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false)); + CHECK_FALSE(LLLeap::create("bad exe", BADPYTHON, false)); } namespace @@ -361,21 +345,21 @@ namespace tut return false; } - void ensure() const + void require_success() const { - tut::ensure(std::string("never posted to ") + getName(), mData.isDefined()); + UNSCOPED_INFO("never posted to " << getName()); + REQUIRE(mData.isDefined()); // Post an empty string for success, non-empty string is failure message. - tut::ensure(mData, mData.asString().empty()); + UNSCOPED_INFO(mData); + REQUIRE(mData.asString().empty()); } LLSD mData; }; } // anonymous namespace - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(llleap_data, "llleap: a plugin's request gets the API's reply", "[llleap]") { - set_test_name("round trip"); AckAPI api; Result result; NamedExtTempFile script("py", @@ -388,9 +372,9 @@ namespace tut "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" " else 'bad: ' + str(resp)\n" "send(pump='" << result.getName() << "', data=result)\n";}); - waitfor(LLLeap::create(get_test_name(), + waitfor(LLLeap::create(ll_test::currentTestName(), StringVec{PYTHON, script.getPath().string()})); - result.ensure(); + result.require_success(); } namespace @@ -408,10 +392,8 @@ namespace tut }; } // anonymous namespace - template<> template<> - void object::test<9>() + TEST_CASE_METHOD(llleap_data, "llleap: many small requests get their replies in order", "[llleap]") { - set_test_name("many small messages"); // It's not clear to me whether there's value in iterating many times // over a send/receive loop -- I don't think that will exercise any // interesting corner cases. This test first sends a large number of @@ -451,13 +433,13 @@ namespace tut " result = 'expected reqid=%s in %s' % (i, resp)\n" " break\n" "send(pump='" << result.getName() << "', data=result)\n";}); - waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getPath().string()}), + waitfor(LLLeap::create(ll_test::currentTestName(), StringVec{PYTHON, script.getPath().string()}), 300); // needs more realtime than most tests - result.ensure(); + result.require_success(); } - // This is the body of test<10>, extracted so we can run it over a number - // of large-message sizes. + // This is the body of the very large message test, extracted so we can + // run it over a number of large-message sizes. static void test_large_message(const std::string& PYTHON, const std::string& reader_module, const std::string& test_name, size_t size) { @@ -520,7 +502,7 @@ namespace tut waitfor(LLLeap::create(test_name, StringVec{PYTHON, script.getPath().string(), stringize(size)}), 180); // try a longer timeout - result.ensure(); + result.require_success(); } namespace @@ -575,9 +557,9 @@ namespace tut std::cout << "test_large_message(" << size << ") succeeded" << std::endl; return success; } - catch (const failure& e) + catch (const ll_test::Failure&) { - std::cout << "test_large_message(" << size << ") failed: " << e.what() << std::endl; + std::cout << "test_large_message(" << size << ") failed" << std::endl; return ! success; } } @@ -598,9 +580,9 @@ namespace tut { test_large_message(PYTHON, reader_module, test_name, size); } - catch (const failure& e) + catch (const ll_test::Failure&) { - std::cout << "test_large_message(" << size << ") failed: " << e.what() << std::endl; + std::cout << "test_large_message(" << size << ") failed" << std::endl; // If it still fails below 4K, give up: subdividing any further is // pointless. if (size >= 4096) @@ -646,7 +628,7 @@ namespace tut << "that succeeds" << std::endl; } } - catch (const failure&) + catch (const ll_test::Failure&) { // The recursive test_or_split() call above has already // handled the exception. We don't want our caller to see @@ -656,14 +638,12 @@ namespace tut // In any case, because we reached here through failure of // our original test_large_message(size) call, ensure failure // propagates. - throw e; + throw; } } - template<> template<> - void object::test<10>() + TEST_CASE_METHOD(llleap_data, "llleap: a very large message echoes back intact", "[llleap]") { - set_test_name("very large message"); - test_or_split(PYTHON, reader_module, get_test_name(), BUFFERED_LENGTH); + test_or_split(PYTHON, reader_module, ll_test::currentTestName(), BUFFERED_LENGTH); } } // namespace tut diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp index a4fab4ba2f..b0b09e1f66 100644 --- a/indra/llcommon/tests/llmainthreadtask_test.cpp +++ b/indra/llcommon/tests/llmainthreadtask_test.cpp @@ -18,7 +18,7 @@ #include // external library headers // other Linden headers -#include "../test/lltut.h" +#include #include "../test/sync.h" #include "llthread.h" // on_main_thread() #include "lleventtimer.h" @@ -27,7 +27,7 @@ /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct llmainthreadtask_data { @@ -41,47 +41,37 @@ namespace tut on_main_thread(); } }; - typedef test_group llmainthreadtask_group; - typedef llmainthreadtask_group::object object; - llmainthreadtask_group llmainthreadtaskgrp("llmainthreadtask"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(llmainthreadtask_data, "llmainthreadtask: dispatch on the main thread runs the task inline", "[llmainthreadtask]") { - set_test_name("inline"); bool ran = false; bool result = LLMainThreadTask::dispatch( [&ran]()->bool{ ran = true; return true; }); - ensure("didn't run lambda", ran); - ensure("didn't return result", result); + CHECK(ran); + CHECK(result); } - namespace + // LockStatic finds its one instance a process by the type's name, even + // in an anonymous namespace, so the name is this test's own. + struct MainThreadTaskStatic { - // LockStatic finds its one instance a process by the type's name, even - // in an anonymous namespace, so the name is this test's own. - struct MainThreadTaskStatic - { - std::mutex mMutex; // LockStatic looks for mMutex - bool ran{false}; - }; - typedef llthread::LockStatic LockStatic; - } // anonymous namespace + std::mutex mMutex; // LockStatic looks for mMutex + bool ran{false}; + }; + typedef llthread::LockStatic LockStatic; - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(llmainthreadtask_data, "llmainthreadtask: dispatch from another thread runs the task on the main thread", "[llmainthreadtask]") { - set_test_name("cross-thread"); - skip("This test is prone to build-time hangs"); + SKIP("This test is prone to build-time hangs"); std::atomic_bool result(false); // wrapping our thread lambda in a packaged_task will catch any // exceptions it might throw and deliver them via future std::packaged_task thread_work( [this, &result](){ - // unblock test<2>()'s yield_until(1) + // unblock the test's yield_until(1) mSync.set(1); // dispatch work to main thread -- should block here bool on_main( @@ -92,7 +82,7 @@ namespace tut // indicate whether task was run on the main thread return on_main_thread(); })); - // wait for test<2>() to unblock us again + // wait for the test to unblock us again mSync.yield_until(3); result = on_main; }); @@ -107,8 +97,10 @@ namespace tut // try to acquire the lock, should block because thread has it LockStatic lk; // wake up when dispatch() unlocks the static mutex - ensure("shouldn't have run yet", !lk->ran); - ensure("shouldn't have returned yet", !result); + UNSCOPED_INFO("shouldn't have run yet"); + CHECK_FALSE(lk->ran); + UNSCOPED_INFO("shouldn't have returned yet"); + CHECK_FALSE(result); // unlock so the task can acquire the lock lk.unlock(); // run the task -- should unblock thread, which will immediately block @@ -116,8 +108,10 @@ namespace tut LLEventTimer::updateClass(); // 'lk', having unlocked, can no longer be used to access; relock with // a new LockStatic instance - ensure("should now have run", LockStatic()->ran); - ensure("returned too early", !result); + UNSCOPED_INFO("should now have run"); + CHECK(LockStatic()->ran); + UNSCOPED_INFO("returned too early"); + CHECK_FALSE(result); // okay, let thread perform the assignment mSync.set(3); } @@ -136,7 +130,8 @@ namespace tut thread.join(); // deliver any exception thrown by thread_work thread_result.get(); - ensure("ran changed", LockStatic()->ran); - ensure("didn't run on main thread", result); + CHECK(LockStatic()->ran); + UNSCOPED_INFO("didn't run on main thread"); + CHECK(result); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llmutex_test.cpp b/indra/llcommon/tests/llmutex_test.cpp index 040ca79b41..6fb5dc2725 100644 --- a/indra/llcommon/tests/llmutex_test.cpp +++ b/indra/llcommon/tests/llmutex_test.cpp @@ -16,13 +16,13 @@ #include "llmutex.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct llmutex_data { @@ -38,69 +38,56 @@ namespace tut } }; - typedef test_group llmutex_group; - typedef llmutex_group::object object; - llmutex_group llmutexgrp("llmutex"); - - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(llmutex_data, "llmutex: lock marks ownership and unlock clears it", "[llmutex]") { - set_test_name("lock marks ownership, unlock clears it"); - - ensure("unlocked mutex reports self-locked", !mMutex.isSelfLocked()); - ensure_equals("unlocked mutex has an owner", mMutex.lockingThread(), LLThread::id_t()); + CHECK_FALSE(mMutex.isSelfLocked()); + CHECK(mMutex.lockingThread() == LLThread::id_t()); mMutex.lock(); - ensure("locked mutex does not report self-locked", mMutex.isSelfLocked()); - ensure_equals("locking thread not recorded", mMutex.lockingThread(), LLThread::currentID()); - ensure("isLocked() false while held", mMutex.isLocked()); + CHECK(mMutex.isSelfLocked()); + CHECK(mMutex.lockingThread() == LLThread::currentID()); + CHECK(mMutex.isLocked()); mMutex.unlock(); - ensure("still self-locked after unlock", !mMutex.isSelfLocked()); - ensure_equals("owner not cleared on unlock", mMutex.lockingThread(), LLThread::id_t()); - ensure("isLocked() true after unlock", !mMutex.isLocked()); + CHECK_FALSE(mMutex.isSelfLocked()); + CHECK(mMutex.lockingThread() == LLThread::id_t()); + CHECK_FALSE(mMutex.isLocked()); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(llmutex_data, "llmutex: lock is recursive and balanced", "[llmutex]") { - set_test_name("lock is recursive and balanced"); - mMutex.lock(); mMutex.lock(); mMutex.lock(); - ensure("self-lock lost during recursion", mMutex.isSelfLocked()); + CHECK(mMutex.isSelfLocked()); mMutex.unlock(); - ensure("released on the first of three unlocks", mMutex.isLocked()); + UNSCOPED_INFO("released on the first of three unlocks"); + CHECK(mMutex.isLocked()); mMutex.unlock(); - ensure("released on the second of three unlocks", mMutex.isLocked()); + UNSCOPED_INFO("released on the second of three unlocks"); + CHECK(mMutex.isLocked()); mMutex.unlock(); - ensure("not released after balanced unlocks", !mMutex.isLocked()); - ensure("still self-locked after balanced unlocks", !mMutex.isSelfLocked()); + CHECK_FALSE(mMutex.isLocked()); + CHECK_FALSE(mMutex.isSelfLocked()); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(llmutex_data, "llmutex: trylock recurses on a self-held mutex", "[llmutex]") { - set_test_name("trylock recurses on a self-held mutex"); - mMutex.lock(); - ensure("trylock failed on a mutex this thread already holds", mMutex.trylock()); + CHECK(mMutex.trylock()); mMutex.unlock(); - ensure("recursive trylock did not need a matching unlock", mMutex.isLocked()); + UNSCOPED_INFO("recursive trylock did not need a matching unlock"); + CHECK(mMutex.isLocked()); mMutex.unlock(); - ensure("not released after balanced unlocks", !mMutex.isLocked()); + CHECK_FALSE(mMutex.isLocked()); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(llmutex_data, "llmutex: ownership does not leak across threads", "[llmutex]") { - set_test_name("ownership does not leak across threads"); - mMutex.lock(); std::atomic saw_self_locked{true}; @@ -115,8 +102,10 @@ namespace tut } }); - ensure("another thread saw our lock as its own", !saw_self_locked); - ensure("another thread acquired a held mutex", !trylock_succeeded); + UNSCOPED_INFO("another thread saw our lock as its own"); + CHECK_FALSE(saw_self_locked); + UNSCOPED_INFO("another thread acquired a held mutex"); + CHECK_FALSE(trylock_succeeded); mMutex.unlock(); @@ -130,32 +119,27 @@ namespace tut } }); - ensure("another thread could not acquire a released mutex", acquired); + UNSCOPED_INFO("another thread could not acquire a released mutex"); + CHECK(acquired); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(llmutex_data, "llmutex: LLMutexTrylock reports and releases", "[llmutex]") { - set_test_name("LLMutexTrylock reports and releases"); - { LLMutexTrylock lock(&mMutex); - ensure("trylock scope failed on a free mutex", lock.isLocked()); - ensure("trylock scope did not take ownership", mMutex.isSelfLocked()); + CHECK(lock.isLocked()); + CHECK(mMutex.isSelfLocked()); } - ensure("trylock scope did not release", !mMutex.isLocked()); + CHECK_FALSE(mMutex.isLocked()); // A null mutex is legal and locks nothing -- callers pass member pointers that // may not be constructed yet. LLMutexTrylock null_lock(nullptr); - ensure("null trylock scope claimed a lock", !null_lock.isLocked()); + CHECK_FALSE(null_lock.isLocked()); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(llmutex_data, "llmutex: mutual exclusion holds under contention", "[llmutex]") { - set_test_name("mutual exclusion holds under contention"); - // Deliberately a non-atomic counter: the mutex is what makes it safe, so a lost // acquisition shows up as a short count. constexpr int NUM_THREADS = 8; @@ -189,7 +173,9 @@ namespace tut thread.join(); } - ensure_equals("lost an acquisition under contention", counter, NUM_THREADS * NUM_ITERATIONS); - ensure_equals("thread claimed ownership it did not have", false_self_locks.load(), 0); + UNSCOPED_INFO("lost an acquisition under contention"); + CHECK(counter == NUM_THREADS * NUM_ITERATIONS); + UNSCOPED_INFO("thread claimed ownership it did not have"); + CHECK(false_self_locks.load() == 0); } } diff --git a/indra/llcommon/tests/llpounceable_test.cpp b/indra/llcommon/tests/llpounceable_test.cpp index 20954be5d0..f847c3d28d 100644 --- a/indra/llcommon/tests/llpounceable_test.cpp +++ b/indra/llcommon/tests/llpounceable_test.cpp @@ -18,7 +18,7 @@ // external library headers #include // other Linden headers -#include "../test/lltut.h" +#include namespace { @@ -74,24 +74,15 @@ LLPounceable gForward; /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { - struct llpounceable_data - { - }; - typedef test_group llpounceable_group; - typedef llpounceable_group::object object; - llpounceable_group llpounceablegrp("llpounceable"); - - template<> template<> - void object::test<1>() + TEST_CASE("llpounceable: LLPounceableStatic queues calls made before its constructor runs", "[llpounceable]") { - set_test_name("LLPounceableStatic out-of-order test"); // LLPounceable::callWhenReady() must work even // before LLPounceable's constructor runs. That's the whole point of // implementing it with an LLSingleton queue. This models (say) // LLPounceableStatic. - ensure("static_check should still be null", ! static_check); + CHECK_FALSE(static_check); Data myData("test<1>"); // gForward and static_check let go of myData with the test, however // it ends, rather than point past it at whatever runs next. @@ -104,13 +95,11 @@ namespace tut } } let_go; gForward = &myData; // should run setter - ensure_equals("static_check should be &myData", static_check, &myData); + CHECK(static_check == &myData); } - template<> template<> - void object::test<2>() + TEST_CASE("llpounceable: each LLPounceableQueue instance has its own queue", "[llpounceable]") { - set_test_name("LLPounceableQueue different queues"); // We expect that LLPounceable should have // different queues because that specialization stores the queue // directly in the LLPounceable instance. @@ -118,21 +107,19 @@ namespace tut LLPounceable a, b; a.callWhenReady(boost::bind(setter, &aptr, _1)); b.callWhenReady(boost::bind(setter, &bptr, _1)); - ensure("aptr should be null", ! aptr); - ensure("bptr should be null", ! bptr); + CHECK_FALSE(aptr); + CHECK_FALSE(bptr); Data adata("a"), bdata("b"); a = &adata; - ensure_equals("aptr should be &adata", aptr, &adata); + CHECK(aptr == &adata); // but we haven't yet set b - ensure("bptr should still be null", !bptr); + CHECK_FALSE(bptr); b = &bdata; - ensure_equals("bptr should be &bdata", bptr, &bdata); + CHECK(bptr == &bdata); } - template<> template<> - void object::test<3>() + TEST_CASE("llpounceable: each LLPounceableStatic instance has its own queue", "[llpounceable]") { - set_test_name("LLPounceableStatic different queues"); // LLPounceable should also have a distinct // queue for each instance, but that engages an additional map lookup // because there's only one LLSingleton for each T. @@ -140,21 +127,19 @@ namespace tut LLPounceable a, b; a.callWhenReady(boost::bind(setter, &aptr, _1)); b.callWhenReady(boost::bind(setter, &bptr, _1)); - ensure("aptr should be null", ! aptr); - ensure("bptr should be null", ! bptr); + CHECK_FALSE(aptr); + CHECK_FALSE(bptr); Data adata("a"), bdata("b"); a = &adata; - ensure_equals("aptr should be &adata", aptr, &adata); + CHECK(aptr == &adata); // but we haven't yet set b - ensure("bptr should still be null", !bptr); + CHECK_FALSE(bptr); b = &bdata; - ensure_equals("bptr should be &bdata", bptr, &bdata); + CHECK(bptr == &bdata); } - template<> template<> - void object::test<4>() + TEST_CASE("llpounceable: LLPounceable looks like T", "[llpounceable]") { - set_test_name("LLPounceable looks like T"); // We want LLPounceable to be drop-in replaceable for a plain // T for read constructs. In particular, it should behave like a dumb // pointer -- and with zero abstraction cost for such usage. @@ -165,20 +150,18 @@ namespace tut LLPounceable pounceable(&a); // should be able to pass LLPounceable to function accepting T setter(&aptr, pounceable); - ensure_equals("aptr should be &a", aptr, &a); + CHECK(aptr == &a); // should be able to dereference with * - ensure_equals("deref with *", (*pounceable).mData, "a"); + CHECK((*pounceable).mData == "a"); // should be able to dereference with -> - ensure_equals("deref with ->", pounceable->mData, "a"); + CHECK(pounceable->mData == "a"); // bool operations - ensure("test with operator bool()", pounceable); - ensure("test with operator !()", ! (! pounceable)); + CHECK(pounceable); + CHECK_FALSE(! pounceable); } - template<> template<> - void object::test<5>() + TEST_CASE("llpounceable: callWhenReady() queues until non-null then calls at once", "[llpounceable]") { - set_test_name("Multiple callWhenReady() queue items"); Data *p1 = 0, *p2 = 0, *p3 = 0; Data a("a"); LLPounceable pounceable; @@ -186,43 +169,38 @@ namespace tut pounceable.callWhenReady(boost::bind(setter, &p1, _1)); pounceable.callWhenReady(boost::bind(setter, &p2, _1)); // should still be pending - ensure("p1 should be null", !p1); - ensure("p2 should be null", !p2); - ensure("p3 should be null", !p3); + CHECK_FALSE(p1); + CHECK_FALSE(p2); + CHECK_FALSE(p3); pounceable = 0; // assigning a new empty value shouldn't flush the queue - ensure("p1 should still be null", !p1); - ensure("p2 should still be null", !p2); - ensure("p3 should still be null", !p3); + CHECK_FALSE(p1); + CHECK_FALSE(p2); + CHECK_FALSE(p3); // using whichever syntax pounceable.reset(0); - // try to make ensure messages distinct... tough to pin down which - // ensure() failed if multiple ensure() calls in the same test have - // the same message! - ensure("p1 should again be null", !p1); - ensure("p2 should again be null", !p2); - ensure("p3 should again be null", !p3); + CHECK_FALSE(p1); + CHECK_FALSE(p2); + CHECK_FALSE(p3); pounceable.reset(&a); // should flush queue - ensure_equals("p1 should be &a", p1, &a); - ensure_equals("p2 should be &a", p2, &a); - ensure("p3 still not set", !p3); + CHECK(p1 == &a); + CHECK(p2 == &a); + CHECK_FALSE(p3); // immediate call pounceable.callWhenReady(boost::bind(setter, &p3, _1)); - ensure_equals("p3 should be &a", p3, &a); + CHECK(p3 == &a); } - template<> template<> - void object::test<6>() + TEST_CASE("llpounceable: queued calls run in order and firing empties the queue", "[llpounceable]") { - set_test_name("queue order"); std::string data; LLPounceable pounceable; pounceable.callWhenReady(boost::bind(append, _1, "a")); pounceable.callWhenReady(boost::bind(append, _1, "b")); pounceable.callWhenReady(boost::bind(append, _1, "c")); pounceable = &data; - ensure_equals("callWhenReady() must preserve chronological order", - data, "abc"); + UNSCOPED_INFO("callWhenReady() must preserve chronological order"); + CHECK(data == "abc"); std::string data2; pounceable = NULL; @@ -230,16 +208,14 @@ namespace tut pounceable.callWhenReady(boost::bind(append, _1, "e")); pounceable.callWhenReady(boost::bind(append, _1, "f")); pounceable = &data2; - ensure_equals("LLPounceable must reset queue when fired", - data2, "def"); + UNSCOPED_INFO("LLPounceable must reset queue when fired"); + CHECK(data2 == "def"); } - template<> template<> - void object::test<7>() + TEST_CASE("llpounceable: an int tag does not compile (uncomment to check)", "[llpounceable]") { - set_test_name("compile-fail test, uncomment to check"); // The following declaration should fail: only LLPounceableQueue and // LLPounceableStatic should work as tags. // LLPounceable pounceable; } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index a17247d232..d793c94aaf 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -26,7 +26,12 @@ #include #include // other Linden headers -#include "../test/lltut.h" +#include +#include +#include "altestfailure.h" +#include "altesthelper.h" +#include "altestmatchers.h" +#include "altestname.h" #include "../test/namedtempfile.h" #include "../test/catch_and_store_what_in.h" #include "stringize.h" @@ -54,29 +59,28 @@ static std::string apr_strerror_helper(apr_status_t rv) * Helpers *****************************************************************************/ -#define ensure_equals_(left, right) \ +#define require_equals_(left, right) \ do { \ auto _left_val = (left); \ auto _right_val = (right); \ if (_left_val != _right_val) { \ - std::string _msg = std::string(#left) + " != " + std::string(#right); \ - tut::ensure_equals(_msg, _left_val, _right_val); \ + UNSCOPED_INFO(#left " != " #right); REQUIRE_THAT(_left_val, ll_test::LLSDEquals(_right_val)); \ } else { \ - tut::ensure_equals("", _left_val, _right_val); \ + REQUIRE_THAT(_left_val, ll_test::LLSDEquals(_right_val)); \ } \ } while(0) -#define aprchk(expr) aprchk_(#expr, (expr)) -static void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS) +#define require_apr(expr) require_apr_(#expr, (expr)) +static void require_apr_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS) { if (rv != expected) { - std::string msg = std::string(call) + " => " + std::to_string(rv) + ": " + apr_strerror_helper(rv); - tut::ensure_equals(msg, rv, expected); + UNSCOPED_INFO(call << " => " << rv << ": " << apr_strerror_helper(rv)); + REQUIRE_THAT(rv, ll_test::LLSDEquals(expected)); } else { - tut::ensure_equals("", rv, expected); + REQUIRE_THAT(rv, ll_test::LLSDEquals(expected)); } } @@ -100,7 +104,7 @@ static std::string readfile(const std::filesystem::path& pathname, const std::st std::string output; if (!std::getline(inf, output)) { - tut::ensure("No output " + use_desc, false); + FAIL("No output " << use_desc); } std::string more; while (std::getline(inf, more)) @@ -145,8 +149,8 @@ static void waitfor(LLProcess& proc, int timeout=60) } // Pump once more after the process exits to flush any final events such as EOF. yield(0); - std::string msg = "process took longer than " + std::to_string(timeout) + " seconds to terminate"; - tut::ensure(msg, i < timeout); + INFO("process took longer than " << timeout << " seconds to terminate"); + REQUIRE(i < timeout); } static void waitfor(LLProcess::handle h, const std::string& desc, int timeout=60) @@ -158,8 +162,8 @@ static void waitfor(LLProcess::handle h, const std::string& desc, int timeout=60 } // Pump once more after the process exits to flush any final events such as EOF. yield(0); - std::string msg = "process took longer than " + std::to_string(timeout) + " seconds to terminate"; - tut::ensure(msg, i < timeout); + INFO("process took longer than " << timeout << " seconds to terminate"); + REQUIRE(i < timeout); } namespace @@ -182,7 +186,8 @@ struct PythonProcessLauncher mScript("py", script) { auto PYTHON(LLStringUtil::getenv("PYTHON")); - tut::ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); + UNSCOPED_INFO("Set $PYTHON to the Python interpreter"); + REQUIRE_FALSE(PYTHON.empty()); mParams.desc = desc + " script"; mParams.executable = PYTHON; @@ -195,10 +200,10 @@ struct PythonProcessLauncher try { mPy = LLProcess::create(mParams); - std::string msg = "Couldn't launch " + mDesc + " script"; - tut::ensure(msg, bool(mPy)); + UNSCOPED_INFO("Couldn't launch " << mDesc << " script"); + REQUIRE(bool(mPy)); } - catch (const tut::failure&) + catch (const ll_test::Failure&) { // On Windows, if APR_LOG is set, our version of APR's // apr_create_proc() logs to the specified file. If this test @@ -320,7 +325,8 @@ namespace for (;;) { DWORD length = GetModuleFileNameA(nullptr, buffer.data(), static_cast(buffer.size())); - tut::ensure("GetModuleFileNameA() failed", length > 0); + UNSCOPED_INFO("GetModuleFileNameA() failed"); + REQUIRE(length > 0); if (length < buffer.size() && (length < buffer.size() - 1 || buffer[length] == '\0')) { @@ -399,6 +405,16 @@ namespace std::exit(0); } + // verify_autokill_on_helper_exit's helper: this binary run again as + // nothing but run_autokill_helper_from_environment(), which exits the + // process itself once the test releases it. + AL_TEST_HELPER(llprocess_autokill) + { + run_autokill_helper_from_environment(); + // It returns only when the environment names no script. + return AUTOKILL_HELPER_INVALID_ENV_EXIT; + } + std::vector wait_for_helper_pids( const std::string& pidfile, int expected_count, @@ -422,9 +438,7 @@ namespace Sleep(AUTOKILL_HELPER_POLL_INTERVAL_MS); LLEventPumps::instance().obtain("mainloop").post(LLSD()); } - tut::ensure(STRINGIZE("expected " << expected_count - << " child pids within " << timeout - << " seconds"), false); + FAIL("expected " << expected_count << " child pids within " << timeout << " seconds"); return {}; } @@ -443,9 +457,10 @@ namespace LLProcess::Params params; params.executable = get_current_executable_path(); + params.args.add(ll_test::Helper::argument("llprocess_autokill")); params.desc = desc + " helper"; LLProcessPtr helper = LLProcess::create(params); - tut::ensure("helper launched", bool(helper)); + REQUIRE(bool(helper)); std::vector pids = wait_for_helper_pids(pidfile.getPath().string(), child_count); std::vector handles; @@ -455,7 +470,8 @@ namespace // SYNCHRONIZE lets the test wait for the child to terminate, while // PROCESS_QUERY_LIMITED_INFORMATION keeps the requested access minimal. HANDLE handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, false, pid); - tut::ensure(STRINGIZE("opened child process handle for pid " << pid), handle != nullptr); + CAPTURE(pid); + REQUIRE(handle != nullptr); handles.push_back(handle); } @@ -465,13 +481,12 @@ namespace } waitfor(*helper); - tut::ensure_equals("helper exited", helper->getStatus().mState, LLProcess::EXITED); + CHECK(helper->getStatus().mState == LLProcess::EXITED); for (HANDLE handle : handles) { - tut::ensure_equals("autokilled child exited", - WaitForSingleObject(handle, AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS), - WAIT_OBJECT_0); + UNSCOPED_INFO("autokilled child exited"); + REQUIRE(WaitForSingleObject(handle, AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS) == WAIT_OBJECT_0); CloseHandle(handle); } } @@ -535,18 +550,8 @@ namespace tut { struct llprocess_data { - llprocess_data() - { -#if LL_WINDOWS - run_autokill_helper_from_environment(); -#endif - } - LLAPRPool pool; }; - typedef test_group llprocess_group; - typedef llprocess_group::object object; - llprocess_group llprocessgrp("llprocess"); namespace { @@ -650,11 +655,8 @@ namespace tut } } - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(llprocess_data, "llprocess: raw APR pipes read a child's output without blocking", "[llprocess]") { - set_test_name("raw APR nonblocking I/O"); - // Create a script file in a temporary place. NamedExtTempFile script("py", "from __future__ import print_function" EOL @@ -677,14 +679,15 @@ namespace tut // Run the child process. apr_procattr_t *procattr = NULL; - aprchk(apr_procattr_create(&procattr, pool.getAPRPool())); - aprchk(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); - aprchk(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); + require_apr(apr_procattr_create(&procattr, pool.getAPRPool())); + require_apr(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); + require_apr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); std::vector argv; apr_proc_t child; auto PYTHON(LLStringUtil::getenv("PYTHON")); - tut::ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); + UNSCOPED_INFO("Set $PYTHON to the Python interpreter"); + REQUIRE_FALSE(PYTHON.empty()); argv.push_back(PYTHON.c_str()); // Have to have a named copy of this std::string so its c_str() value // will persist. @@ -692,11 +695,11 @@ namespace tut argv.push_back(scriptname.c_str()); argv.push_back(NULL); - aprchk(apr_proc_create(&child, argv[0], - &argv[0], - NULL, // if we wanted to pass explicit environment - procattr, - pool.getAPRPool())); + require_apr(apr_proc_create(&child, argv[0], + &argv[0], + NULL, // if we wanted to pass explicit environment + procattr, + pool.getAPRPool())); // We do not want this child process to outlive our APR pool. On // destruction of the pool, forcibly kill the process. Tell APR to try @@ -754,7 +757,7 @@ namespace tut ++history.back().tries; continue; } - aprchk_("apr_file_gets(buf, sizeof(buf), dfli->second)", rv); + require_apr_("apr_file_gets(buf, sizeof(buf), dfli->second)", rv); // Is it even possible to get APR_SUCCESS but read 0 bytes? // Hope not, but defend against that anyway. if (buf[0]) @@ -796,35 +799,37 @@ namespace tut wi.rv = apr_proc_wait(wi.child, &wi.rc, &wi.why, APR_NOWAIT); } // std::cout << "child done: rv = " << rv << " (" << manager.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; - aprchk_("apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT)", wi.rv, APR_CHILD_DONE); + require_apr_("apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT)", wi.rv, APR_CHILD_DONE); // Beyond merely executing all the above successfully, verify that we // obtained expected output -- and that we duly got control while // waiting, proving the non-blocking nature of these pipes. try { - // Perform these ensure_equals_() within this try/catch so that if + // Perform these require_equals_() within this try/catch so that if // we don't get expected results, we'll dump whatever we did get // to help diagnose. - ensure_equals_(wi.why, APR_PROC_EXIT); - ensure_equals_(wi.rc, 0); + require_equals_(wi.why, APR_PROC_EXIT); + require_equals_(wi.rc, 0); unsigned i = 0; - ensure("blocking I/O on child pipe (0)", history[i].tries); - ensure_equals_(history[i].which, "out"); - ensure_equals_(history[i].what, "stdout after wait" EOL); + UNSCOPED_INFO("blocking I/O on child pipe (0)"); + CHECK(history[i].tries); + require_equals_(history[i].which, "out"); + require_equals_(history[i].what, "stdout after wait" EOL); // ++i; -// ensure_equals_(history[i].which, "out"); -// ensure_equals_(history[i].what, "*eof*"); +// require_equals_(history[i].which, "out"); +// require_equals_(history[i].what, "*eof*"); ++i; - ensure("blocking I/O on child pipe (1)", history[i].tries); - ensure_equals_(history[i].which, "err"); - ensure_equals_(history[i].what, "stderr after wait" EOL); + UNSCOPED_INFO("blocking I/O on child pipe (1)"); + CHECK(history[i].tries); + require_equals_(history[i].which, "err"); + require_equals_(history[i].what, "stderr after wait" EOL); // ++i; -// ensure_equals_(history[i].which, "err"); -// ensure_equals_(history[i].what, "*eof*"); +// require_equals_(history[i].which, "err"); +// require_equals_(history[i].what, "*eof*"); } - catch (const failure&) + catch (const ll_test::Failure&) { std::cout << "History:\n"; for (const Item& item : history) @@ -849,15 +854,13 @@ namespace tut } } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(llprocess_data, "llprocess: cwd sets the child's working directory", "[llprocess]") { - set_test_name("setWorkingDirectory()"); // We want to test setWorkingDirectory(). But what directory is // guaranteed to exist on every machine, under every OS? Have to // create one. Naturally, ensure we clean it up when done. NamedTempDir tempdir; - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import with_statement\n" "import os, sys\n" "with open(sys.argv[1], 'w') as f:\n" @@ -870,14 +873,13 @@ namespace tut // but on the Mac, using tolower() fails because "/users" != "/Users"! expected = utf8str_tolower(expected); #endif - ensure_equals("os.getcwd()", py.run_read(), expected); + UNSCOPED_INFO("os.getcwd()"); + CHECK(py.run_read() == expected); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(llprocess_data, "llprocess: args reach the child in order", "[llprocess]") { - set_test_name("arguments"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import with_statement, print_function\n" "import sys\n" // note nonstandard output-file arg! @@ -892,71 +894,69 @@ namespace tut std::string output(py.run_read()); boost::split_iterator li(output, boost::first_finder("\n")), lend; - ensure("didn't get first arg", li != lend); + UNSCOPED_INFO("didn't get first arg"); + REQUIRE(li != lend); std::string arg(li->begin(), li->end()); - ensure_equals(arg, "first arg"); + CHECK(arg == "first arg"); ++li; - ensure("didn't get second arg", li != lend); + UNSCOPED_INFO("didn't get second arg"); + REQUIRE(li != lend); arg.assign(li->begin(), li->end()); - ensure_equals(arg, "second arg"); + CHECK(arg == "second arg"); ++li; - ensure("didn't get output filename?!", li != lend); + UNSCOPED_INFO("didn't get output filename?!"); + REQUIRE(li != lend); arg.assign(li->begin(), li->end()); - ensure("output filename empty?!", ! arg.empty()); + UNSCOPED_INFO("output filename empty?!"); + CHECK_FALSE(arg.empty()); ++li; - ensure("too many args", li == lend); + UNSCOPED_INFO("too many args"); + CHECK(li == lend); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(llprocess_data, "llprocess: exit(0) is reported as EXITED with code 0", "[llprocess]") { - set_test_name("exit(0)"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.exit(0)\n"); py.run(); - ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); - ensure_equals("Status.mData", py.mPy->getStatus().mData, 0); + CHECK(py.mPy->getStatus().mState == LLProcess::EXITED); + CHECK(py.mPy->getStatus().mData == 0); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(llprocess_data, "llprocess: exit(2) is reported as EXITED with code 2", "[llprocess]") { - set_test_name("exit(2)"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.exit(2)\n"); py.run(); - ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); - ensure_equals("Status.mData", py.mPy->getStatus().mData, 2); + CHECK(py.mPy->getStatus().mState == LLProcess::EXITED); + CHECK(py.mPy->getStatus().mData == 2); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(llprocess_data, "llprocess: a script syntax error exits 1 with SyntaxError on stderr", "[llprocess]") { - set_test_name("syntax_error"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "syntax_error:\n"); py.mParams.files.add(LLProcess::FileParam()); // inherit stdin py.mParams.files.add(LLProcess::FileParam()); // inherit stdout py.mParams.files.add(LLProcess::FileParam().type("pipe")); // pipe for stderr py.run(); - ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); - ensure_equals("Status.mData", py.mPy->getStatus().mData, 1); + CHECK(py.mPy->getStatus().mState == LLProcess::EXITED); + CHECK(py.mPy->getStatus().mData == 1); std::istream& rpipe(py.mPy->getReadPipe(LLProcess::STDERR).get_istream()); std::vector buffer(4096); rpipe.read(&buffer[0], buffer.size()); std::streamsize got(rpipe.gcount()); - ensure("Nothing read from stderr pipe", got); + UNSCOPED_INFO("Nothing read from stderr pipe"); + CHECK(got); std::string data(&buffer[0], got); - ensure("Didn't find 'SyntaxError:'", data.find("\nSyntaxError:") != std::string::npos); + CHECK(data.find("\nSyntaxError:") != std::string::npos); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(llprocess_data, "llprocess: kill() stops a sleeping child before it writes again", "[llprocess]") { - set_test_name("explicit kill()"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import with_statement\n" "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -978,32 +978,31 @@ namespace tut break; } // If we broke this loop because of the counter, something's wrong - ensure("script never started", i < timeout); + UNSCOPED_INFO("script never started"); + CHECK(i < timeout); // script has performed its first write and should now be sleeping. py.mPy->kill(); // wait for the script to terminate... one way or another. waitfor(*py.mPy); #if LL_WINDOWS - ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); - ensure_equals("Status.mData", py.mPy->getStatus().mData, -1); + CHECK(py.mPy->getStatus().mState == LLProcess::EXITED); + CHECK(py.mPy->getStatus().mData == -1); #else - ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::KILLED); - ensure_equals("Status.mData", py.mPy->getStatus().mData, SIGTERM); + CHECK(py.mPy->getStatus().mState == LLProcess::KILLED); + CHECK(py.mPy->getStatus().mData == SIGTERM); #endif // If kill() failed, the script would have woken up on its own and // overwritten the file with 'bad'. But if kill() succeeded, it should // not have had that chance. - ensure_equals(get_test_name() + " script output", readfile(out.getPath()), "ok"); + CHECK(readfile(out.getPath()) == "ok"); } - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(llprocess_data, "llprocess: destroying the LLProcess kills its child", "[llprocess]") { - set_test_name("implicit kill()"); NamedTempFile out("out", "not started"); LLProcess::handle phandle(0); { - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import with_statement\n" "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -1026,7 +1025,8 @@ namespace tut break; } // If we broke this loop because of the counter, something's wrong - ensure("script never started", i < timeout); + UNSCOPED_INFO("script never started"); + CHECK(i < timeout); // Script has performed its first write and should now be sleeping. // Destroy the LLProcess, which should kill the child. } @@ -1035,18 +1035,16 @@ namespace tut // If kill() failed, the script would have woken up on its own and // overwritten the file with 'bad'. But if kill() succeeded, it should // not have had that chance. - ensure_equals(get_test_name() + " script output", readfile(out.getPath()), "ok"); + CHECK(readfile(out.getPath()) == "ok"); } - template<> template<> - void object::test<9>() + TEST_CASE_METHOD(llprocess_data, "llprocess: autokill=false lets the child outlive the LLProcess", "[llprocess]") { - set_test_name("autokill=false"); NamedTempFile from("from", "not started"); NamedTempFile to("to", ""); LLProcess::handle phandle(0); { - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import with_statement\n" "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -1080,7 +1078,8 @@ namespace tut break; } // If we broke this loop because of the counter, something's wrong - ensure("script never started", i < timeout); + UNSCOPED_INFO("script never started"); + CHECK(i < timeout); // Now destroy the LLProcess, which should NOT kill the child! } // If the destructor killed the child anyway, give it time to die @@ -1095,20 +1094,18 @@ namespace tut waitfor(phandle, "autokill script"); // If the LLProcess destructor implicitly called kill(), the // script could not have written 'ack' as we expect. - ensure_equals(get_test_name() + " script output", readfile(from.getPath()), "ack"); + CHECK(readfile(from.getPath()) == "ack"); } - template<> template<> - void object::test<10>() + TEST_CASE_METHOD(llprocess_data, "llprocess: attached=false lets the child outlive the LLProcess despite autokill", "[llprocess]") { - set_test_name("attached=false"); // almost just like autokill=false, except set autokill=true with // attached=false. NamedTempFile from("from", "not started"); NamedTempFile to("to", ""); LLProcess::handle phandle(0); { - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import with_statement\n" "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -1143,7 +1140,8 @@ namespace tut break; } // If we broke this loop because of the counter, something's wrong - ensure("script never started", i < timeout); + UNSCOPED_INFO("script never started"); + CHECK(i < timeout); // Now destroy the LLProcess, which should NOT kill the child! } // If the destructor killed the child anyway, give it time to die @@ -1158,91 +1156,81 @@ namespace tut waitfor(phandle, "autokill script"); // If the LLProcess destructor implicitly called kill(), the // script could not have written 'ack' as we expect. - ensure_equals(get_test_name() + " script output", readfile(from.getPath()), "ack"); + CHECK(readfile(from.getPath()) == "ack"); } - template<> template<> - void object::test<11>() + TEST_CASE_METHOD(llprocess_data, "llprocess: an unknown file type is rejected and its slot named", "[llprocess]") { - set_test_name("'bogus' test"); CaptureLog recorder; - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import print_function\n" "print('Hello world')\n"); py.mParams.files.add(LLProcess::FileParam("bogus")); py.mPy = LLProcess::create(py.mParams); - ensure("should have rejected 'bogus'", ! py.mPy); + CHECK_FALSE(py.mPy); std::string message(recorder.messageWith("bogus")); - ensure_contains("did not name 'stdin'", message, "stdin"); + CHECK_THAT(message, Catch::Matchers::ContainsSubstring("stdin")); } - template<> template<> - void object::test<12>() + TEST_CASE_METHOD(llprocess_data, "llprocess: the unimplemented file type is rejected", "[llprocess]") { - set_test_name("'file' test"); // Replace this test with one or more real 'file' tests when we // implement 'file' support - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import print_function\n" "print('Hello world')\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam("file")); py.mPy = LLProcess::create(py.mParams); - ensure("should have rejected 'file'", ! py.mPy); + CHECK_FALSE(py.mPy); } - template<> template<> - void object::test<13>() + TEST_CASE_METHOD(llprocess_data, "llprocess: the unimplemented tpipe type is rejected and its slot named", "[llprocess]") { - set_test_name("'tpipe' test"); // Replace this test with one or more real 'tpipe' tests when we // implement 'tpipe' support CaptureLog recorder; - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import print_function\n" "print('Hello world')\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam("tpipe")); py.mPy = LLProcess::create(py.mParams); - ensure("should have rejected 'tpipe'", ! py.mPy); + CHECK_FALSE(py.mPy); std::string message(recorder.messageWith("tpipe")); - ensure_contains("did not name 'stdout'", message, "stdout"); + CHECK_THAT(message, Catch::Matchers::ContainsSubstring("stdout")); } - template<> template<> - void object::test<14>() + TEST_CASE_METHOD(llprocess_data, "llprocess: the unimplemented npipe type is rejected and its slot named", "[llprocess]") { - set_test_name("'npipe' test"); // Replace this test with one or more real 'npipe' tests when we // implement 'npipe' support CaptureLog recorder; - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import print_function\n" "print('Hello world')\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam("npipe")); py.mPy = LLProcess::create(py.mParams); - ensure("should have rejected 'npipe'", ! py.mPy); + CHECK_FALSE(py.mPy); std::string message(recorder.messageWith("npipe")); - ensure_contains("did not name 'stderr'", message, "stderr"); + CHECK_THAT(message, Catch::Matchers::ContainsSubstring("stderr")); } - template<> template<> - void object::test<15>() + TEST_CASE_METHOD(llprocess_data, "llprocess: a named internal pipe draws a warning but still launches", "[llprocess]") { - set_test_name("internal pipe name warning"); CaptureLog recorder; - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.exit(7)\n"); py.mParams.files.add(LLProcess::FileParam("pipe", "somename")); py.run(); // verify that it did launch anyway - ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); - ensure_equals("Status.mData", py.mPy->getStatus().mData, 7); + CHECK(py.mPy->getStatus().mState == LLProcess::EXITED); + CHECK(py.mPy->getStatus().mData == 7); std::string message(recorder.messageWith("not yet supported")); - ensure_contains("log message did not mention internal pipe name", - message, "somename"); + UNSCOPED_INFO("log message did not mention internal pipe name"); + CHECK_THAT(message, Catch::Matchers::ContainsSubstring("somename")); } /*-------------- support for "get*Pipe() validation" test --------------*/ @@ -1252,21 +1240,21 @@ namespace tut std::string threw; \ /* Both the following calls should work. */ \ (PROCESS).GETPIPE(VALID); \ - ensure(#GETOPTPIPE "(" #VALID ") failed", bool((PROCESS).GETOPTPIPE(VALID))); \ + UNSCOPED_INFO(#GETOPTPIPE "(" #VALID ") failed"); REQUIRE(bool((PROCESS).GETOPTPIPE(VALID))); \ /* pass obviously bogus PIPESLOT */ \ CATCH_IN(threw, LLProcess::NoPipe, (PROCESS).GETPIPE(LLProcess::FILESLOT(4))); \ - ensure_contains("didn't reject bad slot", threw, "no slot"); \ - ensure_contains("didn't mention bad slot num", threw, "4"); \ + UNSCOPED_INFO("didn't reject bad slot"); REQUIRE_THAT(threw, Catch::Matchers::ContainsSubstring("no slot")); \ + UNSCOPED_INFO("didn't mention bad slot num"); REQUIRE_THAT(threw, Catch::Matchers::ContainsSubstring("4")); \ EXPECT_FAIL_WITH_LOG(threw, (PROCESS).GETOPTPIPE(LLProcess::FILESLOT(4))); \ /* pass NOPIPE */ \ CATCH_IN(threw, LLProcess::NoPipe, (PROCESS).GETPIPE(NOPIPE)); \ - ensure_contains("didn't reject non-pipe", threw, "not a monitored"); \ + UNSCOPED_INFO("didn't reject non-pipe"); REQUIRE_THAT(threw, Catch::Matchers::ContainsSubstring("not a monitored")); \ EXPECT_FAIL_WITH_LOG(threw, (PROCESS).GETOPTPIPE(NOPIPE)); \ /* pass BADPIPE: FILESLOT isn't empty but wrong direction */ \ CATCH_IN(threw, LLProcess::NoPipe, (PROCESS).GETPIPE(BADPIPE)); \ /* sneaky: GETPIPE is getReadPipe or getWritePipe */ \ /* so skip "get" to obtain ReadPipe or WritePipe :-P */ \ - ensure_contains("didn't reject wrong pipe", threw, (#GETPIPE)+3); \ + UNSCOPED_INFO("didn't reject wrong pipe"); REQUIRE_THAT(threw, Catch::Matchers::ContainsSubstring((#GETPIPE)+3)); \ EXPECT_FAIL_WITH_LOG(threw, (PROCESS).GETOPTPIPE(BADPIPE)); \ } while (0) @@ -1278,22 +1266,20 @@ namespace tut (THREW) = catch_what([&](){ \ CODE; \ }); \ - ensure("failed to throw " #EXCEPTION ": " #CODE, ! (THREW).empty()); \ + UNSCOPED_INFO("failed to throw " #EXCEPTION ": " #CODE); REQUIRE_FALSE((THREW).empty()); \ } while (0) #define EXPECT_FAIL_WITH_LOG(EXPECT, CODE) \ do \ { \ CaptureLog recorder; \ - ensure(#CODE " succeeded", ! (CODE)); \ + UNSCOPED_INFO(#CODE " succeeded"); REQUIRE_FALSE(CODE); \ recorder.messageWith(EXPECT); \ } while (0) - template<> template<> - void object::test<16>() + TEST_CASE_METHOD(llprocess_data, "llprocess: getReadPipe and getWritePipe reject bad slots and wrong pipes", "[llprocess]") { - set_test_name("get*Pipe() validation"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import print_function\n" "print('this output is expected')\n"); py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stdin @@ -1310,11 +1296,9 @@ namespace tut LLProcess::STDIN); // BADPIPE } - template<> template<> - void object::test<17>() + TEST_CASE_METHOD(llprocess_data, "llprocess: a child answers over its stdin and stdout pipes", "[llprocess]") { - set_test_name("talk to stdin/stdout"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import print_function\n" "import sys, time\n" "print('ok')\n" @@ -1333,16 +1317,17 @@ namespace tut { yield(); } - ensure("script never started", i < timeout); - ensure_equals("bad wakeup from stdin/stdout script", - childout.getline(), "ok"); + UNSCOPED_INFO("script never started"); + CHECK(i < timeout); + CHECK(childout.getline() == "ok"); // important to get the implicit flush from std::endl py.mPy->getWritePipe().get_ostream() << "go" << std::endl; waitfor(*py.mPy); - ensure("script never replied", childout.contains("\n")); - ensure_equals("child didn't ack", childout.getline(), "ack"); - ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED); - ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0); + UNSCOPED_INFO("script never replied"); + CHECK(childout.contains("\n")); + CHECK(childout.getline() == "ack"); + CHECK(py.mPy->getStatus().mState == LLProcess::EXITED); + CHECK(py.mPy->getStatus().mData == 0); } namespace @@ -1369,10 +1354,10 @@ namespace tut { try { - // we expect this lambda to contain tut::ensure() calls + // we expect this lambda to contain REQUIRE() calls std::forward(code)(mHistory); } - catch (const failure&) + catch (const ll_test::Failure&) { LL_INFOS() << "event history:" << LL_ENDL; for (const LLSD& item : mHistory) @@ -1395,11 +1380,9 @@ namespace tut return false; } - template<> template<> - void object::test<18>() + TEST_CASE_METHOD(llprocess_data, "llprocess: ReadPipe events carry the data so far and its length", "[llprocess]") { - set_test_name("listen for ReadPipe events"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.stdout.write('abc')\n" "sys.stdout.flush()\n" @@ -1430,7 +1413,8 @@ namespace tut { yield(); } - ensure("couldn't get first line", i < timeout); + UNSCOPED_INFO("couldn't get first line"); + CHECK(i < timeout); // disconnect from listener listener.mConnection.disconnect(); // finish out the run @@ -1440,29 +1424,31 @@ namespace tut [](const EventListener::Listory& history) { auto li(history.begin()), lend(history.end()); - ensure("no events", li != lend); - ensure_equals("history[0]", (*li)["data"].asString(), "abc"); - ensure_equals("history[0] len", (*li)["len"].asInteger(), 3); + UNSCOPED_INFO("no events"); + REQUIRE(li != lend); + REQUIRE((*li)["data"].asString() == "abc"); + REQUIRE((*li)["len"].asInteger() == 3); ++li; - ensure("only 1 event", li != lend); - ensure_equals("history[1]", (*li)["data"].asString(), "abcdef"); - ensure_equals("history[0] len", (*li)["len"].asInteger(), 6); + UNSCOPED_INFO("only 1 event"); + REQUIRE(li != lend); + REQUIRE((*li)["data"].asString() == "abcdef"); + REQUIRE((*li)["len"].asInteger() == 6); ++li; - ensure("only 2 events", li != lend); - ensure_equals("history[2]", (*li)["data"].asString(), "abcdefghi" EOL); - ensure_equals("history[0] len", (*li)["len"].asInteger(), 9 + sizeof(EOL) - 1); + UNSCOPED_INFO("only 2 events"); + REQUIRE(li != lend); + REQUIRE((*li)["data"].asString() == "abcdefghi" EOL); + REQUIRE((*li)["len"].asInteger() == 9 + sizeof(EOL) - 1); ++li; // We DO NOT expect a whole new event for the second line because we // disconnected. - ensure("more than 3 events", li == lend); + UNSCOPED_INFO("more than 3 events"); + REQUIRE(li == lend); }); } - template<> template<> - void object::test<19>() + TEST_CASE_METHOD(llprocess_data, "llprocess: ReadPipe posts one eof event and posts it last", "[llprocess]") { - set_test_name("ReadPipe \"eof\" event"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import time\n" "time.sleep(1.5)\n"); py.mParams.files.add(LLProcess::FileParam()); // stdin @@ -1487,20 +1473,21 @@ namespace tut [](const EventListener::Listory& history) { auto rli(history.rbegin()), rlend(history.rend()); - ensure("no events", rli != rlend); - ensure("last event not \"eof\"", (*rli)["eof"].asBoolean()); + UNSCOPED_INFO("no events"); + REQUIRE(rli != rlend); + UNSCOPED_INFO("last event not \"eof\""); + REQUIRE((*rli)["eof"].asBoolean()); while (++rli != rlend) { - ensure("\"eof\" event not last", ! (*rli)["eof"].asBoolean()); + UNSCOPED_INFO("\"eof\" event not last"); + REQUIRE_FALSE((*rli)["eof"].asBoolean()); } }); } - template<> template<> - void object::test<20>() + TEST_CASE_METHOD(llprocess_data, "llprocess: setLimit caps the data an event carries but not its len", "[llprocess]") { - set_test_name("setLimit()"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.stdout.write(sys.argv[1])\n"); std::string abc("abcdefghijklmnopqrstuvwxyz"); @@ -1513,27 +1500,24 @@ namespace tut EventListener listener(childout.getPump()); // but set limit childout.setLimit(10); - ensure_equals("getLimit() after setlimit(10)", childout.getLimit(), 10); + CHECK(childout.getLimit() == 10); // okay, pump I/O to pick up output from child waitfor(*py.mPy); listener.checkHistory( [abc](const EventListener::Listory& history) { - ensure("no events", ! history.empty()); + UNSCOPED_INFO("no events"); + REQUIRE_FALSE(history.empty()); // For all we know, that data could have arrived in several different // bursts... probably not, but anyway, only check the last one. - ensure_equals("event[\"len\"]", - history.back()["len"].asInteger(), abc.length()); - ensure_equals("length of setLimit(10) data", - history.back()["data"].asString().length(), 10); + REQUIRE(history.back()["len"].asInteger() == abc.length()); + REQUIRE(history.back()["data"].asString().length() == 10); }); } - template<> template<> - void object::test<21>() + TEST_CASE_METHOD(llprocess_data, "llprocess: ReadPipe peek/contains/find search the buffered data", "[llprocess]") { - set_test_name("peek() ReadPipe data"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.stdout.write(sys.argv[1])\n"); std::string abc("abcdefghijklmnopqrstuvwxyz"); @@ -1545,77 +1529,73 @@ namespace tut // okay, pump I/O to pick up output from child waitfor(*py.mPy); // peek() with substr args - ensure_equals("peek()", childout.peek(), abc); - ensure_equals("peek(23)", childout.peek(23), abc.substr(23)); - ensure_equals("peek(5, 3)", childout.peek(5, 3), abc.substr(5, 3)); - ensure_equals("peek(27, 2)", childout.peek(27, 2), ""); - ensure_equals("peek(23, 5)", childout.peek(23, 5), "xyz"); + CHECK(childout.peek() == abc); + CHECK(childout.peek(23) == abc.substr(23)); + CHECK(childout.peek(5, 3) == abc.substr(5, 3)); + CHECK(childout.peek(27, 2) == ""); + CHECK(childout.peek(23, 5) == "xyz"); // contains() -- we don't exercise as thoroughly as find() because the // contains() implementation is trivially (and visibly) based on find() - ensure("contains(\":\")", ! childout.contains(":")); - ensure("contains(':')", ! childout.contains(':')); - ensure("contains(\"d\")", childout.contains("d")); - ensure("contains('d')", childout.contains('d')); - ensure("contains(\"klm\")", childout.contains("klm")); - ensure("contains(\"klx\")", ! childout.contains("klx")); + CHECK_FALSE(childout.contains(":")); + CHECK_FALSE(childout.contains(':')); + CHECK(childout.contains("d")); + CHECK(childout.contains('d')); + CHECK(childout.contains("klm")); + CHECK_FALSE(childout.contains("klx")); // find() - ensure("find(\":\")", childout.find(":") == LLProcess::ReadPipe::npos); - ensure("find(':')", childout.find(':') == LLProcess::ReadPipe::npos); - ensure_equals("find(\"d\")", childout.find("d"), 3); - ensure_equals("find('d')", childout.find('d'), 3); - ensure_equals("find(\"d\", 3)", childout.find("d", 3), 3); - ensure_equals("find('d', 3)", childout.find('d', 3), 3); - ensure("find(\"d\", 4)", childout.find("d", 4) == LLProcess::ReadPipe::npos); - ensure("find('d', 4)", childout.find('d', 4) == LLProcess::ReadPipe::npos); + CHECK(childout.find(":") == LLProcess::ReadPipe::npos); + CHECK(childout.find(':') == LLProcess::ReadPipe::npos); + CHECK(childout.find("d") == 3); + CHECK(childout.find('d') == 3); + CHECK(childout.find("d", 3) == 3); + CHECK(childout.find('d', 3) == 3); + CHECK(childout.find("d", 4) == LLProcess::ReadPipe::npos); + CHECK(childout.find('d', 4) == LLProcess::ReadPipe::npos); // The case of offset == end and offset > end are different. In the // first case, we can form a valid (albeit empty) iterator range and // search that. In the second, guard logic in the implementation must // realize we can't form a valid iterator range. - ensure("find(\"d\", 26)", childout.find("d", 26) == LLProcess::ReadPipe::npos); - ensure("find('d', 26)", childout.find('d', 26) == LLProcess::ReadPipe::npos); - ensure("find(\"d\", 27)", childout.find("d", 27) == LLProcess::ReadPipe::npos); - ensure("find('d', 27)", childout.find('d', 27) == LLProcess::ReadPipe::npos); - ensure_equals("find(\"ghi\")", childout.find("ghi"), 6); - ensure_equals("find(\"ghi\", 6)", childout.find("ghi"), 6); - ensure("find(\"ghi\", 7)", childout.find("ghi", 7) == LLProcess::ReadPipe::npos); - ensure("find(\"ghi\", 26)", childout.find("ghi", 26) == LLProcess::ReadPipe::npos); - ensure("find(\"ghi\", 27)", childout.find("ghi", 27) == LLProcess::ReadPipe::npos); + CHECK(childout.find("d", 26) == LLProcess::ReadPipe::npos); + CHECK(childout.find('d', 26) == LLProcess::ReadPipe::npos); + CHECK(childout.find("d", 27) == LLProcess::ReadPipe::npos); + CHECK(childout.find('d', 27) == LLProcess::ReadPipe::npos); + CHECK(childout.find("ghi") == 6); + CHECK(childout.find("ghi") == 6); + CHECK(childout.find("ghi", 7) == LLProcess::ReadPipe::npos); + CHECK(childout.find("ghi", 26) == LLProcess::ReadPipe::npos); + CHECK(childout.find("ghi", 27) == LLProcess::ReadPipe::npos); } - template<> template<> - void object::test<22>() + TEST_CASE_METHOD(llprocess_data, "llprocess: postend reports a launch that failed validation", "[llprocess]") { - set_test_name("bad postend"); std::string pumpname("postend"); EventListener listener(LLEventPumps::instance().obtain(pumpname)); LLProcess::Params params; - params.desc = get_test_name(); + params.desc = ll_test::currentTestName(); params.postend = pumpname; LLProcessPtr child = LLProcess::create(params); - ensure("shouldn't have launched", ! child); + CHECK_FALSE(child); listener.checkHistory( [¶ms](const EventListener::Listory& history) { - ensure_equals("number of postend events", history.size(), 1); + REQUIRE(history.size() == 1); LLSD postend(history.front()); - ensure("has id", ! postend.has("id")); - ensure_equals("desc", postend["desc"].asString(), std::string(params.desc)); - ensure_equals("state", postend["state"].asInteger(), LLProcess::UNSTARTED); - ensure("has data", ! postend.has("data")); + REQUIRE_FALSE(postend.has("id")); + REQUIRE(postend["desc"].asString() == std::string(params.desc)); + REQUIRE(postend["state"].asInteger() == LLProcess::UNSTARTED); + REQUIRE_FALSE(postend.has("data")); std::string error(postend["string"]); // All we get from canned parameter validation is a bool, so the // "validation failed" message we ourselves generate can't mention // "executable" by name. Just check that it's nonempty. //ensure_contains("error", error, "executable"); - ensure("string", ! error.empty()); + REQUIRE_FALSE(error.empty()); }); } - template<> template<> - void object::test<23>() + TEST_CASE_METHOD(llprocess_data, "llprocess: postend reports the child's id and exit code", "[llprocess]") { - set_test_name("good postend"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.exit(35)\n"); std::string pumpname("postend"); @@ -1633,16 +1613,17 @@ namespace tut listener.checkHistory( [i, timeout, childid](const EventListener::Listory& history) { - ensure("no postend event", i < timeout); - ensure_equals("number of postend events", history.size(), 1); + UNSCOPED_INFO("no postend event"); + REQUIRE(i < timeout); + REQUIRE(history.size() == 1); LLSD postend(history.front()); - ensure_equals("id", postend["id"].asInteger(), childid); - ensure("desc empty", ! postend["desc"].asString().empty()); - ensure_equals("state", postend["state"].asInteger(), LLProcess::EXITED); - ensure_equals("data", postend["data"].asInteger(), 35); + REQUIRE(postend["id"].asInteger() == childid); + REQUIRE_FALSE(postend["desc"].asString().empty()); + REQUIRE(postend["state"].asInteger() == LLProcess::EXITED); + REQUIRE(postend["data"].asInteger() == 35); std::string str(postend["string"]); - ensure_contains("string", str, "exited"); - ensure_contains("string", str, "35"); + REQUIRE_THAT(str, Catch::Matchers::ContainsSubstring("exited")); + REQUIRE_THAT(str, Catch::Matchers::ContainsSubstring("35")); }); } @@ -1666,7 +1647,9 @@ namespace tut bool postend(const LLSD&) { mTriggered = true; - ensure_equals("postend listener", mReadPipe.read(mReadPipe.size()), mExpect); + // A CHECK: a REQUIRE's throw would go back through LLProcess's + // own handling of the child's end, mid-post. + CHECK(mReadPipe.read(mReadPipe.size()) == mExpect); return false; } @@ -1677,11 +1660,9 @@ namespace tut }; } // anonymous namespace - template<> template<> - void object::test<24>() + TEST_CASE_METHOD(llprocess_data, "llprocess: all output is readable when postend fires", "[llprocess]") { - set_test_name("all data visible at postend"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" // note, no '\n' in written data "sys.stdout.write('partial line')\n"); @@ -1694,14 +1675,12 @@ namespace tut pumpname, "partial line"); waitfor(*py.mPy); - ensure("postend never triggered", listener.mTriggered); + CHECK(listener.mTriggered); } - template<> template<> - void object::test<25>() + TEST_CASE_METHOD(llprocess_data, "llprocess: a megabyte written to stdin reaches the child whole", "[llprocess]") { - set_test_name("large stdin write"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "expected = int(sys.argv[1])\n" "data = sys.stdin.buffer.read(expected)\n" @@ -1728,19 +1707,19 @@ namespace tut { yield(); } - ensure("large stdin write timed out", i < timeout); - ensure("child never replied", childout.contains("\n")); - ensure_equals("large stdin ack", childout.getline(), "ok"); + UNSCOPED_INFO("large stdin write timed out"); + CHECK(i < timeout); + UNSCOPED_INFO("child never replied"); + CHECK(childout.contains("\n")); + CHECK(childout.getline() == "ok"); waitfor(*py.mPy); - ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED); - ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0); + CHECK(py.mPy->getStatus().mState == LLProcess::EXITED); + CHECK(py.mPy->getStatus().mData == 0); } - template<> template<> - void object::test<26>() + TEST_CASE_METHOD(llprocess_data, "llprocess: pipes on all three standard streams work at once", "[llprocess]") { - set_test_name("all three pipes active"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.stdout.write('stdout message\\n')\n" "sys.stderr.write('stderr message\\n')\n" @@ -1762,23 +1741,23 @@ namespace tut { yield(); } - ensure("initial output timeout", i < timeout); + UNSCOPED_INFO("initial output timeout"); + CHECK(i < timeout); - ensure_equals("stdout message", childout.getline(), "stdout message"); - ensure_equals("stderr message", childerr.getline(), "stderr message"); + CHECK(childout.getline() == "stdout message"); + CHECK(childerr.getline() == "stderr message"); py.mPy->getWritePipe().get_ostream() << "test input" << std::endl; waitfor(*py.mPy); - ensure("script never replied", childout.contains("\n")); - ensure_equals("echo response", childout.getline(), "received: test input"); + UNSCOPED_INFO("script never replied"); + CHECK(childout.contains("\n")); + CHECK(childout.getline() == "received: test input"); } - template<> template<> - void object::test<27>() + TEST_CASE_METHOD(llprocess_data, "llprocess: status goes from RUNNING to EXITED", "[llprocess]") { - set_test_name("process state transitions"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import time\n" "time.sleep(1)\n"); @@ -1786,21 +1765,19 @@ namespace tut // Immediately after launch LLProcess::Status status = py.mPy->getStatus(); - ensure_equals("post-launch state", status.mState, LLProcess::RUNNING); - ensure("process is running", py.mPy->isRunning()); + CHECK(status.mState == LLProcess::RUNNING); + CHECK(py.mPy->isRunning()); // After completion waitfor(*py.mPy); status = py.mPy->getStatus(); - ensure_equals("post-completion state", status.mState, LLProcess::EXITED); - ensure("process is not running", !py.mPy->isRunning()); + CHECK(status.mState == LLProcess::EXITED); + CHECK_FALSE(py.mPy->isRunning()); } - template<> template<> - void object::test<28>() + TEST_CASE_METHOD(llprocess_data, "llprocess: setLimit holds for every event under rapid output", "[llprocess]") { - set_test_name("ReadPipe limit with rapid data"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "for i in range(100):\n" " sys.stdout.write('line %d\\n' % i)\n" @@ -1823,17 +1800,15 @@ namespace tut for (const LLSD& event : history) { const std::string data = event["data"].asString(); - ensure("event data within limit", data.length() <= 50); + REQUIRE(data.length() <= 50); saw_data = saw_data || !data.empty(); } - ensure("saw at least one data event", saw_data); + REQUIRE(saw_data); }); } - template<> template<> - void object::test<29>() + TEST_CASE_METHOD(llprocess_data, "llprocess: a nonexistent executable fails with an UNSTARTED postend", "[llprocess]") { - set_test_name("nonexistent executable"); LLProcess::Params params; params.executable = "/path/to/nonexistent/executable"; std::string pumpname("postend_invalid"); @@ -1842,24 +1817,22 @@ namespace tut EventListener listener(LLEventPumps::instance().obtain(pumpname)); LLProcessPtr child = LLProcess::create(params); - ensure("should not create invalid process", !child); + CHECK_FALSE(child); listener.checkHistory( [](const EventListener::Listory& history) { - ensure_equals("got failure event", history.size(), 1); + REQUIRE(history.size() == 1); LLSD event = history.front(); - ensure_equals("state is UNSTARTED", - event["state"].asInteger(), LLProcess::UNSTARTED); - ensure("has error string", !event["string"].asString().empty()); + REQUIRE(event["state"].asInteger() == LLProcess::UNSTARTED); + UNSCOPED_INFO("has error string"); + REQUIRE_FALSE(event["string"].asString().empty()); }); } - template<> template<> - void object::test<30>() + TEST_CASE_METHOD(llprocess_data, "llprocess: a running child has a valid id and handle", "[llprocess]") { - set_test_name("process ID and handle validity"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import time\n" "time.sleep(1)\n"); py.launch(); @@ -1867,29 +1840,26 @@ namespace tut LLProcess::id pid = py.mPy->getProcessID(); LLProcess::handle handle = py.mPy->getProcessHandle(); - ensure("PID is valid", pid != 0); - ensure("handle is valid", handle != 0); + CHECK(pid != 0); + CHECK(handle != 0); #if LL_WINDOWS // On Windows, verify handle is a valid process handle DWORD exitCode; - ensure("GetExitCodeProcess succeeds", - GetExitCodeProcess(handle, &exitCode) != 0); - ensure_equals("process still running", exitCode, STILL_ACTIVE); + REQUIRE(GetExitCodeProcess(handle, &exitCode) != 0); + CHECK(exitCode == STILL_ACTIVE); #else // On POSIX, PID and handle should be the same - ensure_equals("PID equals handle", pid, handle); - ensure("process still running", py.mPy->isRunning()); + CHECK(pid == handle); + CHECK(py.mPy->isRunning()); #endif waitfor(*py.mPy); } - template<> template<> - void object::test<31>() + TEST_CASE_METHOD(llprocess_data, "llprocess: ReadPipe find and peek handle the ends of the data", "[llprocess]") { - set_test_name("ReadPipe search at boundaries"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import sys\n" "sys.stdout.write('abcdefghijklmnopqrstuvwxyz')\n"); py.mParams.files.add(LLProcess::FileParam()); // stdin @@ -1900,39 +1870,34 @@ namespace tut waitfor(*py.mPy); // Test find at exact end - ensure("find at end succeeds", - childout.find("xyz", 23) != LLProcess::ReadPipe::npos); - ensure("find past end returns npos", - childout.find("a", 30) == LLProcess::ReadPipe::npos); + CHECK(childout.find("xyz", 23) != LLProcess::ReadPipe::npos); + CHECK(childout.find("a", 30) == LLProcess::ReadPipe::npos); // Test empty string search - ensure("contains empty string", childout.contains("")); + CHECK(childout.contains("")); // Test single char at boundaries - ensure_equals("find 'a' at 0", childout.find('a', 0), 0); - ensure_equals("find 'z' at end", childout.find('z'), 25); + CHECK(childout.find('a', 0) == 0); + CHECK(childout.find('z') == 25); // Test peek at boundaries - ensure_equals("peek at exact size", childout.peek(26), ""); - ensure_equals("peek past end", childout.peek(30, 10), ""); + CHECK(childout.peek(26) == ""); + CHECK(childout.peek(30, 10) == ""); } - template<> template<> - void object::test<32>() + TEST_CASE_METHOD(llprocess_data, "llprocess: ten short-lived children in a row each exit cleanly", "[llprocess]") { - set_test_name("rapid process lifecycle"); const int ITERATIONS = 10; for (int i = 0; i < ITERATIONS; ++i) { - PythonProcessLauncher py(STRINGIZE(get_test_name() << " " << i), + PythonProcessLauncher py(STRINGIZE(ll_test::currentTestName() << " " << i), "import sys\n" "sys.exit(0)\n"); py.run(); - ensure_equals("quick exit status", - py.mPy->getStatus().mState, LLProcess::EXITED); - ensure_equals("quick exit code", - py.mPy->getStatus().mData, 0); + CAPTURE(i); + REQUIRE(py.mPy->getStatus().mState == LLProcess::EXITED); + REQUIRE(py.mPy->getStatus().mData == 0); // Let the process object destroy } @@ -1940,11 +1905,9 @@ namespace tut yield(0); } - template<> template<> - void object::test<33>() + TEST_CASE_METHOD(llprocess_data, "llprocess: a silent child posts one empty eof event on each pipe", "[llprocess]") { - set_test_name("process with no output"); - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import time\n" "time.sleep(1)\n" "# Produce no output\n"); @@ -1972,48 +1935,47 @@ namespace tut LLEventPumps::instance().obtain("mainloop").post(LLSD()); } - ensure_equals("stdout size", childout.size(), 0); - ensure_equals("stderr size", childerr.size(), 0); - ensure_equals("process exited", py.mPy->getStatus().mState, LLProcess::EXITED); + CHECK(childout.size() == 0); + CHECK(childerr.size() == 0); + CHECK(py.mPy->getStatus().mState == LLProcess::EXITED); - auto check_eof = [](const EventListener::Listory& history, const std::string& which) + auto require_eof = [](const EventListener::Listory& history, const std::string& which) { - ensure_equals(STRINGIZE(which << " events"), history.size(), 1); + CAPTURE(which); + REQUIRE(history.size() == 1); const LLSD& event = history.front(); - ensure(STRINGIZE(which << " eof event"), event["eof"].asBoolean()); - ensure_equals(STRINGIZE(which << " len"), event["len"].asInteger(), 0); + REQUIRE(event["eof"].asBoolean()); + REQUIRE(event["len"].asInteger() == 0); }; outListener.checkHistory( [&](const EventListener::Listory& history) { - check_eof(history, "stdout"); + require_eof(history, "stdout"); }); errListener.checkHistory( [&](const EventListener::Listory& history) { - check_eof(history, "stderr"); + require_eof(history, "stderr"); }); } - template<> template<> - void object::test<34>() + TEST_CASE_METHOD(llprocess_data, "llprocess: tick() completes quickly after kill()", "[llprocess]") { - set_test_name("tick() completes quickly after kill()"); // Regression test: tick() must not block after kill() is called. // The old Windows code called WaitForSingleObject(..., 100) in tick(), // causing a 100 ms stall on the main thread. This test ensures that a // mainloop tick completes well under that threshold even when the child // process has been killed and may still be exiting. - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import time\n" "time.sleep(120)\n"); py.launch(); // Wait for the process to start up yield(); - ensure("process started", py.mPy->isRunning()); + CHECK(py.mPy->isRunning()); // Send the kill signal py.mPy->kill(); @@ -2029,30 +1991,27 @@ namespace tut auto elapsed_ms = std::chrono::duration_cast( std::chrono::steady_clock::now() - start).count(); - ensure(STRINGIZE("tick() took " << elapsed_ms << " ms, expected < 75 ms"), - elapsed_ms < 75); + CHECK(elapsed_ms < 75); // Let the process fully exit so cleanup is orderly waitfor(*py.mPy); } - template<> template<> - void object::test<35>() + TEST_CASE_METHOD(llprocess_data, "llprocess: LLProcess destructor completes quickly after kill()", "[llprocess]") { - set_test_name("LLProcess destructor completes quickly after kill()"); // Regression test: after an explicit kill() call the destructor must // not perform a blocking wait. The old Windows code called // WaitForSingleObject(..., 100) in the destructor, causing a 100 ms // stall on the main thread. With mKillCalled set to true, the // destructor skips the termination/wait block entirely. - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "import time\n" "time.sleep(120)\n"); py.launch(); // Wait for the process to start up yield(); - ensure("process started", py.mPy->isRunning()); + CHECK(py.mPy->isRunning()); // Kill the process (sets mKillCalled = true) py.mPy->kill(); @@ -2063,43 +2022,36 @@ namespace tut auto elapsed_ms = std::chrono::duration_cast( std::chrono::steady_clock::now() - start).count(); - ensure(STRINGIZE("destructor took " << elapsed_ms << " ms, expected < 75 ms"), - elapsed_ms < 75); + CHECK(elapsed_ms < 75); } - template<> template<> - void object::test<36>() + TEST_CASE_METHOD(llprocess_data, "llprocess: autokill ends a child when its parent exits", "[llprocess]") { - set_test_name("autokill ensures child termination on parent exit"); #if !LL_WINDOWS - skip("Windows-specific test"); + SKIP("Windows-specific test"); #else - verify_autokill_on_helper_exit(get_test_name(), 1); + verify_autokill_on_helper_exit(ll_test::currentTestName(), 1); #endif } - template<> template<> - void object::test<37>() + TEST_CASE_METHOD(llprocess_data, "llprocess: autokill ends every child when their parent exits", "[llprocess]") { - set_test_name("multiple processes with autokill"); #if !LL_WINDOWS - skip("Windows-specific test"); + SKIP("Windows-specific test"); #else - verify_autokill_on_helper_exit(get_test_name(), 2); + verify_autokill_on_helper_exit(ll_test::currentTestName(), 2); #endif } - template<> template<> - void object::test<38>() + TEST_CASE_METHOD(llprocess_data, "llprocess: attached=true with autokill=false still kills on destruction", "[llprocess]") { - set_test_name("attached=true, autokill=false"); // 'attached' governs destruction of the LLProcess object, 'autokill' // governs termination of the parent, so this combination must still // kill the child on destruction. NamedTempFile out("out", "not started"); LLProcess::handle phandle(0); { - PythonProcessLauncher py(get_test_name(), + PythonProcessLauncher py(ll_test::currentTestName(), "from __future__ import with_statement\n" "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -2121,13 +2073,14 @@ namespace tut if (readfile(out.getPath(), "from kill() script") == "ok") break; } - ensure("script never started", i < timeout); + UNSCOPED_INFO("script never started"); + CHECK(i < timeout); // Script has performed its first write and should now be sleeping. // Destroy the LLProcess, which should kill the child. } waitfor(phandle, "kill() script"); // Had the destructor detached instead, the script would have woken on // its own and overwritten the file with 'bad'. - ensure_equals(get_test_name() + " script output", readfile(out.getPath()), "ok"); + CHECK(readfile(out.getPath()) == "ok"); } } // namespace tut diff --git a/indra/llcommon/tests/llprocessor_test.cpp b/indra/llcommon/tests/llprocessor_test.cpp index a2467d9205..414a71e7a6 100644 --- a/indra/llcommon/tests/llprocessor_test.cpp +++ b/indra/llcommon/tests/llprocessor_test.cpp @@ -25,26 +25,15 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llprocessor.h" -namespace tut +namespace { - struct processor + TEST_CASE("LLProcessor: LLProcessorInfo names the CPU and gives a sane frequency", "[LLProcessor]") { - }; - - typedef test_group processor_t; - typedef processor_t::object processor_object_t; - tut::processor_t tut_processor("LLProcessor"); - - template<> template<> - void processor_object_t::test<1>() - { - set_test_name("LLProcessorInfo regression test"); - LLProcessorInfo pi; F64 freq = pi.getCPUFrequency(); //bool sse = pi.hasSSE(); @@ -54,8 +43,9 @@ namespace tut std::string brand = pi.getCPUBrandName(); //std::string steam = pi.getCPUFeatureDescription(); - ensure_not_equals("Unknown Brand name", brand, "Unknown"); - ensure_not_equals("Unknown Family name", family, "Unknown"); - ensure("Reasonable CPU Frequency > 100 && < 10000", freq > 100 && freq < 10000); + CHECK(brand != "Unknown"); + CHECK(family != "Unknown"); + CHECK(freq > 100); + CHECK(freq < 10000); } } diff --git a/indra/llcommon/tests/llprocinfo_test.cpp b/indra/llcommon/tests/llprocinfo_test.cpp index 6a151048c2..d647e036fd 100644 --- a/indra/llcommon/tests/llprocinfo_test.cpp +++ b/indra/llcommon/tests/llprocinfo_test.cpp @@ -28,14 +28,14 @@ #include "../llprocinfo.h" -#include "../test/lltut.h" +#include #include "../lltimer.h" static const LLProcInfo::time_type bad_user(289375U), bad_system(275U); -namespace tut +namespace { struct procinfo_test @@ -45,35 +45,24 @@ struct procinfo_test } }; -typedef test_group procinfo_group_t; -typedef procinfo_group_t::object procinfo_object_t; -tut::procinfo_group_t procinfo_instance("LLProcInfo"); - - // Basic invocation works -template<> template<> -void procinfo_object_t::test<1>() +TEST_CASE_METHOD(procinfo_test, "LLProcInfo: getCPUUsage() writes both its arguments", "[LLProcInfo]") { LLProcInfo::time_type user(bad_user), system(bad_system); - set_test_name("getCPUUsage() basic function"); - LLProcInfo::getCPUUsage(user, system); - ensure_not_equals("getCPUUsage() writes to its user argument", user, bad_user); - ensure_not_equals("getCPUUsage() writes to its system argument", system, bad_system); + CHECK(user != bad_user); + CHECK(system != bad_system); } // Time increases -template<> template<> -void procinfo_object_t::test<2>() +TEST_CASE_METHOD(procinfo_test, "LLProcInfo: getCPUUsage() never goes down over time", "[LLProcInfo]") { LLProcInfo::time_type user(bad_user), system(bad_system); LLProcInfo::time_type user2(bad_user), system2(bad_system); - set_test_name("getCPUUsage() increases over time"); - LLProcInfo::getCPUUsage(user, system); for (int i(0); i < 100000; ++i) @@ -83,9 +72,9 @@ void procinfo_object_t::test<2>() LLProcInfo::getCPUUsage(user2, system2); - ensure_equals("getCPUUsage() user value doesn't decrease over time", user2 >= user, true); - ensure_equals("getCPUUsage() system value doesn't decrease over time", system2 >= system, true); + CHECK(user2 >= user); + CHECK(system2 >= system); } -} // end namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llptrto_test.cpp b/indra/llcommon/tests/llptrto_test.cpp index 0f81f1688d..525f36b50e 100644 --- a/indra/llcommon/tests/llptrto_test.cpp +++ b/indra/llcommon/tests/llptrto_test.cpp @@ -37,7 +37,7 @@ // other Linden headers #include "../llmemory.h" -#include "../test/lltut.h" +#include namespace { @@ -101,25 +101,16 @@ namespace } /***************************************************************************** -* tut test group +* Tests *****************************************************************************/ -namespace tut +namespace { - struct llptrto_data - { - }; - typedef test_group llptrto_group; - typedef llptrto_group::object llptrto_object; - llptrto_group llptrtogrp("LLPtrTo"); - // The checks above are static_asserts, made when this file compiles. Here, // what LLPtrTo names for a refcounted class holds a reference, as an // LLPointer does. - template<> template<> - void llptrto_object::test<1>() + TEST_CASE("LLPtrTo: LLPtrTo of a refcounted class holds a reference", "[LLPtrTo]") { - set_test_name("LLPtrTo of a refcounted class holds a reference"); LLPtrTo::type ptr(new RCFoo); - ensure_equals("one reference", ptr->getNumRefs(), 1); + CHECK(ptr->getNumRefs() == 1); } } diff --git a/indra/llcommon/tests/llrand_test.cpp b/indra/llcommon/tests/llrand_test.cpp index fca8275c6c..720615d4f7 100644 --- a/indra/llcommon/tests/llrand_test.cpp +++ b/indra/llcommon/tests/llrand_test.cpp @@ -26,7 +26,7 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../test/alrandomreseed.h" #include "../llrand.h" @@ -42,17 +42,16 @@ // In llrand.h, every function is documented to return less than the high end // -- specifically, because you can pass a negative extent, they're documented // never to return a value equal to the extent. -// So that we don't need two different versions of ensure_in_range(), when +// So that we don't need two different versions of require_in_range(), when // testing extent < 0, negate the return value and the extent before passing -// into ensure_in_range(). +// into require_in_range(). template -static void ensure_in_range(const std::string_view& name, - NUMBER value, NUMBER low, NUMBER high) +static void require_in_range(const std::string_view& name, + NUMBER value, NUMBER low, NUMBER high) { - auto failmsg{ stringize(name, " >= ", low, " (", value, ')') }; - tut::ensure(failmsg, (value >= low)); - failmsg = stringize(name, " < ", high, " (", value, ')'); - tut::ensure(failmsg, (value < high)); + INFO(name); + REQUIRE(value >= low); + REQUIRE(value < high); } namespace @@ -75,7 +74,7 @@ namespace } // That `draws` spread over `counts` as evenly as chance allows. - void ensure_uniform(const std::string& name, const std::vector& counts, U64 draws) + void require_uniform(const std::string& name, const std::vector& counts, U64 draws) { const F64 expected = F64(draws) / F64(counts.size()); F64 stat = 0.0; @@ -85,11 +84,12 @@ namespace stat += off * off / expected; } const F64 limit = chi_square_limit(counts.size() - 1); - tut::ensure(stringize(name, ": chi-square ", stat, " under ", limit), stat < limit); + INFO(name << ": chi-square"); + REQUIRE(stat < limit); } // That draws uniform over [0, 1) have its mean, 1/2, and variance, 1/12. - void ensure_unit_moments(const std::string& name, F64 sum, F64 sum_of_squares, U64 draws) + void require_unit_moments(const std::string& name, F64 sum, F64 sum_of_squares, U64 draws) { const F64 n = F64(draws); const F64 mean = sum / n; @@ -98,8 +98,10 @@ namespace // fourth central moment, 1/80, less its square, over n. const F64 mean_bound = ONE_IN_A_MILLION_Z * std::sqrt(1.0 / 12.0 / n); const F64 variance_bound = ONE_IN_A_MILLION_Z * std::sqrt((1.0 / 80.0 - 1.0 / 144.0) / n); - tut::ensure(stringize(name, ": mean ", mean), std::fabs(mean - 0.5) < mean_bound); - tut::ensure(stringize(name, ": variance ", variance), std::fabs(variance - 1.0 / 12.0) < variance_bound); + INFO(name); + CAPTURE(mean, variance); + REQUIRE(std::fabs(mean - 0.5) < mean_bound); + REQUIRE(std::fabs(variance - 1.0 / 12.0) < variance_bound); } // Every kind of draw, interleaved, so a sequence differs if any of them @@ -130,7 +132,7 @@ namespace } } -namespace tut +namespace { struct random { @@ -138,126 +140,111 @@ namespace tut ~random() { ll_test::reseedRandomFromEntropy(); } }; - typedef test_group random_t; - typedef random_t::object random_object_t; - tut::random_t tut_random("LLSeedRand"); - - template<> template<> - void random_object_t::test<1>() + TEST_CASE_METHOD(random, "LLSeedRand: frand() draws at least 0 and under 1", "[LLSeedRand]") { for(S32 ii = 0; ii < 100000; ++ii) { - ensure_in_range("frand", ll_frand(), 0.0f, 1.0f); + require_in_range("frand", ll_frand(), 0.0f, 1.0f); } } - template<> template<> - void random_object_t::test<2>() + TEST_CASE_METHOD(random, "LLSeedRand: drand() draws at least 0 and under 1", "[LLSeedRand]") { for(S32 ii = 0; ii < 100000; ++ii) { - ensure_in_range("drand", ll_drand(), 0.0, 1.0); + require_in_range("drand", ll_drand(), 0.0, 1.0); } } - template<> template<> - void random_object_t::test<3>() + TEST_CASE_METHOD(random, "LLSeedRand: frand(2.0f) draws at least 0 and under 2", "[LLSeedRand]") { for(S32 ii = 0; ii < 100000; ++ii) { - ensure_in_range("frand(2.0f)", ll_frand(2.0f) - 1.0f, -1.0f, 1.0f); + require_in_range("frand(2.0f)", ll_frand(2.0f) - 1.0f, -1.0f, 1.0f); } } - template<> template<> - void random_object_t::test<4>() + TEST_CASE_METHOD(random, "LLSeedRand: frand(-7.0) draws at most 0 and over -7", "[LLSeedRand]") { for(S32 ii = 0; ii < 100000; ++ii) { // Negate the result so we don't have to allow a templated low-end // comparison as well. - ensure_in_range("-frand(-7.0)", -ll_frand(-7.0), 0.0f, 7.0f); + require_in_range("-frand(-7.0)", -ll_frand(-7.0), 0.0f, 7.0f); } } - template<> template<> - void random_object_t::test<5>() + TEST_CASE_METHOD(random, "LLSeedRand: drand(-2.0) draws at most 0 and over -2", "[LLSeedRand]") { for(S32 ii = 0; ii < 100000; ++ii) { - ensure_in_range("-drand(-2.0)", -ll_drand(-2.0), 0.0, 2.0); + require_in_range("-drand(-2.0)", -ll_drand(-2.0), 0.0, 2.0); } } - template<> template<> - void random_object_t::test<6>() + TEST_CASE_METHOD(random, "LLSeedRand: rand(100) draws at least 0 and under 100", "[LLSeedRand]") { for(S32 ii = 0; ii < 100000; ++ii) { - ensure_in_range("rand(100)", ll_rand(100), 0, 100); + require_in_range("rand(100)", ll_rand(100), 0, 100); } } - template<> template<> - void random_object_t::test<7>() + TEST_CASE_METHOD(random, "LLSeedRand: rand(-127) draws at most 0 and over -127", "[LLSeedRand]") { for(S32 ii = 0; ii < 100000; ++ii) { - ensure_in_range("-rand(-127)", -ll_rand(-127), 0, 127); + require_in_range("-rand(-127)", -ll_rand(-127), 0, 127); } } - // Tests 8 to 15 hold for any generator under llrand. Tests 16 to 18 are - // what the generator itself draws, and change when it does. + // The cases from here to the one on scaling unit values hold for any + // generator under llrand. The last three are what the generator itself + // draws, and change when it does. - template<> template<> - void random_object_t::test<8>() + TEST_CASE_METHOD(random, "LLSeedRand: extents of zero or one give zero and the widest extents stay in range", "[LLSeedRand]") { - set_test_name("an extent of zero or one gives zero, and the widest extents hold their draws"); ll_rand_seed(8); for (S32 ii = 0; ii < 10000; ++ii) { - ensure_equals("rand(0)", ll_rand(0), 0); - ensure_equals("rand(1)", ll_rand(1), 0); - ensure_equals("rand(-1)", ll_rand(-1), 0); - ensure_equals("frand(0)", ll_frand(0.f), 0.f); - ensure_equals("drand(0)", ll_drand(0.0), 0.0); + REQUIRE(ll_rand(0) == 0); + REQUIRE(ll_rand(1) == 0); + REQUIRE(ll_rand(-1) == 0); + REQUIRE(ll_frand(0.f) == 0.f); + REQUIRE(ll_drand(0.0) == 0.0); - ensure_in_range("rand(INT_MAX)", ll_rand(INT_MAX), 0, INT_MAX); + require_in_range("rand(INT_MAX)", ll_rand(INT_MAX), 0, INT_MAX); const S32 lowest = ll_rand(INT_MIN); - ensure(stringize("rand(INT_MIN) in (INT_MIN, 0] (", lowest, ')'), lowest > INT_MIN && lowest <= 0); + CAPTURE(lowest); + REQUIRE((lowest > INT_MIN && lowest <= 0)); - ensure_in_range("frand(FLT_MAX)", ll_frand(std::numeric_limits::max()), 0.f, std::numeric_limits::max()); - ensure_in_range("frand(FLT_MIN)", ll_frand(std::numeric_limits::min()), 0.f, std::numeric_limits::min()); - ensure_in_range("drand(DBL_MAX)", ll_drand(std::numeric_limits::max()), 0.0, std::numeric_limits::max()); - ensure_in_range("drand(DBL_MIN)", ll_drand(std::numeric_limits::min()), 0.0, std::numeric_limits::min()); + require_in_range("frand(FLT_MAX)", ll_frand(std::numeric_limits::max()), 0.f, std::numeric_limits::max()); + require_in_range("frand(FLT_MIN)", ll_frand(std::numeric_limits::min()), 0.f, std::numeric_limits::min()); + require_in_range("drand(DBL_MAX)", ll_drand(std::numeric_limits::max()), 0.0, std::numeric_limits::max()); + require_in_range("drand(DBL_MIN)", ll_drand(std::numeric_limits::min()), 0.0, std::numeric_limits::min()); } } - template<> template<> - void random_object_t::test<9>() + TEST_CASE_METHOD(random, "LLSeedRand: a seed repeats its draws on any thread and another seed differs", "[LLSeedRand]") { - set_test_name("a seed draws the same again, on any thread, and another seed draws otherwise"); ll_rand_seed(1234); const std::vector first = draw_each(16); ll_rand_seed(1234); - ensure("the same seed draws the same", draw_each(16) == first); + CHECK(draw_each(16) == first); std::vector elsewhere; std::thread([&elsewhere] { ll_rand_seed(1234); elsewhere = draw_each(16); }).join(); - ensure("on another thread too", elsewhere == first); + CHECK(elsewhere == first); ll_rand_seed(1235); - ensure("another seed draws otherwise", draw_each(16) != first); + CHECK(draw_each(16) != first); } - template<> template<> - void random_object_t::test<10>() + TEST_CASE_METHOD(random, "LLSeedRand: rand(n) draws each of its n values as often as the others", "[LLSeedRand]") { - set_test_name("rand(n) draws each of its n values as often as the others"); ll_rand_seed(10); const U64 draws = 256000; for (const S32 n : { 2, 3, 7, 100, 256 }) @@ -267,7 +254,7 @@ namespace tut { ++counts.at(ll_rand(n)); } - ensure_uniform(stringize("rand(", n, ")"), counts, draws); + require_uniform(stringize("rand(", n, ")"), counts, draws); } std::vector counts(7); @@ -275,13 +262,11 @@ namespace tut { ++counts.at(-ll_rand(-7)); } - ensure_uniform("rand(-7)", counts, draws); + require_uniform("rand(-7)", counts, draws); } - template<> template<> - void random_object_t::test<11>() + TEST_CASE_METHOD(random, "LLSeedRand: frand() and drand() spread evenly over the unit interval", "[LLSeedRand]") { - set_test_name("frand() and drand() spread evenly over [0, 1)"); ll_rand_seed(11); const U64 draws = 1 << 20; std::vector frand_bins(64); @@ -300,16 +285,14 @@ namespace tut drand_sum += d; drand_squares += d * d; } - ensure_uniform("frand", frand_bins, draws); - ensure_uniform("drand", drand_bins, draws); - ensure_unit_moments("frand", frand_sum, frand_squares, draws); - ensure_unit_moments("drand", drand_sum, drand_squares, draws); + require_uniform("frand", frand_bins, draws); + require_uniform("drand", drand_bins, draws); + require_unit_moments("frand", frand_sum, frand_squares, draws); + require_unit_moments("drand", drand_sum, drand_squares, draws); } - template<> template<> - void random_object_t::test<12>() + TEST_CASE_METHOD(random, "LLSeedRand: rand_u32() draws each byte evenly and sets each bit half the time", "[LLSeedRand]") { - set_test_name("rand_u32() draws each of its bytes evenly and sets each bit half the time"); ll_rand_seed(12); const U64 draws = 256000; std::vector> bytes(4, std::vector(256)); @@ -328,32 +311,29 @@ namespace tut } for (U32 byte = 0; byte < 4; ++byte) { - ensure_uniform(stringize("byte ", byte), bytes[byte], draws); + require_uniform(stringize("byte ", byte), bytes[byte], draws); } for (U32 bit = 0; bit < 32; ++bit) { - ensure_uniform(stringize("bit ", bit), { ones[bit], draws - ones[bit] }, draws); + require_uniform(stringize("bit ", bit), { ones[bit], draws - ones[bit] }, draws); } } - template<> template<> - void random_object_t::test<13>() + TEST_CASE_METHOD(random, "LLSeedRand: a thread that was never seeded draws apart from every other", "[LLSeedRand]") { - set_test_name("a thread that was never seeded draws apart from every other"); std::vector one, two; std::thread first([&one] { one = draw_drand(8); }); std::thread second([&two] { two = draw_drand(8); }); first.join(); second.join(); const std::vector here = draw_drand(8); - ensure("two threads draw apart", one != two); - ensure("and apart from this one", one != here && two != here); + CHECK(one != two); + CHECK(one != here); + CHECK(two != here); } - template<> template<> - void random_object_t::test<14>() + TEST_CASE_METHOD(random, "LLSeedRand: seeding and drawing on another thread leaves this thread's draws alone", "[LLSeedRand]") { - set_test_name("seeding and drawing on another thread leaves this thread's draws alone"); ll_rand_seed(14); std::vector drawn = draw_drand(4); std::thread([] { @@ -364,67 +344,70 @@ namespace tut drawn.insert(drawn.end(), after.begin(), after.end()); ll_rand_seed(14); - ensure("this thread's draws went on as if alone", draw_drand(8) == drawn); + CHECK(draw_drand(8) == drawn); } - template<> template<> - void random_object_t::test<15>() + TEST_CASE_METHOD(random, "LLSeedRand: unit values scale short of the extent and integers truncate toward zero", "[LLSeedRand]") { - set_test_name("a unit value scales to each extent, an integer truncated toward zero, and never reaches it"); using namespace ALRandMap; const F64 top53 = 1.0 - 0x1p-53; const F64 top48 = 1.0 - 0x1p-48; - ensure_equals("0 of 100", extentS32(0.0, 100), 0); - ensure_equals("a half of 100", extentS32(0.5, 100), 50); - ensure_equals("the top of 100, to 48 bits", extentS32(top48, 100), 99); - ensure_equals("the top of 100, to 53 bits", extentS32(top53, 100), 99); - ensure_equals("the top of INT_MAX", extentS32(top53, INT_MAX), INT_MAX - 1); - ensure_equals("the top of 2^30", extentS32(top53, 1 << 30), (1 << 30) - 1); - ensure_equals("a half of -7, toward zero", extentS32(0.5, -7), -3); - ensure_equals("the top of -7", extentS32(top53, -7), -6); - ensure_equals("the top of INT_MIN", extentS32(top53, INT_MIN), INT_MIN + 1); - ensure_equals("a unit of 1 gives 0, not the extent", extentS32(1.0, 100), 0); - ensure_equals("nor the negative extent", extentS32(1.0, -100), 0); - - ensure_equals("a half of 2", extentReal(0.5f, 2.f), 1.f); - ensure_equals("a half of -7", extentReal(0.5f, -7.f), -3.5f); - ensure_equals("0 of 0", extentReal(0.5, 0.0), 0.0); - ensure("the top F32 of 3 stays under 3", extentReal(1.f - 0x1p-24f, 3.f) < 3.f); - ensure("the top F64 of 3 stays under 3", extentReal(top53, 3.0) < 3.0); - ensure("the top F64 of -3 stays over -3", extentReal(top53, -3.0) > -3.0); - ensure_equals("a unit of 1 gives 0, not the extent", extentReal(1.f, 2.f), 0.f); - ensure_equals("nor the negative extent", extentReal(1.0, -2.0), 0.0); + CHECK(extentS32(0.0, 100) == 0); + CHECK(extentS32(0.5, 100) == 50); + CHECK(extentS32(top48, 100) == 99); + CHECK(extentS32(top53, 100) == 99); + CHECK(extentS32(top53, INT_MAX) == INT_MAX - 1); + CHECK(extentS32(top53, 1 << 30) == (1 << 30) - 1); + CHECK(extentS32(0.5, -7) == -3); + CHECK(extentS32(top53, -7) == -6); + CHECK(extentS32(top53, INT_MIN) == INT_MIN + 1); + UNSCOPED_INFO("a unit of 1 gives 0, not the extent"); + CHECK(extentS32(1.0, 100) == 0); + UNSCOPED_INFO("a unit of 1 gives 0, not the negative extent"); + CHECK(extentS32(1.0, -100) == 0); + + CHECK(extentReal(0.5f, 2.f) == 1.f); + CHECK(extentReal(0.5f, -7.f) == -3.5f); + CHECK(extentReal(0.5, 0.0) == 0.0); + CHECK(extentReal(1.f - 0x1p-24f, 3.f) < 3.f); + CHECK(extentReal(top53, 3.0) < 3.0); + CHECK(extentReal(top53, -3.0) > -3.0); + UNSCOPED_INFO("a unit of 1 gives 0, not the extent"); + CHECK(extentReal(1.f, 2.f) == 0.f); + UNSCOPED_INFO("a unit of 1 gives 0, not the negative extent"); + CHECK(extentReal(1.0, -2.0) == 0.0); } - template<> template<> - void random_object_t::test<16>() + TEST_CASE_METHOD(random, "LLSeedRand: unitF64/unitF32/bitsU32 take exactly a draw's top 53/24/32 bits", "[LLSeedRand]") { - set_test_name("a draw's top 53 bits are the F64, its top 24 the F32 and its top 32 the U32, each exact"); using namespace ALRandMap; const U64 all = ~U64(0); - ensure_equals("0", unitF64(0), 0.0); - ensure_equals("a half", unitF64(U64(1) << 63), 0.5); - ensure_equals("the top", unitF64(all), 1.0 - 0x1p-53); - ensure_equals("the low 11 bits are passed over", unitF64(0x7FF), 0.0); - ensure_equals("the lowest bit used", unitF64(0x800), 0x1p-53); - - ensure_equals("0", unitF32(0), 0.f); - ensure_equals("a half", unitF32(U64(1) << 63), 0.5f); - ensure_equals("the top, which does not round up to 1", unitF32(all), 1.f - 0x1p-24f); - ensure_equals("the low 40 bits are passed over", unitF32((U64(1) << 40) - 1), 0.f); - ensure_equals("the lowest bit used", unitF32(U64(1) << 40), 0x1p-24f); - - ensure_equals("the bits of 0", bitsU32(0), 0u); - ensure_equals("the top 32 bits", bitsU32(0x123456789ABCDEF0), 0x12345678u); - ensure_equals("the bits of the top", bitsU32(all), 0xFFFFFFFFu); + CHECK(unitF64(0) == 0.0); + CHECK(unitF64(U64(1) << 63) == 0.5); + CHECK(unitF64(all) == 1.0 - 0x1p-53); + UNSCOPED_INFO("the low 11 bits are passed over"); + CHECK(unitF64(0x7FF) == 0.0); + UNSCOPED_INFO("the lowest bit used"); + CHECK(unitF64(0x800) == 0x1p-53); + + CHECK(unitF32(0) == 0.f); + CHECK(unitF32(U64(1) << 63) == 0.5f); + UNSCOPED_INFO("the top, which does not round up to 1"); + CHECK(unitF32(all) == 1.f - 0x1p-24f); + UNSCOPED_INFO("the low 40 bits are passed over"); + CHECK(unitF32((U64(1) << 40) - 1) == 0.f); + UNSCOPED_INFO("the lowest bit used"); + CHECK(unitF32(U64(1) << 40) == 0x1p-24f); + + CHECK(bitsU32(0) == 0u); + CHECK(bitsU32(0x123456789ABCDEF0) == 0x12345678u); + CHECK(bitsU32(all) == 0xFFFFFFFFu); } - template<> template<> - void random_object_t::test<17>() + TEST_CASE_METHOD(random, "LLSeedRand: seed 42 draws what std::mt19937_64 does under every standard library", "[LLSeedRand]") { - set_test_name("seed 42 draws what std::mt19937_64 does, from every standard library"); const F64 drand[] = { 0x1.82a3befaddcbcp-1, 0x1.472f1f73724ap-1, 0x1.81192cfe1cbcfp-1, 0x1.171621fc50d68p-3, 0x1.ce79451c9a6c3p-1, 0x1.814dc629c7f48p-4, 0x1.262e1432cba84p-1, 0x1.7dd645e8fadeep-2 }; const F32 frand[] = { 0x1.82a3bep-1f, 0x1.472f1ep-1f, 0x1.81192cp-1f, 0x1.17162p-3f, @@ -435,43 +418,50 @@ namespace tut ll_rand_seed(42); for (S32 ii = 0; ii < 8; ++ii) { - ensure_equals(stringize("drand ", ii), ll_drand(), drand[ii]); + CAPTURE(ii); + REQUIRE(ll_drand() == drand[ii]); } ll_rand_seed(42); for (S32 ii = 0; ii < 8; ++ii) { - ensure_equals(stringize("frand ", ii), ll_frand(), frand[ii]); + CAPTURE(ii); + REQUIRE(ll_frand() == frand[ii]); } ll_rand_seed(42); for (S32 ii = 0; ii < 8; ++ii) { - ensure_equals(stringize("rand(100) ", ii), ll_rand(100), rand100[ii]); + CAPTURE(ii); + REQUIRE(ll_rand(100) == rand100[ii]); } ll_rand_seed(42); for (S32 ii = 0; ii < 8; ++ii) { - ensure_equals(stringize("rand_u32 ", ii), ll_rand_u32(), u32[ii]); + CAPTURE(ii); + REQUIRE(ll_rand_u32() == u32[ii]); } } - template<> template<> - void random_object_t::test<18>() + TEST_CASE_METHOD(random, "LLSeedRand: drand() draws to 53 bits and frand() to 24 and each uses all of them", "[LLSeedRand]") { - set_test_name("drand() draws to 53 bits and frand() to 24, and each uses all of its bits"); ll_rand_seed(18); bool drand_last_bit = false; bool frand_last_bit = false; for (S32 ii = 0; ii < 100000; ++ii) { + CAPTURE(ii); const F64 drawn = std::ldexp(ll_drand(), 53); - ensure(stringize("drand ", ii, " is a multiple of 2^-53"), drawn == std::floor(drawn)); + UNSCOPED_INFO("drand is a multiple of 2^-53"); + REQUIRE(drawn == std::floor(drawn)); drand_last_bit = drand_last_bit || std::fmod(drawn, 2.0) != 0.0; const F64 drawn_f = std::ldexp(F64(ll_frand()), 24); - ensure(stringize("frand ", ii, " is a multiple of 2^-24"), drawn_f == std::floor(drawn_f)); + UNSCOPED_INFO("frand is a multiple of 2^-24"); + REQUIRE(drawn_f == std::floor(drawn_f)); frand_last_bit = frand_last_bit || std::fmod(drawn_f, 2.0) != 0.0; } - ensure("some drand sets the 53rd bit", drand_last_bit); - ensure("some frand sets the 24th bit", frand_last_bit); + UNSCOPED_INFO("some drand sets the 53rd bit"); + CHECK(drand_last_bit); + UNSCOPED_INFO("some frand sets the 24th bit"); + CHECK(frand_last_bit); } } diff --git a/indra/llcommon/tests/llsd_test.cpp b/indra/llcommon/tests/llsd_test.cpp index 900ba8c9d0..a57a67a413 100644 --- a/indra/llcommon/tests/llsd_test.cpp +++ b/indra/llcommon/tests/llsd_test.cpp @@ -27,7 +27,7 @@ #define LLSD_DEBUG_INFO #include "linden_common.h" -#include "lltut.h" +#include #include "llsd.h" #include "llsdtraits.h" @@ -41,29 +41,27 @@ namespace tut { // LLSD's live count is back where it started once what the body made // is gone. Checked here rather than in a destructor, which is - // noexcept: a failed ensure() there would end the process instead of + // noexcept: a failed REQUIRE there would end the process instead of // failing the test. template - void ensure_cleaned_up(BODY&& body) + void require_cleaned_up(BODY&& body) { const U32 outstanding = llsd::outstandingCount(); body(); - ensure_equals("SDCleanupCheck", - llsd::outstandingCount(), outstanding); + REQUIRE(llsd::outstandingCount() == outstanding); } // ...and the body allocated exactly as many LLSD values as expected. template - void ensure_allocations(const std::string& message, U32 expectedAllocations, BODY&& body) + void require_allocations(const std::string& what, U32 expectedAllocations, BODY&& body) { const U32 outstanding = llsd::outstandingCount(); const U32 allocations = llsd::allocationCount(); body(); - ensure_equals(message + " SDAllocationCheck", - llsd::allocationCount() - allocations, + INFO(what); + REQUIRE(llsd::allocationCount() - allocations == expectedAllocations); - ensure_equals("SDCleanupCheck", - llsd::outstandingCount(), outstanding); + REQUIRE(llsd::outstandingCount() == outstanding); } } // anonymous namespace @@ -73,38 +71,27 @@ namespace tut // Static in tut rather than in the anonymous namespace, whose TUT // ensure_equals would hide lltut.h's overloads for LLSD's types. template - static void ensureTypeAndValue(const char* msg, const LLSD& actual, + static void require_type_and_value(const char* what, const LLSD& actual, T expectedValue) { LLSDTraits traits; - std::string s(msg); - - ensure( s + " type", traits.checkType(actual)); - ensure_equals( s + " value", traits.get(actual), expectedValue); + INFO(what); + REQUIRE(traits.checkType(actual)); + REQUIRE(traits.get(actual) == expectedValue); } template - static void ensureTypeAndRefValue(const char* msg, const LLSD& actual, + static void require_type_and_ref_value(const char* what, const LLSD& actual, const T& expectedValue) { LLSDTraits traits; - std::string s(msg); - - ensure( s + " type", traits.checkType(actual)); - ensure_equals( s + " value", traits.get(actual), expectedValue); + INFO(what); + REQUIRE(traits.checkType(actual)); + REQUIRE(traits.get(actual) == expectedValue); } - struct SDTestData - { - }; - - typedef test_group SDTestGroup; - typedef SDTestGroup::object SDTestObject; - - SDTestGroup sdTestGroup("LLSD(new)"); - // template<> template<> // void SDTestObject::test<1>() // // construction and test of undefined @@ -115,134 +102,128 @@ namespace tut // ensure("is undefined", u.isUndefined()); // } - template<> template<> - void SDTestObject::test<2>() - // setting and fetching scalar types + TEST_CASE("LLSD(new): setting and fetching scalar types", "[LLSD_new]") { - ensure_cleaned_up([&] + require_cleaned_up([&] { LLSD v; - v = true; ensureTypeAndValue("set true", v, true); - v = false; ensureTypeAndValue("set false", v, false); - v = true; ensureTypeAndValue("set true again", v, true); + v = true; require_type_and_value("set true", v, true); + v = false; require_type_and_value("set false", v, false); + v = true; require_type_and_value("set true again", v, true); - v = 42; ensureTypeAndValue("set to 42", v, 42); - v = 0; ensureTypeAndValue("set to zero", v, 0); - v = -12345; ensureTypeAndValue("set to neg", v, -12345); - v = 2000000000; ensureTypeAndValue("set to big", v, 2000000000); + v = 42; require_type_and_value("set to 42", v, 42); + v = 0; require_type_and_value("set to zero", v, 0); + v = -12345; require_type_and_value("set to neg", v, -12345); + v = 2000000000; require_type_and_value("set to big", v, 2000000000); v = 3.14159265359; - ensureTypeAndValue("set to pi", v, 3.14159265359); - ensure_not_equals("isn't float", v.asReal(), + require_type_and_value("set to pi", v, 3.14159265359); + REQUIRE(v.asReal() != (float)3.14159265359); - v = 6.7e256; ensureTypeAndValue("set to big", v, 6.7e256); + v = 6.7e256; require_type_and_value("set to big", v, 6.7e256); LLUUID nullUUID; LLUUID newUUID; newUUID.generate(); - v = nullUUID; ensureTypeAndValue("set to null UUID", v, nullUUID); - v = newUUID; ensureTypeAndValue("set to new UUID", v, newUUID); - v = nullUUID; ensureTypeAndValue("set to null again", v, nullUUID); + v = nullUUID; require_type_and_value("set to null UUID", v, nullUUID); + v = newUUID; require_type_and_value("set to new UUID", v, newUUID); + v = nullUUID; require_type_and_value("set to null again", v, nullUUID); // strings must be tested with two types of string objects std::string s = "now is the time"; const char* cs = "for all good zorks"; - v = s; ensureTypeAndValue("set to std::string", v, s); - v = cs; ensureTypeAndValue("set to const char*", v, cs); + v = s; require_type_and_value("set to std::string", v, s); + v = cs; require_type_and_value("set to const char*", v, cs); LLDate epoch; LLDate aDay("2001-10-22T10:11:12.00Z"); - v = epoch; ensureTypeAndValue("set to epoch", v, epoch); - v = aDay; ensureTypeAndValue("set to a day", v, aDay); + v = epoch; require_type_and_value("set to epoch", v, epoch); + v = aDay; require_type_and_value("set to a day", v, aDay); LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/"); - v = path; ensureTypeAndValue("set to a uri", v, path); + v = path; require_type_and_value("set to a uri", v, path); const char source[] = "once in a blue moon"; std::vector data; copy(&source[0], &source[sizeof(source)], back_inserter(data)); - v = data; ensureTypeAndRefValue("set to data", v, data); + v = data; require_type_and_ref_value("set to data", v, data); v.clear(); - ensure("reset to undefined", v.type() == LLSD::TypeUndefined); + REQUIRE(v.type() == LLSD::TypeUndefined); }); } - template<> template<> - void SDTestObject::test<3>() - // construction via scalar values - // tests both constructor and initialize forms + TEST_CASE("LLSD(new): construction and initialization from scalar values", "[LLSD_new]") { - ensure_cleaned_up([&] + require_cleaned_up([&] { - LLSD b1(true); ensureTypeAndValue("construct boolean", b1, true); - LLSD b2 = true; ensureTypeAndValue("initialize boolean", b2, true); - LLSD i1(42); ensureTypeAndValue("construct int", i1, 42); - LLSD i2 =42; ensureTypeAndValue("initialize int", i2, 42); - LLSD d1(1.2); ensureTypeAndValue("construct double", d1, 1.2); - LLSD d2 = 1.2; ensureTypeAndValue("initialize double", d2, 1.2); + LLSD b1(true); require_type_and_value("construct boolean", b1, true); + LLSD b2 = true; require_type_and_value("initialize boolean", b2, true); + LLSD i1(42); require_type_and_value("construct int", i1, 42); + LLSD i2 =42; require_type_and_value("initialize int", i2, 42); + LLSD d1(1.2); require_type_and_value("construct double", d1, 1.2); + LLSD d2 = 1.2; require_type_and_value("initialize double", d2, 1.2); LLUUID newUUID; newUUID.generate(); LLSD u1(newUUID); - ensureTypeAndValue("construct UUID", u1, newUUID); + require_type_and_value("construct UUID", u1, newUUID); LLSD u2 = newUUID; - ensureTypeAndValue("initialize UUID", u2, newUUID); + require_type_and_value("initialize UUID", u2, newUUID); LLSD ss1(std::string("abc")); - ensureTypeAndValue("construct std::string", ss1, "abc"); + require_type_and_value("construct std::string", ss1, "abc"); LLSD ss2 = std::string("abc"); - ensureTypeAndValue("initialize std::string",ss2, "abc"); + require_type_and_value("initialize std::string",ss2, "abc"); LLSD sl1(std::string("def")); - ensureTypeAndValue("construct std::string", sl1, "def"); + require_type_and_value("construct std::string", sl1, "def"); LLSD sl2 = std::string("def"); - ensureTypeAndValue("initialize std::string", sl2, "def"); + require_type_and_value("initialize std::string", sl2, "def"); LLSD sc1("ghi"); - ensureTypeAndValue("construct const char*", sc1, "ghi"); + require_type_and_value("construct const char*", sc1, "ghi"); LLSD sc2 = "ghi"; - ensureTypeAndValue("initialize const char*",sc2, "ghi"); + require_type_and_value("initialize const char*",sc2, "ghi"); LLDate aDay("2001-10-22T10:11:12.00Z"); - LLSD t1(aDay); ensureTypeAndValue("construct LLDate", t1, aDay); - LLSD t2 = aDay; ensureTypeAndValue("initialize LLDate", t2, aDay); + LLSD t1(aDay); require_type_and_value("construct LLDate", t1, aDay); + LLSD t2 = aDay; require_type_and_value("initialize LLDate", t2, aDay); LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/"); - LLSD p1(path); ensureTypeAndValue("construct LLURI", p1, path); - LLSD p2 = path; ensureTypeAndValue("initialize LLURI", p2, path); + LLSD p1(path); require_type_and_value("construct LLURI", p1, path); + LLSD p2 = path; require_type_and_value("initialize LLURI", p2, path); const char source[] = "once in a blue moon"; std::vector data; copy(&source[0], &source[sizeof(source)], back_inserter(data)); - LLSD x1(data); ensureTypeAndRefValue("construct vector", x1, data); - LLSD x2 = data; ensureTypeAndRefValue("initialize vector", x2, data); + LLSD x1(data); require_type_and_ref_value("construct vector", x1, data); + LLSD x2 = data; require_type_and_ref_value("initialize vector", x2, data); }); } - static void checkConversions(const char* msg, const LLSD& v, + static void require_conversions(const char* what, const LLSD& v, LLSD::Boolean eBoolean, LLSD::Integer eInteger, LLSD::Real eReal, const LLSD::String& eString) { - std::string s(msg); - - ensure_equals(s+" to bool", v.asBoolean(), eBoolean); - ensure_equals(s+" to int", v.asInteger(), eInteger); + INFO(what); + REQUIRE(v.asBoolean() == eBoolean); + REQUIRE(v.asInteger() == eInteger); if (eReal == eReal) { - ensure_equals(s+" to real", v.asReal(), eReal); - ensure_equals(s+" to string", v.asString(), eString); + REQUIRE(v.asReal() == eReal); + REQUIRE(v.asString() == eString); } else { int left = fpclassify(v.asReal()); int right = fpclassify(eReal); - ensure_equals(s+" to real", left, right); + REQUIRE(left == right); // ensure_equals(s+" to string", v.asString(), eString); // I've commented this check out, since there doesn't // seem to be uniform string representation for NaN on @@ -252,26 +233,25 @@ namespace tut } } - template<> template<> - void SDTestObject::test<4>() + TEST_CASE("LLSD(new): conversion of undefined and scalars to the basic scalar types", "[LLSD_new]") // conversion between undefined and basic scalar types: // boolean, integer, real and string { - ensure_cleaned_up([&] + require_cleaned_up([&] { - LLSD v; checkConversions("untitled", v, false, 0, 0.0, ""); + LLSD v; require_conversions("untitled", v, false, 0, 0.0, ""); - v = false; checkConversions("false", v, false, 0, 0.0, ""); - v = true; checkConversions("true", v, true, 1, 1.0, "true"); + v = false; require_conversions("false", v, false, 0, 0.0, ""); + v = true; require_conversions("true", v, true, 1, 1.0, "true"); - v = 0; checkConversions("zero", v, false, 0, 0.0, "0"); - v = 1; checkConversions("one", v, true, 1, 1.0, "1"); - v = -33; checkConversions("neg33", v, true, -33, -33.0, "-33"); + v = 0; require_conversions("zero", v, false, 0, 0.0, "0"); + v = 1; require_conversions("one", v, true, 1, 1.0, "1"); + v = -33; require_conversions("neg33", v, true, -33, -33.0, "-33"); - v = 0.0; checkConversions("0.0", v, false, 0, 0.0, "0"); - v = 0.5; checkConversions("point5", v, true, 0, 0.5, "0.5"); - v = 0.9; checkConversions("point9", v, true, 0, 0.9, "0.9"); - v = -3.9; checkConversions("neg3dot9", v, true, -3, -3.9, "-3.9"); + v = 0.0; require_conversions("0.0", v, false, 0, 0.0, "0"); + v = 0.5; require_conversions("point5", v, true, 0, 0.5, "0.5"); + v = 0.9; require_conversions("point9", v, true, 0, 0.9, "0.9"); + v = -3.9; require_conversions("neg3dot9", v, true, -3, -3.9, "-3.9"); // Get rid of NaN test. First, some libraries don't reliably return // NaN for sqrt(-1.0) -- meaning that I don't even know how to // portably, reliably produce a NaN value. Second, we observe failures @@ -282,42 +262,41 @@ namespace tut // we're not testing any LLSD code: we're testing the local library's // vsnprintf() function, which (empirically) produces idiosyncratic // results. This is just not a good test case. - // v = sqrt(-1.0); checkConversions("NaN", v, false, 0, sqrt(-1.0), "nan"); + // v = sqrt(-1.0); require_conversions("NaN", v, false, 0, sqrt(-1.0), "nan"); - v = ""; checkConversions("empty", v, false, 0, 0.0, ""); - v = "0"; checkConversions("digit0", v, true, 0, 0.0, "0"); - v = "10"; checkConversions("digit10", v, true, 10, 10.0, "10"); - v = "-2.345"; checkConversions("decdigits", v, + v = ""; require_conversions("empty", v, false, 0, 0.0, ""); + v = "0"; require_conversions("digit0", v, true, 0, 0.0, "0"); + v = "10"; require_conversions("digit10", v, true, 10, 10.0, "10"); + v = "-2.345"; require_conversions("decdigits", v, true, -2, -2.345, "-2.345"); - v = "apple"; checkConversions("apple", v, true, 0, 0.0, "apple"); - v = "33bob"; checkConversions("digialpha", v, true, 0, 0.0, "33bob"); - v = " "; checkConversions("space", v, true, 0, 0.0, " "); - v = "\n"; checkConversions("newline", v, true, 0, 0.0, "\n"); + v = "apple"; require_conversions("apple", v, true, 0, 0.0, "apple"); + v = "33bob"; require_conversions("digialpha", v, true, 0, 0.0, "33bob"); + v = " "; require_conversions("space", v, true, 0, 0.0, " "); + v = "\n"; require_conversions("newline", v, true, 0, 0.0, "\n"); }); } template - static void checkRoundTrip(const std::string& msg, const LLSD& actual, + static void require_round_trip(const std::string& what, const LLSD& actual, const char* sExpected, T vExpected) { + INFO(what); std::string str = actual.asString(); if (sExpected) { - ensure_equals(msg + " string", str, sExpected); + REQUIRE(str == sExpected); } LLSD u(str); LLSDTraits traits; - ensure_equals(msg + " value", traits.get(u), vExpected); + REQUIRE(traits.get(u) == vExpected); } - template<> template<> - void SDTestObject::test<5>() - // conversion of String to and from UUID, Date and URI. + TEST_CASE("LLSD(new): UUID and Date and URI convert to and from String", "[LLSD_new]") { - ensure_cleaned_up([&] + require_cleaned_up([&] { LLSD v; @@ -325,56 +304,55 @@ namespace tut LLUUID someUUID; someUUID.generate(); - v = nullUUID; checkRoundTrip("null uuid", v, + v = nullUUID; require_round_trip("null uuid", v, "00000000-0000-0000-0000-000000000000", nullUUID); - v = someUUID; checkRoundTrip("random uuid", v, 0, someUUID); + v = someUUID; require_round_trip("random uuid", v, 0, someUUID); LLDate epoch; LLDate beta("2003-04-30T04:00:00Z"); LLDate oneOh("2003-06-23T04:00:00Z"); - v = epoch; checkRoundTrip("epoch date", v, 0, epoch); - v = beta; checkRoundTrip("beta date", v, + v = epoch; require_round_trip("epoch date", v, 0, epoch); + v = beta; require_round_trip("beta date", v, "2003-04-30T04:00:00Z", beta); - v = oneOh; checkRoundTrip("1.0 date", v, + v = oneOh; require_round_trip("1.0 date", v, "2003-06-23T04:00:00Z", oneOh); LLURI empty; LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/"); LLURI mail("mailto:zero.linden@secondlife.com"); - v = empty; checkRoundTrip("empty URI", v, 0, empty); - v = path; checkRoundTrip("path URI", v, + v = empty; require_round_trip("empty URI", v, 0, empty); + v = path; require_round_trip("path URI", v, "http://slurl.com/secondlife/Ambleside/57/104/26/", path); - v = mail; checkRoundTrip("mail URI", v, + v = mail; require_round_trip("mail URI", v, "mailto:zero.linden@secondlife.com", mail); }); } - template<> template<> - void SDTestObject::test<6>() + TEST_CASE("LLSD(new): copies made by construction or assignment change independently", "[LLSD_new]") // copy construction and assignment // checking for shared values after constr. or assignment // checking in both the same type and change of type case { - ensure_cleaned_up([&] + require_cleaned_up([&] { { LLSD v = 42; LLSD w0(v); - ensureTypeAndValue("int constr.", w0, 42); + require_type_and_value("int constr.", w0, 42); LLSD w1(v); w1 = 13; - ensureTypeAndValue("int constr. change case 1", w1, 13); - ensureTypeAndValue("int constr. change case 2", v, 42); + require_type_and_value("int constr. change case 1", w1, 13); + require_type_and_value("int constr. change case 2", v, 42); LLSD w2(v); v = 7; - ensureTypeAndValue("int constr. change case 3", w2, 42); - ensureTypeAndValue("int constr. change case 4", v, 7); + require_type_and_value("int constr. change case 3", w2, 42); + require_type_and_value("int constr. change case 4", v, 7); } { @@ -382,13 +360,13 @@ namespace tut LLSD w1(v); w1 = "bob"; - ensureTypeAndValue("string constr. change case 1", w1, "bob"); - ensureTypeAndValue("string constr. change case 2", v, 42); + require_type_and_value("string constr. change case 1", w1, "bob"); + require_type_and_value("string constr. change case 2", v, 42); LLSD w2(v); v = "amy"; - ensureTypeAndValue("string constr. change case 3", w2, 42); - ensureTypeAndValue("string constr. change case 4", v, "amy"); + require_type_and_value("string constr. change case 3", w2, 42); + require_type_and_value("string constr. change case 4", v, "amy"); } { @@ -396,19 +374,19 @@ namespace tut LLSD w0; w0 = v; - ensureTypeAndValue("int assign", w0, 42); + require_type_and_value("int assign", w0, 42); LLSD w1; w1 = v; w1 = 13; - ensureTypeAndValue("int assign change case 1", w1, 13); - ensureTypeAndValue("int assign change case 2", v, 42); + require_type_and_value("int assign change case 1", w1, 13); + require_type_and_value("int assign change case 2", v, 42); LLSD w2; w2 = v; v = 7; - ensureTypeAndValue("int assign change case 3", w2, 42); - ensureTypeAndValue("int assign change case 4", v, 7); + require_type_and_value("int assign change case 3", w2, 42); + require_type_and_value("int assign change case 4", v, 7); } { @@ -417,50 +395,50 @@ namespace tut LLSD w1; w1 = v; w1 = "bob"; - ensureTypeAndValue("string assign change case 1", w1, "bob"); - ensureTypeAndValue("string assign change case 2", v, 42); + require_type_and_value("string assign change case 1", w1, "bob"); + require_type_and_value("string assign change case 2", v, 42); LLSD w2; w2 = v; v = "amy"; - ensureTypeAndValue("string assign change case 3", w2, 42); - ensureTypeAndValue("string assign change case 4", v, "amy"); + require_type_and_value("string assign change case 3", w2, 42); + require_type_and_value("string assign change case 4", v, "amy"); } }); } - template<> template<> - void SDTestObject::test<7>() + TEST_CASE("LLSD(new): assigning and casting LLSD to scalar types converts", "[LLSD_new]") // Test assignment and casting to various scalar types. These // assignments should invoke the right conversion without it being // mentioned explicitly. The few exceptions are marked SAD. { - ensure_cleaned_up([&] + require_cleaned_up([&] { LLSD v(" 42.375"); bool b = false; - b = v; ensure_equals("assign to bool", b, true); - b = (bool)v; ensure_equals("cast to bool", b, true); + b = v; UNSCOPED_INFO("assign to bool"); REQUIRE(b); + b = (bool)v; UNSCOPED_INFO("cast to bool"); REQUIRE(b); int i = 99; - i = v; ensure_equals("assign to int", i, 42); - i = (int)v; ensure_equals("cast to int", i, 42); + i = v; UNSCOPED_INFO("assign to int"); REQUIRE(i == 42); + i = (int)v; UNSCOPED_INFO("cast to int"); REQUIRE(i == 42); double d = 3.14159; - d = v; ensure_equals("assign to double", d, 42.375); - d = (double)v; ensure_equals("cast to double", d, 42.375); + d = v; UNSCOPED_INFO("assign to double"); REQUIRE(d == 42.375); + d = (double)v; UNSCOPED_INFO("cast to double"); REQUIRE(d == 42.375); std::string s = "yo"; // SAD s = v; ensure_equals("assign to string", s, " 42.375"); - s = (std::string)v; ensure_equals("cast to string", s, " 42.375"); + s = (std::string)v; UNSCOPED_INFO("cast to string"); REQUIRE(s == " 42.375"); std::string uuidStr = "b1e50c2b-b627-4d23-8a86-a65d97b6319b"; v = uuidStr; LLUUID u; u = v; - ensure_equals("assign to LLUUID", u, LLUUID(uuidStr)); + UNSCOPED_INFO("assign to LLUUID"); + REQUIRE(u == LLUUID(uuidStr)); // SAD u = (LLUUID)v; // ensure_equals("cast to LLUUID", u, LLUUID(uuidStr)); @@ -468,7 +446,8 @@ namespace tut v = dateStr; LLDate date; date = v; - ensure_equals("assign to LLDate", date.asString(), dateStr); + UNSCOPED_INFO("assign to LLDate"); + REQUIRE(date.asString() == dateStr); // SAD date = (LLDate)v; // ensure_equals("cast to LLDate", date.asString(), dateStr); @@ -476,284 +455,303 @@ namespace tut v = uriStr; LLURI uri; uri = v; - ensure_equals("assign to LLURI", uri.asString(), uriStr); + UNSCOPED_INFO("assign to LLURI"); + REQUIRE(uri.asString() == uriStr); // SAD uri = (LLURI)v; // ensure_equals("cast to LLURI", uri.asString(), uriStr); }); } - template<> template<> - void SDTestObject::test<8>() + TEST_CASE("LLSD(new): constructing scalar types from LLSD converts", "[LLSD_new]") // Test construction of various scalar types from LLSD. // Test both construction and initialization forms. // These should invoke the right conversion without it being // mentioned explicitly. The few exceptions are marked SAD. { - ensure_cleaned_up([&] + require_cleaned_up([&] { LLSD v(" 42.375"); - bool b1(v); ensure_equals("contruct bool", b1, true); - bool b2 = v; ensure_equals("initialize bool", b2, true); + bool b1(v); UNSCOPED_INFO("construct bool"); REQUIRE(b1); + bool b2 = v; UNSCOPED_INFO("initialize bool"); REQUIRE(b2); - int i1(v); ensure_equals("contruct int", i1, 42); - int i2 = v; ensure_equals("initialize int", i2, 42); + int i1(v); UNSCOPED_INFO("construct int"); REQUIRE(i1 == 42); + int i2 = v; UNSCOPED_INFO("initialize int"); REQUIRE(i2 == 42); - double d1(v); ensure_equals("contruct double", d1, 42.375); - double d2 = v; ensure_equals("initialize double", d2, 42.375); + double d1(v); UNSCOPED_INFO("construct double"); REQUIRE(d1 == 42.375); + double d2 = v; UNSCOPED_INFO("initialize double"); REQUIRE(d2 == 42.375); std::string s1(v); std::string s2 = v; - ensure_equals("contruct string", s1, " 42.375"); - ensure_equals("initialize string", s2, " 42.375"); + UNSCOPED_INFO("construct string"); + REQUIRE(s1 == " 42.375"); + UNSCOPED_INFO("initialize string"); + REQUIRE(s2 == " 42.375"); std::string t1(v); std::string t2 = v.asString(); // SAD - ensure_equals("contruct std::string", t1, " 42.375"); - ensure_equals("initialize std::string", t2, " 42.375"); + UNSCOPED_INFO("construct std::string"); + REQUIRE(t1 == " 42.375"); + UNSCOPED_INFO("initialize std::string"); + REQUIRE(t2 == " 42.375"); std::string uuidStr = "b1e50c2b-b627-4d23-8a86-a65d97b6319b"; v = uuidStr; LLUUID uuid1(v.asUUID()); // SAD LLUUID uuid2 = v; - ensure_equals("contruct LLUUID", uuid1, LLUUID(uuidStr)); - ensure_equals("initialize LLUUID", uuid2, LLUUID(uuidStr)); + UNSCOPED_INFO("construct LLUUID"); + REQUIRE(uuid1 == LLUUID(uuidStr)); + UNSCOPED_INFO("initialize LLUUID"); + REQUIRE(uuid2 == LLUUID(uuidStr)); std::string dateStr = "2005-10-24T15:00:00Z"; v = dateStr; LLDate date1(v.asDate()); // SAD LLDate date2 = v; - ensure_equals("contruct LLDate", date1.asString(), dateStr); - ensure_equals("initialize LLDate", date2.asString(), dateStr); + UNSCOPED_INFO("construct LLDate"); + REQUIRE(date1.asString() == dateStr); + UNSCOPED_INFO("initialize LLDate"); + REQUIRE(date2.asString() == dateStr); std::string uriStr = "http://secondlife.com"; v = uriStr; LLURI uri1(v.asURI()); // SAD LLURI uri2 = v; - ensure_equals("contruct LLURI", uri1.asString(), uriStr); - ensure_equals("initialize LLURI", uri2.asString(), uriStr); + UNSCOPED_INFO("construct LLURI"); + REQUIRE(uri1.asString() == uriStr); + UNSCOPED_INFO("initialize LLURI"); + REQUIRE(uri2.asString() == uriStr); }); } - template<> template<> - void SDTestObject::test<9>() + TEST_CASE("LLSD(new): the string 0 is true as a bool but 0 as an int", "[LLSD_new]") // test to make sure v is interpreted as a bool in a various // scenarios. { - ensure_cleaned_up([&] + require_cleaned_up([&] { LLSD v = "0"; // magic value that is interpreted as boolean true, but integer false! - ensure_equals("trinary operator bool", (v ? true : false), true); - ensure_equals("convert to int, then bool", - ((int)v ? true : false), false); + UNSCOPED_INFO("trinary operator bool"); + REQUIRE((v ? true : false) == true); + UNSCOPED_INFO("convert to int, then bool"); + REQUIRE(((int)v ? true : false) == false); if(v) { - ensure("if converted to bool", true); + REQUIRE(true); } else { - fail("bool did not convert to a bool in if statement."); + FAIL("bool did not convert to a bool in if statement."); } if(!v) { - fail("bool did not convert to a bool in negated if statement."); + FAIL("bool did not convert to a bool in negated if statement."); } }); } - template<> template<> - void SDTestObject::test<10>() - // map operations + TEST_CASE("LLSD(new): map has/get/insert/erase and copy", "[LLSD_new]") { - ensure_cleaned_up([&] + require_cleaned_up([&] { LLSD v; - ensure("undefined has no members", !v.has("amy")); - ensure("undefined get() is undefined", v.get("bob").isUndefined()); + UNSCOPED_INFO("undefined has no members"); + REQUIRE_FALSE(v.has("amy")); + UNSCOPED_INFO("undefined get() is undefined"); + REQUIRE(v.get("bob").isUndefined()); v = LLSD::emptyMap(); - ensure("empty map is a map", v.isMap()); - ensure("empty map has no members", !v.has("cam")); - ensure("empty map get() is undefined", v.get("don").isUndefined()); + REQUIRE(v.isMap()); + UNSCOPED_INFO("empty map has no members"); + REQUIRE_FALSE(v.has("cam")); + UNSCOPED_INFO("empty map get() is undefined"); + REQUIRE(v.get("don").isUndefined()); v.clear(); v.insert("eli", 43); - ensure("insert converts to map", v.isMap()); - ensure("inserted key is present", v.has("eli")); - ensureTypeAndValue("inserted value", v.get("eli"), 43); + UNSCOPED_INFO("insert converts to map"); + REQUIRE(v.isMap()); + REQUIRE(v.has("eli")); + require_type_and_value("inserted value", v.get("eli"), 43); v.insert("fra", false); - ensure("first key still present", v.has("eli")); - ensure("second key is present", v.has("fra")); - ensureTypeAndValue("first value", v.get("eli"), 43); - ensureTypeAndValue("second value", v.get("fra"), false); + UNSCOPED_INFO("first key still present"); + REQUIRE(v.has("eli")); + REQUIRE(v.has("fra")); + require_type_and_value("first value", v.get("eli"), 43); + require_type_and_value("second value", v.get("fra"), false); v.erase("eli"); - ensure("first key now gone", !v.has("eli")); - ensure("second key still present", v.has("fra")); - ensure("first value gone", v.get("eli").isUndefined()); - ensureTypeAndValue("second value sill there", v.get("fra"), false); + REQUIRE_FALSE(v.has("eli")); + UNSCOPED_INFO("second key still present"); + REQUIRE(v.has("fra")); + REQUIRE(v.get("eli").isUndefined()); + require_type_and_value("second value still there", v.get("fra"), false); v.erase("fra"); - ensure("second key now gone", !v.has("fra")); - ensure("second value gone", v.get("fra").isUndefined()); + REQUIRE_FALSE(v.has("fra")); + REQUIRE(v.get("fra").isUndefined()); v["gil"] = (std::string)"good morning"; - ensure("third key present", v.has("gil")); - ensureTypeAndValue("third key value", v.get("gil"), "good morning"); + REQUIRE(v.has("gil")); + require_type_and_value("third key value", v.get("gil"), "good morning"); const LLSD& cv = v; // FIX ME IF POSSIBLE - ensure("missing key", cv["ham"].isUndefined()); - ensure("key not present", !v.has("ham")); + REQUIRE(cv["ham"].isUndefined()); + UNSCOPED_INFO("const operator[] does not insert"); + REQUIRE_FALSE(v.has("ham")); LLSD w = 43; const LLSD& cw = w; // FIX ME IF POSSIBLE int i = cw["ian"]; - ensureTypeAndValue("other missing value", i, 0); - ensure("other missing key", !w.has("ian")); - ensure("no conversion", w.isInteger()); + require_type_and_value("other missing value", i, 0); + UNSCOPED_INFO("const operator[] does not insert"); + REQUIRE_FALSE(w.has("ian")); + UNSCOPED_INFO("const operator[] does not convert"); + REQUIRE(w.isInteger()); LLSD x; x = v; - ensure("copy map type", x.isMap()); - ensureTypeAndValue("copy map value gil", x.get("gil"), "good morning"); + REQUIRE(x.isMap()); + require_type_and_value("copy map value gil", x.get("gil"), "good morning"); }); } - template<> template<> - void SDTestObject::test<11>() - // array operations + TEST_CASE("LLSD(new): array append/insert/set/erase and copy", "[LLSD_new]") { - ensure_cleaned_up([&] + require_cleaned_up([&] { LLSD v; - ensure_equals("undefined has no size", v.size(), 0); - ensure("undefined get() is undefined", v.get(0).isUndefined()); + UNSCOPED_INFO("undefined has no size"); + REQUIRE(v.size() == 0); + UNSCOPED_INFO("undefined get() is undefined"); + REQUIRE(v.get(0).isUndefined()); v = LLSD::emptyArray(); - ensure("empty array is an array", v.isArray()); - ensure_equals("empty array has no size", v.size(), 0); - ensure("empty map get() is undefined", v.get(0).isUndefined()); + REQUIRE(v.isArray()); + UNSCOPED_INFO("empty array has no size"); + REQUIRE(v.size() == 0); + UNSCOPED_INFO("empty array get() is undefined"); + REQUIRE(v.get(0).isUndefined()); v.clear(); v.append(88); v.append("noodle"); v.append(true); - ensure_equals("appened array size", v.size(), 3); - ensure("append array is an array", v.isArray()); - ensureTypeAndValue("append 0", v[0], 88); - ensureTypeAndValue("append 1", v[1], "noodle"); - ensureTypeAndValue("append 2", v[2], true); + REQUIRE(v.size() == 3); + REQUIRE(v.isArray()); + require_type_and_value("append 0", v[0], 88); + require_type_and_value("append 1", v[1], "noodle"); + require_type_and_value("append 2", v[2], true); v.insert(0, 77); v.insert(2, "soba"); v.insert(4, false); - ensure_equals("inserted array size", v.size(), 6); - ensureTypeAndValue("post insert 0", v[0], 77); - ensureTypeAndValue("post insert 1", v[1], 88); - ensureTypeAndValue("post insert 2", v[2], "soba"); - ensureTypeAndValue("post insert 3", v[3], "noodle"); - ensureTypeAndValue("post insert 4", v[4], false); - ensureTypeAndValue("post insert 5", v[5], true); + REQUIRE(v.size() == 6); + require_type_and_value("post insert 0", v[0], 77); + require_type_and_value("post insert 1", v[1], 88); + require_type_and_value("post insert 2", v[2], "soba"); + require_type_and_value("post insert 3", v[3], "noodle"); + require_type_and_value("post insert 4", v[4], false); + require_type_and_value("post insert 5", v[5], true); LLSD beyond; beyond.insert(2, "far"); - ensure_equals("beyond-end insert size", beyond.size(), 3); - ensure("beyond-end insert pad 0", beyond[0].isUndefined()); - ensure("beyond-end insert pad 1", beyond[1].isUndefined()); - ensureTypeAndValue("beyond-end insert value", beyond[2], "far"); - - ensureTypeAndValue("get 1", v.get(1), 88); + UNSCOPED_INFO("beyond-end insert size"); + REQUIRE(beyond.size() == 3); + UNSCOPED_INFO("beyond-end insert pad 0"); + REQUIRE(beyond[0].isUndefined()); + UNSCOPED_INFO("beyond-end insert pad 1"); + REQUIRE(beyond[1].isUndefined()); + require_type_and_value("beyond-end insert value", beyond[2], "far"); + + require_type_and_value("get 1", v.get(1), 88); v.set(1, "hot"); - ensureTypeAndValue("set 1", v.get(1), "hot"); + require_type_and_value("set 1", v.get(1), "hot"); v.erase(3); - ensure_equals("post erase array size", v.size(), 5); - ensureTypeAndValue("post erase 0", v[0], 77); - ensureTypeAndValue("post erase 1", v[1], "hot"); - ensureTypeAndValue("post erase 2", v[2], "soba"); - ensureTypeAndValue("post erase 3", v[3], false); - ensureTypeAndValue("post erase 4", v[4], true); + REQUIRE(v.size() == 5); + require_type_and_value("post erase 0", v[0], 77); + require_type_and_value("post erase 1", v[1], "hot"); + require_type_and_value("post erase 2", v[2], "soba"); + require_type_and_value("post erase 3", v[3], false); + require_type_and_value("post erase 4", v[4], true); v.append(34); - ensure_equals("size after append", v.size(), 6); - ensureTypeAndValue("post append 5", v[5], 34); + REQUIRE(v.size() == 6); + require_type_and_value("post append 5", v[5], 34); LLSD w; w = v; - ensure("copy array type", w.isArray()); - ensure_equals("copy array size", w.size(), 6); - ensureTypeAndValue("copy array 0", w[0], 77); - ensureTypeAndValue("copy array 1", w[1], "hot"); - ensureTypeAndValue("copy array 2", w[2], "soba"); - ensureTypeAndValue("copy array 3", w[3], false); - ensureTypeAndValue("copy array 4", w[4], true); - ensureTypeAndValue("copy array 5", w[5], 34); + REQUIRE(w.isArray()); + REQUIRE(w.size() == 6); + require_type_and_value("copy array 0", w[0], 77); + require_type_and_value("copy array 1", w[1], "hot"); + require_type_and_value("copy array 2", w[2], "soba"); + require_type_and_value("copy array 3", w[3], false); + require_type_and_value("copy array 4", w[4], true); + require_type_and_value("copy array 5", w[5], 34); }); } - template<> template<> - void SDTestObject::test<12>() - // no sharing + TEST_CASE("LLSD(new): a copy shares no values with its original", "[LLSD_new]") { - ensure_cleaned_up([&] + require_cleaned_up([&] { LLSD a = 99; LLSD b = a; a = 34; - ensureTypeAndValue("top level original changed", a, 34); - ensureTypeAndValue("top level copy unaltered", b, 99); + require_type_and_value("top level original changed", a, 34); + require_type_and_value("top level copy unaltered", b, 99); b = a; b = 66; - ensureTypeAndValue("top level original unaltered", a, 34); - ensureTypeAndValue("top level copy changed", b, 66); + require_type_and_value("top level original unaltered", a, 34); + require_type_and_value("top level copy changed", b, 66); a[0] = "uno"; a[1] = 99; a[2] = 1.414; b = a; a[1] = 34; - ensureTypeAndValue("array member original changed", a[1], 34); - ensureTypeAndValue("array member copy unaltered", b[1], 99); + require_type_and_value("array member original changed", a[1], 34); + require_type_and_value("array member copy unaltered", b[1], 99); b = a; b[1] = 66; - ensureTypeAndValue("array member original unaltered", a[1], 34); - ensureTypeAndValue("array member copy changed", b[1], 66); + require_type_and_value("array member original unaltered", a[1], 34); + require_type_and_value("array member copy changed", b[1], 66); a["alpha"] = "uno"; a["beta"] = 99; a["gamma"] = 1.414; b = a; a["beta"] = 34; - ensureTypeAndValue("map member original changed", a["beta"], 34); - ensureTypeAndValue("map member copy unaltered", b["beta"], 99); + require_type_and_value("map member original changed", a["beta"], 34); + require_type_and_value("map member copy unaltered", b["beta"], 99); b = a; b["beta"] = 66; - ensureTypeAndValue("map member original unaltered", a["beta"], 34); - ensureTypeAndValue("map member copy changed", b["beta"], 66); + require_type_and_value("map member original unaltered", a["beta"], 34); + require_type_and_value("map member copy changed", b["beta"], 66); }); } - template<> template<> - void SDTestObject::test<13>() - // sharing implementation + TEST_CASE("LLSD(new): copies share their implementation and scalars never allocate", "[LLSD_new]") { - ensure_cleaned_up([&] + require_cleaned_up([&] { - ensure_allocations("copy construct undefinded", 0, [] + require_allocations("copy construct undefined", 0, [] { LLSD v; LLSD w = v; }); - ensure_allocations("assign undefined", 0, [] + require_allocations("assign undefined", 0, [] { LLSD v; LLSD w; @@ -761,27 +759,27 @@ namespace tut }); // scalar values are stored inline and never allocate - ensure_allocations("assign integer value", 0, [] + require_allocations("assign integer value", 0, [] { LLSD v = 45; v = 33; v = 0; }); - ensure_allocations("copy construct integer", 0, [] + require_allocations("copy construct integer", 0, [] { LLSD v = 45; LLSD w = v; }); - ensure_allocations("assign integer", 0, [] + require_allocations("assign integer", 0, [] { LLSD v = 45; LLSD w; w = v; }); - ensure_allocations("avoids extra clone", 1, [] + require_allocations("avoids extra clone", 1, [] { LLSD v = 45; LLSD w = v; @@ -789,7 +787,7 @@ namespace tut }); // map + array + two strings; integers, reals and undefs are inline - ensure_allocations("shared values test for threaded work", 4, [] + require_allocations("shared values test for threaded work", 4, [] { //U32 start_llsd_count = LLSD::outstandingCount(); @@ -826,36 +824,32 @@ namespace tut }); } - template<> template<> - void SDTestObject::test<14>() - // make sure that assignment of char* NULL in a string does not crash. + TEST_CASE("LLSD(new): assigning a null char pointer makes a string without crashing", "[LLSD_new]") { LLSD v; v = (const char*)NULL; - ensure("type is a string", v.isString()); + CHECK(v.isString()); } - template<> template<> - void SDTestObject::test<15>() - // XMLRPC value serialization + TEST_CASE("LLSD(new): asXMLRPCValue serializes each type", "[LLSD_new]") { - ensure_cleaned_up([&] + require_cleaned_up([&] { - ensure_equals("xmlrpc undef", LLSD().asXMLRPCValue(), + REQUIRE(LLSD().asXMLRPCValue() == ""); - ensure_equals("xmlrpc bool", LLSD(true).asXMLRPCValue(), + REQUIRE(LLSD(true).asXMLRPCValue() == "1"); - ensure_equals("xmlrpc int", LLSD(3).asXMLRPCValue(), + REQUIRE(LLSD(3).asXMLRPCValue() == "3"); - ensure_equals("xmlrpc real", LLSD(1.5).asXMLRPCValue(), + REQUIRE(LLSD(1.5).asXMLRPCValue() == "1.5"); - ensure_equals("xmlrpc string", LLSD("aa<b"); LLSD array = LLSD::emptyArray(); array.append(1); array.append("two"); - ensure_equals("xmlrpc array", array.asXMLRPCValue(), + REQUIRE(array.asXMLRPCValue() == "" "1" "two" @@ -863,7 +857,7 @@ namespace tut LLSD map = LLSD::emptyMap(); map["a"] = 1; - ensure_equals("xmlrpc map", map.asXMLRPCValue(), + REQUIRE(map.asXMLRPCValue() == "" "a1" ""); diff --git a/indra/llcommon/tests/llsdjson_test.cpp b/indra/llcommon/tests/llsdjson_test.cpp index 027d0c36bc..5e8888c2ec 100644 --- a/indra/llcommon/tests/llsdjson_test.cpp +++ b/indra/llcommon/tests/llsdjson_test.cpp @@ -25,7 +25,7 @@ */ #include "linden_common.h" -#include "lltut.h" +#include #include "llsdjson.h" #include "llsdutil.h" @@ -36,178 +36,161 @@ #include #include -namespace tut +namespace { struct llsdjson_data { // parse json text expecting success - LLSD parse(const std::string& json) + LLSD require_parse(const std::string& json) { LLSD result; std::string errmsg; bool ok = LlsdFromJsonString(json, result, &errmsg); - ensure("parse failed for " + json + ": " + errmsg, ok); + CAPTURE(json, errmsg); + REQUIRE(ok); return result; } }; - typedef test_group llsdjson_test; - typedef llsdjson_test::object llsdjson_object; - tut::llsdjson_test tllsdjson("LlsdJson"); - // scalar parsing: types and values - template<> template<> - void llsdjson_object::test<1>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: scalars parse to the right type and value", "[LlsdJson]") { - LLSD val = parse("null"); - ensure_equals("null type", val.type(), LLSD::TypeUndefined); + LLSD val = require_parse("null"); + CHECK(val.type() == LLSD::TypeUndefined); - val = parse("true"); - ensure_equals("bool type", val.type(), LLSD::TypeBoolean); - ensure("bool value", val.asBoolean()); + val = require_parse("true"); + CHECK(val.type() == LLSD::TypeBoolean); + CHECK(val.asBoolean()); - val = parse("false"); - ensure("false value", !val.asBoolean()); + val = require_parse("false"); + CHECK_FALSE(val.asBoolean()); - val = parse("42"); - ensure_equals("int type", val.type(), LLSD::TypeInteger); - ensure_equals("int value", val.asInteger(), 42); + val = require_parse("42"); + CHECK(val.type() == LLSD::TypeInteger); + CHECK(val.asInteger() == 42); - val = parse("-7"); - ensure_equals("negative int", val.asInteger(), -7); + val = require_parse("-7"); + CHECK(val.asInteger() == -7); - val = parse("3.25"); - ensure_equals("real type", val.type(), LLSD::TypeReal); - ensure_equals("real value", val.asReal(), 3.25); + val = require_parse("3.25"); + CHECK(val.type() == LLSD::TypeReal); + CHECK(val.asReal() == 3.25); - val = parse("\"hello world\""); - ensure_equals("string type", val.type(), LLSD::TypeString); - ensure_equals("string value", val.asString(), "hello world"); + val = require_parse("\"hello world\""); + CHECK(val.type() == LLSD::TypeString); + CHECK(val.asString() == "hello world"); } - // integer vs real distinction is preserved by the parse - template<> template<> - void llsdjson_object::test<2>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: integer vs real distinction is preserved by the parse", "[LlsdJson]") { - ensure_equals("1 is integer", parse("1").type(), LLSD::TypeInteger); - ensure_equals("1.0 is real", parse("1.0").type(), LLSD::TypeReal); - ensure_equals("1e0 is real", parse("1e0").type(), LLSD::TypeReal); - ensure_equals("0 is integer", parse("0").type(), LLSD::TypeInteger); + CHECK(require_parse("1").type() == LLSD::TypeInteger); + CHECK(require_parse("1.0").type() == LLSD::TypeReal); + CHECK(require_parse("1e0").type() == LLSD::TypeReal); + CHECK(require_parse("0").type() == LLSD::TypeInteger); } - // arrays: order, nesting, empties - template<> template<> - void llsdjson_object::test<3>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: arrays parse in order with nesting and empty arrays", "[LlsdJson]") { - LLSD val = parse("[1, \"two\", 3.0, null, [true]]"); - ensure_equals("array type", val.type(), LLSD::TypeArray); - ensure_equals("array size", val.size(), 5); - ensure_equals("elt 0", val[0].asInteger(), 1); - ensure_equals("elt 1", val[1].asString(), "two"); - ensure_equals("elt 2", val[2].asReal(), 3.0); - ensure_equals("elt 3 undefined", val[3].type(), LLSD::TypeUndefined); - ensure_equals("nested array", val[4][0].asBoolean(), true); - - val = parse("[]"); - ensure_equals("empty array type", val.type(), LLSD::TypeArray); - ensure_equals("empty array size", val.size(), 0); + LLSD val = require_parse("[1, \"two\", 3.0, null, [true]]"); + CHECK(val.type() == LLSD::TypeArray); + REQUIRE(val.size() == 5); + CHECK(val[0].asInteger() == 1); + CHECK(val[1].asString() == "two"); + CHECK(val[2].asReal() == 3.0); + CHECK(val[3].type() == LLSD::TypeUndefined); + CHECK(val[4][0].asBoolean()); + + val = require_parse("[]"); + CHECK(val.type() == LLSD::TypeArray); + CHECK(val.size() == 0); } - // objects: keys, nesting, empties - template<> template<> - void llsdjson_object::test<4>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: objects parse by key with nesting and empty objects", "[LlsdJson]") { - LLSD val = parse("{\"a\": 1, \"b\": {\"c\": [2, 3]}}"); - ensure_equals("map type", val.type(), LLSD::TypeMap); - ensure_equals("map size", val.size(), 2); - ensure_equals("member a", val["a"].asInteger(), 1); - ensure_equals("nested member", val["b"]["c"][1].asInteger(), 3); - - val = parse("{}"); - ensure_equals("empty map type", val.type(), LLSD::TypeMap); - ensure_equals("empty map size", val.size(), 0); + LLSD val = require_parse("{\"a\": 1, \"b\": {\"c\": [2, 3]}}"); + CHECK(val.type() == LLSD::TypeMap); + REQUIRE(val.size() == 2); + CHECK(val["a"].asInteger() == 1); + CHECK(val["b"]["c"][1].asInteger() == 3); + + val = require_parse("{}"); + CHECK(val.type() == LLSD::TypeMap); + CHECK(val.size() == 0); } - // serialization golden strings - template<> template<> - void llsdjson_object::test<5>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: serialization writes the golden strings", "[LlsdJson]") { - ensure_equals("undefined", LlsdToJson(LLSD()), "null"); - ensure_equals("true", LlsdToJson(LLSD(true)), "true"); - ensure_equals("false", LlsdToJson(LLSD(false)), "false"); - ensure_equals("integer", LlsdToJson(LLSD(17)), "17"); - ensure_equals("negative", LlsdToJson(LLSD(-17)), "-17"); - ensure_equals("real", LlsdToJson(LLSD(2.5)), "2.5"); - ensure_equals("string", LlsdToJson(LLSD("simple")), "\"simple\""); - ensure_equals("empty string", LlsdToJson(LLSD("")), "\"\""); - ensure_equals("empty map", LlsdToJson(LLSD::emptyMap()), "{}"); - ensure_equals("empty array", LlsdToJson(LLSD::emptyArray()), "[]"); + CHECK(LlsdToJson(LLSD()) == "null"); + CHECK(LlsdToJson(LLSD(true)) == "true"); + CHECK(LlsdToJson(LLSD(false)) == "false"); + CHECK(LlsdToJson(LLSD(17)) == "17"); + CHECK(LlsdToJson(LLSD(-17)) == "-17"); + CHECK(LlsdToJson(LLSD(2.5)) == "2.5"); + CHECK(LlsdToJson(LLSD("simple")) == "\"simple\""); + CHECK(LlsdToJson(LLSD("")) == "\"\""); + CHECK(LlsdToJson(LLSD::emptyMap()) == "{}"); + CHECK(LlsdToJson(LLSD::emptyArray()) == "[]"); LLSD map; map["b"] = 2; map["a"] = 1; // LLSD maps iterate in sorted key order - ensure_equals("map", LlsdToJson(map), "{\"a\":1,\"b\":2}"); + CHECK(LlsdToJson(map) == "{\"a\":1,\"b\":2}"); LLSD array; array.append(1); array.append("x"); - ensure_equals("array", LlsdToJson(array), "[1,\"x\"]"); + CHECK(LlsdToJson(array) == "[1,\"x\"]"); } - // string escaping in serialization - template<> template<> - void llsdjson_object::test<6>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: string escaping in serialization", "[LlsdJson]") { - ensure_equals("quote", LlsdToJson(LLSD("a\"b")), "\"a\\\"b\""); - ensure_equals("backslash", LlsdToJson(LLSD("a\\b")), "\"a\\\\b\""); - ensure_equals("newline", LlsdToJson(LLSD("a\nb")), "\"a\\nb\""); - ensure_equals("tab", LlsdToJson(LLSD("a\tb")), "\"a\\tb\""); + UNSCOPED_INFO("quote"); + CHECK(LlsdToJson(LLSD("a\"b")) == "\"a\\\"b\""); + UNSCOPED_INFO("backslash"); + CHECK(LlsdToJson(LLSD("a\\b")) == "\"a\\\\b\""); + UNSCOPED_INFO("newline"); + CHECK(LlsdToJson(LLSD("a\nb")) == "\"a\\nb\""); + UNSCOPED_INFO("tab"); + CHECK(LlsdToJson(LLSD("a\tb")) == "\"a\\tb\""); // control character must be escaped one way or another; round-trip it LLSD ctrl; - ensure("control char parses", - LlsdFromJsonString(LlsdToJson(LLSD(std::string("a\x01z"))), ctrl)); - ensure_equals("control char round-trip", ctrl.asString(), std::string("a\x01z")); + REQUIRE(LlsdFromJsonString(LlsdToJson(LLSD(std::string("a\x01z"))), ctrl)); + CHECK(ctrl.asString() == std::string("a\x01z")); // UTF-8 passes through unescaped or escaped, but must round-trip std::string utf8("\xE3\x81\x93\xE3\x82\x93 caf\xC3\xA9"); LLSD uni; - ensure("utf8 parses", LlsdFromJsonString(LlsdToJson(LLSD(utf8)), uni)); - ensure_equals("utf8 round-trip", uni.asString(), utf8); + REQUIRE(LlsdFromJsonString(LlsdToJson(LLSD(utf8)), uni)); + CHECK(uni.asString() == utf8); } - // special scalar types serialize as strings - template<> template<> - void llsdjson_object::test<7>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: special scalar types serialize as strings", "[LlsdJson]") { LLUUID id("c96f9b8e-f5ad-4b72-b6f7-a2b30d1444d2"); - ensure_equals("uuid", LlsdToJson(LLSD(id)), + CHECK(LlsdToJson(LLSD(id)) == "\"c96f9b8e-f5ad-4b72-b6f7-a2b30d1444d2\""); LLDate date("2026-06-11T12:34:56Z"); - ensure_equals("date", LlsdToJson(LLSD(date)), "\"2026-06-11T12:34:56Z\""); + CHECK(LlsdToJson(LLSD(date)) == "\"2026-06-11T12:34:56Z\""); LLURI uri("http://example.com/path"); - ensure_equals("uri", LlsdToJson(LLSD(uri)), "\"http://example.com/path\""); + CHECK(LlsdToJson(LLSD(uri)) == "\"http://example.com/path\""); } - // non-finite reals degrade to null (JSON has no NaN/Infinity) - template<> template<> - void llsdjson_object::test<8>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: non-finite reals degrade to null (JSON has no NaN/Infinity)", "[LlsdJson]") { - ensure_equals("nan", LlsdToJson(LLSD(std::numeric_limits::quiet_NaN())), "null"); - ensure_equals("inf", LlsdToJson(LLSD(std::numeric_limits::infinity())), "null"); - ensure_equals("-inf", LlsdToJson(LLSD(-std::numeric_limits::infinity())), "null"); + CHECK(LlsdToJson(LLSD(std::numeric_limits::quiet_NaN())) == "null"); + CHECK(LlsdToJson(LLSD(std::numeric_limits::infinity())) == "null"); + CHECK(LlsdToJson(LLSD(-std::numeric_limits::infinity())) == "null"); // and stay valid inside containers LLSD map; map["bad"] = std::numeric_limits::quiet_NaN(); - ensure_equals("nan in map", LlsdToJson(map), "{\"bad\":null}"); + CHECK(LlsdToJson(map) == "{\"bad\":null}"); } - // structured round trip preserves everything - template<> template<> - void llsdjson_object::test<9>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: structured round trip preserves everything", "[LlsdJson]") { LLSD src; src["int"] = 123; @@ -221,26 +204,24 @@ namespace tut LLSD dst; std::string json = LlsdToJson(src); - ensure("round-trip parses: " + json, LlsdFromJsonString(json, dst)); - ensure("round-trip equality: " + json, llsd_equals(src, dst)); + CAPTURE(json); + REQUIRE(LlsdFromJsonString(json, dst)); + CHECK(llsd_equals(src, dst)); } - // full-precision reals survive the round trip - template<> template<> - void llsdjson_object::test<10>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: full-precision reals survive the round trip", "[LlsdJson]") { F64 vals[] = { 0.1, 1.0/3.0, 2.718281828459045, 1e-300, 1.7976931348623157e308 }; for (F64 v : vals) { LLSD parsed; - ensure("real parses", LlsdFromJsonString(LlsdToJson(LLSD(v)), parsed)); - ensure_equals("real precision", parsed.asReal(), v); + CAPTURE(v); + REQUIRE(LlsdFromJsonString(LlsdToJson(LLSD(v)), parsed)); + REQUIRE(parsed.asReal() == v); } } - // parse failures report errors and clear the output - template<> template<> - void llsdjson_object::test<11>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: parse failures report errors and clear the output", "[LlsdJson]") { const char* bad[] = { @@ -262,42 +243,37 @@ namespace tut LLSD out = LLSD::emptyMap(); std::string errmsg; bool ok = LlsdFromJsonString(json, out, &errmsg); - ensure(std::string("rejects: ") + json, !ok); - ensure(std::string("errmsg set for: ") + json, !errmsg.empty()); - ensure_equals(std::string("output cleared for: ") + json, - out.type(), LLSD::TypeUndefined); + CAPTURE(json); + REQUIRE_FALSE(ok); + REQUIRE_FALSE(errmsg.empty()); + REQUIRE(out.type() == LLSD::TypeUndefined); } // errmsg pointer is optional LLSD out; - ensure("no errmsg crash", !LlsdFromJsonString("{", out)); + UNSCOPED_INFO("without an errmsg pointer"); + CHECK_FALSE(LlsdFromJsonString("{", out)); } - // unicode object keys - template<> template<> - void llsdjson_object::test<12>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: unicode object keys round-trip", "[LlsdJson]") { LLSD src; src["caf\xC3\xA9"] = 1; LLSD dst; - ensure("unicode key parses", LlsdFromJsonString(LlsdToJson(src), dst)); - ensure_equals("unicode key value", dst["caf\xC3\xA9"].asInteger(), 1); + REQUIRE(LlsdFromJsonString(LlsdToJson(src), dst)); + CHECK(dst["caf\xC3\xA9"].asInteger() == 1); } - // padded_string overload parses in place - template<> template<> - void llsdjson_object::test<13>() + TEST_CASE_METHOD(llsdjson_data, "LlsdJson: padded_string overload parses in place", "[LlsdJson]") { LLSD out; - ensure("padded parse", - LlsdFromJsonString(simdjson::padded_string(std::string("{\"a\": [1, 2.5, \"three\"]}")), out)); - ensure_equals("padded int", out["a"][0].asInteger(), 1); - ensure_equals("padded real", out["a"][1].asReal(), 2.5); - ensure_equals("padded string", out["a"][2].asString(), "three"); + REQUIRE(LlsdFromJsonString(simdjson::padded_string(std::string("{\"a\": [1, 2.5, \"three\"]}")), out)); + CHECK(out["a"][0].asInteger() == 1); + CHECK(out["a"][1].asReal() == 2.5); + CHECK(out["a"][2].asString() == "three"); std::string errmsg; - ensure("padded parse fails", - !LlsdFromJsonString(simdjson::padded_string(std::string("{")), out, &errmsg)); - ensure("padded errmsg set", !errmsg.empty()); + REQUIRE_FALSE(LlsdFromJsonString(simdjson::padded_string(std::string("{")), out, &errmsg)); + CHECK_FALSE(errmsg.empty()); } } diff --git a/indra/llcommon/tests/llsdserialize_python_test.cpp b/indra/llcommon/tests/llsdserialize_python_test.cpp new file mode 100644 index 0000000000..0949395270 --- /dev/null +++ b/indra/llcommon/tests/llsdserialize_python_test.cpp @@ -0,0 +1,435 @@ +/** + * @file llsdserialize_python_test.cpp + * @date 2006-04 + * @brief LLSDSerialize round trips through Python's llsd package + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * 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 + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// llsdserialize_test.cpp's group that needs a Python interpreter, apart so +// the rest can run without one. + +#include "linden_common.h" + +#if LL_WINDOWS +#include +#include +#else +#include +#include +#include "llprocess.h" +#endif + +#include "llsd.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include "llmemorystream.h" +#include "llstring.h" + +#include "hexdump.h" +#include +#include "altestfailure.h" +#include "altestmatchers.h" +#include "../test/namedtempfile.h" +#include "StringVec.h" +#include + +typedef std::function FormatterFunction; +typedef std::function ParserFunction; + +namespace tut +{ + // helper for TestPythonCompatible + static std::string import_llsd("import os.path\n" + "import sys\n" + "import llsd\n"); + + // helper for TestPythonCompatible + template + static void python_expect(const std::string& desc, const CONTENT& script, int expect=0, + ARGS&&... args) + { + auto PYTHON(LLStringUtil::getenv("PYTHON")); + UNSCOPED_INFO("Set $PYTHON to the Python interpreter"); + REQUIRE_FALSE(PYTHON.empty()); + + INFO(desc << " script"); + NamedTempFile scriptfile("py", script); + +#if LL_WINDOWS + std::string q("\""); + std::string qPYTHON(q + PYTHON + q); + std::string qscript(q + scriptfile.getPath().string() + q); + int rc = (int)_spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), + std::forward(args)..., NULL); + if (rc == -1) + { + char buffer[256]; + strerror_s(buffer, errno); // C++ can infer the buffer size! :-O + FAIL("couldn't run Python: " << buffer); + } + else + { + REQUIRE(rc == expect); + } + +#else // LL_DARWIN, LL_LINUX + LLProcess::Params params; + params.executable = PYTHON; + params.args.add(scriptfile.getPath().string()); + for (const std::string& arg : StringVec{ std::forward(args)... }) + { + params.args.add(arg); + } + LLProcessPtr py(LLProcess::create(params)); + UNSCOPED_INFO("couldn't launch"); + REQUIRE(bool(py)); + // Implementing timeout would mean messing with alarm() and + // catching SIGALRM... later maybe... + int status(0); + if (waitpid(py->getProcessID(), &status, 0) == -1) + { + int waitpid_errno(errno); + UNSCOPED_INFO("couldn't retrieve rc: waitpid() failed"); + REQUIRE(waitpid_errno == ECHILD); + } + else + { + if (WIFEXITED(status)) + { + int rc(WEXITSTATUS(status)); + REQUIRE(rc == expect); + } + else if (WIFSIGNALED(status)) + { + FAIL("terminated by signal " << WTERMSIG(status)); + } + else + { + FAIL("produced impossible status " << status); + } + } +#endif + } + + // helper for TestPythonCompatible + template + static void python(const std::string& desc, const CONTENT& script, ARGS&&... args) + { + // plain python() expects rc 0 + python_expect(desc, script, 0, std::forward(args)...); + } + + struct TestPythonCompatible + { + TestPythonCompatible() {} + ~TestPythonCompatible() {} + }; + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: python_expect() sees a nonzero exit code", "[LLSD_serialize_Python_compatibility]") + { + python_expect("hello", + "import sys\n" + "sys.exit(17)\n", + 17); // expect nonzero rc + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: python() runs a script in a NamedTempFile", "[LLSD_serialize_Python_compatibility]") + { + python("platform", + "import sys\n" + "print('Running on', sys.platform)\n"); + } + + // helper for test<3> - test<7> + static void writeLLSDArray(const FormatterFunction& serialize, + std::ostream& out, const LLSD& array) + { + for (const LLSD& item: llsd::inArray(array)) + { + // It's important to delimit the entries in this file somehow + // because, although Python's llsd.parse() can accept a file + // stream, the XML parser expects EOF after a single outer element + // -- it doesn't just stop. So we must extract a sequence of bytes + // strings from the file. But since one of the serialization + // formats we want to test is binary, we can't pick any single + // byte value as a delimiter! Use a binary integer length prefix + // instead. + std::ostringstream buffer; + serialize(item, buffer); + auto buffstr{ buffer.str() }; + int bufflen{ static_cast(buffstr.length()) }; + out.write(reinterpret_cast(&bufflen), sizeof(bufflen)); + LL_DEBUGS() << "Wrote length: " + << LL::hexdump(reinterpret_cast(&bufflen), + sizeof(bufflen)) + << LL_ENDL; + out.write(buffstr.c_str(), buffstr.length()); + LL_DEBUGS() << "Wrote data: " + << LL::hexmix(buffstr.c_str(), buffstr.length()) + << LL_ENDL; + } + } + + // helper for test<3> - test<7> + static void toPythonUsing(const std::string& desc, + const FormatterFunction& serialize) + { + LLSD cdata(llsd::array(17, 3.14, + "This string\n" + "has several\n" + "lines.")); + + const char pydata[] = + "def verify(iterable):\n" + " it = iter(iterable)\n" + " assert next(it) == 17\n" + " assert abs(next(it) - 3.14) < 0.01\n" + " assert next(it) == '''\\\n" + "This string\n" + "has several\n" + "lines.'''\n" + " try:\n" + " next(it)\n" + " except StopIteration:\n" + " pass\n" + " else:\n" + " raise AssertionError('Too many data items')\n"; + + // Create an llsdXXXXXX file containing 'data' serialized per + // FormatterFunction. + NamedTempFile file("llsd", + // NamedTempFile's function constructor + // takes a callable. To this callable it passes the + // std::ostream with which it's writing the + // NamedTempFile. + [serialize, cdata] + (std::ostream& out) + { writeLLSDArray(serialize, out, cdata); }); + + // 'debug' starts empty because it's intended as an output file + NamedTempFile debug("debug", ""); + + try + { + python("read C++ " + desc, + [&](std::ostream& out){ out << + import_llsd << + "from functools import partial\n" + "import io\n" + "import struct\n" + "lenformat = struct.Struct('i')\n" + "def parse_each(inf):\n" + " for rawlen in iter(partial(inf.read, lenformat.size), b''):\n" + " print('Read length:', ''.join(('%02x' % b) for b in rawlen),\n" + " file=debug)\n" + " len = lenformat.unpack(rawlen)[0]\n" + // Since llsd.parse() has no max_bytes argument, instead of + // passing the input stream directly to parse(), read the item + // into a distinct bytes object and parse that. + " data = inf.read(len)\n" + " print('Read data: ', repr(data), file=debug)\n" + " try:\n" + " frombytes = llsd.parse(data)\n" + " except llsd.LLSDParseError as err:\n" + " print(f'*** {err}')\n" + " print(f'Bad content:\\n{data!r}')\n" + " raise\n" + // Also try parsing from a distinct stream. + " stream = io.BytesIO(data)\n" + " fromstream = llsd.parse(stream)\n" + " assert frombytes == fromstream\n" + " yield frombytes\n" + << pydata << + // Don't forget raw-string syntax for Windows pathnames. + "debug = open(r'" << debug.getPath().string() << "', 'w')\n" + "verify(parse_each(open(r'" << file.getPath().string() << "', 'rb')))\n";}); + } + catch (const ll_test::Failure&) + { + LL_DEBUGS() << "Script debug output:" << LL_ENDL; + debug.peep_log(); + throw; + } + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: to Python using LLSDSerialize::serialize(LLSD_XML)", "[LLSD_serialize_Python_compatibility]") + { + toPythonUsing("LLSD_XML", + [](const LLSD& sd, std::ostream& out) + { LLSDSerialize::serialize(sd, out, LLSDSerialize::LLSD_XML); }); + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: to Python using LLSDSerialize::serialize(LLSD_NOTATION)", "[LLSD_serialize_Python_compatibility]") + { + toPythonUsing("LLSD_NOTATION", + [](const LLSD& sd, std::ostream& out) + { LLSDSerialize::serialize(sd, out, LLSDSerialize::LLSD_NOTATION); }); + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: to Python using LLSDSerialize::serialize(LLSD_BINARY)", "[LLSD_serialize_Python_compatibility]") + { + toPythonUsing("LLSD_BINARY", + [](const LLSD& sd, std::ostream& out) + { LLSDSerialize::serialize(sd, out, LLSDSerialize::LLSD_BINARY); }); + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: to Python using LLSDSerialize::toXML()", "[LLSD_serialize_Python_compatibility]") + { + toPythonUsing("toXML()", LLSDSerialize::toXML); + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: to Python using LLSDSerialize::toNotation()", "[LLSD_serialize_Python_compatibility]") + { + toPythonUsing("toNotation()", LLSDSerialize::toNotation); + } + +/*==========================================================================*| + template<> template<> + void TestPythonCompatibleObject::test<8>() + { + set_test_name("to Python using LLSDSerialize::toBinary()"); + // We don't expect this to work because, without a header, + // llsd.parse() will assume notation rather than binary. + toPythonUsing("toBinary()", LLSDSerialize::toBinary); + } +|*==========================================================================*/ + + // helper for test<8> - test<12> + static bool itemFromStream(std::istream& istr, LLSD& item, const ParserFunction& parse) + { + // reset the output value for debugging clarity + item.clear(); + // We use an int length prefix as a foolproof delimiter even for + // binary serialized streams. + int length{ 0 }; + istr.read(reinterpret_cast(&length), sizeof(length)); +// return parse(istr, item, length); + // Sadly, as of 2022-12-01 it seems we can't really trust our LLSD + // parsers to honor max_bytes: this test works better when we read + // each item into its own distinct LLMemoryStream, instead of passing + // the original istr with a max_bytes constraint. + std::vector buffer(length); + istr.read(reinterpret_cast(buffer.data()), length); + LLMemoryStream stream(buffer.data(), length); + return parse(stream, item, length); + } + + // helper for test<8> - test<12> + static void fromPythonUsing(const std::string& pyformatter, + const ParserFunction& parse= + [](std::istream& istr, LLSD& data, llssize max_bytes) + { return LLSDSerialize::deserialize(data, istr, max_bytes); }) + { + // Create an empty data file. This is just a placeholder for our + // script to write into. Create it to establish a unique name that + // we know. + NamedTempFile file("llsd", ""); + + python("Python " + pyformatter, + [&](std::ostream& out){ out << + import_llsd << + "import struct\n" + "lenformat = struct.Struct('i')\n" + "DATA = [\n" + " 17,\n" + " 3.14,\n" + " '''\\\n" + "This string\n" + "has several\n" + "lines.''',\n" + "]\n" + // Don't forget raw-string syntax for Windows pathnames. + // N.B. Using 'print' implicitly adds newlines. + "with open(r'" << ll_convert(file.getPath().u8string()).c_str() << "', 'wb') as f:\n" + " for item in DATA:\n" + " serialized = llsd." << pyformatter << "(item)\n" + " f.write(lenformat.pack(len(serialized)))\n" + " f.write(serialized)\n";}); + + llifstream inf(file.getPath()); + LLSD item; + try + { + UNSCOPED_INFO("reading the Integer"); + REQUIRE(itemFromStream(inf, item, parse)); + REQUIRE(item.asInteger() == 17); + UNSCOPED_INFO("reading the Real"); + REQUIRE(itemFromStream(inf, item, parse)); + REQUIRE_THAT(item.asReal(), ll_test::WithinBits(3.14, 7)); // 7 bits ~= 0.01 + UNSCOPED_INFO("reading the String"); + REQUIRE(itemFromStream(inf, item, parse)); + REQUIRE(item.asString() == + "This string\n" + "has several\n" + "lines."); + } + catch (const ll_test::Failure&) + { + std::cout << "for item = " << item << std::endl; + throw; + } + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: from Python XML using LLSDSerialize::deserialize()", "[LLSD_serialize_Python_compatibility]") + { + fromPythonUsing("format_xml"); + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: from Python notation using LLSDSerialize::deserialize()", "[LLSD_serialize_Python_compatibility]") + { + fromPythonUsing("format_notation"); + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: from Python binary using LLSDSerialize::deserialize()", "[LLSD_serialize_Python_compatibility]") + { + fromPythonUsing("format_binary"); + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: from Python XML using fromXML()", "[LLSD_serialize_Python_compatibility]") + { + // fromXML()'s optional 3rd param isn't max_bytes, it's emit_errors + fromPythonUsing("format_xml", + [](std::istream& istr, LLSD& data, llssize) + { return LLSDSerialize::fromXML(data, istr) > 0; }); + } + + TEST_CASE_METHOD(TestPythonCompatible, "LLSD serialize Python compatibility: from Python notation using fromNotation()", "[LLSD_serialize_Python_compatibility]") + { + fromPythonUsing("format_notation", + [](std::istream& istr, LLSD& data, llssize max_bytes) + { return LLSDSerialize::fromNotation(data, istr, max_bytes) > 0; }); + } + +/*==========================================================================*| + template<> template<> + void TestPythonCompatibleObject::test<13>() + { + set_test_name("from Python binary using fromBinary()"); + // We don't expect this to work because format_binary() emits a + // header, but fromBinary() won't recognize a header. + fromPythonUsing("format_binary", + [](std::istream& istr, LLSD& data, llssize max_bytes) + { return LLSDSerialize::fromBinary(data, istr, max_bytes) > 0; }); + } +|*==========================================================================*/ +} diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 189c18420c..f39a2e224c 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -31,17 +31,8 @@ #if LL_WINDOWS #include typedef U32 uint32_t; -#include -#include #else -#include #include -#include -#include -#include -#include -#include "llprocess.h" -#include "llstring.h" #endif #include "boost/range.hpp" @@ -54,11 +45,8 @@ typedef U32 uint32_t; #include "llformat.h" #include "llmemorystream.h" -#include "hexdump.h" -#include "../test/lltut.h" -#include "../test/namedtempfile.h" -#include "stringize.h" -#include "StringVec.h" +#include +#include "altestmatchers.h" #include "wrapllerrs.h" #include @@ -70,7 +58,7 @@ static std::vector string_to_vector(const std::string& str) return std::vector(str.begin(), str.end()); } -namespace tut +namespace { struct sd_xml_data { @@ -80,54 +68,50 @@ namespace tut } LLSD mSD; LLPointer mFormatter; - void xml_test(const char* name, const std::string& expected) + void require_xml(const char* what, const std::string& expected) { std::ostringstream ostr; mFormatter->format(mSD, ostr); - ensure_equals(name, ostr.str(), expected); + INFO(what); + REQUIRE(ostr.str() == expected); } }; - typedef test_group sd_xml_test; - typedef sd_xml_test::object sd_xml_object; - tut::sd_xml_test sd_xml_stream("LLSDXMLFormatter"); - - template<> template<> - void sd_xml_object::test<1>() + TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: formats each scalar type", "[LLSDXMLFormatter]") { // random atomic tests std::string expected; expected = "\n"; - xml_test("undef", expected); + require_xml("undef", expected); mSD = 3463; expected = "3463\n"; - xml_test("integer", expected); + require_xml("integer", expected); mSD = ""; expected = "\n"; - xml_test("empty string", expected); + require_xml("empty string", expected); mSD = "foobar"; expected = "foobar\n"; - xml_test("string", expected); + require_xml("string", expected); mSD = LLUUID::null; expected = "\n"; - xml_test("null uuid", expected); + require_xml("null uuid", expected); mSD = LLUUID("c96f9b1e-f589-4100-9774-d98643ce0bed"); expected = "c96f9b1e-f589-4100-9774-d98643ce0bed\n"; - xml_test("uuid", expected); + require_xml("uuid", expected); mSD = LLURI("https://secondlife.com/login"); expected = "https://secondlife.com/login\n"; - xml_test("uri", expected); + require_xml("uri", expected); mSD = LLDate("2006-04-24T16:11:33Z"); expected = "2006-04-24T16:11:33Z\n"; - xml_test("date", expected); + require_xml("date", expected); // Generated by: echo -n 'hello' | openssl enc -e -base64 std::vector hello; @@ -138,11 +122,10 @@ namespace tut hello.push_back('o'); mSD = hello; expected = "aGVsbG8=\n"; - xml_test("binary", expected); + require_xml("binary", expected); } - template<> template<> - void sd_xml_object::test<2>() + TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: booleans format as words or digits by boolalpha", "[LLSDXMLFormatter]") { // tests with boolean values. std::string expected; @@ -150,23 +133,22 @@ namespace tut mFormatter->boolalpha(true); mSD = true; expected = "true\n"; - xml_test("bool alpha true", expected); + require_xml("bool alpha true", expected); mSD = false; expected = "false\n"; - xml_test("bool alpha false", expected); + require_xml("bool alpha false", expected); mFormatter->boolalpha(false); mSD = true; expected = "1\n"; - xml_test("bool true", expected); + require_xml("bool true", expected); mSD = false; expected = "0\n"; - xml_test("bool false", expected); + require_xml("bool false", expected); } - template<> template<> - void sd_xml_object::test<3>() + TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: reals format shortest by default or by realFormat", "[LLSDXMLFormatter]") { // tests with real values. std::string expected; @@ -174,72 +156,69 @@ namespace tut // default format: shortest representation that round-trips exactly mSD = 0.1; expected = "0.1\n"; - xml_test("real default shortest", expected); + require_xml("real default shortest", expected); mSD = 1.0; expected = "1\n"; - xml_test("real default integral", expected); + require_xml("real default integral", expected); mFormatter->realFormat("%.2f"); mSD = 1.0; expected = "1.00\n"; - xml_test("real 1", expected); + require_xml("real 1", expected); mSD = -34379.0438; expected = "-34379.04\n"; - xml_test("real reduced precision", expected); + require_xml("real reduced precision", expected); mFormatter->realFormat("%.4f"); expected = "-34379.0438\n"; - xml_test("higher precision", expected); + require_xml("higher precision", expected); mFormatter->realFormat("%.0f"); mSD = 0.0; expected = "0\n"; - xml_test("no decimal 0", expected); + require_xml("no decimal 0", expected); mSD = 3287.4387; expected = "3287\n"; - xml_test("no decimal real number", expected); + require_xml("no decimal real number", expected); } - template<> template<> - void sd_xml_object::test<4>() + TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: arrays format empty and with elements", "[LLSDXMLFormatter]") { // tests with arrays std::string expected; mSD = LLSD::emptyArray(); expected = "\n"; - xml_test("empty array", expected); + require_xml("empty array", expected); mSD.append(LLSD()); expected = "\n"; - xml_test("1 element array", expected); + require_xml("1 element array", expected); mSD.append(1); expected = "1\n"; - xml_test("2 element array", expected); + require_xml("2 element array", expected); } - template<> template<> - void sd_xml_object::test<5>() + TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: maps format empty and with their keys sorted", "[LLSDXMLFormatter]") { // tests with arrays std::string expected; mSD = LLSD::emptyMap(); expected = "\n"; - xml_test("empty map", expected); + require_xml("empty map", expected); mSD["foo"] = "bar"; expected = "foobar\n"; - xml_test("1 element map", expected); + require_xml("1 element map", expected); mSD["baz"] = LLSD(); expected = "bazfoobar\n"; - xml_test("2 element map", expected); + require_xml("2 element map", expected); } - template<> template<> - void sd_xml_object::test<6>() + TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: binary formats as base64", "[LLSDXMLFormatter]") { // tests with binary std::string expected; @@ -247,15 +226,14 @@ namespace tut // Generated by: echo -n 'hello' | openssl enc -e -base64 mSD = string_to_vector("hello"); expected = "aGVsbG8=\n"; - xml_test("binary", expected); + require_xml("binary", expected); mSD = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); expected = "Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==\n"; - xml_test("binary", expected); + require_xml("binary", expected); } - template<> template<> - void sd_xml_object::test<7>() + TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: a single-line document deserializes without a spurious error", "[LLSDXMLFormatter]") { // A complete, single-line legacy document (no embedded '\n') is // read entirely into the header buffer by LLSDSerialize::deserialize and @@ -263,25 +241,24 @@ namespace tut // first chunk with nothing left for the stream to supply. That is a // successful parse and must not be reported as an error. Empty // containers are the common single-line form. - auto deserialize_no_spurious_error = + auto require_no_spurious_error = [](const std::string& xml, const LLSD& expected) { CaptureLog capture(LLError::LEVEL_INFO); std::istringstream input(xml); LLSD parsed; bool ok = LLSDSerialize::deserialize(parsed, input, xml.size()); - ensure(STRINGIZE("deserialize " << xml << " succeeded"), ok); - ensure_equals(STRINGIZE("deserialize " << xml << " value"), parsed, expected); - ensure(STRINGIZE("no spurious parse error for " << xml), - capture.messageWith("Unexpected XML parsing error", false).empty()); + CAPTURE(xml); + REQUIRE(ok); + REQUIRE_THAT(parsed, ll_test::LLSDEquals(expected)); + REQUIRE(capture.messageWith("Unexpected XML parsing error", false).empty()); }; - deserialize_no_spurious_error("\n", LLSD::emptyMap()); - deserialize_no_spurious_error("\n", LLSD::emptyArray()); + require_no_spurious_error("\n", LLSD::emptyMap()); + require_no_spurious_error("\n", LLSD::emptyArray()); } - template<> template<> - void sd_xml_object::test<8>() + TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: format restores the stream's precision and exception bits", "[LLSDXMLFormatter]") { // Test that stream state (precision and exceptions) is correctly restored after format() { @@ -299,15 +276,13 @@ namespace tut S32 result = mFormatter->format(mSD, ostr); // Verify formatting succeeded - ensure("format should succeed", result >= 0); + CHECK(result >= 0); // Verify precision was restored - ensure_equals("precision should be restored", - ostr.precision(), 10); + CHECK(ostr.precision() == 10); // Verify exceptions were restored - ensure_equals("exception bits should be restored", - ostr.exceptions(), original_exceptions); + CHECK(ostr.exceptions() == original_exceptions); } // Test with no bits set @@ -320,15 +295,13 @@ namespace tut S32 result = mFormatter->format(mSD, ostr); // Verify formatting succeeded - ensure("format should succeed", result >= 0); + CHECK(result >= 0); // Verify exceptions were removed - ensure_equals("exception bits should remain unchanged", - ostr.exceptions(), original_exceptions); + CHECK(ostr.exceptions() == original_exceptions); // Verify precision was still restored even on failure - ensure_equals("precision should be restored even on stream failure", - ostr.precision(), (std::streamsize)5); + CHECK(ostr.precision() == (std::streamsize)5); } } @@ -339,7 +312,7 @@ namespace tut ~TestLLSDSerializeData(); void doRoundTripTests(const std::string&); - void checkRoundTrip(const std::string&, const LLSD& v); + void require_round_trip(const std::string& what, const LLSD& v); void setFormatterParser(LLPointer formatter, LLPointer parser) { @@ -378,7 +351,7 @@ namespace tut { } - void TestLLSDSerializeData::checkRoundTrip(const std::string& msg, const LLSD& v) + void TestLLSDSerializeData::require_round_trip(const std::string& what, const LLSD& v) { std::stringstream stream; mFormatter(v, stream); @@ -388,7 +361,8 @@ namespace tut try { - ensure_equals(msg, w, v); + INFO(what); + REQUIRE_THAT(w, ll_test::LLSDEquals(v)); } catch (...) { @@ -417,58 +391,58 @@ namespace tut void TestLLSDSerializeData::doRoundTripTests(const std::string& msg) { LLSD v; - checkRoundTrip(msg + " undefined", v); + require_round_trip(msg + " undefined", v); v = true; - checkRoundTrip(msg + " true bool", v); + require_round_trip(msg + " true bool", v); v = false; - checkRoundTrip(msg + " false bool", v); + require_round_trip(msg + " false bool", v); v = 1; - checkRoundTrip(msg + " positive int", v); + require_round_trip(msg + " positive int", v); v = 0; - checkRoundTrip(msg + " zero int", v); + require_round_trip(msg + " zero int", v); v = -1; - checkRoundTrip(msg + " negative int", v); + require_round_trip(msg + " negative int", v); v = 1234.5f; - checkRoundTrip(msg + " positive float", v); + require_round_trip(msg + " positive float", v); v = 0.0f; - checkRoundTrip(msg + " zero float", v); + require_round_trip(msg + " zero float", v); v = -1234.5f; - checkRoundTrip(msg + " negative float", v); + require_round_trip(msg + " negative float", v); // reals whose decimal expansion exceeds the default 6-digit stream // precision must still round-trip exactly v = 3.141592653589793; - checkRoundTrip(msg + " full precision real", v); + require_round_trip(msg + " full precision real", v); v = 1.0e300; - checkRoundTrip(msg + " huge real", v); + require_round_trip(msg + " huge real", v); v = -2.718281828459045e-12; - checkRoundTrip(msg + " tiny negative real", v); + require_round_trip(msg + " tiny negative real", v); // FIXME: need a NaN test v = LLUUID::null; - checkRoundTrip(msg + " null uuid", v); + require_round_trip(msg + " null uuid", v); LLUUID newUUID; newUUID.generate(); v = newUUID; - checkRoundTrip(msg + " new uuid", v); + require_round_trip(msg + " new uuid", v); v = ""; - checkRoundTrip(msg + " empty string", v); + require_round_trip(msg + " empty string", v); v = "some string"; - checkRoundTrip(msg + " non-empty string", v); + require_round_trip(msg + " non-empty string", v); v = "Second Life is a 3-D virtual world entirely built and owned by its residents. " @@ -491,7 +465,7 @@ namespace tut "\n" "Welcome to Second Life. We look forward to seeing you in-world!\n" ; - checkRoundTrip(msg + " long string", v); + require_round_trip(msg + " long string", v); static const U32 block_size = 0x000020; for (U32 block = 0x000000; block <= 0x10ffff; block += block_size) @@ -540,20 +514,20 @@ namespace tut std::ostringstream blockmsg; blockmsg << msg << " unicode string block 0x" << std::hex << block; - checkRoundTrip(blockmsg.str(), v); + require_round_trip(blockmsg.str(), v); } LLDate epoch; v = epoch; - checkRoundTrip(msg + " epoch date", v); + require_round_trip(msg + " epoch date", v); LLDate aDay("2002-12-07T05:07:15.00Z"); v = aDay; - checkRoundTrip(msg + " date", v); + require_round_trip(msg + " date", v); LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/"); v = path; - checkRoundTrip(msg + " url", v); + require_round_trip(msg + " url", v); const char source[] = "it must be a blue moon again"; std::vector data; @@ -561,130 +535,39 @@ namespace tut copy(&source[0], &source[sizeof(source)], back_inserter(data)); v = data; - checkRoundTrip(msg + " binary", v); + require_round_trip(msg + " binary", v); v = LLSD::emptyMap(); - checkRoundTrip(msg + " empty map", v); + require_round_trip(msg + " empty map", v); v = LLSD::emptyMap(); v["name"] = "luke"; //v.insert("name", "luke"); v["age"] = 3; //v.insert("age", 3); - checkRoundTrip(msg + " map", v); + require_round_trip(msg + " map", v); v.clear(); v["a"]["1"] = true; v["b"]["0"] = false; - checkRoundTrip(msg + " nested maps", v); + require_round_trip(msg + " nested maps", v); v = LLSD::emptyArray(); - checkRoundTrip(msg + " empty array", v); + require_round_trip(msg + " empty array", v); v = LLSD::emptyArray(); v.append("ali"); v.append(28); - checkRoundTrip(msg + " array", v); + require_round_trip(msg + " array", v); v.clear(); v[0][0] = true; v[1][0] = false; - checkRoundTrip(msg + " nested arrays", v); + require_round_trip(msg + " nested arrays", v); v = LLSD::emptyMap(); fillmap(v, 10, 3); // 10^6 maps - checkRoundTrip(msg + " many nested maps", v); - } - - typedef tut::test_group TestLLSDSerializeGroup; - typedef TestLLSDSerializeGroup::object TestLLSDSerializeObject; - TestLLSDSerializeGroup gTestLLSDSerializeGroup("llsd serialization"); - - template<> template<> - void TestLLSDSerializeObject::test<1>() - { - setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_PRETTY_BINARY), - new LLSDNotationParser()); - doRoundTripTests("pretty binary notation serialization"); - } - - template<> template<> - void TestLLSDSerializeObject::test<2>() - { - setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE), - new LLSDNotationParser()); - doRoundTripTests("raw binary notation serialization"); - } - - template<> template<> - void TestLLSDSerializeObject::test<3>() - { - setFormatterParser(new LLSDXMLFormatter(), new LLSDXMLParser()); - doRoundTripTests("xml serialization"); - } - - template<> template<> - void TestLLSDSerializeObject::test<4>() - { - setFormatterParser(new LLSDBinaryFormatter(), new LLSDBinaryParser()); - doRoundTripTests("binary serialization"); + require_round_trip(msg + " many nested maps", v); } - template<> template<> - void TestLLSDSerializeObject::test<5>() - { - mFormatter = [](const LLSD& sd, std::ostream& str) - { - LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_BINARY); - }; - setParser(LLSDSerialize::deserialize); - doRoundTripTests("serialize(LLSD_BINARY)"); - }; - - template<> template<> - void TestLLSDSerializeObject::test<6>() - { - mFormatter = [](const LLSD& sd, std::ostream& str) - { - LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_XML); - }; - setParser(LLSDSerialize::deserialize); - doRoundTripTests("serialize(LLSD_XML)"); - }; - - template<> template<> - void TestLLSDSerializeObject::test<7>() - { - mFormatter = [](const LLSD& sd, std::ostream& str) - { - LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_NOTATION); - }; - setParser(LLSDSerialize::deserialize); - // In this test, serialize(LLSD_NOTATION) emits a header recognized by - // deserialize(). - doRoundTripTests("serialize(LLSD_NOTATION)"); - }; - - template<> template<> - void TestLLSDSerializeObject::test<8>() - { - setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE), - new LLSDNotationParser()); - setParser(LLSDSerialize::deserialize); - // This is an interesting test because LLSDNotationFormatter does not - // emit an llsd/notation header. - doRoundTripTests("LLSDNotationFormatter -> deserialize"); - }; - - template<> template<> - void TestLLSDSerializeObject::test<9>() - { - setFormatterParser(new LLSDXMLFormatter(false, "", LLSDFormatter::OPTIONS_NONE), - new LLSDXMLParser()); - setParser(LLSDSerialize::deserialize); - // This is an interesting test because LLSDXMLFormatter does not - // emit an LLSD/XML header. - doRoundTripTests("LLSDXMLFormatter -> deserialize"); - }; - /*==========================================================================*| // We do not expect this test to succeed. Without a header, neither // notation LLSD nor binary LLSD reliably start with a distinct character, @@ -702,174 +585,6 @@ namespace tut }; |*==========================================================================*/ - template<> template<> - void TestLLSDSerializeObject::test<10>() - { - // deserialize() with SIZE_UNLIMITED must not turn the unlimited - // byte budget into a bogus negative one when a header line is - // present; sized payloads (binary strings) used to fail the - // resulting negative budget check. - LLSD sd; - sd["foo"] = "bar"; - sd["bin"] = LLSD::Binary{1, 2, 3}; - - for (auto fmt : {LLSDSerialize::LLSD_BINARY, - LLSDSerialize::LLSD_XML, - LLSDSerialize::LLSD_NOTATION}) - { - std::stringstream stream; - LLSDSerialize::serialize(sd, stream, fmt); - LLSD parsed; - ensure(STRINGIZE("deserialize unlimited fmt " << int(fmt)), - LLSDSerialize::deserialize(parsed, stream, LLSDSerialize::SIZE_UNLIMITED)); - ensure_equals(STRINGIZE("deserialize unlimited fmt " << int(fmt) << " value"), - parsed, sd); - } - } - - template<> template<> - void TestLLSDSerializeObject::test<11>() - { - // strip_deprecated_header() must consume the newline following the - // header (the binary parser does not tolerate leading whitespace) - // and report the actual number of bytes skipped. - LLSD sd; - sd["int"] = 17; - sd["str"] = "hello"; - - std::stringstream binstream; - LLSDSerialize::toBinary(sd, binstream); - const std::string body = binstream.str(); - - // header with trailing newline, as LLSDSerialize::serialize() writes it - { - std::string payload = "\n" + body; - std::vector buf(payload.begin(), payload.end()); - llssize cur_size = (llssize)buf.size(); - llssize header_size = 0; - char* p = strip_deprecated_header(buf.data(), cur_size, &header_size); - ensure_equals("stripped header+newline", header_size, llssize(18)); - ensure_equals("stripped size", cur_size, llssize(body.size())); - LLMemoryStream mstr((U8*)p, (S32)cur_size); - LLSD parsed; - ensure("parse after strip", - LLSDSerialize::fromBinary(parsed, mstr, cur_size) > 0); - ensure_equals("value after strip", parsed, sd); - } - - // bare header with no newline - { - std::string payload = "" + body; - std::vector buf(payload.begin(), payload.end()); - llssize cur_size = (llssize)buf.size(); - llssize header_size = 0; - char* p = strip_deprecated_header(buf.data(), cur_size, &header_size); - ensure_equals("stripped bare header", header_size, llssize(17)); - ensure_equals("stripped bare size", cur_size, llssize(body.size())); - LLMemoryStream mstr((U8*)p, (S32)cur_size); - LLSD parsed; - ensure("parse after bare strip", - LLSDSerialize::fromBinary(parsed, mstr, cur_size) > 0); - ensure_equals("value after bare strip", parsed, sd); - } - } - - template<> template<> - void TestLLSDSerializeObject::test<12>() - { - // zip_llsd/unzip_llsd round trip, and clean rejection of a - // corrupted compressed stream - LLSD sd; - sd["message"] = "destination unknown"; - sd["mode"] = 7; - LLSD arr = LLSD::emptyArray(); - for (S32 i = 0; i < 256; ++i) - { - arr.append(i * 0.25); - } - sd["samples"] = arr; - - std::string compressed = zip_llsd(sd); - ensure("zip_llsd produced data", !compressed.empty()); - - LLSD out; - ensure_equals("unzip_llsd ok", - LLUZipHelper::unzip_llsd(out, - (const U8*)compressed.data(), - (S32)compressed.size()), - LLUZipHelper::ZR_OK); - ensure_equals("unzip_llsd round trip", out, sd); - - std::string corrupt(compressed); - for (size_t i = corrupt.size() / 2; i < corrupt.size(); ++i) - { - corrupt[i] ^= 0x5A; - } - LLSD out2; - ensure("corrupt unzip_llsd rejected", - LLUZipHelper::unzip_llsd(out2, - (const U8*)corrupt.data(), - (S32)corrupt.size()) != LLUZipHelper::ZR_OK); - } - - template<> template<> - void TestLLSDSerializeObject::test<13>() - { - // unzip_llsdNavMesh: gzip round trip, and clean failure on a - // corrupted stream after partial output has been produced (this - // path used to realloc a freed buffer and double-free on error) - std::string payload; - payload.reserve(96 * 1024); - while (payload.size() < 96 * 1024) - { - payload += "navmesh payload block "; - } - - z_stream strm{}; - int ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, - 15 + 16, // gzip wrapper - 8, Z_DEFAULT_STRATEGY); - ensure_equals("deflateInit2", ret, Z_OK); - std::string compressed(deflateBound(&strm, (uLong)payload.size()) + 32, '\0'); - strm.next_in = (Bytef*)payload.data(); - strm.avail_in = (uInt)payload.size(); - strm.next_out = (Bytef*)&compressed[0]; - strm.avail_out = (uInt)compressed.size(); - ret = deflate(&strm, Z_FINISH); - ensure_equals("deflate finish", ret, Z_STREAM_END); - compressed.resize(compressed.size() - strm.avail_out); - deflateEnd(&strm); - - { - std::istringstream in(compressed); - bool valid = false; - size_t outsize = 0; - U8* out = unzip_llsdNavMesh(valid, outsize, in, (S32)compressed.size()); - ensure("navmesh unzip valid", valid); - ensure("navmesh unzip buffer", out != NULL); - ensure_equals("navmesh unzip size", outsize, payload.size()); - ensure("navmesh unzip content", - payload.compare(0, payload.size(), - (const char*)out, outsize) == 0); - free(out); - } - - { - // corrupt the deflate tail and gzip trailer - std::string corrupt(compressed); - for (size_t i = corrupt.size() - 16; i < corrupt.size(); ++i) - { - corrupt[i] ^= 0x5A; - } - std::istringstream in(corrupt); - bool valid = true; - size_t outsize = 0; - U8* out = unzip_llsdNavMesh(valid, outsize, in, (S32)corrupt.size()); - ensure("corrupt navmesh rejected", !valid); - ensure("corrupt navmesh returns null", out == NULL); - } - } - /** * @class TestLLSDParsing * @brief Base class for of a parse tester. @@ -883,8 +598,8 @@ namespace tut mParser = new parser_t; } - void ensureParse( - const std::string& msg, + void require_parse( + const std::string& what, const std::string& in, const LLSD& expected_value, S32 expected_count, @@ -896,14 +611,13 @@ namespace tut LLSD parsed_result; mParser->reset(); // reset() call is needed since test code re-uses mParser S32 parsed_count = mParser->parse(input, parsed_result, in.size(), depth_limit); - ensure_equals(msg.c_str(), parsed_result, expected_value); + INFO(what); + REQUIRE_THAT(parsed_result, ll_test::LLSDEquals(expected_value)); // This count check is really only useful for expected // parse failures, since the ensures equal will already // require equality. - std::string count_msg(msg); - count_msg += " (count)"; - ensure_equals(count_msg, parsed_count, expected_count); + REQUIRE(parsed_count == expected_count); } LLPointer mParser; @@ -920,31 +634,26 @@ namespace tut TestLLSDXMLParsing() {} }; - typedef tut::test_group TestLLSDXMLParsingGroup; - typedef TestLLSDXMLParsingGroup::object TestLLSDXMLParsingObject; - TestLLSDXMLParsingGroup gTestLLSDXMLParsingGroup("llsd XML parsing"); - - template<> template<> - void TestLLSDXMLParsingObject::test<1>() + TEST_CASE_METHOD(TestLLSDXMLParsing, "llsd XML parsing: xml that is not llsd fails to parse", "[llsd_XML_parsing]") { // test handling of xml not recognized as llsd results in an // LLSD Undefined - ensureParse( + require_parse( "malformed xml", "ha ha", LLSD(), LLSDParser::PARSE_FAILURE); - ensureParse( + require_parse( "not llsd", "

ha ha

", LLSD(), LLSDParser::PARSE_FAILURE); - ensureParse( + require_parse( "value without llsd", "ha ha", LLSD(), LLSDParser::PARSE_FAILURE); - ensureParse( + require_parse( "key without llsd", "ha ha", LLSD(), @@ -952,8 +661,7 @@ namespace tut } - template<> template<> - void TestLLSDXMLParsingObject::test<2>() + TEST_CASE_METHOD(TestLLSDXMLParsing, "llsd XML parsing: a value of unknown type parses as undefined", "[llsd_XML_parsing]") { // test handling of unrecognized or unparseable llsd values LLSD v; @@ -961,7 +669,7 @@ namespace tut v["bob"] = LLSD(); v["cam"] = 1.23; - ensureParse( + require_parse( "unknown data type", "" "amy23" @@ -972,8 +680,7 @@ namespace tut static_cast(v.size()) + 1); } - template<> template<> - void TestLLSDXMLParsingObject::test<3>() + TEST_CASE_METHOD(TestLLSDXMLParsing, "llsd XML parsing: html inside maps and arrays is skipped", "[llsd_XML_parsing]") { // test handling of nested bad data @@ -981,7 +688,7 @@ namespace tut v["amy"] = 23; v["cam"] = 1.23; - ensureParse( + require_parse( "map with html", "" "amy23" @@ -994,7 +701,7 @@ namespace tut v.clear(); v["amy"] = 23; v["cam"] = 1.23; - ensureParse( + require_parse( "map with value for key", "" "amy23" @@ -1008,7 +715,7 @@ namespace tut v["amy"] = 23; v["bob"] = LLSD::emptyMap(); v["cam"] = 1.23; - ensureParse( + require_parse( "map with map of html", "" "amy23" @@ -1026,7 +733,7 @@ namespace tut v[1] = LLSD(); v[2] = 1.23; - ensureParse( + require_parse( "array value of html", "" "23" @@ -1040,7 +747,7 @@ namespace tut v[0] = 23; v[1] = LLSD::emptyMap(); v[2] = 1.23; - ensureParse( + require_parse( "array with map of html", "" "23" @@ -1053,8 +760,7 @@ namespace tut static_cast(v.size()) + 1); } - template<> template<> - void TestLLSDXMLParsingObject::test<4>() + TEST_CASE_METHOD(TestLLSDXMLParsing, "llsd XML parsing: base64 binary parses on one line or many", "[llsd_XML_parsing]") { // test handling of binary object in XML std::string xml; @@ -1063,7 +769,7 @@ namespace tut // Generated by: echo -n 'hello' | openssl enc -e -base64 expected = string_to_vector("hello"); xml = "aGVsbG8=\n"; - ensureParse( + require_parse( "the word 'hello' packed in binary encoded base64", xml, expected, @@ -1071,7 +777,7 @@ namespace tut expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); xml = "Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==\n"; - ensureParse( + require_parse( "a common binary blob for object -> agent offline inv transfer", xml, expected, @@ -1083,15 +789,14 @@ namespace tut xml += "LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZm\n"; xml += "ZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMy\n"; xml += "OXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==\n"; - ensureParse( - "a common binary blob for object -> agent offline inv transfer", + require_parse( + "the same blob in base64 broken into lines", xml, expected, 1); } - template<> template<> - void TestLLSDXMLParsingObject::test<5>() + TEST_CASE_METHOD(TestLLSDXMLParsing, "llsd XML parsing: deeply nested maps parse", "[llsd_XML_parsing]") { // test deeper nested levels LLSD level_5 = LLSD::emptyMap(); level_5["level_5"] = 42.f; @@ -1104,7 +809,7 @@ namespace tut LLSD v; v["deep"] = level_0; - ensureParse( + require_parse( "deep llsd xml map", "" "deep" @@ -1126,21 +831,20 @@ namespace tut } - template<> template<> - void TestLLSDXMLParsingObject::test<6>() + TEST_CASE_METHOD(TestLLSDXMLParsing, "llsd XML parsing: empty binary parses as empty Binary not undefined", "[llsd_XML_parsing]") { // empty binary must parse to an empty LLSD::Binary, not undef - ensureParse( + require_parse( "empty binary self-closed", "", LLSD(LLSD::Binary()), 1); - ensureParse( + require_parse( "empty binary open/close", "", LLSD(LLSD::Binary()), 1); - ensureParse( + require_parse( "whitespace-only binary", " \n ", LLSD(LLSD::Binary()), @@ -1166,282 +870,666 @@ namespace tut TestLLSDNotationParsing() {} }; - typedef tut::test_group TestLLSDNotationParsingGroup; - typedef TestLLSDNotationParsingGroup::object TestLLSDNotationParsingObject; - TestLLSDNotationParsingGroup gTestLLSDNotationParsingGroup( - "llsd notation parsing"); + /** + * @class TestLLSDBinaryParsing + * @brief Concrete instance of a parse tester. + */ + class TestLLSDBinaryParsing : public TestLLSDParsing + { + public: + TestLLSDBinaryParsing() {} + }; - template<> template<> - void TestLLSDNotationParsingObject::test<1>() + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: a string parses only with the right size", "[llsd_binary_parsing]") { - // test handling of xml not recognized as llsd results in an - // LLSD Undefined - ensureParse( - "malformed notation map", - "{'ha ha'", - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "malformed notation array", - "['ha ha'", - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "malformed notation string", - "'ha ha", - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "bad notation noise", - "g48ejlnfr", - LLSD(), - LLSDParser::PARSE_FAILURE); - } + std::vector vec; + vec.resize(6); + vec[0] = 'a'; vec[1] = 'b'; vec[2] = 'c'; + vec[3] = '3'; vec[4] = '2'; vec[5] = '1'; + std::string string_expected((char*)&vec[0], vec.size()); + LLSD value = string_expected; - template<> template<> - void TestLLSDNotationParsingObject::test<2>() - { - ensureParse("valid undef", "!", LLSD(), 1); - } + vec.resize(11); + vec[0] = 's'; // for string + vec[5] = 'a'; vec[6] = 'b'; vec[7] = 'c'; + vec[8] = '3'; vec[9] = '2'; vec[10] = '1'; - template<> template<> - void TestLLSDNotationParsingObject::test<3>() - { - LLSD val = false; - ensureParse("valid boolean false 0", "false", val, 1); - ensureParse("valid boolean false 1", "f", val, 1); - ensureParse("valid boolean false 2", "0", val, 1); - ensureParse("valid boolean false 3", "F", val, 1); - ensureParse("valid boolean false 4", "FALSE", val, 1); - val = true; - ensureParse("valid boolean true 0", "true", val, 1); - ensureParse("valid boolean true 1", "t", val, 1); - ensureParse("valid boolean true 2", "1", val, 1); - ensureParse("valid boolean true 3", "T", val, 1); - ensureParse("valid boolean true 4", "TRUE", val, 1); + uint32_t size = htonl(6); + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_good((char*)&vec[0], vec.size()); + require_parse("correct string parse", str_good, value, 1); - val.clear(); - ensureParse("invalid true", "TR", val, LLSDParser::PARSE_FAILURE); - ensureParse("invalid false", "FAL", val, LLSDParser::PARSE_FAILURE); - } + size = htonl(7); + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_bad_1((char*)&vec[0], vec.size()); + require_parse( + "incorrect size string parse 1", + str_bad_1, + LLSD(), + LLSDParser::PARSE_FAILURE); - template<> template<> - void TestLLSDNotationParsingObject::test<4>() - { - LLSD val = 123; - ensureParse("valid integer", "i123", val, 1); - ensureParse("explicit plus integer", "i+123", val, 1); - val = -123; - ensureParse("negative integer", "i-123", val, 1); - val.clear(); - ensureParse("invalid integer", "421", val, LLSDParser::PARSE_FAILURE); - ensureParse("empty integer", "i,", val, LLSDParser::PARSE_FAILURE); - // an oversized token must fail whole, not parse as a buffered prefix - ensureParse("oversized integer token", - "i" + std::string(100, '1'), val, LLSDParser::PARSE_FAILURE); + size = htonl(100000); + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_bad_2((char*)&vec[0], vec.size()); + require_parse( + "incorrect size string parse 2", + str_bad_2, + LLSD(), + LLSDParser::PARSE_FAILURE); } - template<> template<> - void TestLLSDNotationParsingObject::test<5>() + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: binary parses only with the right size", "[llsd_binary_parsing]") { - LLSD val = 456.7; - ensureParse("valid real", "r456.7", val, 1); - ensureParse("explicit plus real", "r+456.7", val, 1); - val = -456.7; - ensureParse("negative real", "r-456.7", val, 1); - val = 1500.0; - ensureParse("scientific real", "r1.5e3", val, 1); - ensureParse("scientific real explicit plus exponent", "r1.5E+3", val, 1); - val = std::numeric_limits::infinity(); - ensureParse("infinity real", "rinf", val, 1); - val = -std::numeric_limits::infinity(); - ensureParse("negative infinity real", "r-inf", val, 1); - val.clear(); - ensureParse("invalid real", "456.7", val, LLSDParser::PARSE_FAILURE); - ensureParse("empty real", "r,", val, LLSDParser::PARSE_FAILURE); - // an oversized token must fail whole, not parse as a buffered prefix - ensureParse("oversized real token", - "r0." + std::string(600, '3'), val, LLSDParser::PARSE_FAILURE); - } + std::vector vec; + vec.resize(6); + vec[0] = 'a'; vec[1] = 'b'; vec[2] = 'c'; + vec[3] = '3'; vec[4] = '2'; vec[5] = '1'; + LLSD value = vec; - template<> template<> - void TestLLSDNotationParsingObject::test<6>() - { - LLUUID id; - LLSD val = id; - ensureParse( - "unparseable uuid", - "u123", + vec.resize(11); + vec[0] = 'b'; // for binary + vec[5] = 'a'; vec[6] = 'b'; vec[7] = 'c'; + vec[8] = '3'; vec[9] = '2'; vec[10] = '1'; + + uint32_t size = htonl(6); + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_good((char*)&vec[0], vec.size()); + require_parse("correct binary parse", str_good, value, 1); + + size = htonl(7); + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_bad_1((char*)&vec[0], vec.size()); + require_parse( + "incorrect size binary parse 1", + str_bad_1, LLSD(), LLSDParser::PARSE_FAILURE); - id.generate(); - val = id; - std::string uuid_str("u"); - uuid_str += id.asString(); - ensureParse("valid uuid", uuid_str.c_str(), val, 1); - } - template<> template<> - void TestLLSDNotationParsingObject::test<7>() - { - LLSD val = std::string("foolish"); - ensureParse("valid string 1", "\"foolish\"", val, 1); - val = std::string("g'day"); - ensureParse("valid string 2", "\"g'day\"", val, 1); - val = std::string("have a \"nice\" day"); - ensureParse("valid string 3", "'have a \"nice\" day'", val, 1); - val = std::string("whatever"); - ensureParse("valid string 4", "s(8)\"whatever\"", val, 1); + size = htonl(100000); + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_bad_2((char*)&vec[0], vec.size()); + require_parse( + "incorrect size binary parse 2", + str_bad_2, + LLSD(), + LLSDParser::PARSE_FAILURE); } - template<> template<> - void TestLLSDNotationParsingObject::test<8>() + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: malformed input fails to parse", "[llsd_binary_parsing]") { - ensureParse( - "invalid string 1", - "s(7)\"whatever\"", + // test handling of xml not recognized as llsd results in an + // LLSD Undefined + require_parse( + "malformed binary map", + "{'ha ha'", LLSD(), LLSDParser::PARSE_FAILURE); - ensureParse( - "invalid string 2", - "s(9)\"whatever\"", + require_parse( + "malformed binary array", + "['ha ha'", + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "malformed binary string", + "'ha ha", + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "bad noise", + "g48ejlnfr", LLSD(), LLSDParser::PARSE_FAILURE); } - - template<> template<> - void TestLLSDNotationParsingObject::test<9>() + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: undef parses", "[llsd_binary_parsing]") { - LLSD val = LLURI("http://www.google.com"); - ensureParse("valid uri", "l\"http://www.google.com\"", val, 1); + require_parse("valid undef", "!", LLSD(), 1); } - template<> template<> - void TestLLSDNotationParsingObject::test<10>() + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: booleans parse from 0 and 1 only", "[llsd_binary_parsing]") { - LLSD val = LLDate("2007-12-28T09:22:53.10Z"); - ensureParse("valid date", "d\"2007-12-28T09:22:53.10Z\"", val, 1); - } + LLSD val = false; + require_parse("valid boolean false 2", "0", val, 1); + val = true; + require_parse("valid boolean true 2", "1", val, 1); - template<> template<> - void TestLLSDNotationParsingObject::test<11>() - { - std::vector vec; - vec.push_back((U8)'a'); vec.push_back((U8)'b'); vec.push_back((U8)'c'); - vec.push_back((U8)'3'); vec.push_back((U8)'2'); vec.push_back((U8)'1'); - LLSD val = vec; - ensureParse("valid binary b64", "b64\"YWJjMzIx\"", val, 1); - ensureParse("valid bainry b16", "b16\"616263333231\"", val, 1); - ensureParse("valid bainry raw", "b(6)\"abc321\"", val, 1); + val.clear(); + require_parse("invalid true", "t", val, LLSDParser::PARSE_FAILURE); + require_parse("invalid false", "f", val, LLSDParser::PARSE_FAILURE); } - template<> template<> - void TestLLSDNotationParsingObject::test<12>() + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: a map parses only with the right sizes and terminator", "[llsd_binary_parsing]") { - ensureParse( - "invalid -- binary length specified too long", - "b(7)\"abc321\"", + std::vector vec; + vec.push_back('{'); + vec.resize(vec.size() + 4); + uint32_t size = htonl(1); + memcpy(&vec[1], &size, sizeof(uint32_t)); + vec.push_back('k'); + auto key_size_loc = vec.size(); + size = htonl(1); // 1 too short + vec.resize(vec.size() + 4); + memcpy(&vec[key_size_loc], &size, sizeof(uint32_t)); + vec.push_back('a'); vec.push_back('m'); vec.push_back('y'); + vec.push_back('i'); + auto integer_loc = vec.size(); + vec.resize(vec.size() + 4); + uint32_t val_int = htonl(23); + memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); + std::string str_bad_1((char*)&vec[0], vec.size()); + require_parse( + "invalid key size", + str_bad_1, LLSD(), LLSDParser::PARSE_FAILURE); - ensureParse( - "invalid -- binary length specified way too long", - "b(1000000)\"abc321\"", + + // check with correct size, but unterminated map (missing '}') + size = htonl(3); // correct size + memcpy(&vec[key_size_loc], &size, sizeof(uint32_t)); + std::string str_bad_2((char*)&vec[0], vec.size()); + require_parse( + "valid key size, unterminated map", + str_bad_2, LLSD(), LLSDParser::PARSE_FAILURE); - } - - template<> template<> - void TestLLSDNotationParsingObject::test<13>() - { - LLSD val; - val["amy"] = 23; - val["bob"] = LLSD(); - val["cam"] = 1.23; - ensureParse("simple map", "{'amy':i23,'bob':!,'cam':r1.23}", val, 4); - - val["bob"] = LLSD::emptyMap(); - val["bob"]["vehicle"] = std::string("bicycle"); - ensureParse( - "nested map", - "{'amy':i23,'bob':{'vehicle':'bicycle'},'cam':r1.23}", - val, - 5); - } - template<> template<> - void TestLLSDNotationParsingObject::test<14>() - { - LLSD val; - val.append(23); - val.append(LLSD()); - val.append(1.23); - ensureParse("simple array", "[i23,!,r1.23]", val, 4); - val[1] = LLSD::emptyArray(); - val[1].append("bicycle"); - ensureParse("nested array", "[i23,['bicycle'],r1.23]", val, 5); - } - - template<> template<> - void TestLLSDNotationParsingObject::test<15>() - { + // check w/ correct size and correct map termination LLSD val; val["amy"] = 23; - val["bob"]["dogs"] = LLSD::emptyArray(); - val["bob"]["dogs"].append(LLSD::emptyMap()); - val["bob"]["dogs"][0]["name"] = std::string("groove"); - val["bob"]["dogs"][0]["breed"] = std::string("samoyed"); - val["bob"]["dogs"].append(LLSD::emptyMap()); - val["bob"]["dogs"][1]["name"] = std::string("greyley"); - val["bob"]["dogs"][1]["breed"] = std::string("chow/husky"); - val["cam"] = 1.23; - ensureParse( - "nested notation", - "{'amy':i23," - " 'bob':{'dogs':[" - "{'name':'groove', 'breed':'samoyed'}," - "{'name':'greyley', 'breed':'chow/husky'}]}," - " 'cam':r1.23}", + vec.push_back('}'); + std::string str_good((char*)&vec[0], vec.size()); + require_parse( + "valid map", + str_good, val, - 11); - } + 2); - template<> template<> - void TestLLSDNotationParsingObject::test<16>() - { - // text to make sure that incorrect sizes bail because - std::string bad_str("s(5)\"hi\""); - ensureParse( - "size longer than bytes left", - bad_str, + // check w/ incorrect sizes and correct map termination + size = htonl(0); // 1 too few (for the map entry) + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_bad_3((char*)&vec[0], vec.size()); + require_parse( + "invalid map too long", + str_bad_3, LLSD(), LLSDParser::PARSE_FAILURE); - } - template<> template<> - void TestLLSDNotationParsingObject::test<17>() - { - // text to make sure that incorrect sizes bail because - std::string bad_bin("b(5)\"hi\""); - ensureParse( - "size longer than bytes left", - bad_bin, + size = htonl(2); // 1 too many + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_bad_4((char*)&vec[0], vec.size()); + require_parse( + "invalid map too short", + str_bad_4, LLSD(), LLSDParser::PARSE_FAILURE); } - template<> template<> - void TestLLSDNotationParsingObject::test<18>() + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: an array parses only with the right size and terminator", "[llsd_binary_parsing]") { - LLSD level_1 = LLSD::emptyMap(); level_1["level_2"] = 99; - LLSD level_0 = LLSD::emptyMap(); level_0["level_1"] = level_1; + std::vector vec; + vec.push_back('['); + vec.resize(vec.size() + 4); + uint32_t size = htonl(1); // 1 too short + memcpy(&vec[1], &size, sizeof(uint32_t)); + vec.push_back('"'); vec.push_back('a'); vec.push_back('m'); + vec.push_back('y'); vec.push_back('"'); vec.push_back('i'); + auto integer_loc = vec.size(); + vec.resize(vec.size() + 4); + uint32_t val_int = htonl(23); + memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); - LLSD deep = LLSD::emptyMap(); - deep["level_0"] = level_0; + std::string str_bad_1((char*)&vec[0], vec.size()); + require_parse( + "invalid array size", + str_bad_1, + LLSD(), + LLSDParser::PARSE_FAILURE); - LLSD root = LLSD::emptyMap(); + // check with correct size, but unterminated map (missing ']') + size = htonl(2); // correct size + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_bad_2((char*)&vec[0], vec.size()); + require_parse( + "unterminated array", + str_bad_2, + LLSD(), + LLSDParser::PARSE_FAILURE); + + // check w/ correct size and correct map termination + LLSD val; + val.append("amy"); + val.append(23); + vec.push_back(']'); + std::string str_good((char*)&vec[0], vec.size()); + require_parse( + "valid array", + str_good, + val, + 3); + + // check with too many elements + size = htonl(3); // 1 too long + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_bad_3((char*)&vec[0], vec.size()); + require_parse( + "array too short", + str_bad_3, + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: an empty map parses", "[llsd_binary_parsing]") + { + std::vector vec; + vec.push_back('{'); + vec.resize(vec.size() + 4); + memset(&vec[1], 0, 4); + vec.push_back('}'); + std::string str_good((char*)&vec[0], vec.size()); + LLSD val = LLSD::emptyMap(); + require_parse( + "empty map", + str_good, + val, + 1); + } + + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: an empty array parses", "[llsd_binary_parsing]") + { + std::vector vec; + vec.push_back('['); + vec.resize(vec.size() + 4); + memset(&vec[1], 0, 4); + vec.push_back(']'); + std::string str_good((char*)&vec[0], vec.size()); + LLSD val = LLSD::emptyArray(); + require_parse( + "empty array", + str_good, + val, + 1); + } + + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: a uri parses only with the right length", "[llsd_binary_parsing]") + { + std::vector vec; + vec.push_back('l'); + vec.resize(vec.size() + 4); + uint32_t size = htonl(14); // 1 too long + memcpy(&vec[1], &size, sizeof(uint32_t)); + vec.push_back('h'); vec.push_back('t'); vec.push_back('t'); + vec.push_back('p'); vec.push_back(':'); vec.push_back('/'); + vec.push_back('/'); vec.push_back('s'); vec.push_back('l'); + vec.push_back('.'); vec.push_back('c'); vec.push_back('o'); + vec.push_back('m'); + std::string str_bad((char*)&vec[0], vec.size()); + require_parse( + "invalid uri length size", + str_bad, + LLSD(), + LLSDParser::PARSE_FAILURE); + + LLSD val; + val = LLURI("http://sl.com"); + size = htonl(13); // correct length + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_good((char*)&vec[0], vec.size()); + require_parse( + "valid uri length", + str_good, + val, + 1); + } + + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: hostile and negative sizes fail", "[llsd_binary_parsing]") + { + // hostile size fields must not be trusted + std::vector vec; + vec.push_back('['); + vec.resize(vec.size() + 4); + uint32_t size = htonl(0x7FFFFFFF); + memcpy(&vec[1], &size, sizeof(uint32_t)); + vec.push_back('i'); + auto integer_loc = vec.size(); + vec.resize(vec.size() + 4); + uint32_t val_int = htonl(23); + memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); + vec.push_back(']'); + std::string str_huge((char*)&vec[0], vec.size()); + require_parse( + "array size lies huge", + str_huge, + LLSD(), + LLSDParser::PARSE_FAILURE); + + // negative (unrepresentable) sizes used to sign-extend into a + // multi-gigabyte reserve() request + size = htonl(0xFFFFFFFF); + memcpy(&vec[1], &size, sizeof(uint32_t)); + std::string str_neg_array((char*)&vec[0], vec.size()); + require_parse( + "negative array size", + str_neg_array, + LLSD(), + LLSDParser::PARSE_FAILURE); + + vec[0] = '{'; + vec.back() = '}'; + std::string str_neg_map((char*)&vec[0], vec.size()); + require_parse( + "negative map size", + str_neg_map, + LLSD(), + LLSDParser::PARSE_FAILURE); + + std::vector bin; + bin.push_back('b'); + bin.resize(bin.size() + 4); + memcpy(&bin[1], &size, sizeof(uint32_t)); + std::string str_neg_bin((char*)&bin[0], bin.size()); + require_parse( + "negative binary size", + str_neg_bin, + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: truncated fixed-width payloads fail", "[llsd_binary_parsing]") + { + // truncated fixed-width payloads must fail, not parse as zero + require_parse( + "truncated integer", + std::string("i\x00\x01", 3), + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "truncated real", + std::string("r\x3f\xf0\x00\x00", 5), + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "truncated uuid", + std::string("u\x01\x02\x03\x04\x05\x06\x07\x08", 9), + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: a bogus map key marker fails", "[llsd_binary_parsing]") + { + // a map key must be marked 'k' or quoted; anything else used to be + // swallowed silently as an empty key, desynchronizing the stream + std::vector vec; + vec.push_back('{'); + vec.resize(vec.size() + 4); + uint32_t size = htonl(1); + memcpy(&vec[1], &size, sizeof(uint32_t)); + vec.push_back('x'); // bogus key marker + vec.push_back('i'); + auto integer_loc = vec.size(); + vec.resize(vec.size() + 4); + uint32_t val_int = htonl(23); + memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); + vec.push_back('}'); + std::string str_bad((char*)&vec[0], vec.size()); + require_parse( + "bogus map key marker", + str_bad, + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: malformed input fails to parse", "[llsd_notation_parsing]") + { + // test handling of xml not recognized as llsd results in an + // LLSD Undefined + require_parse( + "malformed notation map", + "{'ha ha'", + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "malformed notation array", + "['ha ha'", + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "malformed notation string", + "'ha ha", + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "bad notation noise", + "g48ejlnfr", + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: undef parses", "[llsd_notation_parsing]") + { + require_parse("valid undef", "!", LLSD(), 1); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: booleans parse in every spelling and fail when cut short", "[llsd_notation_parsing]") + { + LLSD val = false; + require_parse("valid boolean false 0", "false", val, 1); + require_parse("valid boolean false 1", "f", val, 1); + require_parse("valid boolean false 2", "0", val, 1); + require_parse("valid boolean false 3", "F", val, 1); + require_parse("valid boolean false 4", "FALSE", val, 1); + val = true; + require_parse("valid boolean true 0", "true", val, 1); + require_parse("valid boolean true 1", "t", val, 1); + require_parse("valid boolean true 2", "1", val, 1); + require_parse("valid boolean true 3", "T", val, 1); + require_parse("valid boolean true 4", "TRUE", val, 1); + + val.clear(); + require_parse("invalid true", "TR", val, LLSDParser::PARSE_FAILURE); + require_parse("invalid false", "FAL", val, LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: integers parse with a sign and fail when malformed", "[llsd_notation_parsing]") + { + LLSD val = 123; + require_parse("valid integer", "i123", val, 1); + require_parse("explicit plus integer", "i+123", val, 1); + val = -123; + require_parse("negative integer", "i-123", val, 1); + val.clear(); + require_parse("invalid integer", "421", val, LLSDParser::PARSE_FAILURE); + require_parse("empty integer", "i,", val, LLSDParser::PARSE_FAILURE); + // an oversized token must fail whole, not parse as a buffered prefix + require_parse("oversized integer token", + "i" + std::string(100, '1'), val, LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: reals parse signed and scientific and infinite", "[llsd_notation_parsing]") + { + LLSD val = 456.7; + require_parse("valid real", "r456.7", val, 1); + require_parse("explicit plus real", "r+456.7", val, 1); + val = -456.7; + require_parse("negative real", "r-456.7", val, 1); + val = 1500.0; + require_parse("scientific real", "r1.5e3", val, 1); + require_parse("scientific real explicit plus exponent", "r1.5E+3", val, 1); + val = std::numeric_limits::infinity(); + require_parse("infinity real", "rinf", val, 1); + val = -std::numeric_limits::infinity(); + require_parse("negative infinity real", "r-inf", val, 1); + val.clear(); + require_parse("invalid real", "456.7", val, LLSDParser::PARSE_FAILURE); + require_parse("empty real", "r,", val, LLSDParser::PARSE_FAILURE); + // an oversized token must fail whole, not parse as a buffered prefix + require_parse("oversized real token", + "r0." + std::string(600, '3'), val, LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: a uuid parses only when well-formed", "[llsd_notation_parsing]") + { + LLUUID id; + LLSD val = id; + require_parse( + "unparseable uuid", + "u123", + LLSD(), + LLSDParser::PARSE_FAILURE); + id.generate(); + val = id; + std::string uuid_str("u"); + uuid_str += id.asString(); + require_parse("valid uuid", uuid_str.c_str(), val, 1); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: strings parse quoted or sized", "[llsd_notation_parsing]") + { + LLSD val = std::string("foolish"); + require_parse("valid string 1", "\"foolish\"", val, 1); + val = std::string("g'day"); + require_parse("valid string 2", "\"g'day\"", val, 1); + val = std::string("have a \"nice\" day"); + require_parse("valid string 3", "'have a \"nice\" day'", val, 1); + val = std::string("whatever"); + require_parse("valid string 4", "s(8)\"whatever\"", val, 1); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: a sized string with the wrong size fails", "[llsd_notation_parsing]") + { + require_parse( + "invalid string 1", + "s(7)\"whatever\"", + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "invalid string 2", + "s(9)\"whatever\"", + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: a uri parses", "[llsd_notation_parsing]") + { + LLSD val = LLURI("http://www.google.com"); + require_parse("valid uri", "l\"http://www.google.com\"", val, 1); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: a date parses", "[llsd_notation_parsing]") + { + LLSD val = LLDate("2007-12-28T09:22:53.10Z"); + require_parse("valid date", "d\"2007-12-28T09:22:53.10Z\"", val, 1); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: binary parses as b64 or b16 or raw", "[llsd_notation_parsing]") + { + std::vector vec; + vec.push_back((U8)'a'); vec.push_back((U8)'b'); vec.push_back((U8)'c'); + vec.push_back((U8)'3'); vec.push_back((U8)'2'); vec.push_back((U8)'1'); + LLSD val = vec; + require_parse("valid binary b64", "b64\"YWJjMzIx\"", val, 1); + require_parse("valid binary b16", "b16\"616263333231\"", val, 1); + require_parse("valid binary raw", "b(6)\"abc321\"", val, 1); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: binary with too long a length fails", "[llsd_notation_parsing]") + { + require_parse( + "invalid -- binary length specified too long", + "b(7)\"abc321\"", + LLSD(), + LLSDParser::PARSE_FAILURE); + require_parse( + "invalid -- binary length specified way too long", + "b(1000000)\"abc321\"", + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: maps parse flat and nested", "[llsd_notation_parsing]") + { + LLSD val; + val["amy"] = 23; + val["bob"] = LLSD(); + val["cam"] = 1.23; + require_parse("simple map", "{'amy':i23,'bob':!,'cam':r1.23}", val, 4); + + val["bob"] = LLSD::emptyMap(); + val["bob"]["vehicle"] = std::string("bicycle"); + require_parse( + "nested map", + "{'amy':i23,'bob':{'vehicle':'bicycle'},'cam':r1.23}", + val, + 5); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: arrays parse flat and nested", "[llsd_notation_parsing]") + { + LLSD val; + val.append(23); + val.append(LLSD()); + val.append(1.23); + require_parse("simple array", "[i23,!,r1.23]", val, 4); + val[1] = LLSD::emptyArray(); + val[1].append("bicycle"); + require_parse("nested array", "[i23,['bicycle'],r1.23]", val, 5); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: maps and arrays nest in each other", "[llsd_notation_parsing]") + { + LLSD val; + val["amy"] = 23; + val["bob"]["dogs"] = LLSD::emptyArray(); + val["bob"]["dogs"].append(LLSD::emptyMap()); + val["bob"]["dogs"][0]["name"] = std::string("groove"); + val["bob"]["dogs"][0]["breed"] = std::string("samoyed"); + val["bob"]["dogs"].append(LLSD::emptyMap()); + val["bob"]["dogs"][1]["name"] = std::string("greyley"); + val["bob"]["dogs"][1]["breed"] = std::string("chow/husky"); + val["cam"] = 1.23; + require_parse( + "nested notation", + "{'amy':i23," + " 'bob':{'dogs':[" + "{'name':'groove', 'breed':'samoyed'}," + "{'name':'greyley', 'breed':'chow/husky'}]}," + " 'cam':r1.23}", + val, + 11); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: a string size past the end of input fails", "[llsd_notation_parsing]") + { + // text to make sure that incorrect sizes bail because + std::string bad_str("s(5)\"hi\""); + require_parse( + "size longer than bytes left", + bad_str, + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: a binary size past the end of input fails", "[llsd_notation_parsing]") + { + // text to make sure that incorrect sizes bail because + std::string bad_bin("b(5)\"hi\""); + require_parse( + "size longer than bytes left", + bad_bin, + LLSD(), + LLSDParser::PARSE_FAILURE); + } + + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: nesting 5 deep parses within a depth limit of 5", "[llsd_notation_parsing]") + { + LLSD level_1 = LLSD::emptyMap(); level_1["level_2"] = 99; + LLSD level_0 = LLSD::emptyMap(); level_0["level_1"] = level_1; + + LLSD deep = LLSD::emptyMap(); + deep["level_0"] = level_0; + + LLSD root = LLSD::emptyMap(); root["deep"] = deep; - ensureParse( + require_parse( "nested notation 3 deep", "{'deep' : {'level_0':{'level_1':{'level_2': i99} } } }", root, @@ -1449,8 +1537,7 @@ namespace tut 5); // 4 '{' plus i99 also counts as llsd, so real depth is 5 } - template<> template<> - void TestLLSDNotationParsingObject::test<19>() + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: nesting 12 deep parses within a depth limit of 15", "[llsd_notation_parsing]") { LLSD level_9 = LLSD::emptyMap(); level_9["level_9"] = (S32)99; LLSD level_8 = LLSD::emptyMap(); level_8["level_8"] = level_9; @@ -1466,7 +1553,7 @@ namespace tut LLSD deep = LLSD::emptyMap(); deep["deep"] = level_0; - ensureParse( + require_parse( "nested notation 10 deep", "{'deep' : {'level_0':{'level_1':{'level_2':{'level_3':{'level_4':{'level_5':{'level_6':{'level_7':{'level_8':{'level_9':i99}" "} } } } } } } } } }", @@ -1475,8 +1562,7 @@ namespace tut 15); } - template<> template<> - void TestLLSDNotationParsingObject::test<20>() + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: nesting over 50 deep parses with no depth limit", "[llsd_notation_parsing]") { LLSD end = LLSD::emptyMap(); end["end"] = (S32)99; @@ -1538,7 +1624,7 @@ namespace tut LLSD deep = LLSD::emptyMap(); deep["deep"] = level_0; - ensureParse( + require_parse( "nested notation deep", "{'deep':" "{'level_0' :{'level_1' :{'level_2' :{'level_3' :{'level_4' :{'level_5' :{'level_6' :{'level_7' :{'level_8' :{'level_9' :" @@ -1557,11 +1643,10 @@ namespace tut 53); } - template<> template<> - void TestLLSDNotationParsingObject::test<21>() + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: nesting past the depth limit fails", "[llsd_notation_parsing]") { - ensureParse( - "nested notation 10 deep", + require_parse( + "nested notation 10 deep past a depth limit of 9", "{'deep' : {'level_0':{'level_1':{'level_2':{'level_3':{'level_4':{'level_5':{'level_6':{'level_7':{'level_8':{'level_9':i99}" "} } } } } } } } } }", LLSD(), @@ -1569,488 +1654,298 @@ namespace tut 9); } - template<> template<> - void TestLLSDNotationParsingObject::test<22>() + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: b16 that is empty parses and odd or unterminated fails", "[llsd_notation_parsing]") { // b16 edge cases - ensureParse("empty b16", "b16\"\"", LLSD(LLSD::Binary()), 1); + require_parse("empty b16", "b16\"\"", LLSD(LLSD::Binary()), 1); // an odd number of hex digits cannot form whole bytes - ensureParse( - "odd-length b16", - "b16\"616\"", - LLSD(), - LLSDParser::PARSE_FAILURE); - // unterminated b16 data must fail rather than spin forever - ensureParse( - "unterminated b16", - "b16\"6162", - LLSD(), - LLSDParser::PARSE_FAILURE); - } - - template<> template<> - void TestLLSDNotationParsingObject::test<23>() - { - // b64 edge cases - ensureParse("empty b64", "b64\"\"", LLSD(LLSD::Binary()), 1); - - std::vector vec; - vec.push_back((U8)'a'); vec.push_back((U8)'b'); vec.push_back((U8)'c'); - vec.push_back((U8)'3'); vec.push_back((U8)'2'); vec.push_back((U8)'1'); - LLSD val = vec; - // forgiving base64: ASCII whitespace inside the payload is ignored - ensureParse("b64 with whitespace", "b64\"YWJj\nMzIx\"", val, 1); - ensureParse( - "invalid b64 data", - "b64\"Y!WJjMzIx\"", - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "unterminated b64", - "b64\"YWJj", - LLSD(), - LLSDParser::PARSE_FAILURE); - } - - template<> template<> - void TestLLSDNotationParsingObject::test<24>() - { - // negative sizes must not be fed to resize() - ensureParse( - "negative raw binary size", - "b(-5)\"hi\"", - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "negative raw string size", - "s(-5)\"hi\"", - LLSD(), - LLSDParser::PARSE_FAILURE); - } - - /** - * @class TestLLSDBinaryParsing - * @brief Concrete instance of a parse tester. - */ - class TestLLSDBinaryParsing : public TestLLSDParsing - { - public: - TestLLSDBinaryParsing() {} - }; - - typedef tut::test_group TestLLSDBinaryParsingGroup; - typedef TestLLSDBinaryParsingGroup::object TestLLSDBinaryParsingObject; - TestLLSDBinaryParsingGroup gTestLLSDBinaryParsingGroup( - "llsd binary parsing"); - - template<> template<> - void TestLLSDBinaryParsingObject::test<1>() - { - std::vector vec; - vec.resize(6); - vec[0] = 'a'; vec[1] = 'b'; vec[2] = 'c'; - vec[3] = '3'; vec[4] = '2'; vec[5] = '1'; - std::string string_expected((char*)&vec[0], vec.size()); - LLSD value = string_expected; - - vec.resize(11); - vec[0] = 's'; // for string - vec[5] = 'a'; vec[6] = 'b'; vec[7] = 'c'; - vec[8] = '3'; vec[9] = '2'; vec[10] = '1'; - - uint32_t size = htonl(6); - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_good((char*)&vec[0], vec.size()); - ensureParse("correct string parse", str_good, value, 1); - - size = htonl(7); - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_bad_1((char*)&vec[0], vec.size()); - ensureParse( - "incorrect size string parse", - str_bad_1, - LLSD(), - LLSDParser::PARSE_FAILURE); - - size = htonl(100000); - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_bad_2((char*)&vec[0], vec.size()); - ensureParse( - "incorrect size string parse", - str_bad_2, - LLSD(), - LLSDParser::PARSE_FAILURE); - } - - template<> template<> - void TestLLSDBinaryParsingObject::test<2>() - { - std::vector vec; - vec.resize(6); - vec[0] = 'a'; vec[1] = 'b'; vec[2] = 'c'; - vec[3] = '3'; vec[4] = '2'; vec[5] = '1'; - LLSD value = vec; - - vec.resize(11); - vec[0] = 'b'; // for binary - vec[5] = 'a'; vec[6] = 'b'; vec[7] = 'c'; - vec[8] = '3'; vec[9] = '2'; vec[10] = '1'; - - uint32_t size = htonl(6); - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_good((char*)&vec[0], vec.size()); - ensureParse("correct binary parse", str_good, value, 1); - - size = htonl(7); - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_bad_1((char*)&vec[0], vec.size()); - ensureParse( - "incorrect size binary parse 1", - str_bad_1, - LLSD(), - LLSDParser::PARSE_FAILURE); - - size = htonl(100000); - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_bad_2((char*)&vec[0], vec.size()); - ensureParse( - "incorrect size binary parse 2", - str_bad_2, - LLSD(), - LLSDParser::PARSE_FAILURE); - } - - template<> template<> - void TestLLSDBinaryParsingObject::test<3>() - { - // test handling of xml not recognized as llsd results in an - // LLSD Undefined - ensureParse( - "malformed binary map", - "{'ha ha'", - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "malformed binary array", - "['ha ha'", - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "malformed binary string", - "'ha ha", - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "bad noise", - "g48ejlnfr", - LLSD(), - LLSDParser::PARSE_FAILURE); - } - template<> template<> - void TestLLSDBinaryParsingObject::test<4>() - { - ensureParse("valid undef", "!", LLSD(), 1); - } - - template<> template<> - void TestLLSDBinaryParsingObject::test<5>() - { - LLSD val = false; - ensureParse("valid boolean false 2", "0", val, 1); - val = true; - ensureParse("valid boolean true 2", "1", val, 1); - - val.clear(); - ensureParse("invalid true", "t", val, LLSDParser::PARSE_FAILURE); - ensureParse("invalid false", "f", val, LLSDParser::PARSE_FAILURE); - } - - template<> template<> - void TestLLSDBinaryParsingObject::test<6>() - { - std::vector vec; - vec.push_back('{'); - vec.resize(vec.size() + 4); - uint32_t size = htonl(1); - memcpy(&vec[1], &size, sizeof(uint32_t)); - vec.push_back('k'); - auto key_size_loc = vec.size(); - size = htonl(1); // 1 too short - vec.resize(vec.size() + 4); - memcpy(&vec[key_size_loc], &size, sizeof(uint32_t)); - vec.push_back('a'); vec.push_back('m'); vec.push_back('y'); - vec.push_back('i'); - auto integer_loc = vec.size(); - vec.resize(vec.size() + 4); - uint32_t val_int = htonl(23); - memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); - std::string str_bad_1((char*)&vec[0], vec.size()); - ensureParse( - "invalid key size", - str_bad_1, + require_parse( + "odd-length b16", + "b16\"616\"", LLSD(), LLSDParser::PARSE_FAILURE); - - // check with correct size, but unterminated map (missing '}') - size = htonl(3); // correct size - memcpy(&vec[key_size_loc], &size, sizeof(uint32_t)); - std::string str_bad_2((char*)&vec[0], vec.size()); - ensureParse( - "valid key size, unterminated map", - str_bad_2, + // unterminated b16 data must fail rather than spin forever + require_parse( + "unterminated b16", + "b16\"6162", LLSD(), LLSDParser::PARSE_FAILURE); + } - // check w/ correct size and correct map termination - LLSD val; - val["amy"] = 23; - vec.push_back('}'); - std::string str_good((char*)&vec[0], vec.size()); - ensureParse( - "valid map", - str_good, - val, - 2); + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: b64 empty or with whitespace parses and invalid or unterminated fails", "[llsd_notation_parsing]") + { + // b64 edge cases + require_parse("empty b64", "b64\"\"", LLSD(LLSD::Binary()), 1); - // check w/ incorrect sizes and correct map termination - size = htonl(0); // 1 too few (for the map entry) - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_bad_3((char*)&vec[0], vec.size()); - ensureParse( - "invalid map too long", - str_bad_3, + std::vector vec; + vec.push_back((U8)'a'); vec.push_back((U8)'b'); vec.push_back((U8)'c'); + vec.push_back((U8)'3'); vec.push_back((U8)'2'); vec.push_back((U8)'1'); + LLSD val = vec; + // forgiving base64: ASCII whitespace inside the payload is ignored + require_parse("b64 with whitespace", "b64\"YWJj\nMzIx\"", val, 1); + require_parse( + "invalid b64 data", + "b64\"Y!WJjMzIx\"", LLSD(), LLSDParser::PARSE_FAILURE); - - size = htonl(2); // 1 too many - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_bad_4((char*)&vec[0], vec.size()); - ensureParse( - "invalid map too short", - str_bad_4, + require_parse( + "unterminated b64", + "b64\"YWJj", LLSD(), LLSDParser::PARSE_FAILURE); } - template<> template<> - void TestLLSDBinaryParsingObject::test<7>() + TEST_CASE_METHOD(TestLLSDNotationParsing, "llsd notation parsing: negative raw sizes fail", "[llsd_notation_parsing]") { - std::vector vec; - vec.push_back('['); - vec.resize(vec.size() + 4); - uint32_t size = htonl(1); // 1 too short - memcpy(&vec[1], &size, sizeof(uint32_t)); - vec.push_back('"'); vec.push_back('a'); vec.push_back('m'); - vec.push_back('y'); vec.push_back('"'); vec.push_back('i'); - auto integer_loc = vec.size(); - vec.resize(vec.size() + 4); - uint32_t val_int = htonl(23); - memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); - - std::string str_bad_1((char*)&vec[0], vec.size()); - ensureParse( - "invalid array size", - str_bad_1, + // negative sizes must not be fed to resize() + require_parse( + "negative raw binary size", + "b(-5)\"hi\"", LLSD(), LLSDParser::PARSE_FAILURE); - - // check with correct size, but unterminated map (missing ']') - size = htonl(2); // correct size - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_bad_2((char*)&vec[0], vec.size()); - ensureParse( - "unterminated array", - str_bad_2, + require_parse( + "negative raw string size", + "s(-5)\"hi\"", LLSD(), LLSDParser::PARSE_FAILURE); + } - // check w/ correct size and correct map termination - LLSD val; - val.append("amy"); - val.append(23); - vec.push_back(']'); - std::string str_good((char*)&vec[0], vec.size()); - ensureParse( - "valid array", - str_good, - val, - 3); + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: pretty binary notation round-trips", "[llsd_serialization]") + { + setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_PRETTY_BINARY), + new LLSDNotationParser()); + doRoundTripTests("pretty binary notation serialization"); + } - // check with too many elements - size = htonl(3); // 1 too long - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_bad_3((char*)&vec[0], vec.size()); - ensureParse( - "array too short", - str_bad_3, - LLSD(), - LLSDParser::PARSE_FAILURE); + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: raw binary notation round-trips", "[llsd_serialization]") + { + setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE), + new LLSDNotationParser()); + doRoundTripTests("raw binary notation serialization"); } - template<> template<> - void TestLLSDBinaryParsingObject::test<8>() + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: xml round-trips", "[llsd_serialization]") { - std::vector vec; - vec.push_back('{'); - vec.resize(vec.size() + 4); - memset(&vec[1], 0, 4); - vec.push_back('}'); - std::string str_good((char*)&vec[0], vec.size()); - LLSD val = LLSD::emptyMap(); - ensureParse( - "empty map", - str_good, - val, - 1); + setFormatterParser(new LLSDXMLFormatter(), new LLSDXMLParser()); + doRoundTripTests("xml serialization"); } - template<> template<> - void TestLLSDBinaryParsingObject::test<9>() + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: binary round-trips", "[llsd_serialization]") { - std::vector vec; - vec.push_back('['); - vec.resize(vec.size() + 4); - memset(&vec[1], 0, 4); - vec.push_back(']'); - std::string str_good((char*)&vec[0], vec.size()); - LLSD val = LLSD::emptyArray(); - ensureParse( - "empty array", - str_good, - val, - 1); + setFormatterParser(new LLSDBinaryFormatter(), new LLSDBinaryParser()); + doRoundTripTests("binary serialization"); } - template<> template<> - void TestLLSDBinaryParsingObject::test<10>() + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: serialize(LLSD_BINARY) round-trips through deserialize", "[llsd_serialization]") { - std::vector vec; - vec.push_back('l'); - vec.resize(vec.size() + 4); - uint32_t size = htonl(14); // 1 too long - memcpy(&vec[1], &size, sizeof(uint32_t)); - vec.push_back('h'); vec.push_back('t'); vec.push_back('t'); - vec.push_back('p'); vec.push_back(':'); vec.push_back('/'); - vec.push_back('/'); vec.push_back('s'); vec.push_back('l'); - vec.push_back('.'); vec.push_back('c'); vec.push_back('o'); - vec.push_back('m'); - std::string str_bad((char*)&vec[0], vec.size()); - ensureParse( - "invalid uri length size", - str_bad, - LLSD(), - LLSDParser::PARSE_FAILURE); + mFormatter = [](const LLSD& sd, std::ostream& str) + { + LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_BINARY); + }; + setParser(LLSDSerialize::deserialize); + doRoundTripTests("serialize(LLSD_BINARY)"); + } - LLSD val; - val = LLURI("http://sl.com"); - size = htonl(13); // correct length - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_good((char*)&vec[0], vec.size()); - ensureParse( - "valid key size", - str_good, - val, - 1); + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: serialize(LLSD_XML) round-trips through deserialize", "[llsd_serialization]") + { + mFormatter = [](const LLSD& sd, std::ostream& str) + { + LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_XML); + }; + setParser(LLSDSerialize::deserialize); + doRoundTripTests("serialize(LLSD_XML)"); } - template<> template<> - void TestLLSDBinaryParsingObject::test<11>() + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: serialize(LLSD_NOTATION) round-trips through deserialize", "[llsd_serialization]") { - // hostile size fields must not be trusted - std::vector vec; - vec.push_back('['); - vec.resize(vec.size() + 4); - uint32_t size = htonl(0x7FFFFFFF); - memcpy(&vec[1], &size, sizeof(uint32_t)); - vec.push_back('i'); - auto integer_loc = vec.size(); - vec.resize(vec.size() + 4); - uint32_t val_int = htonl(23); - memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); - vec.push_back(']'); - std::string str_huge((char*)&vec[0], vec.size()); - ensureParse( - "array size lies huge", - str_huge, - LLSD(), - LLSDParser::PARSE_FAILURE); + mFormatter = [](const LLSD& sd, std::ostream& str) + { + LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_NOTATION); + }; + setParser(LLSDSerialize::deserialize); + // In this test, serialize(LLSD_NOTATION) emits a header recognized by + // deserialize(). + doRoundTripTests("serialize(LLSD_NOTATION)"); + } - // negative (unrepresentable) sizes used to sign-extend into a - // multi-gigabyte reserve() request - size = htonl(0xFFFFFFFF); - memcpy(&vec[1], &size, sizeof(uint32_t)); - std::string str_neg_array((char*)&vec[0], vec.size()); - ensureParse( - "negative array size", - str_neg_array, - LLSD(), - LLSDParser::PARSE_FAILURE); + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: LLSDNotationFormatter output without a header deserializes", "[llsd_serialization]") + { + setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE), + new LLSDNotationParser()); + setParser(LLSDSerialize::deserialize); + // This is an interesting test because LLSDNotationFormatter does not + // emit an llsd/notation header. + doRoundTripTests("LLSDNotationFormatter -> deserialize"); + } - vec[0] = '{'; - vec.back() = '}'; - std::string str_neg_map((char*)&vec[0], vec.size()); - ensureParse( - "negative map size", - str_neg_map, - LLSD(), - LLSDParser::PARSE_FAILURE); + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: LLSDXMLFormatter output without a header deserializes", "[llsd_serialization]") + { + setFormatterParser(new LLSDXMLFormatter(false, "", LLSDFormatter::OPTIONS_NONE), + new LLSDXMLParser()); + setParser(LLSDSerialize::deserialize); + // This is an interesting test because LLSDXMLFormatter does not + // emit an LLSD/XML header. + doRoundTripTests("LLSDXMLFormatter -> deserialize"); + } + + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: deserialize with SIZE_UNLIMITED reads every format", "[llsd_serialization]") + { + // deserialize() with SIZE_UNLIMITED must not turn the unlimited + // byte budget into a bogus negative one when a header line is + // present; sized payloads (binary strings) used to fail the + // resulting negative budget check. + LLSD sd; + sd["foo"] = "bar"; + sd["bin"] = LLSD::Binary{1, 2, 3}; + + for (auto fmt : {LLSDSerialize::LLSD_BINARY, + LLSDSerialize::LLSD_XML, + LLSDSerialize::LLSD_NOTATION}) + { + std::stringstream stream; + LLSDSerialize::serialize(sd, stream, fmt); + LLSD parsed; + CAPTURE(fmt); + REQUIRE(LLSDSerialize::deserialize(parsed, stream, LLSDSerialize::SIZE_UNLIMITED)); + REQUIRE_THAT(parsed, ll_test::LLSDEquals(sd)); + } + } + + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: strip_deprecated_header skips a header with or without a newline", "[llsd_serialization]") + { + // strip_deprecated_header() must consume the newline following the + // header (the binary parser does not tolerate leading whitespace) + // and report the actual number of bytes skipped. + LLSD sd; + sd["int"] = 17; + sd["str"] = "hello"; + + std::stringstream binstream; + LLSDSerialize::toBinary(sd, binstream); + const std::string body = binstream.str(); - std::vector bin; - bin.push_back('b'); - bin.resize(bin.size() + 4); - memcpy(&bin[1], &size, sizeof(uint32_t)); - std::string str_neg_bin((char*)&bin[0], bin.size()); - ensureParse( - "negative binary size", - str_neg_bin, - LLSD(), - LLSDParser::PARSE_FAILURE); + // header with trailing newline, as LLSDSerialize::serialize() writes it + { + std::string payload = "\n" + body; + std::vector buf(payload.begin(), payload.end()); + llssize cur_size = (llssize)buf.size(); + llssize header_size = 0; + char* p = strip_deprecated_header(buf.data(), cur_size, &header_size); + UNSCOPED_INFO("the header and its newline"); + CHECK(header_size == llssize(18)); + REQUIRE(cur_size == llssize(body.size())); + LLMemoryStream mstr((U8*)p, (S32)cur_size); + LLSD parsed; + CHECK(LLSDSerialize::fromBinary(parsed, mstr, cur_size) > 0); + CHECK_THAT(parsed, ll_test::LLSDEquals(sd)); + } + + // bare header with no newline + { + std::string payload = "" + body; + std::vector buf(payload.begin(), payload.end()); + llssize cur_size = (llssize)buf.size(); + llssize header_size = 0; + char* p = strip_deprecated_header(buf.data(), cur_size, &header_size); + UNSCOPED_INFO("the bare header"); + CHECK(header_size == llssize(17)); + REQUIRE(cur_size == llssize(body.size())); + LLMemoryStream mstr((U8*)p, (S32)cur_size); + LLSD parsed; + CHECK(LLSDSerialize::fromBinary(parsed, mstr, cur_size) > 0); + CHECK_THAT(parsed, ll_test::LLSDEquals(sd)); + } } - template<> template<> - void TestLLSDBinaryParsingObject::test<12>() + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: zip_llsd round-trips and unzip_llsd rejects corruption", "[llsd_serialization]") { - // truncated fixed-width payloads must fail, not parse as zero - ensureParse( - "truncated integer", - std::string("i\x00\x01", 3), - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "truncated real", - std::string("r\x3f\xf0\x00\x00", 5), - LLSD(), - LLSDParser::PARSE_FAILURE); - ensureParse( - "truncated uuid", - std::string("u\x01\x02\x03\x04\x05\x06\x07\x08", 9), - LLSD(), - LLSDParser::PARSE_FAILURE); + // zip_llsd/unzip_llsd round trip, and clean rejection of a + // corrupted compressed stream + LLSD sd; + sd["message"] = "destination unknown"; + sd["mode"] = 7; + LLSD arr = LLSD::emptyArray(); + for (S32 i = 0; i < 256; ++i) + { + arr.append(i * 0.25); + } + sd["samples"] = arr; + + std::string compressed = zip_llsd(sd); + REQUIRE_FALSE(compressed.empty()); + + LLSD out; + REQUIRE(LLUZipHelper::unzip_llsd(out, + (const U8*)compressed.data(), + (S32)compressed.size()) == + LLUZipHelper::ZR_OK); + CHECK_THAT(out, ll_test::LLSDEquals(sd)); + + std::string corrupt(compressed); + for (size_t i = corrupt.size() / 2; i < corrupt.size(); ++i) + { + corrupt[i] ^= 0x5A; + } + LLSD out2; + CHECK(LLUZipHelper::unzip_llsd(out2, + (const U8*)corrupt.data(), + (S32)corrupt.size()) != LLUZipHelper::ZR_OK); } - template<> template<> - void TestLLSDBinaryParsingObject::test<13>() + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: unzip_llsdNavMesh round-trips and rejects corruption", "[llsd_serialization]") { - // a map key must be marked 'k' or quoted; anything else used to be - // swallowed silently as an empty key, desynchronizing the stream - std::vector vec; - vec.push_back('{'); - vec.resize(vec.size() + 4); - uint32_t size = htonl(1); - memcpy(&vec[1], &size, sizeof(uint32_t)); - vec.push_back('x'); // bogus key marker - vec.push_back('i'); - auto integer_loc = vec.size(); - vec.resize(vec.size() + 4); - uint32_t val_int = htonl(23); - memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); - vec.push_back('}'); - std::string str_bad((char*)&vec[0], vec.size()); - ensureParse( - "bogus map key marker", - str_bad, - LLSD(), - LLSDParser::PARSE_FAILURE); + // unzip_llsdNavMesh: gzip round trip, and clean failure on a + // corrupted stream after partial output has been produced (this + // path used to realloc a freed buffer and double-free on error) + std::string payload; + payload.reserve(96 * 1024); + while (payload.size() < 96 * 1024) + { + payload += "navmesh payload block "; + } + + z_stream strm{}; + int ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + 15 + 16, // gzip wrapper + 8, Z_DEFAULT_STRATEGY); + CHECK(ret == Z_OK); + std::string compressed(deflateBound(&strm, (uLong)payload.size()) + 32, '\0'); + strm.next_in = (Bytef*)payload.data(); + strm.avail_in = (uInt)payload.size(); + strm.next_out = (Bytef*)&compressed[0]; + strm.avail_out = (uInt)compressed.size(); + ret = deflate(&strm, Z_FINISH); + CHECK(ret == Z_STREAM_END); + compressed.resize(compressed.size() - strm.avail_out); + deflateEnd(&strm); + + { + std::istringstream in(compressed); + bool valid = false; + size_t outsize = 0; + U8* out = unzip_llsdNavMesh(valid, outsize, in, (S32)compressed.size()); + CHECK(valid); + REQUIRE(out != NULL); + REQUIRE(outsize == payload.size()); + CHECK(payload.compare(0, payload.size(), + (const char*)out, outsize) == 0); + free(out); + } + + { + // corrupt the deflate tail and gzip trailer + std::string corrupt(compressed); + for (size_t i = corrupt.size() - 16; i < corrupt.size(); ++i) + { + corrupt[i] ^= 0x5A; + } + std::istringstream in(corrupt); + bool valid = true; + size_t outsize = 0; + U8* out = unzip_llsdNavMesh(valid, outsize, in, (S32)corrupt.size()); + CHECK_FALSE(valid); + CHECK(out == NULL); + } } /** @@ -2062,10 +1957,11 @@ namespace tut public: TestLLSDCrossCompatible() {} - void ensureBinaryAndNotation( - const std::string& msg, + void require_via_binary_and_notation( + const std::string& what, const LLSD& input) { + INFO(what); // to binary, and back again std::stringstream str1; S32 count1 = LLSDSerialize::toBinary(input, str1); @@ -2074,37 +1970,30 @@ namespace tut actual_value_bin, str1, LLSDSerialize::SIZE_UNLIMITED); - ensure_equals( - "ensureBinaryAndNotation binary count", - count2, + REQUIRE(count2 == count1); // to notation and back again std::stringstream str2; S32 count3 = LLSDSerialize::toNotation(actual_value_bin, str2); - ensure_equals( - "ensureBinaryAndNotation notation count1", - count3, + REQUIRE(count3 == count2); LLSD actual_value_notation; S32 count4 = LLSDSerialize::fromNotation( actual_value_notation, str2, LLSDSerialize::SIZE_UNLIMITED); - ensure_equals( - "ensureBinaryAndNotation notation count2", - count4, + REQUIRE(count4 == count3); - ensure_equals( - (msg + " (binaryandnotation)").c_str(), - actual_value_notation, - input); + REQUIRE_THAT(actual_value_notation, + ll_test::LLSDEquals(input)); } - void ensureBinaryAndXML( - const std::string& msg, + void require_via_binary_and_xml( + const std::string& what, const LLSD& input) { + INFO(what); // to binary, and back again std::stringstream str1; S32 count1 = LLSDSerialize::toBinary(input, str1); @@ -2113,105 +2002,88 @@ namespace tut actual_value_bin, str1, LLSDSerialize::SIZE_UNLIMITED); - ensure_equals( - "ensureBinaryAndXML binary count", - count2, + REQUIRE(count2 == count1); // to xml and back again std::stringstream str2; S32 count3 = LLSDSerialize::toXML(actual_value_bin, str2); - ensure_equals( - "ensureBinaryAndXML xml count1", - count3, + REQUIRE(count3 == count2); LLSD actual_value_xml; S32 count4 = LLSDSerialize::fromXML(actual_value_xml, str2); - ensure_equals( - "ensureBinaryAndXML xml count2", - count4, + REQUIRE(count4 == count3); - ensure_equals((msg + " (binaryandxml)").c_str(), actual_value_xml, input); + REQUIRE_THAT(actual_value_xml, ll_test::LLSDEquals(input)); } }; - typedef tut::test_group TestLLSDCompatibleGroup; - typedef TestLLSDCompatibleGroup::object TestLLSDCompatibleObject; - TestLLSDCompatibleGroup gTestLLSDCompatibleGroup( - "llsd serialize compatible"); - - template<> template<> - void TestLLSDCompatibleObject::test<1>() + TEST_CASE_METHOD(TestLLSDCrossCompatible, "llsd serialize compatible: scalars round-trip via binary into notation and XML", "[llsd_serialize_compatible]") { LLSD test; - ensureBinaryAndNotation("undef", test); - ensureBinaryAndXML("undef", test); + require_via_binary_and_notation("undef", test); + require_via_binary_and_xml("undef", test); test = true; - ensureBinaryAndNotation("boolean true", test); - ensureBinaryAndXML("boolean true", test); + require_via_binary_and_notation("boolean true", test); + require_via_binary_and_xml("boolean true", test); test = false; - ensureBinaryAndNotation("boolean false", test); - ensureBinaryAndXML("boolean false", test); + require_via_binary_and_notation("boolean false", test); + require_via_binary_and_xml("boolean false", test); test = 0; - ensureBinaryAndNotation("integer zero", test); - ensureBinaryAndXML("integer zero", test); + require_via_binary_and_notation("integer zero", test); + require_via_binary_and_xml("integer zero", test); test = 1; - ensureBinaryAndNotation("integer positive", test); - ensureBinaryAndXML("integer positive", test); + require_via_binary_and_notation("integer positive", test); + require_via_binary_and_xml("integer positive", test); test = -234567; - ensureBinaryAndNotation("integer negative", test); - ensureBinaryAndXML("integer negative", test); + require_via_binary_and_notation("integer negative", test); + require_via_binary_and_xml("integer negative", test); test = 0.0; - ensureBinaryAndNotation("real zero", test); - ensureBinaryAndXML("real zero", test); + require_via_binary_and_notation("real zero", test); + require_via_binary_and_xml("real zero", test); test = 1.0; - ensureBinaryAndNotation("real positive", test); - ensureBinaryAndXML("real positive", test); + require_via_binary_and_notation("real positive", test); + require_via_binary_and_xml("real positive", test); test = -1.0; - ensureBinaryAndNotation("real negative", test); - ensureBinaryAndXML("real negative", test); + require_via_binary_and_notation("real negative", test); + require_via_binary_and_xml("real negative", test); } - template<> template<> - void TestLLSDCompatibleObject::test<2>() + TEST_CASE_METHOD(TestLLSDCrossCompatible, "llsd serialize compatible: a string round-trips via binary into notation and XML", "[llsd_serialize_compatible]") { LLSD test; test = "foobar"; - ensureBinaryAndNotation("string", test); - ensureBinaryAndXML("string", test); + require_via_binary_and_notation("string", test); + require_via_binary_and_xml("string", test); } - template<> template<> - void TestLLSDCompatibleObject::test<3>() + TEST_CASE_METHOD(TestLLSDCrossCompatible, "llsd serialize compatible: a uuid round-trips via binary into notation and XML", "[llsd_serialize_compatible]") { LLSD test; LLUUID id; id.generate(); test = id; - ensureBinaryAndNotation("uuid", test); - ensureBinaryAndXML("uuid", test); + require_via_binary_and_notation("uuid", test); + require_via_binary_and_xml("uuid", test); } - template<> template<> - void TestLLSDCompatibleObject::test<4>() + TEST_CASE_METHOD(TestLLSDCrossCompatible, "llsd serialize compatible: a date round-trips via binary into notation and XML", "[llsd_serialize_compatible]") { LLSD test; test = LLDate(12345.0); - ensureBinaryAndNotation("date", test); - ensureBinaryAndXML("date", test); + require_via_binary_and_notation("date", test); + require_via_binary_and_xml("date", test); } - template<> template<> - void TestLLSDCompatibleObject::test<5>() + TEST_CASE_METHOD(TestLLSDCrossCompatible, "llsd serialize compatible: a uri round-trips via binary into notation and XML", "[llsd_serialize_compatible]") { LLSD test; test = LLURI("http://www.secondlife.com/"); - ensureBinaryAndNotation("uri", test); - ensureBinaryAndXML("uri", test); + require_via_binary_and_notation("uri", test); + require_via_binary_and_xml("uri", test); } - template<> template<> - void TestLLSDCompatibleObject::test<6>() + TEST_CASE_METHOD(TestLLSDCrossCompatible, "llsd serialize compatible: random binary round-trips via binary into notation and XML", "[llsd_serialize_compatible]") { LLSD test; typedef std::vector buf_t; @@ -2226,434 +2098,27 @@ namespace tut rand); } test = val; - ensureBinaryAndNotation("binary", test); - ensureBinaryAndXML("binary", test); + require_via_binary_and_notation("binary", test); + require_via_binary_and_xml("binary", test); } - template<> template<> - void TestLLSDCompatibleObject::test<7>() + TEST_CASE_METHOD(TestLLSDCrossCompatible, "llsd serialize compatible: an array round-trips via binary into notation and XML", "[llsd_serialize_compatible]") { LLSD test; test = LLSD::emptyArray(); test.append(1); test.append("hello"); - ensureBinaryAndNotation("array", test); - ensureBinaryAndXML("array", test); + require_via_binary_and_notation("array", test); + require_via_binary_and_xml("array", test); } - template<> template<> - void TestLLSDCompatibleObject::test<8>() + TEST_CASE_METHOD(TestLLSDCrossCompatible, "llsd serialize compatible: a map round-trips via binary into notation and XML", "[llsd_serialize_compatible]") { LLSD test; test = LLSD::emptyArray(); test["foo"] = "bar"; test["baz"] = 100; - ensureBinaryAndNotation("map", test); - ensureBinaryAndXML("map", test); - } - - // helper for TestPythonCompatible - static std::string import_llsd("import os.path\n" - "import sys\n" - "import llsd\n"); - - // helper for TestPythonCompatible - template - static void python_expect(const std::string& desc, const CONTENT& script, int expect=0, - ARGS&&... args) - { - auto PYTHON(LLStringUtil::getenv("PYTHON")); - ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); - - NamedTempFile scriptfile("py", script); - -#if LL_WINDOWS - std::string q("\""); - std::string qPYTHON(q + PYTHON + q); - std::string qscript(q + scriptfile.getPath().string() + q); - int rc = (int)_spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), - std::forward(args)..., NULL); - if (rc == -1) - { - char buffer[256]; - strerror_s(buffer, errno); // C++ can infer the buffer size! :-O - ensure(STRINGIZE("Couldn't run Python " << desc << "script: " << buffer), false); - } - else - { - ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), rc, expect); - } - -#else // LL_DARWIN, LL_LINUX - LLProcess::Params params; - params.executable = PYTHON; - params.args.add(scriptfile.getPath().string()); - for (const std::string& arg : StringVec{ std::forward(args)... }) - { - params.args.add(arg); - } - LLProcessPtr py(LLProcess::create(params)); - ensure(STRINGIZE("Couldn't launch " << desc << " script"), bool(py)); - // Implementing timeout would mean messing with alarm() and - // catching SIGALRM... later maybe... - int status(0); - if (waitpid(py->getProcessID(), &status, 0) == -1) - { - int waitpid_errno(errno); - ensure_equals(STRINGIZE("Couldn't retrieve rc from " << desc << " script: " - "waitpid() errno " << waitpid_errno), - waitpid_errno, ECHILD); - } - else - { - if (WIFEXITED(status)) - { - int rc(WEXITSTATUS(status)); - ensure_equals(STRINGIZE(desc << " script terminated with rc " << rc), - rc, expect); - } - else if (WIFSIGNALED(status)) - { - ensure(STRINGIZE(desc << " script terminated by signal " << WTERMSIG(status)), - false); - } - else - { - ensure(STRINGIZE(desc << " script produced impossible status " << status), - false); - } - } -#endif - } - - // helper for TestPythonCompatible - template - static void python(const std::string& desc, const CONTENT& script, ARGS&&... args) - { - // plain python() expects rc 0 - python_expect(desc, script, 0, std::forward(args)...); - } - - struct TestPythonCompatible - { - TestPythonCompatible() {} - ~TestPythonCompatible() {} - }; - - typedef tut::test_group TestPythonCompatibleGroup; - typedef TestPythonCompatibleGroup::object TestPythonCompatibleObject; - TestPythonCompatibleGroup pycompat("LLSD serialize Python compatibility"); - - template<> template<> - void TestPythonCompatibleObject::test<1>() - { - set_test_name("verify python()"); - python_expect("hello", - "import sys\n" - "sys.exit(17)\n", - 17); // expect nonzero rc - } - - template<> template<> - void TestPythonCompatibleObject::test<2>() - { - set_test_name("verify NamedTempFile"); - python("platform", - "import sys\n" - "print('Running on', sys.platform)\n"); - } - - // helper for test<3> - test<7> - static void writeLLSDArray(const FormatterFunction& serialize, - std::ostream& out, const LLSD& array) - { - for (const LLSD& item: llsd::inArray(array)) - { - // It's important to delimit the entries in this file somehow - // because, although Python's llsd.parse() can accept a file - // stream, the XML parser expects EOF after a single outer element - // -- it doesn't just stop. So we must extract a sequence of bytes - // strings from the file. But since one of the serialization - // formats we want to test is binary, we can't pick any single - // byte value as a delimiter! Use a binary integer length prefix - // instead. - std::ostringstream buffer; - serialize(item, buffer); - auto buffstr{ buffer.str() }; - int bufflen{ static_cast(buffstr.length()) }; - out.write(reinterpret_cast(&bufflen), sizeof(bufflen)); - LL_DEBUGS() << "Wrote length: " - << LL::hexdump(reinterpret_cast(&bufflen), - sizeof(bufflen)) - << LL_ENDL; - out.write(buffstr.c_str(), buffstr.length()); - LL_DEBUGS() << "Wrote data: " - << LL::hexmix(buffstr.c_str(), buffstr.length()) - << LL_ENDL; - } - } - - // helper for test<3> - test<7> - static void toPythonUsing(const std::string& desc, - const FormatterFunction& serialize) - { - LLSD cdata(llsd::array(17, 3.14, - "This string\n" - "has several\n" - "lines.")); - - const char pydata[] = - "def verify(iterable):\n" - " it = iter(iterable)\n" - " assert next(it) == 17\n" - " assert abs(next(it) - 3.14) < 0.01\n" - " assert next(it) == '''\\\n" - "This string\n" - "has several\n" - "lines.'''\n" - " try:\n" - " next(it)\n" - " except StopIteration:\n" - " pass\n" - " else:\n" - " raise AssertionError('Too many data items')\n"; - - // Create an llsdXXXXXX file containing 'data' serialized per - // FormatterFunction. - NamedTempFile file("llsd", - // NamedTempFile's function constructor - // takes a callable. To this callable it passes the - // std::ostream with which it's writing the - // NamedTempFile. - [serialize, cdata] - (std::ostream& out) - { writeLLSDArray(serialize, out, cdata); }); - - // 'debug' starts empty because it's intended as an output file - NamedTempFile debug("debug", ""); - - try - { - python("read C++ " + desc, - [&](std::ostream& out){ out << - import_llsd << - "from functools import partial\n" - "import io\n" - "import struct\n" - "lenformat = struct.Struct('i')\n" - "def parse_each(inf):\n" - " for rawlen in iter(partial(inf.read, lenformat.size), b''):\n" - " print('Read length:', ''.join(('%02x' % b) for b in rawlen),\n" - " file=debug)\n" - " len = lenformat.unpack(rawlen)[0]\n" - // Since llsd.parse() has no max_bytes argument, instead of - // passing the input stream directly to parse(), read the item - // into a distinct bytes object and parse that. - " data = inf.read(len)\n" - " print('Read data: ', repr(data), file=debug)\n" - " try:\n" - " frombytes = llsd.parse(data)\n" - " except llsd.LLSDParseError as err:\n" - " print(f'*** {err}')\n" - " print(f'Bad content:\\n{data!r}')\n" - " raise\n" - // Also try parsing from a distinct stream. - " stream = io.BytesIO(data)\n" - " fromstream = llsd.parse(stream)\n" - " assert frombytes == fromstream\n" - " yield frombytes\n" - << pydata << - // Don't forget raw-string syntax for Windows pathnames. - "debug = open(r'" << debug.getPath().string() << "', 'w')\n" - "verify(parse_each(open(r'" << file.getPath().string() << "', 'rb')))\n";}); - } - catch (const failure&) - { - LL_DEBUGS() << "Script debug output:" << LL_ENDL; - debug.peep_log(); - throw; - } - } - - template<> template<> - void TestPythonCompatibleObject::test<3>() - { - set_test_name("to Python using LLSDSerialize::serialize(LLSD_XML)"); - toPythonUsing("LLSD_XML", - [](const LLSD& sd, std::ostream& out) - { LLSDSerialize::serialize(sd, out, LLSDSerialize::LLSD_XML); }); - } - - template<> template<> - void TestPythonCompatibleObject::test<4>() - { - set_test_name("to Python using LLSDSerialize::serialize(LLSD_NOTATION)"); - toPythonUsing("LLSD_NOTATION", - [](const LLSD& sd, std::ostream& out) - { LLSDSerialize::serialize(sd, out, LLSDSerialize::LLSD_NOTATION); }); - } - - template<> template<> - void TestPythonCompatibleObject::test<5>() - { - set_test_name("to Python using LLSDSerialize::serialize(LLSD_BINARY)"); - toPythonUsing("LLSD_BINARY", - [](const LLSD& sd, std::ostream& out) - { LLSDSerialize::serialize(sd, out, LLSDSerialize::LLSD_BINARY); }); - } - - template<> template<> - void TestPythonCompatibleObject::test<6>() - { - set_test_name("to Python using LLSDSerialize::toXML()"); - toPythonUsing("toXML()", LLSDSerialize::toXML); - } - - template<> template<> - void TestPythonCompatibleObject::test<7>() - { - set_test_name("to Python using LLSDSerialize::toNotation()"); - toPythonUsing("toNotation()", LLSDSerialize::toNotation); - } - -/*==========================================================================*| - template<> template<> - void TestPythonCompatibleObject::test<8>() - { - set_test_name("to Python using LLSDSerialize::toBinary()"); - // We don't expect this to work because, without a header, - // llsd.parse() will assume notation rather than binary. - toPythonUsing("toBinary()", LLSDSerialize::toBinary); - } -|*==========================================================================*/ - - // helper for test<8> - test<12> - static bool itemFromStream(std::istream& istr, LLSD& item, const ParserFunction& parse) - { - // reset the output value for debugging clarity - item.clear(); - // We use an int length prefix as a foolproof delimiter even for - // binary serialized streams. - int length{ 0 }; - istr.read(reinterpret_cast(&length), sizeof(length)); -// return parse(istr, item, length); - // Sadly, as of 2022-12-01 it seems we can't really trust our LLSD - // parsers to honor max_bytes: this test works better when we read - // each item into its own distinct LLMemoryStream, instead of passing - // the original istr with a max_bytes constraint. - std::vector buffer(length); - istr.read(reinterpret_cast(buffer.data()), length); - LLMemoryStream stream(buffer.data(), length); - return parse(stream, item, length); - } - - // helper for test<8> - test<12> - static void fromPythonUsing(const std::string& pyformatter, - const ParserFunction& parse= - [](std::istream& istr, LLSD& data, llssize max_bytes) - { return LLSDSerialize::deserialize(data, istr, max_bytes); }) - { - // Create an empty data file. This is just a placeholder for our - // script to write into. Create it to establish a unique name that - // we know. - NamedTempFile file("llsd", ""); - - python("Python " + pyformatter, - [&](std::ostream& out){ out << - import_llsd << - "import struct\n" - "lenformat = struct.Struct('i')\n" - "DATA = [\n" - " 17,\n" - " 3.14,\n" - " '''\\\n" - "This string\n" - "has several\n" - "lines.''',\n" - "]\n" - // Don't forget raw-string syntax for Windows pathnames. - // N.B. Using 'print' implicitly adds newlines. - "with open(r'" << ll_convert(file.getPath().u8string()).c_str() << "', 'wb') as f:\n" - " for item in DATA:\n" - " serialized = llsd." << pyformatter << "(item)\n" - " f.write(lenformat.pack(len(serialized)))\n" - " f.write(serialized)\n";}); - - llifstream inf(file.getPath()); - LLSD item; - try - { - ensure("Failed to read LLSD::Integer from Python", - itemFromStream(inf, item, parse)); - ensure_equals(item.asInteger(), 17); - ensure("Failed to read LLSD::Real from Python", - itemFromStream(inf, item, parse)); - ensure_approximately_equals("Bad LLSD::Real value from Python", - item.asReal(), 3.14, 7); // 7 bits ~= 0.01 - ensure("Failed to read LLSD::String from Python", - itemFromStream(inf, item, parse)); - ensure_equals(item.asString(), - "This string\n" - "has several\n" - "lines."); - } - catch (const tut::failure& err) - { - std::cout << "for " << err.what() << ", item = " << item << std::endl; - throw; - } - } - - template<> template<> - void TestPythonCompatibleObject::test<8>() - { - set_test_name("from Python XML using LLSDSerialize::deserialize()"); - fromPythonUsing("format_xml"); - } - - template<> template<> - void TestPythonCompatibleObject::test<9>() - { - set_test_name("from Python notation using LLSDSerialize::deserialize()"); - fromPythonUsing("format_notation"); - } - - template<> template<> - void TestPythonCompatibleObject::test<10>() - { - set_test_name("from Python binary using LLSDSerialize::deserialize()"); - fromPythonUsing("format_binary"); - } - - template<> template<> - void TestPythonCompatibleObject::test<11>() - { - set_test_name("from Python XML using fromXML()"); - // fromXML()'s optional 3rd param isn't max_bytes, it's emit_errors - fromPythonUsing("format_xml", - [](std::istream& istr, LLSD& data, llssize) - { return LLSDSerialize::fromXML(data, istr) > 0; }); - } - - template<> template<> - void TestPythonCompatibleObject::test<12>() - { - set_test_name("from Python notation using fromNotation()"); - fromPythonUsing("format_notation", - [](std::istream& istr, LLSD& data, llssize max_bytes) - { return LLSDSerialize::fromNotation(data, istr, max_bytes) > 0; }); - } - -/*==========================================================================*| - template<> template<> - void TestPythonCompatibleObject::test<13>() - { - set_test_name("from Python binary using fromBinary()"); - // We don't expect this to work because format_binary() emits a - // header, but fromBinary() won't recognize a header. - fromPythonUsing("format_binary", - [](std::istream& istr, LLSD& data, llssize max_bytes) - { return LLSDSerialize::fromBinary(data, istr, max_bytes) > 0; }); + require_via_binary_and_notation("map", test); + require_via_binary_and_xml("map", test); } -|*==========================================================================*/ } diff --git a/indra/llcommon/tests/llsdutil_test.cpp b/indra/llcommon/tests/llsdutil_test.cpp index 1209bb3807..59dc08f688 100644 --- a/indra/llcommon/tests/llsdutil_test.cpp +++ b/indra/llcommon/tests/llsdutil_test.cpp @@ -27,7 +27,8 @@ */ #include "linden_common.h" -#include "lltut.h" +#include +#include #include "m4math.h" #include "v2math.h" #include "v2math.h" @@ -39,16 +40,15 @@ #include "llquaternion.h" #include "llsdutil.h" #include "llsdutil_math.h" -#include "stringize.h" #include #include -namespace tut +namespace { struct llsdutil_data { - void test_matches(const std::string& proto_key, const LLSD& possibles, - const char** begin, const char** end) + void require_matches_only(const std::string& proto_key, const LLSD& possibles, + const char** begin, const char** end) { std::set succeed(begin, end); LLSD prototype(possibles[proto_key]); @@ -57,12 +57,13 @@ namespace tut { std::string match(llsd_matches(prototype, pi->second)); std::set::const_iterator found = succeed.find(pi->first); + CAPTURE(proto_key, pi->first); if (found != succeed.end()) { // This test is supposed to succeed. Comparing to the // empty string ensures that if the test fails, it will // display the string received so we can tell what failed. - ensure_equals("match", match, ""); + REQUIRE(match == ""); } else { @@ -70,53 +71,44 @@ namespace tut // the string 'match' will be empty, which doesn't tell us // much about which case went awry. So construct a more // detailed description string. - ensure(proto_key + " shouldn't match " + pi->first, ! match.empty()); + REQUIRE_FALSE(match.empty()); } } } }; - typedef test_group llsdutil_test;; - typedef llsdutil_test::object llsdutil_object; - tut::llsdutil_test tut_llsdutil("llsdutil"); - template<> template<> - void llsdutil_object::test<1>() + TEST_CASE_METHOD(llsdutil_data, "llsdutil: ll_sd_from_U64 round-trips a U64", "[llsdutil]") { LLSD sd; U64 valueIn , valueOut; valueIn = U64L(0xFEDCBA9876543210); sd = ll_sd_from_U64(valueIn); valueOut = ll_U64_from_sd(sd); - ensure_equals("U64 valueIn->sd->valueOut", valueIn, valueOut); + CHECK(valueIn == valueOut); } - template<> template<> - void llsdutil_object::test<2>() + TEST_CASE_METHOD(llsdutil_data, "llsdutil: ll_sd_from_U32 round-trips a U32", "[llsdutil]") { LLSD sd; U32 valueIn, valueOut; valueIn = 0x87654321; sd = ll_sd_from_U32(valueIn); valueOut = ll_U32_from_sd(sd); - ensure_equals("U32 valueIn->sd->valueOut", valueIn, valueOut); + CHECK(valueIn == valueOut); } - template<> template<> - void llsdutil_object::test<3>() + TEST_CASE_METHOD(llsdutil_data, "llsdutil: ll_sd_from_ipaddr round-trips an address", "[llsdutil]") { U32 valueIn, valueOut; valueIn = 0x87654321; LLSD sd; sd = ll_sd_from_ipaddr(valueIn); valueOut = ll_ipaddr_from_sd(sd); - ensure_equals("valueIn->sd->valueOut", valueIn, valueOut); + CHECK(valueIn == valueOut); } - template<> template<> - void llsdutil_object::test<4>() + TEST_CASE_METHOD(llsdutil_data, "llsdutil: llsd_matches and llsd_equals across every LLSD type", "[llsdutil]") { - set_test_name("llsd_matches"); - // for this test, construct a map of all possible LLSD types LLSD map; map.insert("empty", LLSD()); @@ -144,7 +136,8 @@ namespace tut // empty prototype matches anything for (mi = map.beginMap(); mi != mend; ++mi) { - ensure_equals(std::string("empty matches ") + mi->first, llsd_matches(LLSD(), mi->second), ""); + CAPTURE(mi->first); + REQUIRE(llsd_matches(LLSD(), mi->second) == ""); } LLSD proto_array, data_array; @@ -157,27 +150,31 @@ namespace tut // prototype array matches only array for (mi = map.beginMap(); mi != mend; ++mi) { - ensure(std::string("array doesn't match ") + mi->first, - ! llsd_matches(proto_array, mi->second).empty()); + CAPTURE(mi->first); + REQUIRE_FALSE(llsd_matches(proto_array, mi->second).empty()); } // data array must be at least as long as prototype array proto_array.append(LLSD()); - ensure_equals("data array too short", llsd_matches(proto_array, data_array), + CHECK(llsd_matches(proto_array, data_array) == "Array size 4 required instead of Array size 3"); data_array.append(LLSD()); - ensure_equals("data array just right", llsd_matches(proto_array, data_array), ""); + UNSCOPED_INFO("data array just right"); + CHECK(llsd_matches(proto_array, data_array) == ""); data_array.append(LLSD()); - ensure_equals("data array longer", llsd_matches(proto_array, data_array), ""); + UNSCOPED_INFO("data array longer"); + CHECK(llsd_matches(proto_array, data_array) == ""); // array element matching data_array[0] = LLSD::String(); - ensure_equals("undefined prototype array entry", llsd_matches(proto_array, data_array), ""); + UNSCOPED_INFO("undefined prototype array entry"); + CHECK(llsd_matches(proto_array, data_array) == ""); proto_array[0] = LLSD::Binary(); - ensure_equals("scalar prototype array entry", llsd_matches(proto_array, data_array), + CHECK(llsd_matches(proto_array, data_array) == "[0]: Binary required instead of String"); data_array[0] = LLSD::Binary(); - ensure_equals("matching prototype array entry", llsd_matches(proto_array, data_array), ""); + UNSCOPED_INFO("matching prototype array entry"); + CHECK(llsd_matches(proto_array, data_array) == ""); // build a coupla maps LLSD proto_map, data_map; @@ -194,40 +191,44 @@ namespace tut // prototype map matches only map for (mi = map.beginMap(); mi != mend; ++mi) { - ensure(std::string("map doesn't match ") + mi->first, - ! llsd_matches(proto_map, mi->second).empty()); + CAPTURE(mi->first); + REQUIRE_FALSE(llsd_matches(proto_map, mi->second).empty()); } // data map must contain all keys in prototype map std::string error(llsd_matches(proto_map, data_map)); - ensure_contains("missing keys", error, "missing keys"); - ensure_contains("missing foo", error, "foo"); - ensure_contains("missing bar", error, "bar"); - ensure_does_not_contain("found found", error, "found"); - ensure_does_not_contain("got got", error, "got"); + CHECK_THAT(error, Catch::Matchers::ContainsSubstring("missing keys")); + CHECK_THAT(error, Catch::Matchers::ContainsSubstring("foo")); + CHECK_THAT(error, Catch::Matchers::ContainsSubstring("bar")); + CHECK_THAT(error, !Catch::Matchers::ContainsSubstring("found")); + CHECK_THAT(error, !Catch::Matchers::ContainsSubstring("got")); data_map["bar"] = LLSD(); error = llsd_matches(proto_map, data_map); - ensure_contains("missing foo", error, "foo"); - ensure_does_not_contain("got bar", error, "bar"); + CHECK_THAT(error, Catch::Matchers::ContainsSubstring("foo")); + CHECK_THAT(error, !Catch::Matchers::ContainsSubstring("bar")); data_map["foo"] = LLSD(); - ensure_equals("data map just right", llsd_matches(proto_map, data_map), ""); + UNSCOPED_INFO("data map just right"); + CHECK(llsd_matches(proto_map, data_map) == ""); data_map["extra"] = LLSD(); - ensure_equals("data map with extra", llsd_matches(proto_map, data_map), ""); + UNSCOPED_INFO("data map with extra"); + CHECK(llsd_matches(proto_map, data_map) == ""); // map element matching data_map["foo"] = LLSD::String(); - ensure_equals("undefined prototype map entry", llsd_matches(proto_map, data_map), ""); + UNSCOPED_INFO("undefined prototype map entry"); + CHECK(llsd_matches(proto_map, data_map) == ""); proto_map["foo"] = LLSD::Binary(); - ensure_equals("scalar prototype map entry", llsd_matches(proto_map, data_map), + CHECK(llsd_matches(proto_map, data_map) == "['foo']: Binary required instead of String"); data_map["foo"] = LLSD::Binary(); - ensure_equals("matching prototype map entry", llsd_matches(proto_map, data_map), ""); + UNSCOPED_INFO("matching prototype map entry"); + CHECK(llsd_matches(proto_map, data_map) == ""); // String { static const char* matches[] = { "String", "NumString", "Boolean", "Integer", "Real", "UUID", "Date", "URI" }; - test_matches("String", map, boost::begin(matches), boost::end(matches)); + require_matches_only("String", map, boost::begin(matches), boost::end(matches)); } // Boolean, Integer, Real @@ -236,31 +237,31 @@ namespace tut ni != nend; ++ni) { static const char* matches[] = { "Boolean", "Integer", "Real", "String", "NumString" }; - test_matches(*ni, map, boost::begin(matches), boost::end(matches)); + require_matches_only(*ni, map, boost::begin(matches), boost::end(matches)); } // UUID { static const char* matches[] = { "UUID", "String", "NumString" }; - test_matches("UUID", map, boost::begin(matches), boost::end(matches)); + require_matches_only("UUID", map, boost::begin(matches), boost::end(matches)); } // Date { static const char* matches[] = { "Date", "String", "NumString" }; - test_matches("Date", map, boost::begin(matches), boost::end(matches)); + require_matches_only("Date", map, boost::begin(matches), boost::end(matches)); } // URI { static const char* matches[] = { "URI", "String", "NumString" }; - test_matches("URI", map, boost::begin(matches), boost::end(matches)); + require_matches_only("URI", map, boost::begin(matches), boost::end(matches)); } // Binary { static const char* matches[] = { "Binary" }; - test_matches("Binary", map, boost::begin(matches), boost::end(matches)); + require_matches_only("Binary", map, boost::begin(matches), boost::end(matches)); } /*-------------------------- llsd_equals ---------------------------*/ @@ -276,8 +277,8 @@ namespace tut // interest, e.g "String::Integer". // We expect the values (xmi->second) to be equal if and only // if the type names (xmi->first) are equal. - ensure(STRINGIZE(lmi->first << "::" << rmi->first), - bool(lmi->first == rmi->first) == + CAPTURE(lmi->first, rmi->first); + REQUIRE(bool(lmi->first == rmi->first) == bool(llsd_equals(lmi->second, rmi->second))); } } @@ -288,15 +289,19 @@ namespace tut rarray.append(2); rarray.append("3"); LLSD larray(rarray); - ensure("llsd_equals(equal arrays)", llsd_equals(larray, rarray)); + UNSCOPED_INFO("equal arrays"); + CHECK(llsd_equals(larray, rarray)); rarray[2] = "4"; - ensure("llsd_equals(different [2])", ! llsd_equals(larray, rarray)); + UNSCOPED_INFO("different [2]"); + CHECK_FALSE(llsd_equals(larray, rarray)); rarray = larray; rarray.append(LLSD::Date()); - ensure("llsd_equals(longer right array)", ! llsd_equals(larray, rarray)); + UNSCOPED_INFO("longer right array"); + CHECK_FALSE(llsd_equals(larray, rarray)); rarray = larray; rarray.erase(2); - ensure("llsd_equals(shorter right array)", ! llsd_equals(larray, rarray)); + UNSCOPED_INFO("shorter right array"); + CHECK_FALSE(llsd_equals(larray, rarray)); // Map cases LLSD rmap; @@ -304,57 +309,66 @@ namespace tut rmap["Phoenix"] = 92; rmap["Boston"] = 77; LLSD lmap(rmap); - ensure("llsd_equals(equal maps)", llsd_equals(lmap, rmap)); + UNSCOPED_INFO("equal maps"); + CHECK(llsd_equals(lmap, rmap)); rmap["Boston"] = 80; - ensure("llsd_equals(different [\"Boston\"])", ! llsd_equals(lmap, rmap)); + UNSCOPED_INFO("different Boston"); + CHECK_FALSE(llsd_equals(lmap, rmap)); rmap = lmap; rmap["Atlanta"] = 95; - ensure("llsd_equals(superset right map)", ! llsd_equals(lmap, rmap)); + UNSCOPED_INFO("superset right map"); + CHECK_FALSE(llsd_equals(lmap, rmap)); rmap = lmap; lmap["Seattle"] = 72; - ensure("llsd_equals(superset left map)", ! llsd_equals(lmap, rmap)); + UNSCOPED_INFO("superset left map"); + CHECK_FALSE(llsd_equals(lmap, rmap)); } - template<> template<> - void llsdutil_object::test<5>() + TEST_CASE_METHOD(llsdutil_data, "llsdutil: hashing LLSD follows its type and contents", "[llsdutil]") { - set_test_name("llsd_hashing"); - { LLSD data_s1 = LLSD::String("The quick brown aardvark jumped over the lazy lemming."); LLSD data_s2 = LLSD::String("The quick brown aardvark jumped over the lazy lemming."); - ensure("hash: Identical string hashes match.", boost::hash{}(data_s1) == boost::hash{}(data_s2)); + UNSCOPED_INFO("identical strings"); + CHECK(boost::hash{}(data_s1) == boost::hash{}(data_s2)); } { LLSD data_r1 = LLSD::Real(3.0f); LLSD data_i1 = LLSD::Integer(3); - ensure("hash: equivalent values but different types do not match.", boost::hash{}(data_r1) != boost::hash{}(data_i1)); + UNSCOPED_INFO("equal values of different types"); + CHECK(boost::hash{}(data_r1) != boost::hash{}(data_i1)); } { LLSD data_a1 = llsd::array("A", "B", "C"); LLSD data_a2 = llsd::array("A", "B", "C"); - ensure("hash: identical arrays produce identical results", boost::hash{}(data_a1) == boost::hash{}(data_a2)); + UNSCOPED_INFO("identical arrays"); + CHECK(boost::hash{}(data_a1) == boost::hash{}(data_a2)); data_a2.append(llsd::array(1, 2)); - ensure("hash: changing the array changes the hash.", boost::hash{}(data_a1) != boost::hash{}(data_a2)); + UNSCOPED_INFO("one array appended to"); + CHECK(boost::hash{}(data_a1) != boost::hash{}(data_a2)); data_a1.append(llsd::array(1, 2)); - ensure("hash: identical arrays produce identical results with nested arrays", boost::hash{}(data_a1) == boost::hash{}(data_a2)); + UNSCOPED_INFO("identical arrays with nested arrays"); + CHECK(boost::hash{}(data_a1) == boost::hash{}(data_a2)); } { LLSD data_m1 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); LLSD data_m2 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); - ensure("hash: identical maps produce identical results", boost::hash{}(data_m1) == boost::hash{}(data_m2)); + UNSCOPED_INFO("identical maps"); + CHECK(boost::hash{}(data_m1) == boost::hash{}(data_m2)); LLSD data_m3 = LLSDMap("key1", LLSD::Real(5.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); - ensure("hash: Different values in the map produce different hashes.", boost::hash{}(data_m1) != boost::hash{}(data_m3)); + UNSCOPED_INFO("maps with a different value"); + CHECK(boost::hash{}(data_m1) != boost::hash{}(data_m3)); LLSD data_m4 = LLSDMap("keyA", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); - ensure("hash: Different keys in the map produce different hashes.", boost::hash{}(data_m1) != boost::hash{}(data_m4)); + UNSCOPED_INFO("maps with a different key"); + CHECK(boost::hash{}(data_m1) != boost::hash{}(data_m4)); } } diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 51f2b92f12..1e95e1f18b 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -28,7 +28,8 @@ #include "linden_common.h" #include "llsingleton.h" -#include "../test/lltut.h" +#include +#include #include "wrapllerrs.h" #include "llsd.h" @@ -93,7 +94,7 @@ DEFINE_MEMBERS(B, A) } // anonymous namespace -namespace tut +namespace { struct singleton { @@ -104,98 +105,88 @@ namespace tut }; }; - typedef test_group singleton_t; - typedef singleton_t::object singleton_object_t; - tut::singleton_t tut_llsingleton("LLSingleton"); - - template<> template<> - void singleton_object_t::test<1>() + TEST_CASE_METHOD(singleton, "LLSingleton: an empty case that checks nothing", "[LLSingleton]") { } - template<> template<> - void singleton_object_t::test<2>() + TEST_CASE_METHOD(singleton, "LLSingleton: getInstance() makes an instance", "[LLSingleton]") { LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); - ensure(singleton_test); + CHECK(singleton_test); } - template<> template<> - void singleton_object_t::test<3>() + TEST_CASE_METHOD(singleton, "LLSingleton: deleteSingleton() removes the instance and getInstance() remakes it", "[LLSingleton]") { //Construct the instance LLSingletonTest::getInstance(); - ensure(LLSingletonTest::instanceExists()); + CHECK(LLSingletonTest::instanceExists()); //Delete the instance LLSingletonTest::deleteSingleton(); - ensure(!LLSingletonTest::instanceExists()); + CHECK_FALSE(LLSingletonTest::instanceExists()); //Construct it again. LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); - ensure(singleton_test); - ensure(LLSingletonTest::instanceExists()); + CHECK(singleton_test); + CHECK(LLSingletonTest::instanceExists()); } -#define TESTS(CLS, OTHER, N0, N1, N2, N3) \ - template<> template<> \ - void singleton_object_t::test() \ - { \ - set_test_name("just " #CLS); \ - CLS::sDepFlag = CLS::DEP_NONE; \ - OTHER::sDepFlag = OTHER::DEP_NONE; \ - sLog.clear(); \ - \ - (void)CLS::instance(); \ - ensure_equals(sLog, #CLS "i" #CLS); \ - LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS "i" #CLS "x" #CLS "~" #CLS); \ - } \ - \ - template<> template<> \ - void singleton_object_t::test() \ - { \ - set_test_name(#CLS " ctor depends " #OTHER); \ - CLS::sDepFlag = CLS::DEP_CTOR; \ - OTHER::sDepFlag = OTHER::DEP_NONE; \ - sLog.clear(); \ - \ - (void)CLS::instance(); \ - ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS); \ - LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ - } \ - \ - template<> template<> \ - void singleton_object_t::test() \ - { \ - set_test_name(#CLS " init depends " #OTHER); \ - CLS::sDepFlag = CLS::DEP_INIT; \ - OTHER::sDepFlag = OTHER::DEP_NONE; \ - sLog.clear(); \ - \ - (void)CLS::instance(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ - LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ - } \ - \ - template<> template<> \ - void singleton_object_t::test() \ - { \ - set_test_name(#CLS " circular init"); \ - CLS::sDepFlag = CLS::DEP_INIT; \ - OTHER::sDepFlag = OTHER::DEP_CTOR; \ - sLog.clear(); \ - \ - (void)CLS::instance(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ - LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ + // Makes CLS, CLS and OTHER depending on each other as the flags say, and + // checks the order they were made in, then deletes every singleton and + // checks the order they were cleaned up and destroyed in. + template + static void require_order(typename CLS::dep_flag cls_dep, typename OTHER::dep_flag other_dep, + const std::string& made, const std::string& deleted) + { + CLS::sDepFlag = cls_dep; + OTHER::sDepFlag = other_dep; + sLog.clear(); + + (void)CLS::instance(); + REQUIRE(sLog == made); + LLSingletonBase::deleteAll(); + REQUIRE(sLog == made + deleted); + } + + TEST_CASE_METHOD(singleton, "LLSingleton: A alone is made and deleted in order", "[LLSingleton]") + { + require_order(A::DEP_NONE, B::DEP_NONE, "AiA", "xA~A"); } - TESTS(A, B, 4, 5, 6, 7) - TESTS(B, A, 8, 9, 10, 11) + TEST_CASE_METHOD(singleton, "LLSingleton: A needing B in its constructor is deleted before B", "[LLSingleton]") + { + require_order(A::DEP_CTOR, B::DEP_NONE, "ABiBiA", "xA~AxB~B"); + } + + TEST_CASE_METHOD(singleton, "LLSingleton: A needing B in initSingleton is deleted before B", "[LLSingleton]") + { + require_order(A::DEP_INIT, B::DEP_NONE, "AiABiB", "xA~AxB~B"); + } + + TEST_CASE_METHOD(singleton, "LLSingleton: A needing B in initSingleton and B needing A is deleted before B", "[LLSingleton]") + { + require_order(A::DEP_INIT, B::DEP_CTOR, "AiABiB", "xA~AxB~B"); + } + + TEST_CASE_METHOD(singleton, "LLSingleton: B alone is made and deleted in order", "[LLSingleton]") + { + require_order(B::DEP_NONE, A::DEP_NONE, "BiB", "xB~B"); + } + + TEST_CASE_METHOD(singleton, "LLSingleton: B needing A in its constructor is deleted before A", "[LLSingleton]") + { + require_order(B::DEP_CTOR, A::DEP_NONE, "BAiAiB", "xB~BxA~A"); + } + + TEST_CASE_METHOD(singleton, "LLSingleton: B needing A in initSingleton is deleted before A", "[LLSingleton]") + { + require_order(B::DEP_INIT, A::DEP_NONE, "BiBAiA", "xB~BxA~A"); + } + + TEST_CASE_METHOD(singleton, "LLSingleton: B needing A in initSingleton and A needing B is deleted before A", "[LLSingleton]") + { + require_order(B::DEP_INIT, A::DEP_CTOR, "BiBAiA", "xB~BxA~A"); + } #define PARAMSINGLETON(cls) \ class cls: public LLParamSingleton \ @@ -214,125 +205,113 @@ namespace tut // validly initialize each using two different constructors. If we tried // to test that with a single LLParamSingleton class within the same test // program, we'd get 'trying to use deleted LLParamSingleton' errors. - namespace - { - PARAMSINGLETON(PSing1); - PARAMSINGLETON(PSing2); - } // anonymous namespace + PARAMSINGLETON(PSing1); + PARAMSINGLETON(PSing2); - template<> template<> - void singleton_object_t::test<12>() + TEST_CASE_METHOD(singleton, "LLSingleton: LLParamSingleton must be initialized once and not used after deletion", "[LLSingleton]") { - set_test_name("LLParamSingleton"); - WrapLLErrs catcherr; // query methods - ensure("false positive on instanceExists()", ! PSing1::instanceExists()); - ensure("false positive on wasDeleted()", ! PSing1::wasDeleted()); + CHECK_FALSE(PSing1::instanceExists()); + CHECK_FALSE(PSing1::wasDeleted()); // try to reference before initializing std::string threw = catcherr.catch_llerrs([](){ (void)PSing1::instance(); }); - ensure_contains("too-early instance() didn't throw", threw, "Uninitialized"); + UNSCOPED_INFO("too-early instance() didn't throw"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("Uninitialized")); // getInstance() behaves the same as instance() threw = catcherr.catch_llerrs([](){ (void)PSing1::getInstance(); }); - ensure_contains("too-early getInstance() didn't throw", threw, "Uninitialized"); + UNSCOPED_INFO("too-early getInstance() didn't throw"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("Uninitialized")); // initialize using LLSD::String constructor PSing1::initParamSingleton("string"); - ensure_equals(PSing1::instance().desc(), "string"); - ensure("false negative on instanceExists()", PSing1::instanceExists()); + CHECK(PSing1::instance().desc() == "string"); + CHECK(PSing1::instanceExists()); // try to initialize again threw = catcherr.catch_llerrs([](){ PSing1::initParamSingleton("again"); }); - ensure_contains("second ctor(string) didn't throw", threw, "twice"); + UNSCOPED_INFO("second ctor(string) didn't throw"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("twice")); // try to initialize using the other constructor -- should be // well-formed, but illegal at runtime threw = catcherr.catch_llerrs([](){ PSing1::initParamSingleton(17); }); - ensure_contains("other ctor(int) didn't throw", threw, "twice"); + UNSCOPED_INFO("other ctor(int) didn't throw"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("twice")); PSing1::deleteSingleton(); - ensure("false negative on wasDeleted()", PSing1::wasDeleted()); + CHECK(PSing1::wasDeleted()); threw = catcherr.catch_llerrs([](){ (void)PSing1::instance(); }); - ensure_contains("accessed deleted LLParamSingleton", threw, "deleted"); + UNSCOPED_INFO("accessed deleted LLParamSingleton"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("deleted")); } - template<> template<> - void singleton_object_t::test<13>() + TEST_CASE_METHOD(singleton, "LLSingleton: LLParamSingleton by its alternate constructor initializes only once", "[LLSingleton]") { - set_test_name("LLParamSingleton alternate ctor"); - WrapLLErrs catcherr; // We don't have to restate all the tests for PSing1. Only test validly // using the other constructor. PSing2::initParamSingleton(17); - ensure_equals(PSing2::instance().desc(), "17"); + CHECK(PSing2::instance().desc() == "17"); // can't do it twice std::string threw = catcherr.catch_llerrs([](){ PSing2::initParamSingleton(34); }); - ensure_contains("second ctor(int) didn't throw", threw, "twice"); + UNSCOPED_INFO("second ctor(int) didn't throw"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("twice")); // can't use the other constructor either threw = catcherr.catch_llerrs([](){ PSing2::initParamSingleton("string"); }); - ensure_contains("other ctor(string) didn't throw", threw, "twice"); + UNSCOPED_INFO("other ctor(string) didn't throw"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("twice")); } - namespace + class CircularPCtor: public LLParamSingleton { - class CircularPCtor: public LLParamSingleton + LLSINGLETON(CircularPCtor) { - LLSINGLETON(CircularPCtor) - { - // never mind indirection, just go straight for the circularity - (void)instance(); - } - }; - } // anonymous namespace + // never mind indirection, just go straight for the circularity + (void)instance(); + } + }; - template<> template<> - void singleton_object_t::test<14>() + TEST_CASE_METHOD(singleton, "LLSingleton: a circular LLParamSingleton constructor errs", "[LLSingleton]") { - set_test_name("Circular LLParamSingleton constructor"); WrapLLErrs catcherr; std::string threw = catcherr.catch_llerrs([](){ CircularPCtor::initParamSingleton(); }); - ensure_contains("constructor circularity didn't throw", threw, "constructor"); + CHECK_THAT(threw, Catch::Matchers::ContainsSubstring("constructor")); } - namespace + class CircularPInit: public LLParamSingleton { - class CircularPInit: public LLParamSingleton + LLSINGLETON_EMPTY_CTOR(CircularPInit); + public: + virtual void initSingleton() override { - LLSINGLETON_EMPTY_CTOR(CircularPInit); - public: - virtual void initSingleton() override + // never mind indirection, just go straight for the circularity + CircularPInit *pt = getInstance(); + if (!pt) { - // never mind indirection, just go straight for the circularity - CircularPInit *pt = getInstance(); - if (!pt) - { - throw; - } + throw; } - }; - } // anonymous namespace + } + }; - template<> template<> - void singleton_object_t::test<15>() + TEST_CASE_METHOD(singleton, "LLSingleton: a circular LLParamSingleton initSingleton() is allowed", "[LLSingleton]") { - set_test_name("Circular LLParamSingleton initSingleton()"); WrapLLErrs catcherr; std::string threw = catcherr.catch_llerrs([](){ CircularPInit::initParamSingleton(); }); - ensure("initSingleton() circularity threw", threw.empty()); + CHECK(threw.empty()); } } diff --git a/indra/llcommon/tests/llstreamtools_test.cpp b/indra/llcommon/tests/llstreamtools_test.cpp index 970afb3b02..76d6cb06f6 100644 --- a/indra/llcommon/tests/llstreamtools_test.cpp +++ b/indra/llcommon/tests/llstreamtools_test.cpp @@ -26,25 +26,16 @@ * $/LicenseInfo$ */ -#include +#include +#include "altestmatchers.h" #include "linden_common.h" #include "llstreamtools.h" -#include "lltut.h" -namespace tut +namespace { - struct streamtools_data - { - }; - typedef test_group streamtools_test; - typedef streamtools_test::object streamtools_object; - tut::streamtools_test streamtools_testcase("streamtools"); - - //test cases for skip_whitespace() - template<> template<> - void streamtools_object::test<1>() + TEST_CASE("streamtools: skip_whitespace skips spaces and tabs but not line ends", "[streamtools]") { char arr[255]; std::string str; @@ -53,40 +44,42 @@ namespace tut std::istringstream is; is.str(str = ""); - ensure("skip_whitespace: empty string", (false == skip_whitespace(is))); + UNSCOPED_INFO("empty string"); + CHECK_FALSE(skip_whitespace(is)); is.clear(); is.str(str = " SecondLife is a 3D World"); skip_whitespace(is); is.get(arr, 255, '\0'); expected_result = "SecondLife is a 3D World"; - ensure_equals("skip_whitespace: space", arr, expected_result); + UNSCOPED_INFO("a leading space"); + CHECK(arr == expected_result); is.clear(); is.str(str = "\t \tSecondLife is a 3D World"); skip_whitespace(is); is.get(arr, 255, '\0'); expected_result = "SecondLife is a 3D World"; - ensure_equals("skip_whitespace: space and tabs", arr, expected_result); + UNSCOPED_INFO("spaces and tabs"); + CHECK(arr == expected_result); is.clear(); is.str(str = "\t \tSecondLife is a 3D World "); skip_whitespace(is); is.get(arr, 255, '\0'); expected_result = "SecondLife is a 3D World "; - ensure_equals("skip_whitespace: space at end", arr, expected_result); + UNSCOPED_INFO("trailing spaces are kept"); + CHECK(arr == expected_result); is.clear(); is.str(str = "\t \r\nSecondLife is a 3D World"); skip_whitespace(is); is.get(arr, 255, '\0'); expected_result = "\r\nSecondLife is a 3D World"; - ensure_equals("skip_whitespace: space at end", arr, expected_result); + CHECK(arr == expected_result); } - //testcases for skip_emptyspaces() - template<> template<> - void streamtools_object::test<2>() + TEST_CASE("streamtools: skip_emptyspace skips whitespace and line ends", "[streamtools]") { char arr[255]; std::string str; @@ -100,31 +93,33 @@ namespace tut skip_emptyspace(is); is.get(arr, 255, '\0'); expected_result = "SecondLife is a 3D World.\n"; - ensure_equals("skip_emptyspace: space and tabs", arr, expected_result); + UNSCOPED_INFO("spaces and tabs"); + CHECK(arr == expected_result); is.clear(); is.str(str = " \t\r\n \r SecondLife is a 3D World.\n"); skip_emptyspace(is); is.get(arr, 255, '\0'); expected_result = "SecondLife is a 3D World.\n"; - ensure_equals("skip_emptyspace: space, tabs, carriage return, newline", arr, expected_result); + UNSCOPED_INFO("spaces, tabs, carriage returns and newlines"); + CHECK(arr == expected_result); is.clear(); is.str(str = ""); ret = skip_emptyspace(is); is.get(arr, 255, '\0'); - ensure("skip_emptyspace: empty string", ret == false); + UNSCOPED_INFO("empty string"); + CHECK_FALSE(ret); is.clear(); is.str(str = " \r\n \t "); ret = skip_emptyspace(is); is.get(arr, 255, '\0'); - ensure("skip_emptyspace: space newline empty", ret == false); + UNSCOPED_INFO("only empty space"); + CHECK_FALSE(ret); } - //testcases for skip_comments_and_emptyspace() - template<> template<> - void streamtools_object::test<3>() + TEST_CASE("streamtools: skip_comments_and_emptyspace skips comment lines and empty space", "[streamtools]") { char arr[255]; std::string str; @@ -138,19 +133,21 @@ namespace tut skip_comments_and_emptyspace(is); is.get(arr, 255, '\0'); expected_result = "SecondLife is a 3D World.\n"; - ensure_equals("skip_comments_and_emptyspace: space, tabs, carriage return, newline", arr, expected_result); + UNSCOPED_INFO("spaces, tabs, carriage returns and newlines"); + CHECK(arr == expected_result); is.clear(); is.str(str = "# \r\n SecondLife is a 3D World."); skip_comments_and_emptyspace(is); is.get(arr, 255, '\0'); expected_result = "SecondLife is a 3D World."; - ensure_equals("skip_comments_and_emptyspace: skip comment - 1", arr, expected_result); + UNSCOPED_INFO("a comment line"); + CHECK(arr == expected_result); is.clear(); is.str(str = "# \r\n # SecondLife is a 3D World. ##"); - ensure("should not be good()", ! skip_comments_and_emptyspace(is)); - ensure("should be at eof()", is.eof()); + CHECK_FALSE(skip_comments_and_emptyspace(is)); + CHECK(is.eof()); // don't get(): given bad state, we can't rely on results is.clear(); @@ -158,22 +155,22 @@ namespace tut skip_comments_and_emptyspace(is); is.get(arr, 255, '\0'); expected_result = "SecondLife is a 3D World. ##"; - ensure_equals("skip_comments_and_emptyspace: skip comment - 3", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(str = ""); ret = skip_comments_and_emptyspace(is); - ensure("skip_comments_and_emptyspace: empty string", ! ret); + UNSCOPED_INFO("empty string"); + CHECK_FALSE(ret); is.clear(); is.str(str = " \r\n \t # SecondLife is a 3D World"); ret = skip_comments_and_emptyspace(is); - ensure("skip_comments_and_emptyspace: space newline comment empty", ! ret); + UNSCOPED_INFO("only empty space and a comment"); + CHECK_FALSE(ret); } - //testcases for skip_line() - template<> template<> - void streamtools_object::test<4>() + TEST_CASE("streamtools: skip_line skips past the next newline", "[streamtools]") { char arr[255]; std::string str; @@ -187,32 +184,31 @@ namespace tut skip_line(is); is.get(arr, 255, '\0'); expected_result = "\n It provides an opportunity to the site \nuser to perform real life activities in virtual world."; - ensure_equals("skip_line: 1 newline", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(expected_result); skip_line(is); is.get(arr, 255, '\0'); expected_result = " It provides an opportunity to the site \nuser to perform real life activities in virtual world."; - ensure_equals("skip_line: 2 newline", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(expected_result); skip_line(is); is.get(arr, 255, '\0'); expected_result = "user to perform real life activities in virtual world."; - ensure_equals("skip_line: 3 newline", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(str = ""); ret = skip_line(is); - ensure("skip_line: empty string", ret == false); + UNSCOPED_INFO("empty string"); + CHECK_FALSE(ret); } - // testcases for skip_to_next_word() - template<> template<> - void streamtools_object::test<5>() + TEST_CASE("streamtools: skip_to_next_word skips the word and the delimiters after it", "[streamtools]") { char arr[255]; std::string str; @@ -226,7 +222,7 @@ namespace tut skip_to_next_word(is); // get past SecondLife is.get(arr, 255, '\0'); expected_result = "is a 3D_World.\n\n It-provides an opportunity to the site \nuser to perform real life activities in virtual world."; - ensure_equals("skip_to_next_word: 1", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(expected_result); @@ -235,30 +231,32 @@ namespace tut skip_to_next_word(is); // get past 3D_World.\n\n is.get(arr, 255, '\0'); expected_result = "It-provides an opportunity to the site \nuser to perform real life activities in virtual world."; - ensure_equals("skip_to_next_word: get past .\n\n 2", arr, expected_result); + UNSCOPED_INFO("past a full stop and newlines"); + CHECK(arr == expected_result); is.clear(); is.str(expected_result); skip_to_next_word(is); // get past It- expected_result = "provides an opportunity to the site \nuser to perform real life activities in virtual world."; is.get(arr, 255, '\0'); - ensure_equals("skip_to_next_word: get past -", arr, expected_result); + UNSCOPED_INFO("past a hyphen"); + CHECK(arr == expected_result); is.clear(); is.str(str = ""); ret = skip_to_next_word(is); - ensure("skip_line: empty string", ret == false); + UNSCOPED_INFO("empty string"); + CHECK_FALSE(ret); is.clear(); is.str(str = " \r\n\r\n"); ret = skip_to_next_word(is); - ensure("skip_line: space new lines", ret == false); + UNSCOPED_INFO("only spaces and newlines"); + CHECK_FALSE(ret); } - //testcases for skip_to_end_of_next_keyword() - template<> template<> - void streamtools_object::test<6>() + TEST_CASE("streamtools: skip_to_end_of_next_keyword finds a keyword that starts a line", "[streamtools]") { char arr[255]; std::string str; @@ -272,55 +270,57 @@ namespace tut ret = skip_to_end_of_next_keyword("FIRSTKEY", is); is.get(arr, 255, '\0'); expected_result = " followed by second delimiter\nSECONDKEY\t SecondValue followed by third delimiter \nSECONDKEY\nFOURTHKEY FOURTHVALUEis a 3DWorld."; - ensure_equals("skip_to_end_of_next_keyword: 1", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(expected_result); ret = skip_to_end_of_next_keyword("SECONDKEY", is); is.get(arr, 255, '\0'); expected_result = "\t SecondValue followed by third delimiter \nSECONDKEY\nFOURTHKEY FOURTHVALUEis a 3DWorld."; - ensure_equals("skip_to_end_of_next_keyword: 2", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(expected_result); ret = skip_to_end_of_next_keyword("SECONDKEY", is); is.get(arr, 255, '\0'); expected_result = "\nFOURTHKEY FOURTHVALUEis a 3DWorld."; - ensure_equals("skip_to_end_of_next_keyword: 3", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(expected_result); ret = skip_to_end_of_next_keyword("FOURTHKEY", is); is.get(arr, 255, '\0'); expected_result = " FOURTHVALUEis a 3DWorld."; - ensure_equals("skip_to_end_of_next_keyword: 4", arr, expected_result); + CHECK(arr == expected_result); is.clear(); is.str(str = "{should be skipped as newline/space/tab does not follow but this one should be picked\n { Does it?\n"); ret = skip_to_end_of_next_keyword("{", is); is.get(arr, 255, '\0'); expected_result = " Does it?\n"; - ensure_equals("skip_to_end_of_next_keyword: multiple delim matches on same line", arr, expected_result); + UNSCOPED_INFO("several matches on the same line"); + CHECK(arr == expected_result); is.clear(); is.str(str = "Delim { could not be found at start"); ret = skip_to_end_of_next_keyword("{", is); - ensure("skip_to_end_of_next_keyword: delim should not be present", ret == false); + UNSCOPED_INFO("keyword not at the start of a line"); + CHECK_FALSE(ret); is.clear(); is.str(str = "Empty Delim"); ret = skip_to_end_of_next_keyword("", is); - ensure("skip_to_end_of_next_keyword: empty delim should not be valid", ret == false); + UNSCOPED_INFO("an empty keyword is not valid"); + CHECK_FALSE(ret); is.clear(); is.str(str = ""); ret = skip_to_end_of_next_keyword("}", is); - ensure("skip_to_end_of_next_keyword: empty string", ret == false); + UNSCOPED_INFO("empty string"); + CHECK_FALSE(ret); } - //testcases for get_word(std::string& output_string, std::istream& input_stream) - template<> template<> - void streamtools_object::test<7>() + TEST_CASE("streamtools: get_word reads words split by whitespace", "[streamtools]") { std::string str; std::string expected_result; @@ -333,17 +333,17 @@ namespace tut actual_result = ""; ret = get_word(actual_result, is); expected_result = "First"; - ensure_equals("get_word: 1", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; ret = get_word(actual_result, is); expected_result = "Second"; - ensure_equals("get_word: 2", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; ret = get_word(actual_result, is); expected_result = "Third"; - ensure_equals("get_word: 3", actual_result, expected_result); + CHECK(actual_result == expected_result); // the current implementation of get_word seems inconsistent with // skip_to_next_word. skip_to_next_word treats any character other @@ -354,29 +354,29 @@ namespace tut actual_result = ""; ret = get_word(actual_result, is); // will copy Fourth-ShouldThisBePartOfFourth expected_result = "Fourth-ShouldThisBePartOfFourth"; // as per current impl. - ensure_equals("get_word: 4", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; ret = get_word(actual_result, is); // will copy Fifth expected_result = "Fifth"; // as per current impl. - ensure_equals("get_word: 5", actual_result, expected_result); + CHECK(actual_result == expected_result); is.clear(); is.str(str = " \t \r \n "); actual_result = ""; ret = get_word(actual_result, is); - ensure("get_word: empty all spaces, newline tabs", ret == false); + UNSCOPED_INFO("only whitespace"); + CHECK_FALSE(ret); is.clear(); is.str(str = ""); actual_result = ""; ret = get_word(actual_result, is); - ensure("get_word: empty string", ret == false); + UNSCOPED_INFO("empty string"); + CHECK_FALSE(ret); } - // testcase for get_word and skip_to_next_word compatibility - template<> template<> - void streamtools_object::test<8>() + TEST_CASE("streamtools: get_word and skip_to_next_word reach the same fifth word", "[streamtools]") { std::string str; std::string expected_result; @@ -415,12 +415,10 @@ namespace tut // will copy ShouldThisBePartOfFourth, the fifth word, // while using get_word above five times result in getting "Fifth" get_word(expected_result, is); - ensure_equals("get_word: skip_to_next_word compatibility", actual_result, expected_result); + CHECK(actual_result == expected_result); } - //testcases for get_word(std::string& output_string, std::istream& input_stream, int n) - template<> template<> - void streamtools_object::test<9>() + TEST_CASE("streamtools: get_word with a limit stops after n characters", "[streamtools]") { std::string str; std::string expected_result; @@ -433,44 +431,45 @@ namespace tut actual_result = ""; ret = get_word(actual_result, is, 255); expected_result = "First"; - ensure_equals("get_word: 1", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; ret = get_word(actual_result, is, 4); expected_result = "Seco"; // should be cut short - ensure_equals("get_word: 2", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; ret = get_word(actual_result, is, 255); expected_result = "nd"; // get remainder of Second - ensure_equals("get_word: 3", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; ret = get_word(actual_result, is, 0); // 0 size string expected_result = ""; // get remainder of Second - ensure_equals("get_word: 0 sized output", actual_result, expected_result); + UNSCOPED_INFO("a limit of zero"); + CHECK(actual_result == expected_result); actual_result = ""; ret = get_word(actual_result, is, 255); expected_result = "Third"; - ensure_equals("get_word: 4", actual_result, expected_result); + CHECK(actual_result == expected_result); is.clear(); is.str(str = " \t \r \n "); actual_result = ""; ret = get_word(actual_result, is, 255); - ensure("get_word: empty all spaces, newline tabs", ret == false); + UNSCOPED_INFO("only whitespace"); + CHECK_FALSE(ret); is.clear(); is.str(str = ""); actual_result = ""; ret = get_word(actual_result, is, 255); - ensure("get_word: empty string", ret == false); + UNSCOPED_INFO("empty string"); + CHECK_FALSE(ret); } - //test cases for get_line(std::string& output_string, std::istream& input_stream) - template<> template<> - void streamtools_object::test<10>() + TEST_CASE("streamtools: get_line reads up to and including each newline", "[streamtools]") { std::string str; std::string expected_result; @@ -482,39 +481,38 @@ namespace tut actual_result = ""; get_line(actual_result, is); expected_result = "First Second \t \r\n"; - ensure_equals("get_line: 1", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; get_line(actual_result, is); expected_result = " Third Fourth-ShouldThisBePartOfFourth IsThisFifth\n"; - ensure_equals("get_line: 2", actual_result, expected_result); + CHECK(actual_result == expected_result); is.clear(); is.str(str = "\nFirst Line.\n\nSecond Line.\n"); actual_result = ""; get_line(actual_result, is); expected_result = "\n"; - ensure_equals("get_line: First char as newline", actual_result, expected_result); + UNSCOPED_INFO("a newline first"); + CHECK(actual_result == expected_result); actual_result = ""; get_line(actual_result, is); expected_result = "First Line.\n"; - ensure_equals("get_line: 3", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; get_line(actual_result, is); expected_result = "\n"; - ensure_equals("get_line: 4", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; get_line(actual_result, is); expected_result = "Second Line.\n"; - ensure_equals("get_line: 5", actual_result, expected_result); + CHECK(actual_result == expected_result); } - //test cases for get_line(std::string& output_string, std::istream& input_stream) - template<> template<> - void streamtools_object::test<11>() + TEST_CASE("streamtools: get_line reads a line with no newline and returns good()", "[streamtools]") { std::string str; std::string expected_result; @@ -527,13 +525,11 @@ namespace tut actual_result = ""; ret = get_line(actual_result, is); expected_result = "One Line only with no newline"; - ensure_equals("get_line: No newline", actual_result, expected_result); - ensure_equals("return value is good state of stream", ret, is.good()); + CHECK(actual_result == expected_result); + CHECK(ret == is.good()); } - //test cases for get_line(std::string& output_string, std::istream& input_stream) - template<> template<> - void streamtools_object::test<12>() + TEST_CASE("streamtools: get_line keeps a lone carriage return", "[streamtools]") { std::string str; std::string expected_result; @@ -546,12 +542,10 @@ namespace tut actual_result = ""; get_line(actual_result, is); expected_result = "Should not skip lone \r.\r\n"; - ensure_equals("get_line: carriage return skipped even though not followed by newline", actual_result, expected_result); + CHECK(actual_result == expected_result); } - //test cases for get_line(std::string& output_string, std::istream& input_stream) - template<> template<> - void streamtools_object::test<13>() + TEST_CASE("streamtools: get_line reads a line that is just a newline", "[streamtools]") { std::string str; std::string expected_result; @@ -563,13 +557,11 @@ namespace tut actual_result = ""; get_line(actual_result, is); expected_result = "\n"; - ensure_equals("get_line: Just newline", actual_result, expected_result); + CHECK(actual_result == expected_result); } - //testcases for get_line(std::string& output_string, std::istream& input_stream, int n) - template<> template<> - void streamtools_object::test<14>() + TEST_CASE("streamtools: get_line with a limit cuts the line and adds a newline", "[streamtools]") { std::string str; std::string expected_result; @@ -581,36 +573,38 @@ namespace tut actual_result = ""; get_line(actual_result, is, 255); expected_result = "First Line.\n"; - ensure_equals("get_line: Basic Operation", actual_result, expected_result); + CHECK(actual_result == expected_result); actual_result = ""; get_line(actual_result, is, sizeof("Second")-1); expected_result = "Second\n"; - ensure_equals("get_line: Insufficient length 1", actual_result, expected_result); + UNSCOPED_INFO("cut short"); + CHECK(actual_result == expected_result); actual_result = ""; get_line(actual_result, is, 255); expected_result = " Line.\n"; - ensure_equals("get_line: Remainder after earlier insufficient length", actual_result, expected_result); + UNSCOPED_INFO("the rest after a cut"); + CHECK(actual_result == expected_result); is.clear(); is.str(str = "One Line only with no newline with limited length"); actual_result = ""; get_line(actual_result, is, sizeof("One Line only with no newline with limited length")-1); expected_result = "One Line only with no newline with limited length\n"; - ensure_equals("get_line: No newline with limited length", actual_result, expected_result); + UNSCOPED_INFO("no newline and a limit of its length"); + CHECK(actual_result == expected_result); is.clear(); is.str(str = "One Line only with no newline"); actual_result = ""; get_line(actual_result, is, 255); expected_result = "One Line only with no newline"; - ensure_equals("get_line: No newline", actual_result, expected_result); + UNSCOPED_INFO("no newline"); + CHECK(actual_result == expected_result); } - //testcases for get_line(std::string& output_string, std::istream& input_stream, int n) - template<> template<> - void streamtools_object::test<15>() + TEST_CASE("streamtools: get_line with a limit reads a line with no newline and returns good()", "[streamtools]") { std::string str; std::string expected_result; @@ -623,13 +617,11 @@ namespace tut actual_result = ""; ret = get_line(actual_result, is, 255); expected_result = "One Line only with no newline"; - ensure_equals("get_line: No newline", actual_result, expected_result); - ensure_equals("return value is good state of stream", ret, is.good()); + CHECK(actual_result == expected_result); + CHECK(ret == is.good()); } - //testcases for remove_last_char() - template<> template<> - void streamtools_object::test<16>() + TEST_CASE("streamtools: remove_last_char removes only a matching last character", "[streamtools]") { std::string str; std::string expected_result; @@ -639,25 +631,25 @@ namespace tut ret = remove_last_char('d',str); expected_result = "SecondLife is a 3D Worl"; - ensure_equals("remove_last_char: should remove last char", str, expected_result); + CHECK(str == expected_result); str = "SecondLife is a 3D World"; ret = remove_last_char('W',str); expected_result = "SecondLife is a 3D World"; - ensure_equals("remove_last_char: should not remove as it is not last char", str, expected_result); - ensure("remove_last_char: should return false", ret == false); + UNSCOPED_INFO("not the last char"); + CHECK(str == expected_result); + CHECK_FALSE(ret); str = "SecondLife is a 3D World\n"; ret = remove_last_char('\n',str); expected_result = "SecondLife is a 3D World"; - ensure_equals("remove_last_char: should remove last newline", str, expected_result); - ensure("remove_last_char: should remove newline and return true", ret == true); + UNSCOPED_INFO("a trailing newline"); + CHECK(str == expected_result); + CHECK(ret); } - //testcases for unescaped_string() - template<> template<> - void streamtools_object::test<17>() + TEST_CASE("streamtools: unescape_string unescapes newlines and backslashes only", "[streamtools]") { std::string str; std::string expected_result; @@ -665,37 +657,41 @@ namespace tut str = "SecondLife is a 3D world \\n"; unescape_string(str); expected_result = "SecondLife is a 3D world \n"; - ensure_equals("unescape_string: newline", str, expected_result); + UNSCOPED_INFO("newline"); + CHECK(str == expected_result); str = "SecondLife is a 3D world \\\\t \\n"; unescape_string(str); expected_result = "SecondLife is a 3D world \\t \n"; - ensure_equals("unescape_string: backslash and newline", str, expected_result); + UNSCOPED_INFO("backslash and newline"); + CHECK(str == expected_result); str = "SecondLife is a 3D world \\ "; unescape_string(str); expected_result = "SecondLife is a 3D world \\ "; - ensure_equals("unescape_string: insufficient to unescape", str, expected_result); + UNSCOPED_INFO("a lone trailing backslash"); + CHECK(str == expected_result); str = "SecondLife is a 3D world \\n \\n \\n \\\\\\n"; unescape_string(str); expected_result = "SecondLife is a 3D world \n \n \n \\\n"; - ensure_equals("unescape_string: multipel newline and backslash", str, expected_result); + UNSCOPED_INFO("several newlines and a backslash"); + CHECK(str == expected_result); str = "SecondLife is a 3D world \\t"; unescape_string(str); expected_result = "SecondLife is a 3D world \\t"; - ensure_equals("unescape_string: leaves tab as is", str, expected_result); + UNSCOPED_INFO("leaves a tab as is"); + CHECK(str == expected_result); str = "\\n"; unescape_string(str); expected_result = "\n"; - ensure_equals("unescape_string: only a newline", str, expected_result); + UNSCOPED_INFO("only a newline"); + CHECK(str == expected_result); } - //testcases for escape_string() - template<> template<> - void streamtools_object::test<18>() + TEST_CASE("streamtools: escape_string escapes newlines and backslashes and round-trips", "[streamtools]") { std::string str; std::string expected_result; @@ -703,46 +699,49 @@ namespace tut str = "SecondLife is a 3D world \n"; escape_string(str); expected_result = "SecondLife is a 3D world \\n"; - ensure_equals("escape_string: newline", str, expected_result); + UNSCOPED_INFO("newline"); + CHECK(str == expected_result); str = "SecondLife is a 3D world \\t \n"; escape_string(str); expected_result = "SecondLife is a 3D world \\\\t \\n"; - ensure_equals("escape_string: backslash and newline", str, expected_result); + UNSCOPED_INFO("backslash and newline"); + CHECK(str == expected_result); str = "SecondLife is a 3D world \n \n \n \\\n"; escape_string(str); expected_result = "SecondLife is a 3D world \\n \\n \\n \\\\\\n"; - ensure_equals("escape_string: multipel newline and backslash", str, expected_result); + UNSCOPED_INFO("several newlines and a backslash"); + CHECK(str == expected_result); str = "SecondLife is a 3D world \t"; escape_string(str); expected_result = "SecondLife is a 3D world \t"; - ensure_equals("unescape_string: leaves tab as is", str, expected_result); + UNSCOPED_INFO("leaves a tab as is"); + CHECK(str == expected_result); str = "\n"; escape_string(str); expected_result = "\\n"; - ensure_equals("unescape_string: only a newline", str, expected_result); + UNSCOPED_INFO("only a newline"); + CHECK(str == expected_result); // serialization/deserialization escape->unescape str = "SecondLife is a 3D world \n \n \n \\\n"; escape_string(str); unescape_string(str); expected_result = "SecondLife is a 3D world \n \n \n \\\n"; - ensure_equals("escape_string: should preserve with escape/unescape", str, expected_result); + CHECK(str == expected_result); // serialization/deserialization unescape->escape str = "SecondLife is a 3D world \\n \\n \\n \\\\"; unescape_string(str); escape_string(str); expected_result = "SecondLife is a 3D world \\n \\n \\n \\\\"; - ensure_equals("escape_string: should preserve with unescape/escape", str, expected_result); + CHECK(str == expected_result); } - // testcases for replace_newlines_with_whitespace() - template<> template<> - void streamtools_object::test<19>() + TEST_CASE("streamtools: replace_newlines_with_whitespace replaces only newlines", "[streamtools]") { std::string str; std::string expected_result; @@ -750,27 +749,29 @@ namespace tut str = "SecondLife is a 3D \n\nworld\n"; replace_newlines_with_whitespace(str); expected_result = "SecondLife is a 3D world "; - ensure_equals("replace_newlines_with_whitespace: replace all newline", str, expected_result); + UNSCOPED_INFO("every newline"); + CHECK(str == expected_result); str = "\nSecondLife is a 3D world\n"; replace_newlines_with_whitespace(str); expected_result = " SecondLife is a 3D world "; - ensure_equals("replace_newlines_with_whitespace: begin and newline", str, expected_result); + UNSCOPED_INFO("newlines at both ends"); + CHECK(str == expected_result); str = "SecondLife is a 3D world\r\t"; replace_newlines_with_whitespace(str); expected_result = "SecondLife is a 3D world\r\t"; - ensure_equals("replace_newlines_with_whitespace: should only replace newline", str, expected_result); + UNSCOPED_INFO("carriage returns and tabs are kept"); + CHECK(str == expected_result); str = ""; replace_newlines_with_whitespace(str); expected_result = ""; - ensure_equals("replace_newlines_with_whitespace: empty string", str, expected_result); + UNSCOPED_INFO("empty string"); + CHECK(str == expected_result); } - //testcases for remove_double_quotes() - template<> template<> - void streamtools_object::test<20>() + TEST_CASE("streamtools: remove_double_quotes removes paired and lone double quotes", "[streamtools]") { std::string str; std::string expected_result; @@ -778,42 +779,43 @@ namespace tut str = "SecondLife is a \"\"3D world"; remove_double_quotes(str); expected_result = "SecondLife is a 3D world"; - ensure_equals("remove_double_quotes: replace empty doube quotes", str, expected_result); + UNSCOPED_INFO("an empty pair of quotes"); + CHECK(str == expected_result); str = "SecondLife is a \"3D world"; remove_double_quotes(str); expected_result = "SecondLife is a 3D world"; - ensure_equals("remove_double_quotes: keep as is it matching quote not found", str, expected_result); + CHECK(str == expected_result); } - // testcases for get_brace_count() - template<> template<> - void streamtools_object::test<21>() + TEST_CASE("streamtools: get_brace_count has no checks yet", "[streamtools]") { } - //testcases for get_keyword_and_value() - template<> template<> - void streamtools_object::test<22>() + TEST_CASE("streamtools: get_keyword_and_value splits off the first word as the keyword", "[streamtools]") { std::string s = "SecondLife is a 3D World"; std::string keyword; std::string value; get_keyword_and_value(keyword, value, s); - ensure("get_keyword_and_value: Unable to get Keyword and Value", ((keyword == "SecondLife") && (value == "is a 3D World"))); + CHECK(keyword == "SecondLife"); + CHECK(value == "is a 3D World"); s = "SecondLife"; get_keyword_and_value(keyword, value, s); - ensure("get_keyword_and_value: value should be empty", ((keyword == "SecondLife") && (value == ""))); + CHECK(keyword == "SecondLife"); + CHECK(value == ""); s = "SecondLife \t is cool! \n"; get_keyword_and_value(keyword, value, s); - ensure("get_keyword_and_value: remove space before value but not after", ((keyword == "SecondLife") && (value == "is cool! "))); + { + INFO("space before the value is dropped but not after"); + CHECK(keyword == "SecondLife"); + CHECK(value == "is cool! "); + } } - //testcases for get_keyword_and_value() - template<> template<> - void streamtools_object::test<23>() + TEST_CASE("streamtools: get_keyword_and_value gives no value after a keyword and newline", "[streamtools]") { std::string s; std::string keyword = "SOME PRIOR KEYWORD"; @@ -821,12 +823,11 @@ namespace tut s = "SecondLife\n"; get_keyword_and_value(keyword, value, s); - ensure("get_keyword_and_value: terminated with newline. value should be empty", ((keyword == "SecondLife") && (value == ""))); + CHECK(keyword == "SecondLife"); + CHECK(value == ""); } - //testcases for get_keyword_and_value() - template<> template<> - void streamtools_object::test<24>() + TEST_CASE("streamtools: get_keyword_and_value of an empty string clears both", "[streamtools]") { std::string s; std::string keyword = "SOME PRIOR KEYWORD"; @@ -834,19 +835,18 @@ namespace tut s = ""; get_keyword_and_value(keyword, value, s); - ensure("get_keyword_and_value: empty string. keyword value should empty", ((keyword == "") && (value == ""))); + CHECK(keyword == ""); + CHECK(value == ""); } - //testcase for fullread() - template<> template<> - void streamtools_object::test<25>() + TEST_CASE("streamtools: fullread reads across newlines up to the requested count", "[streamtools]") { std::string str = "First Line.\nSecond Line\n"; std::istringstream is(str); char buf[255] = {0}; fullread(is, buf, 255); - ensure_memory_matches("fullread: read with newlines", (void*) buf, static_cast(str.size())-1, (void*) str.c_str(), static_cast(str.size())-1); + CHECK_THAT(ll_test::Memory(buf, str.size()-1), ll_test::MemoryMatches(str.c_str(), str.size()-1)); is.clear(); is.str(str = "First Line.\nSecond Line\n"); @@ -855,14 +855,11 @@ namespace tut char expected_string[] = "First Line.\nSecond"; int len = sizeof(expected_string)-1; fullread(is, buf, len); - ensure_memory_matches("fullread: read with newlines", (void*) buf, len, (void*) &expected_string, len); + CHECK_THAT(ll_test::Memory(buf, len), ll_test::MemoryMatches(expected_string, len)); } -// testcases for operator >> - - template<> template<> - void streamtools_object::test<26>() + TEST_CASE("streamtools: operator>> consumes a matching C string and fails on a mismatch", "[streamtools]") { char arr[255]; std::string str; @@ -872,12 +869,12 @@ namespace tut stream >> toCheck.c_str(); stream.get(arr, 255, '\0'); expected_result = " is a 3D World"; - ensure_equals("istream << operator", arr, expected_result); + CHECK(arr == expected_result); stream.clear(); stream.str(str = "SecondLife is a 3D World"); toCheck = "is"; stream >> toCheck.c_str(); - ensure("istream << operator should have failed", stream.good() == false); + CHECK_FALSE(stream.good()); } } diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 44a1d3f087..f8538b2ef6 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -31,433 +31,467 @@ #include "../llstring.h" #include "../llsd.h" #include "StringVec.h" // must come BEFORE lltut.h -#include "../test/lltut.h" +#include +#include -namespace tut +namespace { - struct string_index - { - }; - typedef test_group string_index_t; - typedef string_index_t::object string_index_object_t; - tut::string_index_t tut_string_index("LLString"); - - template<> template<> - void string_index_object_t::test<1>() + TEST_CASE("LLString: std::string constructors give the expected text and size", "[LLString]") { std::string llstr1; - ensure("Empty std::string", (llstr1.size() == 0) && llstr1.empty()); + { + INFO("default-constructed"); + CHECK(llstr1.size() == 0); + CHECK(llstr1.empty()); + } std::string llstr2("Hello"); - ensure("std::string = Hello", (!strcmp(llstr2.c_str(), "Hello")) && (llstr2.size() == 5) && !llstr2.empty()); + { + INFO("from a C string"); + CHECK(std::string_view(llstr2.c_str()) == "Hello"); + CHECK(llstr2.size() == 5); + CHECK_FALSE(llstr2.empty()); + } std::string llstr3(llstr2); - ensure("std::string = std::string(std::string)", (!strcmp(llstr3.c_str(), "Hello")) && (llstr3.size() == 5) && !llstr3.empty()); + { + INFO("copy"); + CHECK(std::string_view(llstr3.c_str()) == "Hello"); + CHECK(llstr3.size() == 5); + CHECK_FALSE(llstr3.empty()); + } std::string str("Hello World"); std::string llstr4(str, 6); - ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (!strcmp(llstr4.c_str(), "World")) && (llstr4.size() == 5) && !llstr4.empty()); + { + INFO("from a position"); + CHECK(std::string_view(llstr4.c_str()) == "World"); + CHECK(llstr4.size() == 5); + CHECK_FALSE(llstr4.empty()); + } std::string llstr5(str, str.size()); - ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (llstr5.size() == 0) && llstr5.empty()); + { + INFO("from the end position"); + CHECK(llstr5.size() == 0); + CHECK(llstr5.empty()); + } std::string llstr6(5, 'A'); - ensure("std::string = std::string(count, c)", (!strcmp(llstr6.c_str(), "AAAAA")) && (llstr6.size() == 5) && !llstr6.empty()); + { + INFO("count copies of a character"); + CHECK(std::string_view(llstr6.c_str()) == "AAAAA"); + CHECK(llstr6.size() == 5); + CHECK_FALSE(llstr6.empty()); + } std::string llstr7("Hello World", 5); - ensure("std::string(s, n)", (!strcmp(llstr7.c_str(), "Hello")) && (llstr7.size() == 5) && !llstr7.empty()); + { + INFO("the first n characters"); + CHECK(std::string_view(llstr7.c_str()) == "Hello"); + CHECK(llstr7.size() == 5); + CHECK_FALSE(llstr7.empty()); + } std::string llstr8("Hello World", 6, 5); - ensure("std::string(s, n, count)", (!strcmp(llstr8.c_str(), "World")) && (llstr8.size() == 5) && !llstr8.empty()); + { + INFO("n characters from a position"); + CHECK(std::string_view(llstr8.c_str()) == "World"); + CHECK(llstr8.size() == 5); + CHECK_FALSE(llstr8.empty()); + } std::string llstr9("Hello World", sizeof("Hello World")-1, 5); // go past end - ensure("std::string(s, n, count) goes past end", (llstr9.size() == 0) && llstr9.empty()); + { + INFO("from the end position, asking for more"); + CHECK(llstr9.size() == 0); + CHECK(llstr9.empty()); + } } - template<> template<> - void string_index_object_t::test<3>() + TEST_CASE("LLString: isValidIndex accepts indexes up to and including the length", "[LLString]") { std::string str("Len=5"); - ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == true && - LLStringUtil::isValidIndex(str, 5) == true && - LLStringUtil::isValidIndex(str, 6) == false); + CHECK(LLStringUtil::isValidIndex(str, 0)); + CHECK(LLStringUtil::isValidIndex(str, 5)); + CHECK_FALSE(LLStringUtil::isValidIndex(str, 6)); std::string str1; - ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == false); + UNSCOPED_INFO("an empty string"); + CHECK_FALSE(LLStringUtil::isValidIndex(str1, 0)); } - template<> template<> - void string_index_object_t::test<4>() + TEST_CASE("LLString: trimHead removes leading spaces and line ends", "[LLString]") { std::string str_val(" Testing the extra whitespaces "); LLStringUtil::trimHead(str_val); - ensure_equals("1: trimHead failed", str_val, "Testing the extra whitespaces "); + CHECK(str_val == "Testing the extra whitespaces "); std::string str_val1("\n\t\r\n Testing the extra whitespaces "); LLStringUtil::trimHead(str_val1); - ensure_equals("2: trimHead failed", str_val1, "Testing the extra whitespaces "); + CHECK(str_val1 == "Testing the extra whitespaces "); } - template<> template<> - void string_index_object_t::test<5>() + TEST_CASE("LLString: trimTail removes trailing spaces and line ends", "[LLString]") { std::string str_val(" Testing the extra whitespaces "); LLStringUtil::trimTail(str_val); - ensure_equals("1: trimTail failed", str_val, " Testing the extra whitespaces"); + CHECK(str_val == " Testing the extra whitespaces"); std::string str_val1("\n Testing the extra whitespaces \n\t\r\n "); LLStringUtil::trimTail(str_val1); - ensure_equals("2: trimTail failed", str_val1, "\n Testing the extra whitespaces"); + CHECK(str_val1 == "\n Testing the extra whitespaces"); } - template<> template<> - void string_index_object_t::test<6>() + TEST_CASE("LLString: trim removes whitespace at both ends only", "[LLString]") { std::string str_val(" \t \r Testing the extra \r\n whitespaces \n \t "); LLStringUtil::trim(str_val); - ensure_equals("1: trim failed", str_val, "Testing the extra \r\n whitespaces"); + CHECK(str_val == "Testing the extra \r\n whitespaces"); } - template<> template<> - void string_index_object_t::test<7>() + TEST_CASE("LLString: truncate shortens to a length and to nothing", "[LLString]") { std::string str("Second LindenLabs"); LLStringUtil::truncate(str, 6); - ensure_equals("1: truncate", str, "Second"); + CHECK(str == "Second"); // further truncate more than the length LLStringUtil::truncate(str, 0); - ensure_equals("2: truncate", str, ""); + CHECK(str == ""); } - template<> template<> - void string_index_object_t::test<8>() + TEST_CASE("LLString: toUpper uppercases ASCII", "[LLString]") { std::string str_val("SecondLife Source"); LLStringUtil::toUpper(str_val); - ensure_equals("toUpper failed", str_val, "SECONDLIFE SOURCE"); + CHECK(str_val == "SECONDLIFE SOURCE"); } - template<> template<> - void string_index_object_t::test<9>() + TEST_CASE("LLString: toLower lowercases ASCII", "[LLString]") { std::string str_val("SecondLife Source"); LLStringUtil::toLower(str_val); - ensure_equals("toLower failed", str_val, "secondlife source"); + CHECK(str_val == "secondlife source"); } - template<> template<> - void string_index_object_t::test<10>() + TEST_CASE("LLString: isHead matches a prefix and never an empty string", "[LLString]") { std::string str_val("Second"); - ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == true); - ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == false); + CHECK(LLStringUtil::isHead(str_val, "SecondLife Source")); + CHECK_FALSE(LLStringUtil::isHead(str_val, " SecondLife Source")); std::string str_val2(""); - ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == false); + CHECK_FALSE(LLStringUtil::isHead(str_val2, "")); } - template<> template<> - void string_index_object_t::test<11>() + TEST_CASE("LLString: addCRLF and removeCRLF convert line ends both ways", "[LLString]") { std::string str_val("Hello.\n\n Lindenlabs. \n This is \na simple test.\n"); std::string orig_str_val(str_val); LLStringUtil::addCRLF(str_val); - ensure_equals("addCRLF failed", str_val, "Hello.\r\n\r\n Lindenlabs. \r\n This is \r\na simple test.\r\n"); + CHECK(str_val == "Hello.\r\n\r\n Lindenlabs. \r\n This is \r\na simple test.\r\n"); LLStringUtil::removeCRLF(str_val); - ensure_equals("removeCRLF failed", str_val, orig_str_val); + CHECK(str_val == orig_str_val); } - template<> template<> - void string_index_object_t::test<12>() + TEST_CASE("LLString: replaceTabsWithSpaces puts n spaces for each tab", "[LLString]") { std::string str_val("Hello.\n\n\t \t Lindenlabs. \t\t"); std::string orig_str_val(str_val); LLStringUtil::replaceTabsWithSpaces(str_val, 1); - ensure_equals("replaceTabsWithSpaces failed", str_val, "Hello.\n\n Lindenlabs. "); + CHECK(str_val == "Hello.\n\n Lindenlabs. "); LLStringUtil::replaceTabsWithSpaces(orig_str_val, 0); - ensure_equals("replaceTabsWithSpaces failed for 0", orig_str_val, "Hello.\n\n Lindenlabs. "); + UNSCOPED_INFO("a width of zero"); + CHECK(orig_str_val == "Hello.\n\n Lindenlabs. "); str_val = "\t\t\t\t"; LLStringUtil::replaceTabsWithSpaces(str_val, 0); - ensure_equals("replaceTabsWithSpaces failed for all tabs", str_val, ""); + UNSCOPED_INFO("only tabs"); + CHECK(str_val == ""); } - template<> template<> - void string_index_object_t::test<13>() + TEST_CASE("LLString: replaceNonstandardASCII replaces controls other than newline", "[LLString]") { std::string str_val("Hello.\n\n\t\t\r\nLindenlabsX."); LLStringUtil::replaceNonstandardASCII(str_val, 'X'); - ensure_equals("replaceNonstandardASCII failed", str_val, "Hello.\n\nXXX\nLindenlabsX."); + CHECK(str_val == "Hello.\n\nXXX\nLindenlabsX."); } - template<> template<> - void string_index_object_t::test<14>() + TEST_CASE("LLString: replaceChar replaces every instance of a character", "[LLString]") { std::string str_val("Hello.\n\t\r\nABCDEFGHIABABAB"); LLStringUtil::replaceChar(str_val, 'A', 'X'); - ensure_equals("1: replaceChar failed", str_val, "Hello.\n\t\r\nXBCDEFGHIXBXBXB"); + CHECK(str_val == "Hello.\n\t\r\nXBCDEFGHIXBXBXB"); std::string str_val1("Hello.\n\t\r\nABCDEFGHIABABAB"); } - template<> template<> - void string_index_object_t::test<15>() + TEST_CASE("LLString: containsNonprintable finds controls and separators but not text", "[LLString]") { std::string str_val("Hello.\n\r\t"); - ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == true); + CHECK(LLStringUtil::containsNonprintable(str_val)); str_val = "ABC "; - ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == false); + CHECK_FALSE(LLStringUtil::containsNonprintable(str_val)); // Controls above the ASCII range are two bytes, and neither of them // looks like a control on its own. U+0085 NEXT LINE. str_val = "AB\xC2\x85" "C"; - ensure("C1 control found", LLStringUtil::containsNonprintable(str_val) == true); + UNSCOPED_INFO("C1 control found"); + CHECK(LLStringUtil::containsNonprintable(str_val)); // U+2028 LINE SEPARATOR. str_val = "AB\xE2\x80\xA8" "C"; - ensure("line separator found", LLStringUtil::containsNonprintable(str_val) == true); + UNSCOPED_INFO("line separator found"); + CHECK(LLStringUtil::containsNonprintable(str_val)); // DEL, which a test for "below 0x20" never saw. str_val = "AB\x7F" "C"; - ensure("delete found", LLStringUtil::containsNonprintable(str_val) == true); + UNSCOPED_INFO("delete found"); + CHECK(LLStringUtil::containsNonprintable(str_val)); // Non-ASCII text is printable, and so are the format characters that // scripts depend on -- ZWJ here, which Indic conjuncts need. str_val = "\xE6\x97\xA5" "\xC3\xA9" "ok"; - ensure("ordinary text is printable", LLStringUtil::containsNonprintable(str_val) == false); + UNSCOPED_INFO("ordinary text is printable"); + CHECK_FALSE(LLStringUtil::containsNonprintable(str_val)); str_val = "\xE0\xA4\x95\xE2\x80\x8D\xE0\xA4\xB7"; - ensure("ZWJ is printable", LLStringUtil::containsNonprintable(str_val) == false); + UNSCOPED_INFO("ZWJ is printable"); + CHECK_FALSE(LLStringUtil::containsNonprintable(str_val)); } - template<> template<> - void string_index_object_t::test<16>() + TEST_CASE("LLString: stripNonprintable removes controls and separators whole", "[LLString]") { std::string str_val("Hello.\n\r\t Again!"); LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable failed", str_val, "Hello. Again!"); + CHECK(str_val == "Hello. Again!"); str_val = "\r\n\t\t"; LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable resulting in empty string failed", str_val, ""); + UNSCOPED_INFO("only controls"); + CHECK(str_val == ""); str_val = ""; LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable of empty string resulting in empty string failed", str_val, ""); + UNSCOPED_INFO("empty string"); + CHECK(str_val == ""); // A control above the ASCII range goes whole, taking both its bytes. str_val = "AB\xC2\x85" "C"; LLStringUtil::stripNonprintable(str_val); - ensure_equals("C1 control stripped whole", str_val, "ABC"); + UNSCOPED_INFO("C1 control stripped whole"); + CHECK(str_val == "ABC"); // Text either side of it is left alone, bytes and all. str_val = "\xE6\x97\xA5" "\xE2\x80\xA8" "\xC3\xA9"; LLStringUtil::stripNonprintable(str_val); - ensure_equals("only the separator goes", str_val, "\xE6\x97\xA5" "\xC3\xA9"); - } - - // Capitalising a byte can only ever reach ASCII, so the narrow form works - // in codepoints. - template<> template<> - void string_index_object_t::test<45>() - { - std::string str_val("hello world-again_now"); - LLStringUtil::capitalize(str_val); - ensure_equals("ascii words", str_val, "Hello World-Again_Now"); - - // U+00E9 -> U+00C9, which uppercasing one byte at a time could not do. - str_val = "\xC3\xA9" "cole"; - LLStringUtil::capitalize(str_val); - ensure_equals("accented first letter", str_val, "\xC3\x89" "cole"); - - // The word rule is the caller's and still only sees these three. - str_val = "a b-c_d.e"; - LLStringUtil::capitalize(str_val); - ensure_equals("dot is not a separator", str_val, "A B-C_D.e"); - - str_val = ""; - LLStringUtil::capitalize(str_val); - ensure_equals("empty", str_val, ""); + UNSCOPED_INFO("only the separator goes"); + CHECK(str_val == "\xE6\x97\xA5" "\xC3\xA9"); } - template<> template<> - void string_index_object_t::test<17>() + TEST_CASE("LLString: convertToBOOL reads the true and false spellings", "[LLString]") { bool value; std::string str_val("1"); - ensure("convertToBOOL 1 failed", LLStringUtil::convertToBOOL(str_val, value) && value); + REQUIRE(LLStringUtil::convertToBOOL(str_val, value)); + CHECK(value); str_val = "T"; - ensure("convertToBOOL T failed", LLStringUtil::convertToBOOL(str_val, value) && value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK(value); str_val = "t"; - ensure("convertToBOOL t failed", LLStringUtil::convertToBOOL(str_val, value) && value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK(value); str_val = "TRUE"; - ensure("convertToBOOL TRUE failed", LLStringUtil::convertToBOOL(str_val, value) && value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK(value); str_val = "True"; - ensure("convertToBOOL True failed", LLStringUtil::convertToBOOL(str_val, value) && value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK(value); str_val = "true"; - ensure("convertToBOOL true failed", LLStringUtil::convertToBOOL(str_val, value) && value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK(value); str_val = "0"; - ensure("convertToBOOL 0 failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK_FALSE(value); str_val = "F"; - ensure("convertToBOOL F failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK_FALSE(value); str_val = "f"; - ensure("convertToBOOL f failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK_FALSE(value); str_val = "FALSE"; - ensure("convertToBOOL FASLE failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK_FALSE(value); str_val = "False"; - ensure("convertToBOOL False failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK_FALSE(value); str_val = "false"; - ensure("convertToBOOL false failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + CHECK(LLStringUtil::convertToBOOL(str_val, value)); + CHECK_FALSE(value); str_val = "Tblah"; - ensure("convertToBOOL false failed", !LLStringUtil::convertToBOOL(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToBOOL(str_val, value)); } - template<> template<> - void string_index_object_t::test<18>() + TEST_CASE("LLString: convertToU8 accepts 0 to 255 only", "[LLString]") { U8 value; std::string str_val("255"); - ensure("1: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 255); + REQUIRE(LLStringUtil::convertToU8(str_val, value)); + CHECK(value == 255); str_val = "0"; - ensure("2: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 0); + CHECK(LLStringUtil::convertToU8(str_val, value)); + CHECK(value == 0); str_val = "-1"; - ensure("3: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToU8(str_val, value)); str_val = "256"; // bigger than MAX_U8 - ensure("4: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToU8(str_val, value)); } - template<> template<> - void string_index_object_t::test<19>() + TEST_CASE("LLString: convertToS8 accepts -128 to 127 only", "[LLString]") { S8 value; std::string str_val("127"); - ensure("1: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 127); + REQUIRE(LLStringUtil::convertToS8(str_val, value)); + CHECK(value == 127); str_val = "0"; - ensure("2: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 0); + CHECK(LLStringUtil::convertToS8(str_val, value)); + CHECK(value == 0); str_val = "-128"; - ensure("3: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == -128); + CHECK(LLStringUtil::convertToS8(str_val, value)); + CHECK(value == -128); str_val = "128"; // bigger than MAX_S8 - ensure("4: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToS8(str_val, value)); str_val = "-129"; - ensure("5: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToS8(str_val, value)); } - template<> template<> - void string_index_object_t::test<20>() + TEST_CASE("LLString: convertToS16 accepts -32768 to 32767 only", "[LLString]") { S16 value; std::string str_val("32767"); - ensure("1: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 32767); + REQUIRE(LLStringUtil::convertToS16(str_val, value)); + CHECK(value == 32767); str_val = "0"; - ensure("2: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 0); + CHECK(LLStringUtil::convertToS16(str_val, value)); + CHECK(value == 0); str_val = "-32768"; - ensure("3: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == -32768); + CHECK(LLStringUtil::convertToS16(str_val, value)); + CHECK(value == -32768); str_val = "32768"; - ensure("4: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToS16(str_val, value)); str_val = "-32769"; - ensure("5: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToS16(str_val, value)); } - template<> template<> - void string_index_object_t::test<21>() + TEST_CASE("LLString: convertToU16 accepts 0 to 65535 only", "[LLString]") { U16 value; std::string str_val("65535"); //0xFFFF - ensure("1: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 65535); + REQUIRE(LLStringUtil::convertToU16(str_val, value)); + CHECK(value == 65535); str_val = "0"; - ensure("2: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 0); + CHECK(LLStringUtil::convertToU16(str_val, value)); + CHECK(value == 0); str_val = "-1"; - ensure("3: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToU16(str_val, value)); str_val = "65536"; - ensure("4: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToU16(str_val, value)); } - template<> template<> - void string_index_object_t::test<22>() + TEST_CASE("LLString: convertToU32 accepts 0 to 4294967295 and a leading plus", "[LLString]") { U32 value; std::string str_val("4294967295"); //0xFFFFFFFF - ensure("1: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 4294967295UL); + REQUIRE(LLStringUtil::convertToU32(str_val, value)); + CHECK(value == 4294967295UL); str_val = "0"; - ensure("2: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 0); + CHECK(LLStringUtil::convertToU32(str_val, value)); + CHECK(value == 0); str_val = "4294967296"; - ensure("3: convertToU32 failed", !LLStringUtil::convertToU32(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToU32(str_val, value)); // A negative is rejected rather than wrapped. convertToU8 and // convertToU16 above already answer false for "-1" -- they range-check // after parsing as signed -- so this is the unsigned form agreeing with // its own siblings. Stream extraction let num_get negate it instead. str_val = "-1"; - ensure("4: convertToU32 failed", !LLStringUtil::convertToU32(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToU32(str_val, value)); // A leading '+' is still accepted, as it was through the stream. str_val = "+7"; - ensure("5: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 7); + CHECK(LLStringUtil::convertToU32(str_val, value)); + CHECK(value == 7); } - template<> template<> - void string_index_object_t::test<23>() + TEST_CASE("LLString: convertToS32 reads the S32 range with a sign or spaces or trailing text", "[LLString]") { S32 value; std::string str_val("2147483647"); //0x7FFFFFFF - ensure("1: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 2147483647); + REQUIRE(LLStringUtil::convertToS32(str_val, value)); + CHECK(value == 2147483647); str_val = "0"; - ensure("2: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 0); + CHECK(LLStringUtil::convertToS32(str_val, value)); + CHECK(value == 0); // Avoid "unary minus operator applied to unsigned type" warning on VC++. JC S32 min_val = -2147483647 - 1; str_val = "-2147483648"; - ensure("3: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == min_val); + CHECK(LLStringUtil::convertToS32(str_val, value)); + CHECK(value == min_val); str_val = "2147483648"; - ensure("4: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToS32(str_val, value)); str_val = "-2147483649"; - ensure("5: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToS32(str_val, value)); // from_chars does not take a leading '+' on its own; the conversions // strip it so they keep accepting what stream extraction accepted. str_val = "+42"; - ensure("6: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 42); + CHECK(LLStringUtil::convertToS32(str_val, value)); + CHECK(value == 42); // Surrounding space is trimmed, and a parse that stops early still // reports what it read -- both as the stream did. str_val = " 13 "; - ensure("7: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 13); + CHECK(LLStringUtil::convertToS32(str_val, value)); + CHECK(value == 13); str_val = "12abc"; - ensure("8: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 12); + CHECK(LLStringUtil::convertToS32(str_val, value)); + CHECK(value == 12); str_val = "abc"; - ensure("9: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToS32(str_val, value)); } - template<> template<> - void string_index_object_t::test<24>() + TEST_CASE("LLString: convertToF32 reads decimals and exponents and signs", "[LLString]") { F32 value; std::string str_val("2147483647"); //0x7FFFFFFF - ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647.f); + REQUIRE(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == 2147483647.f); str_val = "0"; - ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0); + CHECK(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == 0); /* Need to find max/min F32 values str_val = "-2147483648"; @@ -474,33 +508,39 @@ namespace tut // a leading '+' all read the same as they did through the stream, and // the decimal point stays a point whatever the C locale is set to. str_val = "1.5"; - ensure("6: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 1.5f); + CHECK(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == 1.5f); str_val = "-2.25"; - ensure("7: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2.25f); + CHECK(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == -2.25f); str_val = "+0.5"; - ensure("8: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0.5f); + CHECK(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == 0.5f); str_val = "1.5e2"; - ensure("9: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 150.f); + CHECK(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == 150.f); str_val = " 0.25 "; - ensure("10: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0.25f); + CHECK(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == 0.25f); str_val = "nonsense"; - ensure("11: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + CHECK_FALSE(LLStringUtil::convertToF32(str_val, value)); } - template<> template<> - void string_index_object_t::test<25>() + TEST_CASE("LLString: convertToF64 reads a large integer and zero", "[LLString]") { F64 value; std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF - ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807.0); + REQUIRE(LLStringUtil::convertToF64(str_val, value)); + CHECK(value == 9223372036854775807.0); str_val = "0"; - ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F); + CHECK(LLStringUtil::convertToF64(str_val, value)); + CHECK(value == 0.0F); /* Need to find max/min F64 values str_val = "-2147483648"; @@ -514,118 +554,115 @@ namespace tut */ } - template<> template<> - void string_index_object_t::test<26>() + TEST_CASE("LLString: compareStrings puts null after text and compares text", "[LLString]") { const char* str1 = NULL; const char* str2 = NULL; - ensure("1: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); + CHECK(LLStringUtil::compareStrings(str1, str2) == 0); str2 = "A"; - ensure("2: compareStrings failed", LLStringUtil::compareStrings(str1, str2) > 0); - ensure("3: compareStrings failed", LLStringUtil::compareStrings(str2, str1) < 0); + CHECK(LLStringUtil::compareStrings(str1, str2) > 0); + CHECK(LLStringUtil::compareStrings(str2, str1) < 0); str1 = "A is smaller than B"; str2 = "B is greater than A"; - ensure("4: compareStrings failed", LLStringUtil::compareStrings(str1, str2) < 0); + CHECK(LLStringUtil::compareStrings(str1, str2) < 0); str2 = "A is smaller than B"; - ensure("5: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); + CHECK(LLStringUtil::compareStrings(str1, str2) == 0); } - template<> template<> - void string_index_object_t::test<27>() + TEST_CASE("LLString: compareInsensitive ignores case but not accents or sharp s", "[LLString]") { const char* str1 = NULL; const char* str2 = NULL; - ensure("1: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); + CHECK(LLStringUtil::compareInsensitive(str1, str2) == 0); str2 = "A"; - ensure("2: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) > 0); - ensure("3: compareInsensitive failed", LLStringUtil::compareInsensitive(str2, str1) < 0); + CHECK(LLStringUtil::compareInsensitive(str1, str2) > 0); + CHECK(LLStringUtil::compareInsensitive(str2, str1) < 0); str1 = "A is equal to a"; str2 = "a is EQUAL to A"; - ensure("4: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); + CHECK(LLStringUtil::compareInsensitive(str1, str2) == 0); // Ignoring case is not the same as uppercasing both sides, which is // what this used to do: that turns sharp s into SS and so calls two // different words the same word. - ensure("sharp s is not ss", - LLStringUtil::compareInsensitive(std::string("stra\xC3\x9F""e"), + UNSCOPED_INFO("sharp s is not ss"); + CHECK(LLStringUtil::compareInsensitive(std::string("stra\xC3\x9F""e"), std::string("strasse")) != 0); - ensure("sharp s is not SS", - LLStringUtil::compareInsensitive(std::string("stra\xC3\x9F""e"), + UNSCOPED_INFO("sharp s is not SS"); + CHECK(LLStringUtil::compareInsensitive(std::string("stra\xC3\x9F""e"), std::string("STRASSE")) != 0); // Accents are a secondary difference and survive; case is tertiary // and does not. - ensure("accents survive", - LLStringUtil::compareInsensitive(std::string("ab"), + UNSCOPED_INFO("accents survive"); + CHECK(LLStringUtil::compareInsensitive(std::string("ab"), std::string("\xC3\xA4""b")) < 0); - ensure("case does not", - LLStringUtil::compareInsensitive(std::string("\xC3\x84""B"), + UNSCOPED_INFO("case does not"); + CHECK(LLStringUtil::compareInsensitive(std::string("\xC3\x84""B"), std::string("\xC3\xA4""b")) == 0); } - template<> template<> - void string_index_object_t::test<28>() + TEST_CASE("LLString: compareDict and precedesDict order dictionary style", "[LLString]") { std::string lhs_str("PROgraM12files"); std::string rhs_str("PROgram12Files"); - ensure("compareDict 1 failed", LLStringUtil::compareDict(lhs_str, rhs_str) < 0); - ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == true); + CHECK(LLStringUtil::compareDict(lhs_str, rhs_str) < 0); + CHECK(LLStringUtil::precedesDict(lhs_str, rhs_str)); lhs_str = "PROgram12Files"; rhs_str = "PROgram12Files"; - ensure("compareDict 2 failed", LLStringUtil::compareDict(lhs_str, rhs_str) == 0); - ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); + CHECK(LLStringUtil::compareDict(lhs_str, rhs_str) == 0); + CHECK_FALSE(LLStringUtil::precedesDict(lhs_str, rhs_str)); lhs_str = "PROgram12Files"; rhs_str = "PROgRAM12FILES"; - ensure("compareDict 3 failed", LLStringUtil::compareDict(lhs_str, rhs_str) > 0); - ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); + CHECK(LLStringUtil::compareDict(lhs_str, rhs_str) > 0); + CHECK_FALSE(LLStringUtil::precedesDict(lhs_str, rhs_str)); // Uppercase sorts first, which is a tertiary-level ordering. The // caseless form drops that level and so sees no difference at all. - ensure("compareDictInsensitive case", LLStringUtil::compareDictInsensitive(lhs_str, rhs_str) == 0); - ensure("compareDict case", LLStringUtil::compareDict(std::string("abc"), std::string("ABC")) > 0); - ensure("compareDict case 2", LLStringUtil::compareDict(std::string("Apple"), std::string("apple")) < 0); + CHECK(LLStringUtil::compareDictInsensitive(lhs_str, rhs_str) == 0); + CHECK(LLStringUtil::compareDict(std::string("abc"), std::string("ABC")) > 0); + CHECK(LLStringUtil::compareDict(std::string("Apple"), std::string("apple")) < 0); // Digit runs compare as the numbers they spell. - ensure("natural 2 before 10", - LLStringUtil::compareDict(std::string("item2"), std::string("item10")) < 0); - ensure("natural 10 after 9", - LLStringUtil::compareDict(std::string("item10"), std::string("item9")) > 0); + UNSCOPED_INFO("natural 2 before 10"); + CHECK(LLStringUtil::compareDict(std::string("item2"), std::string("item10")) < 0); + UNSCOPED_INFO("natural 10 after 9"); + CHECK(LLStringUtil::compareDict(std::string("item10"), std::string("item9")) > 0); // An accented letter sorts beside the letter it decorates rather than // past the end of the alphabet: a < a-diaeresis < b, not merely // somewhere before z. Codepoint order put it after z. - ensure("accent after plain", - LLStringUtil::compareDict(std::string("ab"), std::string("\xC3\xA4" "b")) < 0); - ensure("accent before next letter", - LLStringUtil::compareDict(std::string("\xC3\xA4" "b"), std::string("bb")) < 0); - ensure("accent before z", - LLStringUtil::compareDict(std::string("\xC3\xA4" "b"), std::string("zb")) < 0); + UNSCOPED_INFO("accent after plain"); + CHECK(LLStringUtil::compareDict(std::string("ab"), std::string("\xC3\xA4" "b")) < 0); + UNSCOPED_INFO("accent before next letter"); + CHECK(LLStringUtil::compareDict(std::string("\xC3\xA4" "b"), std::string("bb")) < 0); + UNSCOPED_INFO("accent before z"); + CHECK(LLStringUtil::compareDict(std::string("\xC3\xA4" "b"), std::string("zb")) < 0); // ... and case-insensitive still keeps accents apart. - ensure("caseless keeps accents", - LLStringUtil::compareDictInsensitive(std::string("ab"), std::string("\xC3\xA4" "b")) < 0); + UNSCOPED_INFO("caseless keeps accents"); + CHECK(LLStringUtil::compareDictInsensitive(std::string("ab"), std::string("\xC3\xA4" "b")) < 0); } - template<> template<> - void string_index_object_t::test<29>() + TEST_CASE("LLString: copy copies no more than the destination size", "[LLString]") { char str1[] = "First String..."; char str2[100]; LLStringUtil::copy(str2, str1, 100); - ensure("LLStringUtil::copy with enough dest length failed", strcmp(str2, str1) == 0); + UNSCOPED_INFO("enough room"); + CHECK(std::string_view(str2) == std::string_view(str1)); LLStringUtil::copy(str2, str1, sizeof("First")); - ensure("LLStringUtil::copy with less dest length failed", strcmp(str2, "First") == 0); + UNSCOPED_INFO("too little room"); + CHECK(std::string_view(str2) == "First"); } - template<> template<> - void string_index_object_t::test<30>() + TEST_CASE("LLString: copyInto inserts at an offset and appends at the end", "[LLString]") { std::string str1 = "This is the sentence..."; std::string str2 = "This is the "; @@ -636,15 +673,14 @@ namespace tut dest = str1; LLStringUtil::copyInto(dest, str3, str2.length()); - ensure("LLStringUtil::copyInto insert failed", dest == str4); + CHECK(dest == str4); dest = str1; LLStringUtil::copyInto(dest, str3, dest.length()); - ensure("LLStringUtil::copyInto append failed", dest == str5); + CHECK(dest == str5); } - template<> template<> - void string_index_object_t::test<31>() + TEST_CASE("LLString: strip_invalid_xml removes controls other than tab and line ends", "[LLString]") { std::string stripped; @@ -652,7 +688,8 @@ namespace tut // should not be altered. std::string simple_text = "Hello, world!"; stripped = LLStringFn::strip_invalid_xml(simple_text); - ensure("Simple text passed unchanged", stripped == simple_text); + UNSCOPED_INFO("plain text unchanged"); + CHECK(stripped == simple_text); // Control characters should be removed // except for 0x09, 0x0a, 0x0d @@ -667,8 +704,8 @@ namespace tut allowed_control_chars.push_back( (char)0x0d ); stripped = LLStringFn::strip_invalid_xml(control_chars); - ensure("Only tab, LF, CR control characters allowed", - stripped == allowed_control_chars); + UNSCOPED_INFO("only tab, LF and CR kept"); + CHECK(stripped == allowed_control_chars); // UTF-8 should be passed intact, including high byte // characters. Try Francais (with C squiggle cedilla) @@ -677,11 +714,11 @@ namespace tut french.push_back( (char)0xA7 ); french += "ais"; stripped = LLStringFn::strip_invalid_xml( french ); - ensure("UTF-8 high byte text is allowed", french == stripped ); + UNSCOPED_INFO("UTF-8 kept"); + CHECK(french == stripped); } - template<> template<> - void string_index_object_t::test<32>() + TEST_CASE("LLString: format substitutes keys and counts the substitutions", "[LLString]") { // Test LLStringUtil::format() string interpolation LLStringUtil::format_map_t fmt_map; @@ -706,15 +743,16 @@ namespace tut // Test when source string is entirely one key std::string s1 = (std::string)iter->first; std::string s2 = (std::string)iter->second; + CAPTURE(s1); subcount = LLStringUtil::format(s1, fmt_map); - ensure_equals("LLStringUtil::format: Raw interpolation result", s1, s2); + REQUIRE(s1 == s2); if (s1 == "?" || s1 == "[]") // no interp expected { - ensure_equals("LLStringUtil::format: Raw interpolation result count", 0, subcount); + REQUIRE(0 == subcount); } else { - ensure_equals("LLStringUtil::format: Raw interpolation result count", 1, subcount); + REQUIRE(1 == subcount); } } @@ -723,16 +761,17 @@ namespace tut // Test when source string is one key, duplicated std::string s1 = (std::string)iter->first; std::string s2 = (std::string)iter->second; + CAPTURE(s1); s = s1 + s1 + s1 + s1; subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Rawx4 interpolation result", s, s2 + s2 + s2 + s2); + REQUIRE(s == s2 + s2 + s2 + s2); if (s1 == "?" || s1 == "[]") // no interp expected { - ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 0, subcount); + REQUIRE(0 == subcount); } else { - ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 4, subcount); + REQUIRE(4 == subcount); } } @@ -740,55 +779,54 @@ namespace tut std::string srcs = "!!!!!!!!!!!!!!!!"; s = srcs; subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: No key test result", s, srcs); - ensure_equals("LLStringUtil::format: No key test result count", 0, subcount); + CHECK(s == srcs); + CHECK(0 == subcount); // Test when source string has no keys and is empty std::string srcs3; s = srcs3; subcount = LLStringUtil::format(s, fmt_map); - ensure("LLStringUtil::format: No key test3 result", s.empty()); - ensure_equals("LLStringUtil::format: No key test3 result count", 0, subcount); + CHECK(s.empty()); + CHECK(0 == subcount); // Test a substitution where a key is substituted with blankness std::string srcs2 = "[DELETE]"; s = srcs2; subcount = LLStringUtil::format(s, fmt_map); - ensure("LLStringUtil::format: Delete key test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Delete key test2 result count", 1, subcount); + CHECK(s.empty()); + CHECK(1 == subcount); // Test an assorted substitution std::string srcs4 = "[TRICK1][A][B][AAA][BBB][TRICK2][KEYLONGER][KEYSHORTER]?[DELETE]"; s = srcs4; subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test1 result", s, "[A]abaaabbb[A]shortAm I not a long string??"); - ensure_equals("LLStringUtil::format: Assorted Test1 result count", 9, subcount); + CHECK(s == "[A]abaaabbb[A]shortAm I not a long string??"); + CHECK(9 == subcount); // Test an assorted substitution std::string srcs5 = "[DELETE]?[KEYSHORTER][KEYLONGER][TRICK2][BBB][AAA][B][A][TRICK1]"; s = srcs5; subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "?Am I not a long string?short[A]bbbaaaba[A]"); - ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); + CHECK(s == "?Am I not a long string?short[A]bbbaaaba[A]"); + CHECK(9 == subcount); // Test on nested brackets std::string srcs6 = "[[TRICK1]][[A]][[B]][[AAA]][[BBB]][[TRICK2]][[KEYLONGER]][[KEYSHORTER]]?[[DELETE]]"; s = srcs6; subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "[[A]][a][b][aaa][bbb][[A]][short][Am I not a long string?]?[]"); - ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); + CHECK(s == "[[A]][a][b][aaa][bbb][[A]][short][Am I not a long string?]?[]"); + CHECK(9 == subcount); // Test an assorted substitution std::string srcs8 = "foo[DELETE]bar?"; s = srcs8; subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test3 result", s, "foobar?"); - ensure_equals("LLStringUtil::format: Assorted Test3 result count", 1, subcount); + CHECK(s == "foobar?"); + CHECK(1 == subcount); } - template<> template<> - void string_index_object_t::test<33>() + TEST_CASE("LLString: format with an empty map changes nothing", "[LLString]") { // Test LLStringUtil::format() string interpolation LLStringUtil::format_map_t blank_fmt_map; @@ -799,19 +837,18 @@ namespace tut std::string srcs6 = "12345"; s = srcs6; subcount = LLStringUtil::format(s, blank_fmt_map); - ensure_equals("LLStringUtil::format: Blankfmt Test1 result", s, "12345"); - ensure_equals("LLStringUtil::format: Blankfmt Test1 result count", 0, subcount); + CHECK(s == "12345"); + CHECK(0 == subcount); // Test substituting a blank string out of a blank format_map std::string srcs7; s = srcs7; subcount = LLStringUtil::format(s, blank_fmt_map); - ensure("LLStringUtil::format: Blankfmt Test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Blankfmt Test2 result count", 0, subcount); + CHECK(s.empty()); + CHECK(0 == subcount); } - template<> template<> - void string_index_object_t::test<34>() + TEST_CASE("LLString: format with an empty key in the map changes nothing", "[LLString]") { // Test that incorrect LLStringUtil::format() use does not explode. LLStringUtil::format_map_t nasty_fmt_map; @@ -824,222 +861,208 @@ namespace tut std::string srcs6 = "12345"; s = srcs6; subcount = LLStringUtil::format(s, nasty_fmt_map); - ensure_equals("LLStringUtil::format: Nastyfmt Test1 result", s, "12345"); - ensure_equals("LLStringUtil::format: Nastyfmt Test1 result count", 0, subcount); + CHECK(s == "12345"); + CHECK(0 == subcount); // Test substituting a blank string out of a nasty format_map std::string srcs7; s = srcs7; subcount = LLStringUtil::format(s, nasty_fmt_map); - ensure("LLStringUtil::format: Nastyfmt Test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Nastyfmt Test2 result count", 0, subcount); + CHECK(s.empty()); + CHECK(0 == subcount); } - template<> template<> - void string_index_object_t::test<35>() + TEST_CASE("LLString: startsWith finds a prefix", "[LLString]") { // Make sure startsWith works std::string string("anybody in there?"); std::string substr("anybody"); - ensure("startsWith works.", LLStringUtil::startsWith(string, substr)); + CHECK(LLStringUtil::startsWith(string, substr)); } - template<> template<> - void string_index_object_t::test<36>() + TEST_CASE("LLString: startsWith rejects a word that is not the prefix", "[LLString]") { // Make sure startsWith correctly fails std::string string("anybody in there?"); std::string substr("there"); - ensure("startsWith fails.", !LLStringUtil::startsWith(string, substr)); + CHECK_FALSE(LLStringUtil::startsWith(string, substr)); } - template<> template<> - void string_index_object_t::test<37>() + TEST_CASE("LLString: startsWith is false where either string is empty", "[LLString]") { // startsWith fails on empty strings std::string value("anybody in there?"); std::string empty; - ensure("empty string.", !LLStringUtil::startsWith(value, empty)); - ensure("empty substr.", !LLStringUtil::startsWith(empty, value)); - ensure("empty everything.", !LLStringUtil::startsWith(empty, empty)); + CHECK_FALSE(LLStringUtil::startsWith(value, empty)); + CHECK_FALSE(LLStringUtil::startsWith(empty, value)); + CHECK_FALSE(LLStringUtil::startsWith(empty, empty)); } - template<> template<> - void string_index_object_t::test<38>() + TEST_CASE("LLString: endsWith finds a suffix", "[LLString]") { // Make sure endsWith works correctly std::string string("anybody in there?"); std::string substr("there?"); - ensure("endsWith works.", LLStringUtil::endsWith(string, substr)); + CHECK(LLStringUtil::endsWith(string, substr)); } - template<> template<> - void string_index_object_t::test<39>() + TEST_CASE("LLString: endsWith rejects what is not the suffix", "[LLString]") { // Make sure endsWith correctly fails std::string string("anybody in there?"); std::string substr("anybody"); - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + CHECK_FALSE(LLStringUtil::endsWith(string, substr)); substr = "there"; - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + CHECK_FALSE(LLStringUtil::endsWith(string, substr)); substr = "ther?"; - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + CHECK_FALSE(LLStringUtil::endsWith(string, substr)); } - template<> template<> - void string_index_object_t::test<40>() + TEST_CASE("LLString: endsWith is false where either string is empty", "[LLString]") { // endsWith fails on empty strings std::string value("anybody in there?"); std::string empty; - ensure("empty string.", !LLStringUtil::endsWith(value, empty)); - ensure("empty substr.", !LLStringUtil::endsWith(empty, value)); - ensure("empty everything.", !LLStringUtil::endsWith(empty, empty)); + CHECK_FALSE(LLStringUtil::endsWith(value, empty)); + CHECK_FALSE(LLStringUtil::endsWith(empty, value)); + CHECK_FALSE(LLStringUtil::endsWith(empty, empty)); } - template<> template<> - void string_index_object_t::test<41>() + TEST_CASE("LLString: getTokens splits on delimiters and drops them", "[LLString]") { - set_test_name("getTokens(\"delims\")"); - ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec()); - ensure_equals("only delims", - LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec()); - ensure_equals("sequence of delims", - LLStringUtil::getTokens(",,, one ,,,", ","), StringVec{"one"}); + UNSCOPED_INFO("empty string"); + CHECK(LLStringUtil::getTokens("", " ") == StringVec()); + UNSCOPED_INFO("only delims"); + CHECK(LLStringUtil::getTokens(" \r\n ", " \r\n") == StringVec()); + UNSCOPED_INFO("sequence of delims"); + CHECK(LLStringUtil::getTokens(",,, one ,,,", ",") == StringVec{"one"}); // nat considers this a dubious implementation side effect, but I'd // hate to change it now... - ensure_equals("noncontiguous tokens", - LLStringUtil::getTokens(", ,, , one ,,,", ","), StringVec{ "", "", "one" }); - ensure_equals("space-padded tokens", - LLStringUtil::getTokens(", one , two ,", ","), StringVec{"one", "two"}); - ensure_equals("no delims", LLStringUtil::getTokens("one", ","), StringVec{ "one" }); + UNSCOPED_INFO("noncontiguous tokens"); + CHECK(LLStringUtil::getTokens(", ,, , one ,,,", ",") == StringVec{ "", "", "one" }); + UNSCOPED_INFO("space-padded tokens"); + CHECK(LLStringUtil::getTokens(", one , two ,", ",") == StringVec{"one", "two"}); + UNSCOPED_INFO("no delims"); + CHECK(LLStringUtil::getTokens("one", ",") == StringVec{ "one" }); } // Shorthand for verifying that getTokens() behaves the same when you // don't pass a string of escape characters, when you pass an empty string // (different overloads), and when you pass a string of characters that // aren't actually present. - static void ensure_getTokens(const std::string& desc, - const std::string& string, - const std::string& drop_delims, - const std::string& keep_delims, - const std::string& quotes, - const std::vector& expect) + static void require_tokens(const std::string& desc, + const std::string& string, + const std::string& drop_delims, + const std::string& keep_delims, + const std::string& quotes, + const std::vector& expect) { - ensure_equals(desc + " - no esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes), + INFO(desc); + REQUIRE(LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes) == expect); - ensure_equals(desc + " - empty esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""), + REQUIRE(LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "") == expect); - ensure_equals(desc + " - unused esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"), + REQUIRE(LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!") == expect); } - static void ensure_getTokens(const std::string& desc, - const std::string& string, - const std::string& drop_delims, - const std::string& keep_delims, - const std::vector& expect) + static void require_tokens(const std::string& desc, + const std::string& string, + const std::string& drop_delims, + const std::string& keep_delims, + const std::vector& expect) { - ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect); + require_tokens(desc, string, drop_delims, keep_delims, "", expect); } - template<> template<> - void string_index_object_t::test<42>() + TEST_CASE("LLString: getTokens keeps delimiters and handles quotes and escapes", "[LLString]") { - set_test_name("getTokens(\"delims\", etc.)"); // Signatures to test in this method: // getTokens(string, drop_delims, keep_delims [, quotes [, escapes]]) // If you omit keep_delims, you get the older function (test above). // cases like the getTokens(string, delims) tests above - ensure_getTokens("empty string", "", " ", "", StringVec()); - ensure_getTokens("only delims", - " \r\n ", " \r\n", "", StringVec()); - ensure_getTokens("sequence of delims", - ",,, one ,,,", ", ", "", StringVec{"one"}); + require_tokens("empty string", "", " ", "", StringVec()); + require_tokens("only delims", + " \r\n ", " \r\n", "", StringVec()); + require_tokens("sequence of delims", + ",,, one ,,,", ", ", "", StringVec{"one"}); // Note contrast with the case in the previous method - ensure_getTokens("noncontiguous tokens", - ", ,, , one ,,,", ", ", "", StringVec{"one"}); - ensure_getTokens("space-padded tokens", - ", one , two ,", ", ", "", - StringVec{"one", "two"}); - ensure_getTokens("no delims", "one", ",", "", StringVec{ "one" }); + require_tokens("noncontiguous tokens", + ", ,, , one ,,,", ", ", "", StringVec{"one"}); + require_tokens("space-padded tokens", + ", one , two ,", ", ", "", + StringVec{"one", "two"}); + require_tokens("no delims", "one", ",", "", StringVec{ "one" }); // drop_delims vs. keep_delims - ensure_getTokens("arithmetic", - " ab+def / xx* yy ", " ", "+-*/", { "ab", "+", "def", "/", "xx", "*", "yy" }); + require_tokens("arithmetic", + " ab+def / xx* yy ", " ", "+-*/", { "ab", "+", "def", "/", "xx", "*", "yy" }); // quotes - ensure_getTokens("no quotes", - "She said, \"Don't go.\"", " ", ",", "", { "She", "said", ",", "\"Don't", "go.\"" }); - ensure_getTokens("quotes", - "She said, \"Don't go.\"", " ", ",", "\"", { "She", "said", ",", "Don't go." }); - ensure_getTokens("quotes and delims", - "run c:/'Documents and Settings'/someone", " ", "", "'", - { "run", "c:/Documents and Settings/someone" }); - ensure_getTokens("unmatched quote", - "baby don't leave", " ", "", "'", { "baby", "don't", "leave" }); - ensure_getTokens("adjacent quoted", - "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'", { "abcdef \"ghijkl' mnopqr" }); - ensure_getTokens("quoted empty string", - "--set SomeVar ''", " ", "", "'", { "--set", "SomeVar", "" }); + require_tokens("no quotes", + "She said, \"Don't go.\"", " ", ",", "", { "She", "said", ",", "\"Don't", "go.\"" }); + require_tokens("quotes", + "She said, \"Don't go.\"", " ", ",", "\"", { "She", "said", ",", "Don't go." }); + require_tokens("quotes and delims", + "run c:/'Documents and Settings'/someone", " ", "", "'", + { "run", "c:/Documents and Settings/someone" }); + require_tokens("unmatched quote", + "baby don't leave", " ", "", "'", { "baby", "don't", "leave" }); + require_tokens("adjacent quoted", + "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'", { "abcdef \"ghijkl' mnopqr" }); + require_tokens("quoted empty string", + "--set SomeVar ''", " ", "", "'", { "--set", "SomeVar", "" }); // escapes // Don't use backslash as an escape for these tests -- you'll go nuts // between the C++ string scanner and getTokens() escapes. Test with // something else! - ensure_equals("escaped delims", - LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"), + UNSCOPED_INFO("escaped delims"); + CHECK(LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^") == StringVec{ " a", "-", "dog-gone phrase" }); - ensure_equals("escaped quotes", - LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"), + UNSCOPED_INFO("escaped quotes"); + CHECK(LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^") == StringVec{ "say:", "this isn't working." }); - ensure_equals("escaped escape", - LLStringUtil::getTokens("want x^^2", " ", "", "", "^"), StringVec{ "want", "x^2" }); - ensure_equals("escape at end", LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"), StringVec{ "it's up", "there^" }); + UNSCOPED_INFO("escaped escape"); + CHECK(LLStringUtil::getTokens("want x^^2", " ", "", "", "^") == StringVec{ "want", "x^2" }); + UNSCOPED_INFO("escape at end"); + CHECK(LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^") == StringVec{ "it's up", "there^" }); } - template<> template<> - void string_index_object_t::test<43>() + TEST_CASE("LLString: xml_encode and xml_decode escape element and attribute specials", "[LLString]") { - set_test_name("xml_encode/xml_decode"); - - ensure_equals("encode passthrough", - LLStringFn::xml_encode("plain text"), "plain text"); - ensure_equals("encode element specials", - LLStringFn::xml_encode("ac&d\"e'f"), + UNSCOPED_INFO("encode passthrough"); + CHECK(LLStringFn::xml_encode("plain text") == "plain text"); + UNSCOPED_INFO("encode element specials"); + CHECK(LLStringFn::xml_encode("ac&d\"e'f") == "a<b>c&d\"e'f"); - ensure_equals("encode attribute specials", - LLStringFn::xml_encode("ac&d\"e'f", true), + UNSCOPED_INFO("encode attribute specials"); + CHECK(LLStringFn::xml_encode("ac&d\"e'f", true) == "a<b>c&d"e'f"); - ensure_equals("encode empty", LLStringFn::xml_encode(""), ""); + UNSCOPED_INFO("encode empty"); + CHECK(LLStringFn::xml_encode("") == ""); - ensure_equals("decode element specials", - LLStringFn::xml_decode("a<b>c&d"e'f"), + UNSCOPED_INFO("decode element specials"); + CHECK(LLStringFn::xml_decode("a<b>c&d"e'f") == "ac&d"e'f"); - ensure_equals("decode attribute specials", - LLStringFn::xml_decode("a<b>c&d"e'f", true), + UNSCOPED_INFO("decode attribute specials"); + CHECK(LLStringFn::xml_decode("a<b>c&d"e'f", true) == "ac&d\"e'f"); - ensure_equals("decode unknown entity untouched", - LLStringFn::xml_decode("a&foo;b&"), "a&foo;b&"); + UNSCOPED_INFO("decode unknown entity untouched"); + CHECK(LLStringFn::xml_decode("a&foo;b&") == "a&foo;b&"); // each literal decodes exactly once: &lt; is the text "<", // not a doubly-encoded "<" - ensure_equals("decode does not cascade", - LLStringFn::xml_decode("a&lt;b"), "a<b"); - ensure_equals("round trip nested entity text", - LLStringFn::xml_decode(LLStringFn::xml_encode("a<b")), + UNSCOPED_INFO("decode does not cascade"); + CHECK(LLStringFn::xml_decode("a&lt;b") == "a<b"); + UNSCOPED_INFO("round trip nested entity text"); + CHECK(LLStringFn::xml_decode(LLStringFn::xml_encode("a<b")) == "a<b"); } - template<> template<> - void string_index_object_t::test<44>() + TEST_CASE("LLString: format leaves unsubstitutable strings alone", "[LLString]") { - set_test_name("format leaves unsubstitutable strings alone"); - LLStringUtil::format_map_t fmt_map; fmt_map["FOO"] = "bar"; @@ -1060,80 +1083,49 @@ namespace tut for (const char* src : untouched) { + CAPTURE(src); std::string s(src); - ensure_equals(std::string("format_map count: ") + src, - 0, LLStringUtil::format(s, fmt_map)); - ensure_equals(std::string("format_map result: ") + src, s, std::string(src)); + REQUIRE(0 == LLStringUtil::format(s, fmt_map)); + REQUIRE(s == std::string(src)); std::string s2(src); - ensure_equals(std::string("LLSD count: ") + src, - 0, LLStringUtil::format(s2, sd_map)); - ensure_equals(std::string("LLSD result: ") + src, s2, std::string(src)); + REQUIRE(0 == LLStringUtil::format(s2, sd_map)); + REQUIRE(s2 == std::string(src)); } // A real substitution still fires, and the surrounding text — including // brackets that are not a key — survives it. std::string s("no [FOO] here [MISSING] -=[Stylized Name]=-"); - ensure_equals("mixed count", 1, LLStringUtil::format(s, fmt_map)); - ensure_equals("mixed result", s, + CHECK(1 == LLStringUtil::format(s, fmt_map)); + CHECK(s == std::string("no bar here [MISSING] -=[Stylized Name]=-")); } - template<> template<> - void string_index_object_t::test<48>() + // Capitalising a byte can only ever reach ASCII, so the narrow form works + // in codepoints. + TEST_CASE("LLString: capitalize works in codepoints and not bytes", "[LLString]") { - set_test_name("format over two maps reads the first's value where both have a key, as a merge with the first kept would"); - - LLStringUtil::format_map_t first; - first["[APP]"] = "Alchemy"; - first["[BOTH]"] = "first"; - first["[BARE]"] = "bracketed in the first"; - first["datetime"] = "0"; - LLStringUtil::format_map_t second; - second["[NAME]"] = "Rye"; - second["[BOTH]"] = "second"; - second["BARE"] = "bare in the second"; - second["[COUNT]"] = "3.14159"; - - // A bare key is found before a bracketed one whichever map has it, - // as the merge's one lookup order had it. - const char* const text = "[APP] for [NAME]: [BOTH], [BARE], [COUNT,number,2], [MISSING]"; - std::string s(text); - ensure_equals("five put in", 5, LLStringUtil::format(s, first, second)); - ensure_equals("from either, the first first, a number formatted, what neither has left", s, - std::string("Alchemy for Rye: first, bare in the second, 3.14, [MISSING]")); - - // The same as the merge would give. - LLStringUtil::format_map_t merged = first; - merged.insert(second.begin(), second.end()); - std::string m(text); - LLStringUtil::format(m, merged); - ensure_equals("as the merge", s, m); + std::string str_val("hello world-again_now"); + LLStringUtil::capitalize(str_val); + UNSCOPED_INFO("ascii words"); + CHECK(str_val == "Hello World-Again_Now"); - // The maps the other way about: the second's value where both have - // the key. - std::string r("[BOTH]"); - ensure_equals("one put in", 1, LLStringUtil::format(r, second, first)); - ensure_equals("the other first", r, std::string("second")); + // U+00E9 -> U+00C9, which uppercasing one byte at a time could not do. + str_val = "\xC3\xA9" "cole"; + LLStringUtil::capitalize(str_val); + UNSCOPED_INFO("accented first letter"); + CHECK(str_val == "\xC3\x89" "cole"); - std::string plain("nothing bracketed"); - ensure_equals("nothing to do", 0, LLStringUtil::format(plain, first, second)); - ensure_equals("and nothing done", plain, std::string("nothing bracketed")); + // The word rule is the caller's and still only sees these three. + str_val = "a b-c_d.e"; + LLStringUtil::capitalize(str_val); + UNSCOPED_INFO("dot is not a separator"); + CHECK(str_val == "A B-C_D.e"); - // A map over an LLSD map, as a notification's substitutions under - // the defaults: the map first, the LLSD where the map has nothing, - // and a datetime the LLSD lacks counted from the epoch as the LLSD - // form has always had it. - LLSD sd; - sd["NAME"] = "Rye"; - sd["[BOTH]"] = "the notification's"; - std::string over("[APP] for [NAME]: [BOTH]"); - ensure_equals("three put in", 3, LLStringUtil::format(over, first, sd)); - ensure_equals("the map first, the LLSD after", over, std::string("Alchemy for Rye: first")); - LLSD not_a_map; - std::string alone("[APP] [NAME]"); - ensure_equals("with no LLSD map, the map alone", 1, LLStringUtil::format(alone, first, not_a_map)); - ensure_equals("what the map had", alone, std::string("Alchemy [NAME]")); + str_val = ""; + LLStringUtil::capitalize(str_val); + UNSCOPED_INFO("empty"); + CHECK(str_val == ""); } // Both views replaceString takes are allowed to point into the string it is @@ -1141,16 +1133,15 @@ namespace tut // buffer under them. Taking them by value used to make that safe without // saying so; a view does not, so the guard is the only thing holding it up // and nothing was watching the guard. - template<> template<> - void string_index_object_t::test<46>() + TEST_CASE("LLString: replaceString takes views into the string it replaces in", "[LLString]") { // replacement views the subject: the classic case, and the one the // original guard covered. { std::string s("abcabc"); LLStringUtil::replaceString(s, "b", std::string_view(s).substr(0, 3)); - ensure_equals("replacement viewing the subject", - s, std::string("aabccaabcc")); + UNSCOPED_INFO("replacement viewing the subject"); + CHECK(s == std::string("aabccaabcc")); } // target views the subject. This one is read on every turn of the loop, @@ -1158,7 +1149,8 @@ namespace tut { std::string s("xyxyxy"); LLStringUtil::replaceString(s, std::string_view(s).substr(0, 2), "Q"); - ensure_equals("target viewing the subject", s, std::string("QQQ")); + UNSCOPED_INFO("target viewing the subject"); + CHECK(s == std::string("QQQ")); } // Both at once, and a replacement long enough to force the subject to @@ -1168,34 +1160,35 @@ namespace tut LLStringUtil::replaceString(s, std::string_view(s).substr(1, 1), std::string_view(s).substr(0, 1)); - ensure_equals("both viewing the subject", s, std::string("aabac")); + UNSCOPED_INFO("both viewing the subject"); + CHECK(s == std::string("aabac")); } // And the ordinary path still works. { std::string s("one two one"); LLStringUtil::replaceString(s, "one", "three"); - ensure_equals("unrelated arguments", s, std::string("three two three")); + UNSCOPED_INFO("unrelated arguments"); + CHECK(s == std::string("three two three")); } { std::string s("aaa"); LLStringUtil::replaceString(s, "a", "aa"); - ensure_equals("replacement containing the target terminates", - s, std::string("aaaaaa")); + UNSCOPED_INFO("replacement containing the target terminates"); + CHECK(s == std::string("aaaaaa")); } } // copyInto has the same shape: src may view dst, the append path appends // from it, and the insert path assigns dst out from under it first. - template<> template<> - void string_index_object_t::test<47>() + TEST_CASE("LLString: copyInto takes a source that views the destination", "[LLString]") { // Append from a view of the destination. { std::string s("abcd"); LLStringUtil::copyInto(s, std::string_view(s).substr(0, 2), s.length()); - ensure_equals("append from a view of the destination", - s, std::string("abcdab")); + UNSCOPED_INFO("append from a view of the destination"); + CHECK(s == std::string("abcdab")); } // Insert from a view of the destination, at the front, so the assign @@ -1203,16 +1196,16 @@ namespace tut { std::string s("abcd"); LLStringUtil::copyInto(s, std::string_view(s).substr(2, 2), 0); - ensure_equals("insert from a view of the destination", - s, std::string("cdabcd")); + UNSCOPED_INFO("insert from a view of the destination"); + CHECK(s == std::string("cdabcd")); } // A view of the whole of it, long enough that the growth reallocates. { std::string s("0123456789"); LLStringUtil::copyInto(s, std::string_view(s), 5); - ensure_equals("insert the whole of the destination into itself", - s, std::string("01234012345678956789")); + UNSCOPED_INFO("insert the whole of the destination into itself"); + CHECK(s == std::string("01234012345678956789")); } // Offset at the end is the append fast path; offset past it is not a @@ -1220,7 +1213,73 @@ namespace tut { std::string s("head"); LLStringUtil::copyInto(s, std::string("-tail"), s.length()); - ensure_equals("unrelated source appends", s, std::string("head-tail")); + UNSCOPED_INFO("unrelated source appends"); + CHECK(s == std::string("head-tail")); } } + + TEST_CASE("LLString: format over two maps reads the first's value as a merge would", "[LLString]") + { + LLStringUtil::format_map_t first; + first["[APP]"] = "Alchemy"; + first["[BOTH]"] = "first"; + first["[BARE]"] = "bracketed in the first"; + first["datetime"] = "0"; + LLStringUtil::format_map_t second; + second["[NAME]"] = "Rye"; + second["[BOTH]"] = "second"; + second["BARE"] = "bare in the second"; + second["[COUNT]"] = "3.14159"; + + // A bare key is found before a bracketed one whichever map has it, + // as the merge's one lookup order had it. + const char* const text = "[APP] for [NAME]: [BOTH], [BARE], [COUNT,number,2], [MISSING]"; + std::string s(text); + UNSCOPED_INFO("five put in"); + CHECK(5 == LLStringUtil::format(s, first, second)); + UNSCOPED_INFO("from either, the first first, a number formatted, what neither has left"); + CHECK(s == + std::string("Alchemy for Rye: first, bare in the second, 3.14, [MISSING]")); + + // The same as the merge would give. + LLStringUtil::format_map_t merged = first; + merged.insert(second.begin(), second.end()); + std::string m(text); + LLStringUtil::format(m, merged); + UNSCOPED_INFO("as the merge"); + CHECK(s == m); + + // The maps the other way about: the second's value where both have + // the key. + std::string r("[BOTH]"); + UNSCOPED_INFO("one put in"); + CHECK(1 == LLStringUtil::format(r, second, first)); + UNSCOPED_INFO("the other first"); + CHECK(r == std::string("second")); + + std::string plain("nothing bracketed"); + UNSCOPED_INFO("nothing to do"); + CHECK(0 == LLStringUtil::format(plain, first, second)); + UNSCOPED_INFO("and nothing done"); + CHECK(plain == std::string("nothing bracketed")); + + // A map over an LLSD map, as a notification's substitutions under + // the defaults: the map first, the LLSD where the map has nothing, + // and a datetime the LLSD lacks counted from the epoch as the LLSD + // form has always had it. + LLSD sd; + sd["NAME"] = "Rye"; + sd["[BOTH]"] = "the notification's"; + std::string over("[APP] for [NAME]: [BOTH]"); + UNSCOPED_INFO("three put in"); + CHECK(3 == LLStringUtil::format(over, first, sd)); + UNSCOPED_INFO("the map first, the LLSD after"); + CHECK(over == std::string("Alchemy for Rye: first")); + LLSD not_a_map; + std::string alone("[APP] [NAME]"); + UNSCOPED_INFO("with no LLSD map, the map alone"); + CHECK(1 == LLStringUtil::format(alone, first, not_a_map)); + UNSCOPED_INFO("what the map had"); + CHECK(alone == std::string("Alchemy [NAME]")); + } } diff --git a/indra/llcommon/tests/llstring_utf_test.cpp b/indra/llcommon/tests/llstring_utf_test.cpp index c53c7d0d4f..f5b9da603c 100644 --- a/indra/llcommon/tests/llstring_utf_test.cpp +++ b/indra/llcommon/tests/llstring_utf_test.cpp @@ -37,7 +37,7 @@ #include "linden_common.h" #include "../llstring.h" -#include "../test/lltut.h" +#include #include #include @@ -181,31 +181,31 @@ namespace return removed; } - // llutf16string element types have no std::ostream operator<<, so - // ensure_equals fails to instantiate its failure formatter. Compare by - // value and format diagnostics ourselves. - void ensure_wstring_equals(const std::string& msg, - const std::u32string& actual, - const std::u32string& expected) + // char32_t has no std::ostream operator<<, so a failed comparison of + // two std::u32strings could not show them. Compare by value and format + // diagnostics ourselves. + void require_same_u32(const std::string& what, + const std::u32string& actual, + const std::u32string& expected) { if (actual == expected) return; std::ostringstream oss; - oss << msg << ": std::u32string mismatch (actual.size=" << actual.size() + oss << what << ": std::u32string mismatch (actual.size=" << actual.size() << " expected.size=" << expected.size() << ")\n actual: "; oss << std::hex << std::uppercase; for (llwchar c : actual) oss << "U+" << (U32)c << ' '; oss << "\n expect: "; for (llwchar c : expected) oss << "U+" << (U32)c << ' '; - throw tut::failure(oss.str()); + FAIL(oss.str()); } } -namespace tut +namespace { struct llstring_utf_data {}; - // Tests are numbered by category: + // The cases come in categories, which TUT numbered: // 1x conversion (utf8 <-> wstring/utf16, basic round-trips) // 2x validation (well-formed input pins) // 3x case (ICU-backed casing) @@ -219,109 +219,96 @@ namespace tut // 11x utf8str helpers (substChar/makeASCII/removeCRLF/split/preview) // 12x utf8 walkers (byte-offset segmentation against its wide twin) // 14x trim (Unicode whitespace, and what is not whitespace) - // The TUT default registers only test<1>..test<50>, but the explicit - // test_group<..., 150> below raises that ceiling. Keep this index in - // sync with categories used below. - typedef test_group llstring_utf_t; - typedef llstring_utf_t::object llstring_utf_object_t; - tut::llstring_utf_t tut_llstring_utf("LLStringUTF"); + // Keep this index in sync with the categories used below. // --------------------------------------------------------------- // round-trip coverage // --------------------------------------------------------------- // Pure ASCII: all six conversion directions must round-trip byte-for-byte. - template<> template<> - void llstring_utf_object_t::test<1>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: ASCII converts in every direction", "[LLStringUTF]") { const std::string ascii = "Hello, World!"; const std::u32string wascii (ascii.begin(), ascii.end()); const llutf16string u16ascii(ascii.begin(), ascii.end()); - ensure_wstring_equals ("utf8->wstring ASCII", to_u32 (ascii), wascii); - ensure_equals ("wstring->utf8 ASCII", to_utf8 (wascii), ascii); - ensure_equals ("utf16->utf8 ASCII", utf16str_to_utf8str(u16ascii), ascii); + require_same_u32("utf8->wstring ASCII", to_u32 (ascii), wascii); + CHECK(to_utf8 (wascii) == ascii); + CHECK(utf16str_to_utf8str(u16ascii) == ascii); } // Latin-1 BMP: 2-byte UTF-8 (é = U+00E9 → C3 A9). - template<> template<> - void llstring_utf_object_t::test<2>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a two-byte character converts in every direction", "[LLStringUTF]") { const std::string utf8 = "H\xC3\xA9llo"; const std::u32string w = { (llwchar)'H', (llwchar)0x00E9, (llwchar)'l', (llwchar)'l', (llwchar)'o' }; const llutf16string u16 = { (char16_t)'H', (char16_t)0x00E9, (char16_t)'l', (char16_t)'l', (char16_t)'o' }; - ensure_wstring_equals ("utf8->wstring 2-byte", to_u32 (utf8), w); - ensure_equals ("wstring->utf8 2-byte", to_utf8 (w), utf8); - ensure_equals ("utf16->utf8 2-byte", utf16str_to_utf8str(u16), utf8); + require_same_u32("utf8->wstring 2-byte", to_u32 (utf8), w); + CHECK(to_utf8 (w) == utf8); + CHECK(utf16str_to_utf8str(u16) == utf8); } // CJK BMP: 3-byte UTF-8 (日 = U+65E5 → E6 97 A5, 本 = U+672C → E6 9C AC). - template<> template<> - void llstring_utf_object_t::test<3>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: three-byte CJK converts in every direction", "[LLStringUTF]") { const std::string utf8 = "\xE6\x97\xA5\xE6\x9C\xAC"; const std::u32string w = { (llwchar)0x65E5, (llwchar)0x672C }; const llutf16string u16 = { (char16_t)0x65E5, (char16_t)0x672C }; - ensure_wstring_equals ("utf8->wstring 3-byte", to_u32 (utf8), w); - ensure_equals ("wstring->utf8 3-byte", to_utf8 (w), utf8); - ensure_equals ("utf16->utf8 3-byte", utf16str_to_utf8str(u16), utf8); + require_same_u32("utf8->wstring 3-byte", to_u32 (utf8), w); + CHECK(to_utf8 (w) == utf8); + CHECK(utf16str_to_utf8str(u16) == utf8); } // Astral: 4-byte UTF-8 (U+1F680 rocket → F0 9F 9A 80; UTF-16 surrogate pair D83D DE80). - template<> template<> - void llstring_utf_object_t::test<4>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: an astral character converts in every direction", "[LLStringUTF]") { const std::string utf8 = "\xF0\x9F\x9A\x80"; const std::u32string w = { (llwchar)0x1F680 }; const llutf16string u16 = { (char16_t)0xD83D, (char16_t)0xDE80 }; - ensure_wstring_equals ("utf8->wstring astral", to_u32 (utf8), w); - ensure_equals ("wstring->utf8 astral", to_utf8 (w), utf8); - ensure_equals ("utf16->utf8 astral", utf16str_to_utf8str(u16), utf8); + require_same_u32("utf8->wstring astral", to_u32 (utf8), w); + CHECK(to_utf8 (w) == utf8); + CHECK(utf16str_to_utf8str(u16) == utf8); } // Mixed: ASCII + 3-byte BMP + 4-byte astral. Uses explicit string-literal // concatenation between astral bytes and the trailing 'B' so \xA5B and // \x80B don't eat an extra hex digit. - template<> template<> - void llstring_utf_object_t::test<5>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: mixed one- three- and four-byte text converts in every direction", "[LLStringUTF]") { const std::string utf8 = "A\xE6\x97\xA5\xF0\x9F\x9A\x80" "B"; const std::u32string w = { (llwchar)'A', (llwchar)0x65E5, (llwchar)0x1F680, (llwchar)'B' }; const llutf16string u16 = { (char16_t)'A', (char16_t)0x65E5, (char16_t)0xD83D, (char16_t)0xDE80, (char16_t)'B' }; - ensure_wstring_equals ("utf8->wstring mixed", to_u32 (utf8), w); - ensure_equals ("wstring->utf8 mixed", to_utf8 (w), utf8); - ensure_equals ("utf16->utf8 mixed", utf16str_to_utf8str(u16), utf8); + require_same_u32("utf8->wstring mixed", to_u32 (utf8), w); + CHECK(to_utf8 (w) == utf8); + CHECK(utf16str_to_utf8str(u16) == utf8); } // Empty inputs produce empty outputs in every direction. - template<> template<> - void llstring_utf_object_t::test<6>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: empty input converts to empty output in every direction", "[LLStringUTF]") { - ensure_equals("utf8->wstring empty", to_u32 (std::string()).size(), size_t(0)); - ensure_equals("wstring->utf8 empty", to_utf8 (std::u32string()), std::string()); - ensure_equals("utf16->utf8 empty", utf16str_to_utf8str(llutf16string()), std::string()); + CHECK(to_u32 (std::string()).size() == size_t(0)); + CHECK(to_utf8 (std::u32string()) == std::string()); + CHECK(utf16str_to_utf8str(llutf16string()) == std::string()); } // Round-trip via rawstr_to_utf8 (utf8 -> std::u32string -> utf8) preserves valid input. - template<> template<> - void llstring_utf_object_t::test<7>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: rawstr_to_utf8 leaves valid UTF-8 as it is", "[LLStringUTF]") { const std::string mixed = "A\xE6\x97\xA5\xF0\x9F\x9A\x80" "B"; - ensure_equals("rawstr_to_utf8 preserves valid utf8", rawstr_to_utf8(mixed), mixed); + CHECK(rawstr_to_utf8(mixed) == mixed); } // utf8 <-> u8 reinterpret (not conversion) preserves bytes. - template<> template<> - void llstring_utf_object_t::test<8>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: str_to_u8str and u8str_to_str keep the bytes", "[LLStringUTF]") { const std::string bytes = "\xF0\x9F\x9A\x80"; const std::u8string u8 = str_to_u8str(bytes); - ensure_equals("str_to_u8str size", u8.size(), bytes.size()); - ensure_equals("u8 round-trip", u8str_to_str(u8), bytes); + CHECK(u8.size() == bytes.size()); + CHECK(u8str_to_str(u8) == bytes); } // --------------------------------------------------------------- @@ -329,67 +316,63 @@ namespace tut // --------------------------------------------------------------- // wchar_to_utf8chars covers 1, 2, 3, 4-byte canonical forms. - template<> template<> - void llstring_utf_object_t::test<10>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: wchar_to_utf8chars writes one- to four-byte canonical forms", "[LLStringUTF]") { char buf[8]; std::memset(buf, 0xAA, sizeof(buf)); - ensure_equals("'A' len", wchar_to_utf8chars(0x41, buf), std::ptrdiff_t(1)); - ensure_equals("'A' b0", (U32)(U8)buf[0], U32(0x41)); + CHECK(wchar_to_utf8chars(0x41, buf) == std::ptrdiff_t(1)); + CHECK((U32)(U8)buf[0] == U32(0x41)); - ensure_equals("é len", wchar_to_utf8chars(0x00E9, buf), std::ptrdiff_t(2)); - ensure_equals("é b0", (U32)(U8)buf[0], U32(0xC3)); - ensure_equals("é b1", (U32)(U8)buf[1], U32(0xA9)); + CHECK(wchar_to_utf8chars(0x00E9, buf) == std::ptrdiff_t(2)); + CHECK((U32)(U8)buf[0] == U32(0xC3)); + CHECK((U32)(U8)buf[1] == U32(0xA9)); - ensure_equals("日 len", wchar_to_utf8chars(0x65E5, buf), std::ptrdiff_t(3)); - ensure_equals("日 b0", (U32)(U8)buf[0], U32(0xE6)); - ensure_equals("日 b1", (U32)(U8)buf[1], U32(0x97)); - ensure_equals("日 b2", (U32)(U8)buf[2], U32(0xA5)); + CHECK(wchar_to_utf8chars(0x65E5, buf) == std::ptrdiff_t(3)); + CHECK((U32)(U8)buf[0] == U32(0xE6)); + CHECK((U32)(U8)buf[1] == U32(0x97)); + CHECK((U32)(U8)buf[2] == U32(0xA5)); - ensure_equals("🚀 len", wchar_to_utf8chars(0x1F680, buf), std::ptrdiff_t(4)); - ensure_equals("🚀 b0", (U32)(U8)buf[0], U32(0xF0)); - ensure_equals("🚀 b1", (U32)(U8)buf[1], U32(0x9F)); - ensure_equals("🚀 b2", (U32)(U8)buf[2], U32(0x9A)); - ensure_equals("🚀 b3", (U32)(U8)buf[3], U32(0x80)); + CHECK(wchar_to_utf8chars(0x1F680, buf) == std::ptrdiff_t(4)); + CHECK((U32)(U8)buf[0] == U32(0xF0)); + CHECK((U32)(U8)buf[1] == U32(0x9F)); + CHECK((U32)(U8)buf[2] == U32(0x9A)); + CHECK((U32)(U8)buf[3] == U32(0x80)); } // wchar_to_utf8chars current behaviour: emits 5 and 6-byte legacy // encodings for codepoints > U+10FFFF. simdutf rejects these — test // pins today's behaviour so the migration change is visible. - template<> template<> - void llstring_utf_object_t::test<11>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: wchar_to_utf8chars still writes five- and six-byte forms", "[LLStringUTF]") { char buf[8]; std::memset(buf, 0xAA, sizeof(buf)); - ensure_equals("5-byte len", wchar_to_utf8chars(0x00200000, buf), std::ptrdiff_t(5)); - ensure_equals("5-byte lead", (U32)((U8)buf[0] & 0xF8), U32(0xF8)); + CHECK(wchar_to_utf8chars(0x00200000, buf) == std::ptrdiff_t(5)); + CHECK((U32)((U8)buf[0] & 0xF8) == U32(0xF8)); std::memset(buf, 0xAA, sizeof(buf)); - ensure_equals("6-byte len", wchar_to_utf8chars(0x04000000, buf), std::ptrdiff_t(6)); - ensure_equals("6-byte lead", (U32)((U8)buf[0] & 0xFC), U32(0xFC)); + CHECK(wchar_to_utf8chars(0x04000000, buf) == std::ptrdiff_t(6)); + CHECK((U32)((U8)buf[0] & 0xFC) == U32(0xFC)); } // --------------------------------------------------------------- // length / count helpers // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<20>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: wchar_utf8_length counts one to six bytes", "[LLStringUTF]") { - ensure_equals("wchar_utf8_length ASCII", wchar_utf8_length(0x00000041), S32(1)); - ensure_equals("wchar_utf8_length 2-byte", wchar_utf8_length(0x000000E9), S32(2)); - ensure_equals("wchar_utf8_length 3-byte", wchar_utf8_length(0x000065E5), S32(3)); - ensure_equals("wchar_utf8_length 4-byte", wchar_utf8_length(0x0001F680), S32(4)); - ensure_equals("wchar_utf8_length 5-byte", wchar_utf8_length(0x00200000), S32(5)); - ensure_equals("wchar_utf8_length 6-byte", wchar_utf8_length(0x04000000), S32(6)); + CHECK(wchar_utf8_length(0x00000041) == S32(1)); + CHECK(wchar_utf8_length(0x000000E9) == S32(2)); + CHECK(wchar_utf8_length(0x000065E5) == S32(3)); + CHECK(wchar_utf8_length(0x0001F680) == S32(4)); + CHECK(wchar_utf8_length(0x00200000) == S32(5)); + CHECK(wchar_utf8_length(0x04000000) == S32(6)); } #if LL_WINDOWS // wide_wstring_length is Windows-only because it assumes std::wstring holds UTF-16. - template<> template<> - void llstring_utf_object_t::test<24>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: wide_wstring_length counts a surrogate pair as one codepoint", "[LLStringUTF]") { std::wstring w; w.push_back(L'A'); @@ -397,8 +380,8 @@ namespace tut w.push_back((wchar_t)0xDE80); w.push_back(L'B'); // 4 UTF-16 units = 3 codepoints. - ensure_equals("wide_wstring_length 4u -> 3cp", wide_wstring_length(w, 4), S32(3)); - ensure_equals("wide_wstring_length 0", wide_wstring_length(w, 0), S32(0)); + CHECK(wide_wstring_length(w, 4) == S32(3)); + CHECK(wide_wstring_length(w, 0) == S32(0)); } #endif @@ -406,14 +389,13 @@ namespace tut // truncation helpers // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<30>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_truncate cuts ASCII at the length given", "[LLStringUTF]") { const std::string s = "Hello"; - ensure_equals("truncate noop", utf8str_truncate(s, 10), s); - ensure_equals("truncate exact", utf8str_truncate(s, 5), s); - ensure_equals("truncate to 3", utf8str_truncate(s, 3), std::string("Hel")); - ensure_equals("truncate to 0", utf8str_truncate(s, 0), std::string()); + CHECK(utf8str_truncate(s, 10) == s); + CHECK(utf8str_truncate(s, 5) == s); + CHECK(utf8str_truncate(s, 3) == std::string("Hel")); + CHECK(utf8str_truncate(s, 0) == std::string()); } // Truncation walks back only when max_len lands *inside* a multi-byte @@ -421,36 +403,33 @@ namespace tut // max_len=2 → cut at 0x97 (cont) → walk back to 1 → "A" // max_len=3 → cut at 0xA5 (cont) → walk back to 1 → "A" // max_len=4 → cut at 0x42 (ASCII 'B') → no walk-back → "A日" - template<> template<> - void llstring_utf_object_t::test<31>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_truncate backs off only from inside a character", "[LLStringUTF]") { const std::string s = "A\xE6\x97\xA5" "B"; - ensure_equals("trunc 2 inside 日", utf8str_truncate(s, 2), std::string("A")); - ensure_equals("trunc 3 inside 日", utf8str_truncate(s, 3), std::string("A")); - ensure_equals("trunc 4 on 'B'", utf8str_truncate(s, 4), std::string("A\xE6\x97\xA5")); - ensure_equals("trunc 5 whole", utf8str_truncate(s, 5), s); + CHECK(utf8str_truncate(s, 2) == std::string("A")); + CHECK(utf8str_truncate(s, 3) == std::string("A")); + CHECK(utf8str_truncate(s, 4) == std::string("A\xE6\x97\xA5")); + CHECK(utf8str_truncate(s, 5) == s); } - template<> template<> - void llstring_utf_object_t::test<32>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_substr takes a start and a length", "[LLStringUTF]") { const std::string s = "ABCDE"; - ensure_equals("substr 1,3", utf8str_substr(s, 1, 3), std::string("BCD")); - ensure_equals("substr 3,2", utf8str_substr(s, 3, 2), std::string("DE")); - ensure_equals("substr 0,0", utf8str_substr(s, 0, 0), std::string()); + CHECK(utf8str_substr(s, 1, 3) == std::string("BCD")); + CHECK(utf8str_substr(s, 3, 2) == std::string("DE")); + CHECK(utf8str_substr(s, 0, 0) == std::string()); } // utf8str_symbol_truncate returns the first N complete UTF-8 symbols. // "A日🚀B" = 1+3+4+1 = 9 bytes, 4 symbols. - template<> template<> - void llstring_utf_object_t::test<33>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_symbol_truncate returns the first N complete UTF-8 symbols", "[LLStringUTF]") { const std::string s = "A\xE6\x97\xA5\xF0\x9F\x9A\x80" "B"; - ensure_equals("sym 4 whole", utf8str_symbol_truncate(s, 4), s); - ensure_equals("sym 3", utf8str_symbol_truncate(s, 3), std::string("A\xE6\x97\xA5\xF0\x9F\x9A\x80")); - ensure_equals("sym 2", utf8str_symbol_truncate(s, 2), std::string("A\xE6\x97\xA5")); - ensure_equals("sym 1", utf8str_symbol_truncate(s, 1), std::string("A")); - ensure_equals("sym 0", utf8str_symbol_truncate(s, 0), std::string()); + CHECK(utf8str_symbol_truncate(s, 4) == s); + CHECK(utf8str_symbol_truncate(s, 3) == std::string("A\xE6\x97\xA5\xF0\x9F\x9A\x80")); + CHECK(utf8str_symbol_truncate(s, 2) == std::string("A\xE6\x97\xA5")); + CHECK(utf8str_symbol_truncate(s, 1) == std::string("A")); + CHECK(utf8str_symbol_truncate(s, 0) == std::string()); } // --------------------------------------------------------------- @@ -459,23 +438,22 @@ namespace tut // Lone continuation byte (0x80) and invalid leader (0xFF) each map to // one LL_UNKNOWN_CHAR. - template<> template<> - void llstring_utf_object_t::test<40>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a lone continuation or invalid lead byte is one bad character", "[LLStringUTF]") { for (const std::string& bad : { std::string("\x80"), std::string("\xFF") }) { - ensure("rejected outright", !utf8str_is_valid(bad)); + REQUIRE_FALSE(utf8str_is_valid(bad)); // The decoder gives one replacement character per bad byte, so a // walk driven off it always advances. const std::u32string w = to_u32(bad); - ensure_equals("one bad byte, one character", w.size(), size_t(1)); - ensure_equals("decodes to U+FFFD", (U32)w[0], U32(0xFFFD)); + REQUIRE(w.size() == size_t(1)); + REQUIRE((U32)w[0] == U32(0xFFFD)); // Repair substitutes LL_UNKNOWN_CHAR, and the result is valid. const std::string fixed = utf8str_sanitize(bad); - ensure_equals("repaired to one byte", fixed, std::string(1, LL_UNKNOWN_CHAR)); - ensure("repair is valid", utf8str_is_valid(fixed)); + REQUIRE(fixed == std::string(1, LL_UNKNOWN_CHAR)); + REQUIRE(utf8str_is_valid(fixed)); } } @@ -484,102 +462,89 @@ namespace tut // we get one '?' per rejected byte. The previous hand-rolled decoder // produced a single '?' per malformed sequence; this behavioural change // is intentional for the migration. - template<> template<> - void llstring_utf_object_t::test<41>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: an overlong two-byte NUL is two bad bytes", "[LLStringUTF]") { const std::string overlong("\xC0\x80"); - ensure("overlong NUL rejected", !utf8str_is_valid(overlong)); + CHECK_FALSE(utf8str_is_valid(overlong)); const std::u32string w = to_u32(overlong); - ensure_equals("overlong NUL size", w.size(), size_t(2)); - ensure_equals("overlong NUL[0]", (U32)w[0], U32(0xFFFD)); - ensure_equals("overlong NUL[1]", (U32)w[1], U32(0xFFFD)); + REQUIRE(w.size() == size_t(2)); + CHECK((U32)w[0] == U32(0xFFFD)); + CHECK((U32)w[1] == U32(0xFFFD)); - ensure_equals("repaired byte for byte", - utf8str_sanitize(overlong), std::string(2, LL_UNKNOWN_CHAR)); + CHECK(utf8str_sanitize(overlong) == std::string(2, LL_UNKNOWN_CHAR)); } // Truncated 3-byte sequence "A\xE6\x97" — simdutf rejects both 0xE6 (leader // without a full body) and 0x97 (lone continuation), so the result is // "A??" (A + two replacement chars). Previous hand-rolled decoder // produced "A?". - template<> template<> - void llstring_utf_object_t::test<42>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a truncated three-byte sequence is two bad bytes", "[LLStringUTF]") { const std::string truncated("A\xE6\x97"); - ensure("truncated rejected", !utf8str_is_valid(truncated)); + CHECK_FALSE(utf8str_is_valid(truncated)); const std::u32string w = to_u32(truncated); - ensure_equals("truncated size", w.size(), size_t(3)); - ensure_equals("truncated[0]='A'", (U32)w[0], U32('A')); - ensure_equals("truncated[1]", (U32)w[1], U32(0xFFFD)); - ensure_equals("truncated[2]", (U32)w[2], U32(0xFFFD)); + REQUIRE(w.size() == size_t(3)); + CHECK((U32)w[0] == U32('A')); + CHECK((U32)w[1] == U32(0xFFFD)); + CHECK((U32)w[2] == U32(0xFFFD)); - ensure_equals("the good byte survives the repair", - utf8str_sanitize(truncated), std::string("A??")); + CHECK(utf8str_sanitize(truncated) == std::string("A??")); } // Legacy 5-byte UTF-8 encoding (U+00200000 → F8 88 80 80 80) is no longer // accepted — RFC-3629 compliance. Each byte is flagged individually, so // the input becomes 5 replacement chars instead of 1 wchar with the legacy // codepoint. - template<> template<> - void llstring_utf_object_t::test<43>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a legacy five-byte sequence is five bad bytes", "[LLStringUTF]") { const std::string legacy("\xF8\x88\x80\x80\x80"); - ensure("5-byte legacy rejected", !utf8str_is_valid(legacy)); + CHECK_FALSE(utf8str_is_valid(legacy)); const std::u32string w = to_u32(legacy); - ensure_equals("5-byte legacy size", w.size(), size_t(5)); + CHECK(w.size() == size_t(5)); for (size_t i = 0; i < w.size(); ++i) { - ensure_equals("5-byte legacy[i]", (U32)w[i], U32(0xFFFD)); + CAPTURE(i); + REQUIRE((U32)w[i] == U32(0xFFFD)); } - ensure_equals("every byte repaired", - utf8str_sanitize(legacy), std::string(5, LL_UNKNOWN_CHAR)); + CHECK(utf8str_sanitize(legacy) == std::string(5, LL_UNKNOWN_CHAR)); } // UTF-16 lone high surrogate followed by a non-surrogate: simdutf rejects // the lone 0xD83D (one '?') and converts the following 0x0041 normally. // Previous hand-rolled decoder greedily consumed both units as one garbage // codepoint. - template<> template<> - void llstring_utf_object_t::test<44>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a lone UTF-16 high surrogate becomes one unknown character", "[LLStringUTF]") { const llutf16string bad = { (char16_t)0xD83D, (char16_t)0x0041 }; const std::string out = utf16str_to_utf8str(bad); - ensure_equals("lone surrogate + 'A' size", out.size(), size_t(2)); - ensure_equals("lone surrogate -> '?'", (U32)(U8)out[0], U32((U8)LL_UNKNOWN_CHAR)); - ensure_equals("'A' preserved", (U32)(U8)out[1], U32('A')); + REQUIRE(out.size() == size_t(2)); + CHECK((U32)(U8)out[0] == U32((U8)LL_UNKNOWN_CHAR)); + CHECK((U32)(U8)out[1] == U32('A')); } // --------------------------------------------------------------- // round-trip helpers (smoke) // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<50>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_trim and utf8str_tolower keep non-ASCII text", "[LLStringUTF]") { // These compose utf8str_to_wstring + wstring_to_utf8str. Covered here // so composition regressions surface before we diagnose them through // callers. const std::string mixed = "H\xC3\xA9LLO \xE6\x97\xA5"; - ensure_equals("utf8str_trim passthrough", - utf8str_trim(mixed), mixed); - ensure_equals("utf8str_tolower preserves BMP non-ASCII", - utf8str_tolower(mixed), std::string("h\xC3\xA9llo \xE6\x97\xA5")); + CHECK(utf8str_trim(mixed) == mixed); + CHECK(utf8str_tolower(mixed) == std::string("h\xC3\xA9llo \xE6\x97\xA5")); } - template<> template<> - void llstring_utf_object_t::test<51>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_compare_insensitive orders regardless of case", "[LLStringUTF]") { - ensure_equals("compare_insensitive eq", - utf8str_compare_insensitive("Hello", "hello"), S32(0)); - ensure("compare_insensitive lt", - utf8str_compare_insensitive("abc", "abd") < 0); - ensure("compare_insensitive gt", - utf8str_compare_insensitive("abd", "abc") > 0); + CHECK(utf8str_compare_insensitive("Hello", "hello") == S32(0)); + CHECK(utf8str_compare_insensitive("abc", "abd") < 0); + CHECK(utf8str_compare_insensitive("abd", "abc") > 0); } // --------------------------------------------------------------- @@ -590,56 +555,60 @@ namespace tut // in colour on its own. Symbols that only become emoji when a VS-16 asks // them to (©, ®, ❤) are not, and neither is a codepoint that merely sits // in an emoji block. - template<> template<> - void llstring_utf_object_t::test<60>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: LLStringOps::isEmoji is Unicode's Emoji_Presentation", "[LLStringUTF]") { - ensure("isEmoji rocket", LLStringOps::isEmoji((llwchar)0x1F680)); + UNSCOPED_INFO("isEmoji rocket"); + CHECK(LLStringOps::isEmoji((llwchar)0x1F680)); // Both mahjong tiles, one of which Unicode calls an emoji and one of // which it does not. A block-shaped range test cannot tell them apart. - ensure("isEmoji mahjong red dragon", LLStringOps::isEmoji((llwchar)0x1F004)); - ensure("not isEmoji mahjong east wind", !LLStringOps::isEmoji((llwchar)0x1F000)); + UNSCOPED_INFO("isEmoji mahjong red dragon"); + CHECK(LLStringOps::isEmoji((llwchar)0x1F004)); + UNSCOPED_INFO("not isEmoji mahjong east wind"); + CHECK_FALSE(LLStringOps::isEmoji((llwchar)0x1F000)); // A BMP emoji, which the old astral-only range could never see. - ensure("isEmoji watch", LLStringOps::isEmoji((llwchar)0x231A)); - ensure("not isEmoji 'A'", !LLStringOps::isEmoji((llwchar)'A')); - ensure("not isEmoji ©", !LLStringOps::isEmoji((llwchar)0x00A9)); - ensure("not isEmoji ❤", !LLStringOps::isEmoji((llwchar)0x2764)); - ensure("not isEmoji CJK-Ext", !LLStringOps::isEmoji((llwchar)0x20000)); + UNSCOPED_INFO("isEmoji watch"); + CHECK(LLStringOps::isEmoji((llwchar)0x231A)); + CHECK_FALSE(LLStringOps::isEmoji((llwchar)'A')); + UNSCOPED_INFO("not isEmoji ©"); + CHECK_FALSE(LLStringOps::isEmoji((llwchar)0x00A9)); + UNSCOPED_INFO("not isEmoji ❤"); + CHECK_FALSE(LLStringOps::isEmoji((llwchar)0x2764)); + UNSCOPED_INFO("not isEmoji CJK-Ext"); + CHECK_FALSE(LLStringOps::isEmoji((llwchar)0x20000)); } // wstring_remove_emojis must strip consecutive emojis without skipping // the following code point (the implementation uses i-- after erase to // re-check the new index). - template<> template<> - void llstring_utf_object_t::test<62>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: wstring_remove_emojis strips consecutive emoji and keeps what follows", "[LLStringUTF]") { std::u32string ws = { (llwchar)'H', (llwchar)0x1F680, (llwchar)0x1F681, (llwchar)'i' }; const std::u32string expected = { (llwchar)'H', (llwchar)'i' }; - ensure("remove_emojis returned true", wstring_remove_emojis(ws)); - ensure_wstring_equals("consecutive emojis stripped", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("consecutive emojis stripped", ws, expected); std::u32string none = { (llwchar)'H', (llwchar)'i' }; - ensure("remove_emojis returned false", !wstring_remove_emojis(none)); - ensure_wstring_equals("unchanged when no emojis", none, expected); + CHECK_FALSE(wstring_remove_emojis(none)); + require_same_u32("unchanged when no emojis", none, expected); } - template<> template<> - void llstring_utf_object_t::test<63>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_remove_emojis strips whole clusters from UTF-8", "[LLStringUTF]") { std::string s = "Hi\xF0\x9F\x9A\x80" "!"; - ensure("utf8str_remove_emojis found", utf8str_remove_emojis(s)); - ensure_equals("utf8 stripped", s, std::string("Hi!")); + CHECK(utf8str_remove_emojis(s)); + CHECK(s == std::string("Hi!")); std::string clean = "Hello"; - ensure("utf8str_remove_emojis clean", !utf8str_remove_emojis(clean)); - ensure_equals("clean unchanged", clean, std::string("Hello")); + CHECK_FALSE(utf8str_remove_emojis(clean)); + CHECK(clean == std::string("Hello")); // Heart-on-fire ❤️‍🔥 round-trips through utf8 encoding without // the wstring layer disagreeing with the byte layer about cluster // bounds. UTF-8 byte sequence: U+2764 (E2 9D A4) U+FE0F (EF B8 8F) // U+200D (E2 80 8D) U+1F525 (F0 9F 94 A5). std::string fire = "A" "\xE2\x9D\xA4" "\xEF\xB8\x8F" "\xE2\x80\x8D" "\xF0\x9F\x94\xA5" "B"; - ensure("utf8 heart-on-fire found", utf8str_remove_emojis(fire)); - ensure_equals("utf8 heart-on-fire stripped", fire, std::string("AB")); + CHECK(utf8str_remove_emojis(fire)); + CHECK(fire == std::string("AB")); // Subdivision flag 🏴󠁧󠁢󠁥󠁮󠁧󠁿 — base + 5 tag chars + CANCEL TAG, all // astral. UTF-8 bytes: U+1F3F4 (F0 9F 8F B4) followed by 6 four-byte @@ -648,15 +617,14 @@ namespace tut "\xF3\xA0\x81\xA7" "\xF3\xA0\x81\xA2" "\xF3\xA0\x81\xB3" "\xF3\xA0\x81\xA3" "\xF3\xA0\x81\xB4" "\xF3\xA0\x81\xBF" "]"; - ensure("utf8 subdiv flag found", utf8str_remove_emojis(flag)); - ensure_equals("utf8 subdiv flag stripped", flag, std::string("[]")); + CHECK(utf8str_remove_emojis(flag)); + CHECK(flag == std::string("[]")); } // ZWJ family (U+1F468 U+200D U+1F469 U+200D U+1F467) must strip as a // single unit — the old implementation left the two ZWJ code points // behind, producing visible tofu. - template<> template<> - void llstring_utf_object_t::test<64>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a ZWJ family strips as one unit", "[LLStringUTF]") { std::u32string ws = { (llwchar)'H', (llwchar)0x1F468, (llwchar)0x200D, @@ -664,55 +632,51 @@ namespace tut (llwchar)0x1F467, (llwchar)'i' }; const std::u32string expected = { (llwchar)'H', (llwchar)'i' }; - ensure("ZWJ family found", wstring_remove_emojis(ws)); - ensure_wstring_equals("ZWJ family stripped", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("ZWJ family stripped", ws, expected); } // Skin-tone modifier (U+1F3FB..FF) must strip together with its base emoji. - template<> template<> - void llstring_utf_object_t::test<65>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a skin-tone modifier strips with its base emoji", "[LLStringUTF]") { std::u32string ws = { (llwchar)'M', (llwchar)0x1F468, (llwchar)0x1F3FB, (llwchar)'X' }; const std::u32string expected = { (llwchar)'M', (llwchar)'X' }; - ensure("skin tone found", wstring_remove_emojis(ws)); - ensure_wstring_equals("skin tone stripped", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("skin tone stripped", ws, expected); } // Regional indicator flag (🇺🇸 = U+1F1FA U+1F1F8). Both code points are in // the astral emoji range so both strip — the output must not leave a // dangling half-flag indicator. - template<> template<> - void llstring_utf_object_t::test<66>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a flag strips both its regional indicators", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F1FA, (llwchar)0x1F1F8, (llwchar)'!' }; const std::u32string expected = { (llwchar)'!' }; - ensure("flag found", wstring_remove_emojis(ws)); - ensure_wstring_equals("flag stripped", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("flag stripped", ws, expected); } // Trailing VS16 (U+FE0F) after an astral emoji must be consumed together // with the base glyph. - template<> template<> - void llstring_utf_object_t::test<67>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a trailing VS16 strips with its astral base", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F680, (llwchar)0xFE0F, (llwchar)'!' }; const std::u32string expected = { (llwchar)'!' }; - ensure("VS16 found", wstring_remove_emojis(ws)); - ensure_wstring_equals("VS16 stripped with base", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("VS16 stripped with base", ws, expected); } // Bare ZWJ or VS16 (no adjacent emoji) are not emoji themselves and must // be left intact — critical because ZWJ is used in Arabic/Indic shaping // outside any emoji context. - template<> template<> - void llstring_utf_object_t::test<68>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a bare ZWJ is not an emoji and stays", "[LLStringUTF]") { std::u32string ws = { (llwchar)'a', (llwchar)0x200D, (llwchar)'b' }; const std::u32string expected = ws; - ensure("bare ZWJ not found", !wstring_remove_emojis(ws)); - ensure_wstring_equals("bare ZWJ preserved", ws, expected); + CHECK_FALSE(wstring_remove_emojis(ws)); + require_same_u32("bare ZWJ preserved", ws, expected); } // Broad-contract rewrite: wstring_remove_emojis now drives off the @@ -722,20 +686,19 @@ namespace tut // builds left this pair intact under a narrower isEmoji-only contract; // the change unifies the strip behavior with cluster-aware tooltip and // cursor logic so they can't disagree on cluster bounds. - template<> template<> - void llstring_utf_object_t::test<69>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a heart with VS16 strips but a bare heart stays", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x2764, (llwchar)0xFE0F }; - ensure("BMP heart+VS16 stripped", wstring_remove_emojis(ws)); - ensure_wstring_equals("BMP heart+VS16 cleared", ws, std::u32string()); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("BMP heart+VS16 cleared", ws, std::u32string()); // Bare BMP heart (no VS-16) is not a cluster — single-codepoint // pictograph, shaped via the 1:1 path. Outside isEmoji's narrow // range too. Must pass through. std::u32string bare_heart = { (llwchar)0x2764 }; const std::u32string bare_expected = bare_heart; - ensure("bare heart not stripped", !wstring_remove_emojis(bare_heart)); - ensure_wstring_equals("bare heart preserved", bare_heart, bare_expected); + CHECK_FALSE(wstring_remove_emojis(bare_heart)); + require_same_u32("bare heart preserved", bare_heart, bare_expected); } // --------------------------------------------------------------- @@ -747,16 +710,15 @@ namespace tut // heart/VS-16/ZWJ then strip the lone fire, leaving `❤️‍` (heart + // VS-16 + dangling ZWJ) on screen. Cluster-driven removal strips // the whole sequence as one unit. - template<> template<> - void llstring_utf_object_t::test<70>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: heart-on-fire strips whole though its base is in the BMP", "[LLStringUTF]") { std::u32string ws = { (llwchar)'A', (llwchar)0x2764, (llwchar)0xFE0F, (llwchar)0x200D, (llwchar)0x1F525, (llwchar)'B' }; const std::u32string expected = { (llwchar)'A', (llwchar)'B' }; - ensure("heart-on-fire found", wstring_remove_emojis(ws)); - ensure_wstring_equals("heart-on-fire cleared", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("heart-on-fire cleared", ws, expected); } // Keycap `1️⃣` (`'1' + VS-16 + U+20E3`): no codepoint inside the cluster @@ -764,36 +726,34 @@ namespace tut // the old narrow walker left the whole thing intact. The cluster walker // identifies it as a single 3-codepoint cluster, and the broad contract // strips it. - template<> template<> - void llstring_utf_object_t::test<71>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: keycaps strip as clusters and a bare digit stays", "[LLStringUTF]") { std::u32string digit_kc = { (llwchar)'1', (llwchar)0xFE0F, (llwchar)0x20E3, (llwchar)'!' }; const std::u32string expected = { (llwchar)'!' }; - ensure("digit keycap found", wstring_remove_emojis(digit_kc)); - ensure_wstring_equals("digit keycap cleared", digit_kc, expected); + CHECK(wstring_remove_emojis(digit_kc)); + require_same_u32("digit keycap cleared", digit_kc, expected); std::u32string hash_kc = { (llwchar)'#', (llwchar)0xFE0F, (llwchar)0x20E3 }; - ensure("hash keycap found", wstring_remove_emojis(hash_kc)); - ensure_wstring_equals("hash keycap cleared", hash_kc, std::u32string()); + CHECK(wstring_remove_emojis(hash_kc)); + require_same_u32("hash keycap cleared", hash_kc, std::u32string()); std::u32string star_kc = { (llwchar)'*', (llwchar)0xFE0F, (llwchar)0x20E3 }; - ensure("star keycap found", wstring_remove_emojis(star_kc)); - ensure_wstring_equals("star keycap cleared", star_kc, std::u32string()); + CHECK(wstring_remove_emojis(star_kc)); + require_same_u32("star keycap cleared", star_kc, std::u32string()); // Bare digit (no VS-16 + combiner) must pass through. The cluster // walker rejects it as a starter; nothing to strip. std::u32string bare_digit = { (llwchar)'9' }; const std::u32string bare_expected = bare_digit; - ensure("bare digit not stripped", !wstring_remove_emojis(bare_digit)); - ensure_wstring_equals("bare digit preserved", bare_digit, bare_expected); + CHECK_FALSE(wstring_remove_emojis(bare_digit)); + require_same_u32("bare digit preserved", bare_digit, bare_expected); } // Subdivision flag 🏴󠁧󠁢󠁥󠁮󠁧󠁿 (U+1F3F4 + tag bytes + U+E007F CANCEL TAG). // Pre-rewrite the walker stripped the astral 🏴 base via isEmoji but // left tag chars and the U+E007F terminator behind as garbage code // points. Cluster-driven removal sweeps the whole sequence. - template<> template<> - void llstring_utf_object_t::test<72>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a subdivision flag strips with its tag characters", "[LLStringUTF]") { std::u32string ws = { (llwchar)'<', (llwchar)0x1F3F4, @@ -802,8 +762,8 @@ namespace tut (llwchar)0xE007F, (llwchar)'>' }; const std::u32string expected = { (llwchar)'<', (llwchar)'>' }; - ensure("subdivision flag found", wstring_remove_emojis(ws)); - ensure_wstring_equals("subdivision flag cleared", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("subdivision flag cleared", ws, expected); } // Astral emoji + VS-15 (U+FE0E text-presentation selector) — VS-15 is @@ -811,156 +771,68 @@ namespace tut // gets stripped together with its base. Pre-rewrite VS-15 was not in // is_emoji_sequence_extender's narrow set and would have been left as // an orphan after the base was stripped. - template<> template<> - void llstring_utf_object_t::test<73>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a VS15 after an astral emoji strips with it", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F680, (llwchar)0xFE0E, (llwchar)'!' }; const std::u32string expected = { (llwchar)'!' }; - ensure("VS-15 cluster found", wstring_remove_emojis(ws)); - ensure_wstring_equals("VS-15 cluster cleared", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("VS-15 cluster cleared", ws, expected); } // Two flags back-to-back 🇺🇸🇯🇵 — distinct clusters [0,2) and [2,4). // Both must strip cleanly; no leftover RI codepoints. - template<> template<> - void llstring_utf_object_t::test<74>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: two flags back to back both strip", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F1FA, (llwchar)0x1F1F8, (llwchar)0x1F1EF, (llwchar)0x1F1F5, (llwchar)'!' }; const std::u32string expected = { (llwchar)'!' }; - ensure("two flags found", wstring_remove_emojis(ws)); - ensure_wstring_equals("two flags cleared", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("two flags cleared", ws, expected); } // ZWSP (U+200B) is not an emoji extender — it must pass through even // when adjacent to an astral emoji. Pre-rewrite this happened to work // (ZWSP isn't in the extender set), but pin it now that the path uses // the cluster walker, which also treats ZWSP as inert. - template<> template<> - void llstring_utf_object_t::test<75>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a ZWSP next to an emoji is not stripped", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F680, (llwchar)0x200B, (llwchar)'X' }; const std::u32string expected = { (llwchar)0x200B, (llwchar)'X' }; - ensure("rocket+ZWSP found", wstring_remove_emojis(ws)); - ensure_wstring_equals("rocket stripped, ZWSP kept", ws, expected); + CHECK(wstring_remove_emojis(ws)); + require_same_u32("rocket stripped, ZWSP kept", ws, expected); } // --------------------------------------------------------------- // 11x utf8str helper smoke pins // --------------------------------------------------------------- - // utf8str_substChar replaces every occurrence of a code point (incl. - // astrals) with another code point, routing through std::u32string so the - // byte-length change is handled for us. - template<> template<> - void llstring_utf_object_t::test<110>() - { - const std::string in = "A\xF0\x9F\x9A\x80" "B\xF0\x9F\x9A\x80" "C"; - const std::string out = utf8str_substChar(in, (llwchar)0x1F680, (llwchar)'X'); - ensure_equals("substChar astral -> ASCII", out, std::string("AXBXC")); - - const std::string in2 = "A\xE6\x97\xA5" "B"; - const std::string out2 = utf8str_substChar(in2, (llwchar)0x65E5, (llwchar)'.'); - ensure_equals("substChar BMP -> ASCII", out2, std::string("A.B")); - } - - // utf8str_makeASCII replaces every non-ASCII llwchar (>0x7F) with '?' - // before serializing back to utf-8. - template<> template<> - void llstring_utf_object_t::test<111>() - { - const std::string mixed = "A\xE6\x97\xA5" "B\xF0\x9F\x9A\x80" "C"; - const std::string expected = "A?B?C"; - ensure_equals("utf8str_makeASCII", utf8str_makeASCII(mixed), expected); - ensure_equals("utf8str_makeASCII ascii passthrough", - utf8str_makeASCII("Hello"), std::string("Hello")); - } - - // mbcsstring_makeASCII is byte-level: every byte >0x7F becomes '?'. So a - // 3-byte UTF-8 character yields three '?' rather than one. - template<> template<> - void llstring_utf_object_t::test<112>() - { - const std::string mixed = "A\xE6\x97\xA5" "B"; - const std::string expected = "A???B"; - ensure_equals("mbcsstring_makeASCII", mbcsstring_makeASCII(mixed), expected); - } - - // utf8str_removeCRLF strips CR (0x0D) only — LF is preserved. - template<> template<> - void llstring_utf_object_t::test<113>() - { - ensure_equals("removeCRLF crlf", utf8str_removeCRLF("a\r\nb"), std::string("a\nb")); - ensure_equals("removeCRLF cr", utf8str_removeCRLF("a\rb"), std::string("ab")); - ensure_equals("removeCRLF lf", utf8str_removeCRLF("a\nb"), std::string("a\nb")); - ensure_equals("removeCRLF empty", utf8str_removeCRLF(""), std::string()); - } - - // utf8str_split splits at the last split-token at or before maxlen bytes, - // falling back to a hard byte cut when no token is in range. Non-final - // chunks retain the trailing split-token (current implementation). - template<> template<> - void llstring_utf_object_t::test<114>() - { - { - std::list out; - utf8str_split(out, "Hello there world", 10, ' '); - ensure_equals("split 3 parts", out.size(), size_t(3)); - auto it = out.begin(); - ensure_equals("split[0]", *it++, std::string("Hello")); - ensure_equals("split[1]", *it++, std::string("there ")); - ensure_equals("split[2]", *it, std::string("world")); - } - { - std::list out; - utf8str_split(out, "short", 10, ' '); - ensure_equals("split single", out.size(), size_t(1)); - ensure_equals("split single val", out.front(), std::string("short")); - } - } - - // wchar_utf8_preview formats the code point in hex, plus (when multi-byte) - // the decoded UTF-8 byte sequence also in hex. - template<> template<> - void llstring_utf_object_t::test<115>() - { - ensure_equals("preview ASCII", wchar_utf8_preview((llwchar)'A'), std::string("41")); - // é = U+00E9 -> 0xC3 0xA9 - ensure_equals("preview 2-byte", wchar_utf8_preview((llwchar)0x00E9), - std::string("E9 [C3, A9]")); - // Rocket = U+1F680 -> 0xF0 0x9F 0x9A 0x80 - ensure_equals("preview astral", wchar_utf8_preview((llwchar)0x1F680), - std::string("1F680 [F0, 9F, 9A, 80]")); - } - // --------------------------------------------------------------- // byte-level helpers // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<81>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: is_char_hex accepts hex digits only", "[LLStringUTF]") { for (char c : std::string("0123456789abcdefABCDEF")) { - ensure("is_char_hex yes", is_char_hex(c)); + CAPTURE(c); + REQUIRE(is_char_hex(c)); } - ensure("is_char_hex 'g'", !is_char_hex('g')); - ensure("is_char_hex 'Z'", !is_char_hex('Z')); - ensure("is_char_hex ' '", !is_char_hex(' ')); + CHECK_FALSE(is_char_hex('g')); + CHECK_FALSE(is_char_hex('Z')); + CHECK_FALSE(is_char_hex(' ')); } - template<> template<> - void llstring_utf_object_t::test<82>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: hex_as_nybble reads either case and gives 0 otherwise", "[LLStringUTF]") { - ensure_equals("nybble '0'", (U32)hex_as_nybble('0'), U32(0)); - ensure_equals("nybble '9'", (U32)hex_as_nybble('9'), U32(9)); - ensure_equals("nybble 'a'", (U32)hex_as_nybble('a'), U32(10)); - ensure_equals("nybble 'f'", (U32)hex_as_nybble('f'), U32(15)); - ensure_equals("nybble 'A'", (U32)hex_as_nybble('A'), U32(10)); - ensure_equals("nybble 'F'", (U32)hex_as_nybble('F'), U32(15)); + CHECK((U32)hex_as_nybble('0') == U32(0)); + CHECK((U32)hex_as_nybble('9') == U32(9)); + CHECK((U32)hex_as_nybble('a') == U32(10)); + CHECK((U32)hex_as_nybble('f') == U32(15)); + CHECK((U32)hex_as_nybble('A') == U32(10)); + CHECK((U32)hex_as_nybble('F') == U32(15)); // Non-hex inputs fall through to 0 (undefined-but-documented behavior). - ensure_equals("nybble 'g' -> 0", (U32)hex_as_nybble('g'), U32(0)); + CHECK((U32)hex_as_nybble('g') == U32(0)); } // --------------------------------------------------------------- @@ -970,110 +842,102 @@ namespace tut // Inputs that never need shaping — empty, ASCII, CJK, a single isolated // emoji, and two adjacent but unrelated emoji all render correctly via // the 1:1 codepoint path, so the detector must return no runs. - template<> template<> - void llstring_utf_object_t::test<90>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: text that needs no shaping has no emoji clusters", "[LLStringUTF]") { - ensure_equals("empty", wstring_find_emoji_clusters(std::u32string()).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(std::u32string()).size() == size_t(0)); std::u32string ascii = { (llwchar)'H', (llwchar)'i', (llwchar)'!' }; - ensure_equals("ascii", wstring_find_emoji_clusters(ascii).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(ascii).size() == size_t(0)); std::u32string cjk = { (llwchar)0x65E5, (llwchar)0x672C }; - ensure_equals("cjk", wstring_find_emoji_clusters(cjk).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(cjk).size() == size_t(0)); std::u32string lone = { (llwchar)0x1F680 }; - ensure_equals("lone emoji", wstring_find_emoji_clusters(lone).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(lone).size() == size_t(0)); std::u32string pair = { (llwchar)0x1F680, (llwchar)0x1F681 }; - ensure_equals("two adj em", wstring_find_emoji_clusters(pair).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(pair).size() == size_t(0)); } // ZWJ family 👨‍👩‍👧 = U+1F468 U+200D U+1F469 U+200D U+1F467 — must emerge // as a single 5-codepoint run. - template<> template<> - void llstring_utf_object_t::test<91>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a ZWJ family is one five-codepoint cluster", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F468, (llwchar)0x200D, (llwchar)0x1F469, (llwchar)0x200D, (llwchar)0x1F467 }; auto runs = wstring_find_emoji_clusters(ws); - ensure_equals("one run", runs.size(), size_t(1)); - ensure_equals("run begin", runs[0].first, size_t(0)); - ensure_equals("run end", runs[0].second, size_t(5)); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].first == size_t(0)); + CHECK(runs[0].second == size_t(5)); } // Skin-tone modifier (U+1F3FB..FF) combines with the preceding emoji. - template<> template<> - void llstring_utf_object_t::test<92>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a skin-tone modifier joins the emoji before it", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F468, (llwchar)0x1F3FB }; auto runs = wstring_find_emoji_clusters(ws); - ensure_equals("skintone runs", runs.size(), size_t(1)); - ensure_equals("skintone end", runs[0].second, size_t(2)); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].second == size_t(2)); } // Trailing VS16 (U+FE0F) forces emoji presentation and must be part of // the same shaped run as its base. - template<> template<> - void llstring_utf_object_t::test<93>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a trailing VS16 joins its base's cluster", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F680, (llwchar)0xFE0F }; auto runs = wstring_find_emoji_clusters(ws); - ensure_equals("vs16 runs", runs.size(), size_t(1)); - ensure_equals("vs16 end", runs[0].second, size_t(2)); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].second == size_t(2)); } // Regional indicator pair — 🇺🇸 = U+1F1FA U+1F1F8 — becomes a flag glyph // only under shaping. Two consecutive RIs form exactly one pair. - template<> template<> - void llstring_utf_object_t::test<94>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: regional indicators pair into flag clusters", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F1FA, (llwchar)0x1F1F8 }; auto runs = wstring_find_emoji_clusters(ws); - ensure_equals("flag runs", runs.size(), size_t(1)); - ensure_equals("flag end", runs[0].second, size_t(2)); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].second == size_t(2)); // Four RIs in a row form two separate flags. std::u32string four = { (llwchar)0x1F1FA, (llwchar)0x1F1F8, (llwchar)0x1F1EB, (llwchar)0x1F1F7 }; auto more = wstring_find_emoji_clusters(four); - ensure_equals("two flags", more.size(), size_t(2)); - ensure_equals("flag0 end", more[0].second, size_t(2)); - ensure_equals("flag1 begin", more[1].first, size_t(2)); - ensure_equals("flag1 end", more[1].second, size_t(4)); + REQUIRE(more.size() == size_t(2)); + CHECK(more[0].second == size_t(2)); + CHECK(more[1].first == size_t(2)); + CHECK(more[1].second == size_t(4)); } // Keycap: digit/#/* + U+FE0F + U+20E3 is a shaped 3-codepoint sequence. // shapeRun itemises this into per-face sub-runs (the ASCII digit on // the text font, the combining mark on the emoji font), so the // cluster is still detected here for cursor-snapping purposes. - template<> template<> - void llstring_utf_object_t::test<95>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a keycap is a three-codepoint cluster", "[LLStringUTF]") { std::u32string digit_kc = { (llwchar)'9', (llwchar)0xFE0F, (llwchar)0x20E3 }; auto runs = wstring_find_emoji_clusters(digit_kc); - ensure_equals("keycap runs", runs.size(), size_t(1)); - ensure_equals("keycap end", runs[0].second, size_t(3)); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].second == size_t(3)); std::u32string hash_kc = { (llwchar)'#', (llwchar)0xFE0F, (llwchar)0x20E3 }; - ensure_equals("hash keycap", wstring_find_emoji_clusters(hash_kc).size(), size_t(1)); + CHECK(wstring_find_emoji_clusters(hash_kc).size() == size_t(1)); std::u32string bare = { (llwchar)'5' }; - ensure_equals("bare digit", wstring_find_emoji_clusters(bare).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(bare).size() == size_t(0)); } // Subdivision flag — base 🏴 (U+1F3F4) + tag characters (U+E0020..U+E007F) // terminated by U+E007F. All tag bytes must be absorbed into the run. - template<> template<> - void llstring_utf_object_t::test<96>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a subdivision flag's cluster takes in its tag characters", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x1F3F4, (llwchar)0xE0067, (llwchar)0xE0062, (llwchar)0xE0073, (llwchar)0xE0063, (llwchar)0xE0074, (llwchar)0xE007F }; auto runs = wstring_find_emoji_clusters(ws); - ensure_equals("tag runs", runs.size(), size_t(1)); - ensure_equals("tag begin", runs[0].first, size_t(0)); - ensure_equals("tag end", runs[0].second, size_t(7)); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].first == size_t(0)); + CHECK(runs[0].second == size_t(7)); } // Mixed: ASCII + flag + ASCII + ZWJ family + ASCII. Two disjoint runs // with correct bounds; surrounding ASCII is untouched. - template<> template<> - void llstring_utf_object_t::test<97>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a flag and a ZWJ family among ASCII are two clusters", "[LLStringUTF]") { std::u32string ws = { (llwchar)'H', (llwchar)0x1F1FA, (llwchar)0x1F1F8, // flag @ [1,3) @@ -1083,29 +947,28 @@ namespace tut (llwchar)0x1F467, (llwchar)'!' }; auto runs = wstring_find_emoji_clusters(ws); - ensure_equals("two runs", runs.size(), size_t(2)); - ensure_equals("run0 begin", runs[0].first, size_t(1)); - ensure_equals("run0 end", runs[0].second, size_t(3)); - ensure_equals("run1 begin", runs[1].first, size_t(4)); - ensure_equals("run1 end", runs[1].second, size_t(9)); + REQUIRE(runs.size() == size_t(2)); + CHECK(runs[0].first == size_t(1)); + CHECK(runs[0].second == size_t(3)); + CHECK(runs[1].first == size_t(4)); + CHECK(runs[1].second == size_t(9)); } // Bare ZWJ/VS16 surrounded by non-emoji (as used in Arabic/Indic shaping // outside any emoji context) must NOT produce a run. There is no emoji // face to shape with and the caller should keep the 1:1 path. - template<> template<> - void llstring_utf_object_t::test<98>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: bare extenders and malformed sequences cluster as pinned", "[LLStringUTF]") { std::u32string zwj = { (llwchar)'a', (llwchar)0x200D, (llwchar)'b' }; - ensure_equals("bare zwj", wstring_find_emoji_clusters(zwj).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(zwj).size() == size_t(0)); std::u32string vs16 = { (llwchar)'a', (llwchar)0xFE0F, (llwchar)'b' }; - ensure_equals("bare vs16", wstring_find_emoji_clusters(vs16).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(vs16).size() == size_t(0)); // Malformed inputs: leading/trailing extenders, lone modifiers, and // singletons that need a partner. Pin current behavior so future // refactors don't silently change cluster boundaries on broken input. std::u32string lead_zwj = { (llwchar)0x200D, (llwchar)0x1F468 }; - ensure_equals("leading zwj", wstring_find_emoji_clusters(lead_zwj).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(lead_zwj).size() == size_t(0)); std::u32string trail_zwj = { (llwchar)0x1F468, (llwchar)0x200D }; // Trailing ZWJ: is_shaping_starter accepts the base because the // next codepoint is a ZWJ, but advance_shaping_run hits the @@ -1113,26 +976,26 @@ namespace tut // the resulting length-1 "run" so the result matches the doc // contract (isolated single-codepoint emoji are intentionally // absent from the cluster list). - ensure_equals("trailing zwj", wstring_find_emoji_clusters(trail_zwj).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(trail_zwj).size() == size_t(0)); // Same shape but with VS-16 instead of ZWJ: 'is_shaping_starter' // accepts because VS-16 is a valid extender, advance consumes it, // run length is 2 → real 2-codepoint cluster. Confirm we didn't // over-trim and accidentally drop legitimate 2-codepoint clusters. std::u32string base_vs16 = { (llwchar)0x1F680, (llwchar)0xFE0F }; auto vs_runs = wstring_find_emoji_clusters(base_vs16); - ensure_equals("base+VS16 count", vs_runs.size(), size_t(1)); - ensure_equals("base+VS16 begin", vs_runs[0].first, size_t(0)); - ensure_equals("base+VS16 end", vs_runs[0].second, size_t(2)); + REQUIRE(vs_runs.size() == size_t(1)); + CHECK(vs_runs[0].first == size_t(0)); + CHECK(vs_runs[0].second == size_t(2)); std::u32string lone_skin = { (llwchar)0x1F3FB }; - ensure_equals("lone skintone", wstring_find_emoji_clusters(lone_skin).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(lone_skin).size() == size_t(0)); std::u32string lone_ri = { (llwchar)0x1F1FA }; - ensure_equals("lone RI", wstring_find_emoji_clusters(lone_ri).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(lone_ri).size() == size_t(0)); // Range boundaries on isPictographBase's astral / BMP windows. std::u32string just_below_astral = { (llwchar)0x1FFF }; - ensure_equals("U+1FFF no run", wstring_find_emoji_clusters(just_below_astral).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(just_below_astral).size() == size_t(0)); std::u32string just_above_bmp = { (llwchar)0x3300 }; - ensure_equals("U+3300 no run", wstring_find_emoji_clusters(just_above_bmp).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(just_above_bmp).size() == size_t(0)); // Back-to-back ZWJ families with no separator. Pin current behavior: // each MAN-ZWJ-WOMAN sub-sequence registers as its own cluster, so we @@ -1140,47 +1003,47 @@ namespace tut std::u32string two_fam = { (llwchar)0x1F468, (llwchar)0x200D, (llwchar)0x1F469, (llwchar)0x1F468, (llwchar)0x200D, (llwchar)0x1F469 }; auto two_fam_runs = wstring_find_emoji_clusters(two_fam); - ensure_equals("two_fam count", two_fam_runs.size(), size_t(2)); - ensure_equals("two_fam[0].first", two_fam_runs[0].first, size_t(0)); - ensure_equals("two_fam[0].second", two_fam_runs[0].second, size_t(3)); - ensure_equals("two_fam[1].first", two_fam_runs[1].first, size_t(3)); - ensure_equals("two_fam[1].second", two_fam_runs[1].second, size_t(6)); + REQUIRE(two_fam_runs.size() == size_t(2)); + CHECK(two_fam_runs[0].first == size_t(0)); + CHECK(two_fam_runs[0].second == size_t(3)); + CHECK(two_fam_runs[1].first == size_t(3)); + CHECK(two_fam_runs[1].second == size_t(6)); // ZWJ followed by a non-pictograph: starter accepts the base // (next is ZWJ), advance_shaping_run reaches the ZWJ, sees the // non-pictograph 'A' afterwards, breaks orphan-ZWJ. Length-1 run // gets discarded by the cluster walker. No cluster emitted. std::u32string zwj_plus_nonemoji = { (llwchar)0x1F468, (llwchar)0x200D, (llwchar)'A' }; - ensure_equals("ZWJ+non-pictograph", wstring_find_emoji_clusters(zwj_plus_nonemoji).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(zwj_plus_nonemoji).size() == size_t(0)); // Double ZWJ (ZWJ + ZWJ between bases): advance hits the first // ZWJ, looks at the next codepoint, finds another ZWJ which is // not a pictograph base. Orphan-ZWJ break. Length 1, discarded. std::u32string double_zwj = { (llwchar)0x1F468, (llwchar)0x200D, (llwchar)0x200D, (llwchar)0x1F469 }; - ensure_equals("double ZWJ", wstring_find_emoji_clusters(double_zwj).size(), size_t(0)); + CHECK(wstring_find_emoji_clusters(double_zwj).size() == size_t(0)); // Truncated subdivision flag — base 🏴 + a couple of tag bytes, // no U+E007F terminator. Pin: walker greedily eats every tag // codepoint to end-of-string. Real implementation behavior. std::u32string trunc_tag = { (llwchar)0x1F3F4, (llwchar)0xE0067, (llwchar)0xE0062 }; auto trunc_runs = wstring_find_emoji_clusters(trunc_tag); - ensure_equals("trunc tag count", trunc_runs.size(), size_t(1)); - ensure_equals("trunc tag begin", trunc_runs[0].first, size_t(0)); - ensure_equals("trunc tag end", trunc_runs[0].second, size_t(3)); + REQUIRE(trunc_runs.size() == size_t(1)); + CHECK(trunc_runs[0].first == size_t(0)); + CHECK(trunc_runs[0].second == size_t(3)); // Three RIs — first two form a flag at [0,2), third RI alone is // not a starter (no partner). Pin: one cluster, length 2. std::u32string three_ri = { (llwchar)0x1F1FA, (llwchar)0x1F1F8, (llwchar)0x1F1F8 }; auto three_ri_runs = wstring_find_emoji_clusters(three_ri); - ensure_equals("three RI count", three_ri_runs.size(), size_t(1)); - ensure_equals("three RI begin", three_ri_runs[0].first, size_t(0)); - ensure_equals("three RI end", three_ri_runs[0].second, size_t(2)); + REQUIRE(three_ri_runs.size() == size_t(1)); + CHECK(three_ri_runs[0].first == size_t(0)); + CHECK(three_ri_runs[0].second == size_t(2)); // Star keycap '*' + VS-16 + U+20E3. Pin parity with digit/hash. std::u32string star_kc = { (llwchar)'*', (llwchar)0xFE0F, (llwchar)0x20E3 }; auto star_runs = wstring_find_emoji_clusters(star_kc); - ensure_equals("star kc count", star_runs.size(), size_t(1)); - ensure_equals("star kc end", star_runs[0].second, size_t(3)); + REQUIRE(star_runs.size() == size_t(1)); + CHECK(star_runs[0].second == size_t(3)); } // BMP pictograph + VS16 + ZWJ + astral emoji (❤️‍🔥 = U+2764 U+FE0F @@ -1188,15 +1051,14 @@ namespace tut // range, but the shaping detector must still pick up the whole sequence // so HarfBuzz gets to compose the "heart on fire" glyph instead of // leaving ❤ and 🔥 as disjoint codepoints. - template<> template<> - void llstring_utf_object_t::test<99>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: heart-on-fire is one cluster though its base is in the BMP", "[LLStringUTF]") { std::u32string ws = { (llwchar)0x2764, (llwchar)0xFE0F, (llwchar)0x200D, (llwchar)0x1F525 }; auto runs = wstring_find_emoji_clusters(ws); - ensure_equals("bmp zwj runs", runs.size(), size_t(1)); - ensure_equals("bmp zwj begin", runs[0].first, size_t(0)); - ensure_equals("bmp zwj end", runs[0].second, size_t(4)); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].first == size_t(0)); + CHECK(runs[0].second == size_t(4)); } // --------------------------------------------------------------- @@ -1206,14 +1068,13 @@ namespace tut // Forward step must move exactly one codepoint through ASCII and skip // past entire emoji clusters in one move so the caret never lands mid- // ZWJ-family. - template<> template<> - void llstring_utf_object_t::test<100>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: stepping forward crosses one character or one whole cluster", "[LLStringUTF]") { // Pure ASCII — one codepoint per step, clamped at size. std::u32string ascii = { (llwchar)'a', (llwchar)'b', (llwchar)'c' }; - ensure_equals("ascii 0->1", wstring_step_grapheme_forward(ascii, 0), size_t(1)); - ensure_equals("ascii 2->3", wstring_step_grapheme_forward(ascii, 2), size_t(3)); - ensure_equals("ascii at end stays", wstring_step_grapheme_forward(ascii, 3), size_t(3)); + CHECK(wstring_step_grapheme_forward(ascii, 0) == size_t(1)); + CHECK(wstring_step_grapheme_forward(ascii, 2) == size_t(3)); + CHECK(wstring_step_grapheme_forward(ascii, 3) == size_t(3)); // ZWJ family in the middle: H, 👨, ZWJ, 👩, ZWJ, 👧, i // 0 1 2 3 4 5 6 @@ -1222,46 +1083,44 @@ namespace tut (llwchar)0x1F469, (llwchar)0x200D, (llwchar)0x1F467, (llwchar)'i' }; - ensure_equals("H->fam start", wstring_step_grapheme_forward(fam, 0), size_t(1)); - ensure_equals("fam start jumps past cluster", - wstring_step_grapheme_forward(fam, 1), size_t(6)); - ensure_equals("from mid-cluster snaps past", - wstring_step_grapheme_forward(fam, 3), size_t(6)); - ensure_equals("fam end -> 'i'", wstring_step_grapheme_forward(fam, 6), size_t(7)); + CHECK(wstring_step_grapheme_forward(fam, 0) == size_t(1)); + UNSCOPED_INFO("fam start jumps past cluster"); + CHECK(wstring_step_grapheme_forward(fam, 1) == size_t(6)); + UNSCOPED_INFO("from mid-cluster snaps past"); + CHECK(wstring_step_grapheme_forward(fam, 3) == size_t(6)); + CHECK(wstring_step_grapheme_forward(fam, 6) == size_t(7)); // Regional indicator flag: US = U+1F1FA U+1F1F8. One step covers both. std::u32string flag = { (llwchar)0x1F1FA, (llwchar)0x1F1F8 }; - ensure_equals("flag 0->past both", - wstring_step_grapheme_forward(flag, 0), size_t(2)); + CHECK(wstring_step_grapheme_forward(flag, 0) == size_t(2)); } // Backward step is the mirror: one codepoint through ASCII, snap to the // start of any emoji cluster we'd otherwise land inside. - template<> template<> - void llstring_utf_object_t::test<101>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: stepping back crosses one character or one whole cluster", "[LLStringUTF]") { std::u32string ascii = { (llwchar)'a', (llwchar)'b', (llwchar)'c' }; - ensure_equals("ascii 3->2", wstring_step_grapheme_backward(ascii, 3), size_t(2)); - ensure_equals("ascii 1->0", wstring_step_grapheme_backward(ascii, 1), size_t(0)); - ensure_equals("ascii at 0 stays", wstring_step_grapheme_backward(ascii, 0), size_t(0)); + CHECK(wstring_step_grapheme_backward(ascii, 3) == size_t(2)); + CHECK(wstring_step_grapheme_backward(ascii, 1) == size_t(0)); + CHECK(wstring_step_grapheme_backward(ascii, 0) == size_t(0)); std::u32string fam = { (llwchar)'H', (llwchar)0x1F468, (llwchar)0x200D, (llwchar)0x1F469, (llwchar)0x200D, (llwchar)0x1F467, (llwchar)'i' }; - ensure_equals("end of string -> 'i' start", - wstring_step_grapheme_backward(fam, 7), size_t(6)); - ensure_equals("'i' start snaps back to cluster start", - wstring_step_grapheme_backward(fam, 6), size_t(1)); - ensure_equals("mid-cluster snaps to cluster start", - wstring_step_grapheme_backward(fam, 4), size_t(1)); - ensure_equals("cluster start -> 'H'", - wstring_step_grapheme_backward(fam, 1), size_t(0)); + UNSCOPED_INFO("end of string -> 'i' start"); + CHECK(wstring_step_grapheme_backward(fam, 7) == size_t(6)); + UNSCOPED_INFO("'i' start snaps back to cluster start"); + CHECK(wstring_step_grapheme_backward(fam, 6) == size_t(1)); + UNSCOPED_INFO("mid-cluster snaps to cluster start"); + CHECK(wstring_step_grapheme_backward(fam, 4) == size_t(1)); + UNSCOPED_INFO("cluster start -> 'H'"); + CHECK(wstring_step_grapheme_backward(fam, 1) == size_t(0)); std::u32string flag = { (llwchar)0x1F1FA, (llwchar)0x1F1F8 }; - ensure_equals("flag end -> 0 (whole flag is one cluster)", - wstring_step_grapheme_backward(flag, 2), size_t(0)); + UNSCOPED_INFO("the whole flag is one cluster"); + CHECK(wstring_step_grapheme_backward(flag, 2) == size_t(0)); } // --------------------------------------------------------------- @@ -1270,56 +1129,47 @@ namespace tut // emoji-cluster start and flags still pair up. // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<102>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: LLStringOps::isPictographBase is Extended_Pictographic and flags", "[LLStringUTF]") { // Astral emoji: both predicates accept. - ensure("isPictographBase rocket", - LLStringOps::isPictographBase((llwchar)0x1F680)); - ensure("isPictographBase fire", - LLStringOps::isPictographBase((llwchar)0x1F525)); + UNSCOPED_INFO("isPictographBase rocket"); + CHECK(LLStringOps::isPictographBase((llwchar)0x1F680)); + UNSCOPED_INFO("isPictographBase fire"); + CHECK(LLStringOps::isPictographBase((llwchar)0x1F525)); // BMP pictographs that isEmoji rejects but isPictographBase accepts. - ensure("isPictographBase heart", - LLStringOps::isPictographBase((llwchar)0x2764)); - ensure("isPictographBase copyright", - LLStringOps::isPictographBase((llwchar)0x00A9)); - ensure("isPictographBase registered", - LLStringOps::isPictographBase((llwchar)0x00AE)); + UNSCOPED_INFO("isPictographBase heart"); + CHECK(LLStringOps::isPictographBase((llwchar)0x2764)); + UNSCOPED_INFO("isPictographBase copyright"); + CHECK(LLStringOps::isPictographBase((llwchar)0x00A9)); + UNSCOPED_INFO("isPictographBase registered"); + CHECK(LLStringOps::isPictographBase((llwchar)0x00AE)); // Plain ASCII / digits never qualify. - ensure("not isPictographBase 'A'", - !LLStringOps::isPictographBase((llwchar)'A')); - ensure("not isPictographBase '0'", - !LLStringOps::isPictographBase((llwchar)'0')); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)'A')); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)'0')); // ZWJ and VS-16 are extenders, not bases. - ensure("not isPictographBase ZWJ", - !LLStringOps::isPictographBase((llwchar)0x200D)); - ensure("not isPictographBase VS-16", - !LLStringOps::isPictographBase((llwchar)0xFE0F)); - ensure("not isPictographBase U+20000", - !LLStringOps::isPictographBase((llwchar)0x20000)); - ensure("not isPictographBase U+1FFF", - !LLStringOps::isPictographBase((llwchar)0x1FFF)); - ensure("not isPictographBase U+3300 boundary", - !LLStringOps::isPictographBase((llwchar)0x3300)); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x200D)); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0xFE0F)); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x20000)); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x1FFF)); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x3300)); // A flag is two regional indicators and nothing else. Unicode does // not call them pictographic, so they are named here separately; // reading Extended_Pictographic alone would stop flags clustering. - ensure("isPictographBase regional indicator", - LLStringOps::isPictographBase((llwchar)0x1F1FA)); + CHECK(LLStringOps::isPictographBase((llwchar)0x1F1FA)); // The old range ran from U+2000 to U+3300, which swept up punctuation, // maths, and every kana and Hangul jamo along the way. - ensure("not isPictographBase EN QUAD", - !LLStringOps::isPictographBase((llwchar)0x2000)); - ensure("not isPictographBase summation", - !LLStringOps::isPictographBase((llwchar)0x2211)); - ensure("not isPictographBase hiragana", - !LLStringOps::isPictographBase((llwchar)0x3042)); - ensure("not isPictographBase katakana", - !LLStringOps::isPictographBase((llwchar)0x30A2)); - ensure("not isPictographBase Hangul jamo", - !LLStringOps::isPictographBase((llwchar)0x3131)); + UNSCOPED_INFO("not isPictographBase EN QUAD"); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x2000)); + UNSCOPED_INFO("not isPictographBase summation"); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x2211)); + UNSCOPED_INFO("not isPictographBase hiragana"); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x3042)); + UNSCOPED_INFO("not isPictographBase katakana"); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x30A2)); + UNSCOPED_INFO("not isPictographBase Hangul jamo"); + CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x3131)); } // --------------------------------------------------------------- @@ -1329,8 +1179,7 @@ namespace tut // wstring_find_emoji_clusters skips. // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<103>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: wstring_emoji_range_at gives the cluster or pictograph at a position", "[LLStringUTF]") { // ZWJ family "H 👨 ZWJ 👩 ZWJ 👧 i" — cluster spans [1, 6). std::u32string fam = { (llwchar)'H', @@ -1341,79 +1190,81 @@ namespace tut auto on_lead = wstring_emoji_range_at(fam, 1); auto inside = wstring_emoji_range_at(fam, 3); auto on_end = wstring_emoji_range_at(fam, 6); - ensure_equals("fam pos==first.first", on_lead.first, size_t(1)); - ensure_equals("fam pos==first.second", on_lead.second, size_t(6)); - ensure_equals("fam mid.first", inside.first, size_t(1)); - ensure_equals("fam mid.second", inside.second, size_t(6)); + CHECK(on_lead.first == size_t(1)); + CHECK(on_lead.second == size_t(6)); + CHECK(inside.first == size_t(1)); + CHECK(inside.second == size_t(6)); // pos == run.second is past the cluster — falls through to the // codepoint there ('i', non-emoji), so empty range. - ensure_equals("fam past-end empty", on_end.first, on_end.second); + CHECK(on_end.first == on_end.second); // Plain ASCII: empty range everywhere. std::u32string ascii = { (llwchar)'a', (llwchar)'b', (llwchar)'c' }; auto a0 = wstring_emoji_range_at(ascii, 0); auto a2 = wstring_emoji_range_at(ascii, 2); - ensure_equals("ascii 0 empty", a0.first, a0.second); - ensure_equals("ascii 2 empty", a2.first, a2.second); + CHECK(a0.first == a0.second); + CHECK(a2.first == a2.second); // Single-codepoint emoji 😀 (U+1F600): not in cluster list, but // the helper synthesises [pos, pos+1). std::u32string lone = { (llwchar)0x1F600 }; auto le = wstring_emoji_range_at(lone, 0); - ensure_equals("lone emoji.first", le.first, size_t(0)); - ensure_equals("lone emoji.second", le.second, size_t(1)); + CHECK(le.first == size_t(0)); + CHECK(le.second == size_t(1)); // Out-of-bounds and exact-end positions are empty. auto at_end = wstring_emoji_range_at(ascii, 3); auto past_end = wstring_emoji_range_at(ascii, 99); - ensure_equals("ascii at-end empty", at_end.first, at_end.second); - ensure_equals("ascii oob empty", past_end.first, past_end.second); + CHECK(at_end.first == at_end.second); + CHECK(past_end.first == past_end.second); // Empty wstring: empty range. auto empty = wstring_emoji_range_at(std::u32string(), 0); - ensure_equals("empty wstr empty", empty.first, empty.second); + CHECK(empty.first == empty.second); // Flag pair 🇺🇸 — one cluster spanning [0, 2). std::u32string flag = { (llwchar)0x1F1FA, (llwchar)0x1F1F8 }; auto f0 = wstring_emoji_range_at(flag, 0); auto f1 = wstring_emoji_range_at(flag, 1); - ensure_equals("flag at lead.first", f0.first, size_t(0)); - ensure_equals("flag at lead.second", f0.second, size_t(2)); - ensure_equals("flag mid.first", f1.first, size_t(0)); - ensure_equals("flag mid.second", f1.second, size_t(2)); + CHECK(f0.first == size_t(0)); + CHECK(f0.second == size_t(2)); + CHECK(f1.first == size_t(0)); + CHECK(f1.second == size_t(2)); // BMP pictographs — the orthodox cross bug. isPictographBase accepts // U+2000..U+33FF plus © U+00A9 and ® U+00AE; isEmoji rejects them. // Tooltip lookup must still find them. - auto check_bmp = [](llwchar cp, const char* tag) { + auto require_single_range = [](llwchar cp, const char* tag) { std::u32string ws { cp }; auto r = wstring_emoji_range_at(ws, 0); - ensure_equals(std::string(tag) + ".first", r.first, size_t(0)); - ensure_equals(std::string(tag) + ".second", r.second, size_t(1)); + INFO(tag); + REQUIRE(r.first == size_t(0)); + REQUIRE(r.second == size_t(1)); }; - check_bmp((llwchar)0x2626, "orthodox cross"); - check_bmp((llwchar)0x00A9, "copyright"); - check_bmp((llwchar)0x00AE, "registered"); - check_bmp((llwchar)0x2764, "heart"); - check_bmp((llwchar)0x2693, "anchor"); + require_single_range((llwchar)0x2626, "orthodox cross"); + require_single_range((llwchar)0x00A9, "copyright"); + require_single_range((llwchar)0x00AE, "registered"); + require_single_range((llwchar)0x2764, "heart"); + require_single_range((llwchar)0x2693, "anchor"); // Bare extenders / out-of-range codepoints get an empty range — // isPictographBase explicitly rejects these so they don't trigger // a tooltip lookup on their own. - auto check_no_range = [](llwchar cp, const char* tag) { + auto require_no_range = [](llwchar cp, const char* tag) { std::u32string ws { cp }; auto r = wstring_emoji_range_at(ws, 0); - ensure_equals(std::string(tag) + " empty", r.first, r.second); + INFO(tag); + REQUIRE(r.first == r.second); }; - check_no_range((llwchar)0x200D, "bare ZWJ"); - check_no_range((llwchar)0xFE0F, "bare VS-16"); - check_no_range((llwchar)0xFE0E, "bare VS-15"); - check_no_range((llwchar)0x20E3, "bare keycap combiner"); - check_no_range((llwchar)0x1F3FB, "bare skintone"); - check_no_range((llwchar)0xE0067, "bare tag char"); - check_no_range((llwchar)0x1FFF, "U+1FFF below astral"); - check_no_range((llwchar)0x3300, "U+3300 above BMP"); - check_no_range((llwchar)0x20000, "U+20000 above astral"); + require_no_range((llwchar)0x200D, "bare ZWJ"); + require_no_range((llwchar)0xFE0F, "bare VS-16"); + require_no_range((llwchar)0xFE0E, "bare VS-15"); + require_no_range((llwchar)0x20E3, "bare keycap combiner"); + require_no_range((llwchar)0x1F3FB, "bare skintone"); + require_no_range((llwchar)0xE0067, "bare tag char"); + require_no_range((llwchar)0x1FFF, "U+1FFF below astral"); + require_no_range((llwchar)0x3300, "U+3300 above BMP"); + require_no_range((llwchar)0x20000, "U+20000 above astral"); // Back-to-back flags 🇺🇸🇯🇵 — pos lookup must find the *second* run // when pos has passed the first. Exercises the ordered-iteration @@ -1423,12 +1274,12 @@ namespace tut auto tf0 = wstring_emoji_range_at(two_flags, 0); auto tf2 = wstring_emoji_range_at(two_flags, 2); auto tf3 = wstring_emoji_range_at(two_flags, 3); - ensure_equals("two_flags[0].first", tf0.first, size_t(0)); - ensure_equals("two_flags[0].second", tf0.second, size_t(2)); - ensure_equals("two_flags[2].first", tf2.first, size_t(2)); - ensure_equals("two_flags[2].second", tf2.second, size_t(4)); - ensure_equals("two_flags mid 2nd flag", tf3.first, size_t(2)); - ensure_equals("two_flags mid 2nd second", tf3.second, size_t(4)); + CHECK(tf0.first == size_t(0)); + CHECK(tf0.second == size_t(2)); + CHECK(tf2.first == size_t(2)); + CHECK(tf2.second == size_t(4)); + CHECK(tf3.first == size_t(2)); + CHECK(tf3.second == size_t(4)); // Mixed "A 🚀 B": ASCII positions return empty, the rocket position // returns its [pos, pos+1) range via the single-codepoint fallback. @@ -1436,10 +1287,10 @@ namespace tut auto m0 = wstring_emoji_range_at(mixed, 0); auto m1 = wstring_emoji_range_at(mixed, 1); auto m2 = wstring_emoji_range_at(mixed, 2); - ensure_equals("mixed A empty", m0.first, m0.second); - ensure_equals("mixed rocket.first", m1.first, size_t(1)); - ensure_equals("mixed rocket.second", m1.second, size_t(2)); - ensure_equals("mixed B empty", m2.first, m2.second); + CHECK(m0.first == m0.second); + CHECK(m1.first == size_t(1)); + CHECK(m1.second == size_t(2)); + CHECK(m2.first == m2.second); } // --------------------------------------------------------------- @@ -1451,18 +1302,16 @@ namespace tut // the contract so neither group regresses. // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<104>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: grapheme alignment moves only positions inside a cluster", "[LLStringUTF]") { // ASCII: every position is already on a boundary, so both halves // are identity functions across the entire range. std::u32string ascii = { (llwchar)'a', (llwchar)'b', (llwchar)'c' }; for (size_t p = 0; p <= ascii.size(); ++p) { - ensure_equals("ascii align_backward", - wstring_grapheme_align_backward(ascii, p), p); - ensure_equals("ascii align_forward", - wstring_grapheme_align_forward(ascii, p), p); + CAPTURE(p); + REQUIRE(wstring_grapheme_align_backward(ascii, p) == p); + REQUIRE(wstring_grapheme_align_forward(ascii, p) == p); } // ZWJ family "H 👨 ZWJ 👩 ZWJ 👧 i" — cluster spans [1, 6). @@ -1474,43 +1323,32 @@ namespace tut // Endpoint short-circuits: pos == 0 stays 0 (backward); pos >= size // stays size (forward) regardless of trailing emoji. - ensure_equals("fam align_backward(0)", - wstring_grapheme_align_backward(fam, 0), size_t(0)); - ensure_equals("fam align_backward(size)", - wstring_grapheme_align_backward(fam, fam.size()), fam.size()); - ensure_equals("fam align_forward(size)", - wstring_grapheme_align_forward(fam, fam.size()), fam.size()); - ensure_equals("fam align_forward(99)", - wstring_grapheme_align_forward(fam, 99), fam.size()); + CHECK(wstring_grapheme_align_backward(fam, 0) == size_t(0)); + CHECK(wstring_grapheme_align_backward(fam, fam.size()) == fam.size()); + CHECK(wstring_grapheme_align_forward(fam, fam.size()) == fam.size()); + CHECK(wstring_grapheme_align_forward(fam, 99) == fam.size()); // pos == run.first — strictly outside, returned unchanged. This is // the contract LLTextEditor::prevWordPos relies on: a word-walk // that already landed on a cluster start is fine and must not be // pulled backward into something else. - ensure_equals("fam align_backward(1)", - wstring_grapheme_align_backward(fam, 1), size_t(1)); - ensure_equals("fam align_forward(1)", - wstring_grapheme_align_forward(fam, 1), size_t(1)); + CHECK(wstring_grapheme_align_backward(fam, 1) == size_t(1)); + CHECK(wstring_grapheme_align_forward(fam, 1) == size_t(1)); // pos == run.second — also strictly outside, unchanged. - ensure_equals("fam align_backward(6)", - wstring_grapheme_align_backward(fam, 6), size_t(6)); - ensure_equals("fam align_forward(6)", - wstring_grapheme_align_forward(fam, 6), size_t(6)); + CHECK(wstring_grapheme_align_backward(fam, 6) == size_t(6)); + CHECK(wstring_grapheme_align_forward(fam, 6) == size_t(6)); // pos strictly inside [2..5] — backward snaps to 1, forward to 6. for (size_t p = 2; p <= 5; ++p) { - ensure_equals("fam mid align_backward", - wstring_grapheme_align_backward(fam, p), size_t(1)); - ensure_equals("fam mid align_forward", - wstring_grapheme_align_forward(fam, p), size_t(6)); + CAPTURE(p); + REQUIRE(wstring_grapheme_align_backward(fam, p) == size_t(1)); + REQUIRE(wstring_grapheme_align_forward(fam, p) == size_t(6)); } // Outside the cluster — surrounding ASCII positions unchanged. - ensure_equals("fam align_backward(7)", - wstring_grapheme_align_backward(fam, 7), size_t(7)); - ensure_equals("fam align_forward(7)", - wstring_grapheme_align_forward(fam, 7), size_t(7)); + CHECK(wstring_grapheme_align_backward(fam, 7) == size_t(7)); + CHECK(wstring_grapheme_align_forward(fam, 7) == size_t(7)); // Two disjoint clusters in one string — flag at [1,3), family at // [4,9). Inside-the-second-cluster lookups must reach the second @@ -1525,25 +1363,19 @@ namespace tut (llwchar)0x1F467, (llwchar)'!' }; // Flag interior (pos == 2) → flag.first 1. - ensure_equals("two flag mid backward", - wstring_grapheme_align_backward(two, 2), size_t(1)); - ensure_equals("two flag mid forward", - wstring_grapheme_align_forward(two, 2), size_t(3)); + CHECK(wstring_grapheme_align_backward(two, 2) == size_t(1)); + CHECK(wstring_grapheme_align_forward(two, 2) == size_t(3)); // Family interior (pos == 6) → family bounds [4, 9). - ensure_equals("two fam mid backward", - wstring_grapheme_align_backward(two, 6), size_t(4)); - ensure_equals("two fam mid forward", - wstring_grapheme_align_forward(two, 6), size_t(9)); + CHECK(wstring_grapheme_align_backward(two, 6) == size_t(4)); + CHECK(wstring_grapheme_align_forward(two, 6) == size_t(9)); // Between the two clusters (the space at pos 3) — unchanged. - ensure_equals("two between backward", - wstring_grapheme_align_backward(two, 3), size_t(3)); - ensure_equals("two between forward", - wstring_grapheme_align_forward(two, 3), size_t(3)); + CHECK(wstring_grapheme_align_backward(two, 3) == size_t(3)); + CHECK(wstring_grapheme_align_forward(two, 3) == size_t(3)); } // --------------------------------------------------------------- // step_grapheme + emoji_range_at: malformed-input pins. The simple - // ZWJ-family / flag cases are covered above (tests 100/101/103/104); + // ZWJ-family / flag cases are covered by the cases above; // here we cover the rougher edges that show up in user-pasted text: // empty strings, out-of-bounds positions, clusters at the very ends // of the buffer, two clusters back-to-back with no separator, and @@ -1552,49 +1384,40 @@ namespace tut // synthetic singleton range). // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<105>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: grapheme stepping and emoji ranges in edge cases", "[LLStringUTF]") { // Empty wstring — no grapheme moves possible. std::u32string empty; - ensure_equals("empty step_forward", wstring_step_grapheme_forward(empty, 0), size_t(0)); - ensure_equals("empty step_backward", wstring_step_grapheme_backward(empty, 0), size_t(0)); + CHECK(wstring_step_grapheme_forward(empty, 0) == size_t(0)); + CHECK(wstring_step_grapheme_backward(empty, 0) == size_t(0)); // Out-of-bounds position — both directions clamp into [0, size], as the // header says. The old walker returned pos-1 here, handing a caller an // index past the end of its own string. std::u32string ascii = { (llwchar)'a', (llwchar)'b' }; - ensure_equals("oob step_forward", wstring_step_grapheme_forward(ascii, 99), size_t(2)); - ensure_equals("oob step_backward", wstring_step_grapheme_backward(ascii, 99), size_t(2)); + CHECK(wstring_step_grapheme_forward(ascii, 99) == size_t(2)); + CHECK(wstring_step_grapheme_backward(ascii, 99) == size_t(2)); // Cluster at start of string — backward from inside or just-past // snaps to 0 (cluster.first). std::u32string lead = { (llwchar)0x1F1FA, (llwchar)0x1F1F8, (llwchar)'X' }; - ensure_equals("lead flag step_forward(0)", - wstring_step_grapheme_forward(lead, 0), size_t(2)); - ensure_equals("lead flag step_backward(2)", - wstring_step_grapheme_backward(lead, 2), size_t(0)); + CHECK(wstring_step_grapheme_forward(lead, 0) == size_t(2)); + CHECK(wstring_step_grapheme_backward(lead, 2) == size_t(0)); // Cluster at end of string — forward from inside snaps to size. std::u32string trail = { (llwchar)'X', (llwchar)0x1F1FA, (llwchar)0x1F1F8 }; - ensure_equals("trail flag step_forward(1)", - wstring_step_grapheme_forward(trail, 1), size_t(3)); - ensure_equals("trail flag step_backward(size)", - wstring_step_grapheme_backward(trail, trail.size()), size_t(1)); + CHECK(wstring_step_grapheme_forward(trail, 1) == size_t(3)); + CHECK(wstring_step_grapheme_backward(trail, trail.size()) == size_t(1)); // Two clusters back-to-back with no separator: step_forward from // inside cluster A must land at A.end (which is also B.first), not // skip into cluster B. std::u32string two_flags = { (llwchar)0x1F1FA, (llwchar)0x1F1F8, (llwchar)0x1F1EF, (llwchar)0x1F1F5 }; - ensure_equals("two flags forward", - wstring_step_grapheme_forward(two_flags, 0), size_t(2)); - ensure_equals("two flags forward 2nd", - wstring_step_grapheme_forward(two_flags, 2), size_t(4)); - ensure_equals("two flags backward", - wstring_step_grapheme_backward(two_flags, 4), size_t(2)); - ensure_equals("two flags backward 1st", - wstring_step_grapheme_backward(two_flags, 2), size_t(0)); + CHECK(wstring_step_grapheme_forward(two_flags, 0) == size_t(2)); + CHECK(wstring_step_grapheme_forward(two_flags, 2) == size_t(4)); + CHECK(wstring_step_grapheme_backward(two_flags, 4) == size_t(2)); + CHECK(wstring_step_grapheme_backward(two_flags, 2) == size_t(0)); // emoji_range_at on a tag char inside a subdivision flag — the // hit-test caller wants the whole flag's range so the tooltip key @@ -1610,14 +1433,15 @@ namespace tut for (size_t p = 1; p < 8; ++p) { auto r = wstring_emoji_range_at(subdiv, p); - ensure_equals("subdiv begin", r.first, size_t(1)); - ensure_equals("subdiv end", r.second, size_t(8)); + CAPTURE(p); + REQUIRE(r.first == size_t(1)); + REQUIRE(r.second == size_t(8)); } // pos on the surrounding ASCII falls outside the cluster. auto pre = wstring_emoji_range_at(subdiv, 0); auto post = wstring_emoji_range_at(subdiv, 8); - ensure_equals("subdiv pre empty", pre.first, pre.second); - ensure_equals("subdiv post empty", post.first, post.second); + CHECK(pre.first == pre.second); + CHECK(post.first == post.second); } // --------------------------------------------------------------- @@ -1629,10 +1453,9 @@ namespace tut // (e.g. a bug fix in the loop) needs to fail loudly here. // --------------------------------------------------------------- - template<> template<> - void llstring_utf_object_t::test<106>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_emoji_range_at answers the same given the cluster list", "[LLStringUTF]") { - // Same mixed-content string used in test<104>: ASCII + flag + + // Same mixed-content string as the align case above: ASCII + flag + // space + ZWJ family + ASCII. Two disjoint clusters at [1,3) and // [4,9), so the loop bodies exercise both early-out and middle- // of-vector iteration. @@ -1645,7 +1468,7 @@ namespace tut (llwchar)'!' }; const std::string utf8 = to_utf8(two); const auto clusters = utf8str_find_emoji_clusters(utf8); - ensure_equals("two clusters", clusters.size(), size_t(2)); + CHECK(clusters.size() == size_t(2)); // Handing the list in has to give the same answer as building it // inside, at every position including the ones inside a character. @@ -1653,161 +1476,242 @@ namespace tut { auto r1 = utf8str_emoji_range_at(utf8, p); auto r2 = utf8str_emoji_range_at(utf8, p, clusters); - ensure_equals("range_at first", r1.first, r2.first); - ensure_equals("range_at second", r1.second, r2.second); + CAPTURE(p); + REQUIRE(r1.first == r2.first); + REQUIRE(r1.second == r2.second); } // Empty string -- pinning that the with-clusters overload handles a // degenerate empty cluster vector identically to the no-clusters form. const EmojiClusterList no_clusters; - ensure_equals("empty range_at first overload", - utf8str_emoji_range_at(std::string_view(), 0).first, + CHECK(utf8str_emoji_range_at(std::string_view(), 0).first == utf8str_emoji_range_at(std::string_view(), 0, no_clusters).first); } + // utf8str_substChar replaces every occurrence of a code point (incl. + // astrals) with another code point, routing through std::u32string so the + // byte-length change is handled for us. + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_substChar replaces every occurrence of a code point", "[LLStringUTF]") + { + const std::string in = "A\xF0\x9F\x9A\x80" "B\xF0\x9F\x9A\x80" "C"; + const std::string out = utf8str_substChar(in, (llwchar)0x1F680, (llwchar)'X'); + CHECK(out == std::string("AXBXC")); + + const std::string in2 = "A\xE6\x97\xA5" "B"; + const std::string out2 = utf8str_substChar(in2, (llwchar)0x65E5, (llwchar)'.'); + CHECK(out2 == std::string("A.B")); + } + + // utf8str_makeASCII replaces every non-ASCII llwchar (>0x7F) with '?' + // before serializing back to utf-8. + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_makeASCII turns each non-ASCII character into a question mark", "[LLStringUTF]") + { + const std::string mixed = "A\xE6\x97\xA5" "B\xF0\x9F\x9A\x80" "C"; + const std::string expected = "A?B?C"; + CHECK(utf8str_makeASCII(mixed) == expected); + CHECK(utf8str_makeASCII("Hello") == std::string("Hello")); + } + + // mbcsstring_makeASCII is byte-level: every byte >0x7F becomes '?'. So a + // 3-byte UTF-8 character yields three '?' rather than one. + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: mbcsstring_makeASCII turns each non-ASCII byte into a question mark", "[LLStringUTF]") + { + const std::string mixed = "A\xE6\x97\xA5" "B"; + const std::string expected = "A???B"; + CHECK(mbcsstring_makeASCII(mixed) == expected); + } + + // utf8str_removeCRLF strips CR (0x0D) only — LF is preserved. + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_removeCRLF strips CR and keeps LF", "[LLStringUTF]") + { + CHECK(utf8str_removeCRLF("a\r\nb") == std::string("a\nb")); + CHECK(utf8str_removeCRLF("a\rb") == std::string("ab")); + CHECK(utf8str_removeCRLF("a\nb") == std::string("a\nb")); + CHECK(utf8str_removeCRLF("") == std::string()); + } + + // utf8str_split splits at the last split-token at or before maxlen bytes, + // falling back to a hard byte cut when no token is in range. Non-final + // chunks retain the trailing split-token (current implementation). + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_split splits at the last split-token at or before maxlen bytes", "[LLStringUTF]") + { + { + std::list out; + utf8str_split(out, "Hello there world", 10, ' '); + REQUIRE(out.size() == size_t(3)); + auto it = out.begin(); + CHECK(*it++ == std::string("Hello")); + CHECK(*it++ == std::string("there ")); + CHECK(*it == std::string("world")); + } + { + std::list out; + utf8str_split(out, "short", 10, ' '); + REQUIRE(out.size() == size_t(1)); + CHECK(out.front() == std::string("short")); + } + } + + // wchar_utf8_preview formats the code point in hex, plus (when multi-byte) + // the decoded UTF-8 byte sequence also in hex. + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: wchar_utf8_preview gives the code point and its UTF-8 bytes in hex", "[LLStringUTF]") + { + CHECK(wchar_utf8_preview((llwchar)'A') == std::string("41")); + // é = U+00E9 -> 0xC3 0xA9 + CHECK(wchar_utf8_preview((llwchar)0x00E9) == + std::string("E9 [C3, A9]")); + // Rocket = U+1F680 -> 0xF0 0x9F 0x9A 0x80 + CHECK(wchar_utf8_preview((llwchar)0x1F680) == + std::string("1F680 [F0, 9F, 9A, 80]")); + } + // The grapheme walkers moved from an emoji-only cluster list to full // UAX #29 via ICU. These are the cases the old walker stepped // through one codepoint at a time. - template<> template<> - void llstring_utf_object_t::test<117>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: grapheme stepping follows UAX #29 beyond emoji", "[LLStringUTF]") { // "e" + COMBINING ACUTE, then "x". The base and its mark are one // cluster; the old walker split them. const std::u32string combining = { (llwchar)'e', (llwchar)0x0301, (llwchar)'x' }; - ensure_equals("combining fwd from 0", wstring_step_grapheme_forward(combining, 0), size_t(2)); - ensure_equals("combining back from 2", wstring_step_grapheme_backward(combining, 2), size_t(0)); - ensure_equals("combining align back mid", wstring_grapheme_align_backward(combining, 1), size_t(0)); - ensure_equals("combining align fwd mid", wstring_grapheme_align_forward(combining, 1), size_t(2)); + CHECK(wstring_step_grapheme_forward(combining, 0) == size_t(2)); + CHECK(wstring_step_grapheme_backward(combining, 2) == size_t(0)); + CHECK(wstring_grapheme_align_backward(combining, 1) == size_t(0)); + CHECK(wstring_grapheme_align_forward(combining, 1) == size_t(2)); // Hangul LVT: HANGUL SYLLABLE inputs as jamo L + V + T are one cluster. const std::u32string hangul = { (llwchar)0x1100, (llwchar)0x1161, (llwchar)0x11A8, (llwchar)'!' }; - ensure_equals("hangul fwd from 0", wstring_step_grapheme_forward(hangul, 0), size_t(3)); - ensure_equals("hangul back from 3", wstring_step_grapheme_backward(hangul, 3), size_t(0)); + CHECK(wstring_step_grapheme_forward(hangul, 0) == size_t(3)); + CHECK(wstring_step_grapheme_backward(hangul, 3) == size_t(0)); // Regional indicator pairs still pair up, and a third RI starts a new // cluster rather than joining the first two. const std::u32string flags = { (llwchar)0x1F1FA, (llwchar)0x1F1F8, (llwchar)0x1F1FA }; - ensure_equals("flag pair fwd", wstring_step_grapheme_forward(flags, 0), size_t(2)); - ensure_equals("third RI is its own", wstring_step_grapheme_forward(flags, 2), size_t(3)); + CHECK(wstring_step_grapheme_forward(flags, 0) == size_t(2)); + CHECK(wstring_step_grapheme_forward(flags, 2) == size_t(3)); // ZWJ family stays one cluster, as before. const std::u32string family = { (llwchar)0x1F468, (llwchar)0x200D, (llwchar)0x1F469, (llwchar)0x200D, (llwchar)0x1F467, (llwchar)'!' }; - ensure_equals("zwj family fwd", wstring_step_grapheme_forward(family, 0), size_t(5)); - ensure_equals("zwj family back", wstring_step_grapheme_backward(family, 5), size_t(0)); + CHECK(wstring_step_grapheme_forward(family, 0) == size_t(5)); + CHECK(wstring_step_grapheme_backward(family, 5) == size_t(0)); // GB4 breaks after LF, which is also what bounds the backward scan. const std::u32string lines = { (llwchar)'a', (llwchar)'\n', (llwchar)'e', (llwchar)0x0301 }; - ensure_equals("break after lf", wstring_step_grapheme_backward(lines, 2), size_t(1)); - ensure_equals("cluster after lf", wstring_step_grapheme_backward(lines, 4), size_t(2)); + CHECK(wstring_step_grapheme_backward(lines, 2) == size_t(1)); + UNSCOPED_INFO("cluster after lf"); + CHECK(wstring_step_grapheme_backward(lines, 4) == size_t(2)); // Degenerate input: empty, and positions past the end clamp. const std::u32string empty; - ensure_equals("empty fwd", wstring_step_grapheme_forward(empty, 0), size_t(0)); - ensure_equals("empty back", wstring_step_grapheme_backward(empty, 0), size_t(0)); - ensure_equals("past end fwd", wstring_step_grapheme_forward(combining, 99), combining.size()); - ensure_equals("past end align", wstring_grapheme_align_forward(combining, 99), combining.size()); + CHECK(wstring_step_grapheme_forward(empty, 0) == size_t(0)); + CHECK(wstring_step_grapheme_backward(empty, 0) == size_t(0)); + CHECK(wstring_step_grapheme_forward(combining, 99) == combining.size()); + CHECK(wstring_grapheme_align_forward(combining, 99) == combining.size()); // Stepping forward from every position must strictly advance, and // backward must strictly retreat, or a caret can wedge. for (size_t p = 0; p < family.size(); ++p) { - ensure("fwd advances", wstring_step_grapheme_forward(family, p) > p); + CAPTURE(p); + REQUIRE(wstring_step_grapheme_forward(family, p) > p); } for (size_t p = 1; p <= family.size(); ++p) { - ensure("back retreats", wstring_step_grapheme_backward(family, p) < p); + CAPTURE(p); + REQUIRE(wstring_step_grapheme_backward(family, p) < p); } } // Word stepping lands on the start of a word, stepping over the whitespace // between. UAX #29 via ICU, replacing an alnum-or-underscore test. - template<> template<> - void llstring_utf_object_t::test<118>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: word stepping lands on the start of each word", "[LLStringUTF]") { const std::u32string two_words = to_u32("foo bar"); - ensure_equals("fwd to next word", wstring_step_word_forward(two_words, 0), size_t(4)); - ensure_equals("fwd from next word", wstring_step_word_forward(two_words, 4), size_t(7)); - ensure_equals("back from end", wstring_step_word_backward(two_words, 7), size_t(4)); - ensure_equals("back over the space", wstring_step_word_backward(two_words, 4), size_t(0)); - ensure_equals("back from mid-word", wstring_step_word_backward(two_words, 5), size_t(4)); + CHECK(wstring_step_word_forward(two_words, 0) == size_t(4)); + CHECK(wstring_step_word_forward(two_words, 4) == size_t(7)); + CHECK(wstring_step_word_backward(two_words, 7) == size_t(4)); + CHECK(wstring_step_word_backward(two_words, 4) == size_t(0)); + UNSCOPED_INFO("back from mid-word"); + CHECK(wstring_step_word_backward(two_words, 5) == size_t(4)); // A full stop between letters does not split the word -- UAX #29 wants // example.com and 3.14 to hold together (WB6/WB7, FULL STOP is // MidNumLet). So the word here is "foo.bar" and the next one is "baz". const std::u32string dotted = to_u32("foo.bar baz"); - ensure_equals("dot does not split", wstring_step_word_forward(dotted, 0), size_t(8)); - ensure_equals("fwd from inside the dot word", - wstring_step_word_forward(dotted, 3), size_t(8)); + CHECK(wstring_step_word_forward(dotted, 0) == size_t(8)); + UNSCOPED_INFO("fwd from inside the dot word"); + CHECK(wstring_step_word_forward(dotted, 3) == size_t(8)); // A comma does separate, and standing on one used to wedge the cursor: // it is neither alnum nor a space, so both of the old loops declined to // move and the caller had to retry from pos+1 (LLLineEditor::removeWord // still carries that workaround). const std::u32string comma = to_u32("foo, bar"); - ensure_equals("fwd stops on the comma", wstring_step_word_forward(comma, 0), size_t(3)); - ensure_equals("fwd off the comma", wstring_step_word_forward(comma, 3), size_t(5)); - ensure("fwd never stands still", wstring_step_word_forward(comma, 3) > size_t(3)); + UNSCOPED_INFO("fwd stops on the comma"); + CHECK(wstring_step_word_forward(comma, 0) == size_t(3)); + UNSCOPED_INFO("fwd off the comma"); + CHECK(wstring_step_word_forward(comma, 3) == size_t(5)); + UNSCOPED_INFO("fwd never stands still"); + CHECK(wstring_step_word_forward(comma, 3) > size_t(3)); // An apostrophe is inside the word, not a break in it. The old walk // stopped at index 3, mid-word. const std::u32string contraction = to_u32("don't stop"); - ensure_equals("contraction is one word", - wstring_step_word_forward(contraction, 0), size_t(6)); - ensure_equals("back over a contraction", - wstring_step_word_backward(contraction, 10), size_t(6)); + CHECK(wstring_step_word_forward(contraction, 0) == size_t(6)); + UNSCOPED_INFO("back over a contraction"); + CHECK(wstring_step_word_backward(contraction, 10) == size_t(6)); // Tabs are whitespace to step over; the old test was == ' ' only. const std::u32string tabbed = to_u32("foo\tbar"); - ensure_equals("fwd over a tab", wstring_step_word_forward(tabbed, 0), size_t(4)); + CHECK(wstring_step_word_forward(tabbed, 0) == size_t(4)); // Neither direction crosses a newline, matching what the old walk did // by accident (a newline is neither a word char nor a space). const std::u32string lines = to_u32("ab\ncd"); - ensure_equals("fwd stops at eol", wstring_step_word_forward(lines, 0), size_t(2)); - ensure_equals("fwd parked on eol", wstring_step_word_forward(lines, 2), size_t(2)); - ensure_equals("back stops at bol", wstring_step_word_backward(lines, 5), size_t(3)); - ensure_equals("back parked on bol", wstring_step_word_backward(lines, 3), size_t(3)); + UNSCOPED_INFO("fwd stops at eol"); + CHECK(wstring_step_word_forward(lines, 0) == size_t(2)); + UNSCOPED_INFO("fwd parked on eol"); + CHECK(wstring_step_word_forward(lines, 2) == size_t(2)); + UNSCOPED_INFO("back stops at bol"); + CHECK(wstring_step_word_backward(lines, 5) == size_t(3)); + UNSCOPED_INFO("back parked on bol"); + CHECK(wstring_step_word_backward(lines, 3) == size_t(3)); // Trailing whitespace has no word after it, so the line's end is where // forward stops. const std::u32string trailing = to_u32("foo "); - ensure_equals("fwd into trailing space", - wstring_step_word_forward(trailing, 0), size_t(6)); + CHECK(wstring_step_word_forward(trailing, 0) == size_t(6)); // Degenerate input. const std::u32string empty; - ensure_equals("empty fwd", wstring_step_word_forward(empty, 0), size_t(0)); - ensure_equals("empty back", wstring_step_word_backward(empty, 0), size_t(0)); - ensure_equals("past end fwd", wstring_step_word_forward(two_words, 99), two_words.size()); - ensure_equals("past end back", wstring_step_word_backward(two_words, 99), size_t(4)); + CHECK(wstring_step_word_forward(empty, 0) == size_t(0)); + CHECK(wstring_step_word_backward(empty, 0) == size_t(0)); + CHECK(wstring_step_word_forward(two_words, 99) == two_words.size()); + CHECK(wstring_step_word_backward(two_words, 99) == size_t(4)); // Scripts without spaces still segment, rather than swallowing the // whole run the way an alnum test would. const std::u32string cjk = to_u32("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E ok"); - ensure("cjk advances", wstring_step_word_forward(cjk, 0) > size_t(0)); - ensure("cjk terminates", wstring_step_word_forward(cjk, 0) <= cjk.size()); + CHECK(wstring_step_word_forward(cjk, 0) > size_t(0)); + CHECK(wstring_step_word_forward(cjk, 0) <= cjk.size()); // Adjacent emoji are separate words, so a word step crosses exactly // one. Callers must not pre-step a grapheme and then ask for the next // word -- that lands two emoji away. const std::u32string two_emoji = { (llwchar)0x1F436, (llwchar)0x1F431, (llwchar)'x' }; - ensure_equals("one emoji forward", wstring_step_word_forward(two_emoji, 0), size_t(1)); - ensure_equals("one emoji back", wstring_step_word_backward(two_emoji, 2), size_t(1)); + CHECK(wstring_step_word_forward(two_emoji, 0) == size_t(1)); + CHECK(wstring_step_word_backward(two_emoji, 2) == size_t(1)); // A ZWJ sequence is a single word, however many codepoints it spans. const std::u32string flag_then_dog = { (llwchar)0x1F3F3, (llwchar)0xFE0F, (llwchar)0x200D, (llwchar)0x26A7, (llwchar)0xFE0F, (llwchar)0x1F436 }; - ensure_equals("zwj sequence is one word", - wstring_step_word_forward(flag_then_dog, 0), size_t(5)); - ensure_equals("back over the zwj sequence", - wstring_step_word_backward(flag_then_dog, 6), size_t(5)); + CHECK(wstring_step_word_forward(flag_then_dog, 0) == size_t(5)); + CHECK(wstring_step_word_backward(flag_then_dog, 6) == size_t(5)); } // UAX #14 line break opportunities, which replaced a spaces-plus-CJK-range // heuristic in LLFontGL::maxDrawableBytes. - template<> template<> - void llstring_utf_object_t::test<119>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: line break opportunities follow UAX #14", "[LLStringUTF]") { std::vector breaks; @@ -1815,106 +1719,105 @@ namespace tut // space and the next one starts on the word. This is what the old // start_of_last_word tracking computed, and it has to stay that way. wstring_line_break_opportunities(to_u32("hello world"), breaks); - ensure_equals("two opportunities", breaks.size(), size_t(2)); - ensure_equals("after the space", breaks[0], size_t(6)); - ensure_equals("and the end", breaks[1], size_t(11)); + REQUIRE(breaks.size() == size_t(2)); + CHECK(breaks[0] == size_t(6)); + CHECK(breaks[1] == size_t(11)); // The string's end is always an opportunity; 0 never is. wstring_line_break_opportunities(to_u32("unbroken"), breaks); - ensure_equals("single word, one opportunity", breaks.size(), size_t(1)); - ensure_equals("at the end", breaks[0], size_t(8)); + REQUIRE(breaks.size() == size_t(1)); + CHECK(breaks[0] == size_t(8)); // A non-breaking space is glue. The old code special-cased U+00A0 by // hand; here it simply produces no opportunity. wstring_line_break_opportunities(to_u32("a\xC2\xA0" "b"), breaks); - ensure_equals("nbsp does not break", breaks.size(), size_t(1)); - ensure_equals("only the end", breaks[0], size_t(3)); + REQUIRE(breaks.size() == size_t(1)); + CHECK(breaks[0] == size_t(3)); // CJK ideographs may be split between characters... const std::u32string cjk = to_u32("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E"); wstring_line_break_opportunities(cjk, breaks); - ensure("cjk splits between characters", breaks.size() > size_t(1)); + CHECK(breaks.size() > size_t(1)); // ...but a line may not begin with closing punctuation, so there is no // opportunity immediately before U+3002 IDEOGRAPHIC FULL STOP. The old // ideograph-range test would have broken there happily. const std::u32string cjk_stop = to_u32("\xE6\x97\xA5\xE6\x9C\xAC\xE3\x80\x82"); wstring_line_break_opportunities(cjk_stop, breaks); - ensure("no break before the full stop", - std::find(breaks.begin(), breaks.end(), size_t(2)) == breaks.end()); + CHECK(std::find(breaks.begin(), breaks.end(), size_t(2)) == breaks.end()); // Every opportunity is in range and strictly ascending, since the // consumer walks them with a single forward cursor. const std::u32string mixed = to_u32("one two\xC2\xA0three, four"); wstring_line_break_opportunities(mixed, breaks); - ensure("mixed has opportunities", !breaks.empty()); + REQUIRE_FALSE(breaks.empty()); for (size_t k = 0; k < breaks.size(); ++k) { - ensure("in range", breaks[k] > 0 && breaks[k] <= mixed.size()); + CAPTURE(k); + REQUIRE(breaks[k] > 0); + REQUIRE(breaks[k] <= mixed.size()); if (k > 0) { - ensure("ascending", breaks[k] > breaks[k - 1]); + REQUIRE(breaks[k] > breaks[k - 1]); } } - ensure_equals("ends at the end", breaks.back(), mixed.size()); + CHECK(breaks.back() == mixed.size()); // Empty input clears the caller's buffer rather than leaving it alone, // since the buffer is reused across lines. wstring_line_break_opportunities(std::u32string(), breaks); - ensure("empty clears", breaks.empty()); + CHECK(breaks.empty()); } // Case conversion, and the index map that lets a search fold its haystack // without losing track of where a match sits in the original. - template<> template<> - void llstring_utf_object_t::test<120>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: case conversion and mapping offsets back from a folded copy", "[LLStringUTF]") { // Uppercasing sharp s produces two characters. towupper could not do // this at all -- it maps one codepoint to one codepoint. std::string sharp_s = "stra\xC3\x9F" "e"; - ensure_equals("sharp s starts at 7 bytes", sharp_s.size(), size_t(7)); + CHECK(sharp_s.size() == size_t(7)); LLStringUtil::toUpper(sharp_s); - ensure_equals("uppercased grows", sharp_s, std::string("STRASSE")); + CHECK(sharp_s == std::string("STRASSE")); std::string plain = "Hello"; LLStringUtil::toLower(plain); - ensure_equals("ordinary lowercase", plain, std::string("hello")); + CHECK(plain == std::string("hello")); // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE lowercases to two // characters, so every offset after it shifts. This is the case that // breaks an offset taken from a folded copy. const std::string dotted_i = "a\xC4\xB0" "b"; - ensure_equals("four bytes in", dotted_i.size(), size_t(4)); + REQUIRE(dotted_i.size() == size_t(4)); std::string folded = dotted_i; LLStringUtil::toLower(folded); - ensure("fold grew", folded.size() > dotted_i.size()); + CHECK(folded.size() > dotted_i.size()); // The point of all this: find 'b' in the folded copy and land on the // right byte in the original. Taken raw, the offset would be past it. const size_t folded_at = folded.find('b'); - ensure("found in fold", folded_at != std::string::npos); - ensure("raw offset would be wrong", folded_at != size_t(3)); - ensure_equals("mapped offset is right", - utf8str_bytes_from_cased_bytes(dotted_i, folded_at, false), size_t(3)); + REQUIRE(folded_at != std::string::npos); + CHECK(folded_at != size_t(3)); + CHECK(utf8str_bytes_from_cased_bytes(dotted_i, folded_at, false) == size_t(3)); // Every prefix of the fold maps back onto a character boundary of the // original, and never past its end. for (size_t k = 0; k <= folded.size(); ++k) { const size_t back = utf8str_bytes_from_cased_bytes(dotted_i, k, false); - ensure("never past the end", back <= dotted_i.size()); - ensure("lands on a character start", - back == dotted_i.size() - || (((unsigned char)dotted_i[back]) & 0xC0) != 0x80); + CAPTURE(k); + REQUIRE(back <= dotted_i.size()); + REQUIRE((back == dotted_i.size() + || (((unsigned char)dotted_i[back]) & 0xC0) != 0x80)); } // ASCII stays one-to-one, so an offset needs no mapping at all. const std::string ascii = "ABC"; for (size_t k = 0; k <= ascii.size(); ++k) { - ensure_equals("ascii maps to itself", - utf8str_bytes_from_cased_bytes(ascii, k, false), k); + CAPTURE(k); + REQUIRE(utf8str_bytes_from_cased_bytes(ascii, k, false) == k); } // Sharp s changes the character count without changing the byte @@ -1925,40 +1828,38 @@ namespace tut const std::string strasse = "stra\xC3\x9F" "e"; std::string upper = strasse; LLStringUtil::toUpper(upper); - ensure_equals("same byte length", upper.size(), strasse.size()); - ensure_equals("but one more character", - utf8str_codepoint_count(upper), utf8str_codepoint_count(strasse) + 1); + REQUIRE(upper.size() == strasse.size()); + CHECK(utf8str_codepoint_count(upper) == utf8str_codepoint_count(strasse) + 1); const size_t back_from[] = { 0, 1, 2, 3, 4, 4, 6, 7 }; for (size_t k = 0; k <= upper.size(); ++k) { - ensure_equals("cased offset " + std::to_string(k) + " maps back", - utf8str_bytes_from_cased_bytes(strasse, k, true), back_from[k]); + CAPTURE(k); + REQUIRE(utf8str_bytes_from_cased_bytes(strasse, k, true) == back_from[k]); } } // The narrow forms case UTF-8 now, and the helper that brings an offset // taken against a cased copy back onto the text it was built from. - template<> template<> - void llstring_utf_object_t::test<121>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: narrow case conversion and offsets mapped back from a cased key", "[LLStringUTF]") { std::string s = "stra\xC3\x9F" "e"; LLStringUtil::toUpper(s); - ensure_equals("narrow toUpper is unicode", s, std::string("STRASSE")); + CHECK(s == std::string("STRASSE")); std::string acc = "\xC3\xA9" "cole"; LLStringUtil::toUpper(acc); - ensure_equals("narrow toUpper accents", acc, std::string("\xC3\x89" "COLE")); + CHECK(acc == std::string("\xC3\x89" "COLE")); std::string upper = "\xC3\x89" "COLE"; LLStringUtil::toLower(upper); - ensure_equals("narrow toLower accents", upper, std::string("\xC3\xA9" "cole")); + CHECK(upper == std::string("\xC3\xA9" "cole")); // ASCII is untouched, which is what the identifier and protocol callers // rely on. std::string ascii = "Image.PNG"; LLStringUtil::toLower(ascii); - ensure_equals("ascii unchanged", ascii, std::string("image.png")); + CHECK(ascii == std::string("image.png")); // The ff ligature uppercases to "FF": three bytes become two, so an // offset taken from the key sits BEFORE where the same text starts in @@ -1968,14 +1869,12 @@ namespace tut std::string key = label; LLStringUtil::toUpper(key); const size_t at = key.find("XYZ"); - ensure("found in the key", at != std::string::npos); - ensure("the ligature expanded, so the key is shorter", - key.size() < label.size()); + REQUIRE(at != std::string::npos); + CHECK(key.size() < label.size()); // xyz begins at byte 6 of the label and byte 5 of the key. - ensure_equals("offset maps onto the label's own bytes", - utf8str_bytes_from_cased_bytes(label, at, true), size_t(6)); - ensure("the raw offset would have been wrong", at != size_t(6)); + CHECK(utf8str_bytes_from_cased_bytes(label, at, true) == size_t(6)); + CHECK(at != size_t(6)); } // "straße" uppercases to "STRASSE": one codepoint longer, but the same @@ -1985,57 +1884,54 @@ namespace tut std::string key = label; LLStringUtil::toUpper(key); const size_t at = key.find("XYZ"); - ensure("found in the key", at != std::string::npos); - ensure_equals("same byte length either cased or not", key.size(), label.size()); + REQUIRE(at != std::string::npos); + CHECK(key.size() == label.size()); const size_t mapped = utf8str_bytes_from_cased_bytes(label, at, true); - ensure_equals("mapped to the label's own byte offset", mapped, size_t(8)); + CHECK(mapped == size_t(8)); // And the matched span's own length, which is not the key's. const size_t span_begin = utf8str_bytes_from_cased_bytes(label, 0, true); const size_t span_end = utf8str_bytes_from_cased_bytes(label, key.find(' '), true); - ensure_equals("word before the space is seven bytes", span_end - span_begin, size_t(7)); + CHECK(span_end - span_begin == size_t(7)); // Offset 0 and a whole-string offset are the degenerate ends. - ensure_equals("zero maps to zero", - utf8str_bytes_from_cased_bytes(label, 0, true), size_t(0)); - ensure_equals("whole string maps to its byte count", - utf8str_bytes_from_cased_bytes(label, key.size(), true), size_t(11)); + CHECK(utf8str_bytes_from_cased_bytes(label, 0, true) == size_t(0)); + CHECK(utf8str_bytes_from_cased_bytes(label, key.size(), true) == size_t(11)); // ASCII text maps one-to-one, so existing offsets are unaffected. const std::string plain = "hello world"; - ensure_equals("ascii maps straight through", - utf8str_bytes_from_cased_bytes(plain, 6, true), size_t(6)); + CHECK(utf8str_bytes_from_cased_bytes(plain, 6, true) == size_t(6)); - ensure_equals("empty", utf8str_bytes_from_cased_bytes(std::string(), 4, true), size_t(0)); + CHECK(utf8str_bytes_from_cased_bytes(std::string(), 4, true) == size_t(0)); } // "Which word is here" and "where is the next one", the questions // double-click selection, spell check and autoreplace ask. - template<> template<> - void llstring_utf_object_t::test<122>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: the word at a position and the next word from it", "[LLStringUTF]") { const std::u32string text = to_u32("don't stop, ok"); // A contraction is one word. The alnum-or-underscore walk this replaced // stopped at the apostrophe and offered "don" or "t". auto word = wstring_word_range_at(text, 0); - ensure_equals("contraction begin", word.first, size_t(0)); - ensure_equals("contraction end", word.second, size_t(5)); + CHECK(word.first == size_t(0)); + CHECK(word.second == size_t(5)); // Anywhere inside it gives the same word. for (size_t p = 0; p < 5; ++p) { const auto at = wstring_word_range_at(text, p); - ensure_equals("same word from inside", at.first, size_t(0)); - ensure_equals("same word end", at.second, size_t(5)); + CAPTURE(p); + REQUIRE(at.first == size_t(0)); + REQUIRE(at.second == size_t(5)); } // Whitespace and punctuation are segments too, and neither is a word. const auto space = wstring_word_range_at(text, 5); - ensure("space is not a word", space.first == space.second); + CHECK(space.first == space.second); const auto comma = wstring_word_range_at(text, 10); - ensure("comma is not a word", comma.first == comma.second); + CHECK(comma.first == comma.second); // Iterating: each call yields the next word, skipping what is between. std::vector found; @@ -2048,46 +1944,45 @@ namespace tut found.push_back(to_utf8(text.substr(next.first, next.second - next.first))); at = next.second; } - ensure_equals("three words", found.size(), size_t(3)); - ensure_equals("first", found[0], std::string("don't")); - ensure_equals("second", found[1], std::string("stop")); - ensure_equals("third", found[2], std::string("ok")); + REQUIRE(found.size() == size_t(3)); + CHECK(found[0] == std::string("don't")); + CHECK(found[1] == std::string("stop")); + CHECK(found[2] == std::string("ok")); // Iteration crosses lines, which the spell checker relies on. const std::u32string lines = to_u32("one\ntwo"); const auto first = wstring_next_word_range(lines, 0); - ensure_equals("first line word end", first.second, size_t(3)); + CHECK(first.second == size_t(3)); const auto second = wstring_next_word_range(lines, first.second); - ensure_equals("second line word begin", second.first, size_t(4)); - ensure_equals("second line word end", second.second, size_t(7)); + CHECK(second.first == size_t(4)); + CHECK(second.second == size_t(7)); // Past the end, and past the last word, both terminate. const auto none = wstring_next_word_range(lines, 7); - ensure("no more words", none.first == none.second); + CHECK(none.first == none.second); const auto oob = wstring_word_range_at(lines, 99); - ensure("past end is empty", oob.first == oob.second); + CHECK(oob.first == oob.second); const std::u32string empty; const auto on_empty = wstring_word_range_at(empty, 0); - ensure("empty string", on_empty.first == on_empty.second); + CHECK(on_empty.first == on_empty.second); // A whole string that is one word -- what autoreplace validates a // keyword with. const std::u32string keyword = to_u32("don't"); const auto whole = wstring_word_range_at(keyword, 0); - ensure("keyword is one word", - whole.first == size_t(0) && whole.second == keyword.size()); + CHECK(whole.first == size_t(0)); + CHECK(whole.second == keyword.size()); const std::u32string two_words = to_u32("not one"); const auto partial = wstring_word_range_at(two_words, 0); - ensure("two words is not one", partial.second != two_words.size()); + CHECK(partial.second != two_words.size()); } // Classification above the BMP. Every one of these codepoints used to be // handed to , whose wint_t is 16 bits on Windows -- so each // arrived with its top half cut off and was answered for by whatever // happened to live at the remaining address. - template<> template<> - void llstring_utf_object_t::test<123>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: classification and case above the BMP", "[LLStringUTF]") { // The low half of each is given as the answer it used to produce. const llwchar CJK_EXT_B_A = 0x2000A; // -> U+000A, a line feed @@ -2098,14 +1993,14 @@ namespace tut const llwchar MATH_BOLD_A = 0x1D400; // -> U+D400, a lone surrogate const llwchar GRINNING_FACE = 0x1F600; - ensure("CJK ideograph is not whitespace", !LLStringOps::isSpace(CJK_EXT_B_A)); - ensure("CJK ideograph is alphanumeric", LLStringOps::isAlnum(CJK_EXT_B_A)); - ensure("CJK ideograph is not a digit", !LLStringOps::isDigit(CJK_EXT_B_ONE)); - ensure("CJK ideograph is alphanumeric", LLStringOps::isAlnum(CJK_EXT_B_ONE)); - ensure("Linear B is not whitespace", !LLStringOps::isSpace(LINEAR_B)); - ensure("math capital is a letter", LLStringOps::isAlpha(MATH_BOLD_A)); - ensure("emoji is not alphanumeric", !LLStringOps::isAlnum(GRINNING_FACE)); - ensure("emoji is not whitespace", !LLStringOps::isSpace(GRINNING_FACE)); + CHECK_FALSE(LLStringOps::isSpace(CJK_EXT_B_A)); + CHECK(LLStringOps::isAlnum(CJK_EXT_B_A)); + CHECK_FALSE(LLStringOps::isDigit(CJK_EXT_B_ONE)); + CHECK(LLStringOps::isAlnum(CJK_EXT_B_ONE)); + CHECK_FALSE(LLStringOps::isSpace(LINEAR_B)); + CHECK(LLStringOps::isAlpha(MATH_BOLD_A)); + CHECK_FALSE(LLStringOps::isAlnum(GRINNING_FACE)); + CHECK_FALSE(LLStringOps::isSpace(GRINNING_FACE)); // Case above the BMP: this used to return an unrelated codepoint // rather than fail to convert. @@ -2113,39 +2008,44 @@ namespace tut // failure message rather than compared as they stand. const auto cased = [](llwchar c) { return (U32)c; }; - ensure("Deseret capital is upper", LLStringOps::isUpper(DESERET_CAP)); - ensure_equals("Deseret capital lowercases within Deseret", - cased(LLStringOps::toLower(DESERET_CAP)), cased(DESERET_SMALL)); - ensure_equals("Deseret small uppercases back", - cased(LLStringOps::toUpper(DESERET_SMALL)), cased(DESERET_CAP)); + CHECK(LLStringOps::isUpper(DESERET_CAP)); + CHECK(cased(LLStringOps::toLower(DESERET_CAP)) == cased(DESERET_SMALL)); + CHECK(cased(LLStringOps::toUpper(DESERET_SMALL)) == cased(DESERET_CAP)); // Case inside the BMP, which towlower left alone entirely. - ensure_equals("Greek sigma lowercases", cased(LLStringOps::toLower(llwchar(0x03A3))), cased(0x03C3)); - ensure_equals("Cyrillic Io lowercases", cased(LLStringOps::toLower(llwchar(0x0401))), cased(0x0451)); - ensure_equals("Cyrillic io uppercases", cased(LLStringOps::toUpper(llwchar(0x0451))), cased(0x0401)); + UNSCOPED_INFO("Greek sigma lowercases"); + CHECK(cased(LLStringOps::toLower(llwchar(0x03A3))) == cased(0x03C3)); + UNSCOPED_INFO("Cyrillic Io lowercases"); + CHECK(cased(LLStringOps::toLower(llwchar(0x0401))) == cased(0x0451)); + UNSCOPED_INFO("Cyrillic io uppercases"); + CHECK(cased(LLStringOps::toUpper(llwchar(0x0451))) == cased(0x0401)); // Whitespace is the Unicode property, not a guess. - ensure("no-break space is whitespace", LLStringOps::isSpace(llwchar(0x00A0))); - ensure("ideographic space is whitespace", LLStringOps::isSpace(llwchar(0x3000))); - ensure("zero width space is not", !LLStringOps::isSpace(llwchar(0x200B))); + UNSCOPED_INFO("no-break space is whitespace"); + CHECK(LLStringOps::isSpace(llwchar(0x00A0))); + UNSCOPED_INFO("ideographic space is whitespace"); + CHECK(LLStringOps::isSpace(llwchar(0x3000))); + UNSCOPED_INFO("zero width space is not"); + CHECK_FALSE(LLStringOps::isSpace(llwchar(0x200B))); // Digits are ASCII on purpose -- every caller reads the run as a number. - ensure("Arabic-Indic zero is not a digit", !LLStringOps::isDigit(llwchar(0x0660))); - ensure("Arabic-Indic zero is alphanumeric", LLStringOps::isAlnum(llwchar(0x0660))); + UNSCOPED_INFO("Arabic-Indic zero is not a digit"); + CHECK_FALSE(LLStringOps::isDigit(llwchar(0x0660))); + UNSCOPED_INFO("Arabic-Indic zero is alphanumeric"); + CHECK(LLStringOps::isAlnum(llwchar(0x0660))); // ASCII keeps answering as it always did. - ensure("A is upper", LLStringOps::isUpper(llwchar('A'))); - ensure("a is lower", LLStringOps::isLower(llwchar('a'))); - ensure("5 is a digit", LLStringOps::isDigit(llwchar('5'))); - ensure("space is space", LLStringOps::isSpace(llwchar(' '))); - ensure("dot is punct", LLStringOps::isPunct(llwchar('.'))); - ensure_equals("A lowercases", cased(LLStringOps::toLower(llwchar('A'))), cased(llwchar('a'))); + CHECK(LLStringOps::isUpper(llwchar('A'))); + CHECK(LLStringOps::isLower(llwchar('a'))); + CHECK(LLStringOps::isDigit(llwchar('5'))); + CHECK(LLStringOps::isSpace(llwchar(' '))); + CHECK(LLStringOps::isPunct(llwchar('.'))); + CHECK(cased(LLStringOps::toLower(llwchar('A'))) == cased(llwchar('a'))); } // The consequence of the above for the cursor: a CJK ideograph whose low // half is a line feed must not read as a run of whitespace to step over. - template<> template<> - void llstring_utf_object_t::test<124>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: an astral ideograph is a word to the cursor and not a gap", "[LLStringUTF]") { std::u32string text = to_u32("ab"); text.push_back(0x2000A); @@ -2156,66 +2056,67 @@ namespace tut // so the cursor stops at the first. Reading their low halves as line // feeds made all three of the ideographs and the space look like one // gap, and the cursor jumped clear to "cd" at 5. - ensure_equals("cursor stops at the ideograph", - wstring_step_word_forward(text, 0), size_t(2)); + CHECK(wstring_step_word_forward(text, 0) == size_t(2)); const auto range = wstring_word_range_at(text, 2); - ensure("an ideograph is a word", range.first != range.second); + CHECK(range.first != range.second); } // utf8str_grapheme_align_backward: the byte offsets are the caller's own, // and a cut backs off to where a whole character starts. Everything that // trims UTF-8 to a length leans on this. - template<> template<> - void llstring_utf_object_t::test<125>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_grapheme_align_backward backs off to a character start", "[LLStringUTF]") { // ASCII: every offset is already a boundary. const std::string ascii = "abcd"; for (size_t i = 0; i <= ascii.size(); ++i) { - ensure_equals("ascii is all boundaries", - utf8str_grapheme_align_backward(ascii, i), i); + CAPTURE(i); + REQUIRE(utf8str_grapheme_align_backward(ascii, i) == i); } // Inside a multi-byte codepoint, back to where it starts. "A" + U+65E5 // (3 bytes) + "B". const std::string cjk = "A\xE6\x97\xA5" "B"; - ensure_equals("before the codepoint", utf8str_grapheme_align_backward(cjk, 1), size_t(1)); - ensure_equals("inside it, byte 2", utf8str_grapheme_align_backward(cjk, 2), size_t(1)); - ensure_equals("inside it, byte 3", utf8str_grapheme_align_backward(cjk, 3), size_t(1)); - ensure_equals("after it", utf8str_grapheme_align_backward(cjk, 4), size_t(4)); + UNSCOPED_INFO("before the codepoint"); + CHECK(utf8str_grapheme_align_backward(cjk, 1) == size_t(1)); + UNSCOPED_INFO("inside it, byte 2"); + CHECK(utf8str_grapheme_align_backward(cjk, 2) == size_t(1)); + UNSCOPED_INFO("inside it, byte 3"); + CHECK(utf8str_grapheme_align_backward(cjk, 3) == size_t(1)); + UNSCOPED_INFO("after it"); + CHECK(utf8str_grapheme_align_backward(cjk, 4) == size_t(4)); // A base and its combining mark are one character: "e" + U+0301. const std::string combining = "e\xCC\x81" "x"; - ensure_equals("between base and mark", - utf8str_grapheme_align_backward(combining, 1), size_t(0)); - ensure_equals("inside the mark", - utf8str_grapheme_align_backward(combining, 2), size_t(0)); - ensure_equals("after the whole cluster", - utf8str_grapheme_align_backward(combining, 3), size_t(3)); + UNSCOPED_INFO("between base and mark"); + CHECK(utf8str_grapheme_align_backward(combining, 1) == size_t(0)); + UNSCOPED_INFO("inside the mark"); + CHECK(utf8str_grapheme_align_backward(combining, 2) == size_t(0)); + UNSCOPED_INFO("after the whole cluster"); + CHECK(utf8str_grapheme_align_backward(combining, 3) == size_t(3)); // A flag is two codepoints and eight bytes, and one character. Cutting // anywhere inside gives nothing rather than half a flag. const std::string flag = "\xF0\x9F\x87\xBA\xF0\x9F\x87\xB8"; for (size_t i = 1; i < flag.size(); ++i) { - ensure_equals("no half flags", utf8str_grapheme_align_backward(flag, i), size_t(0)); + CAPTURE(i); + REQUIRE(utf8str_grapheme_align_backward(flag, i) == size_t(0)); } - ensure_equals("whole flag survives", - utf8str_grapheme_align_backward(flag, flag.size()), flag.size()); + CHECK(utf8str_grapheme_align_backward(flag, flag.size()) == flag.size()); // Degenerate input. - ensure_equals("zero", utf8str_grapheme_align_backward(cjk, 0), size_t(0)); - ensure_equals("past end", utf8str_grapheme_align_backward(cjk, 99), cjk.size()); - ensure_equals("empty", utf8str_grapheme_align_backward(std::string(), 0), size_t(0)); + CHECK(utf8str_grapheme_align_backward(cjk, 0) == size_t(0)); + CHECK(utf8str_grapheme_align_backward(cjk, 99) == cjk.size()); + CHECK(utf8str_grapheme_align_backward(std::string(), 0) == size_t(0)); } // The walkers work in UTF-8 and the wide entry points are adapters over // them, so walking the same text both ways has to land in the same places. // Text with a byte per codepoint, three, two, and a two-codepoint cluster, // so the two index spaces disagree everywhere after the first space. - template<> template<> - void llstring_utf_object_t::test<126>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: the UTF-8 walkers find every boundary in mixed text", "[LLStringUTF]") { const std::string utf8 = "ab " // ascii @@ -2224,7 +2125,7 @@ namespace tut "\xF0\x9F\x87\xBA\xF0\x9F\x87\xB8" // flag: two codepoints, one cluster " end"; // a=0 b=1 sp=2 CJK=3,6 sp=9 e=10 acute=11 sp=13 flag=14 sp=22 end=23. - ensure_equals("layout", utf8.size(), size_t(26)); + REQUIRE(utf8.size() == size_t(26)); // Every grapheme boundary, written out. The combining acute and the // two regional indicators are the ones a byte walk would split. @@ -2235,25 +2136,28 @@ namespace tut for (size_t i = 1; i < count; ++i) { at = utf8str_step_grapheme_forward(utf8, at); - ensure_equals("forward boundary " + std::to_string(i), at, boundaries[i]); + CAPTURE(i); + REQUIRE(at == boundaries[i]); } - ensure_equals("forward walk ended at the end", at, utf8.size()); + CHECK(at == utf8.size()); for (size_t i = count - 1; i > 0; --i) { at = utf8str_step_grapheme_backward(utf8, at); - ensure_equals("backward boundary " + std::to_string(i - 1), at, boundaries[i - 1]); + CAPTURE(i); + REQUIRE(at == boundaries[i - 1]); } - ensure_equals("backward walk came back to nothing", at, size_t(0)); + CHECK(at == size_t(0)); // Every offset inside a cluster snaps to that cluster's edges. for (size_t i = 1; i < count; ++i) { for (size_t inside = boundaries[i - 1] + 1; inside < boundaries[i]; ++inside) { - ensure_equals("aligns back", utf8str_grapheme_align_backward(utf8, inside), + CAPTURE(i, inside); + REQUIRE(utf8str_grapheme_align_backward(utf8, inside) == boundaries[i - 1]); - ensure_equals("aligns forward", utf8str_grapheme_align_forward(utf8, inside), + REQUIRE(utf8str_grapheme_align_forward(utf8, inside) == boundaries[i]); } } @@ -2264,41 +2168,38 @@ namespace tut for (size_t i = 0; i < sizeof(word_starts) / sizeof(word_starts[0]); ++i) { word_at = utf8str_step_word_forward(utf8, word_at); - ensure_equals("word start " + std::to_string(i), word_at, word_starts[i]); + CAPTURE(i); + REQUIRE(word_at == word_starts[i]); } // The word under the first ideograph is the ideograph pair. const auto cjk_word = utf8str_word_range_at(utf8, 3); - ensure_equals("CJK word begins", cjk_word.first, size_t(3)); - ensure_equals("CJK word ends", cjk_word.second, size_t(9)); + CHECK(cjk_word.first == size_t(3)); + CHECK(cjk_word.second == size_t(9)); // Line break opportunities: after each space, between the ideographs, // and at the end. Never inside a character or a flag. std::vector breaks; utf8str_line_break_opportunities(utf8, breaks); - ensure("the end is always an opportunity", - !breaks.empty() && breaks.back() == utf8.size()); + REQUIRE_FALSE(breaks.empty()); + CHECK(breaks.back() == utf8.size()); for (size_t where : breaks) { - ensure("never zero", where != 0); - ensure("always on a grapheme boundary", - std::find(boundaries, boundaries + count, where) != boundaries + count); + CAPTURE(where); + REQUIRE(where != 0); + REQUIRE(std::find(boundaries, boundaries + count, where) != boundaries + count); } - ensure("CJK may be split between the ideographs", - std::find(breaks.begin(), breaks.end(), size_t(6)) != breaks.end()); - ensure("a flag may not be split", - std::find(breaks.begin(), breaks.end(), size_t(18)) == breaks.end()); + CHECK(std::find(breaks.begin(), breaks.end(), size_t(6)) != breaks.end()); + CHECK(std::find(breaks.begin(), breaks.end(), size_t(18)) == breaks.end()); // A flag is one cluster of eight bytes, and nothing cuts it in half. const size_t flag = utf8.find("\xF0\x9F\x87\xBA"); - ensure_equals("flag steps whole", - utf8str_step_grapheme_forward(utf8, flag), flag + 8); + CHECK(utf8str_step_grapheme_forward(utf8, flag) == flag + 8); for (size_t inside = 1; inside < 8; ++inside) { - ensure_equals("inside the flag aligns back to its start", - utf8str_grapheme_align_backward(utf8, flag + inside), flag); - ensure_equals("and forward to its end", - utf8str_grapheme_align_forward(utf8, flag + inside), flag + 8); + CAPTURE(inside); + REQUIRE(utf8str_grapheme_align_backward(utf8, flag + inside) == flag); + REQUIRE(utf8str_grapheme_align_forward(utf8, flag + inside) == flag + 8); } } @@ -2309,8 +2210,7 @@ namespace tut // The two cluster walkers are one set of rules instantiated twice, so // every run the wide walker finds has to be the same run in the UTF-8 // walker's byte coordinates, and there must be no extra ones. - template<> template<> - void llstring_utf_object_t::test<127>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_find_emoji_clusters finds each run in bytes", "[LLStringUTF]") { const std::vector corpus = { std::u32string(), @@ -2346,21 +2246,20 @@ namespace tut {13, true, 0, 13}, // heart on fire, 3+3+3+4 {16, true, 4, 15}, // a + ideograph + ZWJ pair + z }; - ensure_equals("every corpus entry is accounted for", - corpus.size(), sizeof(expected) / sizeof(expected[0])); + REQUIRE(corpus.size() == sizeof(expected) / sizeof(expected[0])); for (size_t c = 0; c < corpus.size(); ++c) { const std::string utf8 = to_utf8(corpus[c]); - const std::string tag = "corpus " + std::to_string(c); - ensure_equals(tag + ": byte length", utf8.size(), expected[c].bytes); + CAPTURE(c); + REQUIRE(utf8.size() == expected[c].bytes); const auto runs = utf8str_find_emoji_clusters(utf8); - ensure_equals(tag + ": run count", runs.size(), expected[c].has_run ? size_t(1) : size_t(0)); + REQUIRE(runs.size() == (expected[c].has_run ? size_t(1) : size_t(0))); if (expected[c].has_run) { - ensure_equals(tag + ": run begin", runs[0].first, expected[c].begin); - ensure_equals(tag + ": run end", runs[0].second, expected[c].end); + REQUIRE(runs[0].first == expected[c].begin); + REQUIRE(runs[0].second == expected[c].end); } } } @@ -2368,33 +2267,31 @@ namespace tut // Byte coordinates pinned outright, so a change that moved both walkers // the same wrong way is still caught. A ZWJ pair sitting behind an // ideograph starts at byte 4, not codepoint 2. - template<> template<> - void llstring_utf_object_t::test<128>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a run after an ideograph is placed in bytes not codepoints", "[LLStringUTF]") { std::u32string wide = { (llwchar)'a', (llwchar)0x65E5, (llwchar)0x1F468, (llwchar)0x200D, (llwchar)0x1F469, (llwchar)'z' }; const std::string utf8 = to_utf8(wide); - ensure_equals("byte length", utf8.size(), size_t(1 + 3 + 4 + 3 + 4 + 1)); + REQUIRE(utf8.size() == size_t(1 + 3 + 4 + 3 + 4 + 1)); const auto runs = utf8str_find_emoji_clusters(utf8); - ensure_equals("one run", runs.size(), size_t(1)); - ensure_equals("begins past 'a' and the ideograph", runs[0].first, size_t(4)); - ensure_equals("ends before 'z'", runs[0].second, size_t(15)); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].first == size_t(4)); + CHECK(runs[0].second == size_t(15)); } // Malformed UTF-8 must not stall the walk or invent a cluster. Every bad // byte decodes to U+FFFD over one byte, which matches no rule. - template<> template<> - void llstring_utf_object_t::test<129>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: malformed UTF-8 neither stalls the walk nor invents a cluster", "[LLStringUTF]") { - ensure_equals("lone continuation", - utf8str_find_emoji_clusters("\x80\x80\x80").size(), size_t(0)); - ensure_equals("truncated four-byte", - utf8str_find_emoji_clusters("\xF0\x9F\x91").size(), size_t(0)); - ensure_equals("bad lead", - utf8str_find_emoji_clusters("\xFF\xFE").size(), size_t(0)); + UNSCOPED_INFO("lone continuation"); + CHECK(utf8str_find_emoji_clusters("\x80\x80\x80").size() == size_t(0)); + UNSCOPED_INFO("truncated four-byte"); + CHECK(utf8str_find_emoji_clusters("\xF0\x9F\x91").size() == size_t(0)); + UNSCOPED_INFO("bad lead"); + CHECK(utf8str_find_emoji_clusters("\xFF\xFE").size() == size_t(0)); // A bad byte in front of a real cluster still leaves the cluster // findable, at the offset that byte pushed it to. @@ -2402,17 +2299,16 @@ namespace tut text += to_utf8(std::u32string{ (llwchar)0x1F468, (llwchar)0x200D, (llwchar)0x1F469 }); const auto runs = utf8str_find_emoji_clusters(text); - ensure_equals("one run past the bad byte", runs.size(), size_t(1)); - ensure_equals("begins at byte 1", runs[0].first, size_t(1)); - ensure_equals("ends at the end", runs[0].second, text.size()); + REQUIRE(runs.size() == size_t(1)); + CHECK(runs[0].first == size_t(1)); + CHECK(runs[0].second == text.size()); } // utf8str_emoji_range_at answered at every position in the string -- // including the ones inside a character, which belong to no pictograph. // The expected ranges are written out rather than derived, so a change that // moved the lookup and the walk the same wrong way is still caught. - template<> template<> - void llstring_utf_object_t::test<130>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_emoji_range_at answers at every byte position", "[LLStringUTF]") { const std::u32string wide = { (llwchar)'a', // 1 byte (llwchar)0x2764, (llwchar)0xFE0F,// heart + VS16 @@ -2424,7 +2320,7 @@ namespace tut const std::string utf8 = to_utf8(wide); // a=0 heart=1 VS16=4 ideograph=7 man=10 ZWJ=14 woman=17 (c)=21 z=23. - ensure_equals("layout", utf8.size(), size_t(24)); + REQUIRE(utf8.size() == size_t(24)); // The range every byte offset belongs to, written out. Inside a // cluster every byte reports the whole cluster, character start or @@ -2445,62 +2341,60 @@ namespace tut for (const Expect& e : expected) { const auto got = utf8str_emoji_range_at(utf8, e.at); - ensure_equals("range begin at " + std::to_string(e.at), got.first, e.begin); - ensure_equals("range end at " + std::to_string(e.at), got.second, e.end); + CAPTURE(e.at); + REQUIRE(got.first == e.begin); + REQUIRE(got.second == e.end); } // The heart carries a presentation selector, so it is a cluster and // its range spans both codepoints -- three bytes plus three. const auto heart = utf8str_emoji_range_at(utf8, 1); - ensure_equals("heart begins at 1", heart.first, size_t(1)); - ensure_equals("heart spans VS16", heart.second, size_t(7)); + CHECK(heart.first == size_t(1)); + CHECK(heart.second == size_t(7)); // The lone copyright sign is no cluster, but it is a pictograph base, // so it reports its own two bytes rather than an empty range. const size_t copy_at = 1 + 6 + 3 + 11; const auto copyright = utf8str_emoji_range_at(utf8, copy_at); - ensure_equals("(c) begins", copyright.first, copy_at); - ensure_equals("(c) is two bytes", copyright.second, copy_at + 2); + CHECK(copyright.first == copy_at); + CHECK(copyright.second == copy_at + 2); // A position inside the ideograph is not a character start and so // belongs to nothing. const auto split = utf8str_emoji_range_at(utf8, 8); - ensure_equals("mid-character is empty", split.first, split.second); + CHECK(split.first == split.second); // Past the end reports an empty range at the position asked about, // not at the end of the string. const auto past = utf8str_emoji_range_at(utf8, utf8.size() + 4); - ensure_equals("past the end is empty", past.first, past.second); + CHECK(past.first == past.second); } // utf8str_codepoint_count is what a limit expressed in characters gets // compared against, so it has to agree with the UTF-32 form's size(). - template<> template<> - void llstring_utf_object_t::test<132>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_codepoint_count agrees with the UTF-32 size", "[LLStringUTF]") { - ensure_equals("empty", utf8str_codepoint_count(""), size_t(0)); - ensure_equals("ascii", utf8str_codepoint_count("abc"), size_t(3)); + CHECK(utf8str_codepoint_count("") == size_t(0)); + CHECK(utf8str_codepoint_count("abc") == size_t(3)); const std::u32string wide = { (llwchar)'a', (llwchar)0x00E9, (llwchar)0x65E5, (llwchar)0x1F600, (llwchar)0x200D, (llwchar)0x1F600 }; const std::string utf8 = to_utf8(wide); - ensure_equals("bytes", utf8.size(), size_t(1 + 2 + 3 + 4 + 3 + 4)); - ensure_equals("agrees with the wide size", - utf8str_codepoint_count(utf8), wide.size()); + CHECK(utf8.size() == size_t(1 + 2 + 3 + 4 + 3 + 4)); + CHECK(utf8str_codepoint_count(utf8) == wide.size()); // Malformed bytes count one each, so the count never disagrees with a // walk over the same text. - ensure_equals("lone continuation bytes", - utf8str_codepoint_count("\x80\x80"), size_t(2)); - ensure_equals("truncated sequence", - utf8str_codepoint_count("\xF0\x9F"), size_t(2)); + UNSCOPED_INFO("lone continuation bytes"); + CHECK(utf8str_codepoint_count("\x80\x80") == size_t(2)); + UNSCOPED_INFO("truncated sequence"); + CHECK(utf8str_codepoint_count("\xF0\x9F") == size_t(2)); } // The UTF-16 pair has to agree with the wide forms it stands beside: the // Win32 IME hands offsets in code units and the editors index bytes, so a // disagreement here is a caret landing in the wrong place mid-composition. - template<> template<> - void llstring_utf_object_t::test<133>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: UTF-16 unit counts and byte counts convert both ways", "[LLStringUTF]") { // 'a', e-acute, an ideograph, an astral emoji (a surrogate pair), 'z'. const std::u32string wide = { (llwchar)'a', (llwchar)0x00E9, (llwchar)0x65E5, @@ -2508,8 +2402,8 @@ namespace tut const std::string utf8 = to_utf8(wide); // Bytes 1 + 2 + 3 + 4 + 1; code units 1 + 1 + 1 + 2 + 1. - ensure_equals("bytes", utf8.size(), size_t(11)); - ensure_equals("units", utf8str_utf16_length(utf8, 0, S32_MAX), S32(6)); + REQUIRE(utf8.size() == size_t(11)); + CHECK(utf8str_utf16_length(utf8, 0, S32_MAX) == S32(6)); // Every prefix, by hand: the byte offset of each character start and // the code units the text up to it occupies. @@ -2517,8 +2411,8 @@ namespace tut const S32 unit_at[] = { 0, 1, 2, 3, 5, 6 }; for (size_t cp = 0; cp < 6; ++cp) { - ensure_equals("prefix at " + std::to_string(cp), - utf8str_utf16_length(utf8, 0, byte_at[cp]), unit_at[cp]); + CAPTURE(cp); + REQUIRE(utf8str_utf16_length(utf8, 0, byte_at[cp]) == unit_at[cp]); } // Back the other way: a budget in code units becomes a byte count, and @@ -2527,44 +2421,42 @@ namespace tut for (S32 units = 0; units <= 6; ++units) { bool unaligned = false; - ensure_equals("bytes for " + std::to_string(units) + " units", - utf8str_length_from_utf16_length(utf8, 0, units, &unaligned), + CAPTURE(units); + REQUIRE(utf8str_length_from_utf16_length(utf8, 0, units, &unaligned) == bytes_for_units[units]); - ensure("unaligned only mid-pair", unaligned == (units == 4)); + REQUIRE(unaligned == (units == 4)); } // Offsets are honoured, not just whole-string calls. - ensure_equals("offset form", utf8str_utf16_length(utf8, 3, S32_MAX), S32(4)); + CHECK(utf8str_utf16_length(utf8, 3, S32_MAX) == S32(4)); // Out of range clamps rather than running off. - ensure_equals("past the end", utf8str_utf16_length(utf8, 999, 999), S32(0)); - ensure_equals("no budget", utf8str_length_from_utf16_length(utf8, 0, 0), S32(0)); + CHECK(utf8str_utf16_length(utf8, 999, 999) == S32(0)); + CHECK(utf8str_length_from_utf16_length(utf8, 0, 0) == S32(0)); } // Valid text has to come back byte-identical -- the clipboard calls this on // every paste, and a sanitizer that rewrites what was already correct would // be worse than none. - template<> template<> - void llstring_utf_object_t::test<134>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_sanitize returns valid text byte for byte", "[LLStringUTF]") { const std::string ascii = "plain ascii"; - ensure_equals("ascii untouched", utf8str_sanitize(ascii), ascii); + CHECK(utf8str_sanitize(ascii) == ascii); const std::u32string wide = { (llwchar)'a', (llwchar)0x00E9, (llwchar)0x65E5, (llwchar)0x1F600, (llwchar)'z' }; const std::string mixed = to_utf8(wide); - ensure_equals("mixed untouched", utf8str_sanitize(mixed), mixed); + CHECK(utf8str_sanitize(mixed) == mixed); - ensure_equals("empty", utf8str_sanitize(std::string_view()), std::string()); + CHECK(utf8str_sanitize(std::string_view()) == std::string()); // Embedded NUL is valid UTF-8 and must survive; string_view carries it. const std::string with_nul("a\0b", 3); - ensure_equals("nul survives", utf8str_sanitize(with_nul), with_nul); + CHECK(utf8str_sanitize(with_nul) == with_nul); } // And malformed text has to come back valid, whatever shape the damage took. - template<> template<> - void llstring_utf_object_t::test<135>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_sanitize makes malformed text valid", "[LLStringUTF]") { // Against simdutf, not against the function under test. Defining // validity as "sanitize leaves it alone" only asks whether the repair @@ -2588,24 +2480,22 @@ namespace tut for (const std::string& s : bad) { - ensure("input really is malformed", !is_valid(s)); + UNSCOPED_INFO("input really is malformed"); + REQUIRE_FALSE(is_valid(s)); // The predicate the widgets ask before deciding whether to repair // has to agree with simdutf, or a widget skips a repair it needed. - ensure_equals("utf8str_is_valid agrees with simdutf", - utf8str_is_valid(s), is_valid(s)); + REQUIRE(utf8str_is_valid(s) == is_valid(s)); const std::string fixed = utf8str_sanitize(s); - ensure("output is valid", is_valid(fixed)); - ensure_equals("and the predicate says so too", - utf8str_is_valid(fixed), true); - ensure("the good bytes are still there", - fixed.find("ok") == 0 && fixed.rfind("ok") == fixed.size() - 2); + REQUIRE(is_valid(fixed)); + REQUIRE(utf8str_is_valid(fixed)); + REQUIRE(fixed.find("ok") == 0); + REQUIRE(fixed.rfind("ok") == fixed.size() - 2); } } // A decoder that accepts more than the encoding allows lets a character // reach somewhere a filter already looked for it. - template<> template<> - void llstring_utf_object_t::test<136>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_decode_at accepts only what UTF-8 allows", "[LLStringUTF]") { struct { const char* bytes; const char* what; } bad[] = { { "\xC0\xAF", "overlong slash" }, @@ -2622,25 +2512,23 @@ namespace tut { const std::string s(c.bytes); const LLCodepointAt at = utf8str_decode_at(s, 0); - ensure_equals(std::string(c.what) + " decodes to the replacement", - (S32)at.cp, (S32)0xFFFD); - ensure_equals(std::string(c.what) + " consumes exactly one byte", - (S32)at.next, 1); + INFO(c.what); + REQUIRE((S32)at.cp == (S32)0xFFFD); + REQUIRE((S32)at.next == 1); } // The shapes that are legal still decode whole. - ensure_equals("ASCII", (S32)utf8str_decode_at(std::string("A"), 0).cp, (S32)'A'); - ensure_equals("two-byte", (S32)utf8str_decode_at(std::string("\xC3\xA9"), 0).cp, (S32)0xE9); - ensure_equals("three-byte", (S32)utf8str_decode_at(std::string("\xE6\x97\xA5"), 0).cp, (S32)0x65E5); - ensure_equals("four-byte", (S32)utf8str_decode_at(std::string("\xF0\x9F\x90\xB6"), 0).cp, (S32)0x1F436); - ensure_equals("four-byte spans four", (S32)utf8str_decode_at(std::string("\xF0\x9F\x90\xB6"), 0).next, (S32)4); + CHECK((S32)utf8str_decode_at(std::string("A"), 0).cp == (S32)'A'); + CHECK((S32)utf8str_decode_at(std::string("\xC3\xA9"), 0).cp == (S32)0xE9); + CHECK((S32)utf8str_decode_at(std::string("\xE6\x97\xA5"), 0).cp == (S32)0x65E5); + CHECK((S32)utf8str_decode_at(std::string("\xF0\x9F\x90\xB6"), 0).cp == (S32)0x1F436); + CHECK((S32)utf8str_decode_at(std::string("\xF0\x9F\x90\xB6"), 0).next == (S32)4); } // Shortening text must not empty it. The budget counts codepoints while the // cut lands on a cluster, so a first cluster that spends more codepoints // than the budget allows has nowhere to fall back to except itself. - template<> template<> - void llstring_utf_object_t::test<137>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_symbol_truncate never cuts text to nothing", "[LLStringUTF]") { // A ZWJ family: five codepoints, eighteen bytes, one cluster. const std::string family("\xF0\x9F\x91\xA8\xE2\x80\x8D\xF0\x9F\x91\xA9\xE2\x80\x8D\xF0\x9F\x91\xA7"); @@ -2649,86 +2537,70 @@ namespace tut for (S32 budget = 1; budget <= 5; ++budget) { const std::string cut = utf8str_symbol_truncate(text, budget); - ensure("a budget under the first cluster still returns text", !cut.empty()); - ensure_equals("and returns the whole of that cluster", cut, family); + CAPTURE(budget); + REQUIRE_FALSE(cut.empty()); + REQUIRE(cut == family); } // Once the budget clears the cluster the next characters come too. - ensure_equals("budget past the cluster takes the next character", - utf8str_symbol_truncate(text, 6), family + "a"); - ensure_equals("input already short enough is untouched", - utf8str_symbol_truncate(std::string("abc"), 10), std::string("abc")); + CHECK(utf8str_symbol_truncate(text, 6) == family + "a"); + CHECK(utf8str_symbol_truncate(std::string("abc"), 10) == std::string("abc")); } // Case-insensitive over ASCII compares bytes, so a spelling the format // never allowed cannot pass for the one it did. - template<> template<> - void llstring_utf_object_t::test<138>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: LLStringUtil::isEqualInsensitiveASCII folds ASCII and nothing else", "[LLStringUTF]") { - ensure("same word, different case", - LLStringUtil::isEqualInsensitiveASCII(std::string("llsd/binary"), std::string("LLSD/Binary"))); - ensure("a different length never matches", - !LLStringUtil::isEqualInsensitiveASCII(std::string("llsd"), std::string("llsd/binary"))); + CHECK(LLStringUtil::isEqualInsensitiveASCII(std::string("llsd/binary"), std::string("LLSD/Binary"))); + CHECK_FALSE(LLStringUtil::isEqualInsensitiveASCII(std::string("llsd"), std::string("llsd/binary"))); // Fullwidth 'l' is its own character, whatever a collator makes of it. - ensure("fullwidth is not the ASCII letter", - !LLStringUtil::isEqualInsensitiveASCII( + CHECK_FALSE(LLStringUtil::isEqualInsensitiveASCII( std::string("\xEF\xBD\x8C\xEF\xBD\x8Csd/binary"), std::string("llsd/binary"))); // A soft hyphen is a character, not an absence of one. - ensure("an ignorable is still a difference", - !LLStringUtil::isEqualInsensitiveASCII( + CHECK_FALSE(LLStringUtil::isEqualInsensitiveASCII( std::string("llsd/bin\xC2\xAD" "ary"), std::string("llsd/binary"))); // Above ASCII nothing is folded -- these are simply different bytes. - ensure("no folding above ASCII", - !LLStringUtil::isEqualInsensitiveASCII( + CHECK_FALSE(LLStringUtil::isEqualInsensitiveASCII( std::string("caf\xC3\xA9"), std::string("caf\xC3\x89"))); - ensure("empty equals empty", - LLStringUtil::isEqualInsensitiveASCII(std::string(), std::string())); + CHECK(LLStringUtil::isEqualInsensitiveASCII(std::string(), std::string())); } // The word walkers driven by their own offsets. The tests beside these go // through the wstring_* adapters, which prove the ALUtf8View index map is // self-consistent -- not that a byte offset supplied by a caller is read // correctly, which is what every caller in llui now passes. - template<> template<> - void llstring_utf_object_t::test<139>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: the UTF-8 word walkers read byte offsets", "[LLStringUTF]") { // ASCII first, so the byte answers can be read against the wide test. const std::string two_words = "foo bar"; - ensure_equals("fwd to next word", utf8str_step_word_forward(two_words, 0), size_t(4)); - ensure_equals("fwd from next word", utf8str_step_word_forward(two_words, 4), size_t(7)); - ensure_equals("back from end", utf8str_step_word_backward(two_words, 7), size_t(4)); - ensure_equals("back over the space", utf8str_step_word_backward(two_words, 4), size_t(0)); + CHECK(utf8str_step_word_forward(two_words, 0) == size_t(4)); + CHECK(utf8str_step_word_forward(two_words, 4) == size_t(7)); + CHECK(utf8str_step_word_backward(two_words, 7) == size_t(4)); + CHECK(utf8str_step_word_backward(two_words, 4) == size_t(0)); // And where a character is not a byte. "café bar": the accent makes // the second word start at byte 6 where it is codepoint 5, so these // numbers are wrong under any reading but the byte one. const std::string accented = "caf\xC3\xA9 bar"; - ensure_equals("the fixture string is nine bytes", - accented.size(), size_t(9)); - ensure_equals("fwd over an accented word", - utf8str_step_word_forward(accented, 0), size_t(6)); - ensure_equals("fwd from the last word runs to the end", - utf8str_step_word_forward(accented, 6), size_t(9)); - ensure_equals("back from the end", - utf8str_step_word_backward(accented, 9), size_t(6)); - ensure_equals("back over the space", - utf8str_step_word_backward(accented, 6), size_t(0)); + REQUIRE(accented.size() == size_t(9)); + CHECK(utf8str_step_word_forward(accented, 0) == size_t(6)); + CHECK(utf8str_step_word_forward(accented, 6) == size_t(9)); + CHECK(utf8str_step_word_backward(accented, 9) == size_t(6)); + CHECK(utf8str_step_word_backward(accented, 6) == size_t(0)); // An offset inside the accent is still inside the first word. - ensure_equals("stepping from inside a character", - utf8str_step_word_forward(accented, 4), size_t(6)); + CHECK(utf8str_step_word_forward(accented, 4) == size_t(6)); // The walkers stay on their line -- LLTextEditor::nextWordPos crosses // the break itself, and would stall on every newline if this changed. const std::string lines = "ab\ncd"; - ensure_equals("fwd stops at eol", utf8str_step_word_forward(lines, 0), size_t(2)); - ensure_equals("fwd parked on eol", utf8str_step_word_forward(lines, 2), size_t(2)); - ensure_equals("back parked on bol", utf8str_step_word_backward(lines, 3), size_t(3)); + CHECK(utf8str_step_word_forward(lines, 0) == size_t(2)); + CHECK(utf8str_step_word_forward(lines, 2) == size_t(2)); + CHECK(utf8str_step_word_backward(lines, 3) == size_t(3)); } // Word ranges in bytes: what a double-click selects, and what comes next. - template<> template<> - void llstring_utf_object_t::test<140>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: word ranges in bytes for selection and for the next word", "[LLStringUTF]") { const std::string accented = "caf\xC3\xA9 bar"; @@ -2737,87 +2609,77 @@ namespace tut for (size_t at : { size_t(0), size_t(2), size_t(3), size_t(4) }) { const auto range = utf8str_word_range_at(accented, at); - ensure_equals("word range begins at 0", range.first, size_t(0)); - ensure_equals("word range ends after the accent", range.second, size_t(5)); + CAPTURE(at); + REQUIRE(range.first == size_t(0)); + REQUIRE(range.second == size_t(5)); } const auto second = utf8str_word_range_at(accented, 7); - ensure_equals("second word begins at 6", second.first, size_t(6)); - ensure_equals("second word ends at 9", second.second, size_t(9)); + CHECK(second.first == size_t(6)); + CHECK(second.second == size_t(9)); // next_word_range finds the word whose end lies past the position -- // the one the position is in, when it is in one, and only otherwise // the following word. From inside the first word that is still the // first word. const auto from_start = utf8str_next_word_range(accented, 0); - ensure_equals("from 0 the word is the one at 0", from_start.first, size_t(0)); - ensure_equals("and it ends after the accent", from_start.second, size_t(5)); + CHECK(from_start.first == size_t(0)); + CHECK(from_start.second == size_t(5)); // Standing on the space, the answer is the word after it. const auto from_space = utf8str_next_word_range(accented, 5); - ensure_equals("from the space the next word begins at 6", - from_space.first, size_t(6)); - ensure_equals("and ends at 9", from_space.second, size_t(9)); + CHECK(from_space.first == size_t(6)); + CHECK(from_space.second == size_t(9)); // Past the last word there is nothing further to select. const auto none = utf8str_next_word_range(accented, accented.size()); - ensure_equals("nothing begins past the end", none.first, accented.size()); - ensure_equals("nothing ends past the end", none.second, accented.size()); + CHECK(none.first == accented.size()); + CHECK(none.second == accented.size()); } // The caret forms differ from the raw walkers in exactly one way: they // cross a line break instead of stalling on it. Both text widgets forward // to these rather than carrying the policy twice, so it is worth pinning // the difference rather than only the agreement. - template<> template<> - void llstring_utf_object_t::test<141>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: the caret word walkers cross line breaks the raw ones stop at", "[LLStringUTF]") { const std::string lines = "one two\nthree four"; // Away from a break the two are the same walk. - ensure_equals("forward agrees mid-line", - utf8str_caret_word_forward(lines, 0), + CHECK(utf8str_caret_word_forward(lines, 0) == utf8str_step_word_forward(lines, 0)); - ensure_equals("backward agrees mid-line", - utf8str_caret_word_backward(lines, 7), + CHECK(utf8str_caret_word_backward(lines, 7) == utf8str_step_word_backward(lines, 7)); // A caret already at the edge of a word must not skip the adjacent // word, and a caret inside a word must land at that word's start. const std::string adjacent = "foo bar"; - ensure_equals("forward from a word end reaches the adjacent word", - utf8str_caret_word_forward(adjacent, 3), size_t(4)); - ensure_equals("backward from inside a word reaches its start", - utf8str_caret_word_backward(adjacent, 5), size_t(4)); + CHECK(utf8str_caret_word_forward(adjacent, 3) == size_t(4)); + CHECK(utf8str_caret_word_backward(adjacent, 5) == size_t(4)); // On the break the raw walker has nowhere to go and says so. - ensure_equals("the raw walker stalls on the newline", - utf8str_step_word_forward(lines, 7), size_t(7)); - ensure("the caret walker crosses it", - utf8str_caret_word_forward(lines, 7) > size_t(7)); + CHECK(utf8str_step_word_forward(lines, 7) == size_t(7)); + CHECK(utf8str_caret_word_forward(lines, 7) > size_t(7)); // And at a line start, going back. - ensure_equals("the raw walker stalls at the line start", - utf8str_step_word_backward(lines, 8), size_t(8)); - ensure("the caret walker crosses back", - utf8str_caret_word_backward(lines, 8) < size_t(8)); + CHECK(utf8str_step_word_backward(lines, 8) == size_t(8)); + CHECK(utf8str_caret_word_backward(lines, 8) < size_t(8)); // Neither runs off either end. - ensure_equals("nothing before the beginning", - utf8str_caret_word_backward(lines, 0), size_t(0)); - ensure_equals("nothing after the end", - utf8str_caret_word_forward(lines, lines.size()), lines.size()); + CHECK(utf8str_caret_word_backward(lines, 0) == size_t(0)); + CHECK(utf8str_caret_word_forward(lines, lines.size()) == lines.size()); // Repeated stepping terminates rather than parking. size_t at = 0; for (int i = 0; i < 32 && at < lines.size(); ++i) { const size_t next = utf8str_caret_word_forward(lines, at); - ensure("forward never moves backward", next >= at); + CAPTURE(at); + REQUIRE(next >= at); if (next == at) break; at = next; } - ensure_equals("stepping forward reaches the end", at, lines.size()); + CHECK(at == lines.size()); } // --------------------------------------------------------------- @@ -2827,8 +2689,7 @@ namespace tut // LLStringUtil::trim answers for Unicode whitespace, not just ASCII. A // no-break space arrives pasted out of a web page and an ideographic // space sits either side of CJK; a byte-wise isspace() saw neither. - template<> template<> - void llstring_utf_object_t::test<142>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: LLStringUtil::trim answers for Unicode whitespace", "[LLStringUTF]") { const std::string nbsp = "\xC2\xA0"; // U+00A0 const std::string ideo = "\xE3\x80\x80"; // U+3000 @@ -2838,88 +2699,85 @@ namespace tut std::string s = nbsp + "Rye" + nbsp; LLStringUtil::trim(s); - ensure_equals("a no-break space is whitespace", s, std::string("Rye")); + CHECK(s == std::string("Rye")); s = ideo + cjk + ideo; LLStringUtil::trim(s); - ensure_equals("an ideographic space is whitespace", s, cjk); + CHECK(s == cjk); s = em + narrow + " \tx\t " + narrow + em; LLStringUtil::trim(s); - ensure_equals("ASCII and Unicode spaces mixed", s, std::string("x")); + CHECK(s == std::string("x")); s = nbsp + "a"; LLStringUtil::trimHead(s); - ensure_equals("trimHead crosses one", s, std::string("a")); + CHECK(s == std::string("a")); s = "a" + nbsp; LLStringUtil::trimTail(s); - ensure_equals("trimTail crosses one", s, std::string("a")); + CHECK(s == std::string("a")); s = nbsp + ideo + em; LLStringUtil::trim(s); - ensure("all whitespace trims to nothing", s.empty()); + CHECK(s.empty()); } // What is not White_Space stays, and malformed bytes are content: a trim // that guessed at them could cut a character in half. - template<> template<> - void llstring_utf_object_t::test<143>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: trim keeps what is not White_Space and malformed bytes", "[LLStringUTF]") { const std::string zwsp = "\xE2\x80\x8B"; // U+200B, not White_Space const std::string bom = "\xEF\xBB\xBF"; // U+FEFF, not White_Space std::string s = zwsp + "a" + zwsp; LLStringUtil::trim(s); - ensure_equals("a zero-width space is not whitespace", s, zwsp + "a" + zwsp); + CHECK(s == zwsp + "a" + zwsp); s = bom + "a" + bom; LLStringUtil::trim(s); - ensure_equals("a byte order mark is not whitespace", s, bom + "a" + bom); + CHECK(s == bom + "a" + bom); s = std::string("\x80") + "a" + std::string("\x80"); LLStringUtil::trim(s); - ensure_equals("a stray continuation byte is content", s.size(), size_t(3)); + CHECK(s.size() == size_t(3)); // U+3000 missing its last byte. The lead byte it shares with a real // ideographic space must not be enough to cut it. s = std::string("a") + "\xE3\x80"; LLStringUtil::trim(s); - ensure_equals("a truncated character is left whole", s.size(), size_t(3)); + CHECK(s.size() == size_t(3)); s = " hello "; LLStringUtil::trim(s); - ensure_equals("ASCII trims as it always did", s, std::string("hello")); + CHECK(s == std::string("hello")); s.clear(); LLStringUtil::trim(s); - ensure("an empty string survives", s.empty()); + CHECK(s.empty()); } // The view forms cut where the string forms cut, and utf8str_trim shares // that decision rather than keeping a second one. - template<> template<> - void llstring_utf_object_t::test<144>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: the view forms cut where the string forms cut", "[LLStringUTF]") { const std::string src = std::string("\xC2\xA0") + "\t" + "caf\xC3\xA9" + "\xE3\x80\x80"; const std::string expected = "caf\xC3\xA9"; std::string owned = src; LLStringUtil::trim(owned); - ensure_equals("the string form", owned, expected); + CHECK(owned == expected); std::string_view view = src; LLStringUtil::trim(view); - ensure_equals("the view form cuts the same place", std::string(view), expected); + CHECK(std::string(view) == expected); - ensure_equals("utf8str_trim agrees", utf8str_trim(src), expected); + CHECK(utf8str_trim(src) == expected); } // Where each cluster starts, walked at once, is where stepping one at a // time stops: the combining acute and the flag's two indicators kept // with what they belong to; none past the end asked for. - template<> template<> - void llstring_utf_object_t::test<145>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_grapheme_starts stops where stepping stops", "[LLStringUTF]") { const std::string utf8 = "ab " @@ -2932,17 +2790,17 @@ namespace tut { stepped.push_back(at); } - ensure("the same stops", utf8str_grapheme_starts(utf8, utf8.size()) == stepped); - ensure("those before the end asked for", utf8str_grapheme_starts(utf8, 11) == std::vector({ 0, 1, 2, 3, 6, 9, 10 })); - ensure("nothing of nothing", utf8str_grapheme_starts(std::string(), 5).empty() && utf8str_grapheme_starts(utf8, 0).empty()); + CHECK(utf8str_grapheme_starts(utf8, utf8.size()) == stepped); + CHECK(utf8str_grapheme_starts(utf8, 11) == std::vector({ 0, 1, 2, 3, 6, 9, 10 })); + CHECK(utf8str_grapheme_starts(std::string(), 5).empty()); + CHECK(utf8str_grapheme_starts(utf8, 0).empty()); } // Every word at once is every word utf8str_next_word_range finds one at // a time from the start: over lines, contractions, numbers, scripts // that do not space their words, combining marks, emoji, blank lines // and nothing at all. - template<> template<> - void llstring_utf_object_t::test<146>() + TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_word_ranges finds the words next_word_range does", "[LLStringUTF]") { const std::string texts[] = { "", @@ -2972,10 +2830,11 @@ namespace tut } std::vector> all = { { 9, 9 } }; utf8str_word_ranges(text, all); - ensure("the same words in \"" + text + "\"", all == stepped); + CAPTURE(text); + REQUIRE(all == stepped); } std::vector> words; utf8str_word_ranges("don't stop", words); - ensure("a contraction is one word", words == std::vector>({ { 0, 5 }, { 6, 10 } })); + CHECK(words == std::vector>({ { 0, 5 }, { 6, 10 } })); } } diff --git a/indra/llcommon/tests/lltimer_test.cpp b/indra/llcommon/tests/lltimer_test.cpp index 22e9cf5216..ac93d5fe40 100644 --- a/indra/llcommon/tests/lltimer_test.cpp +++ b/indra/llcommon/tests/lltimer_test.cpp @@ -26,7 +26,7 @@ #include "../lltimer.h" -#include "../test/lltut.h" +#include #include #include @@ -34,7 +34,7 @@ #include #include -namespace tut +namespace { struct timer_data { @@ -42,74 +42,60 @@ namespace tut // LLCommon::initClass, which the test runner does not call. timer_data() { LLTimer::initClass(); } }; - typedef test_group timer_group; - typedef timer_group::object timer_object; - tut::timer_group timer_instance("LLTimer"); - template<> template<> - void timer_object::test<1>() + TEST_CASE_METHOD(timer_data, "LLTimer: a new timer reads expired until it is given an expiry", "[LLTimer]") { - set_test_name("a new timer reads expired until it is given an expiry"); LLTimer timer; - ensure("expired", timer.hasExpired()); + CHECK(timer.hasExpired()); timer.setTimerExpirySec(10.f); - ensure("not expired once given ten seconds", !timer.hasExpired()); + CHECK_FALSE(timer.hasExpired()); } - template<> template<> - void timer_object::test<2>() + TEST_CASE_METHOD(timer_data, "LLTimer: resetWithExpiry restarts the timer and keeps the expiry", "[LLTimer]") { - set_test_name("resetWithExpiry restarts the timer and keeps the expiry"); LLTimer timer; ms_sleep(50); timer.resetWithExpiry(10.f); - ensure("not expired", !timer.hasExpired()); - ensure("the whole expiry to run", timer.getRemainingTimeF32() > 9.9f); - ensure("restarted", timer.getElapsedTimeF32() < 0.04f); + CHECK_FALSE(timer.hasExpired()); + CHECK(timer.getRemainingTimeF32() > 9.9f); + CHECK(timer.getElapsedTimeF32() < 0.04f); } - template<> template<> - void timer_object::test<3>() + TEST_CASE_METHOD(timer_data, "LLTimer: start() after an expiry clears it which is why resetWithExpiry exists", "[LLTimer]") { - set_test_name("start() after an expiry clears it, which is why resetWithExpiry exists"); LLTimer timer; timer.setTimerExpirySec(10.f); timer.start(); - ensure("start() cleared the expiry", timer.hasExpired()); + CHECK(timer.hasExpired()); } - template<> template<> - void timer_object::test<4>() + TEST_CASE_METHOD(timer_data, "LLTimer: the uptime is the application's and not this timer's", "[LLTimer]") { - set_test_name("the uptime is the application's, not this timer's"); LLTimer timer; ms_sleep(50); timer.reset(); - ensure("the timer was just reset", timer.getElapsedTimeF64() < 0.04); - ensure("the uptime runs from before the reset", LLTimer::getUptimeSeconds() > timer.getElapsedTimeF64()); + CHECK(timer.getElapsedTimeF64() < 0.04); + UNSCOPED_INFO("the uptime runs from before the reset"); + CHECK(LLTimer::getUptimeSeconds() > timer.getElapsedTimeF64()); } - template<> template<> - void timer_object::test<5>() + TEST_CASE_METHOD(timer_data, "LLTimer: total time matches the calendar and keeps pace with the steady clock", "[LLTimer]") { - set_test_name("the total time reads the calendar once and keeps pace with the steady clock"); const auto calendar = std::chrono::system_clock::now().time_since_epoch(); const F64 calendar_seconds = std::chrono::duration(calendar).count(); const F64 total = LLTimer::getTotalSeconds(); - ensure("anchored to the calendar", std::fabs(total - calendar_seconds) < 1.0); + CHECK(std::fabs(total - calendar_seconds) < 1.0); const auto steady_start = std::chrono::steady_clock::now(); const U64 total_start = totalTime(); ms_sleep(200); const F64 steady = std::chrono::duration(std::chrono::steady_clock::now() - steady_start).count(); const F64 counted = (F64)((U64)totalTime() - total_start) / 1000000.0; - ensure("counts as the steady clock does", std::fabs(counted - steady) < 0.005); + CHECK(std::fabs(counted - steady) < 0.005); } - template<> template<> - void timer_object::test<6>() + TEST_CASE_METHOD(timer_data, "LLTimer: total time read on many threads never steps back or runs fast", "[LLTimer]") { - set_test_name("the total time read on many threads at once never steps and never runs fast"); const auto steady_start = std::chrono::steady_clock::now(); const U64 total_start = totalTime(); std::atomic stepped_back{ false }; @@ -136,7 +122,7 @@ namespace tut } const F64 steady = std::chrono::duration(std::chrono::steady_clock::now() - steady_start).count(); const F64 counted = (F64)((U64)totalTime() - total_start) / 1000000.0; - ensure("no thread saw it step back", !stepped_back); - ensure("counted no faster than the steady clock", counted < steady + 0.005); + CHECK_FALSE(stepped_back); + CHECK(counted < steady + 0.005); } } diff --git a/indra/llcommon/tests/lltrace_test.cpp b/indra/llcommon/tests/lltrace_test.cpp index af8fe63911..3e9e12e749 100644 --- a/indra/llcommon/tests/lltrace_test.cpp +++ b/indra/llcommon/tests/lltrace_test.cpp @@ -30,7 +30,7 @@ #include "lltrace.h" #include "lltracethreadrecorder.h" #include "lltracerecording.h" -#include "../test/lltut.h" +#include #ifdef LL_WINDOWS #pragma warning(disable : 4244) // possible loss of data on conversions @@ -59,36 +59,33 @@ LL_DECLARE_UNIT_TYPEDEFS(LLUnits, Grams); LL_DECLARE_UNIT_TYPEDEFS(LLUnits, Milligrams); -namespace tut +namespace { using namespace LLTrace; - namespace + // A ThreadRecorder makes itself this thread's recorder, with its own + // timer stack and current accumulators, and leaves all of them null + // when it goes. This takes the main thread's as they were before the + // test's recorder, and puts them back after it. + struct ThreadRecorderRestore { - // A ThreadRecorder makes itself this thread's recorder, with its own - // timer stack and current accumulators, and leaves all of them null - // when it goes. This takes the main thread's as they were before the - // test's recorder, and puts them back after it. - struct ThreadRecorderRestore + ThreadRecorder* mRecorder{ get_thread_recorder() }; + BlockTimerStackRecord* mTimerStack{ LLThreadLocalSingletonPointer::getInstance() }; + CountAccumulator* mCounts{ LLThreadLocalSingletonPointer::getInstance() }; + SampleAccumulator* mSamples{ LLThreadLocalSingletonPointer::getInstance() }; + EventAccumulator* mEvents{ LLThreadLocalSingletonPointer::getInstance() }; + TimeBlockAccumulator* mTimers{ LLThreadLocalSingletonPointer::getInstance() }; + + ~ThreadRecorderRestore() { - ThreadRecorder* mRecorder{ get_thread_recorder() }; - BlockTimerStackRecord* mTimerStack{ LLThreadLocalSingletonPointer::getInstance() }; - CountAccumulator* mCounts{ LLThreadLocalSingletonPointer::getInstance() }; - SampleAccumulator* mSamples{ LLThreadLocalSingletonPointer::getInstance() }; - EventAccumulator* mEvents{ LLThreadLocalSingletonPointer::getInstance() }; - TimeBlockAccumulator* mTimers{ LLThreadLocalSingletonPointer::getInstance() }; - - ~ThreadRecorderRestore() - { - set_thread_recorder(mRecorder); - LLThreadLocalSingletonPointer::setInstance(mTimerStack); - LLThreadLocalSingletonPointer::setInstance(mCounts); - LLThreadLocalSingletonPointer::setInstance(mSamples); - LLThreadLocalSingletonPointer::setInstance(mEvents); - LLThreadLocalSingletonPointer::setInstance(mTimers); - } - }; - } // anonymous namespace + set_thread_recorder(mRecorder); + LLThreadLocalSingletonPointer::setInstance(mTimerStack); + LLThreadLocalSingletonPointer::setInstance(mCounts); + LLThreadLocalSingletonPointer::setInstance(mSamples); + LLThreadLocalSingletonPointer::setInstance(mEvents); + LLThreadLocalSingletonPointer::setInstance(mTimers); + } + }; struct trace { @@ -97,10 +94,6 @@ namespace tut ThreadRecorder mRecorder; }; - typedef test_group trace_t; - typedef trace_t::object trace_object_t; - tut::trace_t tut_lltrace("LLTrace"); - static CountStatHandle sCupsOfCoffeeConsumed("coffeeconsumed", "Delicious cup of dark roast."); static SampleStatHandle sCaffeineLevelStat("caffeinelevel", "Coffee buzz quotient"); static EventStatHandle sOuncesPerCup("cupsize", "Large, huge, or ginormous"); @@ -121,8 +114,7 @@ namespace tut } // basic data collection - template<> template<> - void trace_object_t::test<1>() + TEST_CASE_METHOD(trace, "LLTrace: recordings collect counts and events and samples only while active", "[LLTrace]") { sample(sCaffeineLevelStat, sCaffeineLevel); @@ -150,30 +142,42 @@ namespace tut //after_3pm.stop(); //all_day.stop(); - ensure("count stats are counted when recording is active", - at_work.getSum(sCupsOfCoffeeConsumed) == 3 - && all_day.getSum(sCupsOfCoffeeConsumed) == 5 - && after_3pm.getSum(sCupsOfCoffeeConsumed) == 2); - ensure("measurement sums are counted when recording is active", - at_work.getSum(sOuncesPerCup) == S32Ounces(48) - && all_day.getSum(sOuncesPerCup) == S32Ounces(80) - && after_3pm.getSum(sOuncesPerCup) == S32Ounces(36)); - ensure("measurement min is specific to when recording is active", - at_work.getMin(sOuncesPerCup) == S32GrandeCup(1) - && all_day.getMin(sOuncesPerCup) == S32TallCup(1) - && after_3pm.getMin(sOuncesPerCup) == S32GrandeCup(1)); - ensure("measurement max is specific to when recording is active", - at_work.getMax(sOuncesPerCup) == S32GrandeCup(1) - && all_day.getMax(sOuncesPerCup) == S32VentiCup(1) - && after_3pm.getMax(sOuncesPerCup) == S32VentiCup(1)); - ensure("sample min is specific to when recording is active", - at_work.getMin(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1)).value() - && all_day.getMin(sCaffeineLevelStat) == F32Milligrams(0.f) - && after_3pm.getMin(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1) + (S32Ounces)S32GrandeCup(2)).value()); - ensure("sample max is specific to when recording is active", - at_work.getMax(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1) + (S32Ounces)S32GrandeCup(3)).value() - && all_day.getMax(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1) + (S32Ounces)S32GrandeCup(3) + (S32Ounces)S32VentiCup(1)).value() - && after_3pm.getMax(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1) + (S32Ounces)S32GrandeCup(3) + (S32Ounces)S32VentiCup(1)).value()); + { + INFO("count stats are counted when recording is active"); + CHECK(at_work.getSum(sCupsOfCoffeeConsumed) == 3); + CHECK(all_day.getSum(sCupsOfCoffeeConsumed) == 5); + CHECK(after_3pm.getSum(sCupsOfCoffeeConsumed) == 2); + } + { + INFO("measurement sums are counted when recording is active"); + CHECK(at_work.getSum(sOuncesPerCup) == S32Ounces(48)); + CHECK(all_day.getSum(sOuncesPerCup) == S32Ounces(80)); + CHECK(after_3pm.getSum(sOuncesPerCup) == S32Ounces(36)); + } + { + INFO("measurement min is specific to when recording is active"); + CHECK(at_work.getMin(sOuncesPerCup) == S32GrandeCup(1)); + CHECK(all_day.getMin(sOuncesPerCup) == S32TallCup(1)); + CHECK(after_3pm.getMin(sOuncesPerCup) == S32GrandeCup(1)); + } + { + INFO("measurement max is specific to when recording is active"); + CHECK(at_work.getMax(sOuncesPerCup) == S32GrandeCup(1)); + CHECK(all_day.getMax(sOuncesPerCup) == S32VentiCup(1)); + CHECK(after_3pm.getMax(sOuncesPerCup) == S32VentiCup(1)); + } + { + INFO("sample min is specific to when recording is active"); + CHECK(at_work.getMin(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1)).value()); + CHECK(all_day.getMin(sCaffeineLevelStat) == F32Milligrams(0.f)); + CHECK(after_3pm.getMin(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1) + (S32Ounces)S32GrandeCup(2)).value()); + } + { + INFO("sample max is specific to when recording is active"); + CHECK(at_work.getMax(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1) + (S32Ounces)S32GrandeCup(3)).value()); + CHECK(all_day.getMax(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1) + (S32Ounces)S32GrandeCup(3) + (S32Ounces)S32VentiCup(1)).value()); + CHECK(after_3pm.getMax(sCaffeineLevelStat) == sCaffeinePerOz * ((S32Ounces)S32TallCup(1) + (S32Ounces)S32GrandeCup(3) + (S32Ounces)S32VentiCup(1)).value()); + } } } diff --git a/indra/llcommon/tests/lltreeiterators_test.cpp b/indra/llcommon/tests/lltreeiterators_test.cpp index 44932ce7f6..b1bc03d3f1 100644 --- a/indra/llcommon/tests/lltreeiterators_test.cpp +++ b/indra/llcommon/tests/lltreeiterators_test.cpp @@ -44,20 +44,14 @@ #include "../lltreeiterators.h" #include "../llpointer.h" -#include "../test/lltut.h" +#include /***************************************************************************** * tut test group *****************************************************************************/ -namespace tut +namespace { - struct iter_data - { - }; - typedef test_group iter_group; - typedef iter_group::object iter_object; - tut::iter_group ig("LLTreeIterators"); -} // namespace tut +} // anonymous namespace namespace { @@ -231,10 +225,9 @@ class PlainNode } // anonymous namespace -namespace tut +namespace { - template<> template<> - void iter_object::test<1>() + TEST_CASE("LLTreeIterators: LLLinkedIter walks a non-refcounted list", "[LLTreeIterators]") { // set_test_name("LLLinkedIter -- non-refcounted class"); PlainNode* last(new PlainNode("c")); @@ -251,10 +244,9 @@ namespace tut pni != end; ++pni) { // std::cout << (*pni)->name() << '\n'; - ensure("LLLinkedIter(NULL)", false); + FAIL("LLLinkedIter(NULL) should not iterate"); } - ensure(desc1, - verify(desc1, + CHECK(verify(desc1, boost::make_iterator_range(LLLinkedIter(first, boost::bind(&PlainNode::mNext, _1)), LLLinkedIter()), @@ -263,8 +255,7 @@ namespace tut // std::cout << desc2 << ":\n"; // for (LLLinkedIter pni(first, boost::bind(&PlainNode::next, _1)); ! (pni == end); ++pni) // std::cout << (**pni).name() << '\n'; - ensure(desc2, - verify(desc2, + CHECK(verify(desc2, boost::make_iterator_range(LLLinkedIter(first, boost::bind(&PlainNode::next, _1)), LLLinkedIter()), @@ -276,11 +267,11 @@ namespace tut } { LLLinkedIter pni(first, boost::bind(&PlainNode::next, _1)); - ensure_equals("first", (*pni++)->name(), "a"); - ensure_equals("second", (*pni )->name(), "b"); + CHECK((*pni++)->name() == "a"); + CHECK((*pni )->name() == "b"); } } -} // tut +} // anonymous namespace namespace { @@ -335,10 +326,9 @@ RCNode::~RCNode() } // anonymous namespace -namespace tut +namespace { - template<> template<> - void iter_object::test<2>() + TEST_CASE("LLTreeIterators: LLLinkedIter keeps a refcounted list alive", "[LLTreeIterators]") { // set_test_name("LLLinkedIter -- refcounted class"); LLLinkedIter rcni, end2; @@ -350,20 +340,20 @@ namespace tut rcni = LLLinkedIter(head, boost::bind(&RCNode::next, _1)); } // std::cout << "Now the LLLinkedIter is the only remaining reference to RCNode chain\n"; - ensure_equals(last_RCNode_destroyed, ""); - ensure(rcni != end2); - ensure_equals((*rcni)->name(), "x"); + CHECK(last_RCNode_destroyed == ""); + REQUIRE(rcni != end2); + CHECK((*rcni)->name() == "x"); ++rcni; - ensure_equals(last_RCNode_destroyed, "x"); - ensure(rcni != end2); - ensure_equals((*rcni)->name(), "y"); + CHECK(last_RCNode_destroyed == "x"); + REQUIRE(rcni != end2); + CHECK((*rcni)->name() == "y"); ++rcni; - ensure_equals(last_RCNode_destroyed, "y"); - ensure(rcni != end2); - ensure_equals((*rcni)->name(), "z"); + CHECK(last_RCNode_destroyed == "y"); + REQUIRE(rcni != end2); + CHECK((*rcni)->name() == "z"); ++rcni; - ensure_equals(last_RCNode_destroyed, "z"); - ensure(rcni == end2); + CHECK(last_RCNode_destroyed == "z"); + CHECK(rcni == end2); } } @@ -1093,26 +1083,24 @@ bool LLTreeIter_tests(const std::string& nodename, } // anonymous namespace -namespace tut +namespace { - template<> template<> - void iter_object::test<3>() + TEST_CASE("LLTreeIterators: LLTreeRootIter and LLTreeWalkIter visit each tree in order", "[LLTreeIterators]") { // set_test_name("LLTreeIter tests"); - ensure(LLTreeIter_tests + CHECK(LLTreeIter_tests ("TreeNode", boost::bind(&TreeNode::getParent, _1), boost::bind(&TreeNode::child_begin, _1), boost::bind(&TreeNode::child_end, _1))); - ensure(LLTreeIter_tests > + CHECK(LLTreeIter_tests > ("PlainTree", boost::bind(&PlainTree::mParent, _1), PlainTree_child_begin, PlainTree_child_end)); } - template<> template<> - void iter_object::test<4>() + TEST_CASE("LLTreeIterators: getRootRange() runs from a node to the root", "[LLTreeIterators]") { // set_test_name("getRootRange() tests"); // This test function illustrates the looping techniques described in the @@ -1133,8 +1121,7 @@ namespace tut { // std::cout << node->name() << '\n'; } - ensure(desc1, - verify(desc1, getRootRange(tnB2b), RootExpected())); + CHECK(verify(desc1, getRootRange(tnB2b), RootExpected())); EnhancedTreeNodePtr etnroot(example_tree()); EnhancedTreeNodePtr etnB2b(get_B2b @@ -1158,12 +1145,10 @@ namespace tut { // std::cout << node->name() << '\n'; } - ensure(desc2, - verify(desc2, etnB2b->getRootRange(), RootExpected())); + CHECK(verify(desc2, etnB2b->getRootRange(), RootExpected())); } - template<> template<> - void iter_object::test<5>() + TEST_CASE("LLTreeIterators: getWalkRange() walks each kind of tree in each order", "[LLTreeIterators]") { // set_test_name("getWalkRange() tests"); // This test function doesn't illustrate the looping permutations for @@ -1173,36 +1158,30 @@ namespace tut // TreeNode, using helper function TreeNodePtr tnroot(example_tree()); std::string desc_tnpre("getWalkRange(tnroot)"); - ensure(desc_tnpre, - verify(desc_tnpre, + CHECK(verify(desc_tnpre, getWalkRange(tnroot), WalkExpected())); std::string desc_tnpost("getWalkRange(tnroot)"); - ensure(desc_tnpost, - verify(desc_tnpost, + CHECK(verify(desc_tnpost, getWalkRange(tnroot), WalkExpected())); std::string desc_tnb("getWalkRange(tnroot)"); - ensure(desc_tnb, - verify(desc_tnb, + CHECK(verify(desc_tnb, getWalkRange(tnroot), WalkExpected())); // EnhancedTreeNode, using method EnhancedTreeNodePtr etnroot(example_tree()); std::string desc_etnpre("etnroot->getWalkRange()"); - ensure(desc_etnpre, - verify(desc_etnpre, + CHECK(verify(desc_etnpre, etnroot->getWalkRange(), WalkExpected())); std::string desc_etnpost("etnroot->getWalkRange()"); - ensure(desc_etnpost, - verify(desc_etnpost, + CHECK(verify(desc_etnpost, etnroot->getWalkRange(), WalkExpected())); std::string desc_etnb("etnroot->getWalkRange()"); - ensure(desc_etnb, - verify(desc_etnb, + CHECK(verify(desc_etnb, etnroot->getWalkRange(), WalkExpected())); @@ -1210,19 +1189,16 @@ namespace tut PlainTree* ptroot(example_tree()); Cleanup cleanup(ptroot); std::string desc_ptpre("getWalkRange(ptroot)"); - ensure(desc_ptpre, - verify(desc_ptpre, + CHECK(verify(desc_ptpre, getWalkRange(ptroot), WalkExpected())); std::string desc_ptpost("getWalkRange(ptroot)"); - ensure(desc_ptpost, - verify(desc_ptpost, + CHECK(verify(desc_ptpost, getWalkRange(ptroot), WalkExpected())); std::string desc_ptb("getWalkRange(ptroot)"); - ensure(desc_ptb, - verify(desc_ptb, + CHECK(verify(desc_ptb, getWalkRange(ptroot), WalkExpected())); } -} // tut +} // anonymous namespace diff --git a/indra/llcommon/tests/llunits_test.cpp b/indra/llcommon/tests/llunits_test.cpp index cb7924f630..1277e22cbf 100644 --- a/indra/llcommon/tests/llunits_test.cpp +++ b/indra/llcommon/tests/llunits_test.cpp @@ -28,7 +28,7 @@ #include "linden_common.h" #include "llunits.h" -#include "../test/lltut.h" +#include namespace LLUnits { @@ -60,147 +60,139 @@ LL_DECLARE_UNIT_TYPEDEFS(LLUnits, Fahrenheit); LL_DECLARE_UNIT_TYPEDEFS(LLUnits, Kelvin); -namespace tut +namespace { using namespace LLUnits; - struct units - { - }; - - typedef test_group units_t; - typedef units_t::object units_object_t; - tut::units_t tut_llunits("LLUnit"); // storage type conversions - template<> template<> - void units_object_t::test<1>() + TEST_CASE("LLUnit: units convert between storage types and integers truncate", "[LLUnit]") { LLUnit float_quatloos; - ensure("default float unit is zero", float_quatloos == F32Quatloos(0.f)); + CHECK(float_quatloos == F32Quatloos(0.f)); LLUnit float_initialize_quatloos(1); - ensure("non-zero initialized unit", float_initialize_quatloos == F32Quatloos(1.f)); + CHECK(float_initialize_quatloos == F32Quatloos(1.f)); LLUnit int_quatloos; - ensure("default int unit is zero", int_quatloos == S32Quatloos(0)); + CHECK(int_quatloos == S32Quatloos(0)); int_quatloos = S32Quatloos(42); - ensure("int assignment is preserved", int_quatloos == S32Quatloos(42)); + CHECK(int_quatloos == S32Quatloos(42)); float_quatloos = int_quatloos; - ensure("float assignment from int preserves value", float_quatloos == F32Quatloos(42.f)); + CHECK(float_quatloos == F32Quatloos(42.f)); int_quatloos = float_quatloos; - ensure("int assignment from float preserves value", int_quatloos == S32Quatloos(42)); + CHECK(int_quatloos == S32Quatloos(42)); float_quatloos = F32Quatloos(42.1f); int_quatloos = float_quatloos; - ensure("int units truncate float units on assignment", int_quatloos == S32Quatloos(42)); + CHECK(int_quatloos == S32Quatloos(42)); LLUnit unsigned_int_quatloos(float_quatloos); - ensure("unsigned int can be initialized from signed int", unsigned_int_quatloos == S32Quatloos(42)); + CHECK(unsigned_int_quatloos == S32Quatloos(42)); S32Solari int_solari(1); float_quatloos = int_solari; - ensure("fractional units are preserved in conversion from integer to float type", float_quatloos == F32Quatloos(0.25f)); + UNSCOPED_INFO("fractional units are preserved in conversion from integer to float type"); + CHECK(float_quatloos == F32Quatloos(0.25f)); int_quatloos = S32Quatloos(1); F32Solari float_solari = int_quatloos; - ensure("can convert with fractional intermediates from integer to float type", float_solari == F32Solari(4.f)); + UNSCOPED_INFO("can convert with fractional intermediates from integer to float type"); + CHECK(float_solari == F32Solari(4.f)); } // conversions to/from base unit - template<> template<> - void units_object_t::test<2>() + TEST_CASE("LLUnit: units convert to and from their base unit", "[LLUnit]") { LLUnit quatloos(1.f); LLUnit latinum_bars(quatloos); - ensure("conversion between units is automatic via initialization", latinum_bars == F32Latinum(1.f / 4.f)); + CHECK(latinum_bars == F32Latinum(1.f / 4.f)); latinum_bars = S32Latinum(256); quatloos = latinum_bars; - ensure("conversion between units is automatic via assignment, and bidirectional", quatloos == S32Quatloos(1024)); + CHECK(quatloos == S32Quatloos(1024)); LLUnit single_quatloo(1); LLUnit quarter_latinum = single_quatloo; - ensure("division of integer unit preserves fractional values when converted to float unit", quarter_latinum == F32Latinum(0.25f)); + UNSCOPED_INFO("division of integer unit preserves fractional values when converted to float unit"); + CHECK(quarter_latinum == F32Latinum(0.25f)); } // conversions across non-base units - template<> template<> - void units_object_t::test<3>() + TEST_CASE("LLUnit: non-base units convert into each other", "[LLUnit]") { LLUnit quatloos(1024); LLUnit solari(quatloos); - ensure("conversions can work between indirectly related units: Quatloos -> Latinum -> Solari", solari == S32Solari(4096)); + UNSCOPED_INFO("conversions can work between indirectly related units: Quatloos -> Latinum -> Solari"); + CHECK(solari == S32Solari(4096)); LLUnit latinum_bars = solari; - ensure("Non base units can be converted between each other", latinum_bars == S32Latinum(256)); + CHECK(latinum_bars == S32Latinum(256)); } // math operations - template<> template<> - void units_object_t::test<4>() + TEST_CASE("LLUnit: arithmetic works with scalars and with other units", "[LLUnit]") { // exercise math operations LLUnit quatloos(1.f); quatloos *= 4.f; - ensure(quatloos == S32Quatloos(4)); + CHECK(quatloos == S32Quatloos(4)); quatloos = quatloos * 2; - ensure(quatloos == S32Quatloos(8)); + CHECK(quatloos == S32Quatloos(8)); quatloos = 2.f * quatloos; - ensure(quatloos == S32Quatloos(16)); + CHECK(quatloos == S32Quatloos(16)); quatloos += F32Quatloos(4.f); - ensure(quatloos == S32Quatloos(20)); + CHECK(quatloos == S32Quatloos(20)); quatloos += S32Quatloos(4); - ensure(quatloos == S32Quatloos(24)); + CHECK(quatloos == S32Quatloos(24)); quatloos = quatloos + S32Quatloos(4); - ensure(quatloos == S32Quatloos(28)); + CHECK(quatloos == S32Quatloos(28)); quatloos = S32Quatloos(4) + quatloos; - ensure(quatloos == S32Quatloos(32)); + CHECK(quatloos == S32Quatloos(32)); quatloos += quatloos * 3; - ensure(quatloos == S32Quatloos(128)); + CHECK(quatloos == S32Quatloos(128)); quatloos -= quatloos / 4 * 3; - ensure(quatloos == S32Quatloos(32)); + CHECK(quatloos == S32Quatloos(32)); quatloos = quatloos - S32Quatloos(8); - ensure(quatloos == S32Quatloos(24)); + CHECK(quatloos == S32Quatloos(24)); quatloos -= S32Quatloos(4); - ensure(quatloos == S32Quatloos(20)); + CHECK(quatloos == S32Quatloos(20)); quatloos -= F32Quatloos(4.f); - ensure(quatloos == S32Quatloos(16)); + CHECK(quatloos == S32Quatloos(16)); quatloos /= 2.f; - ensure(quatloos == S32Quatloos(8)); + CHECK(quatloos == S32Quatloos(8)); quatloos = quatloos / 4; - ensure(quatloos == S32Quatloos(2)); + CHECK(quatloos == S32Quatloos(2)); F32 ratio = quatloos / LLUnit(2.f); - ensure(ratio == 1); + CHECK(ratio == 1); ratio = quatloos / LLUnit(8.f); - ensure(ratio == 1); + CHECK(ratio == 1); quatloos += LLUnit(8.f); - ensure(quatloos == S32Quatloos(4)); + CHECK(quatloos == S32Quatloos(4)); quatloos -= LLUnit(1.f); - ensure(quatloos == S32Quatloos(0)); + CHECK(quatloos == S32Quatloos(0)); } // comparison operators - template<> template<> - void units_object_t::test<5>() + TEST_CASE("LLUnit: units compare across storage types and units", "[LLUnit]") { LLUnit quatloos(1); - ensure("can perform less than comparison against same type", quatloos < S32Quatloos(2)); - ensure("can perform less than comparison against different storage type", quatloos < F32Quatloos(2.f)); - ensure("can perform less than comparison against different units", quatloos < S32Latinum(5)); - ensure("can perform less than comparison against different storage type and units", quatloos < F32Latinum(5.f)); + CHECK(quatloos < S32Quatloos(2)); + CHECK(quatloos < F32Quatloos(2.f)); + CHECK(quatloos < S32Latinum(5)); + CHECK(quatloos < F32Latinum(5.f)); - ensure("can perform greater than comparison against same type", quatloos > S32Quatloos(0)); - ensure("can perform greater than comparison against different storage type", quatloos > F32Quatloos(0.f)); - ensure("can perform greater than comparison against different units", quatloos > S32Latinum(0)); - ensure("can perform greater than comparison against different storage type and units", quatloos > F32Latinum(0.f)); + CHECK(quatloos > S32Quatloos(0)); + CHECK(quatloos > F32Quatloos(0.f)); + CHECK(quatloos > S32Latinum(0)); + CHECK(quatloos > F32Latinum(0.f)); } @@ -210,116 +202,113 @@ namespace tut } // signature compatibility - template<> template<> - void units_object_t::test<6>() + TEST_CASE("LLUnit: unit values pass as function arguments", "[LLUnit]") { S32Quatloos quatloos(1); - ensure("can pass unit values as argument", accept_explicit_quatloos(S32Quatloos(1))); - ensure("can pass unit values as argument", accept_explicit_quatloos(quatloos)); + CHECK(accept_explicit_quatloos(S32Quatloos(1))); + CHECK(accept_explicit_quatloos(quatloos)); } // implicit units - template<> template<> - void units_object_t::test<7>() + TEST_CASE("LLUnit: implicit units mix with explicit units and plain values", "[LLUnit]") { LLUnit quatloos; LLUnitImplicit quatloos_implicit = quatloos + S32Quatloos(1); - ensure("can initialize implicit unit from explicit", quatloos_implicit == 1); + CHECK(quatloos_implicit == 1); quatloos = quatloos_implicit; - ensure("can assign implicit unit to explicit unit", quatloos == S32Quatloos(1)); + CHECK(quatloos == S32Quatloos(1)); quatloos += quatloos_implicit; - ensure("can perform math operation using mixture of implicit and explicit units", quatloos == S32Quatloos(2)); + CHECK(quatloos == S32Quatloos(2)); // math operations on implicits quatloos_implicit = 1; - ensure(quatloos_implicit == 1); + CHECK(quatloos_implicit == 1); quatloos_implicit += 2; - ensure(quatloos_implicit == 3); + CHECK(quatloos_implicit == 3); quatloos_implicit *= 2; - ensure(quatloos_implicit == 6); + CHECK(quatloos_implicit == 6); quatloos_implicit -= 1; - ensure(quatloos_implicit == 5); + CHECK(quatloos_implicit == 5); quatloos_implicit /= 5; - ensure(quatloos_implicit == 1); + CHECK(quatloos_implicit == 1); quatloos_implicit = quatloos_implicit + 3 + quatloos_implicit; - ensure(quatloos_implicit == 5); + CHECK(quatloos_implicit == 5); quatloos_implicit = 10 - quatloos_implicit - 1; - ensure(quatloos_implicit == 4); + CHECK(quatloos_implicit == 4); quatloos_implicit = 2 * quatloos_implicit * 2; - ensure(quatloos_implicit == 16); + CHECK(quatloos_implicit == 16); F32 one_half = quatloos_implicit / (quatloos_implicit * 2); - ensure(one_half == 0.5f); + CHECK(one_half == 0.5f); // implicit conversion to POD F32 float_val = quatloos_implicit; - ensure("implicit units convert implicitly to regular values", float_val == 16); + CHECK(float_val == 16); S32 int_val = (S32)quatloos_implicit; - ensure("implicit units convert implicitly to regular values", int_val == 16); + CHECK(int_val == 16); // conversion of implicits LLUnitImplicit latinum_implicit(2); - ensure("implicit units of different types are comparable", latinum_implicit * 2 == quatloos_implicit); + CHECK(latinum_implicit * 2 == quatloos_implicit); quatloos_implicit += F32Quatloos(10); - ensure("can add-assign explicit units", quatloos_implicit == 26); + CHECK(quatloos_implicit == 26); quatloos_implicit -= F32Quatloos(10); - ensure("can subtract-assign explicit units", quatloos_implicit == 16); + CHECK(quatloos_implicit == 16); // comparisons - ensure("can compare greater than implicit unit", quatloos_implicit > F32QuatloosImplicit(0.f)); - ensure("can compare greater than non-implicit unit", quatloos_implicit > F32Quatloos(0.f)); - ensure("can compare greater than or equal to implicit unit", quatloos_implicit >= F32QuatloosImplicit(0.f)); - ensure("can compare greater than or equal to non-implicit unit", quatloos_implicit >= F32Quatloos(0.f)); - ensure("can compare less than implicit unit", quatloos_implicit < F32QuatloosImplicit(20.f)); - ensure("can compare less than non-implicit unit", quatloos_implicit < F32Quatloos(20.f)); - ensure("can compare less than or equal to implicit unit", quatloos_implicit <= F32QuatloosImplicit(20.f)); - ensure("can compare less than or equal to non-implicit unit", quatloos_implicit <= F32Quatloos(20.f)); + CHECK(quatloos_implicit > F32QuatloosImplicit(0.f)); + CHECK(quatloos_implicit > F32Quatloos(0.f)); + CHECK(quatloos_implicit >= F32QuatloosImplicit(0.f)); + CHECK(quatloos_implicit >= F32Quatloos(0.f)); + CHECK(quatloos_implicit < F32QuatloosImplicit(20.f)); + CHECK(quatloos_implicit < F32Quatloos(20.f)); + CHECK(quatloos_implicit <= F32QuatloosImplicit(20.f)); + CHECK(quatloos_implicit <= F32Quatloos(20.f)); } // precision tests - template<> template<> - void units_object_t::test<8>() + TEST_CASE("LLUnit: conversions keep full precision and mix signed with unsigned", "[LLUnit]") { U32Bytes max_bytes(U32_MAX); S32Megabytes mega_bytes = max_bytes; - ensure("max available precision is used when converting units", mega_bytes == (S32Megabytes)4095); + UNSCOPED_INFO("max available precision is used when converting units"); + CHECK(mega_bytes == (S32Megabytes)4095); mega_bytes = (S32Megabytes)-5 + (U32Megabytes)1; - ensure("can mix signed and unsigned in units addition", mega_bytes == (S32Megabytes)-4); + CHECK(mega_bytes == (S32Megabytes)-4); mega_bytes = (U32Megabytes)5 + (S32Megabytes)-1; - ensure("can mix unsigned and signed in units addition", mega_bytes == (S32Megabytes)4); + CHECK(mega_bytes == (S32Megabytes)4); } // default units - template<> template<> - void units_object_t::test<9>() + TEST_CASE("LLUnit: the built-in units convert by their factors", "[LLUnit]") { U32Gigabytes GB(1); U32Megabytes MB(GB); U32Kilobytes KB(GB); U32Bytes B(GB); - ensure("GB -> MB conversion", MB.value() == 1024); - ensure("GB -> KB conversion", KB.value() == 1024 * 1024); - ensure("GB -> B conversion", B.value() == 1024 * 1024 * 1024); + CHECK(MB.value() == 1024); + CHECK(KB.value() == 1024 * 1024); + CHECK(B.value() == 1024 * 1024 * 1024); KB = U32Kilobytes(1); U32Kilobits Kb(KB); U32Bits b(KB); - ensure("KB -> Kb conversion", Kb.value() == 8); - ensure("KB -> b conversion", b.value() == 8 * 1024); + CHECK(Kb.value() == 8); + CHECK(b.value() == 8 * 1024); U32Days days(1); U32Hours hours(days); @@ -327,40 +316,40 @@ namespace tut U32Seconds seconds(days); U32Milliseconds ms(days); - ensure("days -> hours conversion", hours.value() == 24); - ensure("days -> minutes conversion", minutes.value() == 24 * 60); - ensure("days -> seconds conversion", seconds.value() == 24 * 60 * 60); - ensure("days -> ms conversion", ms.value() == 24 * 60 * 60 * 1000); + CHECK(hours.value() == 24); + CHECK(minutes.value() == 24 * 60); + CHECK(seconds.value() == 24 * 60 * 60); + CHECK(ms.value() == 24 * 60 * 60 * 1000); U32Kilometers km(1); U32Meters m(km); U32Centimeters cm(km); U32Millimeters mm(km); - ensure("km -> m conversion", m.value() == 1000); - ensure("km -> cm conversion", cm.value() == 1000 * 100); - ensure("km -> mm conversion", mm.value() == 1000 * 1000); + CHECK(m.value() == 1000); + CHECK(cm.value() == 1000 * 100); + CHECK(mm.value() == 1000 * 1000); U32Gigahertz GHz(1); U32Megahertz MHz(GHz); U32Kilohertz KHz(GHz); U32Hertz Hz(GHz); - ensure("GHz -> MHz conversion", MHz.value() == 1000); - ensure("GHz -> KHz conversion", KHz.value() == 1000 * 1000); - ensure("GHz -> Hz conversion", Hz.value() == 1000 * 1000 * 1000); + CHECK(MHz.value() == 1000); + CHECK(KHz.value() == 1000 * 1000); + CHECK(Hz.value() == 1000 * 1000 * 1000); F32Radians rad(6.2831853071795f); S32Degrees deg(rad); - ensure("radians -> degrees conversion", deg.value() == 360); + CHECK(deg.value() == 360); F32Percent percent(50); F32Ratio ratio(percent); - ensure("percent -> ratio conversion", ratio.value() == 0.5f); + CHECK(ratio.value() == 0.5f); U32Kilotriangles ktris(1); U32Triangles tris(ktris); - ensure("kilotriangles -> triangles conversion", tris.value() == 1000); + CHECK(tris.value() == 1000); } static bool value_near(F32 value, F32 target, F32 threshold) @@ -369,21 +358,20 @@ namespace tut } // linear transforms - template<> template<> - void units_object_t::test<10>() + TEST_CASE("LLUnit: linear transforms convert Celsius to Fahrenheit and back", "[LLUnit]") { F32Celcius float_celcius(100); F32Fahrenheit float_fahrenheit(float_celcius); - ensure("floating point celcius -> fahrenheit conversion using linear transform", value_near(float_fahrenheit.value(), 212, 0.1f) ); + CHECK(value_near(float_fahrenheit.value(), 212, 0.1f)); float_celcius = float_fahrenheit; - ensure("floating point fahrenheit -> celcius conversion using linear transform (round trip)", value_near(float_celcius.value(), 100.f, 0.1f) ); + CHECK(value_near(float_celcius.value(), 100.f, 0.1f)); S32Celcius int_celcius(100); S32Fahrenheit int_fahrenheit(int_celcius); - ensure("integer celcius -> fahrenheit conversion using linear transform", int_fahrenheit.value() == 212); + CHECK(int_fahrenheit.value() == 212); int_celcius = int_fahrenheit; - ensure("integer fahrenheit -> celcius conversion using linear transform (round trip)", int_celcius.value() == 100); + CHECK(int_celcius.value() == 100); } } diff --git a/indra/llcommon/tests/lluri_test.cpp b/indra/llcommon/tests/lluri_test.cpp index b48dd3ef1b..f364a5a995 100644 --- a/indra/llcommon/tests/lluri_test.cpp +++ b/indra/llcommon/tests/lluri_test.cpp @@ -30,248 +30,219 @@ #include "../llsd.h" #include "../lluri.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct URITestData { - void checkParts(const LLURI& u, + void require_parts(const LLURI& u, const char* expectedScheme, const char* expectedOpaque, const char* expectedAuthority, const char* expectedPath, const char* expectedQuery = "") { - ensure_equals("scheme", u.scheme(), expectedScheme); - ensure_equals("opaque", u.opaque(), expectedOpaque); - ensure_equals("authority", u.authority(), expectedAuthority); - ensure_equals("path", u.path(), expectedPath); - ensure_equals("query", u.query(), expectedQuery); + INFO("uri " << u.asString()); + REQUIRE(u.scheme() == expectedScheme); + REQUIRE(u.opaque() == expectedOpaque); + REQUIRE(u.authority() == expectedAuthority); + REQUIRE(u.path() == expectedPath); + REQUIRE(u.query() == expectedQuery); } - void escapeRoundTrip(const std::string& uri_raw_1) + void require_escape_round_trip(const std::string& uri_raw_1) { std::string uri_esc_1(LLURI::escape(uri_raw_1)); std::string uri_raw_2(LLURI::unescape(uri_esc_1)); - ensure_equals("escape/unescape raw", uri_raw_2, uri_raw_1); + REQUIRE(uri_raw_2 == uri_raw_1); std::string uri_esc_2(LLURI::escape(uri_raw_2)); - ensure_equals("escape/unescape escaped", uri_esc_2, uri_esc_1); + REQUIRE(uri_esc_2 == uri_esc_1); } }; - typedef test_group URITestGroup; - typedef URITestGroup::object URITestObject; - - URITestGroup uriTestGroup("LLURI"); - - template<> template<> - void URITestObject::test<1>() + TEST_CASE_METHOD(URITestData, "LLURI: parts and query map of an http URI", "[LLURI]") { LLURI u("http://abc.com/def/ghi?x=37&y=hello"); - ensure_equals("scheme", u.scheme(), "http"); - ensure_equals("authority", u.authority(), "abc.com"); - ensure_equals("path", u.path(), "/def/ghi"); - ensure_equals("query", u.query(), "x=37&y=hello"); + CHECK(u.scheme() == "http"); + CHECK(u.authority() == "abc.com"); + CHECK(u.path() == "/def/ghi"); + CHECK(u.query() == "x=37&y=hello"); - ensure_equals("host name", u.hostName(), "abc.com"); - ensure_equals("host port", u.hostPort(), 80); + CHECK(u.hostName() == "abc.com"); + CHECK(u.hostPort() == 80); LLSD query = u.queryMap(); - ensure_equals("query x", query["x"].asInteger(), 37); - ensure_equals("query y", query["y"].asString(), "hello"); + CHECK(query["x"].asInteger() == 37); + CHECK(query["y"].asString() == "hello"); query = LLURI::queryMap("x=22.23&y=https://lindenlab.com/"); - ensure_equals("query x", query["x"].asReal(), 22.23); - ensure_equals("query y", query["y"].asURI().asString(), "https://lindenlab.com/"); + CHECK(query["x"].asReal() == 22.23); + CHECK(query["y"].asURI().asString() == "https://lindenlab.com/"); } - template<> template<> - void URITestObject::test<2>() + TEST_CASE_METHOD(URITestData, "LLURI: an empty string has no parts", "[LLURI]") { - set_test_name("empty string"); - checkParts(LLURI(""), "", "", "", ""); + require_parts(LLURI(""), "", "", "", ""); } - template<> template<> - void URITestObject::test<3>() + TEST_CASE_METHOD(URITestData, "LLURI: without a scheme the whole string is opaque", "[LLURI]") { - set_test_name("no scheme"); - checkParts(LLURI("foo"), "", "foo", "", ""); - checkParts(LLURI("foo%3A"), "", "foo:", "", ""); + require_parts(LLURI("foo"), "", "foo", "", ""); + require_parts(LLURI("foo%3A"), "", "foo:", "", ""); } - template<> template<> - void URITestObject::test<4>() + TEST_CASE_METHOD(URITestData, "LLURI: a scheme without paths leaves no authority or path", "[LLURI]") { - set_test_name("scheme w/o paths"); - checkParts(LLURI("mailto:zero@ll.com"), + require_parts(LLURI("mailto:zero@ll.com"), "mailto", "zero@ll.com", "", ""); - checkParts(LLURI("silly://abc/def?foo"), + require_parts(LLURI("silly://abc/def?foo"), "silly", "//abc/def?foo", "", ""); } - template<> template<> - void URITestObject::test<5>() + TEST_CASE_METHOD(URITestData, "LLURI: the authority ends at the first unescaped slash or question mark", "[LLURI]") { - set_test_name("authority section"); - checkParts(LLURI("http:///"), + require_parts(LLURI("http:///"), "http", "///", "", "/"); - checkParts(LLURI("http://abc"), + require_parts(LLURI("http://abc"), "http", "//abc", "abc", ""); - checkParts(LLURI("http://a%2Fb/cd"), + require_parts(LLURI("http://a%2Fb/cd"), "http", "//a/b/cd", "a/b", "/cd"); - checkParts(LLURI("http://host?"), + require_parts(LLURI("http://host?"), "http", "//host?", "host", ""); } - template<> template<> - void URITestObject::test<6>() + TEST_CASE_METHOD(URITestData, "LLURI: the path is unescaped and keeps colons and trailing slashes", "[LLURI]") { - set_test_name("path section"); - checkParts(LLURI("http://host/a/b/"), + require_parts(LLURI("http://host/a/b/"), "http", "//host/a/b/", "host", "/a/b/"); - checkParts(LLURI("http://host/a%3Fb/"), + require_parts(LLURI("http://host/a%3Fb/"), "http", "//host/a?b/", "host", "/a?b/"); - checkParts(LLURI("http://host/a:b/"), + require_parts(LLURI("http://host/a:b/"), "http", "//host/a:b/", "host", "/a:b/"); } - template<> template<> - void URITestObject::test<7>() + TEST_CASE_METHOD(URITestData, "LLURI: the query follows the first question mark and is unescaped", "[LLURI]") { - set_test_name("query string"); - checkParts(LLURI("http://host/?"), + require_parts(LLURI("http://host/?"), "http", "//host/?", "host", "/", ""); - checkParts(LLURI("http://host/?x"), + require_parts(LLURI("http://host/?x"), "http", "//host/?x", "host", "/", "x"); - checkParts(LLURI("http://host/??"), + require_parts(LLURI("http://host/??"), "http", "//host/??", "host", "/", "?"); - checkParts(LLURI("http://host/?%3F"), + require_parts(LLURI("http://host/?%3F"), "http", "//host/??", "host", "/", "?"); } - template<> template<> - void URITestObject::test<8>() + TEST_CASE_METHOD(URITestData, "LLURI: buildHTTP joins and escapes path parts and a query map", "[LLURI]") { LLSD path; path.append("x"); path.append("123"); - checkParts(LLURI::buildHTTP("host", path), + require_parts(LLURI::buildHTTP("host", path), "http", "//host/x/123", "host", "/x/123"); LLSD query; query["123"] = "12"; query["abcd"] = "abc"; - checkParts(LLURI::buildHTTP("host", path, query), + require_parts(LLURI::buildHTTP("host", path, query), "http", "//host/x/123?123=12&abcd=abc", "host", "/x/123", "123=12&abcd=abc"); - ensure_equals(LLURI::buildHTTP("host", "").asString(), + CHECK(LLURI::buildHTTP("host", "").asString() == "http://host"); - ensure_equals(LLURI::buildHTTP("host", "/").asString(), + CHECK(LLURI::buildHTTP("host", "/").asString() == "http://host/"); - ensure_equals(LLURI::buildHTTP("host", "//").asString(), + CHECK(LLURI::buildHTTP("host", "//").asString() == "http://host/"); - ensure_equals(LLURI::buildHTTP("host", "dir name").asString(), + CHECK(LLURI::buildHTTP("host", "dir name").asString() == "http://host/dir%20name"); - ensure_equals(LLURI::buildHTTP("host", "dir name/").asString(), + CHECK(LLURI::buildHTTP("host", "dir name/").asString() == "http://host/dir%20name/"); - ensure_equals(LLURI::buildHTTP("host", "/dir name").asString(), + CHECK(LLURI::buildHTTP("host", "/dir name").asString() == "http://host/dir%20name"); - ensure_equals(LLURI::buildHTTP("host", "/dir name/").asString(), + CHECK(LLURI::buildHTTP("host", "/dir name/").asString() == "http://host/dir%20name/"); - ensure_equals(LLURI::buildHTTP("host", "dir name/subdir name").asString(), + CHECK(LLURI::buildHTTP("host", "dir name/subdir name").asString() == "http://host/dir%20name/subdir%20name"); - ensure_equals(LLURI::buildHTTP("host", "dir name/subdir name/").asString(), + CHECK(LLURI::buildHTTP("host", "dir name/subdir name/").asString() == "http://host/dir%20name/subdir%20name/"); - ensure_equals(LLURI::buildHTTP("host", "/dir name/subdir name").asString(), + CHECK(LLURI::buildHTTP("host", "/dir name/subdir name").asString() == "http://host/dir%20name/subdir%20name"); - ensure_equals(LLURI::buildHTTP("host", "/dir name/subdir name/").asString(), + CHECK(LLURI::buildHTTP("host", "/dir name/subdir name/").asString() == "http://host/dir%20name/subdir%20name/"); - ensure_equals(LLURI::buildHTTP("host", "//dir name//subdir name//").asString(), + CHECK(LLURI::buildHTTP("host", "//dir name//subdir name//").asString() == "http://host/dir%20name/subdir%20name/"); } - template<> template<> - void URITestObject::test<9>() + TEST_CASE_METHOD(URITestData, "LLURI: buildHTTP takes path parts with unescaped special characters", "[LLURI]") { - set_test_name("test unescaped path components"); LLSD path; path.append("x@*//*$&^"); path.append("123"); - checkParts(LLURI::buildHTTP("host", path), + require_parts(LLURI::buildHTTP("host", path), "http", "//host/x@*//*$&^/123", "host", "/x@*//*$&^/123"); } - template<> template<> - void URITestObject::test<10>() + TEST_CASE_METHOD(URITestData, "LLURI: buildHTTP takes a query with unescaped special characters", "[LLURI]") { - set_test_name("test unescaped query components"); LLSD path; path.append("x"); path.append("123"); LLSD query; query["123"] = "?&*#//"; query["**@&?//"] = "abc"; - checkParts(LLURI::buildHTTP("host", path, query), + require_parts(LLURI::buildHTTP("host", path, query), "http", "//host/x/123?**@&?//=abc&123=?&*#//", "host", "/x/123", "**@&?//=abc&123=?&*#//"); } - template<> template<> - void URITestObject::test<11>() + TEST_CASE_METHOD(URITestData, "LLURI: buildHTTP takes a host with unescaped special characters", "[LLURI]") { - set_test_name("test unescaped host components"); LLSD path; path.append("x"); path.append("123"); LLSD query; query["123"] = "12"; query["abcd"] = "abc"; - checkParts(LLURI::buildHTTP("hi123*33--}{:portstuffs", path, query), + require_parts(LLURI::buildHTTP("hi123*33--}{:portstuffs", path, query), "http", "//hi123*33--}{:portstuffs/x/123?123=12&abcd=abc", "hi123*33--}{:portstuffs", "/x/123", "123=12&abcd=abc"); } - template<> template<> - void URITestObject::test<12>() + TEST_CASE_METHOD(URITestData, "LLURI: buildHTTP takes a host_port that is really a URI prefix", "[LLURI]") { - set_test_name("test funky host_port values that are actually prefixes"); - - checkParts(LLURI::buildHTTP("http://example.com:8080", LLSD()), + require_parts(LLURI::buildHTTP("http://example.com:8080", LLSD()), "http", "//example.com:8080", "example.com:8080", ""); - checkParts(LLURI::buildHTTP("http://example.com:8080/", LLSD()), + require_parts(LLURI::buildHTTP("http://example.com:8080/", LLSD()), "http", "//example.com:8080/", "example.com:8080", "/"); - checkParts(LLURI::buildHTTP("http://example.com:8080/a/b", LLSD()), + require_parts(LLURI::buildHTTP("http://example.com:8080/a/b", LLSD()), "http", "//example.com:8080/a/b", "example.com:8080", "/a/b"); } - template<> template<> - void URITestObject::test<13>() + TEST_CASE_METHOD(URITestData, "LLURI: escape percent-encodes all but the allowed characters", "[LLURI]") { const std::string unreserved = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" "0123456789" "-._~"; - set_test_name("test escape"); - ensure_equals("escaping", LLURI::escape("abcdefg", "abcdef"), "abcdef%67"); - ensure_equals("escaping", LLURI::escape("|/&\\+-_!@", ""), "%7C%2F%26%5C%2B%2D%5F%21%40"); - ensure_equals("escaping as query variable", - LLURI::escape("http://10.0.1.4:12032/agent/god/agent-id/map/layer/?resume=http://station3.ll.com:12032/agent/203ad6df-b522-491d-ba48-4e24eb57aeff/send-postcard", unreserved + ":@!$'()*+,="), + CHECK(LLURI::escape("abcdefg", "abcdef") == "abcdef%67"); + CHECK(LLURI::escape("|/&\\+-_!@", "") == "%7C%2F%26%5C%2B%2D%5F%21%40"); + UNSCOPED_INFO("escaping as query variable"); + CHECK(LLURI::escape("http://10.0.1.4:12032/agent/god/agent-id/map/layer/?resume=http://station3.ll.com:12032/agent/203ad6df-b522-491d-ba48-4e24eb57aeff/send-postcard", unreserved + ":@!$'()*+,=") == "http:%2F%2F10.0.1.4:12032%2Fagent%2Fgod%2Fagent-id%2Fmap%2Flayer%2F%3Fresume=http:%2F%2Fstation3.ll.com:12032%2Fagent%2F203ad6df-b522-491d-ba48-4e24eb57aeff%2Fsend-postcard"); // French cedilla (C with squiggle, like in the word Francais) is UTF-8 C3 A7 @@ -282,53 +253,41 @@ namespace tut std::string cedilla; cedilla.push_back( (char)0xC3 ); cedilla.push_back( (char)0xA7 ); - ensure_equals("escape UTF8", LLURI::escape( cedilla, unreserved), "%C3%A7"); + CHECK(LLURI::escape( cedilla, unreserved) == "%C3%A7"); } - template<> template<> - void URITestObject::test<14>() + TEST_CASE_METHOD(URITestData, "LLURI: escape and unescape of an empty string are empty", "[LLURI]") { - set_test_name("make sure escape and unescape of empty strings return empty strings."); std::string uri_esc(LLURI::escape("")); - ensure("escape string empty", uri_esc.empty()); + CHECK(uri_esc.empty()); std::string uri_raw(LLURI::unescape("")); - ensure("unescape string empty", uri_raw.empty()); + CHECK(uri_raw.empty()); } - template<> template<> - void URITestObject::test<15>() + TEST_CASE_METHOD(URITestData, "LLURI: escape and unescape round-trip URIs", "[LLURI]") { - set_test_name("do some round-trip tests"); - escapeRoundTrip("http://secondlife.com"); - escapeRoundTrip("http://secondlife.com/url with spaces"); - escapeRoundTrip("http://bad[domain]name.com/"); - escapeRoundTrip("ftp://bill.gates@ms/micro$oft.com/c:\\autoexec.bat"); - escapeRoundTrip(""); + require_escape_round_trip("http://secondlife.com"); + require_escape_round_trip("http://secondlife.com/url with spaces"); + require_escape_round_trip("http://bad[domain]name.com/"); + require_escape_round_trip("ftp://bill.gates@ms/micro$oft.com/c:\\autoexec.bat"); + require_escape_round_trip(""); } - template<> template<> - void URITestObject::test<16>() + TEST_CASE_METHOD(URITestData, "LLURI: escape by default escapes colons and slashes too", "[LLURI]") { - set_test_name("Test the default escaping"); // yes -- this mangles the url. This is expected behavior std::string simple("http://secondlife.com"); - ensure_equals( - "simple http", - LLURI::escape(simple), + CHECK(LLURI::escape(simple) == "http%3A%2F%2Fsecondlife.com"); - ensure_equals( - "needs escape", - LLURI::escape("http://get.secondlife.com/windows viewer"), + CHECK(LLURI::escape("http://get.secondlife.com/windows viewer") == "http%3A%2F%2Fget.secondlife.com%2Fwindows%20viewer"); } - template<> template<> - void URITestObject::test<17>() + TEST_CASE_METHOD(URITestData, "LLURI: escape and unescape round-trip very long strings", "[LLURI]") { - set_test_name("do some round-trip tests with very long strings."); - escapeRoundTrip("Welcome to Second Life.We hope you'll have a richly rewarding experience, filled with creativity, self expression and fun.The goals of the Community Standards are simple: treat each other with respect and without harassment, adhere to local standards as indicated by simulator ratings, and refrain from any hate activity which slurs a real-world individual or real-world community. Behavioral Guidelines - The Big Six"); - escapeRoundTrip( + require_escape_round_trip("Welcome to Second Life.We hope you'll have a richly rewarding experience, filled with creativity, self expression and fun.The goals of the Community Standards are simple: treat each other with respect and without harassment, adhere to local standards as indicated by simulator ratings, and refrain from any hate activity which slurs a real-world individual or real-world community. Behavioral Guidelines - The Big Six"); + require_escape_round_trip( "'asset_data':b(12100){'task_id':ucc706f2d-0b68-68f8-11a4-f1043ff35ca0}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444921\n\ttotal_crc\t323\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.368634403\t0.00781063363\t-0.569040775\n\toldpos\t150.117996\t25.8658009\t8.19664001\n\trotation\t-0.06293071806430816650390625\t-0.6995697021484375\t-0.7002241611480712890625\t0.1277817934751510620117188\n\tchildpos\t-0.00499999989\t-0.0359999985\t0.307999998\n\tchildrot\t-0.515492737293243408203125\t-0.46601200103759765625\t0.529055416584014892578125\t0.4870323240756988525390625\n\tscale" "\t0.074629\t0.289956\t0.01\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tf" "aces\t6\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204" @@ -347,75 +306,69 @@ namespace tut } - template<> template<> - void URITestObject::test<18>() + TEST_CASE_METHOD(URITestData, "LLURI: a secondlife URI splits with or without an authority", "[LLURI]") { LLURI u("secondlife:///app/login?first_name=Testert4&last_name=Tester&web_login_key=test"); // if secondlife is the scheme, LLURI should parse /app/login as path, with no authority - ensure_equals("scheme", u.scheme(), "secondlife"); - ensure_equals("authority", u.authority(), ""); - ensure_equals("path", u.path(), "/app/login"); - ensure_equals("pathmap", u.pathArray()[0].asString(), "app"); - ensure_equals("pathmap", u.pathArray()[1].asString(), "login"); - ensure_equals("query", u.query(), "first_name=Testert4&last_name=Tester&web_login_key=test"); - ensure_equals("query map element", u.queryMap()["last_name"].asString(), "Tester"); + CHECK(u.scheme() == "secondlife"); + CHECK(u.authority() == ""); + CHECK(u.path() == "/app/login"); + CHECK(u.pathArray()[0].asString() == "app"); + CHECK(u.pathArray()[1].asString() == "login"); + CHECK(u.query() == "first_name=Testert4&last_name=Tester&web_login_key=test"); + CHECK(u.queryMap()["last_name"].asString() == "Tester"); u = LLURI("secondlife://Da Boom/128/128/128"); // if secondlife is the scheme, LLURI should parse /128/128/128 as path, with Da Boom as authority - ensure_equals("scheme", u.scheme(), "secondlife"); - ensure_equals("authority", u.authority(), "Da Boom"); - ensure_equals("path", u.path(), "/128/128/128"); - ensure_equals("pathmap", u.pathArray()[0].asString(), "128"); - ensure_equals("pathmap", u.pathArray()[1].asString(), "128"); - ensure_equals("pathmap", u.pathArray()[2].asString(), "128"); - ensure_equals("query", u.query(), ""); + CHECK(u.scheme() == "secondlife"); + CHECK(u.authority() == "Da Boom"); + CHECK(u.path() == "/128/128/128"); + CHECK(u.pathArray()[0].asString() == "128"); + CHECK(u.pathArray()[1].asString() == "128"); + CHECK(u.pathArray()[2].asString() == "128"); + CHECK(u.query() == ""); } - template<> template<> - void URITestObject::test<19>() + TEST_CASE_METHOD(URITestData, "LLURI: an about: URI has a path and a query", "[LLURI]") { - set_test_name("Parse about: schemes"); LLURI u("about:blank?redirect-http-hack=secondlife%3A%2F%2F%2Fapp%2Flogin%3Ffirst_name%3DCallum%26last_name%3DLinden%26location%3Dspecify%26grid%3Dvaak%26region%3D%2FMorris%2F128%2F128%26web_login_key%3Defaa4795-c2aa-4c58-8966-763c27931e78"); - ensure_equals("scheme", u.scheme(), "about"); - ensure_equals("authority", u.authority(), ""); - ensure_equals("path", u.path(), "blank"); - ensure_equals("pathmap", u.pathArray()[0].asString(), "blank"); - ensure_equals("query", u.query(), "redirect-http-hack=secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); - ensure_equals("query map element", u.queryMap()["redirect-http-hack"].asString(), "secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); + CHECK(u.scheme() == "about"); + CHECK(u.authority() == ""); + CHECK(u.path() == "blank"); + CHECK(u.pathArray()[0].asString() == "blank"); + CHECK(u.query() == "redirect-http-hack=secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); + CHECK(u.queryMap()["redirect-http-hack"].asString() == "secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); } - template<> template<> - void URITestObject::test<20>() + TEST_CASE_METHOD(URITestData, "LLURI: escapePathAndData escapes paths and data and leaves escapes alone", "[LLURI]") { - set_test_name("escapePathAndData uri test"); - // Basics scheme:[//authority]path[?query][#fragment] - ensure_equals(LLURI::escapePathAndData("dirname?query"), + CHECK(LLURI::escapePathAndData("dirname?query") == "dirname?query"); - ensure_equals(LLURI::escapePathAndData("dirname?query=data"), + CHECK(LLURI::escapePathAndData("dirname?query=data") == "dirname?query=data"); - ensure_equals(LLURI::escapePathAndData("host://dirname/subdir name?query#fragment"), + CHECK(LLURI::escapePathAndData("host://dirname/subdir name?query#fragment") == "host://dirname/subdir%20name?query#fragment"); - ensure_equals(LLURI::escapePathAndData("host://dirname/subdir name?query=some@>data#fragment"), + CHECK(LLURI::escapePathAndData("host://dirname/subdir name?query=some@>data#fragment") == "host://dirname/subdir%20name?query=some@%3Edata#fragment"); - ensure_equals(LLURI::escapePathAndData("host://dir[name/subdir name?query=some[data#fra[gment"), + CHECK(LLURI::escapePathAndData("host://dir[name/subdir name?query=some[data#fra[gment") == "host://dir[name/subdir%20name?query=some%5Bdata#fra[gment"); - ensure_equals(LLURI::escapePathAndData("mailto:zero@ll.com"), + CHECK(LLURI::escapePathAndData("mailto:zero@ll.com") == "mailto:zero@ll.com"); // pre-escaped - ensure_equals(LLURI::escapePathAndData("host://dirname/subdir%20name"), + CHECK(LLURI::escapePathAndData("host://dirname/subdir%20name") == "host://dirname/subdir%20name"); // data:[][;base64], - ensure_equals(LLURI::escapePathAndData("data:,Hello, World!"), + CHECK(LLURI::escapePathAndData("data:,Hello, World!") == "data:,Hello%2C%20World%21"); - ensure_equals(LLURI::escapePathAndData("data:text/html,

Hello, World!

"), + CHECK(LLURI::escapePathAndData("data:text/html,

Hello, World!

") == "data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E"); // pre-escaped - ensure_equals(LLURI::escapePathAndData("data:text/html,%3Ch1%3EHello%2C%20World!"), + CHECK(LLURI::escapePathAndData("data:text/html,%3Ch1%3EHello%2C%20World!") == "data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E"); // assume that base64 does not need escaping - ensure_equals(LLURI::escapePathAndData("data:image;base64,SGVs/bG8sIFd/vcmxkIQ%3D%3D!-&*?="), + CHECK(LLURI::escapePathAndData("data:image;base64,SGVs/bG8sIFd/vcmxkIQ%3D%3D!-&*?=") == "data:image;base64,SGVs/bG8sIFd/vcmxkIQ%3D%3D!-&*?="); } } diff --git a/indra/llcommon/tests/lluuid_test.cpp b/indra/llcommon/tests/lluuid_test.cpp index 45da7e3705..bcb8ea0012 100644 --- a/indra/llcommon/tests/lluuid_test.cpp +++ b/indra/llcommon/tests/lluuid_test.cpp @@ -30,22 +30,17 @@ #include "../lluuid.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { - struct lluuid_data { }; - typedef test_group lluuid_t; - typedef lluuid_t::object lluuid_object_t; - tut::lluuid_t tut_lluuid("LLUUID"); - static const std::string kSampleStr = "01020304-0506-0708-090a-0b0c0d0e0f10"; // Compile-time verification that LLUUID is constexpr-constructible. static_assert(LLUUID{}.mData[0] == 0, "default LLUUID is zero-initialised at compile time"); // LLUUID::null / LLTransactionID::tnull are exported out-of-line constants // (dllimport in consumers), so they are no longer constexpr. Their all-zero - // value is verified at runtime in test<1> instead. + // value is verified at runtime in the default-construction case instead. constexpr U8 kConstexprBytes[UUID_BYTES] = { 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, @@ -62,7 +57,7 @@ namespace tut } // --------------------------------------------------------------------- - // Helpers for the hash group (tests 9, 15-21). + // Helpers for the hash cases. // // An LLUUID is sixteen bytes, and the viewer uses it as its general // 16-byte key, not only as a random v4 UUID: tracking ids, composed asset @@ -96,36 +91,33 @@ namespace tut return z ^ (z >> 31); } - namespace + // Occupancy of a 256-bucket table indexed from one end of the hash or the + // other: containers take the index from the low bits (MSVC's + // std::unordered_map, any hand-rolled `h & (n-1)`) or from the high bits + // (boost's open-addressing tables), so both ends have to be mixed. + struct BucketSpread { - // Occupancy of a 256-bucket table indexed from one end of the hash or the - // other: containers take the index from the low bits (MSVC's - // std::unordered_map, any hand-rolled `h & (n-1)`) or from the high bits - // (boost's open-addressing tables), so both ends have to be mixed. - struct BucketSpread - { - static constexpr size_t NUM_BUCKETS = 256; - int mLowMax = 0, mHighMax = 0, mLowEmpty = 0, mHighEmpty = 0; + static constexpr size_t NUM_BUCKETS = 256; + int mLowMax = 0, mHighMax = 0, mLowEmpty = 0, mHighEmpty = 0; - explicit BucketSpread(const std::vector& ids) + explicit BucketSpread(const std::vector& ids) + { + std::vector low(NUM_BUCKETS, 0), high(NUM_BUCKETS, 0); + for (const LLUUID& id : ids) { - std::vector low(NUM_BUCKETS, 0), high(NUM_BUCKETS, 0); - for (const LLUUID& id : ids) - { - const size_t h = hash_value(id); - ++low[h & (NUM_BUCKETS - 1)]; - ++high[(h >> (sizeof(size_t) * 8 - 8)) & (NUM_BUCKETS - 1)]; - } - for (size_t i = 0; i < NUM_BUCKETS; ++i) - { - if (low[i] > mLowMax) mLowMax = low[i]; - if (high[i] > mHighMax) mHighMax = high[i]; - if (!low[i]) ++mLowEmpty; - if (!high[i]) ++mHighEmpty; - } + const size_t h = hash_value(id); + ++low[h & (NUM_BUCKETS - 1)]; + ++high[(h >> (sizeof(size_t) * 8 - 8)) & (NUM_BUCKETS - 1)]; } - }; - } // anonymous namespace + for (size_t i = 0; i < NUM_BUCKETS; ++i) + { + if (low[i] > mLowMax) mLowMax = low[i]; + if (high[i] > mHighMax) mHighMax = high[i]; + if (!low[i]) ++mLowEmpty; + if (!high[i]) ++mHighEmpty; + } + } + }; // Structured payloads: a counter walking each 8-byte window, every // one-bit value, a pair of small ints in the outermost lanes, and short @@ -177,77 +169,73 @@ namespace tut // The hash tells boost::unordered's open-addressing tables that it is // already mixed, which makes them skip their own mulx pass. Every property - // measured in tests 15-19 is what backs that claim. + // measured in the cases from both halves to the degenerate inputs is what + // backs that claim. static_assert(boost::hash_is_avalanching>::value, "boost::hash must be declared avalanching"); static_assert(boost::hash_is_avalanching>::value, "std::hash must be declared avalanching"); // Default construction is null. - template<> template<> - void lluuid_object_t::test<1>() + TEST_CASE("LLUUID: default construction is null and so are null and tnull", "[LLUUID]") { LLUUID id; - ensure("default-constructed isNull", id.isNull()); - ensure("default-constructed !notNull", !id.notNull()); - ensure("default-constructed == LLUUID::null", id == LLUUID::null); + CHECK(id.isNull()); + CHECK_FALSE(id.notNull()); + CHECK(id == LLUUID::null); // null / tnull are the all-zero UUID (verified at runtime since they are // exported out-of-line constants, no longer constexpr). - ensure("LLUUID::null isNull", LLUUID::null.isNull()); - ensure("LLTransactionID::tnull isNull", LLTransactionID::tnull.isNull()); + CHECK(LLUUID::null.isNull()); + CHECK(LLTransactionID::tnull.isNull()); for (int i = 0; i < UUID_BYTES; ++i) { - ensure_equals("default byte zero", (int)id.mData[i], 0); + CAPTURE(i); + REQUIRE((int)id.mData[i] == 0); } } - // setNull zeroes every byte. - template<> template<> - void lluuid_object_t::test<2>() + TEST_CASE("LLUUID: setNull zeroes every byte", "[LLUUID]") { LLUUID id; fill_sample(id); - ensure("filled UUID is not null", id.notNull()); + CHECK(id.notNull()); id.setNull(); - ensure("setNull -> isNull", id.isNull()); + CHECK(id.isNull()); for (int i = 0; i < UUID_BYTES; ++i) { - ensure_equals("setNull byte zero", (int)id.mData[i], 0); + CAPTURE(i); + REQUIRE((int)id.mData[i] == 0); } } // String parsing round-trip via set() / asString(). - template<> template<> - void lluuid_object_t::test<3>() + TEST_CASE("LLUUID: set() parses a string that asString() gives back", "[LLUUID]") { LLUUID id; - ensure("set valid string", id.set(kSampleStr)); - ensure_equals("asString round-trips", id.asString(), kSampleStr); + CHECK(id.set(kSampleStr)); + CHECK(id.asString() == kSampleStr); for (int i = 0; i < UUID_BYTES; ++i) { - ensure_equals("byte parsed correctly", (int)id.mData[i], i + 1); + CAPTURE(i); + REQUIRE((int)id.mData[i] == i + 1); } } - // set() with an empty string clears to null; invalid input also nulls. - template<> template<> - void lluuid_object_t::test<4>() + TEST_CASE("LLUUID: set() with an empty string clears to null; invalid input also nulls", "[LLUUID]") { LLUUID id; fill_sample(id); - ensure("empty string set returns true", id.set(std::string())); - ensure("empty string -> null", id.isNull()); + CHECK(id.set(std::string())); + CHECK(id.isNull()); fill_sample(id); - ensure("bogus string set returns false", - !id.set(std::string("not-a-uuid"), false)); - ensure("bogus string -> null", id.isNull()); + CHECK_FALSE(id.set(std::string("not-a-uuid"), false)); + CHECK(id.isNull()); } // Equality / inequality for matching and one-byte-different UUIDs. - template<> template<> - void lluuid_object_t::test<5>() + TEST_CASE("LLUUID: equality and inequality for matching and one-byte-different UUIDs", "[LLUUID]") { LLUUID a, b, c; fill_sample(a); @@ -255,17 +243,16 @@ namespace tut fill_sample(c); c.mData[UUID_BYTES - 1] ^= 0x01; - ensure("a == b", a == b); - ensure("!(a != b)", !(a != b)); - ensure("a != c", a != c); - ensure("!(a == c)", !(a == c)); + CHECK(a == b); + CHECK_FALSE(a != b); + CHECK(a != c); + CHECK_FALSE(a == c); } // Byte-lexicographic sort order. IW's comment in lluuid.cpp warns against // optimizing operator< with word-wide reads precisely because the word // interpretation reverses bytes on LE. This test pins the byte-lex behavior. - template<> template<> - void lluuid_object_t::test<6>() + TEST_CASE("LLUUID: operator< orders bytes lexicographically", "[LLUUID]") { LLUUID low, high; low.setNull(); @@ -273,15 +260,14 @@ namespace tut low.mData[1] = 0x01; // word value 0x100 on LE high.mData[0] = 0x01; // word value 0x001 on LE — but first byte wins - ensure("byte-lex: low < high", low < high); - ensure("byte-lex: high > low", high > low); - ensure("irreflexive: !(low < low)", !(low < low)); - ensure("irreflexive: !(low > low)", !(low > low)); + CHECK(low < high); + CHECK(high > low); + CHECK_FALSE(low < low); + CHECK_FALSE(low > low); } // operator^= is its own inverse (XOR twice cancels) and commutative. - template<> template<> - void lluuid_object_t::test<7>() + TEST_CASE("LLUUID: XOR by an id twice cancels and XOR with itself is null", "[LLUUID]") { LLUUID a, b, original; fill_sample(a); @@ -291,114 +277,96 @@ namespace tut b.set("11223344-5566-7788-99aa-bbccddeeff00"); LLUUID xored = a ^ b; - ensure("XOR changes value", xored != a); + CHECK(xored != a); LLUUID xored_twice = xored ^ b; - ensure_equals("double XOR cancels", xored_twice, original); + CHECK(xored_twice == original); LLUUID xored_self = a ^ a; - ensure("self-XOR yields null", xored_self.isNull()); + CHECK(xored_self.isNull()); } // Bit-level regression for the digest/CRC functions. These values are // computed assuming the host is little-endian (effectively all targets); // they pin the pre-UB-fix numerical output so callers that persist or // compare these checksums are unaffected. - template<> template<> - void lluuid_object_t::test<8>() + TEST_CASE("LLUUID: getCRC32 and getCRC16 give their pinned values", "[LLUUID]") { - ensure_equals("null CRC32 == 0", LLUUID::null.getCRC32(), (U32)0); - ensure_equals("null CRC16 == 0", LLUUID::null.getCRC16(), (U16)0); + CHECK(LLUUID::null.getCRC32() == (U32)0); + CHECK(LLUUID::null.getCRC16() == (U16)0); LLUUID id; fill_sample(id); // mData = {0x01, 0x02, ..., 0x10} // LE U32s: 0x04030201, 0x08070605, 0x0c0b0a09, 0x100f0e0d — sum 0x2824201C - ensure_equals("sample CRC32", id.getCRC32(), (U32)0x2824201C); + CHECK(id.getCRC32() == (U32)0x2824201C); // LE U16s sum: high bytes (2+4+...+16)=72=0x48; low bytes (1+3+...+15)=64=0x40 - ensure_equals("sample CRC16", id.getCRC16(), (U16)0x4840); + CHECK(id.getCRC16() == (U16)0x4840); } // Hash: the four spellings of it agree, and equal IDs hash equally. - template<> template<> - void lluuid_object_t::test<9>() + TEST_CASE("LLUUID: the hash spellings agree and equal ids hash equally", "[LLUUID]") { LLUUID a, b; fill_sample(a); fill_sample(b); - ensure_equals("equal IDs equal std::hash", - std::hash{}(a), std::hash{}(b)); - ensure_equals("std::hash matches hash_value", - std::hash{}(a), hash_value(b)); - ensure_equals("boost::hash matches hash_value", - boost::hash{}(a), hash_value(b)); + CHECK(std::hash{}(a) == std::hash{}(b)); + CHECK(std::hash{}(a) == hash_value(b)); + CHECK(boost::hash{}(a) == hash_value(b)); // A different UUID should (overwhelmingly likely) hash differently. LLUUID c; c.set("ffffffff-ffff-ffff-ffff-ffffffffffff"); - ensure_not_equals("distinct IDs typically hash differently", - std::hash{}(a), std::hash{}(c)); + CHECK(std::hash{}(a) != std::hash{}(c)); // LLTransactionID and LLAssetID reach the same hash through the base. LLTransactionID tid; fill_sample(tid); - ensure_equals("LLTransactionID hashes as its LLUUID bytes", - boost::hash{}(tid), hash_value(a)); + CHECK(boost::hash{}(tid) == hash_value(a)); const LLAssetID aid = a; - ensure_equals("LLAssetID hashes as its LLUUID bytes", - std::hash{}(aid), hash_value(a)); + CHECK(std::hash{}(aid) == hash_value(a)); } // generateNewID produces distinct, non-null UUIDs. - template<> template<> - void lluuid_object_t::test<10>() + TEST_CASE("LLUUID: generateNewID produces distinct non-null valid UUIDs", "[LLUUID]") { LLUUID a = LLUUID::generateNewID(); LLUUID b = LLUUID::generateNewID(); - ensure("generated id is not null", a.notNull()); - ensure("generated id is not null (2)", b.notNull()); - ensure("two generated ids differ", a != b); + CHECK(a.notNull()); + CHECK(b.notNull()); + CHECK(a != b); // validate() should accept the generated string form - ensure("generated id validates", LLUUID::validate(a.asString())); + CHECK(LLUUID::validate(a.asString())); } // to_chars emits exactly UUID_STR_LENGTH-1 chars in canonical 8-4-4-4-12 form. - template<> template<> - void lluuid_object_t::test<11>() + TEST_CASE("LLUUID: to_chars emits UUID_STR_LENGTH-1 chars in canonical 8-4-4-4-12 form", "[LLUUID]") { LLUUID id; id.set(kSampleStr); char buf[UUID_STR_LENGTH] = {}; id.to_chars(buf); - ensure_equals("to_chars matches set string", - std::string(buf), kSampleStr); - ensure_equals("to_chars length", strlen(buf), (size_t)(UUID_STR_LENGTH - 1)); + CHECK(std::string(buf) == kSampleStr); + CHECK(strlen(buf) == (size_t)(UUID_STR_LENGTH - 1)); } - // ostream insertion delegates to the canonical 36-char form. - template<> template<> - void lluuid_object_t::test<12>() + TEST_CASE("LLUUID: ostream insertion delegates to the canonical 36-char form", "[LLUUID]") { LLUUID id; id.set(kSampleStr); std::ostringstream os; os << id; - ensure_equals("operator<< matches asString", os.str(), id.asString()); + CHECK(os.str() == id.asString()); } - // validate() accepts canonical forms and rejects garbage. - template<> template<> - void lluuid_object_t::test<13>() + TEST_CASE("LLUUID: validate() accepts canonical forms and rejects garbage", "[LLUUID]") { - ensure("validate canonical", LLUUID::validate(kSampleStr)); - ensure("reject short string", !LLUUID::validate("abc")); - ensure("reject non-hex characters", - !LLUUID::validate("zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz")); + CHECK(LLUUID::validate(kSampleStr)); + CHECK_FALSE(LLUUID::validate("abc")); + CHECK_FALSE(LLUUID::validate("zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz")); } - // std::set via the default operator< yields byte-lex iteration. - template<> template<> - void lluuid_object_t::test<14>() + TEST_CASE("LLUUID: std::set via the default operator< yields byte-lex iteration", "[LLUUID]") { LLUUID one, two; one.setNull(); @@ -411,11 +379,9 @@ namespace tut s.insert(one); auto it = s.begin(); - ensure_equals("first sorted entry has lowest first byte", - (int)it->mData[0], 1); + CHECK((int)it->mData[0] == 1); ++it; - ensure_equals("second sorted entry has next first byte", - (int)it->mData[0], 2); + CHECK((int)it->mData[0] == 2); } // Both halves of the value must reach the hash independently. @@ -423,8 +389,7 @@ namespace tut // This is the test that fails the moment someone replaces the hash with // `lo ^ hi` or any other fold of the two halves: a fold gives the same // answer for (lo, hi) and (lo^m, hi^m), and for (lo, hi) and (hi, lo). - template<> template<> - void lluuid_object_t::test<15>() + TEST_CASE("LLUUID: both halves of the value reach the hash independently", "[LLUUID]") { U64 state = 0x5eed01; int xor_equivalent = 0; @@ -439,11 +404,11 @@ namespace tut if (h == hash_value(make_id(lo ^ m, hi ^ m))) ++xor_equivalent; if (h == hash_value(make_id(hi, lo))) ++half_swapped; } - ensure_equals("ids with an equal XOR of halves must not share a hash " - "(the hash is not a fold of the two halves)", - xor_equivalent, 0); - ensure_equals("swapping the two halves must change the hash", - half_swapped, 0); + UNSCOPED_INFO("ids with an equal XOR of halves must not share a hash " + "(the hash is not a fold of the two halves)"); + CHECK(xor_equivalent == 0); + UNSCOPED_INFO("swapping the two halves must change the hash"); + CHECK(half_swapped == 0); } // Strict avalanche: flipping any one of the 128 input bits must flip each @@ -451,8 +416,7 @@ namespace tut // per output bit, so the sampling error is well under a percent and the // [0.35, 0.65] band is generous for a good hash while far out of reach for // a fold (0.016) or a single constant multiply (0.18). - template<> template<> - void lluuid_object_t::test<16>() + TEST_CASE("LLUUID: flipping any input bit flips each output bit about half the time", "[LLUUID]") { const int kBases = 256; const int kOutputBits = (int)(sizeof(size_t) * 8); @@ -482,16 +446,14 @@ namespace tut } } - ensure_equals("no single input bit flip may leave the hash unchanged", - fixed_points, 0); + UNSCOPED_INFO("no single input bit flip may leave the hash unchanged"); + CHECK(fixed_points == 0); for (int o = 0; o < kOutputBits; ++o) { const double rate = (double)flips[o] / (double)trials; - ensure("every output bit must flip near half the time for a " - "single-bit input change (output bit " - + std::to_string(o) + " flipped " - + std::to_string(rate) + " of the time)", - rate >= 0.35 && rate <= 0.65); + CAPTURE(o); + REQUIRE(rate >= 0.35); + REQUIRE(rate <= 0.65); } } @@ -499,8 +461,7 @@ namespace tut // This is what a composed key looks like: a fixed prefix and a counter. // Both ends of the hash have to spread it, because containers index from // the low bits or the high bits depending on which one you reach for. - template<> template<> - void lluuid_object_t::test<17>() + TEST_CASE("LLUUID: a counter in one 32-bit lane spreads over both ends of the hash", "[LLUUID]") { for (int lane = 0; lane < 4; ++lane) { @@ -516,28 +477,23 @@ namespace tut const BucketSpread spread(ids); const int mean = (int)(ids.size() / BucketSpread::NUM_BUCKETS); // 16 - const std::string where = " (lane " + std::to_string(lane) + ")"; - - ensure("counter in one lane must not pile up in the low bits" + where, - spread.mLowMax <= 3 * mean); - ensure("counter in one lane must not pile up in the high bits" + where, - spread.mHighMax <= 3 * mean); - ensure_equals("counter in one lane must reach every low-bit bucket" + where, - spread.mLowEmpty, 0); - ensure_equals("counter in one lane must reach every high-bit bucket" + where, - spread.mHighEmpty, 0); + + CAPTURE(lane); + REQUIRE(spread.mLowMax <= 3 * mean); + REQUIRE(spread.mHighMax <= 3 * mean); + REQUIRE(spread.mLowEmpty == 0); + REQUIRE(spread.mHighEmpty == 0); } } // The mixed structured corpus: no full 64-bit collisions at all, and no // bucket carrying more than three times its share at either end. - template<> template<> - void lluuid_object_t::test<18>() + TEST_CASE("LLUUID: the structured corpus has no full collisions and no crowded buckets", "[LLUUID]") { std::vector corpus; build_structured_corpus(corpus); - ensure("structured corpus is large enough to be meaningful", - corpus.size() > 8000); + UNSCOPED_INFO("structured corpus is large enough to be meaningful"); + REQUIRE(corpus.size() > 8000); std::set seen; int collisions = 0; @@ -545,32 +501,26 @@ namespace tut { if (!seen.insert(hash_value(id)).second) ++collisions; } - ensure_equals("structured 16-byte payloads must not collide outright", - collisions, 0); + CHECK(collisions == 0); const BucketSpread spread(corpus); const int mean = (int)(corpus.size() / BucketSpread::NUM_BUCKETS); - ensure("structured payloads must not pile up in the low bits", - spread.mLowMax <= 3 * mean); - ensure("structured payloads must not pile up in the high bits", - spread.mHighMax <= 3 * mean); + CHECK(spread.mLowMax <= 3 * mean); + CHECK(spread.mHighMax <= 3 * mean); } // Degenerate inputs: the null UUID must not hash to the zero sentinel, and // the 128 one-bit values must all land somewhere different. - template<> template<> - void lluuid_object_t::test<19>() + TEST_CASE("LLUUID: the null UUID hashes nonzero and the one-bit values hash apart", "[LLUUID]") { - ensure_not_equals("the null UUID must not hash to zero", - hash_value(LLUUID::null), (size_t)0); + CHECK(hash_value(LLUUID::null) != (size_t)0); std::set seen; for (int bit = 0; bit < UUID_BYTES * 8; ++bit) { seen.insert(hash_value(make_bit_id(bit))); } - ensure_equals("each of the 128 one-bit values hashes differently", - seen.size(), (size_t)(UUID_BYTES * 8)); + CHECK(seen.size() == (size_t)(UUID_BYTES * 8)); } // Pinned values for the hash. Nothing persists a hash of an LLUUID, so @@ -578,9 +528,8 @@ namespace tut // reversed half -- rather than to freeze the algorithm. Changing the hash // on purpose means updating these three lines, deliberately. // - // The values assume a little-endian host, as test<8> does. - template<> template<> - void lluuid_object_t::test<20>() + // The values assume a little-endian host, as the CRC regression does. + TEST_CASE("LLUUID: hash_value gives its pinned values on 64-bit hosts", "[LLUUID]") { if constexpr (sizeof(size_t) == 8) { @@ -589,19 +538,16 @@ namespace tut LLUUID ones; std::memset(ones.mData, 0xff, UUID_BYTES); - ensure_equals("pinned hash of the null UUID", - (U64)hash_value(LLUUID::null), 0xc64369fbab9cf4d7ULL); - ensure_equals("pinned hash of 01020304-0506-0708-090a-0b0c0d0e0f10", - (U64)hash_value(sample), 0x47c8de24eeda6e35ULL); - ensure_equals("pinned hash of the all-ones UUID", - (U64)hash_value(ones), 0x24af15be29dd63f0ULL); + CHECK((U64)hash_value(LLUUID::null) == 0xc64369fbab9cf4d7ULL); + UNSCOPED_INFO("the sample is 01020304-0506-0708-090a-0b0c0d0e0f10"); + CHECK((U64)hash_value(sample) == 0x47c8de24eeda6e35ULL); + CHECK((U64)hash_value(ones) == 0x24af15be29dd63f0ULL); } } // The containers the viewer actually keys by LLUUID, exercised with the // structured corpus rather than with random UUIDs. - template<> template<> - void lluuid_object_t::test<21>() + TEST_CASE("LLUUID: the viewer's LLUUID-keyed maps hold and find every structured key", "[LLUUID]") { std::vector corpus; build_structured_corpus(corpus); @@ -613,18 +559,18 @@ namespace tut flat.emplace(corpus[i], i); node.emplace(corpus[i], i); } - ensure_equals("flat map holds every distinct key", flat.size(), corpus.size()); - ensure_equals("node map holds every distinct key", node.size(), corpus.size()); + CHECK(flat.size() == corpus.size()); + CHECK(node.size() == corpus.size()); for (size_t i = 0; i < corpus.size(); ++i) { auto fit = flat.find(corpus[i]); - ensure("flat map finds every key", fit != flat.end()); - ensure_equals("flat map returns the right value", fit->second, i); + REQUIRE(fit != flat.end()); + REQUIRE(fit->second == i); auto nit = node.find(corpus[i]); - ensure("node map finds every key", nit != node.end()); - ensure_equals("node map returns the right value", nit->second, i); + REQUIRE(nit != node.end()); + REQUIRE(nit->second == i); } // A key built from the same bytes by a different route still matches. @@ -632,7 +578,6 @@ namespace tut probe.set(kSampleStr); LLUUID same; fill_sample(same); - ensure_equals("equal bytes hash equally regardless of how they arrived", - hash_value(probe), hash_value(same)); + CHECK(hash_value(probe) == hash_value(same)); } } diff --git a/indra/llcommon/tests/stringize_test.cpp b/indra/llcommon/tests/stringize_test.cpp index 05a6fcc602..b1e4948f4d 100644 --- a/indra/llcommon/tests/stringize_test.cpp +++ b/indra/llcommon/tests/stringize_test.cpp @@ -46,9 +46,9 @@ // other Linden headers #include "../llsd.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct stringize_data { @@ -81,46 +81,40 @@ namespace tut std::wstring def; LLSD llsd; }; - typedef test_group stringize_group; - typedef stringize_group::object stringize_object; - tut::stringize_group strzgrp("stringize_h"); - template<> template<> - void stringize_object::test<1>() + TEST_CASE_METHOD(stringize_data, "stringize_h: stringize renders each type and LLSD notation", "[stringize_h]") { - ensure_equals(stringize(c), "c"); - ensure_equals(stringize(s), "17"); - ensure_equals(stringize(i), "34"); - ensure_equals(stringize(l), "68"); - ensure_equals(stringize(f), "3.14159"); - ensure_equals(stringize(d), "3.14159"); - ensure_equals(stringize(abc), "abc def"); + CHECK(stringize(c) == "c"); + CHECK(stringize(s) == "17"); + CHECK(stringize(i) == "34"); + CHECK(stringize(l) == "68"); + CHECK(stringize(f) == "3.14159"); + CHECK(stringize(d) == "3.14159"); + CHECK(stringize(abc) == "abc def"); //ensure_equals(stringize(def), "def ghi"); //Will generate LL_WARNS() due to narrowing. // LLSDNotationFormatter emits Reals at full round-trip precision - ensure_equals(stringize(llsd), "{'abc':'abc def','d':r3.14159265358979,'i':i34}"); + CHECK(stringize(llsd) == "{'abc':'abc def','d':r3.14159265358979,'i':i34}"); } - template<> template<> - void stringize_object::test<2>() + TEST_CASE_METHOD(stringize_data, "stringize_h: STRINGIZE streams an expression with manipulators", "[stringize_h]") { - ensure_equals(STRINGIZE("c is " << c), "c is c"); - ensure_equals(STRINGIZE(std::setprecision(4) << d), "3.142"); + CHECK(STRINGIZE("c is " << c) == "c is c"); + CHECK(STRINGIZE(std::setprecision(4) << d) == "3.142"); } - template<> template<> - void stringize_object::test<3>() + TEST_CASE_METHOD(stringize_data, "stringize_h: wstringize renders each type on Windows", "[stringize_h]") { //Tests rely on validity of wstring_to_utf8str() #if LL_WINDOWS // Windows wstring is a 2byte UTF16 type - ensure_equals(ll_convert(wstringize(c)), ll_convert(std::wstring(L"c"))); - ensure_equals(ll_convert(wstringize(s)), ll_convert(std::wstring(L"17"))); - ensure_equals(ll_convert(wstringize(i)), ll_convert(std::wstring(L"34"))); - ensure_equals(ll_convert(wstringize(l)), ll_convert(std::wstring(L"68"))); - ensure_equals(ll_convert(wstringize(f)), ll_convert(std::wstring(L"3.14159"))); - ensure_equals(ll_convert(wstringize(d)), ll_convert(std::wstring(L"3.14159"))); - ensure_equals(ll_convert(wstringize(abc)), ll_convert(std::wstring(L"abc def"))); - ensure_equals(ll_convert(wstringize(abc)), ll_convert(wstringize(abc.c_str()))); - ensure_equals(ll_convert(wstringize(def)), ll_convert(std::wstring(L"def ghi"))); + CHECK(ll_convert(wstringize(c)) == ll_convert(std::wstring(L"c"))); + CHECK(ll_convert(wstringize(s)) == ll_convert(std::wstring(L"17"))); + CHECK(ll_convert(wstringize(i)) == ll_convert(std::wstring(L"34"))); + CHECK(ll_convert(wstringize(l)) == ll_convert(std::wstring(L"68"))); + CHECK(ll_convert(wstringize(f)) == ll_convert(std::wstring(L"3.14159"))); + CHECK(ll_convert(wstringize(d)) == ll_convert(std::wstring(L"3.14159"))); + CHECK(ll_convert(wstringize(abc)) == ll_convert(std::wstring(L"abc def"))); + CHECK(ll_convert(wstringize(abc)) == ll_convert(wstringize(abc.c_str()))); + CHECK(ll_convert(wstringize(def)) == ll_convert(std::wstring(L"def ghi"))); #else // ensure_equals(ll_convert(wstringize(c)), wstring_to_utf8str(U"c")); // ensure_equals(ll_convert(wstringize(s)), wstring_to_utf8str(U"17")); @@ -134,4 +128,4 @@ namespace tut #endif // ensure_equals(wstring_to_utf8str(wstringize(llsd)), wstring_to_utf8str(L"{'abc':'abc def','d':r3.14159,'i':i34}")); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/threadsafeschedule_test.cpp b/indra/llcommon/tests/threadsafeschedule_test.cpp index 5e5d6e1259..6afc637d6d 100644 --- a/indra/llcommon/tests/threadsafeschedule_test.cpp +++ b/indra/llcommon/tests/threadsafeschedule_test.cpp @@ -18,7 +18,7 @@ #include // external library headers // other Linden headers -#include "../test/lltut.h" +#include using namespace std::literals::chrono_literals; // ms suffix using namespace std::literals::string_literals; // s suffix @@ -27,20 +27,15 @@ using Queue = LL::ThreadSafeSchedule; /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct threadsafeschedule_data { Queue queue; }; - typedef test_group threadsafeschedule_group; - typedef threadsafeschedule_group::object object; - threadsafeschedule_group threadsafeschedulegrp("threadsafeschedule"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(threadsafeschedule_data, "threadsafeschedule: entries pop in timestamp order and a closed queue drains", "[threadsafeschedule]") { - set_test_name("push"); // Simply calling push() a few times might result in indeterminate // delivery order if the resolution of steady_clock is coarser than // the real time required for each push() call. Explicitly increment @@ -54,17 +49,19 @@ namespace tut queue.push(now + 100ms, "def"s); queue.close(); auto entry = queue.pop(); - ensure_equals("failed to pop first", std::get<0>(entry), "abc"s); + CHECK(std::get<0>(entry) == "abc"s); entry = queue.pop(); - ensure_equals("failed to pop second", std::get<0>(entry), "def"s); - ensure("queue not closed", queue.isClosed()); - ensure("queue prematurely done", ! queue.done()); + CHECK(std::get<0>(entry) == "def"s); + CHECK(queue.isClosed()); + CHECK_FALSE(queue.done()); std::string s; bool popped = queue.tryPopFor(1s, s); - ensure("failed to pop third", popped); - ensure_equals("third is wrong", s, "ghi"s); + UNSCOPED_INFO("failed to pop third"); + CHECK(popped); + CHECK(s == "ghi"s); popped = queue.tryPop(s); - ensure("queue not empty", ! popped); - ensure("queue not done", queue.done()); + UNSCOPED_INFO("queue not empty"); + CHECK_FALSE(popped); + CHECK(queue.done()); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/tuple_test.cpp b/indra/llcommon/tests/tuple_test.cpp index aff129753f..b29251f9ad 100644 --- a/indra/llcommon/tests/tuple_test.cpp +++ b/indra/llcommon/tests/tuple_test.cpp @@ -17,31 +17,22 @@ // std headers // external library headers // other Linden headers -#include "../test/lltut.h" +#include /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { - struct tuple_data + TEST_CASE("tuple: tuple_split undoes tuple_cons", "[tuple]") { - }; - typedef test_group tuple_group; - typedef tuple_group::object object; - tuple_group tuplegrp("tuple"); - - template<> template<> - void object::test<1>() - { - set_test_name("tuple"); std::tuple tup{ "abc", 17 }; std::tuple ptup{ tuple_cons(34, tup) }; std::tuple tup2; int i; std::tie(i, tup2) = tuple_split(ptup); - ensure_equals("tuple_car() fail", i, 34); - ensure_equals("tuple_cdr() (0) fail", std::get<0>(tup2), "abc"); - ensure_equals("tuple_cdr() (1) fail", std::get<1>(tup2), 17); + CHECK(i == 34); + CHECK(std::get<0>(tup2) == "abc"); + CHECK(std::get<1>(tup2) == 17); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp index 209b6b868c..5a78de08b6 100644 --- a/indra/llcommon/tests/workqueue_test.cpp +++ b/indra/llcommon/tests/workqueue_test.cpp @@ -19,7 +19,8 @@ #include // external library headers // other Linden headers -#include "../test/lltut.h" +#include +#include "altestfailure.h" #include "../test/catch_and_store_what_in.h" #include "llcond.h" #include "llcoros.h" @@ -34,44 +35,36 @@ using namespace std::literals::string_literals; // s suffix /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct workqueue_data { WorkSchedule queue{"queue"}; }; - typedef test_group workqueue_group; - typedef workqueue_group::object object; - workqueue_group workqueuegrp("workqueue"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(workqueue_data, "workqueue: a schedule is findable by name and an unnamed one gets a default", "[workqueue]") { - set_test_name("name"); - ensure_equals("didn't capture name", queue.getKey(), "queue"); - ensure("not findable", WorkSchedule::getInstance("queue") == queue.getWeak().lock()); + CHECK(queue.getKey() == "queue"); + CHECK(WorkSchedule::getInstance("queue") == queue.getWeak().lock()); WorkSchedule q2; - ensure("has no name", LLStringUtil::startsWith(q2.getKey(), "WorkQueue")); + CHECK(LLStringUtil::startsWith(q2.getKey(), "WorkQueue")); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(workqueue_data, "workqueue: posted work runs only when the queue is serviced", "[workqueue]") { - set_test_name("post"); bool wasRun{ false }; // We only get away with binding a simple bool because we're running // the work on the same thread. queue.post([&wasRun](){ wasRun = true; }); queue.close(); - ensure("ran too soon", ! wasRun); + UNSCOPED_INFO("ran too soon"); + CHECK_FALSE(wasRun); queue.runUntilClose(); - ensure("didn't run", wasRun); + CHECK(wasRun); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(workqueue_data, "workqueue: postEvery repeats at its interval until the work returns false", "[workqueue]") { - set_test_name("postEvery"); // record of runs using Shared = std::deque; // This is an example of how to share data between the originator of @@ -114,7 +107,7 @@ namespace tut } // Take a copy of the captured deque. Shared result = data.get(); - ensure_equals("called wrong number of times", result.size(), 3); + REQUIRE(result.size() == 3); // postEvery() assumes you want the first call to happen right away. // Pretend our start time was (interval) earlier than that, to make // our too early/too late tests uniform for all entries. @@ -125,10 +118,11 @@ namespace tut start += interval; try { - ensure(STRINGIZE("call " << i << " too soon"), diff >= interval); - ensure(STRINGIZE("call " << i << " too late"), diff < interval*1.5); + CAPTURE(i); + REQUIRE(diff >= interval); + REQUIRE(diff < interval*1.5); } - catch (const tut::failure&) + catch (const ll_test::Failure&) { auto interval_ms = interval / 1ms; auto diff_ms = diff / 1ms; @@ -139,10 +133,8 @@ namespace tut } } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(workqueue_data, "workqueue: postTo hands int and string results to the caller's callback", "[workqueue]") { - set_test_name("postTo"); WorkSchedule main("main"); auto qptr = WorkSchedule::getInstance("queue"); int result = 0; @@ -158,7 +150,7 @@ namespace tut qptr->runOne(); // this should run the callback main.runOne(); - ensure_equals("failed to run int callback", result, 17); + CHECK(result == 17); std::string alpha; // postTo() handles arbitrary return types @@ -168,13 +160,11 @@ namespace tut [&alpha](const std::string& s){ alpha = s; }); qptr->runPending(); main.runPending(); - ensure_equals("failed to run string callback", alpha, "abc"); + CHECK(alpha == "abc"); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(workqueue_data, "workqueue: postTo with void work still runs the callback", "[workqueue]") { - set_test_name("postTo with void return"); WorkSchedule main("main"); auto qptr = WorkSchedule::getInstance("queue"); std::string observe; @@ -188,23 +178,23 @@ namespace tut [&observe](){ observe.append(";main"); }); qptr->runOne(); main.runOne(); - ensure_equals("failed to run both lambdas", observe, "queue;main"); + CHECK(observe == "queue;main"); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(workqueue_data, "workqueue: waitForResult throws on the main coroutine but works on others", "[workqueue]") { - set_test_name("waitForResult"); std::string stored; // Try to call waitForResult() on this thread's main coroutine. It // should throw because the main coroutine must service the queue. auto what{ catch_what( [this, &stored](){ stored = queue.waitForResult( [](){ return "should throw"; }); }) }; - ensure("lambda should not have run", stored.empty()); - ensure_not("waitForResult() should have thrown", what.empty()); - ensure(STRINGIZE("should mention waitForResult: " << what), - what.find("waitForResult") != std::string::npos); + UNSCOPED_INFO("lambda should not have run"); + CHECK(stored.empty()); + UNSCOPED_INFO("waitForResult() should have thrown"); + CHECK_FALSE(what.empty()); + UNSCOPED_INFO("exception: " << what); + CHECK(what.find("waitForResult") != std::string::npos); // Call waitForResult() on a coroutine, with a string result. LLCoros::instance().launch( @@ -218,7 +208,7 @@ namespace tut // 'queue'. Service it. queue.runOne(); llcoro::suspend(); - ensure_equals("bad waitForResult return", stored, "string result"); + CHECK(stored == "string result"); // Call waitForResult() on a coroutine, with a void callable. stored.clear(); @@ -233,7 +223,8 @@ namespace tut llcoro::suspend(); queue.runOne(); llcoro::suspend(); - ensure_equals("didn't run coroutine", stored, "ran"); - ensure("void waitForResult() didn't return", done); + CHECK(stored == "ran"); + UNSCOPED_INFO("void waitForResult() didn't return"); + CHECK(done); } -} // namespace tut +} // anonymous namespace diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 9cd2c69b11..52acc065f1 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -33,7 +33,7 @@ #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally #endif -#include +#include #include "llerrorcontrol.h" #include "llexception.h" #include "stringize.h" @@ -153,9 +153,7 @@ class CaptureLogRecorder : public LLError::Recorder, public boost::noncopyable if (! required) return std::string(); - throw tut::failure(STRINGIZE("failed to find '" << search - << "' in captured log messages:\n" - << *this)); + FAIL("failed to find '" << search << "' in captured log messages:\n" << *this); } std::ostream& streamto(std::ostream& out) const diff --git a/indra/llmath/CMakeLists.txt b/indra/llmath/CMakeLists.txt index 6b51c8dcd9..493440ab90 100644 --- a/indra/llmath/CMakeLists.txt +++ b/indra/llmath/CMakeLists.txt @@ -136,17 +136,18 @@ if(AL_BUILD_TESTS) LIBRARIES llmath llcommon ) - # The batch kernels again at each other x86-64 tier, one binary a tier: code - # the linker folds across files would otherwise run one tier's instructions - # in another's test. The tree's own tier is llmath_tests'; a variant the - # host cannot run skips. + # llcommon's SIMD ops layer and the batch kernels again at each other + # x86-64 tier, one binary a tier: code the linker folds across files would + # otherwise run one tier's instructions in another's test. The tree's own + # tier is llcommon_tests' and llmath_tests'; a variant the host cannot run + # skips. if(BUILD_TARGET_IS_X86_64 AND NOT DARWIN) foreach(tier baseline v2 v3 v4) if(NOT tier STREQUAL AL_ISA_TIER) al_add_test_binary( simd_tests PROJECT llmath - SOURCES tests/alsimdkernels_test.cpp + SOURCES tests/alsimdkernels_test.cpp ${INDRA_SOURCE_DIR}/llcommon/tests/alsimd_test.cpp LIBRARIES llmath llcommon ISA_TIER ${tier} ) diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index ae25f9a371..97b375a221 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -36,6 +36,7 @@ target_sources( al_test_support PRIVATE altestfatal.cpp + altesthelper.cpp altestlogreplay.cpp altestmain.cpp altestmatchers.cpp @@ -51,6 +52,7 @@ target_sources( FILES altestfailure.h altestfatal.h + altesthelper.h altestlogreplay.h altestmatchers.h altestname.h @@ -169,6 +171,21 @@ al_expect_from_test_support( OUTPUT "left the environment changed.*AL_TEST_SUPPORT_SKIP_LEAK=1" ) al_expect_from_test_support(nothing_matched_fails ARGS "no such test case" EXIT 2) +# A test can launch its own binary as a helper process: the main sets the +# process up and runs that helper alone, and no test case. +al_expect_from_test_support( + helper_runs_alone + ARGS "--al-helper=altestsupport_exit" + EXIT 37 + OUTPUT "the helper ran, with APR up" + REJECT "test cases" +) +al_expect_from_test_support( + helper_unknown + ARGS "--al-helper=nonesuch" + EXIT 2 + OUTPUT "No test helper is named nonesuch" +) al_expect_from_test_support( isa_gate_skips ARGS "--isa-level|9" diff --git a/indra/test/altesthelper.cpp b/indra/test/altesthelper.cpp new file mode 100644 index 0000000000..da8bb36ab2 --- /dev/null +++ b/indra/test/altesthelper.cpp @@ -0,0 +1,84 @@ +/** + * @file altesthelper.cpp + * @brief A process a test launches from its own binary, run by name and + * without the binary's test cases. + * + * $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 "altesthelper.h" + +#include +#include +#include +#include + +namespace ll_test +{ + namespace + { + constexpr char PREFIX[] = "--al-helper="; + + // Made on first use, so a helper registered from any static + // initializer finds it ready. + std::map& helpers() + { + static std::map sHelpers; + return sHelpers; + } + } + + Helper::Helper(const char* name, Main main) + { + if (!helpers().emplace(name, main).second) + { + std::cerr << "Two test helpers are named " << name << std::endl; + std::abort(); + } + } + + std::string Helper::argument(const std::string& name) + { + return PREFIX + name; + } + + const char* Helper::named(int argc, char* argv[]) + { + for (int i = 1; i < argc; ++i) + { + if (std::strncmp(argv[i], PREFIX, sizeof(PREFIX) - 1) == 0) + { + return argv[i] + sizeof(PREFIX) - 1; + } + } + return nullptr; + } + + int Helper::run(const std::string& name) + { + auto found = helpers().find(name); + if (found == helpers().end()) + { + std::cerr << "No test helper is named " << name << std::endl; + return 2; + } + return found->second(); + } +} diff --git a/indra/test/altesthelper.h b/indra/test/altesthelper.h new file mode 100644 index 0000000000..29f32f5e18 --- /dev/null +++ b/indra/test/altesthelper.h @@ -0,0 +1,73 @@ +/** + * @file altesthelper.h + * @brief A process a test launches from its own binary, run by name and + * without the binary's test cases. + * + * $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$ + */ + +#ifndef AL_ALTESTHELPER_H +#define AL_ALTESTHELPER_H + +#include + +namespace ll_test +{ + // A test that needs a child process of its own making -- one that owns + // other processes, say, and then exits -- can launch its own binary with + // Helper::argument(name). The main then runs only the helper of that + // name, as LL code expects to find a process set up, and exits with what + // it returns: no test case runs, so a binary whose files share a process + // does not run its whole suite again in the child. The argument is the + // main's alone; Catch2 never sees it. + // + // A helper is registered as a test case is, by AL_TEST_HELPER(name) in + // the test file that launches it. Names are unique within a binary. + class Helper + { + public: + using Main = int (*)(); + + // AL_TEST_HELPER's: a second helper of one name aborts the process, + // as it starts. + Helper(const char* name, Main main); + + // What to pass the binary to run the helper of that name. + static std::string argument(const std::string& name); + + // The main's: the helper argv names, or null when it names none. + static const char* named(int argc, char* argv[]); + + // The main's: runs the helper of that name and returns its exit code, + // or reports that there is none and returns 2. + static int run(const std::string& name); + }; +} + +// Defines the helper `name`, a function returning the helper process's exit +// code, and registers it: +// AL_TEST_HELPER(llprocess_autokill) { ...; return 0; } +#define AL_TEST_HELPER(name) \ + static int al_test_helper_##name(); \ + static const ll_test::Helper al_test_helper_registration_##name(#name, \ + &al_test_helper_##name); \ + static int al_test_helper_##name() + +#endif // AL_ALTESTHELPER_H diff --git a/indra/test/altestmain.cpp b/indra/test/altestmain.cpp index a6358e06d3..7869441d52 100644 --- a/indra/test/altestmain.cpp +++ b/indra/test/altestmain.cpp @@ -27,6 +27,7 @@ #include "linden_common.h" #include "altestfatal.h" +#include "altesthelper.h" #include "altestlogreplay.h" #include "alteststateguard.h" #include "altestteardown.h" @@ -92,6 +93,21 @@ namespace LLError::logToFile(llformat("%s.%d.log", executable, LLApp::getPid())); } } + + // What a test run and a helper both find: APR, LLError as LL code + // expects it, LL_ERRS throwing, and LLTrace's master recorder. + void setUpProcess(const char* executable, bool log_to_file) + { + ll_init_apr(); + setUpLogging(executable, log_to_file); + ll_test::installFatalFunction(); + ll_test::installTerminateHandler(); + + // LLTrace needs a master recorder before any thread records. Never + // freed, as the TUT runner never freed it: static destructors that + // record would find it gone. + LLTrace::set_master_thread_recorder(new LLTrace::ThreadRecorder()); + } } int main(int argc, char* argv[]) @@ -101,6 +117,17 @@ int main(int argc, char* argv[]) LL_PROFILER_FRAME_END; LL_PROFILER_SET_THREAD_NAME("App"); + // A helper a test launched from this binary runs alone, and Catch2 never + // sees its argument. + if (const char* helper = ll_test::Helper::named(argc, argv)) + { + setUpProcess(argv[0], false); + const int result = ll_test::Helper::run(helper); + ll_test::Teardown::runAll(); + ll_cleanup_apr(); + return result; + } + Catch::Session session; // Long names only: Catch2 owns every short one. @@ -139,16 +166,7 @@ int main(int argc, char* argv[]) return 125; } - ll_init_apr(); - setUpLogging(argv[0], log_to_file); - ll_test::installFatalFunction(); - ll_test::installTerminateHandler(); - - // LLTrace needs a master recorder before any thread records. Never freed, - // as the TUT runner never freed it: static destructors that record would - // find it gone. - LLTrace::set_master_thread_recorder(new LLTrace::ThreadRecorder()); - + setUpProcess(argv[0], log_to_file); ll_test::StateGuard::addCommonChecks(); int result = session.run(); diff --git a/indra/test/tests/altestsupport_test.cpp b/indra/test/tests/altestsupport_test.cpp index 4626568892..b9177be416 100644 --- a/indra/test/tests/altestsupport_test.cpp +++ b/indra/test/tests/altestsupport_test.cpp @@ -28,11 +28,13 @@ #include "linden_common.h" #include "altestfatal.h" +#include "altesthelper.h" #include "altestmatchers.h" #include "altestname.h" #include "alteststateguard.h" #include "altestteardown.h" +#include "llapr.h" #include "llerror.h" #include "llsd.h" @@ -43,6 +45,7 @@ #include #include +#include #include #include #include @@ -84,6 +87,14 @@ namespace CATCH_REGISTER_LISTENER(LibraryChecks) +// The helper the helper_* entries run, which says whether it found the +// process set up as a test would, and exits with a code no test run gives. +AL_TEST_HELPER(altestsupport_exit) +{ + std::cout << "the helper ran, " << (gAPRPoolp ? "with" : "without") << " APR up" << std::endl; + return 37; +} + TEST_CASE("WithinBits holds to the bits asked for, and no further", "[altestsupport]") { // 2^-20 apart: equal to 16 bits of the fraction, not to 24. (Not "near": diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index c2139ecfd0..9342074861 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -70,7 +70,10 @@ have stopped early, so neither is held to it; nor is one the build leaves out, which the file's compile command in the build's compile_commands.json, run as clang++ -E, says. Where there is no such - command, one under an #if that never ran is listed. + 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; 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 @@ -276,6 +279,31 @@ def record(args): # -------------------------------------------------------------------------- # check # -------------------------------------------------------------------------- +class LegalXml: + """A Catch2 XML report as a file to parse. Catch2 writes a value it + prints as XML, \\xNN for a control character or a byte that is not + UTF-8, but lets through vertical tab, form feed and a UTF-8 surrogate, + which XML forbids too: a check on bytes that hold one, as commonmisc's + binary LLSD does, would leave its report unreadable. Those are read as + Catch2 writes the rest.""" + + FORBIDDEN = re.compile(rb"[\x0b\x0c]|\xed[\xa0-\xbf][\x80-\xbf]") + + def __init__(self, raw): + self.raw = raw + + def read(self, size=-1): + data = self.raw.read(size) + # A surrogate's three bytes may straddle two reads; 0xED leads one + # and is never a later byte of any. + while b"\xed" in data[-2:]: + more = self.raw.read(2) + if not more: + break + data += more + return self.FORBIDDEN.sub(lambda m: "".join(f"\\x{b:02X}" for b in m.group()).encode(), data) + + def catch2_results(test, timeout): """The cases a Catch2 entry ran, as XML with its passing assertions too (-s), in the order it ran them: each {name, tags, line, result: ok | skip @@ -289,28 +317,29 @@ def catch2_results(test, timeout): cases = [] case = None try: - for event, element in ElementTree.iterparse(report, events=("start", "end")): - if event == "start": - if element.tag == "TestCase": - case = {"name": element.get("name"), "tags": element.get("tags", ""), - "line": int(element.get("line", "0")), "result": "fail", "ran": set()} - continue - if case is None: - continue - if element.tag in RAN_ELEMENTS: - case["ran"].add((element.get("filename", ""), int(element.get("line", "0")))) - element.clear() - elif element.tag == "OverallResult": - if element.get("success") != "true": - case["result"] = "fail" - elif int(element.get("skips", "0")) > 0: - case["result"] = "skip" - else: - case["result"] = "ok" - elif element.tag == "TestCase": - cases.append(case) - case = None - element.clear() + with open(report, "rb") as raw: + for event, element in ElementTree.iterparse(LegalXml(raw), events=("start", "end")): + if event == "start": + if element.tag == "TestCase": + case = {"name": element.get("name"), "tags": element.get("tags", ""), + "line": int(element.get("line", "0")), "result": "fail", "ran": set()} + continue + if case is None: + continue + if element.tag in RAN_ELEMENTS: + case["ran"].add((element.get("filename", ""), int(element.get("line", "0")))) + element.clear() + elif element.tag == "OverallResult": + if element.get("success") != "true": + case["result"] = "fail" + elif int(element.get("skips", "0")) > 0: + case["result"] = "skip" + else: + case["result"] = "ok" + elif element.tag == "TestCase": + cases.append(case) + case = None + element.clear() except (ElementTree.ParseError, FileNotFoundError): return code, None, text return code, cases, text @@ -623,7 +652,11 @@ def compiled(first, last, conditional): continue here = compiled(first, last, bool(blocks - body["held"])) if here: - problems.append(f"{label}: {case['name']!r} passed without running the {macro} on line {first}") + why = 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: + problems.append(f"{label}: {case['name']!r} passed without running the {macro} on line {first}") elif here is None: notes.append(f"{case['name']!r}: the {macro} on line {first} never ran, and is under an #if no " f"compile command here says this build compiles") @@ -716,6 +749,10 @@ def compiled(first, last, conditional): def check(args): mapping, file_records = read_mapping(args.mapping) + args.expect_unrun = {} + if args.unrun: + with open(args.unrun) as f: + 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 @@ -837,6 +874,8 @@ def main(): chk.add_argument("--recounted", action="store_true", 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") 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)