diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 1cad7b256d..15835e025c 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -127,6 +127,12 @@ LLApp::~LLApp() setStopped(); SUBSYSTEM_CLEANUP_DBG(LLCommon); + + // Gone, so instance() no longer finds it. + if (sApplication == this) + { + sApplication = NULL; + } } // static diff --git a/indra/llcommon/tests/alrecoverystore_test.cpp b/indra/llcommon/tests/alrecoverystore_test.cpp index b120767e53..01af3c2f9e 100644 --- a/indra/llcommon/tests/alrecoverystore_test.cpp +++ b/indra/llcommon/tests/alrecoverystore_test.cpp @@ -422,6 +422,7 @@ namespace ALRecoveryStore other(folder, "theirs"); CHECK(other.write(entry("item:x", "left before"))); const std::optional took = store.leftFor("item:x"); + CHECK(took.has_value()); CHECK(store.write(entry("item:x", "typed since"))); // A file where the folder of the discarded would be: nothing can be // written there. @@ -628,8 +629,10 @@ namespace CHECK_FALSE(LLFile::isfile(bare)); CHECK(store.setAside(entry("item:b", "old"))); + const std::vector again = files("/discarded"); + REQUIRE(again.size() == size_t(1)); const std::string garbled = folder + "/discarded/0001.mine.soon.llsd"; - CHECK(LLFile::rename(folder + "/discarded/" + files("/discarded").front(), garbled) == 0); + CHECK(LLFile::rename(folder + "/discarded/" + again.front(), garbled) == 0); store.prune(60.0, LLDate(LLDate::now().secondsSinceEpoch() + 30.0)); UNSCOPED_INFO("a stamp that is no number read past"); CHECK(LLFile::isfile(garbled)); @@ -732,8 +735,10 @@ namespace { 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; + CHECK(store.write(went)); + const std::vector own = store.list(); + REQUIRE(own.size() == size_t(1)); + const LLDate written = own.front().when; UNSCOPED_INFO("not left by another session"); REQUIRE_FALSE(store.leftFor("task:a:b")); @@ -763,7 +768,7 @@ namespace REQUIRE_FALSE(store.reclaim("task:a:b")); ALRecoveryStore other(folder, "earlier"); - other.write(entry("task:c:d", "another session's")); + CHECK(other.write(entry("task:c:d", "another session's"))); { INFO("another session's is not this one's to reclaim"); REQUIRE_FALSE(store.reclaim("task:c:d")); @@ -862,8 +867,10 @@ namespace } CHECK(require_whole(store, store.leftFor("item:a"))->text == "left"); store.markOffered(store.left(), LLDate(offered.secondsSinceEpoch() + 100.0)); + const std::vector remarked = files(); + REQUIRE(remarked.size() == 1); UNSCOPED_INFO("marked once"); - CHECK(files().front() == names.front()); + CHECK(remarked.front() == names.front()); store.prune(60.0, LLDate(offered.secondsSinceEpoch() + 30.0)); UNSCOPED_INFO("young, it stays"); REQUIRE(files().size() == 1); @@ -891,14 +898,15 @@ namespace ms_sleep(5); } std::vector kept = require_whole(store, store.list()); - REQUIRE(files("/discarded").size() == size_t(3)); + const std::vector discarded = files("/discarded"); + REQUIRE(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); + const uintmax_t one = std::filesystem::file_size(fsyspath(folder + "/discarded/" + discarded.front()), ec); store.limitDiscarded(100, static_cast(one * 2 + one / 2)); store.prune(60.0); UNSCOPED_INFO("two, by what they weigh"); diff --git a/indra/llcommon/tests/alregex_test.cpp b/indra/llcommon/tests/alregex_test.cpp index cc60691995..4778f3111a 100644 --- a/indra/llcommon/tests/alregex_test.cpp +++ b/indra/llcommon/tests/alregex_test.cpp @@ -264,9 +264,19 @@ namespace 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. + // After a word, \b is before what is not one: here any byte but a + // word's, 0 and 0xFF among them. With the start's, a word's byte that + // is no small letter, that is every byte, so no range. UNSCOPED_INFO("a boundary before a non-letter"); - CHECK((ALRegex("\\b[^a-z]x").firstByteRange(lo, hi) == false || (lo <= '!' && hi >= '_'))); + CHECK_FALSE(ALRegex("\\b[^a-z]x").firstByteRange(lo, hi)); + // One that can begin only after a word: \b at the start wants a + // word's byte, and no punctuation is one. The range is what may + // follow a word, rounded up one past '/' as RE2 rounds. + UNSCOPED_INFO("a boundary before punctuation"); + CHECK(ALRegex("\\b[!-/]x").firstByteRange(lo, hi)); + CHECK(lo == '!'); + CHECK(hi >= '/'); + CHECK(hi <= '0'); CHECK(ALRegex("@[A-Za-z_][A-Za-z0-9_]*").firstByteRange(lo, hi)); CHECK(lo == '@'); CHECK(hi <= 'A'); diff --git a/indra/llcommon/tests/commonmisc_test.cpp b/indra/llcommon/tests/commonmisc_test.cpp index e6102076f8..1752fc4aff 100644 --- a/indra/llcommon/tests/commonmisc_test.cpp +++ b/indra/llcommon/tests/commonmisc_test.cpp @@ -1,8 +1,8 @@ /** - * @file common.cpp + * @file commonmisc_test.cpp * @author Phoenix * @date 2005-10-12 - * @brief Common templates for test framework + * @brief Tests of llhash, LLMemoryStream, LLSD notation and the U64 conversions * * $LicenseInfo:firstyear=2005&license=viewerlgpl$ * Second Life Viewer Source Code @@ -26,12 +26,6 @@ * $/LicenseInfo$ */ -/** - * - * THOROUGH_DESCRIPTION of common.cpp - * - */ - #include #include #include @@ -46,32 +40,6 @@ #include -namespace -{ -/* - 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 -{ -} - -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]") @@ -128,7 +96,6 @@ namespace 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"); const std::string streamed("b(6)\"random\""); typedef std::vector buf_t; buf_t buf; @@ -293,11 +260,8 @@ namespace 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'; - CHECK(memcmp(&bin[0], &expected[0], 1) == 0); + const std::vector expected{'A'}; + CHECK(sd["singlebinary"].asBinary() == expected); CHECK(sd["singlerawstring"].asString() == std::string("A")); CHECK(sd["endoftest"].asString() == "end"); @@ -359,47 +323,15 @@ namespace std::stringstream str; str << "'" << LLSDNotationFormatter::escapeString(expected) << "'"; LLSD sd; - S32 count = LLSDSerialize::fromNotation(sd, str, expected.size()); + S32 count = LLSDSerialize::fromNotation(sd, str, str.str().size()); REQUIRE(count == 1); std::string actual = sd.asString(); -/* - if(actual != expected) - { - LL_WARNS() << "iteration " << i << LL_ENDL; - std::ostringstream e_str; - std::string::iterator iter = expected.begin(); - std::string::iterator end = expected.end(); - for(; iter != end; ++iter) - { - e_str << (S32)((U8)(*iter)) << " "; - } - e_str << std::endl; - llsd_serialize_string(e_str, expected); - LL_WARNS() << "expected size: " << expected.size() << LL_ENDL; - LL_WARNS() << "expected: " << e_str.str() << LL_ENDL; - - std::ostringstream a_str; - iter = actual.begin(); - end = actual.end(); - for(; iter != end; ++iter) - { - a_str << (S32)((U8)(*iter)) << " "; - } - a_str << std::endl; - llsd_serialize_string(a_str, actual); - LL_WARNS() << "actual size: " << actual.size() << LL_ENDL; - LL_WARNS() << "actual: " << a_str.str() << LL_ENDL; - } -*/ REQUIRE(actual == expected); } } 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."); -//#endif std::string param = "[{'version':i1},{'data':{'binary_bucket':b(0)\"\"},'from_id':u3c115e51-04f4-523c-9fa6-98aff1034730,'from_name':'Phoenix Linden','id':u004e45e5-5576-277a-fba7-859d6a4cb5c8,'message':'hey','offline':i0,'timestamp':i0,'to_id':u3c5f1bb4-5182-7546-6401-1d329b4ff2f8,'type':i0},{'agent_id':u3c115e51-04f4-523c-9fa6-98aff1034730,'god_level':i0,'limited_to_estate':i1}]"; std::istringstream istr; istr.str(param); diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp index 6e172e10dd..5c4d3bc274 100644 --- a/indra/llcommon/tests/lazyeventapi_test.cpp +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -15,6 +15,7 @@ #include "lazyeventapi.h" // STL headers // std headers +#include // external library headers // other Linden headers #include @@ -95,11 +96,12 @@ namespace CHECK(data.asString() == "hey"); } - TEST_CASE_METHOD(lazyeventapi_data, "lazyeventapi: without a LazyEventAPI no listener answers its pump", "[lazyeventapi]") + TEST_CASE_METHOD(lazyeventapi_data, "lazyeventapi: a destroyed LazyEventAPI no longer creates its listener", "[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. + // A registrar destroyed before anyone obtains its pump takes its pump + // factory with it, so this post must NOT reach a MyListener instance. + auto regster{ std::make_unique() }; + regster.reset(); LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot")); CHECK_FALSE(data.isDefined()); } diff --git a/indra/llcommon/tests/llapp_test.cpp b/indra/llcommon/tests/llapp_test.cpp index 964d6c6b3c..619418992b 100644 --- a/indra/llcommon/tests/llapp_test.cpp +++ b/indra/llcommon/tests/llapp_test.cpp @@ -84,4 +84,12 @@ namespace mApp->setOptionData(LLApp::PRIORITY_RUNTIME_OVERRIDE, options); CHECK_FALSE(mApp->getOption("boolean-test").asBoolean()); } + + TEST_CASE_METHOD(application, "application: instance() finds no app once it is gone", "[application]") + { + CHECK(LLApp::instance() == mApp); + delete mApp; + mApp = nullptr; + CHECK(LLApp::instance() == nullptr); + } } diff --git a/indra/llcommon/tests/lldate_test.cpp b/indra/llcommon/tests/lldate_test.cpp index bee9279b72..4419d4bd2d 100644 --- a/indra/llcommon/tests/lldate_test.cpp +++ b/indra/llcommon/tests/lldate_test.cpp @@ -51,17 +51,21 @@ #define INVALID_DATE_EMPTY "" // invalid values -// apr 1.1.1 seems to not care about constraining the date to valid -// dates. Put these back when the parser checks. +// LLDate::fromStream checks only the format: it hands the fields to timegm, +// which normalizes a value out of range rather than rejecting it (24:00:00 is +// the next day's midnight, 2003-02-29 is 2003-03-01). Turn the case below on +// when the parser checks the ranges. #define LL_DATE_PARSER_CHECKS_BOUNDARY 0 -//#define INVALID_DATE_24HOUR_BOUNDARY "2003-04-30T24:00:00Z" -//#define INVALID_DATE_LEAP "2003-04-29T04:00:00Z" -//#define INVALID_DATE_HOUR "2003-04-30T24:59:59Z" -//#define INVALID_DATE_MIN "2003-04-30T22:69:59Z" -//#define INVALID_DATE_SEC "2003-04-30T22:59:69Z" -//#define INVALID_DATE_YEAR "0-04-30T22:59:59Z" -//#define INVALID_DATE_MONTH "2003-13-30T22:59:59Z" -//#define INVALID_DATE_DAY "2003-04-35T22:59:59Z" +#if LL_DATE_PARSER_CHECKS_BOUNDARY +#define INVALID_DATE_24HOUR_BOUNDARY "2003-04-30T24:00:00Z" +#define INVALID_DATE_LEAP "2003-02-29T04:00:00Z" +#define INVALID_DATE_HOUR "2003-04-30T24:59:59Z" +#define INVALID_DATE_MIN "2003-04-30T22:69:59Z" +#define INVALID_DATE_SEC "2003-04-30T22:59:69Z" +#define INVALID_DATE_YEAR "0-04-30T22:59:59Z" +#define INVALID_DATE_MONTH "2003-13-30T22:59:59Z" +#define INVALID_DATE_DAY "2003-04-35T22:59:59Z" +#endif namespace tut { @@ -133,12 +137,11 @@ namespace tut CHECK_FALSE(result); } +#if LL_DATE_PARSER_CHECKS_BOUNDARY /* Invalid Value Handling */ TEST_CASE("LLDate: fromString rejects out-of-range field values", "[LLDate]") { -#if LL_DATE_PARSER_CHECKS_BOUNDARY LLDate date; - std::string expected_string; bool result; result = date.fromString(INVALID_DATE_24HOUR_BOUNDARY); @@ -174,8 +177,8 @@ namespace tut result = date.fromString(INVALID_DATE_DAY); UNSCOPED_INFO("INVALID_DATE_DAY should have failed"); CHECK_FALSE(result); -#endif } +#endif // LL_DATE_PARSER_CHECKS_BOUNDARY /* API checks */ TEST_CASE("LLDate: fromStream reads a date", "[LLDate]") @@ -266,9 +269,12 @@ namespace tut LLDate d; 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). + // RFC1123 wraps toHTTPDateString as "%A, %d %b %Y %H:%M:%S GMT". + // The day-of-week token, whatever the locale names it, comes before + // the first ", ": a strftime success indicator that doesn't pin locale. std::string rfc = d.asRFC1123(); - CHECK(rfc.size() > 0); + const size_t day_end = rfc.find(", "); + CHECK(day_end != std::string::npos); + CHECK(day_end > 0); } } diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp index 06cb890e99..88bf3aa42d 100644 --- a/indra/llcommon/tests/lldeadmantimer_test.cpp +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -334,7 +334,7 @@ TEST_CASE_METHOD(deadmantimer_test, "LLDeadmanTimer: isExpired reports an expiry count = U64L(8); user_cpu = 29000; sys_cpu = 57000; - CHECK_FALSE(timer.isExpired(now, started, stopped, count)); + 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)); diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 98c9974756..bf43e17060 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -384,8 +384,8 @@ namespace tut // first several params are required, last couple optional partial_offset(3) { - // This object is reconstructed for every test method. But - // clear global variables every time too. + // This object is reconstructed for every test case. But clear + // global variables every time too. ::clear(); const char* abs[] = { "a", "b" }; @@ -817,7 +817,7 @@ namespace tut metadata = getMetadata(name_req); REQUIRE(metadata["name"].asString() == name_req); REQUIRE(metadata["desc"].asString() == descs[name_req]); - REQUIRE_THAT(required, ll_test::LLSDEquals(metadata["required"])); + REQUIRE_THAT(metadata["required"], ll_test::LLSDEquals(required)); REQUIRE(metadata["optional"].isUndefined()); } } @@ -893,8 +893,8 @@ namespace tut // remove for comparing the rest. LLSD ameta(getMetadata(adft)); LLSD mmeta(getMetadata(mdft)); - REQUIRE_THAT(adft, ll_test::LLSDEquals(ameta["name"])); - REQUIRE_THAT(mdft, ll_test::LLSDEquals(mmeta["name"])); + REQUIRE_THAT(ameta["name"], ll_test::LLSDEquals(adft)); + REQUIRE_THAT(mmeta["name"], ll_test::LLSDEquals(mdft)); ameta.erase("name"); mmeta.erase("name"); REQUIRE_THAT(ameta, ll_test::LLSDEquals(mmeta)); @@ -1111,9 +1111,9 @@ namespace tut // Look up the Vars instance for this function. Vars* vars(varsfor(name)); // Both the global and stack Vars instances are automatically - // cleared at the start of each test method. But since we're + // cleared at the start of each test case. But since we're // calling these things several different times in the same - // test method, manually reset the Vars between each. + // test case, manually reset the Vars between each. *vars = Vars(); REQUIRE(vars->i == 0); // call function with empty array (or LLSD(), should be equivalent) diff --git a/indra/llcommon/tests/llfile_test.cpp b/indra/llcommon/tests/llfile_test.cpp index dcba540256..82de6ac3d2 100644 --- a/indra/llcommon/tests/llfile_test.cpp +++ b/indra/llcommon/tests/llfile_test.cpp @@ -100,8 +100,9 @@ namespace CHECK_FALSE(LLFile::exists(testfile1)); const char* testdata = "testdata"; - S64 bytes = LLFile::write(testfile1, testdata, 0, sizeof(testdata)); - CHECK(bytes == sizeof(testdata)); + S64 bytes = LLFile::write(testfile1, testdata, 0, strlen(testdata)); + CHECK(bytes == (S64)strlen(testdata)); + CHECK(LLFile::isfile(testfile1)); rc = LLFile::remove(testfile1); UNSCOPED_INFO("remove of the file"); @@ -128,7 +129,6 @@ namespace std::string testdata1("testdata"); std::string testdata2("datateststuff"); - std::time_t current = time(nullptr); S64 bytes = LLFile::write(testfile1, testdata1.c_str(), 0, testdata1.length()); UNSCOPED_INFO("write"); CHECK(bytes == testdata1.length()); diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index d3207614b4..e479181d8f 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -138,14 +138,7 @@ namespace auto snap = Keyed::key_snapshot(); StringVector keys(snap.begin(), snap.end()); std::sort(keys.begin(), keys.end()); - StringVector::const_iterator ki(keys.begin()); - 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. - UNSCOPED_INFO("didn't reach end"); - CHECK(ki == keys.end()); + CHECK(keys == StringVector{ "one", "three", "two" }); // Use a somewhat different approach to order independence with // instance_snapshot(): explicitly capture the instances we know in a @@ -193,8 +186,6 @@ namespace // delete the one instance delete keyed; // traversing the snapshot should reflect the deletion - // avoid ensure_equals() because it requires the ability to stream the - // two values to std::ostream CHECK(snapshot.begin() == snapshot.end()); } @@ -207,22 +198,17 @@ namespace // delete the one instance delete keyed; // traversing the snapshot should reflect the deletion - // avoid ensure_equals() because it requires the ability to stream the - // two values to std::ostream CHECK(snapshot.begin() == snapshot.end()); } TEST_CASE("llinstancetracker: delete Unkeyed with outstanding instance_snapshot", "[llinstancetracker]") { - std::string what; Unkeyed* unkeyed = new Unkeyed; // capture a snapshot but do not yet traverse it auto snapshot = Unkeyed::instance_snapshot(); // delete the one instance delete unkeyed; // traversing the snapshot should reflect the deletion - // avoid ensure_equals() because it requires the ability to stream the - // two values to std::ostream CHECK(snapshot.begin() == snapshot.end()); } @@ -231,29 +217,21 @@ namespace typedef std::set InstanceSet; InstanceSet existing; // We can't use the iterator-range InstanceSet constructor because - // beginInstances() returns an iterator that dereferences to an - // Unkeyed&, not an Unkeyed*. + // instance_snapshot() yields an Unkeyed&, not an Unkeyed*. for (auto& ref : Unkeyed::instance_snapshot()) { existing.insert(&ref); } - try - { - // We don't expect the assignment to take place because we expect - // Unkeyed to respond to the non-empty string param by throwing. - // We know the LLInstanceTracker base-class constructor will have - // run before Unkeyed's constructor, therefore the new instance - // will have added itself to the underlying set. The whole - // question is, when Unkeyed's constructor throws, will - // LLInstanceTracker's destructor remove it from the set? I - // realize we're testing the C++ implementation more than - // Unkeyed's implementation, but this seems an important point to - // nail down. - new Unkeyed("throw"); - } - catch (const Badness&) - { - } + // We expect Unkeyed to respond to the non-empty string param by + // throwing. We know the LLInstanceTracker base-class constructor will + // have run before Unkeyed's constructor, therefore the new instance + // will have added itself to the underlying set. The whole question + // is, when Unkeyed's constructor throws, will LLInstanceTracker's + // destructor remove it from the set? I realize we're testing the C++ + // implementation more than Unkeyed's implementation, but this seems + // an important point to nail down. Made owned, so that one that did + // not throw would not outlive the check. + CHECK_THROWS_AS(std::make_unique("throw"), Badness); // Ensure that every member of the new, updated set of Unkeyed // instances was also present in the original set. If that's not true, // it's because our new Unkeyed ended up in the updated set despite diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 394f4e4efc..f551e4de4b 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -119,7 +119,7 @@ namespace tut " hdr = []\n" " while b':' not in hdr and len(hdr) < 20:\n" " hdr.append(sys.stdin.buffer.read(1))\n" - " if not hdr:\n" + " if not hdr[-1]:\n" " sys.exit(0)\n" " if not hdr[-1] == b':':\n" " raise ProtocolError('Expected len:data, got %r' % hdr, hdr)\n" @@ -443,6 +443,8 @@ namespace tut static void test_large_message(const std::string& PYTHON, const std::string& reader_module, const std::string& test_name, size_t size) { + // test_or_split() runs this at several sizes: say which one failed + CAPTURE(size); ReqIDAPI api; Result result; NamedExtTempFile script("py", @@ -498,6 +500,7 @@ namespace tut " send('" << result.getName() << "',\n" " 'at offset %s, expected %r but got %r' %\n" " (start, large[start:end], echoed[start:end]))\n" + " break\n" "sys.exit(1)\n";}); waitfor(LLLeap::create(test_name, StringVec{PYTHON, script.getPath().string(), stringize(size)}), @@ -572,7 +575,9 @@ namespace tut // test_large_message() can succeed. We still want the overall test to // fail; otherwise we won't get the coder's attention -- but if // test_large_message() fails, try to find a plausible size at which it - // DOES work. + // DOES work. Catch2 records a failed REQUIRE before it throws, so every + // size the search tries and fails is reported as a failure of its own, + // naming that size; the exception rethrown below only ends the case. static void test_or_split(const std::string& PYTHON, const std::string& reader_module, const std::string& test_name, size_t size) { diff --git a/indra/llcommon/tests/llpounceable_test.cpp b/indra/llcommon/tests/llpounceable_test.cpp index f847c3d28d..22707a0698 100644 --- a/indra/llcommon/tests/llpounceable_test.cpp +++ b/indra/llcommon/tests/llpounceable_test.cpp @@ -212,10 +212,9 @@ namespace CHECK(data2 == "def"); } - TEST_CASE("llpounceable: an int tag does not compile (uncomment to check)", "[llpounceable]") - { - // The following declaration should fail: only LLPounceableQueue and - // LLPounceableStatic should work as tags. -// LLPounceable pounceable; - } + // Only LLPounceableQueue and LLPounceableStatic work as tags: any other + // tag, such as int, leaves LLPounceableQueueImpl undefined, so the + // following declaration must fail to compile. That is a compile-time + // property with nothing to check at run time; uncomment it to see. +// LLPounceable pounceable; } // anonymous namespace diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index d793c94aaf..d407145976 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -114,10 +114,15 @@ static std::string readfile(const std::filesystem::path& pathname, const std::st return output; } -#if LL_WINDOWS -static std::string readfile_if_present(const std::string& pathname) +/** + * Read the whole of the specified file, or "" if it cannot be opened. Unlike + * readfile(), an empty file is not an error: a loop polling a file that a + * script rewrites can read it between the script's truncating open and its + * write. + */ +static std::string readfile_if_present(const std::filesystem::path& pathname) { - std::ifstream inf(pathname.c_str()); + std::ifstream inf(pathname); if (!inf.is_open()) { return ""; @@ -125,7 +130,6 @@ static std::string readfile_if_present(const std::string& pathname) return std::string((std::istreambuf_iterator(inf)), std::istreambuf_iterator()); } -#endif /// Looping on LLProcess::isRunning() must now be accompanied by pumping /// "mainloop" -- otherwise the status won't update and you get an infinite @@ -442,6 +446,13 @@ namespace return {}; } + // Closes a process handle however the test leaves the scope holding it. + struct HandleCloser + { + void operator()(HANDLE handle) const { CloseHandle(handle); } + }; + using ScopedHandle = std::unique_ptr; + void verify_autokill_on_helper_exit(const std::string& desc, int child_count) { NamedExtTempFile child_script("py", @@ -463,16 +474,16 @@ namespace REQUIRE(bool(helper)); std::vector pids = wait_for_helper_pids(pidfile.getPath().string(), child_count); - std::vector handles; + std::vector handles; handles.reserve(pids.size()); for (DWORD pid : pids) { // 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); + ScopedHandle handle(OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, false, pid)); CAPTURE(pid); REQUIRE(handle != nullptr); - handles.push_back(handle); + handles.push_back(std::move(handle)); } { @@ -483,11 +494,10 @@ namespace waitfor(*helper); CHECK(helper->getStatus().mState == LLProcess::EXITED); - for (HANDLE handle : handles) + for (const ScopedHandle& handle : handles) { UNSCOPED_INFO("autokilled child exited"); - REQUIRE(WaitForSingleObject(handle, AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS) == WAIT_OBJECT_0); - CloseHandle(handle); + REQUIRE(WaitForSingleObject(handle.get(), AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS) == WAIT_OBJECT_0); } } } @@ -803,55 +813,48 @@ namespace tut // 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 + // waiting, proving the non-blocking nature of these pipes. Any check + // below that fails reports whatever we did get, to help diagnose. + std::ostringstream dump; + dump << "History:\n"; + for (const Item& item : history) { - // 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. - require_equals_(wi.why, APR_PROC_EXIT); - require_equals_(wi.rc, 0); - - unsigned i = 0; - 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; -// require_equals_(history[i].which, "out"); -// require_equals_(history[i].what, "*eof*"); - ++i; - 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; -// require_equals_(history[i].which, "err"); -// require_equals_(history[i].what, "*eof*"); - } - catch (const ll_test::Failure&) - { - std::cout << "History:\n"; - for (const Item& item : history) + std::string what(item.what); + if ((! what.empty()) && what[what.length() - 1] == '\n') { - std::string what(item.what); - if ((! what.empty()) && what[what.length() - 1] == '\n') + what.erase(what.length() - 1); + if ((! what.empty()) && what[what.length() - 1] == '\r') { what.erase(what.length() - 1); - if ((! what.empty()) && what[what.length() - 1] == '\r') - { - what.erase(what.length() - 1); - what.append("\\r"); - } - what.append("\\n"); + what.append("\\r"); } - std::cout << " " << item.which << ": '" << what << "' (" - << item.tries << " tries)\n"; + what.append("\\n"); } - std::cout << std::flush; - // re-raise same error; just want to enrich the output - throw; + dump << " " << item.which << ": '" << what << "' (" + << item.tries << " tries)\n"; } + INFO(dump.str()); + + require_equals_(wi.why, APR_PROC_EXIT); + require_equals_(wi.rc, 0); + + unsigned i = 0; + 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; +// require_equals_(history[i].which, "out"); +// require_equals_(history[i].what, "*eof*"); + // history[1] exists: reading the whole line above pushed it + ++i; + 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; +// require_equals_(history[i].which, "err"); +// require_equals_(history[i].what, "*eof*"); } TEST_CASE_METHOD(llprocess_data, "llprocess: cwd sets the child's working directory", "[llprocess]") @@ -974,7 +977,7 @@ namespace tut for ( ; i < timeout; ++i) { yield(); - if (readfile(out.getPath(), "from kill() script") == "ok") + if (readfile_if_present(out.getPath()) == "ok") break; } // If we broke this loop because of the counter, something's wrong @@ -1021,7 +1024,7 @@ namespace tut for ( ; i < timeout; ++i) { yield(); - if (readfile(out.getPath(), "from kill() script") == "ok") + if (readfile_if_present(out.getPath()) == "ok") break; } // If we broke this loop because of the counter, something's wrong @@ -1074,7 +1077,7 @@ namespace tut for ( ; i < timeout; ++i) { yield(); - if (readfile(from.getPath(), "from autokill script") == "ok") + if (readfile_if_present(from.getPath()) == "ok") break; } // If we broke this loop because of the counter, something's wrong @@ -1136,7 +1139,7 @@ namespace tut for ( ; i < timeout; ++i) { yield(); - if (readfile(from.getPath(), "from autokill script") == "ok") + if (readfile_if_present(from.getPath()) == "ok") break; } // If we broke this loop because of the counter, something's wrong @@ -1560,7 +1563,7 @@ namespace tut 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", 6) == 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); @@ -2070,7 +2073,7 @@ namespace tut for ( ; i < timeout; ++i) { yield(); - if (readfile(out.getPath(), "from kill() script") == "ok") + if (readfile_if_present(out.getPath()) == "ok") break; } UNSCOPED_INFO("script never started"); diff --git a/indra/llcommon/tests/llsd_test.cpp b/indra/llcommon/tests/llsd_test.cpp index a57a67a413..2fa652c7e7 100644 --- a/indra/llcommon/tests/llsd_test.cpp +++ b/indra/llcommon/tests/llsd_test.cpp @@ -1,5 +1,5 @@ /** - * @file llsd_new_tut.cpp + * @file llsd_test.cpp * @date February 2006 * @brief LLSD unit tests * @@ -65,11 +65,9 @@ namespace tut } } // anonymous namespace - // Free functions rather than members of SDTestData: the tests call - // them inside the bodies they hand the checks above, and MSVC does - // not find a member of the data struct by its bare name in a lambda. - // Static in tut rather than in the anonymous namespace, whose TUT - // ensure_equals would hide lltut.h's overloads for LLSD's types. + // Free functions rather than members of a fixture: the tests call them + // inside the bodies they hand the checks above, and MSVC does not find + // a member of the fixture by its bare name in a lambda. template static void require_type_and_value(const char* what, const LLSD& actual, T expectedValue) @@ -539,14 +537,13 @@ namespace tut UNSCOPED_INFO("convert to int, then bool"); REQUIRE(((int)v ? true : false) == false); + bool took_if = false; if(v) { - REQUIRE(true); - } - else - { - FAIL("bool did not convert to a bool in if statement."); + took_if = true; } + UNSCOPED_INFO("bool did not convert to a bool in if statement."); + REQUIRE(took_if); if(!v) { diff --git a/indra/llcommon/tests/llsdserialize_python_test.cpp b/indra/llcommon/tests/llsdserialize_python_test.cpp index 0949395270..1a5457c17b 100644 --- a/indra/llcommon/tests/llsdserialize_python_test.cpp +++ b/indra/llcommon/tests/llsdserialize_python_test.cpp @@ -109,8 +109,15 @@ namespace tut if (waitpid(py->getProcessID(), &status, 0) == -1) { int waitpid_errno(errno); - UNSCOPED_INFO("couldn't retrieve rc: waitpid() failed"); - REQUIRE(waitpid_errno == ECHILD); + // Something else in the process reaped the child first -- a + // SIGCHLD handler, as LLProcess allows for -- and took the exit + // code this helper exists to check: the case cannot say, so it + // skips rather than passing unchecked. + if (waitpid_errno == ECHILD) + { + SKIP(desc << " script's exit code was taken by another waitpid()"); + } + FAIL("couldn't retrieve rc: waitpid() failed with errno " << waitpid_errno); } else { @@ -160,7 +167,7 @@ namespace tut "print('Running on', sys.platform)\n"); } - // helper for test<3> - test<7> + // helper for the "to Python" cases static void writeLLSDArray(const FormatterFunction& serialize, std::ostream& out, const LLSD& array) { @@ -190,7 +197,7 @@ namespace tut } } - // helper for test<3> - test<7> + // helper for the "to Python" cases static void toPythonUsing(const std::string& desc, const FormatterFunction& serialize) { @@ -314,7 +321,7 @@ namespace tut } |*==========================================================================*/ - // helper for test<8> - test<12> + // helper for the "from Python" cases static bool itemFromStream(std::istream& istr, LLSD& item, const ParserFunction& parse) { // reset the output value for debugging clarity @@ -334,7 +341,7 @@ namespace tut return parse(stream, item, length); } - // helper for test<8> - test<12> + // helper for the "from Python" cases static void fromPythonUsing(const std::string& pyformatter, const ParserFunction& parse= [](std::istream& istr, LLSD& data, llssize max_bytes) diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index f39a2e224c..109074de11 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -49,6 +49,7 @@ typedef U32 uint32_t; #include "altestmatchers.h" #include "wrapllerrs.h" #include +#include typedef std::function FormatterFunction; typedef std::function ParserFunction; @@ -202,7 +203,7 @@ namespace TEST_CASE_METHOD(sd_xml_data, "LLSDXMLFormatter: maps format empty and with their keys sorted", "[LLSDXMLFormatter]") { - // tests with arrays + // tests with maps std::string expected; mSD = LLSD::emptyMap(); @@ -297,10 +298,10 @@ namespace // Verify formatting succeeded CHECK(result >= 0); - // Verify exceptions were removed + // Verify the exception mask is still empty CHECK(ostr.exceptions() == original_exceptions); - // Verify precision was still restored even on failure + // Verify precision was restored CHECK(ostr.precision() == (std::streamsize)5); } } @@ -564,7 +565,7 @@ namespace require_round_trip(msg + " nested arrays", v); v = LLSD::emptyMap(); - fillmap(v, 10, 3); // 10^6 maps + fillmap(v, 10, 3); // 1111 maps, 10^3 of them innermost require_round_trip(msg + " many nested maps", v); } @@ -615,8 +616,8 @@ namespace 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. + // parse failures, since the value check above already + // requires equality. REQUIRE(parsed_count == expected_count); } @@ -957,7 +958,7 @@ namespace TEST_CASE_METHOD(TestLLSDBinaryParsing, "llsd binary parsing: malformed input fails to parse", "[llsd_binary_parsing]") { - // test handling of xml not recognized as llsd results in an + // test handling of malformed binary llsd results in an // LLSD Undefined require_parse( "malformed binary map", @@ -1006,7 +1007,7 @@ namespace memcpy(&vec[1], &size, sizeof(uint32_t)); vec.push_back('k'); auto key_size_loc = vec.size(); - size = htonl(1); // 1 too short + size = htonl(1); // 2 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'); @@ -1048,7 +1049,7 @@ namespace memcpy(&vec[1], &size, sizeof(uint32_t)); std::string str_bad_3((char*)&vec[0], vec.size()); require_parse( - "invalid map too long", + "invalid map size one too few", str_bad_3, LLSD(), LLSDParser::PARSE_FAILURE); @@ -1057,7 +1058,7 @@ namespace memcpy(&vec[1], &size, sizeof(uint32_t)); std::string str_bad_4((char*)&vec[0], vec.size()); require_parse( - "invalid map too short", + "invalid map size one too many", str_bad_4, LLSD(), LLSDParser::PARSE_FAILURE); @@ -1068,7 +1069,7 @@ namespace std::vector vec; vec.push_back('['); vec.resize(vec.size() + 4); - uint32_t size = htonl(1); // 1 too short + uint32_t size = htonl(1); // 1 too few 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'); @@ -1079,12 +1080,12 @@ namespace std::string str_bad_1((char*)&vec[0], vec.size()); require_parse( - "invalid array size", + "invalid array size one too few", str_bad_1, LLSD(), LLSDParser::PARSE_FAILURE); - // check with correct size, but unterminated map (missing ']') + // check with correct size, but unterminated array (missing ']') size = htonl(2); // correct size memcpy(&vec[1], &size, sizeof(uint32_t)); std::string str_bad_2((char*)&vec[0], vec.size()); @@ -1094,7 +1095,7 @@ namespace LLSD(), LLSDParser::PARSE_FAILURE); - // check w/ correct size and correct map termination + // check w/ correct size and correct array termination LLSD val; val.append("amy"); val.append(23); @@ -1106,12 +1107,12 @@ namespace val, 3); - // check with too many elements - size = htonl(3); // 1 too long + // check with a size one more than the elements + size = htonl(3); // 1 too many memcpy(&vec[1], &size, sizeof(uint32_t)); std::string str_bad_3((char*)&vec[0], vec.size()); require_parse( - "array too short", + "invalid array size one too many", str_bad_3, LLSD(), LLSDParser::PARSE_FAILURE); @@ -1279,7 +1280,7 @@ namespace 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 + // test handling of malformed notation llsd results in an // LLSD Undefined require_parse( "malformed notation map", @@ -1498,7 +1499,8 @@ namespace 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 + // test that a string size past the bytes left fails rather than + // reading beyond the end of the input std::string bad_str("s(5)\"hi\""); require_parse( "size longer than bytes left", @@ -1509,7 +1511,8 @@ namespace 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 + // test that a binary size past the bytes left fails rather than + // reading beyond the end of the input std::string bad_bin("b(5)\"hi\""); require_parse( "size longer than bytes left", @@ -1892,6 +1895,13 @@ namespace (S32)corrupt.size()) != LLUZipHelper::ZR_OK); } + // unzip_llsdNavMesh() hands back a realloc()ed buffer, for free() to let go. + struct FreeBytes + { + void operator()(U8* bytes) const { free(bytes); } + }; + using NavMeshBytes = std::unique_ptr; + TEST_CASE_METHOD(TestLLSDSerializeData, "llsd serialization: unzip_llsdNavMesh round-trips and rejects corruption", "[llsd_serialization]") { // unzip_llsdNavMesh: gzip round trip, and clean failure on a @@ -1923,13 +1933,12 @@ namespace std::istringstream in(compressed); bool valid = false; size_t outsize = 0; - U8* out = unzip_llsdNavMesh(valid, outsize, in, (S32)compressed.size()); + NavMeshBytes out(unzip_llsdNavMesh(valid, outsize, in, (S32)compressed.size())); CHECK(valid); - REQUIRE(out != NULL); + REQUIRE(out.get() != nullptr); REQUIRE(outsize == payload.size()); CHECK(payload.compare(0, payload.size(), - (const char*)out, outsize) == 0); - free(out); + (const char*)out.get(), outsize) == 0); } { @@ -1942,9 +1951,9 @@ namespace std::istringstream in(corrupt); bool valid = true; size_t outsize = 0; - U8* out = unzip_llsdNavMesh(valid, outsize, in, (S32)corrupt.size()); + NavMeshBytes out(unzip_llsdNavMesh(valid, outsize, in, (S32)corrupt.size())); CHECK_FALSE(valid); - CHECK(out == NULL); + CHECK(out.get() == nullptr); } } @@ -2088,6 +2097,9 @@ namespace LLSD test; typedef std::vector buf_t; buf_t val; + // Reseeding with the pass number on purpose: each pass, and so the + // whole buffer, is the same on every run, whatever drew from rand() + // before this case. for(int ii = 0; ii < 100; ++ii) { srand(ii); /* Flawfinder: ignore */ @@ -2115,7 +2127,7 @@ namespace 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 = LLSD::emptyMap(); test["foo"] = "bar"; test["baz"] = 100; require_via_binary_and_notation("map", test); diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 1e95e1f18b..cb567b7c9b 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -105,10 +105,6 @@ namespace }; }; - TEST_CASE_METHOD(singleton, "LLSingleton: an empty case that checks nothing", "[LLSingleton]") - { - - } TEST_CASE_METHOD(singleton, "LLSingleton: getInstance() makes an instance", "[LLSingleton]") { LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); @@ -131,9 +127,11 @@ namespace CHECK(LLSingletonTest::instanceExists()); } - // 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. + // Makes CLS, with 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. CHECKs, so + // that a wrong order still deletes them all and leaves the next case its + // own to make. 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) @@ -143,9 +141,9 @@ namespace sLog.clear(); (void)CLS::instance(); - REQUIRE(sLog == made); + CHECK(sLog == made); LLSingletonBase::deleteAll(); - REQUIRE(sLog == made + deleted); + CHECK(sLog == made + deleted); } TEST_CASE_METHOD(singleton, "LLSingleton: A alone is made and deleted in order", "[LLSingleton]") @@ -295,23 +293,24 @@ namespace { LLSINGLETON_EMPTY_CTOR(CircularPInit); public: - virtual void initSingleton() override + // Whether getInstance() from initSingleton() gave the instance being + // initialized. Refused, it would have erred, which WrapLLErrs throws. + static inline bool sFoundItself = false; + + void initSingleton() override { // never mind indirection, just go straight for the circularity - CircularPInit *pt = getInstance(); - if (!pt) - { - throw; - } + sFoundItself = getInstance() == this; } }; - TEST_CASE_METHOD(singleton, "LLSingleton: a circular LLParamSingleton initSingleton() is allowed", "[LLSingleton]") + TEST_CASE_METHOD(singleton, "LLSingleton: a circular LLParamSingleton initSingleton() finds the instance", "[LLSingleton]") { WrapLLErrs catcherr; std::string threw = catcherr.catch_llerrs([](){ CircularPInit::initParamSingleton(); }); CHECK(threw.empty()); + CHECK(CircularPInit::sFoundItself); } } diff --git a/indra/llcommon/tests/llstreamtools_test.cpp b/indra/llcommon/tests/llstreamtools_test.cpp index 76d6cb06f6..56cf1ac623 100644 --- a/indra/llcommon/tests/llstreamtools_test.cpp +++ b/indra/llcommon/tests/llstreamtools_test.cpp @@ -40,7 +40,6 @@ namespace char arr[255]; std::string str; std::string expected_result; - std::string actual_result; std::istringstream is; is.str(str = ""); @@ -76,6 +75,7 @@ namespace skip_whitespace(is); is.get(arr, 255, '\0'); expected_result = "\r\nSecondLife is a 3D World"; + UNSCOPED_INFO("stops at a carriage return"); CHECK(arr == expected_result); } @@ -84,7 +84,6 @@ namespace char arr[255]; std::string str; std::string expected_result; - std::string actual_result; std::istringstream is; bool ret; @@ -124,7 +123,6 @@ namespace char arr[255]; std::string str; std::string expected_result; - std::string actual_result; std::istringstream is; bool ret; @@ -175,7 +173,6 @@ namespace char arr[255]; std::string str; std::string expected_result; - std::string actual_result; std::istringstream is; bool ret; @@ -213,7 +210,6 @@ namespace char arr[255]; std::string str; std::string expected_result; - std::string actual_result; std::istringstream is; bool ret; @@ -261,7 +257,6 @@ namespace char arr[255]; std::string str; std::string expected_result; - std::string actual_result; std::istringstream is; bool ret; @@ -270,6 +265,7 @@ namespace 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."; + CHECK(ret); CHECK(arr == expected_result); is.clear(); @@ -277,6 +273,7 @@ namespace 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."; + CHECK(ret); CHECK(arr == expected_result); is.clear(); @@ -284,6 +281,7 @@ namespace ret = skip_to_end_of_next_keyword("SECONDKEY", is); is.get(arr, 255, '\0'); expected_result = "\nFOURTHKEY FOURTHVALUEis a 3DWorld."; + CHECK(ret); CHECK(arr == expected_result); is.clear(); @@ -291,6 +289,7 @@ namespace ret = skip_to_end_of_next_keyword("FOURTHKEY", is); is.get(arr, 255, '\0'); expected_result = " FOURTHVALUEis a 3DWorld."; + CHECK(ret); CHECK(arr == expected_result); is.clear(); @@ -298,8 +297,11 @@ namespace ret = skip_to_end_of_next_keyword("{", is); is.get(arr, 255, '\0'); expected_result = " Does it?\n"; - UNSCOPED_INFO("several matches on the same line"); - CHECK(arr == expected_result); + { + INFO("a match not followed by whitespace is passed over"); + CHECK(ret); + CHECK(arr == expected_result); + } is.clear(); is.str(str = "Delim { could not be found at start"); @@ -334,16 +336,19 @@ namespace ret = get_word(actual_result, is); expected_result = "First"; CHECK(actual_result == expected_result); + CHECK(ret); actual_result = ""; ret = get_word(actual_result, is); expected_result = "Second"; CHECK(actual_result == expected_result); + CHECK(ret); actual_result = ""; ret = get_word(actual_result, is); expected_result = "Third"; CHECK(actual_result == expected_result); + CHECK(ret); // the current implementation of get_word seems inconsistent with // skip_to_next_word. skip_to_next_word treats any character other @@ -355,11 +360,14 @@ namespace ret = get_word(actual_result, is); // will copy Fourth-ShouldThisBePartOfFourth expected_result = "Fourth-ShouldThisBePartOfFourth"; // as per current impl. CHECK(actual_result == expected_result); + CHECK(ret); actual_result = ""; ret = get_word(actual_result, is); // will copy Fifth expected_result = "Fifth"; // as per current impl. CHECK(actual_result == expected_result); + // the newline after it is still to be read + CHECK(ret); is.clear(); is.str(str = " \t \r \n "); @@ -376,46 +384,56 @@ namespace CHECK_FALSE(ret); } - TEST_CASE("streamtools: get_word and skip_to_next_word reach the same fifth word", "[streamtools]") + TEST_CASE("streamtools: get_word splits only at whitespace where skip_to_next_word splits at anything but a letter digit or underscore", "[streamtools]") { + // get_word() treats only isspace() characters (space, form feed, + // newline, carriage return, horizontal and vertical tab) as + // delimiters, while skip_to_next_word treats any character other + // than a letter, a digit or '_' as one. So a hyphen splits a word for + // skip_to_next_word but not for get_word, and an underscore splits it + // for neither. std::string str; - std::string expected_result; - std::string actual_result; + std::string fifth_by_get_word; + std::string fifth_by_skipping; + std::string word; std::istringstream is; is.clear(); is.str(str = " First Second \t \r \n Third Fourth-ShouldThisBePartOfFourth Fifth\n"); - actual_result = ""; - get_word(actual_result, is); // First - actual_result = ""; - get_word(actual_result, is); // Second - actual_result = ""; - get_word(actual_result, is); // Third - - // the current implementation of get_word seems inconsistent with - // skip_to_next_word. skip_to_next_word treats any character other - // than alhpa-numeric and '_' as a delimter, while get_word() - // treats only isspace() (i.e. space, form-feed('\f'), newline ('\n'), - // carriage return ('\r'), horizontal tab ('\t'), and vertical tab ('\v') - // as delimiters - actual_result = ""; - get_word(actual_result, is); // will copy Fourth-ShouldThisBePartOfFourth - - actual_result = ""; - get_word(actual_result, is); // will copy Fifth + get_word(word, is); // First + word = ""; + get_word(word, is); // Second + word = ""; + get_word(word, is); // Third + word = ""; + get_word(word, is); // Fourth-ShouldThisBePartOfFourth + get_word(fifth_by_get_word, is); + CHECK(fifth_by_get_word == "Fifth"); + + is.clear(); + is.str(str); + skip_to_next_word(is); // should now point to First + skip_to_next_word(is); // should now point to Second + skip_to_next_word(is); // should now point to Third + skip_to_next_word(is); // should now point to Fourth + skip_to_next_word(is); // should now point to ShouldThisBePartOfFourth + get_word(fifth_by_skipping, is); + UNSCOPED_INFO("skip_to_next_word splits at the hyphen"); + CHECK(fifth_by_skipping == "ShouldThisBePartOfFourth"); + // With an underscore in place of the hyphen, skip_to_next_word's fifth + // word is "Fifth" too. is.clear(); is.str(str = " First Second \t \r \n Third Fourth_ShouldThisBePartOfFourth Fifth\n"); skip_to_next_word(is); // should now point to First skip_to_next_word(is); // should now point to Second skip_to_next_word(is); // should now point to Third - skip_to_next_word(is); // should now point to Fourth - skip_to_next_word(is); // should now point to ShouldThisBePartOfFourth - expected_result = ""; - // will copy ShouldThisBePartOfFourth, the fifth word, - // while using get_word above five times result in getting "Fifth" - get_word(expected_result, is); - CHECK(actual_result == expected_result); + skip_to_next_word(is); // should now point to Fourth_ShouldThisBePartOfFourth + skip_to_next_word(is); // should now point to Fifth + fifth_by_skipping = ""; + get_word(fifth_by_skipping, is); + UNSCOPED_INFO("skip_to_next_word keeps an underscore in the word"); + CHECK(fifth_by_skipping == "Fifth"); } TEST_CASE("streamtools: get_word with a limit stops after n characters", "[streamtools]") @@ -432,27 +450,32 @@ namespace ret = get_word(actual_result, is, 255); expected_result = "First"; CHECK(actual_result == expected_result); + CHECK(ret); actual_result = ""; ret = get_word(actual_result, is, 4); expected_result = "Seco"; // should be cut short CHECK(actual_result == expected_result); + CHECK(ret); actual_result = ""; ret = get_word(actual_result, is, 255); expected_result = "nd"; // get remainder of Second CHECK(actual_result == expected_result); + CHECK(ret); actual_result = ""; ret = get_word(actual_result, is, 0); // 0 size string - expected_result = ""; // get remainder of Second + expected_result = ""; // reads nothing, though it still skips the whitespace before Third UNSCOPED_INFO("a limit of zero"); CHECK(actual_result == expected_result); + CHECK(ret); actual_result = ""; ret = get_word(actual_result, is, 255); expected_result = "Third"; CHECK(actual_result == expected_result); + CHECK(ret); is.clear(); is.str(str = " \t \r \n "); @@ -536,7 +559,7 @@ namespace std::string actual_result; std::istringstream is; - // need to be check if this test case is wrong or the implementation is wrong. + // A line ends only at '\n', so a '\r' on its own is part of the line. is.clear(); is.str(str = "Should not skip lone \r.\r\n"); actual_result = ""; @@ -632,6 +655,7 @@ namespace ret = remove_last_char('d',str); expected_result = "SecondLife is a 3D Worl"; CHECK(str == expected_result); + CHECK(ret); str = "SecondLife is a 3D World"; ret = remove_last_char('W',str); @@ -785,13 +809,10 @@ namespace str = "SecondLife is a \"3D world"; remove_double_quotes(str); expected_result = "SecondLife is a 3D world"; + UNSCOPED_INFO("a lone quote is removed too"); CHECK(str == expected_result); } - TEST_CASE("streamtools: get_brace_count has no checks yet", "[streamtools]") - { - } - TEST_CASE("streamtools: get_keyword_and_value splits off the first word as the keyword", "[streamtools]") { std::string s = "SecondLife is a 3D World"; @@ -845,8 +866,8 @@ namespace std::istringstream is(str); char buf[255] = {0}; - fullread(is, buf, 255); - CHECK_THAT(ll_test::Memory(buf, str.size()-1), ll_test::MemoryMatches(str.c_str(), str.size()-1)); + CHECK(fullread(is, buf, 255) == std::streamsize(str.size())); + CHECK_THAT(ll_test::Memory(buf, str.size()), ll_test::MemoryMatches(str.c_str(), str.size())); is.clear(); is.str(str = "First Line.\nSecond Line\n"); @@ -854,7 +875,7 @@ namespace char expected_string[] = "First Line.\nSecond"; int len = sizeof(expected_string)-1; - fullread(is, buf, len); + CHECK(fullread(is, buf, len) == len); CHECK_THAT(ll_test::Memory(buf, len), ll_test::MemoryMatches(expected_string, len)); } diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index f8538b2ef6..a85c0fa3bd 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -30,85 +30,12 @@ #include "../llstring.h" #include "../llsd.h" -#include "StringVec.h" // must come BEFORE lltut.h +#include "StringVec.h" #include #include namespace { - TEST_CASE("LLString: std::string constructors give the expected text and size", "[LLString]") - { - std::string llstr1; - { - INFO("default-constructed"); - CHECK(llstr1.size() == 0); - CHECK(llstr1.empty()); - } - - std::string llstr2("Hello"); - { - 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); - { - 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); - { - 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()); - { - INFO("from the end position"); - CHECK(llstr5.size() == 0); - CHECK(llstr5.empty()); - } - - std::string llstr6(5, 'A'); - { - 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); - { - 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); - { - 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 - { - INFO("from the end position, asking for more"); - CHECK(llstr9.size() == 0); - CHECK(llstr9.empty()); - } - } - TEST_CASE("LLString: isValidIndex accepts indexes up to and including the length", "[LLString]") { std::string str("Len=5"); @@ -157,7 +84,7 @@ namespace LLStringUtil::truncate(str, 6); CHECK(str == "Second"); - // further truncate more than the length + // and to nothing at all LLStringUtil::truncate(str, 0); CHECK(str == ""); } @@ -223,7 +150,6 @@ namespace std::string str_val("Hello.\n\t\r\nABCDEFGHIABABAB"); LLStringUtil::replaceChar(str_val, 'A', 'X'); CHECK(str_val == "Hello.\n\t\r\nXBCDEFGHIXBXBXB"); - std::string str_val1("Hello.\n\t\r\nABCDEFGHIABABAB"); } TEST_CASE("LLString: containsNonprintable finds controls and separators but not text", "[LLString]") @@ -482,7 +408,7 @@ namespace CHECK_FALSE(LLStringUtil::convertToS32(str_val, value)); } - TEST_CASE("LLString: convertToF32 reads decimals and exponents and signs", "[LLString]") + TEST_CASE("LLString: convertToF32 reads decimals and exponents and signs from -F32_MAX to F32_MAX only", "[LLString]") { F32 value; std::string str_val("2147483647"); //0x7FFFFFFF @@ -493,16 +419,22 @@ namespace CHECK(LLStringUtil::convertToF32(str_val, value)); CHECK(value == 0); - /* Need to find max/min F32 values - str_val = "-2147483648"; - ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); + // The limits are F32_MAX either way, written as the shortest + // decimal that reads back as exactly F32_MAX. Past them is a + // failure, not an infinity. + str_val = "3.4028234663852886e38"; + CHECK(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == F32_MAX); - str_val = "2147483648"; - ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + str_val = "-3.4028234663852886e38"; + CHECK(LLStringUtil::convertToF32(str_val, value)); + CHECK(value == -F32_MAX); - str_val = "-2147483649"; - ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - */ + str_val = "3.5e38"; + CHECK_FALSE(LLStringUtil::convertToF32(str_val, value)); + + str_val = "-3.5e38"; + CHECK_FALSE(LLStringUtil::convertToF32(str_val, value)); // The reals go through fast_float now. Decimals, exponents, a sign and // a leading '+' all read the same as they did through the stream, and @@ -531,7 +463,7 @@ namespace CHECK_FALSE(LLStringUtil::convertToF32(str_val, value)); } - TEST_CASE("LLString: convertToF64 reads a large integer and zero", "[LLString]") + TEST_CASE("LLString: convertToF64 accepts -F64_MAX to F64_MAX only", "[LLString]") { F64 value; std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF @@ -542,16 +474,22 @@ namespace CHECK(LLStringUtil::convertToF64(str_val, value)); CHECK(value == 0.0F); - /* Need to find max/min F64 values - str_val = "-2147483648"; - ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); + // The limits are F64_MAX either way, written as the shortest decimal + // that reads back as exactly F64_MAX. Past them is a failure, not an + // infinity. + str_val = "1.7976931348623157e308"; + CHECK(LLStringUtil::convertToF64(str_val, value)); + CHECK(value == F64_MAX); - str_val = "2147483648"; - ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + str_val = "-1.7976931348623157e308"; + CHECK(LLStringUtil::convertToF64(str_val, value)); + CHECK(value == -F64_MAX); - str_val = "-2147483649"; - ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - */ + str_val = "1e309"; + CHECK_FALSE(LLStringUtil::convertToF64(str_val, value)); + + str_val = "-1e309"; + CHECK_FALSE(LLStringUtil::convertToF64(str_val, value)); } TEST_CASE("LLString: compareStrings puts null after text and compares text", "[LLString]") @@ -741,12 +679,13 @@ namespace for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) { // Test when source string is entirely one key - std::string s1 = (std::string)iter->first; + const std::string key = (std::string)iter->first; + std::string s1 = key; std::string s2 = (std::string)iter->second; - CAPTURE(s1); + CAPTURE(key); subcount = LLStringUtil::format(s1, fmt_map); REQUIRE(s1 == s2); - if (s1 == "?" || s1 == "[]") // no interp expected + if (key == "?" || key == "[]") // no interp expected { REQUIRE(0 == subcount); } diff --git a/indra/llcommon/tests/llstring_utf_test.cpp b/indra/llcommon/tests/llstring_utf_test.cpp index f5b9da603c..6657c4f204 100644 --- a/indra/llcommon/tests/llstring_utf_test.cpp +++ b/indra/llcommon/tests/llstring_utf_test.cpp @@ -217,7 +217,7 @@ namespace // 9x shaping (cluster walker: wstring_find_emoji_clusters) // 10x grapheme (step + align + range_at) // 11x utf8str helpers (substChar/makeASCII/removeCRLF/split/preview) - // 12x utf8 walkers (byte-offset segmentation against its wide twin) + // 12x utf8 walkers (byte-offset segmentation, offsets written out) // 14x trim (Unicode whitespace, and what is not whitespace) // Keep this index in sync with the categories used below. @@ -225,7 +225,10 @@ namespace // round-trip coverage // --------------------------------------------------------------- - // Pure ASCII: all six conversion directions must round-trip byte-for-byte. + // Pure ASCII: each of the three conversions llstring has -- UTF-8 to + // codepoints (utf8str_decode_at, under to_u32), codepoints to UTF-8 + // (utf8str_append_cp, under to_utf8) and UTF-16 to UTF-8 -- keeps every + // character. TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: ASCII converts in every direction", "[LLStringUTF]") { const std::string ascii = "Hello, World!"; @@ -295,7 +298,8 @@ namespace CHECK(utf16str_to_utf8str(llutf16string()) == std::string()); } - // Round-trip via rawstr_to_utf8 (utf8 -> std::u32string -> utf8) preserves valid input. + // rawstr_to_utf8 repairs through utf8str_sanitize, which hands valid input + // back as it is. 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"; @@ -532,9 +536,10 @@ namespace 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. + // Both work on the UTF-8 itself -- trim by the offsets of its + // whitespace, tolower through LLStringUtil::toLower. Covered here so a + // regression that breaks a character surfaces before we diagnose it + // through callers. const std::string mixed = "H\xC3\xA9LLO \xE6\x97\xA5"; CHECK(utf8str_trim(mixed) == mixed); CHECK(utf8str_tolower(mixed) == std::string("h\xC3\xA9llo \xE6\x97\xA5")); @@ -578,8 +583,7 @@ namespace } // 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). + // the code point that follows them. 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' }; @@ -602,9 +606,8 @@ namespace 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) + // Heart-on-fire ❤️‍🔥 goes as one cluster, bytes and all. + // 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"; CHECK(utf8str_remove_emojis(fire)); @@ -991,7 +994,8 @@ namespace std::u32string lone_ri = { (llwchar)0x1F1FA }; CHECK(wstring_find_emoji_clusters(lone_ri).size() == size_t(0)); - // Range boundaries on isPictographBase's astral / BMP windows. + // Just either side of the BMP window the old range test had. Neither + // is pictographic. std::u32string just_below_astral = { (llwchar)0x1FFF }; CHECK(wstring_find_emoji_clusters(just_below_astral).size() == size_t(0)); std::u32string just_above_bmp = { (llwchar)0x3300 }; @@ -1047,8 +1051,8 @@ namespace } // BMP pictograph + VS16 + ZWJ + astral emoji (❤️‍🔥 = U+2764 U+FE0F - // U+200D U+1F525). The base ❤ is outside LLStringOps::isEmoji's astral - // range, but the shaping detector must still pick up the whole sequence + // U+200D U+1F525). The base ❤ is not one LLStringOps::isEmoji accepts, + // 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. TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: heart-on-fire is one cluster though its base is in the BMP", "[LLStringUTF]") @@ -1149,6 +1153,8 @@ namespace // ZWJ and VS-16 are extenders, not bases. CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x200D)); CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0xFE0F)); + // Just outside the windows the old range test had: a CJK ideograph + // past the astral one, and either side of the BMP one. CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x20000)); CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x1FFF)); CHECK_FALSE(LLStringOps::isPictographBase((llwchar)0x3300)); @@ -1232,8 +1238,8 @@ namespace 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. + // them, being Extended_Pictographic, as it does © U+00A9 and ® + // U+00AE; isEmoji rejects them. Tooltip lookup must still find them. auto require_single_range = [](llwchar cp, const char* tag) { std::u32string ws { cp }; auto r = wstring_emoji_range_at(ws, 0); @@ -1247,9 +1253,9 @@ namespace 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. + // Bare extenders, and codepoints just outside the old range test's + // windows, get an empty range — isPictographBase rejects these so + // they don't trigger a tooltip lookup on their own. auto require_no_range = [](llwchar cp, const char* tag) { std::u32string ws { cp }; auto r = wstring_emoji_range_at(ws, 0); @@ -1262,9 +1268,9 @@ namespace 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"); + require_no_range((llwchar)0x1FFF, "U+1FFF below the old BMP window"); + require_no_range((llwchar)0x3300, "U+3300 above the old BMP window"); + require_no_range((llwchar)0x20000, "U+20000 above the old astral window"); // Back-to-back flags 🇺🇸🇯🇵 — pos lookup must find the *second* run // when pos has passed the first. Exercises the ordered-iteration @@ -1446,9 +1452,9 @@ namespace // --------------------------------------------------------------- // Pin overload-equivalence: the no-clusters and with-clusters forms - // of wstring_emoji_range_at must produce identical results for the + // of utf8str_emoji_range_at must produce identical results for the // same input. The with-clusters overload exists to let callers - // amortise the wstring_find_emoji_clusters scan across many lookups, + // amortise the utf8str_find_emoji_clusters scan across many lookups, // so any future change that updates one body without the other // (e.g. a bug fix in the loop) needs to fail loudly here. // --------------------------------------------------------------- @@ -1489,8 +1495,8 @@ namespace } // 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. + // astrals) with another code point, decoding and re-encoding each one 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"; @@ -2112,10 +2118,10 @@ namespace 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. + // The walkers work in UTF-8, so every boundary is written out here in + // bytes. Text with a byte per codepoint, three, two, and a two-codepoint + // cluster, so byte and codepoint offsets disagree everywhere after the + // first space. TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: the UTF-8 walkers find every boundary in mixed text", "[LLStringUTF]") { const std::string utf8 = @@ -2207,9 +2213,8 @@ namespace // 12x utf8 walkers // --------------------------------------------------------------- - // 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. + // Over a corpus of text, the cluster walker finds each run at the byte + // offsets written out for it, and no extra ones. TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: utf8str_find_emoji_clusters finds each run in bytes", "[LLStringUTF]") { const std::vector corpus = { @@ -2264,8 +2269,7 @@ namespace } } - // Byte coordinates pinned outright, so a change that moved both walkers - // the same wrong way is still caught. A ZWJ pair sitting behind an + // Byte coordinates pinned outright: a ZWJ pair sitting behind an // ideograph starts at byte 4, not codepoint 2. TEST_CASE_METHOD(llstring_utf_data, "LLStringUTF: a run after an ideograph is placed in bytes not codepoints", "[LLStringUTF]") { @@ -2391,9 +2395,9 @@ namespace 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. + // The UTF-16 pair converts between code units and bytes: the Win32 IME + // hands offsets in code units and the editors index bytes, so a wrong + // answer here is a caret landing in the wrong place mid-composition. 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'. @@ -2566,9 +2570,9 @@ namespace } // 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. + // through this file's wstring_* adapters, which turn codepoint indexes + // into bytes and back -- not a byte offset supplied by a caller, which is + // what every caller in llui now passes. 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. diff --git a/indra/llcommon/tests/lltimer_test.cpp b/indra/llcommon/tests/lltimer_test.cpp index ac93d5fe40..ace629ccdf 100644 --- a/indra/llcommon/tests/lltimer_test.cpp +++ b/indra/llcommon/tests/lltimer_test.cpp @@ -54,11 +54,16 @@ namespace TEST_CASE_METHOD(timer_data, "LLTimer: resetWithExpiry restarts the timer and keeps the expiry", "[LLTimer]") { LLTimer timer; - ms_sleep(50); + // A timer the reset did not restart reads at least this quarter + // second (a Windows Sleep can come back a tick, 16 ms, early); one it + // did reads under 0.2 s, with more than 9.8 s of its 10 left, unless + // this thread is kept off the processor that long between the reset + // and the reads. + ms_sleep(250); timer.resetWithExpiry(10.f); CHECK_FALSE(timer.hasExpired()); - CHECK(timer.getRemainingTimeF32() > 9.9f); - CHECK(timer.getElapsedTimeF32() < 0.04f); + CHECK(timer.getRemainingTimeF32() > 9.8f); + CHECK(timer.getElapsedTimeF32() < 0.2f); } TEST_CASE_METHOD(timer_data, "LLTimer: start() after an expiry clears it which is why resetWithExpiry exists", "[LLTimer]") @@ -72,9 +77,11 @@ namespace TEST_CASE_METHOD(timer_data, "LLTimer: the uptime is the application's and not this timer's", "[LLTimer]") { LLTimer timer; - ms_sleep(50); + // As above: unrestarted, at least a quarter second; restarted, under + // 0.2 s however busy the machine, short of a 0.2 s stall. + ms_sleep(250); timer.reset(); - CHECK(timer.getElapsedTimeF64() < 0.04); + CHECK(timer.getElapsedTimeF64() < 0.2); UNSCOPED_INFO("the uptime runs from before the reset"); CHECK(LLTimer::getUptimeSeconds() > timer.getElapsedTimeF64()); } @@ -86,12 +93,22 @@ namespace const F64 total = LLTimer::getTotalSeconds(); CHECK(std::fabs(total - calendar_seconds) < 1.0); - const auto steady_start = std::chrono::steady_clock::now(); + // Each read of the total is between two of the steady clock's, so + // what the total counted lies between what the steady clock counted + // inside those brackets and outside them, however long this thread is + // kept off the processor at any point. The millisecond either side is + // for the total's whole microseconds and the clocks' grain: a total + // fast or slow by half a percent over the 200 ms falls outside it. + const auto outer_start = std::chrono::steady_clock::now(); const U64 total_start = totalTime(); + const auto inner_start = std::chrono::steady_clock::now(); 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; - CHECK(std::fabs(counted - steady) < 0.005); + const auto inner_end = std::chrono::steady_clock::now(); + const U64 total_end = totalTime(); + const auto outer_end = std::chrono::steady_clock::now(); + const F64 counted = (F64)(total_end - total_start) / 1000000.0; + CHECK(counted > std::chrono::duration(inner_end - inner_start).count() - 0.001); + CHECK(counted < std::chrono::duration(outer_end - outer_start).count() + 0.001); } TEST_CASE_METHOD(timer_data, "LLTimer: total time read on many threads never steps back or runs fast", "[LLTimer]") @@ -120,9 +137,13 @@ namespace { reader.join(); } + // The total read between the steady clock's two reads, so it cannot + // have counted more than they did, however the readers were + // scheduled; the millisecond is the clocks' grain, as above. + const U64 total_end = totalTime(); const F64 steady = std::chrono::duration(std::chrono::steady_clock::now() - steady_start).count(); - const F64 counted = (F64)((U64)totalTime() - total_start) / 1000000.0; + const F64 counted = (F64)(total_end - total_start) / 1000000.0; CHECK_FALSE(stepped_back); - CHECK(counted < steady + 0.005); + CHECK(counted < steady + 0.001); } } diff --git a/indra/llcommon/tests/lltrace_test.cpp b/indra/llcommon/tests/lltrace_test.cpp index 3e9e12e749..00685d3b1a 100644 --- a/indra/llcommon/tests/lltrace_test.cpp +++ b/indra/llcommon/tests/lltrace_test.cpp @@ -1,7 +1,7 @@ /** - * @file llsingleton_test.cpp + * @file lltrace_test.cpp * @date 2011-08-11 - * @brief Unit test for the LLSingleton class + * @brief Unit tests for LLTrace's recordings * * $LicenseInfo:firstyear=2011&license=viewerlgpl$ * Second Life Viewer Source Code @@ -40,7 +40,8 @@ namespace LLUnits { namespace { - // using powers of 2 to allow strict floating point equality + // cups of whole ounces, and whole milligrams of caffeine an ounce, so + // every value recorded is a whole number and compares exactly LL_DECLARE_BASE_UNIT(Ounces, "oz"); LL_DECLARE_DERIVED_UNIT(TallCup, "", Ounces, / 12); LL_DECLARE_DERIVED_UNIT(GrandeCup, "", Ounces, / 16); @@ -124,18 +125,19 @@ namespace all_day.start(); { - // warm up with one grande cup + // warm up with one tall cup drink_coffee(1, S32TallCup(1)); // go to work at_work.start(); { - // drink 3 tall cups, 1 after 3 pm + // drink 3 grande cups, 1 after 3 pm drink_coffee(2, S32GrandeCup(1)); after_3pm.start(); drink_coffee(1, S32GrandeCup(1)); } at_work.stop(); + // and one venti after work drink_coffee(1, S32VentiCup(1)); } // don't need to stop recordings to get accurate values out of them diff --git a/indra/llcommon/tests/llunits_test.cpp b/indra/llcommon/tests/llunits_test.cpp index 1277e22cbf..036aaef30c 100644 --- a/indra/llcommon/tests/llunits_test.cpp +++ b/indra/llcommon/tests/llunits_test.cpp @@ -1,7 +1,7 @@ /** - * @file llsingleton_test.cpp + * @file llunits_test.cpp * @date 2011-08-11 - * @brief Unit test for the LLSingleton class + * @brief Unit tests for LLUnit * * $LicenseInfo:firstyear=2011&license=viewerlgpl$ * Second Life Viewer Source Code @@ -88,7 +88,8 @@ namespace int_quatloos = float_quatloos; CHECK(int_quatloos == S32Quatloos(42)); - LLUnit unsigned_int_quatloos(float_quatloos); + // unsigned from signed + LLUnit unsigned_int_quatloos(int_quatloos); CHECK(unsigned_int_quatloos == S32Quatloos(42)); S32Solari int_solari(1); @@ -196,17 +197,17 @@ namespace } - static bool accept_explicit_quatloos(S32Quatloos q) + static S32 accept_explicit_quatloos(S32Quatloos q) { - return true; + return q.value(); } // signature compatibility TEST_CASE("LLUnit: unit values pass as function arguments", "[LLUnit]") { S32Quatloos quatloos(1); - CHECK(accept_explicit_quatloos(S32Quatloos(1))); - CHECK(accept_explicit_quatloos(quatloos)); + CHECK(accept_explicit_quatloos(S32Quatloos(1)) == 1); + CHECK(accept_explicit_quatloos(quatloos) == 1); } // implicit units @@ -253,7 +254,9 @@ namespace F32 float_val = quatloos_implicit; CHECK(float_val == 16); - S32 int_val = (S32)quatloos_implicit; + // and from an integer one to its integer, with no cast + const S32QuatloosImplicit int_quatloos_implicit(quatloos_implicit); + S32 int_val = int_quatloos_implicit; CHECK(int_val == 16); // conversion of implicits diff --git a/indra/llcommon/tests/lluuid_test.cpp b/indra/llcommon/tests/lluuid_test.cpp index bcb8ea0012..9260f0d24e 100644 --- a/indra/llcommon/tests/lluuid_test.cpp +++ b/indra/llcommon/tests/lluuid_test.cpp @@ -266,22 +266,30 @@ namespace CHECK_FALSE(low > low); } - // operator^= is its own inverse (XOR twice cancels) and commutative. - TEST_CASE("LLUUID: XOR by an id twice cancels and XOR with itself is null", "[LLUUID]") + // XOR, by ^ or in place by ^=, is its own inverse (XOR twice cancels) + // and commutative. + TEST_CASE("LLUUID: XOR by an id twice cancels either way round and XOR with itself is null", "[LLUUID]") { - LLUUID a, b, original; + LLUUID a, b; fill_sample(a); - a.set(kSampleStr); - original = a; + const LLUUID original = a; b.set("11223344-5566-7788-99aa-bbccddeeff00"); LLUUID xored = a ^ b; CHECK(xored != a); + UNSCOPED_INFO("commutative"); + CHECK((b ^ a) == xored); LLUUID xored_twice = xored ^ b; CHECK(xored_twice == original); + a ^= b; + UNSCOPED_INFO("^= is ^ in place"); + CHECK(a == xored); + a ^= b; + CHECK(a == original); + LLUUID xored_self = a ^ a; CHECK(xored_self.isNull()); } @@ -377,6 +385,7 @@ namespace std::set s; s.insert(two); s.insert(one); + REQUIRE(s.size() == 2); auto it = s.begin(); CHECK((int)it->mData[0] == 1); diff --git a/indra/llcommon/tests/stringize_test.cpp b/indra/llcommon/tests/stringize_test.cpp index b1e4948f4d..2dba0b46e3 100644 --- a/indra/llcommon/tests/stringize_test.cpp +++ b/indra/llcommon/tests/stringize_test.cpp @@ -91,7 +91,9 @@ namespace 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. +#if LL_WINDOWS // only Windows converts between std::wstring and std::string + CHECK(stringize(def) == "def ghi"); +#endif // LLSDNotationFormatter emits Reals at full round-trip precision CHECK(stringize(llsd) == "{'abc':'abc def','d':r3.14159265358979,'i':i34}"); } @@ -102,10 +104,22 @@ namespace CHECK(STRINGIZE(std::setprecision(4) << d) == "3.142"); } - TEST_CASE_METHOD(stringize_data, "stringize_h: wstringize renders each type on Windows", "[stringize_h]") + TEST_CASE_METHOD(stringize_data, "stringize_h: wstringize renders each type", "[stringize_h]") { - //Tests rely on validity of wstring_to_utf8str() + // Numbers and characters are streamed into a std::wostringstream, and + // a std::wstring is passed through, so none of these converts between + // narrow and wide and all of them work everywhere. + CHECK(wstringize(c) == L"c"); + CHECK(wstringize(s) == L"17"); + CHECK(wstringize(i) == L"34"); + CHECK(wstringize(l) == L"68"); + CHECK(wstringize(f) == L"3.14159"); + CHECK(wstringize(d) == L"3.14159"); + CHECK(wstringize(def) == L"def ghi"); #if LL_WINDOWS // Windows wstring is a 2byte UTF16 type + // A narrow string is converted to wide, which ll_convert does only on + // Windows, as is ll_convert(std::wstring), which these + // compare through. 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"))); @@ -115,17 +129,6 @@ namespace 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")); - // ensure_equals(ll_convert(wstringize(i)), wstring_to_utf8str(U"34")); - // ensure_equals(ll_convert(wstringize(l)), wstring_to_utf8str(U"68")); - // ensure_equals(ll_convert(wstringize(f)), wstring_to_utf8str(U"3.14159")); - // ensure_equals(ll_convert(wstringize(d)), wstring_to_utf8str(U"3.14159")); - // ensure_equals(ll_convert(wstringize(abc)), wstring_to_utf8str(U"abc def")); - // ensure_equals(ll_convert(wstringize(abc)), wstring_to_utf8str(wstringize(abc.c_str()))); - // ensure_equals(ll_convert(wstringize(def)), wstring_to_utf8str(U"def ghi")); #endif - // ensure_equals(wstring_to_utf8str(wstringize(llsd)), wstring_to_utf8str(L"{'abc':'abc def','d':r3.14159,'i':i34}")); } } // anonymous namespace