diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index 655b43b4eb..02c82263b3 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -9,7 +9,7 @@ if(NOT AL_BUILD_TESTS) return() endif() -# al_add_test_binary( PROJECT SOURCES ... +# al_add_test_binary( PROJECT SOURCES ... [UNIT ...] # [LIBRARIES ...] [SUPPORT ...] # [INCLUDES ...] [DEFINES ...] [ENVIRONMENT ...] # [GL] [PYTHON] [ISA_TIER ] [RESOURCE_LOCK ...] [COMBINED]) @@ -22,6 +22,12 @@ endif() # with it, so the tests would vanish without a word. DEFINES and INCLUDES are # the binary's, for every file in it. # +# UNIT names files of PROJECT, the library, compiled into the binary instead +# of linked, for a test that stubs what they call: its stubs would duplicate +# the library's own definitions. The binary sees the library's include +# directories, and LIBRARIES is all it links. Such a test is a binary of its +# own, since every other test links the code it tests. +# # Each test file is a CTest entry, /, that runs the file's # test cases -- chosen by the tag Catch2's --filenames-as-tags gives each case # from the name of its file -- in one process, as an executable to a test file @@ -38,9 +44,10 @@ endif() # /combined runs all of them in one process. It is labelled # combined_pending and manual, which the default run leaves out, until the # binary's tests are known to share a process: then COMBINED labels it -# combined, and it runs with the rest. With AL_TEST_PER_CASE, each test case is -# an entry of its own instead, found when ctest runs, with the environment and -# labels a file's entry would have. +# combined, and it runs with the rest. A binary of one file has none: that +# file's entry already runs every case in one process. With AL_TEST_PER_CASE, +# each test case is an entry of its own instead, found when ctest runs, with +# the environment and labels a file's entry would have. # # ENVIRONMENT sets variables for the runs, VAR=value each. GL, PYTHON and # ISA_TIER mean what they mean to al_add_test: a GL binary is labelled gl and @@ -56,7 +63,7 @@ function(al_add_test_binary name) arg "GL;PYTHON;COMBINED" "PROJECT;ISA_TIER" - "SOURCES;LIBRARIES;SUPPORT;INCLUDES;DEFINES;ENVIRONMENT;RESOURCE_LOCK" + "SOURCES;UNIT;LIBRARIES;SUPPORT;INCLUDES;DEFINES;ENVIRONMENT;RESOURCE_LOCK" ) if(NOT arg_PROJECT) message(FATAL_ERROR "al_add_test_binary(${name}): PROJECT is required") @@ -109,9 +116,15 @@ function(al_add_test_binary name) # vcpkg's toolchain hangs an app-local copy step on every executable it # sees declared; a test runs from the build tree, as al_add_test explains. set(VCPKG_APPLOCAL_DEPS OFF) - add_executable(${target} ${arg_SOURCES}) + add_executable(${target} ${arg_SOURCES} ${arg_UNIT}) target_link_libraries(${target} PRIVATE al::flags al_test_support ${arg_SUPPORT} ${arg_LIBRARIES}) target_include_directories(${target} PRIVATE ${arg_INCLUDES} ${INDRA_SOURCE_DIR}/test) + if(arg_UNIT) + target_include_directories( + ${target} + PRIVATE $ + ) + endif() if(arg_DEFINES) target_compile_definitions(${target} PRIVATE ${arg_DEFINES}) endif() @@ -260,34 +273,36 @@ function(al_add_test_binary name) PROPERTIES ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" LABELS "${labels}" TIMEOUT 60 ) - # As long as the files take, but no longer than an hour: a hang, which - # the combined run is there to find, holds one CI row only so long. list(LENGTH stems file_count) - math(EXPR combined_timeout "${timeout} * ${file_count}") - if(combined_timeout GREATER 3600) - set(combined_timeout 3600) - endif() - set(combined_label "combined_pending;manual") - if(arg_COMBINED) - set(combined_label combined) - endif() - add_test( - NAME ${target}/combined - COMMAND $ ${arguments} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) - set_tests_properties( - ${target}/combined - PROPERTIES - ENVIRONMENT "${environment}" - ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" - LABELS "${labels};${combined_label}" - DISABLED ${disabled} - SKIP_RETURN_CODE 125 - TIMEOUT ${combined_timeout} - ) - if(arg_RESOURCE_LOCK) - set_tests_properties(${target}/combined PROPERTIES RESOURCE_LOCK "${arg_RESOURCE_LOCK}") + if(file_count GREATER 1) + # As long as the files take, but no longer than an hour: a hang, which + # the combined run is there to find, holds one CI row only so long. + math(EXPR combined_timeout "${timeout} * ${file_count}") + if(combined_timeout GREATER 3600) + set(combined_timeout 3600) + endif() + set(combined_label "combined_pending;manual") + if(arg_COMBINED) + set(combined_label combined) + endif() + add_test( + NAME ${target}/combined + COMMAND $ ${arguments} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties( + ${target}/combined + PROPERTIES + ENVIRONMENT "${environment}" + ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" + LABELS "${labels};${combined_label}" + DISABLED ${disabled} + SKIP_RETURN_CODE 125 + TIMEOUT ${combined_timeout} + ) + if(arg_RESOURCE_LOCK) + set_tests_properties(${target}/combined PROPERTIES RESOURCE_LOCK "${arg_RESOURCE_LOCK}") + endif() endif() endif() diff --git a/indra/llcharacter/CMakeLists.txt b/indra/llcharacter/CMakeLists.txt index 6f88b72dc5..1ad92b2973 100644 --- a/indra/llcharacter/CMakeLists.txt +++ b/indra/llcharacter/CMakeLists.txt @@ -63,26 +63,30 @@ target_link_libraries( target_precompile_headers(llcharacter REUSE_FROM llprecompiled) if(AL_BUILD_TESTS) - foreach( - test - lljoint - llpose - llcharacter - llmotioncontroller - llkeyframemotion - llmultigesture - ) - al_add_test(${test} PROJECT llcharacter LIBRARIES llcharacter) - endforeach() + # The state guard's checks on what llcharacter keeps for the process, for + # every test binary that links it. + add_library(llcharacter_test_checks OBJECT tests/llcharacter_test_checks.cpp) + target_link_libraries(llcharacter_test_checks PRIVATE al::flags al_test_support llcharacter) + set_target_properties(llcharacter_test_checks PROPERTIES FOLDER "Tests/llcharacter") - # The translation table this parser reads is shipped by newview, and one it - # refuses stops every BVH upload, so the test reads the shipped file itself. - # The joint names a .bvh may use come from the shipped skeleton, and the - # root joint check will not pass without them, so the test reads it too. - al_add_test( - llbvhloader + # The translation table the BVH parser reads is shipped by newview, and one + # it refuses stops every BVH upload, so llbvhloader's tests read the shipped + # file itself. The joint names a .bvh may use come from the shipped + # skeleton, and the root joint check will not pass without them, so the + # tests read it too. + al_add_test_binary( + llcharacter_tests PROJECT llcharacter + SOURCES + tests/llbvhloader_test.cpp + tests/llcharacter_test.cpp + tests/lljoint_test.cpp + tests/llkeyframemotion_test.cpp + tests/llmotioncontroller_test.cpp + tests/llmultigesture_test.cpp + tests/llpose_test.cpp LIBRARIES llcharacter + SUPPORT llcharacter_test_checks DEFINES "AL_SHIPPED_ANIM_INI=\"${CMAKE_CURRENT_SOURCE_DIR}/../newview/app_settings/anim.ini\"" "AL_SHIPPED_AVATAR_SKELETON=\"${CMAKE_CURRENT_SOURCE_DIR}/../newview/character/avatar_skeleton.xml\"" diff --git a/indra/llcharacter/tests/llbvhloader_test.cpp b/indra/llcharacter/tests/llbvhloader_test.cpp index 9aaf370aed..cf246028b9 100644 --- a/indra/llcharacter/tests/llbvhloader_test.cpp +++ b/indra/llcharacter/tests/llbvhloader_test.cpp @@ -39,7 +39,8 @@ #include #include -#include "../test/lltut.h" +#include +#include "altestmatchers.h" namespace { @@ -317,8 +318,6 @@ namespace } } -namespace tut -{ namespace { struct llbvhloader_data @@ -387,13 +386,14 @@ namespace // A translation table, then a BVH, then the animation that comes out. WrittenAnimation build(const std::string& table, const std::vector& lines) { + CAPTURE(table); std::unique_ptr loader = makeLoader(); - ensure_equals("the table is read", readTable(*loader, table), E_ST_OK); - ensure_equals("the BVH is read", load(*loader, lines), E_ST_OK); + REQUIRE(readTable(*loader, table) == E_ST_OK); + REQUIRE(load(*loader, lines) == E_ST_OK); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation can be read back", read_written(bytes, written)); + REQUIRE(read_written(bytes, written)); return written; } @@ -409,57 +409,56 @@ namespace return motion.deserialize(dp, motion.getID()); } }; - typedef test_group llbvhloader_test; - typedef llbvhloader_test::object llbvhloader_object; - tut::llbvhloader_test llbvhloader_testcase("LLBVHLoader"); -} - template<> template<> - void llbvhloader_object::test<1>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a well formed file is read and written as an animation", "[LLBVHLoader]") { // A well formed file, and what it says coming out the other side. std::unique_ptr loader = makeLoader(); - ensure_equals("a well formed BVH is read", load(*loader, sample_lines()), E_ST_OK); + CHECK(load(*loader, sample_lines()) == E_ST_OK); // Four frames, of which the first is a reference and the last is only // interpolated towards, so two of them are played. - ensure_approximately_equals("the duration is the frames that play", - loader->getDuration(), 2.f * 0.033333f, 16); + UNSCOPED_INFO("the duration is the frames that play"); + CHECK_THAT(loader->getDuration(), ll_test::WithinBits(2.f * 0.033333f, 16)); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation it writes can be read back", read_written(bytes, written)); - - ensure_equals("both joints are written", written.mNumJoints, 2u); - ensure_equals("under the names the table gave them", written.mJointNames[0], std::string("mPelvis")); - ensure_equals("and the second the same", written.mJointNames[1], std::string("mTorso")); - ensure("the root's positions are written", written.mNumPosKeys[0] > 0); - ensure_equals("a three channel joint has no positions to write", written.mNumPosKeys[1], 0); - ensure("both joints have rotations", written.mNumRotKeys[0] > 0 && written.mNumRotKeys[1] > 0); - ensure_equals("nothing asked for a constraint", written.mNumConstraints, 0); + REQUIRE(read_written(bytes, written)); + + REQUIRE(written.mNumJoints == 2u); + UNSCOPED_INFO("under the names the table gave them"); + CHECK(written.mJointNames[0] == std::string("mPelvis")); + CHECK(written.mJointNames[1] == std::string("mTorso")); + UNSCOPED_INFO("the root's positions are written"); + CHECK(written.mNumPosKeys[0] > 0); + UNSCOPED_INFO("a three channel joint has no positions to write"); + CHECK(written.mNumPosKeys[1] == 0); + CHECK(written.mNumRotKeys[0] > 0); + CHECK(written.mNumRotKeys[1] > 0); + UNSCOPED_INFO("nothing asked for a constraint"); + CHECK(written.mNumConstraints == 0); // An animation shorter than the ease times it was given has them // scaled to fit rather than being left longer than itself. - ensure("the eases are scaled down to fit", - written.mEaseIn + written.mEaseOut <= written.mDuration + 1e-5f); + UNSCOPED_INFO("the eases are scaled down to fit"); + CHECK(written.mEaseIn + written.mEaseOut <= written.mDuration + 1e-5f); } - template<> template<> - void llbvhloader_object::test<2>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: LLKeyframeMotion reads what the loader writes", "[LLBVHLoader]") { // The bytes a BVH turns into are read by the same code that reads an // animation somebody sends you, so that is what they are given to. std::unique_ptr loader = makeLoader(); - ensure_equals("a well formed BVH is read", load(*loader, sample_lines()), E_ST_OK); + CHECK(load(*loader, sample_lines()) == E_ST_OK); std::vector bytes = write(*loader); LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("what the loader writes is what the reader takes", loadAsMotion(motion, bytes)); - ensure_equals("with both joints", motion.getNumJointMotions(), 2); + UNSCOPED_INFO("what the loader writes is what the reader takes"); + CHECK(loadAsMotion(motion, bytes)); + CHECK(motion.getNumJointMotions() == 2); } - template<> template<> - void llbvhloader_object::test<3>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a malformed hierarchy is refused", "[LLBVHLoader]") { // The hierarchy, one line wrong at a time. auto mutate = [&](const char* what, S32 index, const std::string& text, ELoadStatus expected) @@ -467,7 +466,8 @@ namespace std::vector lines = sample_lines(); lines[index] = text; std::unique_ptr loader = makeLoader(); - ensure_equals(what, load(*loader, lines), expected); + INFO(what); + REQUIRE(load(*loader, lines) == expected); }; mutate("a file that does not say HIERARCHY is refused", @@ -494,7 +494,8 @@ namespace lines[LINE_ROOT] = "ROOT Hips"; std::unique_ptr loader = makeLoader(); loader->makeTranslation("Hips", "mPelvis"); - ensure_equals("another name for the pelvis is a root", load(*loader, lines), E_ST_OK); + UNSCOPED_INFO("another name for the pelvis is a root"); + CHECK(load(*loader, lines) == E_ST_OK); } // The three lines of an end site are stepped over without looking at @@ -505,16 +506,16 @@ namespace // because that is where the iterator asserts. for (S32 length = LINE_END_SITE + 1; length <= LINE_END_SITE + 3; ++length) { + CAPTURE(length); std::vector lines = sample_lines(); lines.resize(length); std::unique_ptr loader = makeLoader(); - ensure_equals("a file that ends inside an end site is refused", - load(*loader, lines), E_ST_EOF); + UNSCOPED_INFO("a file that ends inside an end site is refused"); + REQUIRE(load(*loader, lines) == E_ST_EOF); } } - template<> template<> - void llbvhloader_object::test<4>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a joint takes only three or six channels", "[LLBVHLoader]") { // Three rotations, or three positions and three rotations, is the // whole of what the frame reader takes off a line. Any other count and @@ -524,7 +525,8 @@ namespace std::vector lines = sample_lines(); lines[LINE_JOINT_CHANNELS] = text; std::unique_ptr loader = makeLoader(); - ensure_equals(what, load(*loader, lines), expected); + INFO(what); + REQUIRE(load(*loader, lines) == expected); }; channels("no channels at all is refused", @@ -557,12 +559,12 @@ namespace lines[LINE_FIRST_FRAME + frame] = frame_line(frame, 12); } std::unique_ptr loader = makeLoader(); - ensure_equals("a six channel child is read", load(*loader, lines), E_ST_OK); + UNSCOPED_INFO("a six channel child is read"); + CHECK(load(*loader, lines) == E_ST_OK); } } - template<> template<> - void llbvhloader_object::test<5>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: the rotation order is read from the rotation channel names", "[LLBVHLoader]") { // The rotation order is read as the letter in front of each of three // "rotation" words. @@ -571,7 +573,8 @@ namespace std::vector lines = sample_lines(); lines[LINE_JOINT_CHANNELS] = text; std::unique_ptr loader = makeLoader(); - ensure_equals(what, load(*loader, lines), expected); + INFO(what); + REQUIRE(load(*loader, lines) == expected); }; channels("two rotations where three are needed is refused", @@ -599,8 +602,7 @@ namespace " CHANNELS 3 Xrotation Xrotation Xrotation", E_ST_OK); } - template<> template<> - void llbvhloader_object::test<6>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a bad frame count or frame time is refused", "[LLBVHLoader]") { // The frame count and the frame time, which together are the only // thing every key time is written as a fraction of. @@ -609,7 +611,8 @@ namespace std::vector lines = sample_lines(); lines[index] = text; std::unique_ptr loader = makeLoader(); - ensure_equals(what, load(*loader, lines), expected); + INFO(what); + REQUIRE(load(*loader, lines) == expected); }; mutate("a frame count that is not a number is refused", @@ -645,17 +648,17 @@ namespace std::vector lines = sample_lines(); lines[LINE_FRAME_TIME] = "Frame Time: 1e30"; std::unique_ptr loader = makeLoader(); - ensure_equals("an enormous frame time is read", load(*loader, lines), E_ST_OK); + UNSCOPED_INFO("an enormous frame time is read"); + CHECK(load(*loader, lines) == E_ST_OK); std::vector bytes = write(*loader); LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("but the animation it makes is too long to play", - !loadAsMotion(motion, bytes)); + UNSCOPED_INFO("but the animation it makes is too long to play"); + CHECK_FALSE(loadAsMotion(motion, bytes)); } } - template<> template<> - void llbvhloader_object::test<7>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a malformed frame is refused and extra numbers are dropped", "[LLBVHLoader]") { // The frames themselves. auto mutate = [&](const char* what, S32 index, const std::string& text, ELoadStatus expected) @@ -663,7 +666,8 @@ namespace std::vector lines = sample_lines(); lines[index] = text; std::unique_ptr loader = makeLoader(); - ensure_equals(what, load(*loader, lines), expected); + INFO(what); + REQUIRE(load(*loader, lines) == expected); }; mutate("a frame with fewer numbers than the joints need is refused", @@ -690,13 +694,12 @@ namespace std::vector lines = sample_lines(); lines.pop_back(); std::unique_ptr loader = makeLoader(); - ensure_equals("a file with fewer frames than it claims is refused", - load(*loader, lines), E_ST_EOF); + UNSCOPED_INFO("a file with fewer frames than it claims is refused"); + CHECK(load(*loader, lines) == E_ST_EOF); } } - template<> template<> - void llbvhloader_object::test<8>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: every cut of a file is refused except within its last number", "[LLBVHLoader]") { // Cut anywhere and it is refused rather than half read. Every prefix // of a well formed file, so the cut lands in each line and in the @@ -708,28 +711,31 @@ namespace // between them is where the last number starts. const std::string whole = bvh_text(sample_lines()); const size_t last_number = whole.rfind(' ') + 1; - ensure("the file is worth cutting up", whole.size() > 200); - ensure("the last number was found", last_number > 1 && last_number < whole.size()); + UNSCOPED_INFO("the file is worth cutting up"); + REQUIRE(whole.size() > 200); + CHECK(last_number > 1); + REQUIRE(last_number < whole.size()); std::unique_ptr loader = makeLoader(); for (size_t length = 0; length <= last_number; ++length) { + CAPTURE(length); rearm(*loader); - ensure("a BVH cut short is refused", - loadText(*loader, whole.substr(0, length)) != E_ST_OK); + UNSCOPED_INFO("a BVH cut short is refused"); + REQUIRE(loadText(*loader, whole.substr(0, length)) != E_ST_OK); } for (size_t length = last_number + 1; length <= whole.size(); ++length) { + CAPTURE(length); rearm(*loader); - ensure_equals("a BVH whose last number is merely shorter is whole", - loadText(*loader, whole.substr(0, length)), E_ST_OK); + UNSCOPED_INFO("a BVH whose last number is merely shorter is whole"); + REQUIRE(loadText(*loader, whole.substr(0, length)) == E_ST_OK); } } - template<> template<> - void llbvhloader_object::test<9>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a malformed table is refused and its globals are written", "[LLBVHLoader]") { // The translation table beside the BVH, which is where everything the // BVH itself does not say comes from. @@ -738,7 +744,8 @@ namespace auto table = [&](const char* what, const std::string& text, ELoadStatus expected) { std::unique_ptr loader = makeLoader(); - ensure_equals(what, readTable(*loader, text), expected); + INFO(what); + REQUIRE(readTable(*loader, text) == expected); }; table("an empty table is refused", "", E_ST_EOF); @@ -767,7 +774,7 @@ namespace // the table is read long before the loader knows. { std::unique_ptr loader = makeLoader(); - ensure_equals("the globals are read", readTable(*loader, + CHECK(readTable(*loader, header + "[GLOBALS]\n" "priority = 4\n" @@ -775,43 +782,46 @@ namespace "easein = 0.1 easeInOut\n" "easeout = 0.2 easeInOut\n" "hand = 3\n" - "emote = wave\n"), E_ST_OK); - ensure_equals("and then the BVH", load(*loader, sample_lines()), E_ST_OK); + "emote = wave\n") == E_ST_OK); + CHECK(load(*loader, sample_lines()) == E_ST_OK); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation can be read back", read_written(bytes, written)); - - ensure_equals("the priority is the one the table gave", written.mBasePriority, 4); - ensure_equals("and every joint is given it", written.mJointPriorities[0], 4); - ensure_equals("the hand pose is the one the table gave", written.mHandPose, 3u); - ensure_equals("so is the emote", written.mEmoteName, std::string("wave")); - ensure_equals("the animation loops", written.mLoop, 1); - ensure_approximately_equals("the ease in is not scaled down under a loop", - written.mEaseIn, 0.1f, 16); - ensure_approximately_equals("nor the ease out", written.mEaseOut, 0.2f, 16); - ensure_approximately_equals("the loop starts a quarter of the way in", - written.mLoopInPoint, 0.25f * written.mDuration, 16); - ensure_approximately_equals("and ends three quarters of the way in", - written.mLoopOutPoint, 0.75f * written.mDuration, 16); - ensure("a loop point is somewhere, not nowhere", written.mLoopOutPoint > 0.f); + REQUIRE(read_written(bytes, written)); + + UNSCOPED_INFO("the priority is the one the table gave"); + CHECK(written.mBasePriority == 4); + UNSCOPED_INFO("and every joint is given it"); + CHECK(written.mJointPriorities[0] == 4); + UNSCOPED_INFO("the hand pose is the one the table gave"); + CHECK(written.mHandPose == 3u); + UNSCOPED_INFO("so is the emote"); + CHECK(written.mEmoteName == std::string("wave")); + CHECK(written.mLoop == 1); + UNSCOPED_INFO("the ease in is not scaled down under a loop"); + CHECK_THAT(written.mEaseIn, ll_test::WithinBits(0.1f, 16)); + UNSCOPED_INFO("nor the ease out"); + CHECK_THAT(written.mEaseOut, ll_test::WithinBits(0.2f, 16)); + CHECK_THAT(written.mLoopInPoint, ll_test::WithinBits(0.25f * written.mDuration, 16)); + CHECK_THAT(written.mLoopOutPoint, ll_test::WithinBits(0.75f * written.mDuration, 16)); + UNSCOPED_INFO("a loop point is somewhere, not nowhere"); + CHECK(written.mLoopOutPoint > 0.f); } // Without a table saying otherwise the loop runs the whole animation. { std::unique_ptr loader = makeLoader(); - ensure_equals("a plain BVH is read", load(*loader, sample_lines()), E_ST_OK); + CHECK(load(*loader, sample_lines()) == E_ST_OK); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation can be read back", read_written(bytes, written)); - ensure_equals("it does not loop", written.mLoop, 0); - ensure_approximately_equals("and the loop still ends at the end", - written.mLoopOutPoint, written.mDuration, 16); + REQUIRE(read_written(bytes, written)); + CHECK(written.mLoop == 0); + UNSCOPED_INFO("the loop still ends at the end"); + CHECK_THAT(written.mLoopOutPoint, ll_test::WithinBits(written.mDuration, 16)); } } - template<> template<> - void llbvhloader_object::test<10>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a constraint line is written as given and nothing more", "[LLBVHLoader]") { // A constraint line is read by two formats, the second of which never // touches the target direction. Every byte of a constraint is written @@ -821,62 +831,54 @@ namespace { std::unique_ptr loader = makeLoader(); - ensure_equals("a constraint without a direction is read", readTable(*loader, - header + "constraint = 2 0.0 0.2 0.8 1.0 mFootRight 1 2 3 mGroundPlane 4 5 6\n"), + UNSCOPED_INFO("a constraint without a direction is read"); + CHECK(readTable(*loader, + header + "constraint = 2 0.0 0.2 0.8 1.0 mFootRight 1 2 3 mGroundPlane 4 5 6\n") == E_ST_OK); - ensure_equals("and then the BVH", load(*loader, sample_lines()), E_ST_OK); + CHECK(load(*loader, sample_lines()) == E_ST_OK); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation can be read back", read_written(bytes, written)); - - ensure_equals("the constraint is written", written.mNumConstraints, 1); - ensure_equals("with its chain length", (S32)written.mChainLength, 2); - ensure_equals("as a point constraint", - (S32)written.mConstraintType, (S32)CONSTRAINT_TYPE_POINT); - ensure_equals("anchored where it said", - volume_name(written.mSourceVolume), std::string("mFootRight")); - ensure_equals("and aimed where it said", - volume_name(written.mTargetVolume), std::string("mGroundPlane")); - ensure_equals("the source offset is the one it gave", - written.mSourceOffset, LLVector3(1.f, 2.f, 3.f)); - ensure_equals("and the target offset", - written.mTargetOffset, LLVector3(4.f, 5.f, 6.f)); + REQUIRE(read_written(bytes, written)); + + CHECK(written.mNumConstraints == 1); + CHECK((S32)written.mChainLength == 2); + CHECK((S32)written.mConstraintType == (S32)CONSTRAINT_TYPE_POINT); + CHECK(volume_name(written.mSourceVolume) == std::string("mFootRight")); + CHECK(volume_name(written.mTargetVolume) == std::string("mGroundPlane")); + CHECK(written.mSourceOffset == LLVector3(1.f, 2.f, 3.f)); + CHECK(written.mTargetOffset == LLVector3(4.f, 5.f, 6.f)); // The line never said a direction, so nothing may be written as // one but nothing. - ensure_equals("a direction nobody gave is not a direction", - written.mTargetDir, LLVector3::zero); - ensure_equals("the bytes past the source name are empty", - trailing_zeroes(written.mSourceVolume), 6); - ensure_equals("and the bytes past the target name", - trailing_zeroes(written.mTargetVolume), 4); - - ensure_approximately_equals("the ease in starts where it said", - written.mEaseInStart, 0.f, 16); - ensure_approximately_equals("and stops where it said", - written.mEaseInStop, 0.2f, 16); - ensure_approximately_equals("the ease out starts where it said", - written.mEaseOutStart, 0.8f, 16); - ensure_approximately_equals("and stops where it said", - written.mEaseOutStop, 1.f, 16); + UNSCOPED_INFO("a direction nobody gave is not a direction"); + CHECK(written.mTargetDir == LLVector3::zero); + UNSCOPED_INFO("the bytes past the source name are empty"); + CHECK(trailing_zeroes(written.mSourceVolume) == 6); + UNSCOPED_INFO("and the bytes past the target name"); + CHECK(trailing_zeroes(written.mTargetVolume) == 4); + + CHECK_THAT(written.mEaseInStart, ll_test::WithinBits(0.f, 16)); + CHECK_THAT(written.mEaseInStop, ll_test::WithinBits(0.2f, 16)); + CHECK_THAT(written.mEaseOutStart, ll_test::WithinBits(0.8f, 16)); + CHECK_THAT(written.mEaseOutStop, ll_test::WithinBits(1.f, 16)); } // The longer format does give a direction, and it is normalized. { std::unique_ptr loader = makeLoader(); - ensure_equals("a planar constraint with a direction is read", readTable(*loader, - header + "planar_constraint = 2 0.0 0.2 0.8 1.0 mFootRight 0 0 0 mGroundPlane 0 0 0 0 0 4\n"), + UNSCOPED_INFO("a planar constraint with a direction is read"); + CHECK(readTable(*loader, + header + "planar_constraint = 2 0.0 0.2 0.8 1.0 mFootRight 0 0 0 mGroundPlane 0 0 0 0 0 4\n") == E_ST_OK); - ensure_equals("and then the BVH", load(*loader, sample_lines()), E_ST_OK); + CHECK(load(*loader, sample_lines()) == E_ST_OK); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation can be read back", read_written(bytes, written)); - ensure_equals("as a plane constraint", - (S32)written.mConstraintType, (S32)CONSTRAINT_TYPE_PLANE); - ensure_equals("the direction it gave is normalized", - written.mTargetDir, LLVector3(0.f, 0.f, 1.f)); + REQUIRE(read_written(bytes, written)); + CHECK((S32)written.mConstraintType == (S32)CONSTRAINT_TYPE_PLANE); + UNSCOPED_INFO("the direction it gave is normalized"); + CHECK(written.mTargetDir == LLVector3(0.f, 0.f, 1.f)); } // A name is read into fifteen of the sixteen bytes and stops there. @@ -885,29 +887,30 @@ namespace // the name running past the field. { std::unique_ptr loader = makeLoader(); - ensure_equals("a name as long as the field is read", readTable(*loader, - header + "constraint = 2 0.0 0.2 0.8 1.0 mAJointWithAVer 0 0 0 mGroundPlane 0 0 0\n"), + UNSCOPED_INFO("a name as long as the field is read"); + CHECK(readTable(*loader, + header + "constraint = 2 0.0 0.2 0.8 1.0 mAJointWithAVer 0 0 0 mGroundPlane 0 0 0\n") == E_ST_OK); - ensure_equals("and then the BVH", load(*loader, sample_lines()), E_ST_OK); + CHECK(load(*loader, sample_lines()) == E_ST_OK); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation can be read back", read_written(bytes, written)); - ensure_equals("the name fills the field", - volume_name(written.mSourceVolume), std::string("mAJointWithAVer")); - ensure_equals("with one byte left to end it", - trailing_zeroes(written.mSourceVolume), 1); + REQUIRE(read_written(bytes, written)); + UNSCOPED_INFO("the name fills the field"); + CHECK(volume_name(written.mSourceVolume) == std::string("mAJointWithAVer")); + UNSCOPED_INFO("with one byte left to end it"); + CHECK(trailing_zeroes(written.mSourceVolume) == 1); } { std::unique_ptr loader = makeLoader(); - ensure_equals("a name longer than the field is refused", readTable(*loader, - header + "constraint = 2 0.0 0.2 0.8 1.0 mAJointWithAVeryLongNameIndeed 0 0 0 mGroundPlane 0 0 0\n"), + UNSCOPED_INFO("a name longer than the field is refused"); + CHECK(readTable(*loader, + header + "constraint = 2 0.0 0.2 0.8 1.0 mAJointWithAVeryLongNameIndeed 0 0 0 mGroundPlane 0 0 0\n") == E_ST_NO_CONSTRAINT); } } - template<> template<> - void llbvhloader_object::test<11>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a joint section renames or ignores a joint and adjusts its priority", "[LLBVHLoader]") { // A section named for a joint, and the keys under it that say what // becomes of that joint. None of these were read at all before. @@ -917,52 +920,55 @@ namespace // The table has the last word over the alias map, which is the // whole point of a table: it is read after the aliases now. WrittenAnimation written = build(header + "[abdomen]\noutname = mChest\n", sample_lines()); - ensure_equals("both joints are still written", written.mNumJoints, 2u); - ensure_equals("the root keeps the name the alias gave it", - written.mJointNames[0], std::string("mPelvis")); - ensure_equals("and the table renames the other", - written.mJointNames[1], std::string("mChest")); + UNSCOPED_INFO("both joints are still written"); + REQUIRE(written.mNumJoints == 2u); + UNSCOPED_INFO("the root keeps the name the alias gave it"); + CHECK(written.mJointNames[0] == std::string("mPelvis")); + UNSCOPED_INFO("and the table renames the other"); + CHECK(written.mJointNames[1] == std::string("mChest")); } { // And the reason it has to be read after them: an alias writes the // name and the frame whole, so one applied after a table takes the // table's outname away with it. std::unique_ptr loader = makeLoader(); - ensure_equals("the table is read", - readTable(*loader, header + "[abdomen]\noutname = mChest\n"), E_ST_OK); + CHECK(readTable(*loader, header + "[abdomen]\noutname = mChest\n") == E_ST_OK); loader->makeTranslation("abdomen", "mTorso"); - ensure_equals("the BVH is read", load(*loader, sample_lines()), E_ST_OK); + CHECK(load(*loader, sample_lines()) == E_ST_OK); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation can be read back", read_written(bytes, written)); - ensure_equals("an alias read after a table undoes what it said", - written.mJointNames[1], std::string("mTorso")); + REQUIRE(read_written(bytes, written)); + UNSCOPED_INFO("an alias read after a table undoes what it said"); + CHECK(written.mJointNames[1] == std::string("mTorso")); } { WrittenAnimation written = build(header + "[abdomen]\nignore = true\n", sample_lines()); - ensure_equals("an ignored joint is not written", written.mNumJoints, 1u); - ensure_equals("and the one left is the other one", - written.mJointNames[0], std::string("mPelvis")); + UNSCOPED_INFO("an ignored joint is not written"); + REQUIRE(written.mNumJoints == 1u); + UNSCOPED_INFO("and the one left is the other one"); + CHECK(written.mJointNames[0] == std::string("mPelvis")); } { WrittenAnimation written = build(header + "[abdomen]\nignore = false\n", sample_lines()); - ensure_equals("a joint told not to be ignored is written", written.mNumJoints, 2u); + UNSCOPED_INFO("a joint told not to be ignored is written"); + CHECK(written.mNumJoints == 2u); } { WrittenAnimation written = build( header + "[GLOBALS]\npriority = 3\n[abdomen]\npriority = 2\n", sample_lines()); - ensure_equals("the base priority is the global one", written.mBasePriority, 3); - ensure_equals("a joint with no section of its own takes it", - written.mJointPriorities[0], 3); - ensure_equals("and a joint with one adds its own to it", - written.mJointPriorities[1], 5); + UNSCOPED_INFO("the base priority is the global one"); + CHECK(written.mBasePriority == 3); + UNSCOPED_INFO("a joint with no section of its own takes it"); + CHECK(written.mJointPriorities[0] == 3); + UNSCOPED_INFO("and a joint with one adds its own to it"); + CHECK(written.mJointPriorities[1] == 5); } { WrittenAnimation written = build( header + "[GLOBALS]\npriority = 3\n[abdomen]\npriority = -2\n", sample_lines()); - ensure_equals("a modifier may take it down again", - written.mJointPriorities[1], 1); + UNSCOPED_INFO("a modifier may take it down again"); + CHECK(written.mJointPriorities[1] == 1); } { // Both halves come off a line of the file, so their sum is taken @@ -970,28 +976,28 @@ namespace WrittenAnimation written = build( header + "[GLOBALS]\npriority = 2147483647\n[abdomen]\npriority = 2147483647\n", sample_lines()); - ensure_equals("the global priority is written as it was given", - written.mBasePriority, 2147483647); - ensure_equals("but a joint priority is brought into range", - written.mJointPriorities[1], LL_CHARACTER_MAX_PRIORITY); + UNSCOPED_INFO("the global priority is written as it was given"); + CHECK(written.mBasePriority == 2147483647); + UNSCOPED_INFO("but a joint priority is brought into range"); + CHECK(written.mJointPriorities[1] == LL_CHARACTER_MAX_PRIORITY); } { WrittenAnimation written = build( header + "[GLOBALS]\npriority = -2147483648\n[abdomen]\npriority = -2147483648\n", sample_lines()); - ensure_equals("from the other end too", - written.mJointPriorities[1], (S32)LLJoint::USE_MOTION_PRIORITY); + UNSCOPED_INFO("from the other end too"); + CHECK(written.mJointPriorities[1] == (S32)LLJoint::USE_MOTION_PRIORITY); } { // A key nobody knows is left alone, which is how a table written // for a later viewer still reads on this one. WrittenAnimation written = build(header + "[abdomen]\nwibble = 3\n", sample_lines()); - ensure_equals("an unknown key is ignored", written.mNumJoints, 2u); + UNSCOPED_INFO("an unknown key is ignored"); + CHECK(written.mNumJoints == 2u); } } - template<> template<> - void llbvhloader_object::test<12>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a joint key with no joint or a bad value is refused", "[LLBVHLoader]") { // Every one of those keys with nothing after the equals, or with // something the key does not take. @@ -1000,7 +1006,8 @@ namespace auto table = [&](const char* what, const std::string& text, ELoadStatus expected) { std::unique_ptr loader = makeLoader(); - ensure_equals(what, readTable(*loader, text), expected); + INFO(what); + REQUIRE(readTable(*loader, text) == expected); }; // A key before any section belongs to no joint. @@ -1052,8 +1059,7 @@ namespace joint + "priority = x\n", E_ST_NO_XLT_PRIORITY); } - template<> template<> - void llbvhloader_object::test<13>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: relative and matrix keys change only the keyframes they apply to", "[LLBVHLoader]") { // The keys that change the keyframes rather than the joint. Each is // read against the same animation built without it, because what they @@ -1061,45 +1067,47 @@ namespace const std::string header = "Translations 1.0\n"; const std::vector lines = moving_lines(); const WrittenAnimation plain = build(header, lines); - ensure_equals("the animation to compare against has two joints", plain.mNumJoints, 2u); - ensure("and keys to compare", !plain.mRotKeys[1].empty() && !plain.mPosKeys[0].empty()); + REQUIRE(plain.mNumJoints == 2u); + REQUIRE_FALSE(plain.mRotKeys[1].empty()); + REQUIRE_FALSE(plain.mPosKeys[0].empty()); { // Every position key is moved by this, and five metres is as far // as a position key goes, so a hundred puts them all at the end. WrittenAnimation written = build(header + "[hip]\nrelativepos = 100 0 0\n", lines); - ensure("a relative position moves the position keys", - written.mPosKeys[0] != plain.mPosKeys[0]); - ensure("as far as they go", written.mPosKeys[0][1] == 0); - ensure("the plain one is nowhere near there", plain.mPosKeys[0][1] > 30000); - ensure("and the rotations are left alone", - written.mRotKeys[0] == plain.mRotKeys[0]); + UNSCOPED_INFO("a relative position moves the position keys"); + CHECK(written.mPosKeys[0] != plain.mPosKeys[0]); + UNSCOPED_INFO("as far as they go"); + CHECK(written.mPosKeys[0][1] == 0); + UNSCOPED_INFO("the plain one is nowhere near there"); + CHECK(plain.mPosKeys[0][1] > 30000); + UNSCOPED_INFO("and the rotations are left alone"); + CHECK(written.mRotKeys[0] == plain.mRotKeys[0]); } { WrittenAnimation written = build(header + "[abdomen]\nrelativerot = firstkey\n", lines); - ensure("a rotation taken from the first key moves the rotation keys", - written.mRotKeys[1] != plain.mRotKeys[1]); - ensure("and leaves the other joint alone", - written.mRotKeys[0] == plain.mRotKeys[0]); + UNSCOPED_INFO("a rotation taken from the first key moves the rotation keys"); + CHECK(written.mRotKeys[1] != plain.mRotKeys[1]); + UNSCOPED_INFO("and leaves the other joint alone"); + CHECK(written.mRotKeys[0] == plain.mRotKeys[0]); } { WrittenAnimation written = build( header + "[abdomen]\nframe = 1 0 0, 0 1 0, 0 0 1\n", lines); - ensure("a frame matrix moves the rotation keys", - written.mRotKeys[1] != plain.mRotKeys[1]); - ensure("and leaves the other joint alone", - written.mRotKeys[0] == plain.mRotKeys[0]); + UNSCOPED_INFO("a frame matrix moves the rotation keys"); + CHECK(written.mRotKeys[1] != plain.mRotKeys[1]); + UNSCOPED_INFO("and leaves the other joint alone"); + CHECK(written.mRotKeys[0] == plain.mRotKeys[0]); } { WrittenAnimation written = build( header + "[abdomen]\noffset = 0 1 0, 0 0 1, 1 0 0\n", lines); - ensure("an offset matrix moves the rotation keys", - written.mRotKeys[1] != plain.mRotKeys[1]); + UNSCOPED_INFO("an offset matrix moves the rotation keys"); + CHECK(written.mRotKeys[1] != plain.mRotKeys[1]); } } - template<> template<> - void llbvhloader_object::test<14>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: merge keys fold another joint into a rotation", "[LLBVHLoader]") { // The merge keys, which name another joint whose rotation is folded // into this one. Nothing filled these in before, so the arm of the @@ -1110,22 +1118,22 @@ namespace { WrittenAnimation written = build(header + "[abdomen]\nmergeparent = hip\n", lines); - ensure("a merged parent moves the rotation keys", - written.mRotKeys[1] != plain.mRotKeys[1]); - ensure("and leaves the parent alone", - written.mRotKeys[0] == plain.mRotKeys[0]); + UNSCOPED_INFO("a merged parent moves the rotation keys"); + CHECK(written.mRotKeys[1] != plain.mRotKeys[1]); + UNSCOPED_INFO("and leaves the parent alone"); + CHECK(written.mRotKeys[0] == plain.mRotKeys[0]); } { WrittenAnimation written = build(header + "[hip]\nmergechild = abdomen\n", lines); - ensure("a merged child moves the rotation keys", - written.mRotKeys[0] != plain.mRotKeys[0]); + UNSCOPED_INFO("a merged child moves the rotation keys"); + CHECK(written.mRotKeys[0] != plain.mRotKeys[0]); } { // A name that is nobody is nobody, rather than an error: the table // is written once and the files come and go. WrittenAnimation written = build(header + "[abdomen]\nmergeparent = elbow\n", lines); - ensure("a merge with a joint the file does not have changes nothing", - written.mRotKeys[1] == plain.mRotKeys[1]); + UNSCOPED_INFO("a merge with a joint the file does not have changes nothing"); + CHECK(written.mRotKeys[1] == plain.mRotKeys[1]); } // The merged joint is read one frame behind, and the first frame of a @@ -1138,28 +1146,28 @@ namespace single.resize(LINE_FIRST_FRAME + 1); const WrittenAnimation one_frame = build(header, single); - ensure_equals("a single frame animation is written", one_frame.mNumJoints, 2u); - ensure_equals("with one rotation key", one_frame.mNumRotKeys[1], 1); + UNSCOPED_INFO("a single frame animation is written"); + REQUIRE(one_frame.mNumJoints == 2u); + CHECK(one_frame.mNumRotKeys[1] == 1); WrittenAnimation merged = build(header + "[abdomen]\nmergeparent = hip\n", single); - ensure("a merge with no frame behind it is no merge", - merged.mRotKeys[1] == one_frame.mRotKeys[1]); + UNSCOPED_INFO("a merge with no frame behind it is no merge"); + CHECK(merged.mRotKeys[1] == one_frame.mRotKeys[1]); } } #ifdef AL_SHIPPED_ANIM_INI - template<> template<> - void llbvhloader_object::test<15>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: the shipped translation table reads and sets nothing", "[LLBVHLoader]") { // The table the viewer ships is read by this parser before every BVH // upload, and one it refuses stops the upload, so the file itself is // read here rather than something that looks like it. std::ifstream file(AL_SHIPPED_ANIM_INI); - ensure("the shipped translation table was found", file.good()); + UNSCOPED_INFO("the shipped translation table was found"); + CHECK(file.good()); std::unique_ptr loader = makeLoader(); - ensure_equals("the shipped translation table is read", - loader->loadTranslationTable(file), E_ST_OK); + CHECK(loader->loadTranslationTable(file) == E_ST_OK); // Nothing in it is a setting, so nothing in it may become one by // accident: an example that stopped being a comment would be read. @@ -1169,50 +1177,53 @@ namespace while (std::getline(again, line)) { number++; + CAPTURE(number, line); if (number == 1) { - ensure_equals("the header is the first line of it", - line.compare(0, 16, "Translations 1.0"), 0); + UNSCOPED_INFO("the header is the first line of it"); + REQUIRE(line.compare(0, 16, "Translations 1.0") == 0); continue; } const size_t first = line.find_first_not_of(" \t\r"); - ensure("every line after it is blank or a comment", - first == std::string::npos || line[first] == '#'); + UNSCOPED_INFO("every line after it is blank or a comment"); + REQUIRE((first == std::string::npos || line[first] == '#')); } - ensure("the file has something in it", number > 1); + UNSCOPED_INFO("the file has something in it"); + CHECK(number > 1); } #endif #ifdef AL_SHIPPED_AVATAR_SKELETON - template<> template<> - void llbvhloader_object::test<16>() + TEST_CASE_METHOD(llbvhloader_data, "LLBVHLoader: a BVH reads against the aliases of the shipped skeleton", "[LLBVHLoader]") { // Every joint name a .bvh is allowed to use comes from the shipped // skeleton, and the root joint check turns away any file whose root is // not the pelvis under one of them. Nothing else stands between an // edit to that file and every BVH upload failing, so it is read here. LLXMLNodePtr root; - ensure("the shipped skeleton was read", - LLXMLNode::parseFile(AL_SHIPPED_AVATAR_SKELETON, root, nullptr)); - ensure("and it has a root", root.notNull()); + UNSCOPED_INFO("the shipped skeleton was read"); + REQUIRE(LLXMLNode::parseFile(AL_SHIPPED_AVATAR_SKELETON, root, nullptr)); + REQUIRE(root.notNull()); alias_map_t aliases; std::vector claimed_twice; collect_bone_aliases(root, aliases, claimed_twice); - ensure("the skeleton has its bones", aliases.size() > 100); + UNSCOPED_INFO("the skeleton has its bones"); + REQUIRE(aliases.size() > 100); // The name the BVH reader insists the root joint answers to. alias_map_t::iterator hip = aliases.find("hip"); - ensure("the skeleton still calls the pelvis hip", hip != aliases.end()); - ensure_equals("and means the pelvis by it", hip->second, std::string("mPelvis")); + UNSCOPED_INFO("the skeleton still calls the pelvis hip"); + REQUIRE(hip != aliases.end()); + UNSCOPED_INFO("and means the pelvis by it"); + CHECK(hip->second == std::string("mPelvis")); // A bone claiming a name another bone already has takes it, and only // says so in the log. - ensure_equals("no alias is claimed by two bones", - claimed_twice.empty() ? std::string() : claimed_twice.front(), - std::string()); + UNSCOPED_INFO("no alias is claimed by two bones"); + CHECK((claimed_twice.empty() ? std::string() : claimed_twice.front()) == std::string()); // The whole map through the whole reader, which is what the upload // does with it. @@ -1223,17 +1234,16 @@ namespace loader->makeTranslation(alias, joint); } - ensure_equals("a BVH is read against the shipped skeleton", - load(*loader, sample_lines()), E_ST_OK); + UNSCOPED_INFO("a BVH is read against the shipped skeleton"); + CHECK(load(*loader, sample_lines()) == E_ST_OK); std::vector bytes = write(*loader); WrittenAnimation written; - ensure("the animation can be read back", read_written(bytes, written)); - ensure_equals("both joints are written", written.mNumJoints, 2u); - ensure_equals("the root under the name the skeleton gives it", - written.mJointNames[0], std::string("mPelvis")); - ensure_equals("and the other one too", - written.mJointNames[1], std::string("mTorso")); + REQUIRE(read_written(bytes, written)); + REQUIRE(written.mNumJoints == 2u); + UNSCOPED_INFO("the root under the name the skeleton gives it"); + CHECK(written.mJointNames[0] == std::string("mPelvis")); + CHECK(written.mJointNames[1] == std::string("mTorso")); } #endif } diff --git a/indra/llcharacter/tests/llcharacter_test.cpp b/indra/llcharacter/tests/llcharacter_test.cpp index ab4dd2b519..8ef8dd5bb1 100644 --- a/indra/llcharacter/tests/llcharacter_test.cpp +++ b/indra/llcharacter/tests/llcharacter_test.cpp @@ -41,14 +41,17 @@ #include #include -#include "../test/lltut.h" +#include +#include +#include "altestmatchers.h" namespace { // q and -q are the same rotation, so compare through the absolute dot product. - void ensure_quat_equals(const char* msg, const LLQuaternion& actual, const LLQuaternion& expected) + void require_same_rotation(const char* what, const LLQuaternion& actual, const LLQuaternion& expected) { - tut::ensure_approximately_equals_range(msg, fabsf(dot(actual, expected)), 1.f, 1e-5f); + INFO(what); + REQUIRE_THAT(fabsf(dot(actual, expected)), Catch::Matchers::WithinAbs(1.f, 1e-5f)); } } @@ -105,8 +108,6 @@ namespace }; } -namespace tut -{ namespace { struct llcharacter_data @@ -121,7 +122,7 @@ namespace { mParamInfos.push_back(std::make_unique(id, name, sex)); ALTestVisualParam* param = new ALTestVisualParam(); - ensure("the parameter takes its info", param->setInfo(mParamInfos.back().get())); + REQUIRE(param->setInfo(mParamInfos.back().get())); mCharacter.addVisualParam(param); return param; } @@ -131,10 +132,10 @@ namespace ALTestMotion* startFreshMotion(LLUUID& id, LLMotionConstructor create = ALTestMotion::create) { id = LLUUID::generateNewID(); - ensure("registerMotion", mCharacter.registerMotion(id, create)); - ensure("startMotion", mCharacter.startMotion(id)); + REQUIRE(mCharacter.registerMotion(id, create)); + REQUIRE(mCharacter.startMotion(id)); LLMotion* motion = mCharacter.findMotion(id); - ensure("started motion is findable", motion != nullptr); + REQUIRE(motion != nullptr); return static_cast(motion); } @@ -143,7 +144,7 @@ namespace void animateJoint(ALTestMotion* motion, const char* joint_name) { LLJoint* joint = mCharacter.getJoint(joint_name); - ensure("joint resolves", joint != nullptr); + REQUIRE(joint != nullptr); motion->addJoint(joint, LLJointState::ROT); } @@ -161,73 +162,69 @@ namespace return mCharacter.getMotionController().getActiveMotions(blend_type); } }; - typedef test_group llcharacter_test; - typedef llcharacter_test::object llcharacter_object; - tut::llcharacter_test llcharacter_testcase("LLCharacter"); -} - template<> template<> - void llcharacter_object::test<1>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: joints resolve by name and index with their numbers and parents", "[LLCharacter]") { LLJoint* pelvis = mCharacter.getJoint("mPelvis"); - ensure("pelvis resolves", pelvis != nullptr); - ensure_equals("pelvis joint number", pelvis->getJointNum(), 0); - ensure("pelvis hangs off the root", pelvis->getParent() == mCharacter.getRootJoint()); + REQUIRE(pelvis != nullptr); + CHECK(pelvis->getJointNum() == 0); + CHECK(pelvis->getParent() == mCharacter.getRootJoint()); LLJoint* head = mCharacter.getJoint("mHead"); - ensure("head resolves", head != nullptr); - ensure_equals("head joint number", head->getJointNum(), 4); - ensure_equals("head parent", head->getParent()->getName(), std::string("mNeck")); + REQUIRE(head != nullptr); + CHECK(head->getJointNum() == 4); + CHECK(head->getParent()->getName() == std::string("mNeck")); - ensure("character joint by index", mCharacter.getCharacterJoint(2) == mCharacter.getJoint("mChest")); - ensure("unknown joint is null", mCharacter.getJoint("mNope") == nullptr); + CHECK(mCharacter.getCharacterJoint(2) == mCharacter.getJoint("mChest")); + CHECK(mCharacter.getJoint("mNope") == nullptr); } - template<> template<> - void llcharacter_object::test<2>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: registerMotion refuses duplicates and createMotion builds one instance", "[LLCharacter]") { LLUUID id = LLUUID::generateNewID(); - ensure("registerMotion accepts a new id", mCharacter.registerMotion(id, ALTestMotion::create)); - ensure("registerMotion refuses a duplicate", !mCharacter.registerMotion(id, ALTestMotion::create)); + CHECK(mCharacter.registerMotion(id, ALTestMotion::create)); + CHECK_FALSE(mCharacter.registerMotion(id, ALTestMotion::create)); LLMotion* motion = mCharacter.createMotion(id); - ensure("createMotion builds an instance", motion != nullptr); - ensure("findMotion returns the same instance", mCharacter.findMotion(id) == motion); - ensure("createMotion again returns the same instance", mCharacter.createMotion(id) == motion); - ensure_equals("onInitialize ran once", static_cast(motion)->mInitializeCount, 1); - ensure("created motion is not active", !mCharacter.isMotionActive(id)); + REQUIRE(motion != nullptr); + CHECK(mCharacter.findMotion(id) == motion); + CHECK(mCharacter.createMotion(id) == motion); + CHECK(static_cast(motion)->mInitializeCount == 1); + CHECK_FALSE(mCharacter.isMotionActive(id)); } - template<> template<> - void llcharacter_object::test<3>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: a started motion updates on normal updates until stopped", "[LLCharacter]") { LLUUID id; ALTestMotion* motion = startFreshMotion(id); - ensure("motion is active", mCharacter.isMotionActive(id)); - ensure_equals("onActivate ran once", motion->mActivateCount, 1); - ensure_equals("activation primes with one onUpdate", motion->mUpdateCount, 1); + CHECK(mCharacter.isMotionActive(id)); + CHECK(motion->mActivateCount == 1); + UNSCOPED_INFO("activation primes with one onUpdate"); + CHECK(motion->mUpdateCount == 1); const LLQuaternion rot(0.7f, LLVector3::y_axis); LLJoint* pelvis = mCharacter.getJoint("mPelvis"); motion->addJoint(pelvis, LLJointState::ROT)->setRotation(rot); mCharacter.updateMotions(LLCharacter::HIDDEN_UPDATE); - ensure_equals("hidden update does not run onUpdate", motion->mUpdateCount, 1); + UNSCOPED_INFO("hidden update does not run onUpdate"); + CHECK(motion->mUpdateCount == 1); mCharacter.updateMotions(LLCharacter::NORMAL_UPDATE); - ensure_equals("normal update runs onUpdate", motion->mUpdateCount, 2); - ensure_quat_equals("normal update blends the pose into the skeleton", pelvis->getRotation(), rot); + UNSCOPED_INFO("normal update runs onUpdate"); + CHECK(motion->mUpdateCount == 2); + require_same_rotation("normal update blends the pose into the skeleton", pelvis->getRotation(), rot); - ensure("stopMotion", mCharacter.stopMotion(id, true)); - ensure("stopped motion is inactive", !mCharacter.isMotionActive(id)); - ensure_equals("onDeactivate ran once", motion->mDeactivateCount, 1); + CHECK(mCharacter.stopMotion(id, true)); + CHECK_FALSE(mCharacter.isMotionActive(id)); + CHECK(motion->mDeactivateCount == 1); mCharacter.updateMotions(LLCharacter::NORMAL_UPDATE); - ensure_equals("inactive motion is not updated", motion->mUpdateCount, 2); + UNSCOPED_INFO("inactive motion is not updated"); + CHECK(motion->mUpdateCount == 2); } - template<> template<> - void llcharacter_object::test<4>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: a motion whose onUpdate returns false is stopped", "[LLCharacter]") { // A motion that reports completion from onUpdate is stopped by the controller. LLUUID id; @@ -235,11 +232,10 @@ namespace motion->mUpdateResult = false; mCharacter.updateMotions(LLCharacter::NORMAL_UPDATE); - ensure("self-completing motion is marked stopped", motion->isStopped()); + CHECK(motion->isStopped()); } - template<> template<> - void llcharacter_object::test<5>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: motions are listed newest first and additive ones update first", "[LLCharacter]") { // The playing motions are kept newest first in a list per blend type, // and an update walks the whole additive list before the normal one: @@ -257,24 +253,31 @@ namespace const LLMotionController::motion_list_t& normals = activeMotions(LLMotion::NORMAL_BLEND); const LLMotionController::motion_list_t& additives = activeMotions(LLMotion::ADDITIVE_BLEND); - ensure_equals("two normal motions", normals.size(), 2u); - ensure("normal list is newest first", normals[0] == normal_second && normals[1] == normal_first); - ensure_equals("two additive motions", additives.size(), 2u); - ensure("additive list is newest first", additives[0] == additive_second && additives[1] == additive_first); + REQUIRE(normals.size() == 2u); + CHECK(normals[0] == normal_second); + CHECK(normals[1] == normal_first); + REQUIRE(additives.size() == 2u); + CHECK(additives[0] == additive_second); + CHECK(additives[1] == additive_first); mCharacter.updateMotions(LLCharacter::NORMAL_UPDATE); - ensure("newest additive motion is updated first", additive_second->mUpdateSerial < additive_first->mUpdateSerial); - ensure("the additive pass finishes before the normal pass begins", additive_first->mUpdateSerial < normal_second->mUpdateSerial); - ensure("newest normal motion is updated first", normal_second->mUpdateSerial < normal_first->mUpdateSerial); - - ensure("stopMotion", mCharacter.stopMotion(additive_first_id, true)); - ensure_equals("stopping an additive motion shortens the additive list", additives.size(), 1u); - ensure("the other additive motion stays", additives[0] == additive_second); - ensure_equals("and leaves the normal list alone", normals.size(), 2u); + UNSCOPED_INFO("newest additive motion is updated first"); + CHECK(additive_second->mUpdateSerial < additive_first->mUpdateSerial); + UNSCOPED_INFO("the additive pass finishes before the normal pass begins"); + CHECK(additive_first->mUpdateSerial < normal_second->mUpdateSerial); + UNSCOPED_INFO("newest normal motion is updated first"); + CHECK(normal_second->mUpdateSerial < normal_first->mUpdateSerial); + + CHECK(mCharacter.stopMotion(additive_first_id, true)); + UNSCOPED_INFO("stopping an additive motion shortens the additive list"); + REQUIRE(additives.size() == 1u); + UNSCOPED_INFO("the other additive motion stays"); + CHECK(additives[0] == additive_second); + UNSCOPED_INFO("and leaves the normal list alone"); + CHECK(normals.size() == 2u); } - template<> template<> - void llcharacter_object::test<6>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: a motion leaving the list mid-update skips no other motion", "[LLCharacter]") { // A motion that finishes easing out takes itself off the list while // the list is being walked. The older motion behind it moves into its @@ -286,7 +289,7 @@ namespace animateJoint(oldest, "mPelvis"); animateJoint(ending, "mTorso"); animateJoint(newest, "mChest"); - ensure("stopMotion", mCharacter.stopMotion(ending_id, false)); + CHECK(mCharacter.stopMotion(ending_id, false)); S32 frames = 0; while (mCharacter.isMotionActive(ending_id) && frames < 20) @@ -294,18 +297,23 @@ namespace runFrame(); ++frames; } - ensure("the ending motion left the list", !mCharacter.isMotionActive(ending_id)); - ensure_equals("the ending motion was deactivated once", ending->mDeactivateCount, 1); - ensure_equals("the motion behind it was updated every frame", oldest->mUpdateCount, 1 + frames); - ensure_equals("the motion ahead of it was updated every frame", newest->mUpdateCount, 1 + frames); + CHECK_FALSE(mCharacter.isMotionActive(ending_id)); + CHECK(ending->mDeactivateCount == 1); + UNSCOPED_INFO("the motion behind it was updated every frame"); + CHECK(oldest->mUpdateCount == 1 + frames); + UNSCOPED_INFO("the motion ahead of it was updated every frame"); + CHECK(newest->mUpdateCount == 1 + frames); const LLMotionController::motion_list_t& normals = activeMotions(LLMotion::NORMAL_BLEND); - ensure_equals("two motions remain", normals.size(), 2u); - ensure("remaining motions are still newest first", normals[0] == newest && normals[1] == oldest); + REQUIRE(normals.size() == 2u); + { + INFO("remaining motions are still newest first"); + CHECK(normals[0] == newest); + CHECK(normals[1] == oldest); + } } - template<> template<> - void llcharacter_object::test<7>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: the pixel area is asked once per pass and not per motion", "[LLCharacter]") { // The LOD test compares every motion against the character's pixel // area, which is the same for all of them and a virtual call. It is @@ -323,11 +331,10 @@ namespace animateJoint(startFreshMotion(id, ALTestMotion::createAdditive), "mNeck"); mCharacter.mPixelAreaQueries = 0; mCharacter.updateMotions(LLCharacter::NORMAL_UPDATE); - ensure_equals("pixel area is not asked once per motion", mCharacter.mPixelAreaQueries, queries_with_one_motion); + CHECK(mCharacter.mPixelAreaQueries == queries_with_one_motion); } - template<> template<> - void llcharacter_object::test<8>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: flushAllMotions restarts normal and additive motions", "[LLCharacter]") { // A flush restarts every playing motion, whichever list it was on. LLUUID normal_id, additive_id; @@ -335,14 +342,13 @@ namespace startFreshMotion(additive_id, ALTestMotion::createAdditive); mCharacter.flushAllMotions(); - ensure("normal motion restarted", mCharacter.isMotionActive(normal_id)); - ensure("additive motion restarted", mCharacter.isMotionActive(additive_id)); - ensure_equals("one normal motion", activeMotions(LLMotion::NORMAL_BLEND).size(), 1u); - ensure_equals("one additive motion", activeMotions(LLMotion::ADDITIVE_BLEND).size(), 1u); + CHECK(mCharacter.isMotionActive(normal_id)); + CHECK(mCharacter.isMotionActive(additive_id)); + CHECK(activeMotions(LLMotion::NORMAL_BLEND).size() == 1u); + CHECK(activeMotions(LLMotion::ADDITIVE_BLEND).size() == 1u); } - template<> template<> - void llcharacter_object::test<9>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: a hidden update drops every stopped motion in one pass", "[LLCharacter]") { // Hidden, the controller drops every stopped motion in one pass, and // a motion leaving the list must not hide the stopped one behind it. @@ -350,18 +356,18 @@ namespace startFreshMotion(oldest_id); startFreshMotion(middle_id); startFreshMotion(newest_id); - ensure("stop middle", mCharacter.stopMotion(middle_id, false)); - ensure("stop oldest", mCharacter.stopMotion(oldest_id, false)); + CHECK(mCharacter.stopMotion(middle_id, false)); + CHECK(mCharacter.stopMotion(oldest_id, false)); mCharacter.updateMotions(LLCharacter::HIDDEN_UPDATE); - ensure("the stopped middle motion left the list", !mCharacter.isMotionActive(middle_id)); - ensure("the stopped motion behind it left the list too", !mCharacter.isMotionActive(oldest_id)); - ensure("the running motion stayed", mCharacter.isMotionActive(newest_id)); - ensure_equals("one motion remains", activeMotions(LLMotion::NORMAL_BLEND).size(), 1u); + CHECK_FALSE(mCharacter.isMotionActive(middle_id)); + UNSCOPED_INFO("the stopped motion behind it left the list too"); + CHECK_FALSE(mCharacter.isMotionActive(oldest_id)); + CHECK(mCharacter.isMotionActive(newest_id)); + CHECK(activeMotions(LLMotion::NORMAL_BLEND).size() == 1u); } - template<> template<> - void llcharacter_object::test<10>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: setTimeStep snaps only a stopped motion's stop time", "[LLCharacter]") { // Moving onto the coarse clock snaps every playing motion's timestamps // to the quantum. A running motion has no stop time to snap, and @@ -372,21 +378,24 @@ namespace ALTestMotion* running = startFreshMotion(running_id); ALTestMotion* stopped = startFreshMotion(stopped_id); runFrame(); - ensure("stopMotion", mCharacter.stopMotion(stopped_id, false)); + CHECK(mCharacter.stopMotion(stopped_id, false)); stopped->mStopTimeCalls = 0; running->mStopTimeCalls = 0; mCharacter.getMotionController().setTimeStep(0.25f); - ensure("the running motion is still running", !running->isStopped()); - ensure_equals("its stop time was never set", running->mStopTimeCalls, 0); - ensure_equals("and it still reads as never stopped", running->getStopTime(), 0.0); - ensure("a motion that never ends keeps its open send-stop time", running->sendStopTimestamp() == F32_MAX); - ensure("the stopped motion is still stopped", stopped->isStopped()); - ensure_equals("its stop time sits on the quantum", fmod(stopped->getStopTime(), 0.25), 0.0); + CHECK_FALSE(running->isStopped()); + UNSCOPED_INFO("its stop time was never set"); + CHECK(running->mStopTimeCalls == 0); + UNSCOPED_INFO("and it still reads as never stopped"); + CHECK(running->getStopTime() == 0.0); + UNSCOPED_INFO("a motion that never ends keeps its open send-stop time"); + CHECK(running->sendStopTimestamp() == F32_MAX); + CHECK(stopped->isStopped()); + UNSCOPED_INFO("its stop time sits on the quantum"); + CHECK(fmod(stopped->getStopTime(), 0.25) == 0.0); } - template<> template<> - void llcharacter_object::test<11>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: a motion started during an update waits for the next frame", "[LLCharacter]") { // A motion's update can start another motion -- an emote's does when // it finds the sit animation is for the wrong sex -- and the new one @@ -407,42 +416,53 @@ namespace newest->mEaseInDuration = 10.f; const LLUUID started_id = LLUUID::generateNewID(); - ensure("registerMotion", mCharacter.registerMotion(started_id, ALTestMotion::create)); + CHECK(mCharacter.registerMotion(started_id, ALTestMotion::create)); middle->mStartOnUpdate = started_id; mCharacter.updateMotions(LLCharacter::NORMAL_UPDATE); - ensure("the started motion is playing", mCharacter.isMotionActive(started_id)); - ensure_equals("the motion ahead of the starter was updated once", newest->mUpdateCount, 2); - ensure_equals("the starter was updated once", middle->mUpdateCount, 2); - ensure_equals("the motion behind the starter was updated once", oldest->mUpdateCount, 2); + CHECK(mCharacter.isMotionActive(started_id)); + UNSCOPED_INFO("the motion ahead of the starter was updated once"); + CHECK(newest->mUpdateCount == 2); + UNSCOPED_INFO("the starter was updated once"); + CHECK(middle->mUpdateCount == 2); + UNSCOPED_INFO("the motion behind the starter was updated once"); + CHECK(oldest->mUpdateCount == 2); ALTestMotion* started = static_cast(mCharacter.findMotion(started_id)); - ensure_equals("the new motion had only its activation update", started->mUpdateCount, 1); + UNSCOPED_INFO("the new motion had only its activation update"); + CHECK(started->mUpdateCount == 1); const LLMotionController::motion_list_t& normals = activeMotions(LLMotion::NORMAL_BLEND); - ensure_equals("four motions listed", normals.size(), 4u); - ensure("newest first, the started one ahead of them all", - normals[0] == started && normals[1] == newest && normals[2] == middle && normals[3] == oldest); + REQUIRE(normals.size() == 4u); + { + INFO("newest first, the started one ahead of them all"); + CHECK(normals[0] == started); + CHECK(normals[1] == newest); + CHECK(normals[2] == middle); + CHECK(normals[3] == oldest); + } } - template<> template<> - void llcharacter_object::test<12>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: animation data is a slot and the hand pose request a copy", "[LLCharacter]") { // The values motions hand one another are slots on the character: set // by whoever owns the value, empty when nobody does. F32 speed = 3.f; - ensure("an unset channel reads empty", mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_WALK_SPEED) == nullptr); + CHECK(mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_WALK_SPEED) == nullptr); mCharacter.setAnimationData(LLCharacter::ANIM_CHANNEL_WALK_SPEED, &speed); - ensure("a set channel reads back what was set", mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_WALK_SPEED) == &speed); - ensure("the other channels are untouched", - mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_POINT_AT_POINT) == nullptr - && mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_LOOK_AT_POINT) == nullptr); + CHECK(mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_WALK_SPEED) == &speed); + { + INFO("the other channels are untouched"); + CHECK(mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_POINT_AT_POINT) == nullptr); + CHECK(mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_LOOK_AT_POINT) == nullptr); + } mCharacter.removeAnimationData(LLCharacter::ANIM_CHANNEL_WALK_SPEED); - ensure("a removed channel reads empty", mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_WALK_SPEED) == nullptr); + CHECK(mCharacter.getAnimationData(LLCharacter::ANIM_CHANNEL_WALK_SPEED) == nullptr); // The hand pose is not a slot but a copy, so the request stands after // whatever it was read from has changed or gone, and it is dropped // with the motions on a flush. - ensure("no hand pose is asked for to begin with", !mCharacter.hasHandPoseRequest()); + UNSCOPED_INFO("no hand pose is asked for to begin with"); + CHECK_FALSE(mCharacter.hasHandPoseRequest()); { S32 pose = 1; S32 priority = 2; @@ -450,19 +470,21 @@ namespace pose = 5; priority = 4; } - ensure("a request stands", mCharacter.hasHandPoseRequest()); - ensure_equals("with the pose asked for", mCharacter.getHandPoseRequest(), 1); - ensure_equals("at the priority asked at", mCharacter.getHandPoseRequestPriority(), 2); + UNSCOPED_INFO("a request stands"); + CHECK(mCharacter.hasHandPoseRequest()); + UNSCOPED_INFO("with the pose asked for"); + CHECK(mCharacter.getHandPoseRequest() == 1); + UNSCOPED_INFO("at the priority asked at"); + CHECK(mCharacter.getHandPoseRequestPriority() == 2); mCharacter.clearHandPoseRequest(); - ensure("a cleared request is gone", !mCharacter.hasHandPoseRequest()); + CHECK_FALSE(mCharacter.hasHandPoseRequest()); mCharacter.requestHandPose(1, 2); mCharacter.flushAllMotions(); - ensure("a flush drops the hand pose", !mCharacter.hasHandPoseRequest()); + CHECK_FALSE(mCharacter.hasHandPoseRequest()); } - template<> template<> - void llcharacter_object::test<13>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: a motion stopped while easing in never rises in weight", "[LLCharacter]") { // A motion stopped before it finished easing in gets one more update, // to hand it the stop time it never saw, and then deactivates. It @@ -478,28 +500,29 @@ namespace runFrame(); runFrame(); const F32 easing_in = motion->getPose()->getWeight(); - ensure("the motion is still easing in", easing_in < 0.5f); + UNSCOPED_INFO("the motion is still easing in"); + CHECK(easing_in < 0.5f); mCharacter.stopMotion(id); runFrame(); - ensure("the last update does not raise the weight", - motion->getPose()->getWeight() <= easing_in); - ensure("the motion is still around for its last update", - activeMotions(LLMotion::NORMAL_BLEND).size() == 1u); + UNSCOPED_INFO("the last update does not raise the weight"); + CHECK(motion->getPose()->getWeight() <= easing_in); + UNSCOPED_INFO("the motion is still around for its last update"); + CHECK(activeMotions(LLMotion::NORMAL_BLEND).size() == 1u); runFrame(); - ensure("and is gone the frame after", activeMotions(LLMotion::NORMAL_BLEND).empty()); + UNSCOPED_INFO("and is gone the frame after"); + CHECK(activeMotions(LLMotion::NORMAL_BLEND).empty()); } - template<> template<> - void llcharacter_object::test<14>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: a motion still easing in does not mask the one under it", "[LLCharacter]") { // The mask that lets a motion's joints go unread is built from motions // at full weight and no others. One still easing in has claimed // nothing, so what is under it is still blended and still shows. LLJoint* joint = mCharacter.getJoint("mPelvis"); - ensure("the joint resolves", joint != nullptr); + REQUIRE(joint != nullptr); const LLQuaternion under_rot(0.9f, LLVector3::x_axis); const LLQuaternion over_rot(-1.4f, LLVector3::y_axis); @@ -518,22 +541,22 @@ namespace runFrame(); runFrame(); - ensure("the one easing in has not reached full weight", - over->getPose()->getWeight() < 0.5f); - ensure("so the one underneath is what the joint shows", - fabsf(dot(joint->getRotation(), under_rot)) > 0.99f); + UNSCOPED_INFO("the one easing in has not reached full weight"); + CHECK(over->getPose()->getWeight() < 0.5f); + UNSCOPED_INFO("so the one underneath is what the joint shows"); + CHECK(fabsf(dot(joint->getRotation(), under_rot)) > 0.99f); // and once it is all the way in, it owns the joint over->mEaseInDuration = 0.f; runFrame(); - ensure_equals("it is at full weight now", over->getPose()->getWeight(), 1.f); - ensure("and the joint is what it asked for", - fabsf(dot(joint->getRotation(), over_rot)) > 0.99f); + UNSCOPED_INFO("it is at full weight now"); + CHECK(over->getPose()->getWeight() == 1.f); + UNSCOPED_INFO("and the joint is what it asked for"); + CHECK(fabsf(dot(joint->getRotation(), over_rot)) > 0.99f); } - template<> template<> - void llcharacter_object::test<15>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: updateVisualParams applies only parameters whose weight moved", "[LLCharacter]") { // The sweep applies a parameter whose weight has moved and leaves the // rest alone, and says whether it did anything. @@ -542,29 +565,35 @@ namespace // A parameter arrives at its default weight, which is the weight it // was last applied at, so there is nothing to do for either of them. - ensure("a sweep with nothing to do says so", !mCharacter.updateVisualParams()); - ensure_equals("and applies nothing", first->mApplied, 0); - ensure_equals("to either of them", second->mApplied, 0); + UNSCOPED_INFO("a sweep with nothing to do says so"); + CHECK_FALSE(mCharacter.updateVisualParams()); + UNSCOPED_INFO("and applies nothing"); + CHECK(first->mApplied == 0); + CHECK(second->mApplied == 0); first->setWeight(0.5f); - ensure("a sweep after a weight moves says so", mCharacter.updateVisualParams()); - ensure_equals("the parameter that moved is applied", first->mApplied, 1); - ensure_equals("and the one that did not is not", second->mApplied, 0); - - ensure("and once it has settled again there is nothing to do", - !mCharacter.updateVisualParams()); - ensure_equals("with nothing applied a second time", first->mApplied, 1); + UNSCOPED_INFO("a sweep after a weight moves says so"); + CHECK(mCharacter.updateVisualParams()); + UNSCOPED_INFO("the parameter that moved is applied"); + CHECK(first->mApplied == 1); + UNSCOPED_INFO("and the one that did not is not"); + CHECK(second->mApplied == 0); + + UNSCOPED_INFO("and once it has settled again there is nothing to do"); + CHECK_FALSE(mCharacter.updateVisualParams()); + UNSCOPED_INFO("with nothing applied a second time"); + CHECK(first->mApplied == 1); // Both of them at once. first->setWeight(0.25f); second->setWeight(0.75f); - ensure("a sweep with two to do says so", mCharacter.updateVisualParams()); - ensure_equals("and applies the first", first->mApplied, 2); - ensure_equals("and the second", second->mApplied, 1); + UNSCOPED_INFO("a sweep with two to do says so"); + CHECK(mCharacter.updateVisualParams()); + CHECK(first->mApplied == 2); + CHECK(second->mApplied == 1); } - template<> template<> - void llcharacter_object::test<16>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: updateVisualParams skips animating and other-sex parameters", "[LLCharacter]") { // A parameter that is animating is left to the animation, and one for // the other sex is applied at its default rather than its weight. @@ -577,23 +606,24 @@ namespace animating->setAnimating(true); animating->setWeight(0.5f); - ensure("an animating parameter is not swept", !mCharacter.updateVisualParams()); - ensure_equals("and not applied", animating->mApplied, settled); + UNSCOPED_INFO("an animating parameter is not swept"); + CHECK_FALSE(mCharacter.updateVisualParams()); + CHECK(animating->mApplied == settled); animating->setAnimating(false); - ensure("once it stops animating the sweep takes it", mCharacter.updateVisualParams()); + UNSCOPED_INFO("once it stops animating the sweep takes it"); + CHECK(mCharacter.updateVisualParams()); // The female parameter settled at its default under a male sex, so // moving its weight changes nothing the sweep can see. const S32 female_settled = female->mApplied; female->setWeight(0.75f); - ensure("a parameter for the other sex has nothing to apply", - !mCharacter.updateVisualParams()); - ensure_equals("and is not applied", female->mApplied, female_settled); + UNSCOPED_INFO("a parameter for the other sex has nothing to apply"); + CHECK_FALSE(mCharacter.updateVisualParams()); + CHECK(female->mApplied == female_settled); } - template<> template<> - void llcharacter_object::test<17>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: visual parameters are found by name in any case and walked by id", "[LLCharacter]") { // Parameters are found by name whatever case the name is asked in, // and the sweep walks them in the order of their ids however they @@ -602,7 +632,7 @@ namespace addParam(10, "First"); addParam(20, "Second"); - ensure_equals("every parameter is counted", mCharacter.getVisualParamCount(), 3); + CHECK(mCharacter.getVisualParamCount() == 3); std::vector ids; for (LLVisualParam* param = mCharacter.getFirstVisualParam(); @@ -611,65 +641,56 @@ namespace { ids.push_back(param->getID()); } - ensure_equals("all three are walked", ids.size(), 3u); - ensure_equals("in id order, not the order they arrived", ids[0], 10); - ensure_equals("second", ids[1], 20); - ensure_equals("third", ids[2], 30); + REQUIRE(ids.size() == 3u); + UNSCOPED_INFO("in id order, not the order they arrived"); + CHECK(ids[0] == 10); + CHECK(ids[1] == 20); + CHECK(ids[2] == 30); LLVisualParam* by_name = mCharacter.getVisualParam("Second"); - ensure("a parameter is found by its name", by_name != nullptr); - ensure_equals("and it is the right one", by_name->getID(), 20); + REQUIRE(by_name != nullptr); + CHECK(by_name->getID() == 20); - ensure_equals("the name is matched without regard to case", - mCharacter.getVisualParam("sEcOnD"), by_name); - ensure_equals("in either direction", - mCharacter.getVisualParam("SECOND"), by_name); - ensure("a name nobody has is not found", - mCharacter.getVisualParam("NotAParameter") == nullptr); + CHECK(mCharacter.getVisualParam("sEcOnD") == by_name); + CHECK(mCharacter.getVisualParam("SECOND") == by_name); + CHECK(mCharacter.getVisualParam("NotAParameter") == nullptr); // And by id, which is the other way in. - ensure_equals("a parameter is found by its id", - mCharacter.getVisualParam(20), by_name); - - ensure("setting a weight by name finds it", - mCharacter.setVisualParamWeight("SeCoNd", 0.25f)); - ensure_approximately_equals("and sets it", - mCharacter.getVisualParamWeight("second"), 0.25f, 16); - ensure("setting one nobody has does not", - !mCharacter.setVisualParamWeight("NotAParameter", 0.25f)); + CHECK(mCharacter.getVisualParam(20) == by_name); + + UNSCOPED_INFO("setting a weight by name finds it"); + CHECK(mCharacter.setVisualParamWeight("SeCoNd", 0.25f)); + CHECK_THAT(mCharacter.getVisualParamWeight("second"), ll_test::WithinBits(0.25f, 16)); + CHECK_FALSE(mCharacter.setVisualParamWeight("NotAParameter", 0.25f)); } - template<> template<> - void llcharacter_object::test<18>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: a parameter added under a taken id replaces the old one", "[LLCharacter]") { // A parameter added under an id that is already taken replaces the one // there, in the list the sweep walks as well as in the map. ALTestVisualParam* first = addParam(7, "First"); ALTestVisualParam* second = addParam(7, "Second"); - ensure_equals("the second one took the first one's place", - mCharacter.getVisualParamCount(), 1); - ensure_equals("and it is the one that is found", - mCharacter.getVisualParam(7), (LLVisualParam*)second); + UNSCOPED_INFO("the second one took the first one's place"); + CHECK(mCharacter.getVisualParamCount() == 1); + CHECK(mCharacter.getVisualParam(7) == (LLVisualParam*)second); S32 walked = 0; for (LLVisualParam* param = mCharacter.getFirstVisualParam(); param; param = mCharacter.getNextVisualParam()) { - ensure_equals("the list holds the one that is found", - param, (LLVisualParam*)second); + REQUIRE(param == (LLVisualParam*)second); walked++; } - ensure_equals("once", walked, 1); + CHECK(walked == 1); // The one it replaced is nobody's now, so the character will not free // it and this test has to. delete first; } - template<> template<> - void llcharacter_object::test<19>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: setVisualParamWeight writes this character's own copy by id", "[LLCharacter]") { // Setting a weight through a parameter looks the parameter up by its // id and writes whatever this character has under that id, which is @@ -681,29 +702,27 @@ namespace ALTestVisualParamInfo foreign_info(5, "Mine"); ALTestVisualParam foreign; - ensure("the foreign parameter takes its info", foreign.setInfo(&foreign_info)); + REQUIRE(foreign.setInfo(&foreign_info)); - ensure("a foreign parameter with a known id is taken", - mCharacter.setVisualParamWeight(&foreign, 0.5f)); - ensure_approximately_equals("and this character's own is what moved", - mine->getWeight(), 0.5f, 16); - ensure_approximately_equals("not the one that was handed over", - foreign.getWeight(), 0.f, 16); + UNSCOPED_INFO("a foreign parameter with a known id is taken"); + CHECK(mCharacter.setVisualParamWeight(&foreign, 0.5f)); + UNSCOPED_INFO("and this character's own is what moved"); + CHECK_THAT(mine->getWeight(), ll_test::WithinBits(0.5f, 16)); + UNSCOPED_INFO("not the one that was handed over"); + CHECK_THAT(foreign.getWeight(), ll_test::WithinBits(0.f, 16)); // Handed this character's own, the lookup finds that same one, which // is what lets a caller who already has it write to it directly. - ensure_equals("the id finds the one that was added", - mCharacter.getVisualParam(5), (LLVisualParam*)mine); + CHECK(mCharacter.getVisualParam(5) == (LLVisualParam*)mine); ALTestVisualParamInfo unknown_info(6, "Unknown"); ALTestVisualParam unknown; - ensure("the unknown parameter takes its info", unknown.setInfo(&unknown_info)); - ensure("a parameter with an id this character has not got is refused", - !mCharacter.setVisualParamWeight(&unknown, 0.5f)); + REQUIRE(unknown.setInfo(&unknown_info)); + UNSCOPED_INFO("a parameter with an id this character has not got is refused"); + CHECK_FALSE(mCharacter.setVisualParamWeight(&unknown, 0.5f)); } - template<> template<> - void llcharacter_object::test<20>() + TEST_CASE_METHOD(llcharacter_data, "LLCharacter: speedMultiplier pins at the maximum instead of changing sign", "[LLCharacter]") { // The walk servo asks how much faster the animation has to play for // the planted foot to keep up with the ground, and the answer changes @@ -719,35 +738,35 @@ namespace // A planted foot is going nowhere, so the foot speed is the avatar's // and the animation plays as authored. - ensure_approximately_equals("a planted foot plays the animation as it is", - multiplier(1.5f, 1.5f), 1.f, 16); + UNSCOPED_INFO("a planted foot plays the animation as it is"); + CHECK_THAT(multiplier(1.5f, 1.5f), ll_test::WithinBits(1.f, 16)); // A foot that is not keeping up asks for more. - ensure_approximately_equals("a foot falling behind asks for more", - multiplier(1.5f, 1.0f), 1.5f, 16); - ensure_approximately_equals("but no more than it is allowed", - multiplier(1.5f, 0.1f), max_multiplier, 16); + UNSCOPED_INFO("a foot falling behind asks for more"); + CHECK_THAT(multiplier(1.5f, 1.0f), ll_test::WithinBits(1.5f, 16)); + UNSCOPED_INFO("but no more than it is allowed"); + CHECK_THAT(multiplier(1.5f, 0.1f), ll_test::WithinBits(max_multiplier, 16)); // A foot going forward exactly as fast as the avatar cannot say how // much faster to play, and the answer is as fast as allowed. - ensure_approximately_equals("a foot going with the avatar asks for all of it", - multiplier(1.5f, 0.f), max_multiplier, 16); + UNSCOPED_INFO("a foot going with the avatar asks for all of it"); + CHECK_THAT(multiplier(1.5f, 0.f), ll_test::WithinBits(max_multiplier, 16)); // And one going forward faster than the avatar is the case that used // to come out as the slowest multiplier there is: the animation slowed, // which made the foot slip further, which kept the answer negative. - ensure_approximately_equals("and one going faster than the avatar does too", - multiplier(1.5f, -0.2f), max_multiplier, 16); - ensure_approximately_equals("however much faster", - multiplier(1.5f, -50.f), max_multiplier, 16); + UNSCOPED_INFO("and one going faster than the avatar does too"); + CHECK_THAT(multiplier(1.5f, -0.2f), ll_test::WithinBits(max_multiplier, 16)); + UNSCOPED_INFO("however much faster"); + CHECK_THAT(multiplier(1.5f, -50.f), ll_test::WithinBits(max_multiplier, 16)); - ensure("a foot going forward faster is never read as standing still", - multiplier(1.5f, -0.2f) > min_multiplier); + UNSCOPED_INFO("a foot going forward faster is never read as standing still"); + CHECK(multiplier(1.5f, -0.2f) > min_multiplier); // Running asks for far more than it is allowed, so it sits at the top // of the range and never goes near the sign change at all. That is why // it was the walk that stuck. - ensure_approximately_equals("running is pinned at the ceiling", - multiplier(5.f, 0.5f), max_multiplier, 16); + UNSCOPED_INFO("running is pinned at the ceiling"); + CHECK_THAT(multiplier(5.f, 0.5f), ll_test::WithinBits(max_multiplier, 16)); } } diff --git a/indra/llcharacter/tests/llcharacter_test_checks.cpp b/indra/llcharacter/tests/llcharacter_test_checks.cpp new file mode 100644 index 0000000000..f1019d2b87 --- /dev/null +++ b/indra/llcharacter/tests/llcharacter_test_checks.cpp @@ -0,0 +1,57 @@ +/** + * @file llcharacter_test_checks.cpp + * @brief The state guard's checks on what llcharacter keeps for the process. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "alteststateguard.h" +#include "llcharacter.h" +#include "llkeyframemotion.h" + +#include +#include + +#include + +namespace +{ + class LLCharacterChecks : public Catch::EventListenerBase + { + public: + using Catch::EventListenerBase::EventListenerBase; + + void testRunStarting(Catch::TestRunInfo const&) override + { + // Every character registers itself for the process, and one a + // test case leaves alive is updated and dumped with the rest. + ll_test::StateGuard::add("the live characters", + [] { return std::to_string(LLCharacter::sInstances.size()); }); + // Keyframe data outlives its motion in the cache, which some + // cases expect to find empty. + ll_test::StateGuard::add("the keyframe data cache", + [] { return std::to_string(LLKeyframeDataCache::size()); }); + } + }; +} + +CATCH_REGISTER_LISTENER(LLCharacterChecks) diff --git a/indra/llcharacter/tests/lljoint_test.cpp b/indra/llcharacter/tests/lljoint_test.cpp index 3c92936e6e..b5c785134e 100644 --- a/indra/llcharacter/tests/lljoint_test.cpp +++ b/indra/llcharacter/tests/lljoint_test.cpp @@ -32,148 +32,126 @@ #include "../lljoint.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" -namespace tut -{ namespace { - struct lljoint_data - { - }; - typedef test_group lljoint_test; - typedef lljoint_test::object lljoint_object; - tut::lljoint_test lljoint_testcase("LLJoint"); -} - - template<> template<> - void lljoint_object::test<1>() + TEST_CASE("LLJoint: a lone joint has no parent and is its own root", "[LLJoint]") { LLJoint lljoint; LLJoint* jnt = lljoint.getParent(); - ensure("getParent() failed ", (NULL == jnt)); - ensure("getRoot() failed ", (&lljoint == lljoint.getRoot())); + CHECK(jnt == NULL); + CHECK(&lljoint == lljoint.getRoot()); } - template<> template<> - void lljoint_object::test<2>() + TEST_CASE("LLJoint: setup attaches a joint to its parent", "[LLJoint]") { std::string str = "LLJoint"; LLJoint parent(str), child; child.setup(str, &parent); LLJoint* jnt = child.getParent(); - ensure("setup() failed ", (&parent == jnt)); + CHECK(&parent == jnt); } - template<> template<> - void lljoint_object::test<3>() + TEST_CASE("LLJoint: findJoint finds a child by name", "[LLJoint]") { LLJoint parent, child; std::string str = "LLJoint"; child.setup(str, &parent); LLJoint* jnt = parent.findJoint(str); - ensure("findJoint() failed ", (&child == jnt)); + CHECK(&child == jnt); } - template<> template<> - void lljoint_object::test<4>() + TEST_CASE("LLJoint: setName and getName round-trip", "[LLJoint]") { LLJoint parent; std::string str1 = "LLJoint", str2; parent.setName(str1); str2 = parent.getName(); - ensure("setName() failed ", (str1 == str2)); + CHECK(str1 == str2); } - template<> template<> - void lljoint_object::test<5>() + TEST_CASE("LLJoint: setPosition and getPosition round-trip", "[LLJoint]") { LLJoint lljoint; LLVector3 vec3(2.3f,30.f,10.f); // SL-315 lljoint.setPosition(vec3); LLVector3 pos = lljoint.getPosition(); - ensure("setPosition()/getPosition() failed ", (vec3 == pos)); + CHECK(vec3 == pos); } - template<> template<> - void lljoint_object::test<6>() + TEST_CASE("LLJoint: setWorldPosition sets the world and last world positions", "[LLJoint]") { LLJoint lljoint; LLVector3 vec3(2.3f,30.f,10.f); // SL-315 lljoint.setWorldPosition(vec3); LLVector3 pos = lljoint.getWorldPosition(); - ensure("1:setWorldPosition()/getWorldPosition() failed ", (vec3 == pos)); + CHECK(vec3 == pos); LLVector3 lastPos = lljoint.getLastWorldPosition(); - ensure("2:getLastWorldPosition failed ", (vec3 == lastPos)); + CHECK(vec3 == lastPos); } - template<> template<> - void lljoint_object::test<7>() + TEST_CASE("LLJoint: setRotation and getRotation round-trip", "[LLJoint]") { LLJoint lljoint("LLJoint"); LLQuaternion q(2.3f,30.f,10.f,1.f); lljoint.setRotation(q); LLQuaternion rot = lljoint.getRotation(); - ensure("setRotation()/getRotation() failed ", (q == rot)); + CHECK(q == rot); } - template<> template<> - void lljoint_object::test<8>() + TEST_CASE("LLJoint: setWorldRotation sets the world and last world rotations", "[LLJoint]") { LLJoint lljoint("LLJoint"); LLQuaternion q(2.3f,30.f,10.f,1.f); lljoint.setWorldRotation(q); LLQuaternion rot = lljoint.getWorldRotation(); - ensure("1:setWorldRotation()/getWorldRotation() failed ", (q == rot)); + CHECK(q == rot); LLQuaternion lastRot = lljoint.getLastWorldRotation(); - ensure("2:getLastWorldRotation failed ", (q == lastRot)); + CHECK(q == lastRot); } - template<> template<> - void lljoint_object::test<9>() + TEST_CASE("LLJoint: setScale and getScale round-trip", "[LLJoint]") { LLJoint lljoint; LLVector3 vec3(2.3f,30.f,10.f); lljoint.setScale(vec3); LLVector3 scale = lljoint.getScale(); - ensure("setScale()/getScale failed ", (vec3 == scale)); + CHECK(vec3 == scale); } - template<> template<> - void lljoint_object::test<10>() + TEST_CASE("LLJoint: setWorldMatrix and getWorldMatrix round-trip the identity", "[LLJoint]") { LLJoint lljoint("LLJoint"); LLMatrix4 mat; mat.setIdentity(); lljoint.setWorldMatrix(mat);//giving warning setWorldMatrix not correctly implemented; LLMatrix4 mat4 = lljoint.getWorldMatrix().toMatrix4(); - ensure("setWorldMatrix()/getWorldMatrix failed ", (mat4 == mat)); + CHECK(mat4 == mat); } - template<> template<> - void lljoint_object::test<11>() + TEST_CASE("LLJoint: the constructor stores the joint number", "[LLJoint]") { S32 joint_num = 12; LLJoint lljoint(joint_num); lljoint.setName("parent"); S32 jointNum = lljoint.getJointNum(); - ensure("getJointNum failed ", (jointNum == joint_num)); + CHECK(jointNum == joint_num); } - template<> template<> - void lljoint_object::test<12>() + TEST_CASE("LLJoint: setSkinOffset and getSkinOffset round-trip", "[LLJoint]") { LLJoint lljoint; LLVector3 vec3(2.3f,30.f,10.f); lljoint.setSkinOffset(vec3); LLVector3 offset = lljoint.getSkinOffset(); - ensure("1:setSkinOffset()/getSkinOffset() failed ", (vec3 == offset)); + CHECK(vec3 == offset); } - template<> template<> - void lljoint_object::test<13>() + TEST_CASE("LLJoint: addChild and removeChild keep the tree's links and lookups", "[LLJoint]") { LLJoint lljointgp("gparent"); LLJoint lljoint("parent"); @@ -186,28 +164,27 @@ namespace LLJoint* jnt = NULL; jnt = lljoint2.getParent(); - ensure("addChild() failed ", (&lljoint == jnt)); + CHECK(&lljoint == jnt); LLJoint* jnt1 = lljoint.findJoint("child3"); - ensure("findJoint() failed ", (&lljoint3 == jnt1)); + CHECK(&lljoint3 == jnt1); lljoint.removeChild(&lljoint3); LLJoint* jnt2 = lljoint.findJoint("child3"); - ensure("removeChild() failed ", (NULL == jnt2)); + CHECK(jnt2 == NULL); lljointgp.addChild(&lljoint); - ensure("GetParent() failed ", (&lljoint== lljoint2.getParent())); - ensure("getRoot() failed ", (&lljointgp == lljoint2.getRoot())); + CHECK(&lljoint== lljoint2.getParent()); + CHECK(&lljointgp == lljoint2.getRoot()); - ensure("getRoot() failed ", &lljoint1 == lljoint.findJoint("child1")); + CHECK(&lljoint1 == lljoint.findJoint("child1")); lljointgp.removeAllChildren(); // parent removed from grandparent - so should not be able to locate child - ensure("removeAllChildren() failed ", (NULL == lljointgp.findJoint("child1"))); + CHECK(lljointgp.findJoint("child1") == NULL); // it should still exist in parent though - ensure("removeAllChildren() failed ", (&lljoint1 == lljoint.findJoint("child1"))); + CHECK(&lljoint1 == lljoint.findJoint("child1")); } - template<> template<> - void lljoint_object::test<14>() + TEST_CASE("LLJoint: addChild takes a child away from its previous parent", "[LLJoint]") { LLJoint lljointgp("gparent"); @@ -220,12 +197,11 @@ namespace llparent1.addChild(&lladoptedchild); llparent2.addChild(&lladoptedchild); - ensure("1. addChild failed to remove prior parent", lladoptedchild.getParent() == &llparent2); - ensure("2. addChild failed to remove prior parent", llparent1.findJoint("child2") == NULL); + CHECK(lladoptedchild.getParent() == &llparent2); + CHECK(llparent1.findJoint("child2") == NULL); } - template<> template<> - void lljoint_object::test<15>() + TEST_CASE("LLJoint: updateWorldMatrixChildren recomputes only dirty updating joints", "[LLJoint]") { // updateWorldMatrixChildren() reports how many world matrices it // recomputed. A joint is recomputed only while MATRIX_DIRTY is set, @@ -237,33 +213,40 @@ namespace c.setup("c", &a); d.setup("d", &root); - ensure_equals("fresh tree recomputes every joint", root.updateWorldMatrixChildren(), 5); - ensure_equals("clean tree recomputes nothing", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("fresh tree recomputes every joint"); + CHECK(root.updateWorldMatrixChildren() == 5); + UNSCOPED_INFO("clean tree recomputes nothing"); + CHECK(root.updateWorldMatrixChildren() == 0); root.setRotation(LLQuaternion(0.5f, LLVector3::x_axis)); - ensure_equals("root rotation dirties the whole tree", root.updateWorldMatrixChildren(), 5); + UNSCOPED_INFO("root rotation dirties the whole tree"); + CHECK(root.updateWorldMatrixChildren() == 5); a.setRotation(LLQuaternion(0.25f, LLVector3::y_axis)); - ensure_equals("mid-level rotation dirties its subtree", root.updateWorldMatrixChildren(), 3); + UNSCOPED_INFO("mid-level rotation dirties its subtree"); + CHECK(root.updateWorldMatrixChildren() == 3); root.setPosition(root.getPosition()); - ensure_equals("unchanged position dirties nothing", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("unchanged position dirties nothing"); + CHECK(root.updateWorldMatrixChildren() == 0); root.setPosition(LLVector3(1.f, 2.f, 3.f)); - ensure_equals("root position dirties the whole tree", root.updateWorldMatrixChildren(), 5); + UNSCOPED_INFO("root position dirties the whole tree"); + CHECK(root.updateWorldMatrixChildren() == 5); a.setUpdateXform(false); root.setRotation(LLQuaternion(0.75f, LLVector3::z_axis)); - ensure_equals("subtree without mUpdateXform is skipped", root.updateWorldMatrixChildren(), 2); + UNSCOPED_INFO("subtree without mUpdateXform is skipped"); + CHECK(root.updateWorldMatrixChildren() == 2); // Coming back into the sweep rebuilds what was missed while out of it. a.setUpdateXform(true); - ensure_equals("rejoining the sweep recomputes the subtree", root.updateWorldMatrixChildren(), 3); + UNSCOPED_INFO("rejoining the sweep recomputes the subtree"); + CHECK(root.updateWorldMatrixChildren() == 3); } - template<> template<> - void lljoint_object::test<16>() + TEST_CASE("LLJoint: rewriting the same rotation does not dirty the joint", "[LLJoint]") { // Writing an unchanged rotation must not dirty the joint, since // touch() would carry that down the entire subtree. @@ -272,22 +255,24 @@ namespace child.setup("child", &root); root.updateWorldMatrixChildren(); - ensure_equals("tree starts clean", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("tree starts clean"); + CHECK(root.updateWorldMatrixChildren() == 0); const LLQuaternion rot(0.4f, LLVector3::y_axis); root.setRotation(rot); - ensure_equals("a new rotation dirties the subtree", root.updateWorldMatrixChildren(), 2); + UNSCOPED_INFO("a new rotation dirties the subtree"); + CHECK(root.updateWorldMatrixChildren() == 2); root.setRotation(rot); - ensure_equals("rewriting the same rotation dirties nothing", root.updateWorldMatrixChildren(), 0); + CHECK(root.updateWorldMatrixChildren() == 0); root.setRotation(LLQuaternion(0.9f, LLVector3::z_axis)); - ensure_equals("a different rotation dirties the subtree again", root.updateWorldMatrixChildren(), 2); + UNSCOPED_INFO("a different rotation dirties the subtree again"); + CHECK(root.updateWorldMatrixChildren() == 2); } - template<> template<> - void lljoint_object::test<17>() + TEST_CASE("LLJoint: touchIfXformParentMoved dirties the tree when the seat moves", "[LLJoint]") { // A sitting avatar hangs its root joint off the seat's transform, // which is no joint, and then writes seat relative values into the @@ -304,27 +289,32 @@ namespace root.getXform()->setParent(&seat); root.updateWorldMatrixChildren(); - ensure_equals("tree starts clean", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("tree starts clean"); + CHECK(root.updateWorldMatrixChildren() == 0); seat.setPosition(LLVector3(5.f, 0.f, 0.f)); seat.updateMatrix(); - ensure_equals("a moved seat dirties nothing on its own", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("a moved seat dirties nothing on its own"); + CHECK(root.updateWorldMatrixChildren() == 0); root.touchIfXformParentMoved(); - ensure_equals("the seat's move dirties the whole tree", root.updateWorldMatrixChildren(), 2); - ensure("the root followed the seat", root.getWorldPosition() == LLVector3(5.f, 0.f, 0.f)); + UNSCOPED_INFO("the seat's move dirties the whole tree"); + CHECK(root.updateWorldMatrixChildren() == 2); + UNSCOPED_INFO("the root followed the seat"); + CHECK(root.getWorldPosition() == LLVector3(5.f, 0.f, 0.f)); root.touchIfXformParentMoved(); - ensure_equals("a still seat dirties nothing", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("a still seat dirties nothing"); + CHECK(root.updateWorldMatrixChildren() == 0); seat.setRotation(LLQuaternion(0.5f, LLVector3::z_axis)); seat.updateMatrix(); root.touchIfXformParentMoved(); - ensure_equals("a rotated seat dirties the whole tree", root.updateWorldMatrixChildren(), 2); + UNSCOPED_INFO("a rotated seat dirties the whole tree"); + CHECK(root.updateWorldMatrixChildren() == 2); } - template<> template<> - void lljoint_object::test<18>() + TEST_CASE("LLJoint: a root slerping onto a still target stops dirtying the tree", "[LLJoint]") { // The avatar root is written every frame from a slerp toward a target // that has stopped moving, with an interpolant taken from the frame @@ -353,20 +343,19 @@ namespace const F32 u = (0.010f + 0.006f * (frame % 5)) / 0.4f; root.setWorldRotationIfMoved(slerp(u, root.getWorldRotation(), target)); } - ensure_equals("a root that has caught up with its target stops dirtying the tree", - root.updateWorldMatrixChildren(), 0); + CHECK(root.updateWorldMatrixChildren() == 0); // What it stopped short by is the whole cost of the tolerance. - ensure("the root stopped within a hundredth of a degree of its target", - root.getWorldRotation().isEqualEps(target, 1.e-4f)); + UNSCOPED_INFO("the root stopped within a hundredth of a degree of its target"); + CHECK(root.getWorldRotation().isEqualEps(target, 1.e-4f)); // A real turn still has to reach the skeleton. root.setWorldRotationIfMoved(LLQuaternion(0.4f + 1.f * DEG_TO_RAD, LLVector3::y_axis)); - ensure_equals("a real turn dirties the whole tree", root.updateWorldMatrixChildren(), 2); + UNSCOPED_INFO("a real turn dirties the whole tree"); + CHECK(root.updateWorldMatrixChildren() == 2); } - template<> template<> - void lljoint_object::test<19>() + TEST_CASE("LLJoint: the sweep descends only into marked subtrees", "[LLJoint]") { // The sweep walks only where something told it to. A joint carrying // neither a dirty matrix of its own nor the mark left by a write below @@ -381,27 +370,29 @@ namespace e.setup("e", &d); root.updateWorldMatrixChildren(); - ensure_equals("tree starts clean", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("tree starts clean"); + CHECK(root.updateWorldMatrixChildren() == 0); c.setRotation(LLQuaternion(0.3f, LLVector3::x_axis)); - ensure("the write is announced all the way up", - (root.mDirtyFlags & LLJoint::SUBTREE_DIRTY) != 0); - ensure("the sibling branch is left alone", d.mDirtyFlags == 0); + UNSCOPED_INFO("the write is announced all the way up"); + CHECK((root.mDirtyFlags & LLJoint::SUBTREE_DIRTY) != 0); + UNSCOPED_INFO("the sibling branch is left alone"); + CHECK(d.mDirtyFlags == 0); // Dirty a joint behind the sweep's back, below a branch nothing // marked. The sweep must stop at that branch's root and never look at // it -- the old walk visited every joint and would have rebuilt it. e.mDirtyFlags |= LLJoint::MATRIX_DIRTY; - ensure_equals("only the marked path is walked", root.updateWorldMatrixChildren(), 1); - ensure("the unmarked branch was never descended into", - (e.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); + UNSCOPED_INFO("only the marked path is walked"); + CHECK(root.updateWorldMatrixChildren() == 1); + UNSCOPED_INFO("the unmarked branch was never descended into"); + CHECK((e.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); - ensure("the mark is cleared once the subtree is clean", - (root.mDirtyFlags & LLJoint::SUBTREE_DIRTY) == 0); + UNSCOPED_INFO("the mark is cleared once the subtree is clean"); + CHECK((root.mDirtyFlags & LLJoint::SUBTREE_DIRTY) == 0); } - template<> template<> - void lljoint_object::test<20>() + TEST_CASE("LLJoint: getWorldMatrix leaves the subtree mark for the sweep", "[LLJoint]") { // getWorldMatrix rebuilds this joint and the ones above it on the // spot, without ever walking down. It must not carry the subtree mark @@ -413,21 +404,23 @@ namespace b.setup("b", &a); root.updateWorldMatrixChildren(); - ensure_equals("tree starts clean", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("tree starts clean"); + CHECK(root.updateWorldMatrixChildren() == 0); root.setRotation(LLQuaternion(0.3f, LLVector3::z_axis)); b.setRotation(LLQuaternion(0.2f, LLVector3::x_axis)); - ensure("the root carries both its own dirt and the mark", - (root.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0 && - (root.mDirtyFlags & LLJoint::SUBTREE_DIRTY) != 0); + { + INFO("the root carries both its own dirt and the mark"); + CHECK((root.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); + CHECK((root.mDirtyFlags & LLJoint::SUBTREE_DIRTY) != 0); + } root.getWorldMatrix(); - ensure_equals("the joints below still get their turn", - root.updateWorldMatrixChildren(), 2); + UNSCOPED_INFO("the joints below still get their turn"); + CHECK(root.updateWorldMatrixChildren() == 2); } - template<> template<> - void lljoint_object::test<21>() + TEST_CASE("LLJoint: world transforms are current without a sweep in between", "[LLJoint]") { // A culled avatar does not get swept at all, which is only safe // because skipping the sweep costs nothing but time: the dirty flags @@ -440,21 +433,20 @@ namespace root.updateWorldMatrixChildren(); root.setPosition(LLVector3(1.f, 0.f, 0.f)); - ensure("a world position is current with no sweep in between", - b.getWorldPosition() == LLVector3(1.f, 0.f, 0.f)); - ensure_equals("and the sweep still owes the matrices", - root.updateWorldMatrixChildren(), 3); + UNSCOPED_INFO("a world position is current with no sweep in between"); + CHECK(b.getWorldPosition() == LLVector3(1.f, 0.f, 0.f)); + UNSCOPED_INFO("and the sweep still owes the matrices"); + CHECK(root.updateWorldMatrixChildren() == 3); root.setPosition(LLVector3(2.f, 0.f, 0.f)); const LLVector3 world_translation(b.getWorldMatrix().getTranslation().getF32ptr()); - ensure("a world matrix is current with no sweep in between", - world_translation == LLVector3(2.f, 0.f, 0.f)); - ensure_equals("and asking for it was the sweep's work", - root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("a world matrix is current with no sweep in between"); + CHECK(world_translation == LLVector3(2.f, 0.f, 0.f)); + UNSCOPED_INFO("and asking for it was the sweep's work"); + CHECK(root.updateWorldMatrixChildren() == 0); } - template<> template<> - void lljoint_object::test<22>() + TEST_CASE("LLJoint: a joint healed before the sweep still sends it to its children", "[LLJoint]") { // The joint that was written is healed before the sweep -- the avatar // asks its root for a world matrix right after moving it -- and the @@ -470,18 +462,18 @@ namespace root.touch(); root.setWorldPosition(LLVector3(1.f, 0.f, 0.f)); root.getWorldMatrix(); - ensure_equals("a healed root still sends the sweep down to its children", - root.updateWorldMatrixChildren(), 2); + UNSCOPED_INFO("a healed root still sends the sweep down to its children"); + CHECK(root.updateWorldMatrixChildren() == 2); a.setRotation(LLQuaternion(0.3f, LLVector3::x_axis)); a.getWorldMatrix(); - ensure_equals("and so does a healed joint in the middle of the tree", - root.updateWorldMatrixChildren(), 1); - ensure_equals("after which the tree is clean", root.updateWorldMatrixChildren(), 0); + UNSCOPED_INFO("and so does a healed joint in the middle of the tree"); + CHECK(root.updateWorldMatrixChildren() == 1); + UNSCOPED_INFO("after which the tree is clean"); + CHECK(root.updateWorldMatrixChildren() == 0); } - template<> template<> - void lljoint_object::test<23>() + TEST_CASE("LLJoint: an unchanged scale does not dirty the joint", "[LLJoint]") { // A scale written back unchanged is not a change. The pose blender // writes every channel of a joint each interpolated frame, so a @@ -491,18 +483,24 @@ namespace a.setup("a", &root); root.setScale(LLVector3(2.f, 2.f, 2.f)); root.updateWorldMatrixChildren(); - ensure("tree starts clean", root.mDirtyFlags == 0 && a.mDirtyFlags == 0); + { + INFO("tree starts clean"); + CHECK(root.mDirtyFlags == 0); + CHECK(a.mDirtyFlags == 0); + } root.setScale(LLVector3(2.f, 2.f, 2.f)); - ensure("the same scale again dirties nothing", root.mDirtyFlags == 0 && a.mDirtyFlags == 0); + CHECK(root.mDirtyFlags == 0); + CHECK(a.mDirtyFlags == 0); root.setScale(LLVector3(3.f, 2.f, 2.f)); - ensure("a different scale dirties the joint", (root.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); - ensure("and its children", (a.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); + UNSCOPED_INFO("a different scale dirties the joint"); + CHECK((root.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); + UNSCOPED_INFO("and its children"); + CHECK((a.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); } - template<> template<> - void lljoint_object::test<24>() + TEST_CASE("LLJoint: setWorldRotation round-trips whatever the parent's scale", "[LLJoint]") { // A world rotation asked for is the world rotation that comes back. // This is the route the foot solver takes six times a frame for a @@ -532,13 +530,11 @@ namespace child.setWorldRotation(wanted); const LLQuaternion got = child.getWorldRotation(); - ensure_approximately_equals("the world rotation asked for is the one that comes back", - fabsf(dot(got, wanted)), 1.f, 16); + REQUIRE_THAT(fabsf(dot(got, wanted)), ll_test::WithinBits(1.f, 16)); } } - template<> template<> - void lljoint_object::test<25>() + TEST_CASE("LLJoint: setWorldRotation matches the matrix route under an unscaled parent", "[LLJoint]") { // With an unscaled parent it is the same local rotation the matrix // route arrived at, so the joints that were already getting the right @@ -563,12 +559,10 @@ namespace child.setWorldRotation(wanted); - ensure_approximately_equals("the two routes agree on the local rotation", - fabsf(dot(child.getRotation(), through_the_matrix)), 1.f, 16); + CHECK_THAT(fabsf(dot(child.getRotation(), through_the_matrix)), ll_test::WithinBits(1.f, 16)); } - template<> template<> - void lljoint_object::test<26>() + TEST_CASE("LLJoint: a scaled parent defeats the matrix route but not setWorldRotation", "[LLJoint]") { // The route this replaced could not do that. It built the answer out // of matrices, and LLMatrix4::invert transposes -- which inverts a @@ -598,14 +592,13 @@ namespace child.setWorldRotation(wanted); const LLQuaternion now = child.getWorldRotation(); - ensure_approximately_equals("a scaled parent gives back what was asked for", - fabsf(dot(now, wanted)), 1.f, 16); - ensure("which the matrix route it replaced did not", - fabsf(dot(through_the_matrix, wanted)) < 0.999f); + UNSCOPED_INFO("a scaled parent gives back what was asked for"); + CHECK_THAT(fabsf(dot(now, wanted)), ll_test::WithinBits(1.f, 16)); + UNSCOPED_INFO("which the matrix route it replaced did not"); + CHECK(fabsf(dot(through_the_matrix, wanted)) < 0.999f); } - template<> template<> - void lljoint_object::test<27>() + TEST_CASE("LLJoint: setWorldRotation undoes a parent rotation that is not unit length", "[LLJoint]") { // Nothing normalizes a rotation on its way into a joint, so a parent // can be carrying one that has drifted off unit length. The world @@ -616,8 +609,8 @@ namespace LLJoint child("child", &parent); LLQuaternion drifted(0.3f, 0.4f, 0.5f, 0.6f); - ensure("the parent's rotation is not unit length", - fabsf(drifted.mQ[0]*drifted.mQ[0] + drifted.mQ[1]*drifted.mQ[1] + UNSCOPED_INFO("the parent's rotation is not unit length"); + CHECK(fabsf(drifted.mQ[0]*drifted.mQ[0] + drifted.mQ[1]*drifted.mQ[1] + drifted.mQ[2]*drifted.mQ[2] + drifted.mQ[3]*drifted.mQ[3] - 1.f) > 0.05f); parent.setRotation(drifted); @@ -627,8 +620,7 @@ namespace const LLQuaternion got = child.getWorldRotation(); for (S32 i = 0; i < 4; ++i) { - ensure_approximately_equals("the world rotation comes back as it was asked for", - got.mQ[i], wanted.mQ[i], 14); + REQUIRE_THAT(got.mQ[i], ll_test::WithinBits(wanted.mQ[i], 14)); } } diff --git a/indra/llcharacter/tests/llkeyframemotion_test.cpp b/indra/llcharacter/tests/llkeyframemotion_test.cpp index 2406d4595a..93e5068571 100644 --- a/indra/llcharacter/tests/llkeyframemotion_test.cpp +++ b/indra/llcharacter/tests/llkeyframemotion_test.cpp @@ -41,7 +41,9 @@ #include #include -#include "../test/lltut.h" +#include +#include +#include "altestmatchers.h" namespace { @@ -246,9 +248,11 @@ namespace } template - void ensure_same_value(const char* what, F32 time, const T& got, const T& want) + void require_same_value(const char* what, F32 time, const T& got, const T& want) { - tut::ensure(std::string(what) + " at t=" + std::to_string(time), got == want); + INFO(what); + CAPTURE(time); + REQUIRE(got == want); } // Rotations are normalized on the way out of the blend, and the curve does @@ -256,34 +260,35 @@ namespace // reference divides by a real one. They agree to about a part in ten // million, which is not the same bits. Everything else here is compared // exactly, this one to a tolerance well inside what a rotation can carry. - void ensure_same_value(const char* what, F32 time, const LLQuaternion& got, const LLQuaternion& want) + void require_same_value(const char* what, F32 time, const LLQuaternion& got, const LLQuaternion& want) { + INFO(what); + CAPTURE(time); for (S32 i = 0; i < 4; ++i) { - tut::ensure_approximately_equals_range( - (std::string(what) + " at t=" + std::to_string(time)).c_str(), - got.mQ[i], want.mQ[i], 1e-6f); + CAPTURE(i); + REQUIRE_THAT(got.mQ[i], Catch::Matchers::WithinAbs(want.mQ[i], 1e-6f)); } } // Every way of sampling a curve without a cursor: far outside the keys, // random within them, and exactly on each one and a step either side. template - void ensure_stateless_parity(const char* what, Rng& rng, const CurveT& curve, const MapCurve& reference) + void require_stateless_parity(const char* what, Rng& rng, const CurveT& curve, const MapCurve& reference) { for (U32 i = 0; i < 2000; ++i) { const F32 time = rng.uniform(-0.5f, DURATION + 0.5f); - ensure_same_value(what, time, sample(curve, time), reference.getValue(time)); + require_same_value(what, time, sample(curve, time), reference.getValue(time)); } for (U32 k = 0; k < curve.getNumKeys(); ++k) { const F32 time = curve.getKeyTime(k); const F32 above = std::nextafter(time, DURATION * 2.f); const F32 below = std::nextafter(time, -DURATION); - ensure_same_value(what, time, sample(curve, time), reference.getValue(time)); - ensure_same_value(what, above, sample(curve, above), reference.getValue(above)); - ensure_same_value(what, below, sample(curve, below), reference.getValue(below)); + require_same_value(what, time, sample(curve, time), reference.getValue(time)); + require_same_value(what, above, sample(curve, above), reference.getValue(above)); + require_same_value(what, below, sample(curve, below), reference.getValue(below)); } } @@ -292,8 +297,8 @@ namespace // looping motion does. Every sample must match the map, and the cursor // must be exactly where the map's search would have landed. template - U32 ensure_playback_parity(const char* what, Rng& rng, const CurveT& curve, const MapCurve& reference, - F32 min_step, F32 max_step, U32 samples) + U32 require_playback_parity(const char* what, Rng& rng, const CurveT& curve, const MapCurve& reference, + F32 min_step, F32 max_step, U32 samples) { U32 cursor = 0; U32 wraps = 0; @@ -306,8 +311,10 @@ namespace time -= DURATION; ++wraps; } - ensure_same_value(what, time, sample(curve, time, cursor), reference.getValue(time)); - tut::ensure_equals(std::string(what) + " cursor at t=" + std::to_string(time), cursor, reference.lowerBound(time)); + require_same_value(what, time, sample(curve, time, cursor), reference.getValue(time)); + INFO(what << ": cursor"); + CAPTURE(time); + REQUIRE_THAT(cursor, ll_test::LLSDEquals(reference.lowerBound(time))); } return wraps; } @@ -486,8 +493,6 @@ namespace } } -namespace tut -{ namespace { struct llkeyframemotion_data @@ -520,13 +525,8 @@ namespace return motion.getPose()->findJointState(std::string(joint_name)); } }; - typedef test_group llkeyframemotion_test; - typedef llkeyframemotion_test::object llkeyframemotion_object; - tut::llkeyframemotion_test llkeyframemotion_testcase("LLKeyframeMotion"); -} - template<> template<> - void llkeyframemotion_object::test<1>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: setKey keeps keys sorted and unique and the last value wins", "[LLKeyframeMotion]") { // Keys arrive in whatever order the asset holds them, and a time can // repeat. The curve keeps them sorted and unique, the last value at a @@ -536,28 +536,30 @@ namespace set_key(curve, 1.f, LLVector3(1.f, 0.f, 0.f)); set_key(curve, 3.f, LLVector3(3.f, 0.f, 0.f)); set_key(curve, 2.f, LLVector3(0.f, 2.f, 0.f)); - ensure_equals("a repeated time does not add a key", curve.getNumKeys(), 3u); - ensure_equals("keys are sorted: first", curve.getKeyTime(0), 1.f); - ensure_equals("keys are sorted: second", curve.getKeyTime(1), 2.f); - ensure_equals("keys are sorted: third", curve.getKeyTime(2), 3.f); - ensure("the last value at a time wins", key_value(curve, 1) == LLVector3(0.f, 2.f, 0.f)); + UNSCOPED_INFO("a repeated time does not add a key"); + CHECK(curve.getNumKeys() == 3u); + CHECK(curve.getKeyTime(0) == 1.f); + CHECK(curve.getKeyTime(1) == 2.f); + CHECK(curve.getKeyTime(2) == 3.f); + UNSCOPED_INFO("the last value at a time wins"); + CHECK(key_value(curve, 1) == LLVector3(0.f, 2.f, 0.f)); Rng rng; RotationCurve rotations; MapCurve reference; build(rng, rotations, reference, LLKeyframeMotion::IT_LINEAR); - ensure_equals("random keys: count matches the map", rotations.getNumKeys(), static_cast(reference.mKeys.size())); + REQUIRE(rotations.getNumKeys() == static_cast(reference.mKeys.size())); U32 k = 0; for (const auto& key : reference.mKeys) { - ensure_equals("random keys: time matches the map", rotations.getKeyTime(k), key.first); - ensure("random keys: value matches the map", key_value(rotations, k) == key.second); + CAPTURE(k); + REQUIRE(rotations.getKeyTime(k) == key.first); + REQUIRE(key_value(rotations, k) == key.second); ++k; } } - template<> template<> - void llkeyframemotion_object::test<2>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: sampling without a cursor matches the map curve", "[LLKeyframeMotion]") { // Sampled anywhere, without a cursor, the array curve returns what // the map curve returned: clamped outside the keys, the key itself on @@ -568,30 +570,29 @@ namespace PositionCurve curve; MapCurve reference; build(rng, curve, reference, LLKeyframeMotion::IT_LINEAR); - ensure_stateless_parity("linear position", rng, curve, reference); + require_stateless_parity("linear position", rng, curve, reference); } { PositionCurve curve; MapCurve reference; build(rng, curve, reference, LLKeyframeMotion::IT_STEP); - ensure_stateless_parity("stepped position", rng, curve, reference); + require_stateless_parity("stepped position", rng, curve, reference); } { RotationCurve curve; MapCurve reference; build(rng, curve, reference, LLKeyframeMotion::IT_LINEAR); - ensure_stateless_parity("linear rotation", rng, curve, reference); + require_stateless_parity("linear rotation", rng, curve, reference); } { RotationCurve curve; MapCurve reference; build(rng, curve, reference, LLKeyframeMotion::IT_STEP); - ensure_stateless_parity("stepped rotation", rng, curve, reference); + require_stateless_parity("stepped rotation", rng, curve, reference); } } - template<> template<> - void llkeyframemotion_object::test<3>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: playback with a cursor matches the map curve and its search", "[LLKeyframeMotion]") { // Played forward with a cursor, fine steps land on the last answer or // the one after it, coarse steps skip past both and fall back to a @@ -603,20 +604,19 @@ namespace MapCurve reference; build(rng, curve, reference, LLKeyframeMotion::IT_LINEAR); - const U32 fine_wraps = ensure_playback_parity("fine playback", rng, curve, reference, 0.f, 0.03f, 1200); - ensure("fine playback wrapped", fine_wraps >= 3); + const U32 fine_wraps = require_playback_parity("fine playback", rng, curve, reference, 0.f, 0.03f, 1200); + CHECK(fine_wraps >= 3); - const U32 coarse_wraps = ensure_playback_parity("coarse playback", rng, curve, reference, 0.3f, 1.f, 200); - ensure("coarse playback wrapped", coarse_wraps >= 20); + const U32 coarse_wraps = require_playback_parity("coarse playback", rng, curve, reference, 0.3f, 1.f, 200); + CHECK(coarse_wraps >= 20); PositionCurve stepped; MapCurve stepped_reference; build(rng, stepped, stepped_reference, LLKeyframeMotion::IT_STEP); - ensure_playback_parity("stepped playback", rng, stepped, stepped_reference, 0.f, 0.05f, 800); + require_playback_parity("stepped playback", rng, stepped, stepped_reference, 0.f, 0.05f, 800); } - template<> template<> - void llkeyframemotion_object::test<4>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: a sample on a key returns that key whatever the cursor", "[LLKeyframeMotion]") { // A sample exactly on a key returns that key, whatever the cursor // claims: behind it, on it, past it, at the end, or past the end of @@ -634,20 +634,22 @@ namespace const U32 hints[] = { 0u, k > 0 ? k - 1 : 0u, k, k + 1, count, count + 7 }; for (U32 hint : hints) { + CAPTURE(k, hint); U32 cursor = hint; - ensure_same_value("exact key under any cursor", time, sample(curve, time, cursor), reference.getValue(time)); - ensure_equals("exact key leaves the cursor on the key", cursor, k); + require_same_value("exact key under any cursor", time, sample(curve, time, cursor), reference.getValue(time)); + UNSCOPED_INFO("exact key leaves the cursor on the key"); + REQUIRE(cursor == k); cursor = hint; const F32 above = std::nextafter(time, DURATION * 2.f); - ensure_same_value("just past a key under any cursor", above, sample(curve, above, cursor), reference.getValue(above)); - ensure_equals("just past a key leaves the cursor after it", cursor, k + 1); + require_same_value("just past a key under any cursor", above, sample(curve, above, cursor), reference.getValue(above)); + UNSCOPED_INFO("just past a key leaves the cursor after it"); + REQUIRE(cursor == k + 1); } } } - template<> template<> - void llkeyframemotion_object::test<5>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: an empty curve gives the identity and a one-key curve its key", "[LLKeyframeMotion]") { // No keys: the identity of the channel. One key: that key, wherever // the sample falls, with the cursor before or after it. @@ -655,26 +657,33 @@ namespace // has to say what it answers with rather than handing back whatever // was in the memory. PositionCurve no_positions; - ensure("an empty position curve is the zero vector", sample(no_positions, 1.f) == LLVector3()); + CHECK(sample(no_positions, 1.f) == LLVector3()); RotationCurve no_rotations; - ensure("an empty rotation curve is the identity", sample(no_rotations, 1.f) == LLQuaternion()); + CHECK(sample(no_rotations, 1.f) == LLQuaternion()); RotationCurve one; const LLQuaternion key(0.5f, LLVector3(0.f, 1.f, 0.f)); set_key(one, 1.f, key); U32 cursor = 0; - ensure("before the only key", sample(one, 0.f, cursor) == key); - ensure_equals("before the only key: cursor", cursor, 0u); - ensure("on the only key", sample(one, 1.f, cursor) == key); - ensure_equals("on the only key: cursor", cursor, 0u); - ensure("after the only key", sample(one, 2.f, cursor) == key); - ensure_equals("after the only key: cursor", cursor, 1u); - ensure("back before the only key", sample(one, 0.5f, cursor) == key); - ensure_equals("back before the only key: cursor", cursor, 0u); + UNSCOPED_INFO("before the only key"); + CHECK(sample(one, 0.f, cursor) == key); + UNSCOPED_INFO("before the only key: cursor"); + CHECK(cursor == 0u); + UNSCOPED_INFO("on the only key"); + CHECK(sample(one, 1.f, cursor) == key); + UNSCOPED_INFO("on the only key: cursor"); + CHECK(cursor == 0u); + UNSCOPED_INFO("after the only key"); + CHECK(sample(one, 2.f, cursor) == key); + UNSCOPED_INFO("after the only key: cursor"); + CHECK(cursor == 1u); + UNSCOPED_INFO("back before the only key"); + CHECK(sample(one, 0.5f, cursor) == key); + UNSCOPED_INFO("back before the only key: cursor"); + CHECK(cursor == 0u); } - template<> template<> - void llkeyframemotion_object::test<6>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: deserialize gives a joint motion per joint holding its keys", "[LLKeyframeMotion]") { // An asset read in gives one joint motion per joint in the file, in // file order, each holding the keys that followed its name. The @@ -683,59 +692,129 @@ namespace LLKeyframeMotion motion(LLUUID::generateNewID()); std::vector buffer(4096); const AssetAnimation anim = two_joint_animation(); - ensure("the animation is accepted", load(motion, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); - ensure_equals("both joints came through", motion.getNumJointMotions(), 2); - ensure_approximately_equals("duration", motion.getDuration(), anim.mDuration, 16); - ensure("the animation loops", motion.getLoop()); - ensure_approximately_equals("ease in", motion.getEaseInDuration(), anim.mEaseIn, 16); - ensure_approximately_equals("ease out", motion.getEaseOutDuration(), anim.mEaseOut, 16); - ensure_equals("base priority", (S32)motion.getPriority(), anim.mBasePriority); + CHECK(motion.getNumJointMotions() == 2); + CHECK_THAT(motion.getDuration(), ll_test::WithinBits(anim.mDuration, 16)); + CHECK(motion.getLoop()); + CHECK_THAT(motion.getEaseInDuration(), ll_test::WithinBits(anim.mEaseIn, 16)); + CHECK_THAT(motion.getEaseOutDuration(), ll_test::WithinBits(anim.mEaseOut, 16)); + CHECK((S32)motion.getPriority() == anim.mBasePriority); LLPose* pose = motion.getPose(); - ensure_equals("a joint state per joint motion", pose->getNumJointStates(), 2); + UNSCOPED_INFO("a joint state per joint motion"); + CHECK(pose->getNumJointStates() == 2); LLJointState* pelvis_state = pose->findJointState(std::string("mPelvis")); - ensure("the pelvis is in the pose", pelvis_state != nullptr); - ensure_equals("the pelvis carries both channels", - pelvis_state->getUsage(), (U32)(LLJointState::ROT | LLJointState::POS)); + REQUIRE(pelvis_state != nullptr); + CHECK(pelvis_state->getUsage() == (U32)(LLJointState::ROT | LLJointState::POS)); LLJointState* torso_state = pose->findJointState(std::string("mTorso")); - ensure("the torso is in the pose", torso_state != nullptr); - ensure_equals("the torso carries rotation only", torso_state->getUsage(), (U32)LLJointState::ROT); - ensure_equals("the torso keeps its own priority", - (S32)torso_state->getPriority(), anim.mJoints[1].mPriority); + REQUIRE(torso_state != nullptr); + CHECK(torso_state->getUsage() == (U32)LLJointState::ROT); + UNSCOPED_INFO("the torso keeps its own priority"); + CHECK((S32)torso_state->getPriority() == anim.mJoints[1].mPriority); // and the keys hold what the file held. The format quantizes both // channels to sixteen bits, positions over five metres either way and // rotations over a packed vector, so this is close rather than equal. for (const auto& key : anim.mJoints[0].mPositions) { + CAPTURE(key.first); LLJointState* sampled = sampleAt(motion, key.first, "mPelvis"); - ensure("the pelvis is sampled", sampled != nullptr); + REQUIRE(sampled != nullptr); const LLVector3 got = sampled->getPosition(); for (S32 i = 0; i < 3; ++i) { - ensure_approximately_equals_range("position key survived the parse", - got.mV[i], key.second.mV[i], 1e-3f); + CAPTURE(i); + UNSCOPED_INFO("position key survived the parse"); + REQUIRE_THAT(got.mV[i], Catch::Matchers::WithinAbs(key.second.mV[i], 1e-3f)); } } for (const auto& key : anim.mJoints[0].mRotations) { + CAPTURE(key.first); LLJointState* sampled = sampleAt(motion, key.first, "mPelvis"); - ensure("the pelvis is sampled", sampled != nullptr); - ensure_approximately_equals_range("rotation key survived the parse", - fabsf(dot(sampled->getRotation(), key.second)), 1.f, 1e-3f); + REQUIRE(sampled != nullptr); + UNSCOPED_INFO("rotation key survived the parse"); + REQUIRE_THAT(fabsf(dot(sampled->getRotation(), key.second)), Catch::Matchers::WithinAbs(1.f, 1e-3f)); } LLJointState* torso_sample = sampleAt(motion, anim.mJoints[1].mRotations[0].first, "mTorso"); - ensure("the torso is sampled", torso_sample != nullptr); - ensure_approximately_equals_range("the torso's one key survived the parse", - fabsf(dot(torso_sample->getRotation(), anim.mJoints[1].mRotations[0].second)), - 1.f, 1e-3f); + REQUIRE(torso_sample != nullptr); + UNSCOPED_INFO("the torso's one key survived the parse"); + CHECK_THAT(fabsf(dot(torso_sample->getRotation(), anim.mJoints[1].mRotations[0].second)), + Catch::Matchers::WithinAbs(1.f, 1e-3f)); + } + + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: serialize then deserialize gives back the same joints and keys", "[LLKeyframeMotion]") + { + // What was read can be written back and read again, and the second + // reading agrees with the first joint for joint and key for key. The + // format quantizes on the way out, so this holds only because the + // first reading was already quantized. + LLKeyframeMotion first(LLUUID::generateNewID()); + std::vector buffer(4096); + REQUIRE(load(first, two_joint_animation(), buffer.data(), (S32)buffer.size())); + + std::vector written(first.getFileSize()); + LLDataPackerBinaryBuffer writer(written.data(), (S32)written.size()); + CHECK(first.serialize(writer)); + + LLKeyframeMotion second(LLUUID::generateNewID()); + second.setCharacter(&mCharacter); + LLDataPackerBinaryBuffer reader(written.data(), writer.getCurrentSize()); + CHECK(second.deserialize(reader, second.getID())); + + CHECK(second.getNumJointMotions() == first.getNumJointMotions()); + CHECK_THAT(second.getDuration(), ll_test::WithinBits(first.getDuration(), 16)); + CHECK((S32)second.getPriority() == (S32)first.getPriority()); + + LLPose* first_pose = first.getPose(); + LLPose* second_pose = second.getPose(); + CHECK(second_pose->getNumJointStates() == first_pose->getNumJointStates()); + for (const char* name : { "mPelvis", "mTorso" }) + { + CAPTURE(name); + LLJointState* a = first_pose->findJointState(std::string(name)); + LLJointState* b = second_pose->findJointState(std::string(name)); + REQUIRE(a != nullptr); + REQUIRE(b != nullptr); + REQUIRE(b->getUsage() == a->getUsage()); + REQUIRE((S32)b->getPriority() == (S32)a->getPriority()); + } + + // The keys themselves, sampled the way playback samples them. Without + // this the round trip could write a joint's name and another joint's + // curve and nothing here would notice. + for (F32 time = 0.f; time < 2.f; time += 0.25f) + { + for (const char* name : { "mPelvis", "mTorso" }) + { + CAPTURE(time, name); + LLJointState* a = sampleAt(first, time, name); + LLJointState* b = sampleAt(second, time, name); + REQUIRE(a != nullptr); + REQUIRE(b != nullptr); + + const LLQuaternion rot_a = a->getRotation(); + const LLQuaternion rot_b = b->getRotation(); + REQUIRE_THAT(fabsf(dot(rot_a, rot_b)), Catch::Matchers::WithinAbs(1.f, 1e-4f)); + + if (a->getUsage() & LLJointState::POS) + { + const LLVector3 pos_a = a->getPosition(); + const LLVector3 pos_b = b->getPosition(); + for (S32 i = 0; i < 3; ++i) + { + CAPTURE(i); + REQUIRE_THAT(pos_b.mV[i], Catch::Matchers::WithinAbs(pos_a.mV[i], 1e-4f)); + } + } + } + } } - template<> template<> - void llkeyframemotion_object::test<8>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: only chains shorter than the solver arrays are accepted", "[LLKeyframeMotion]") { // The solver holds a constraint chain in arrays of MAX_CHAIN_LENGTH // and indexes them from zero to the chain length inclusive, so a chain @@ -746,30 +825,30 @@ namespace for (S32 chain_length = 0; chain_length < MAX_CHAIN_LENGTH; ++chain_length) { + CAPTURE(chain_length); LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("a chain the arrays can hold is accepted", - load(motion, constrained_animation(chain_length), buffer.data(), (S32)buffer.size())); + UNSCOPED_INFO("a chain the arrays can hold is accepted"); + REQUIRE(load(motion, constrained_animation(chain_length), buffer.data(), (S32)buffer.size())); } { // One longer, which every joint above the volume can still supply, // so the length is the only thing left to refuse it. LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("a chain as long as the arrays is refused", - !load(motion, constrained_animation(MAX_CHAIN_LENGTH), buffer.data(), (S32)buffer.size())); + UNSCOPED_INFO("a chain as long as the arrays is refused"); + CHECK_FALSE(load(motion, constrained_animation(MAX_CHAIN_LENGTH), buffer.data(), (S32)buffer.size())); } { // and one past anything the skeleton could supply, which the // joint count check has always refused LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("a chain longer than the skeleton is refused", - !load(motion, constrained_animation(200), buffer.data(), (S32)buffer.size())); + UNSCOPED_INFO("a chain longer than the skeleton is refused"); + CHECK_FALSE(load(motion, constrained_animation(200), buffer.data(), (S32)buffer.size())); } } - template<> template<> - void llkeyframemotion_object::test<9>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: the solver puts back joints it wrote when a higher priority stops it", "[LLKeyframeMotion]") { // The solver writes the chain's joints with the motion's own rotations // to measure against, and has to put them back. It gives up part way @@ -778,14 +857,14 @@ namespace // joint it had already written holding a pose nothing asked for. LLKeyframeMotion motion(LLUUID::generateNewID()); std::vector buffer(4096); - ensure("the constrained animation loads", - load(motion, constrained_animation(1), buffer.data(), (S32)buffer.size())); + REQUIRE(load(motion, constrained_animation(1), buffer.data(), (S32)buffer.size())); // The chain runs up from the collision volume: its parent and then its // grandparent, which for the test character is mHead and mNeck. LLJoint* first_in_chain = mCharacter.getJoint("mHead"); LLJoint* second_in_chain = mCharacter.getJoint("mNeck"); - ensure("the chain joints resolve", first_in_chain != nullptr && second_in_chain != nullptr); + REQUIRE(first_in_chain != nullptr); + REQUIRE(second_in_chain != nullptr); const LLQuaternion marker(1.1f, LLVector3::x_axis); first_in_chain->setRotation(marker); @@ -798,79 +877,11 @@ namespace motion.onUpdate(1.f, mask); const LLQuaternion after = first_in_chain->getRotation(); - ensure_approximately_equals_range("the joint the solver wrote is put back", - fabsf(dot(after, marker)), 1.f, 1e-5f); - } - - template<> template<> - void llkeyframemotion_object::test<7>() - { - // What was read can be written back and read again, and the second - // reading agrees with the first joint for joint and key for key. The - // format quantizes on the way out, so this holds only because the - // first reading was already quantized. - LLKeyframeMotion first(LLUUID::generateNewID()); - std::vector buffer(4096); - ensure("the animation is accepted", load(first, two_joint_animation(), buffer.data(), (S32)buffer.size())); - - std::vector written(first.getFileSize()); - LLDataPackerBinaryBuffer writer(written.data(), (S32)written.size()); - ensure("it serializes", first.serialize(writer)); - - LLKeyframeMotion second(LLUUID::generateNewID()); - second.setCharacter(&mCharacter); - LLDataPackerBinaryBuffer reader(written.data(), writer.getCurrentSize()); - ensure("what was written reads back", second.deserialize(reader, second.getID())); - - ensure_equals("same joint count", second.getNumJointMotions(), first.getNumJointMotions()); - ensure_approximately_equals("same duration", second.getDuration(), first.getDuration(), 16); - ensure_equals("same base priority", (S32)second.getPriority(), (S32)first.getPriority()); - - LLPose* first_pose = first.getPose(); - LLPose* second_pose = second.getPose(); - ensure_equals("same joint state count", - second_pose->getNumJointStates(), first_pose->getNumJointStates()); - for (const char* name : { "mPelvis", "mTorso" }) - { - LLJointState* a = first_pose->findJointState(std::string(name)); - LLJointState* b = second_pose->findJointState(std::string(name)); - ensure("joint survived the round trip", a != nullptr && b != nullptr); - ensure_equals("same usage", b->getUsage(), a->getUsage()); - ensure_equals("same priority", (S32)b->getPriority(), (S32)a->getPriority()); - } - - // The keys themselves, sampled the way playback samples them. Without - // this the round trip could write a joint's name and another joint's - // curve and nothing here would notice. - for (F32 time = 0.f; time < 2.f; time += 0.25f) - { - for (const char* name : { "mPelvis", "mTorso" }) - { - LLJointState* a = sampleAt(first, time, name); - LLJointState* b = sampleAt(second, time, name); - ensure("joint sampled on both sides", a != nullptr && b != nullptr); - - const LLQuaternion rot_a = a->getRotation(); - const LLQuaternion rot_b = b->getRotation(); - ensure_approximately_equals_range("same rotation after the round trip", - fabsf(dot(rot_a, rot_b)), 1.f, 1e-4f); - - if (a->getUsage() & LLJointState::POS) - { - const LLVector3 pos_a = a->getPosition(); - const LLVector3 pos_b = b->getPosition(); - for (S32 i = 0; i < 3; ++i) - { - ensure_approximately_equals_range("same position after the round trip", - pos_b.mV[i], pos_a.mV[i], 1e-4f); - } - } - } - } + UNSCOPED_INFO("the joint the solver wrote is put back"); + CHECK_THAT(fabsf(dot(after, marker)), Catch::Matchers::WithinAbs(1.f, 1e-5f)); } - template<> template<> - void llkeyframemotion_object::test<10>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: a joint claimed at full weight is not sampled unless it also moves", "[LLKeyframeMotion]") { // A joint already turned by a motion at full weight, at a priority // this one cannot reach, is not sampled: the blend would interpolate @@ -879,7 +890,7 @@ namespace LLKeyframeMotion motion(LLUUID::generateNewID()); std::vector buffer(4096); const AssetAnimation anim = two_joint_animation(); - ensure("the animation loads", load(motion, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); U8 nothing_claimed[LL_CHARACTER_MAX_ANIMATED_JOINTS] = {}; U8 all_claimed[LL_CHARACTER_MAX_ANIMATED_JOINTS]; @@ -888,33 +899,33 @@ namespace LLPose* pose = motion.getPose(); LLJointState* torso = pose->findJointState(std::string("mTorso")); LLJointState* pelvis = pose->findJointState(std::string("mPelvis")); - ensure("both joints are in the pose", torso != nullptr && pelvis != nullptr); - ensure_equals("the torso only rotates", torso->getUsage(), (U32)LLJointState::ROT); + REQUIRE(torso != nullptr); + REQUIRE(pelvis != nullptr); + CHECK(torso->getUsage() == (U32)LLJointState::ROT); const LLQuaternion marker(2.4f, LLVector3(0.f, 0.f, 1.f)); // claimed: the curves are not read and the state keeps what it held torso->setRotation(marker); motion.onUpdate(1.5f, all_claimed); - ensure_approximately_equals_range("a claimed rotation is left alone", - fabsf(dot(torso->getRotation(), marker)), 1.f, 1e-5f); + UNSCOPED_INFO("a claimed rotation is left alone"); + CHECK_THAT(fabsf(dot(torso->getRotation(), marker)), Catch::Matchers::WithinAbs(1.f, 1e-5f)); // and unclaimed, the same sample writes it motion.onUpdate(1.5f, nothing_claimed); - ensure("an unclaimed rotation is sampled", - fabsf(dot(torso->getRotation(), marker)) < 0.999f); + UNSCOPED_INFO("an unclaimed rotation is sampled"); + CHECK(fabsf(dot(torso->getRotation(), marker)) < 0.999f); // The pelvis carries a position as well, which is summed on its own // account however much of the rotation is spoken for, so it is sampled // whatever the mask says. pelvis->setRotation(marker); motion.onUpdate(0.5f, all_claimed); - ensure("a joint that also moves is sampled whatever is claimed", - fabsf(dot(pelvis->getRotation(), marker)) < 0.999f); + UNSCOPED_INFO("a joint that also moves is sampled whatever is claimed"); + CHECK(fabsf(dot(pelvis->getRotation(), marker)) < 0.999f); } - template<> template<> - void llkeyframemotion_object::test<11>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: deserialize refuses a malformed header", "[LLKeyframeMotion]") { // The header, field by field, with one thing wrong at a time. Every // one of these arrives in an animation another avatar can play at you. @@ -925,12 +936,14 @@ namespace auto refuses = [&](const char* what, const AssetAnimation& anim) { LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure(what, !load(motion, anim, buffer.data(), (S32)buffer.size())); + INFO(what); + REQUIRE_FALSE(load(motion, anim, buffer.data(), (S32)buffer.size())); }; auto accepts = [&](const char* what, const AssetAnimation& anim) { LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure(what, load(motion, anim, buffer.data(), (S32)buffer.size())); + INFO(what); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); }; { @@ -1002,8 +1015,7 @@ namespace } } - template<> template<> - void llkeyframemotion_object::test<12>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: deserialize refuses malformed joints but accepts an unknown name", "[LLKeyframeMotion]") { // The joints, the same way. std::vector buffer(8192); @@ -1011,11 +1023,13 @@ namespace auto refuses = [&](const char* what, const AssetAnimation& anim) { LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure(what, !load(motion, anim, buffer.data(), (S32)buffer.size())); + INFO(what); + REQUIRE_FALSE(load(motion, anim, buffer.data(), (S32)buffer.size())); }; for (const char* special : { "mRoot", "mScreen" }) { + CAPTURE(special); AssetAnimation anim = two_joint_animation(); anim.mJoints[0].mName = special; refuses("an animation naming a special joint is refused", anim); @@ -1063,14 +1077,14 @@ namespace AssetAnimation anim = two_joint_animation(); anim.mJoints[1].mName = "mNotAJointAtAll"; LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("an unknown joint does not sink the animation", - load(motion, anim, buffer.data(), (S32)buffer.size())); - ensure_equals("and its joint motion is still there", motion.getNumJointMotions(), 2); + UNSCOPED_INFO("an unknown joint does not sink the animation"); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); + UNSCOPED_INFO("and its joint motion is still there"); + CHECK(motion.getNumJointMotions() == 2); } } - template<> template<> - void llkeyframemotion_object::test<13>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: deserialize refuses malformed constraints", "[LLKeyframeMotion]") { // Constraints. The chain length is the one that used to run off the // end of five fixed arrays. @@ -1079,12 +1093,14 @@ namespace auto refuses = [&](const char* what, const AssetAnimation& anim) { LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure(what, !load(motion, anim, buffer.data(), (S32)buffer.size())); + INFO(what); + REQUIRE_FALSE(load(motion, anim, buffer.data(), (S32)buffer.size())); }; auto accepts = [&](const char* what, const AssetAnimation& anim) { LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure(what, load(motion, anim, buffer.data(), (S32)buffer.size())); + INFO(what); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); }; { @@ -1119,8 +1135,7 @@ namespace } } - template<> template<> - void llkeyframemotion_object::test<14>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: deserialize refuses every truncation of an animation", "[LLKeyframeMotion]") { // Cut anywhere and it is refused rather than half read. Every prefix // of a well formed animation, so the cut lands in each field in turn, @@ -1131,24 +1146,27 @@ namespace LLDataPackerBinaryBuffer writer(buffer.data(), (S32)buffer.size()); pack_animation(writer, anim); const S32 whole = writer.getCurrentSize(); - ensure("the animation is worth cutting up", whole > 40); + UNSCOPED_INFO("the animation is worth cutting up"); + CHECK(whole > 40); for (S32 length = 0; length < whole; ++length) { + CAPTURE(length); LLKeyframeMotion motion(LLUUID::generateNewID()); motion.setCharacter(&mCharacter); LLDataPackerBinaryBuffer reader(buffer.data(), length); - ensure("an animation cut short is refused", !motion.deserialize(reader, motion.getID())); + UNSCOPED_INFO("an animation cut short is refused"); + REQUIRE_FALSE(motion.deserialize(reader, motion.getID())); } LLKeyframeMotion motion(LLUUID::generateNewID()); motion.setCharacter(&mCharacter); LLDataPackerBinaryBuffer reader(buffer.data(), whole); - ensure("and the whole of it is not", motion.deserialize(reader, motion.getID())); + UNSCOPED_INFO("and the whole of it is not"); + CHECK(motion.deserialize(reader, motion.getID())); } - template<> template<> - void llkeyframemotion_object::test<15>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: deserialize refuses every truncation of a constrained animation", "[LLKeyframeMotion]") { // The same for a constrained animation, whose tail is the part with // the volume names and the four ease times in it. @@ -1161,21 +1179,22 @@ namespace for (S32 length = 0; length < whole; ++length) { + CAPTURE(length); LLKeyframeMotion motion(LLUUID::generateNewID()); motion.setCharacter(&mCharacter); LLDataPackerBinaryBuffer reader(buffer.data(), length); - ensure("a constrained animation cut short is refused", - !motion.deserialize(reader, motion.getID())); + UNSCOPED_INFO("a constrained animation cut short is refused"); + REQUIRE_FALSE(motion.deserialize(reader, motion.getID())); } LLKeyframeMotion motion(LLUUID::generateNewID()); motion.setCharacter(&mCharacter); LLDataPackerBinaryBuffer reader(buffer.data(), whole); - ensure("and the whole of it is not", motion.deserialize(reader, motion.getID())); + UNSCOPED_INFO("and the whole of it is not"); + CHECK(motion.deserialize(reader, motion.getID())); } - template<> template<> - void llkeyframemotion_object::test<16>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: the data cache shares what was read by reference count", "[LLKeyframeMotion]") { // What an animation was read into is kept so that playing it again // costs nothing, and it belongs to the cache and to every motion @@ -1183,31 +1202,32 @@ namespace std::vector buffer(8192); const AssetAnimation anim = two_joint_animation(); - ensure_equals("the cache starts empty", LLKeyframeDataCache::size(), 0u); + CHECK(LLKeyframeDataCache::size() == 0u); LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("the animation is read", load(motion, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); - ensure_equals("and kept", LLKeyframeDataCache::size(), 1u); + UNSCOPED_INFO("the cache keeps what was read"); + CHECK(LLKeyframeDataCache::size() == 1u); LLKeyframeMotion::JointMotionList* held = LLKeyframeDataCache::getKeyframeData(motion.getID()); - ensure("under the id it was read for", held != nullptr); - ensure_equals("with the joints it was read with", held->getNumJointMotions(), 2u); + REQUIRE(held != nullptr); + CHECK(held->getNumJointMotions() == 2u); // Counted rather than owned: the cache has one hold on it and the // motion reading it has the other. - ensure_equals("two things are holding it", held->getNumRefs(), 2); + CHECK(held->getNumRefs() == 2); { LLKeyframeMotion second(motion.getID()); - ensure_equals("a second motion reading it makes three", - second.onInitialize(&mCharacter), LLMotion::STATUS_SUCCESS); - ensure_equals("holds", held->getNumRefs(), 3); + CHECK(second.onInitialize(&mCharacter) == LLMotion::STATUS_SUCCESS); + UNSCOPED_INFO("a second motion reading it makes three holds"); + CHECK(held->getNumRefs() == 3); } - ensure_equals("and letting go puts it back", held->getNumRefs(), 2); + UNSCOPED_INFO("and letting go puts it back"); + CHECK(held->getNumRefs() == 2); } - template<> template<> - void llkeyframemotion_object::test<17>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: clearing the cache leaves a playing motion its animation", "[LLKeyframeMotion]") { // Emptying the cache does not stop an animation somebody is playing. // This is what the cache could not do before: it held the animation @@ -1216,25 +1236,26 @@ namespace const AssetAnimation anim = two_joint_animation(); LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("the animation is read", load(motion, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); const F32 duration = motion.getDuration(); - ensure("it has a duration to lose", duration > 0.f); + UNSCOPED_INFO("it has a duration to lose"); + CHECK(duration > 0.f); LLKeyframeDataCache::clear(); - ensure_equals("the cache lets go of it", LLKeyframeDataCache::size(), 0u); - ensure("and no longer hands it out", - LLKeyframeDataCache::getKeyframeData(motion.getID()) == nullptr); + CHECK(LLKeyframeDataCache::size() == 0u); + CHECK(LLKeyframeDataCache::getKeyframeData(motion.getID()) == nullptr); - ensure_approximately_equals("the motion still has it", motion.getDuration(), duration, 16); - ensure_equals("whole", motion.getNumJointMotions(), 2); + UNSCOPED_INFO("the motion still has it"); + CHECK_THAT(motion.getDuration(), ll_test::WithinBits(duration, 16)); + CHECK(motion.getNumJointMotions() == 2); // And it still plays, which reads the curves the cache used to own. LLJointState* state = sampleAt(motion, 0.5f, "mPelvis"); - ensure("a motion whose cache entry is gone still animates", state != nullptr); + UNSCOPED_INFO("a motion whose cache entry is gone still animates"); + CHECK(state != nullptr); } - template<> template<> - void llkeyframemotion_object::test<18>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: removeKeyframeData leaves a playing motion its animation", "[LLKeyframeMotion]") { // Removing one by name is the same: it goes from the cache and stays // with whoever is playing it. @@ -1242,19 +1263,20 @@ namespace const AssetAnimation anim = two_joint_animation(); LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("the animation is read", load(motion, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); LLKeyframeDataCache::removeKeyframeData(motion.getID()); - ensure_equals("the cache lets go of it", LLKeyframeDataCache::size(), 0u); - ensure_equals("the motion does not", motion.getNumJointMotions(), 2); + CHECK(LLKeyframeDataCache::size() == 0u); + UNSCOPED_INFO("the motion does not let go of it"); + CHECK(motion.getNumJointMotions() == 2); // Removing one nobody has is not an error. LLKeyframeDataCache::removeKeyframeData(LLUUID::generateNewID()); - ensure_equals("and the cache is no worse for it", LLKeyframeDataCache::size(), 0u); + UNSCOPED_INFO("removing an id nobody has leaves the cache as it was"); + CHECK(LLKeyframeDataCache::size() == 0u); } - template<> template<> - void llkeyframemotion_object::test<19>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: purge drops only what no motion is holding", "[LLKeyframeMotion]") { // The purge drops what no motion is holding and keeps what one is, // which is the whole of how the cache is allowed to end a session @@ -1263,31 +1285,32 @@ namespace const AssetAnimation anim = two_joint_animation(); LLKeyframeMotion kept(LLUUID::generateNewID()); - ensure("the first animation is read", load(kept, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(kept, anim, buffer.data(), (S32)buffer.size())); LLUUID dropped_id; { LLKeyframeMotion dropped(LLUUID::generateNewID()); dropped_id = dropped.getID(); - ensure("the second is read", load(dropped, anim, buffer.data(), (S32)buffer.size())); - ensure_equals("both are in the cache", LLKeyframeDataCache::size(), 2u); + REQUIRE(load(dropped, anim, buffer.data(), (S32)buffer.size())); + CHECK(LLKeyframeDataCache::size() == 2u); LLKeyframeDataCache::purge(); - ensure_equals("a purge keeps what is being played", LLKeyframeDataCache::size(), 2u); + UNSCOPED_INFO("a purge keeps what is being played"); + CHECK(LLKeyframeDataCache::size() == 2u); } // The second motion is gone, so nothing can play what it was reading. LLKeyframeDataCache::purge(); - ensure_equals("a purge drops what nobody is playing", LLKeyframeDataCache::size(), 1u); - ensure("the one that went is the one nobody had", - LLKeyframeDataCache::getKeyframeData(dropped_id) == nullptr); - ensure("and the one that stayed is the one somebody had", - LLKeyframeDataCache::getKeyframeData(kept.getID()) != nullptr); - ensure_equals("still whole", kept.getNumJointMotions(), 2); + UNSCOPED_INFO("a purge drops what nobody is playing"); + CHECK(LLKeyframeDataCache::size() == 1u); + UNSCOPED_INFO("the one that went is the one nobody had"); + CHECK(LLKeyframeDataCache::getKeyframeData(dropped_id) == nullptr); + UNSCOPED_INFO("and the one that stayed is the one somebody had"); + CHECK(LLKeyframeDataCache::getKeyframeData(kept.getID()) != nullptr); + CHECK(kept.getNumJointMotions() == 2); } - template<> template<> - void llkeyframemotion_object::test<20>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: two motions on one id share one cached animation", "[LLKeyframeMotion]") { // Two motions reading the same animation share the one copy of it, // and it outlives either of them going. @@ -1300,26 +1323,26 @@ namespace LLKeyframeMotion second(id); { LLKeyframeMotion first(id); - ensure("the animation is read", load(first, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(first, anim, buffer.data(), (S32)buffer.size())); first_list = LLKeyframeDataCache::getKeyframeData(id); - ensure("and kept", first_list != nullptr); + REQUIRE(first_list != nullptr); // The second finds it in the cache rather than reading it again, // which is the path every repeat play of an animation takes. - ensure_equals("the second motion is initialized from the cache", - second.onInitialize(&mCharacter), LLMotion::STATUS_SUCCESS); - ensure_equals("with the joints the first one read", - second.getNumJointMotions(), 2); + UNSCOPED_INFO("the second motion is initialized from the cache"); + CHECK(second.onInitialize(&mCharacter) == LLMotion::STATUS_SUCCESS); + UNSCOPED_INFO("with the joints the first one read"); + CHECK(second.getNumJointMotions() == 2); } LLKeyframeDataCache::purge(); - ensure_equals("one motion going does not take the animation with it", - LLKeyframeDataCache::size(), 1u); - ensure_equals("and it is the same one", LLKeyframeDataCache::getKeyframeData(id), first_list); + UNSCOPED_INFO("one motion going does not take the animation with it"); + CHECK(LLKeyframeDataCache::size() == 1u); + UNSCOPED_INFO("and it is the same one"); + CHECK(LLKeyframeDataCache::getKeyframeData(id) == first_list); } - template<> template<> - void llkeyframemotion_object::test<21>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: a loop seam leads the tail after the last key back to the loop start", "[LLKeyframeMotion]") { // A looping animation whose keys stop before its loop does. Between // the last key and the loop out point the curve used to hold the last @@ -1331,34 +1354,33 @@ namespace // Without a seam the tail holds the last key, which is what a motion // that does not loop wants. - ensure_approximately_equals_range("no seam: the tail holds the last key", - sample(curve, 1.5f).mV[VX], 10.f, 1e-4f); + UNSCOPED_INFO("no seam: the tail holds the last key"); + CHECK_THAT(sample(curve, 1.5f).mV[VX], Catch::Matchers::WithinAbs(10.f, 1e-4f)); // The loop runs to two, a second past the last key, and starts from // the pose at zero. curve.setLoopSeam(true, 0.f, 2.f); - ensure_approximately_equals_range("the last key is still the last key", - sample(curve, 1.f).mV[VX], 10.f, 1e-4f); - ensure_approximately_equals_range("half way along the tail is half way back", - sample(curve, 1.5f).mV[VX], 5.f, 1e-4f); - ensure_approximately_equals_range("and the end of the tail is the loop's first pose", - sample(curve, 2.f).mV[VX], 0.f, 1e-4f); - ensure_approximately_equals_range("past the loop out point it stays there", - sample(curve, 3.f).mV[VX], 0.f, 1e-4f); + UNSCOPED_INFO("the last key is still the last key"); + CHECK_THAT(sample(curve, 1.f).mV[VX], Catch::Matchers::WithinAbs(10.f, 1e-4f)); + UNSCOPED_INFO("half way along the tail is half way back"); + CHECK_THAT(sample(curve, 1.5f).mV[VX], Catch::Matchers::WithinAbs(5.f, 1e-4f)); + UNSCOPED_INFO("and the end of the tail is the loop's first pose"); + CHECK_THAT(sample(curve, 2.f).mV[VX], Catch::Matchers::WithinAbs(0.f, 1e-4f)); + UNSCOPED_INFO("past the loop out point it stays there"); + CHECK_THAT(sample(curve, 3.f).mV[VX], Catch::Matchers::WithinAbs(0.f, 1e-4f)); // Inside the keys nothing changed. - ensure_approximately_equals_range("the keyed range is untouched", - sample(curve, 0.5f).mV[VX], 5.f, 1e-4f); + UNSCOPED_INFO("the keyed range is untouched"); + CHECK_THAT(sample(curve, 0.5f).mV[VX], Catch::Matchers::WithinAbs(5.f, 1e-4f)); // Told it does not loop, the tail goes away again. curve.setLoopSeam(false, 0.f, 2.f); - ensure_approximately_equals_range("without a loop the tail holds again", - sample(curve, 1.5f).mV[VX], 10.f, 1e-4f); + UNSCOPED_INFO("without a loop the tail holds again"); + CHECK_THAT(sample(curve, 1.5f).mV[VX], Catch::Matchers::WithinAbs(10.f, 1e-4f)); } - template<> template<> - void llkeyframemotion_object::test<22>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: a loop seam with no tail to lead holds the last key", "[LLKeyframeMotion]") { // A loop that ends on its last key has no tail to lead anywhere, and // one that ends before its last key never reaches it. @@ -1367,28 +1389,27 @@ namespace set_key(curve, 1.f, LLVector3(10.f, 0.f, 0.f)); curve.setLoopSeam(true, 0.f, 1.f); - ensure_approximately_equals_range("a loop ending on the last key holds past it", - sample(curve, 1.5f).mV[VX], 10.f, 1e-4f); + UNSCOPED_INFO("a loop ending on the last key holds past it"); + CHECK_THAT(sample(curve, 1.5f).mV[VX], Catch::Matchers::WithinAbs(10.f, 1e-4f)); curve.setLoopSeam(true, 0.f, 0.5f); - ensure_approximately_equals_range("a loop ending before the last key holds too", - sample(curve, 1.5f).mV[VX], 10.f, 1e-4f); + UNSCOPED_INFO("a loop ending before the last key holds too"); + CHECK_THAT(sample(curve, 1.5f).mV[VX], Catch::Matchers::WithinAbs(10.f, 1e-4f)); // A curve with one key has nowhere to lead from. PositionCurve single; set_key(single, 0.f, LLVector3(4.f, 0.f, 0.f)); single.setLoopSeam(true, 0.f, 2.f); - ensure_approximately_equals_range("one key is the whole animation", - sample(single, 1.f).mV[VX], 4.f, 1e-4f); + UNSCOPED_INFO("one key is the whole animation"); + CHECK_THAT(sample(single, 1.f).mV[VX], Catch::Matchers::WithinAbs(4.f, 1e-4f)); // An empty curve is asked nothing and says nothing. PositionCurve empty; empty.setLoopSeam(true, 0.f, 2.f); - ensure_equals("an empty curve has no keys", empty.getNumKeys(), 0u); + CHECK(empty.getNumKeys() == 0u); } - template<> template<> - void llkeyframemotion_object::test<23>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: a loop seam reads the loop start pose from the keys every time", "[LLKeyframeMotion]") { // The loop's first pose is read from the keys, not from a tail left // over from the last time the seam was set up. @@ -1397,18 +1418,18 @@ namespace set_key(curve, 1.f, LLVector3(10.f, 0.f, 0.f)); curve.setLoopSeam(true, 0.f, 2.f); - ensure_approximately_equals_range("the tail leads back to zero", - sample(curve, 1.5f).mV[VX], 5.f, 1e-4f); + UNSCOPED_INFO("the tail leads back to zero"); + CHECK_THAT(sample(curve, 1.5f).mV[VX], Catch::Matchers::WithinAbs(5.f, 1e-4f)); // Setting it up again against a later loop in point reads that pose, // and reads it from the keys rather than through the tail it already // has. curve.setLoopSeam(true, 0.5f, 2.f); - ensure_approximately_equals_range("a later loop in point is read from the keys", - sample(curve, 1.5f).mV[VX], 7.5f, 1e-4f); + UNSCOPED_INFO("a later loop in point is read from the keys"); + CHECK_THAT(sample(curve, 1.5f).mV[VX], Catch::Matchers::WithinAbs(7.5f, 1e-4f)); curve.setLoopSeam(true, 0.5f, 2.f); - ensure_approximately_equals_range("and reads the same the second time", - sample(curve, 1.5f).mV[VX], 7.5f, 1e-4f); + UNSCOPED_INFO("and reads the same the second time"); + CHECK_THAT(sample(curve, 1.5f).mV[VX], Catch::Matchers::WithinAbs(7.5f, 1e-4f)); // A rotation curve leads back the same way. RotationCurve rotations; @@ -1418,13 +1439,12 @@ namespace const LLQuaternion at_end = sample(rotations, 2.f); // q and -q are the same rotation, so compare through the dot product. - ensure_approximately_equals_range("the rotation tail ends on the loop's first pose", - fabsf(dot(at_end, LLQuaternion(0.f, LLVector3::z_axis))), - 1.f, 1e-4f); + UNSCOPED_INFO("the rotation tail ends on the loop's first pose"); + CHECK_THAT(fabsf(dot(at_end, LLQuaternion(0.f, LLVector3::z_axis))), + Catch::Matchers::WithinAbs(1.f, 1e-4f)); } - template<> template<> - void llkeyframemotion_object::test<24>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: a hand pose request outlives the animation and respects priority", "[LLKeyframeMotion]") { // Sampling asks the character for the hand pose the animation carries. // The request has to outlive the animation: the motion can go, and the @@ -1436,42 +1456,45 @@ namespace { LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("the animation is read", load(motion, anim, buffer.data(), (S32)buffer.size())); - ensure("nothing is asked for before it is sampled", !mCharacter.hasHandPoseRequest()); + REQUIRE(load(motion, anim, buffer.data(), (S32)buffer.size())); + UNSCOPED_INFO("nothing is asked for before it is sampled"); + CHECK_FALSE(mCharacter.hasHandPoseRequest()); sampleAt(motion, 0.f, "mPelvis"); - ensure("sampling asks for a hand pose", mCharacter.hasHandPoseRequest()); - ensure_equals("the one the animation carries", - mCharacter.getHandPoseRequest(), (S32)LLHandMotion::HAND_POSE_FIST); - ensure_equals("at the animation's highest joint priority", - mCharacter.getHandPoseRequestPriority(), (S32)LLJoint::HIGH_PRIORITY); + UNSCOPED_INFO("sampling asks for a hand pose"); + CHECK(mCharacter.hasHandPoseRequest()); + UNSCOPED_INFO("the one the animation carries"); + CHECK(mCharacter.getHandPoseRequest() == (S32)LLHandMotion::HAND_POSE_FIST); + UNSCOPED_INFO("at the animation's highest joint priority"); + CHECK(mCharacter.getHandPoseRequestPriority() == (S32)LLJoint::HIGH_PRIORITY); } LLKeyframeDataCache::purge(); - ensure_equals("the animation is gone", LLKeyframeDataCache::size(), 0u); - ensure("the request is not", mCharacter.hasHandPoseRequest()); - ensure_equals("and still reads the pose", - mCharacter.getHandPoseRequest(), (S32)LLHandMotion::HAND_POSE_FIST); + UNSCOPED_INFO("the animation is gone"); + CHECK(LLKeyframeDataCache::size() == 0u); + UNSCOPED_INFO("the request is not"); + CHECK(mCharacter.hasHandPoseRequest()); + UNSCOPED_INFO("and still reads the pose"); + CHECK(mCharacter.getHandPoseRequest() == (S32)LLHandMotion::HAND_POSE_FIST); // A motion asking at a lower priority does not take the request away. anim.mHandPose = LLHandMotion::HAND_POSE_POINT; anim.mJoints[0].mPriority = LLJoint::MEDIUM_PRIORITY; LLKeyframeMotion lower(LLUUID::generateNewID()); - ensure("the lower priority animation is read", load(lower, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(lower, anim, buffer.data(), (S32)buffer.size())); sampleAt(lower, 0.f, "mPelvis"); - ensure_equals("a lower priority motion leaves the request standing", - mCharacter.getHandPoseRequest(), (S32)LLHandMotion::HAND_POSE_FIST); + UNSCOPED_INFO("a lower priority motion leaves the request standing"); + CHECK(mCharacter.getHandPoseRequest() == (S32)LLHandMotion::HAND_POSE_FIST); // One asking at the same priority, later, does. anim.mHandPose = LLHandMotion::HAND_POSE_PEACE_R; anim.mJoints[0].mPriority = LLJoint::HIGH_PRIORITY; LLKeyframeMotion equal(LLUUID::generateNewID()); - ensure("the equal priority animation is read", load(equal, anim, buffer.data(), (S32)buffer.size())); + REQUIRE(load(equal, anim, buffer.data(), (S32)buffer.size())); sampleAt(equal, 0.f, "mPelvis"); - ensure_equals("an equal priority motion asking later takes the request", - mCharacter.getHandPoseRequest(), (S32)LLHandMotion::HAND_POSE_PEACE_R); + UNSCOPED_INFO("an equal priority motion asking later takes the request"); + CHECK(mCharacter.getHandPoseRequest() == (S32)LLHandMotion::HAND_POSE_PEACE_R); } - template<> template<> - void llkeyframemotion_object::test<25>() + TEST_CASE_METHOD(llkeyframemotion_data, "LLKeyframeMotion: a loop a day old still advances by the frame time", "[LLKeyframeMotion]") { // A loop that has been playing for a day still moves by the time a frame // took. Its phase comes from the time since activation: a day of that in @@ -1479,7 +1502,7 @@ namespace // or by nothing, never by the 4.2 ms that passed. std::vector buffer(8192); LLKeyframeMotion motion(LLUUID::generateNewID()); - ensure("the animation is read", load(motion, two_joint_animation(), buffer.data(), (S32)buffer.size())); + REQUIRE(load(motion, two_joint_animation(), buffer.data(), (S32)buffer.size())); motion.setStopped(false); const F64 a_day = 86400.0; @@ -1489,6 +1512,7 @@ namespace const F32 before = motion.getLastUpdateTime(); motion.onUpdate(a_day + frame, mask); const F32 after = motion.getLastUpdateTime(); - ensure_approximately_equals_range("the loop moves by the frame", after - before, (F32)frame, 1e-5f); + UNSCOPED_INFO("the loop moves by the frame"); + CHECK_THAT(after - before, Catch::Matchers::WithinAbs((F32)frame, 1e-5f)); } } diff --git a/indra/llcharacter/tests/llmotioncontroller_test.cpp b/indra/llcharacter/tests/llmotioncontroller_test.cpp index ceece16b05..38ab2ee34c 100644 --- a/indra/llcharacter/tests/llmotioncontroller_test.cpp +++ b/indra/llcharacter/tests/llmotioncontroller_test.cpp @@ -26,7 +26,8 @@ #include "llmotioncontroller.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" namespace { @@ -38,22 +39,11 @@ namespace constexpr S32 FRAMES_PER_QUANTUM = 16; } -namespace tut -{ namespace { - struct llmotioncontroller_data - { - }; - typedef test_group llmotioncontroller_test; - typedef llmotioncontroller_test::object llmotioncontroller_object; - tut::llmotioncontroller_test llmotioncontroller_testcase("LLMotionController"); -} - typedef LLMotionController::QuantumStep QuantumStep; - template<> template<> - void llmotioncontroller_object::test<1>() + TEST_CASE("LLMotionController: computeQuantumStep crosses one boundary per quantum of wall time", "[LLMotionController]") { // Animation time has to track wall time. Run a clock for ten seconds // at the largest quantum the viewer hands out, a quarter second, and @@ -74,11 +64,11 @@ namespace last_count = q.count; } } - ensure_equals("ten seconds at a quarter-second quantum crosses forty boundaries", crossings, 40); + UNSCOPED_INFO("ten seconds at a quarter-second quantum crosses forty boundaries"); + CHECK(crossings == 40); } - template<> template<> - void llmotioncontroller_object::test<2>() + TEST_CASE("LLMotionController: the quantized target stays at most one quantum ahead of real time", "[LLMotionController]") { // The quantized clock runs one quantum ahead of real time, never more // and never behind: the pose it computes is the target the frames in @@ -91,13 +81,15 @@ namespace continuous += dt; const QuantumStep q = LLMotionController::computeQuantumStep(continuous, step, 0); const F32 target = q.count * step; - ensure("target is ahead of real time", target > continuous); - ensure("target is no more than one quantum ahead", target <= continuous + step + 1e-5f); + CAPTURE(frame); + UNSCOPED_INFO("target is ahead of real time"); + REQUIRE(target > continuous); + UNSCOPED_INFO("target is no more than one quantum ahead"); + REQUIRE(target <= continuous + step + 1e-5f); } } - template<> template<> - void llmotioncontroller_object::test<3>() + TEST_CASE("LLMotionController: interp never falls within a quantum and quanta have equal frames", "[LLMotionController]") { // Within a quantum interp climbs from zero toward one and never comes // back; crossing a boundary is reported exactly once, and every @@ -111,41 +103,48 @@ namespace { continuous += FRAME; const QuantumStep q = LLMotionController::computeQuantumStep(continuous, QUANTUM, last_count); - ensure("interp is at least zero", q.interp >= 0.f); - ensure("interp is below one", q.interp < 1.f); + CAPTURE(frame); + REQUIRE(q.interp >= 0.f); + REQUIRE(q.interp < 1.f); if (q.advanced) { - ensure_equals("advanced means the count moved by one", q.count, last_count + 1); - ensure_equals("a quantum is the same number of frames every time", frames_in_quantum, FRAMES_PER_QUANTUM); + UNSCOPED_INFO("advanced means the count moved by one"); + REQUIRE(q.count == last_count + 1); + UNSCOPED_INFO("a quantum is the same number of frames every time"); + REQUIRE(frames_in_quantum == FRAMES_PER_QUANTUM); frames_in_quantum = 0; last_count = q.count; } else { - ensure("interp does not go backwards inside a quantum", q.interp >= last_interp); + UNSCOPED_INFO("interp does not go backwards inside a quantum"); + REQUIRE(q.interp >= last_interp); } last_interp = q.interp; ++frames_in_quantum; } } - template<> template<> - void llmotioncontroller_object::test<4>() + TEST_CASE("LLMotionController: time at or before zero heads for the first boundary", "[LLMotionController]") { // Time before the first quantum boundary heads for the first boundary, // and time before zero is treated as zero. const QuantumStep at_start = LLMotionController::computeQuantumStep(0.f, QUANTUM, 0); - ensure_equals("the first target is the first boundary", at_start.count, 1); - ensure_equals("nothing of the quantum has elapsed", at_start.interp, 0.f); - ensure("a fresh clock has advanced from nowhere", at_start.advanced); + UNSCOPED_INFO("the first target is the first boundary"); + REQUIRE(at_start.count == 1); + UNSCOPED_INFO("nothing of the quantum has elapsed"); + CHECK(at_start.interp == 0.f); + UNSCOPED_INFO("a fresh clock has advanced from nowhere"); + CHECK(at_start.advanced); const QuantumStep before = LLMotionController::computeQuantumStep(-1.f, QUANTUM, 1); - ensure_equals("negative time does not run the count backwards", before.count, 1); - ensure_equals("nor does it produce a negative interp", before.interp, 0.f); + UNSCOPED_INFO("negative time does not run the count backwards"); + REQUIRE(before.count == 1); + UNSCOPED_INFO("nor does it produce a negative interp"); + CHECK(before.interp == 0.f); } - template<> template<> - void llmotioncontroller_object::test<5>() + TEST_CASE("LLMotionController: quantumInterpolant keeps the blended pose on the linear track", "[LLMotionController]") { // The blender lerps from wherever the pose is now toward the target. To // track real time it needs the fraction of the distance still to go, @@ -159,41 +158,51 @@ namespace { const F32 u = LLMotionController::quantumInterpolant(interp, last_interp); pose = lerp(pose, target, u); - ensure_approximately_equals("the pose sits on the linear track", pose, target * interp, 16); + CAPTURE(interp); + UNSCOPED_INFO("the pose sits on the linear track"); + REQUIRE_THAT(pose, ll_test::WithinBits(target * interp, 16)); last_interp = interp; } - ensure_approximately_equals("the pose has arrived at the boundary", pose, target, 16); + UNSCOPED_INFO("the pose has arrived at the boundary"); + CHECK_THAT(pose, ll_test::WithinBits(target, 16)); + } + + TEST_CASE("LLMotionController: quantumInterpolant is zero with no progress and one at the end", "[LLMotionController]") + { + // The edges: no progress is no movement, reaching the end is all of + // the way, and a clock already at the end has nothing left to move. + UNSCOPED_INFO("no progress moves nothing"); + CHECK(LLMotionController::quantumInterpolant(0.3f, 0.3f) == 0.f); + UNSCOPED_INFO("reaching the end moves all the way"); + CHECK(LLMotionController::quantumInterpolant(1.f, 0.3f) == 1.f); + UNSCOPED_INFO("at the end already is all the way"); + CHECK(LLMotionController::quantumInterpolant(0.5f, 1.f) == 1.f); + UNSCOPED_INFO("from nothing to something is that fraction"); + CHECK(LLMotionController::quantumInterpolant(0.25f, 0.f) == 0.25f); } - template<> template<> - void llmotioncontroller_object::test<7>() + TEST_CASE("LLMotionController: quantizeTimeStep lands on sixteenths up to a quarter", "[LLMotionController]") { // The requested quantum is a smooth function of size and crowd and // would move a little every frame. It lands on sixteenths, never // finer than one and never coarser than a quarter, and anything under // a sixteenth is not worth quantizing at all. - ensure_equals("a quarter stays a quarter", LLMotionController::quantizeTimeStep(0.25f), 0.25f); - ensure_equals("more than a quarter is capped", LLMotionController::quantizeTimeStep(0.4f), 0.25f); - ensure_equals("a fifth rounds down to three sixteenths", LLMotionController::quantizeTimeStep(0.2f), 0.1875f); - ensure_equals("just under an eighth is a sixteenth", LLMotionController::quantizeTimeStep(0.124f), 0.0625f); - ensure_equals("a twentieth is too fine to bother with", LLMotionController::quantizeTimeStep(0.05f), 0.f); - ensure_equals("nothing asked for is nothing", LLMotionController::quantizeTimeStep(0.f), 0.f); - ensure_equals("a negative request is nothing", LLMotionController::quantizeTimeStep(-1.f), 0.f); + CHECK(LLMotionController::quantizeTimeStep(0.25f) == 0.25f); + UNSCOPED_INFO("more than a quarter is capped"); + CHECK(LLMotionController::quantizeTimeStep(0.4f) == 0.25f); + UNSCOPED_INFO("a fifth rounds down to three sixteenths"); + CHECK(LLMotionController::quantizeTimeStep(0.2f) == 0.1875f); + UNSCOPED_INFO("just under an eighth is a sixteenth"); + CHECK(LLMotionController::quantizeTimeStep(0.124f) == 0.0625f); + UNSCOPED_INFO("a twentieth is too fine to bother with"); + CHECK(LLMotionController::quantizeTimeStep(0.05f) == 0.f); + CHECK(LLMotionController::quantizeTimeStep(0.f) == 0.f); + CHECK(LLMotionController::quantizeTimeStep(-1.f) == 0.f); // Every rung is exact in binary, so a clock stepping by one is exact. F32 t = 0.f; for (S32 i = 0; i < 64; ++i) { t += LLMotionController::quantizeTimeStep(0.0625f); } - ensure_equals("sixty-four sixteenths is exactly four", t, 4.f); - } - - template<> template<> - void llmotioncontroller_object::test<6>() - { - // The edges: no progress is no movement, reaching the end is all of - // the way, and a clock already at the end has nothing left to move. - ensure_equals("no progress moves nothing", LLMotionController::quantumInterpolant(0.3f, 0.3f), 0.f); - ensure_equals("reaching the end moves all the way", LLMotionController::quantumInterpolant(1.f, 0.3f), 1.f); - ensure_equals("at the end already is all the way", LLMotionController::quantumInterpolant(0.5f, 1.f), 1.f); - ensure_equals("from nothing to something is that fraction", LLMotionController::quantumInterpolant(0.25f, 0.f), 0.25f); + UNSCOPED_INFO("sixty-four sixteenths is exactly four"); + CHECK(t == 4.f); } } diff --git a/indra/llcharacter/tests/llmultigesture_test.cpp b/indra/llcharacter/tests/llmultigesture_test.cpp index 20a20db4e7..b8e6840e62 100644 --- a/indra/llcharacter/tests/llmultigesture_test.cpp +++ b/indra/llcharacter/tests/llmultigesture_test.cpp @@ -28,7 +28,7 @@ #include "lldatapacker.h" -#include "../test/lltut.h" +#include #include #include @@ -75,7 +75,7 @@ namespace { std::vector buffer(gesture.getMaxSerialSize() + 1, '\0'); LLDataPackerAsciiBuffer dp(buffer.data(), (S32)buffer.size()); - tut::ensure("the gesture serializes", gesture.serialize(dp)); + REQUIRE(gesture.serialize(dp)); buffer.resize(dp.getCurrentSize()); buffer.back() = '\0'; return buffer; @@ -86,7 +86,7 @@ namespace { std::vector buffer(step.getMaxSerialSize() + 1, '\0'); LLDataPackerAsciiBuffer dp(buffer.data(), (S32)buffer.size()); - tut::ensure("the step serializes", step.serialize(dp)); + REQUIRE(step.serialize(dp)); buffer.resize(dp.getCurrentSize()); buffer.back() = '\0'; return buffer; @@ -98,9 +98,14 @@ namespace { const std::string text(bytes.data()); const size_t last = text.find_last_of('\n'); - tut::ensure("the step has a last field", last != std::string::npos && last > 0); + { + INFO("the step has a last field"); + REQUIRE(last != std::string::npos); + REQUIRE(last > 0); + } const size_t previous = text.find_last_of('\n', last - 1); - tut::ensure("and one before it", previous != std::string::npos); + UNSCOPED_INFO("and one before it"); + REQUIRE(previous != std::string::npos); std::vector shortened(bytes.begin(), bytes.begin() + previous + 1); shortened.push_back('\0'); @@ -116,20 +121,9 @@ namespace } } -namespace tut -{ namespace { - struct llmultigesture_data - { - }; - typedef test_group llmultigesture_test; - typedef llmultigesture_test::object llmultigesture_object; - tut::llmultigesture_test llmultigesture_testcase("LLMultiGesture"); -} - - template<> template<> - void llmultigesture_object::test<1>() + TEST_CASE("LLMultiGesture: a gesture it wrote reads back", "[LLMultiGesture]") { // What it writes it reads back. std::unique_ptr written = sample_gesture(); @@ -137,24 +131,21 @@ namespace LLMultiGesture read; LLDataPackerAsciiBuffer dp(bytes.data(), (S32)bytes.size()); - ensure("a gesture it wrote is accepted", read.deserialize(dp)); - - ensure_equals("key", read.mKey, written->mKey); - ensure_equals("mask", read.mMask, written->mMask); - ensure_equals("trigger", read.mTrigger, written->mTrigger); - ensure_equals("replace text", read.mReplaceText, written->mReplaceText); - ensure_equals("step count", read.mSteps.size(), written->mSteps.size()); - - ensure_equals("the animation step came back", - static_cast(read.mSteps[0])->mAnimName, - std::string("express_wave")); - ensure_equals("the chat step came back", - static_cast(read.mSteps[2])->mChatText, - std::string("hello")); + REQUIRE(read.deserialize(dp)); + + CHECK(read.mKey == written->mKey); + CHECK(read.mMask == written->mMask); + CHECK(read.mTrigger == written->mTrigger); + CHECK(read.mReplaceText == written->mReplaceText); + REQUIRE(read.mSteps.size() == written->mSteps.size()); + + CHECK(static_cast(read.mSteps[0])->mAnimName == + std::string("express_wave")); + CHECK(static_cast(read.mSteps[2])->mChatText == + std::string("hello")); } - template<> template<> - void llmultigesture_object::test<2>() + TEST_CASE("LLMultiGesture: a gesture cut short anywhere is refused", "[LLMultiGesture]") { // Cut anywhere, and it is refused rather than half read. Every prefix // of a real gesture, so the cut lands in each field in turn. @@ -173,12 +164,11 @@ namespace LLMultiGesture read; LLDataPackerAsciiBuffer dp(truncated.data(), (S32)truncated.size()); - ensure("a gesture cut short is refused", !read.deserialize(dp)); + REQUIRE_FALSE(read.deserialize(dp)); } } - template<> template<> - void llmultigesture_object::test<3>() + TEST_CASE("LLMultiGesture: a step count with no steps behind it is refused", "[LLMultiGesture]") { // The step count says how many to read and arrives in the asset. Set // against a body that has none of them, it used to be the number of @@ -198,12 +188,12 @@ namespace LLMultiGesture read; LLDataPackerAsciiBuffer dp(bytes.data(), (S32)bytes.size()); - ensure("a step count with no steps behind it is refused", !read.deserialize(dp)); - ensure("and nothing was built from it", read.mSteps.empty()); + CHECK_FALSE(read.deserialize(dp)); + UNSCOPED_INFO("and nothing was built from it"); + CHECK(read.mSteps.empty()); } - template<> template<> - void llmultigesture_object::test<4>() + TEST_CASE("LLMultiGesture: a step count past one real step is refused", "[LLMultiGesture]") { // The same, with one real step in front of the lie: what was read is // kept, the gesture is still refused, and the count does not turn into @@ -226,13 +216,12 @@ namespace LLMultiGesture read; LLDataPackerAsciiBuffer dp(bytes.data(), (S32)bytes.size()); - ensure("still refused", !read.deserialize(dp)); - ensure("and stopped at the one step the body actually held", - read.mSteps.size() <= 1u); + CHECK_FALSE(read.deserialize(dp)); + UNSCOPED_INFO("and stopped at the one step the body actually held"); + CHECK(read.mSteps.size() <= 1u); } - template<> template<> - void llmultigesture_object::test<5>() + TEST_CASE("LLMultiGesture: each step reads back and refuses a missing last field", "[LLMultiGesture]") { // Each step reads back what it wrote, and refuses what is missing its // last field. Through a whole gesture the difference does not show -- @@ -246,13 +235,14 @@ namespace const std::vector bytes = serialize_step(full); LLGestureStepAnimation whole; - ensure("the animation step reads back", step_accepts(whole, bytes)); - ensure_equals("with its name", whole.mAnimName, full.mAnimName); - ensure_equals("and its flags", whole.mFlags, full.mFlags); + UNSCOPED_INFO("the animation step reads back"); + CHECK(step_accepts(whole, bytes)); + CHECK(whole.mAnimName == full.mAnimName); + CHECK(whole.mFlags == full.mFlags); LLGestureStepAnimation cut; - ensure("an animation step missing its flags is refused", - !step_accepts(cut, without_last_field(bytes))); + UNSCOPED_INFO("an animation step missing its flags is refused"); + CHECK_FALSE(step_accepts(cut, without_last_field(bytes))); } { LLGestureStepSound full; @@ -262,12 +252,13 @@ namespace const std::vector bytes = serialize_step(full); LLGestureStepSound whole; - ensure("the sound step reads back", step_accepts(whole, bytes)); - ensure_equals("with its name", whole.mSoundName, full.mSoundName); + UNSCOPED_INFO("the sound step reads back"); + CHECK(step_accepts(whole, bytes)); + CHECK(whole.mSoundName == full.mSoundName); LLGestureStepSound cut; - ensure("a sound step missing its flags is refused", - !step_accepts(cut, without_last_field(bytes))); + UNSCOPED_INFO("a sound step missing its flags is refused"); + CHECK_FALSE(step_accepts(cut, without_last_field(bytes))); } { LLGestureStepChat full; @@ -276,13 +267,14 @@ namespace const std::vector bytes = serialize_step(full); LLGestureStepChat whole; - ensure("the chat step reads back", step_accepts(whole, bytes)); - ensure_equals("with its text", whole.mChatText, full.mChatText); - ensure_equals("and its flags", whole.mFlags, full.mFlags); + UNSCOPED_INFO("the chat step reads back"); + CHECK(step_accepts(whole, bytes)); + CHECK(whole.mChatText == full.mChatText); + CHECK(whole.mFlags == full.mFlags); LLGestureStepChat cut; - ensure("a chat step missing its flags is refused", - !step_accepts(cut, without_last_field(bytes))); + UNSCOPED_INFO("a chat step missing its flags is refused"); + CHECK_FALSE(step_accepts(cut, without_last_field(bytes))); } { LLGestureStepWait full; @@ -291,17 +283,17 @@ namespace const std::vector bytes = serialize_step(full); LLGestureStepWait whole; - ensure("the wait step reads back", step_accepts(whole, bytes)); - ensure_equals("with its flags", whole.mFlags, full.mFlags); + UNSCOPED_INFO("the wait step reads back"); + CHECK(step_accepts(whole, bytes)); + CHECK(whole.mFlags == full.mFlags); LLGestureStepWait cut; - ensure("a wait step missing its flags is refused", - !step_accepts(cut, without_last_field(bytes))); + UNSCOPED_INFO("a wait step missing its flags is refused"); + CHECK_FALSE(step_accepts(cut, without_last_field(bytes))); } } - template<> template<> - void llmultigesture_object::test<6>() + TEST_CASE("LLMultiGesture: a bad header is refused and an empty step list loads", "[LLMultiGesture]") { // The header, one thing wrong at a time. Each of these is a line in // the file, and the file comes from whoever sent the gesture. @@ -314,26 +306,27 @@ namespace return gesture.deserialize(dp); }; - ensure("a version from another format is refused", - !read("1\n71\n0\n/wave\n\n0\n")); - ensure("and so is one from no format at all", - !read("999\n71\n0\n/wave\n\n0\n")); - ensure("a negative step count is refused", - !read("2\n71\n0\n/wave\n\n-5\n")); - ensure("a step type off the end of the list is refused", - !read("2\n71\n0\n/wave\n\n1\n99\n")); - ensure("a negative step type is refused", - !read("2\n71\n0\n/wave\n\n1\n-1\n")); - ensure("nothing at all is refused", !read("")); - ensure("and so is a lone newline", !read("\n")); + UNSCOPED_INFO("a version from another format is refused"); + CHECK_FALSE(read("1\n71\n0\n/wave\n\n0\n")); + UNSCOPED_INFO("and so is one from no format at all"); + CHECK_FALSE(read("999\n71\n0\n/wave\n\n0\n")); + UNSCOPED_INFO("a negative step count is refused"); + CHECK_FALSE(read("2\n71\n0\n/wave\n\n-5\n")); + UNSCOPED_INFO("a step type off the end of the list is refused"); + CHECK_FALSE(read("2\n71\n0\n/wave\n\n1\n99\n")); + UNSCOPED_INFO("a negative step type is refused"); + CHECK_FALSE(read("2\n71\n0\n/wave\n\n1\n-1\n")); + UNSCOPED_INFO("nothing at all is refused"); + CHECK_FALSE(read("")); + UNSCOPED_INFO("and so is a lone newline"); + CHECK_FALSE(read("\n")); // A gesture with no steps in it is a gesture, and loads. - ensure("no steps is not the same as broken", - read("2\n71\n0\n/wave\n\n0\n")); + UNSCOPED_INFO("no steps is not the same as broken"); + CHECK(read("2\n71\n0\n/wave\n\n0\n")); } - template<> template<> - void llmultigesture_object::test<7>() + TEST_CASE("LLMultiGesture: a step count smaller than the body reads only that many", "[LLMultiGesture]") { // A count smaller than the body leaves the rest unread, which is not // an error: the reader takes what it was told to take. @@ -349,12 +342,15 @@ namespace { at = text.find('\n', at) + 1; } - ensure("the step count line was found", text[at] == '4'); + UNSCOPED_INFO("the step count line was found"); + CHECK(text[at] == '4'); fewer[at] = '2'; LLMultiGesture read; LLDataPackerAsciiBuffer dp(fewer.data(), (S32)fewer.size()); - ensure("a gesture that claims fewer steps than it holds is taken", read.deserialize(dp)); - ensure_equals("with the number it claimed", read.mSteps.size(), 2u); + UNSCOPED_INFO("a gesture that claims fewer steps than it holds is taken"); + CHECK(read.deserialize(dp)); + UNSCOPED_INFO("with the number it claimed"); + CHECK(read.mSteps.size() == 2u); } } diff --git a/indra/llcharacter/tests/llpose_test.cpp b/indra/llcharacter/tests/llpose_test.cpp index a722691dd1..dc6bad1466 100644 --- a/indra/llcharacter/tests/llpose_test.cpp +++ b/indra/llcharacter/tests/llpose_test.cpp @@ -36,7 +36,8 @@ #include "altestmotion.h" -#include "../test/lltut.h" +#include +#include #include #include @@ -47,17 +48,19 @@ namespace { const F32 TOLERANCE = 1e-5f; - void ensure_vec3_equals(const char* msg, const LLVector3& actual, const LLVector3& expected) + void require_vec3_near(const char* what, const LLVector3& actual, const LLVector3& expected) { - tut::ensure_approximately_equals_range(msg, actual.mV[VX], expected.mV[VX], TOLERANCE); - tut::ensure_approximately_equals_range(msg, actual.mV[VY], expected.mV[VY], TOLERANCE); - tut::ensure_approximately_equals_range(msg, actual.mV[VZ], expected.mV[VZ], TOLERANCE); + INFO(what); + REQUIRE_THAT(actual.mV[VX], Catch::Matchers::WithinAbs(expected.mV[VX], TOLERANCE)); + REQUIRE_THAT(actual.mV[VY], Catch::Matchers::WithinAbs(expected.mV[VY], TOLERANCE)); + REQUIRE_THAT(actual.mV[VZ], Catch::Matchers::WithinAbs(expected.mV[VZ], TOLERANCE)); } // q and -q are the same rotation, so compare through the absolute dot product. - void ensure_quat_equals(const char* msg, const LLQuaternion& actual, const LLQuaternion& expected) + void require_same_rotation(const char* what, const LLQuaternion& actual, const LLQuaternion& expected) { - tut::ensure_approximately_equals_range(msg, fabsf(dot(actual, expected)), 1.f, TOLERANCE); + INFO(what); + REQUIRE_THAT(fabsf(dot(actual, expected)), Catch::Matchers::WithinAbs(1.f, TOLERANCE)); } LLPointer make_state(LLJoint* joint, U32 usage, F32 weight = 1.f, @@ -75,8 +78,6 @@ namespace const LLQuaternion ROT_C(-0.8f, LLVector3::z_axis); } -namespace tut -{ namespace { struct joints @@ -95,38 +96,29 @@ namespace mC.setJointNum(2); } }; -} //------------------------------------------------------------------------- // LLJointState //------------------------------------------------------------------------- -namespace -{ struct lljointstate_data : public joints { }; - typedef test_group lljointstate_test; - typedef lljointstate_test::object lljointstate_object; - tut::lljointstate_test lljointstate_testcase("LLJointState"); -} - template<> template<> - void lljointstate_object::test<1>() + TEST_CASE_METHOD(lljointstate_data, "LLJointState: a fresh state has no usage or weight and default channels", "[LLJointState]") { // What a fresh state holds. The blender seeds a channel from the joint // when nothing contributes to it, so these only ever reach a joint // through a state that carries the matching usage, but a state that // came back with something else in it would be a silent wrong pose. LLPointer state = new LLJointState(&mA); - ensure_equals("no usage", state->getUsage(), 0u); - ensure_equals("no weight", state->getWeight(), 0.f); - ensure_vec3_equals("position starts at the origin", state->getPosition(), LLVector3::zero); - ensure_quat_equals("rotation starts at identity", state->getRotation(), LLQuaternion::DEFAULT); - ensure_vec3_equals("scale starts at zero", state->getScale(), LLVector3::zero); + CHECK(state->getUsage() == 0u); + CHECK(state->getWeight() == 0.f); + require_vec3_near("position starts at the origin", state->getPosition(), LLVector3::zero); + require_same_rotation("rotation starts at identity", state->getRotation(), LLQuaternion::DEFAULT); + require_vec3_near("scale starts at zero", state->getScale(), LLVector3::zero); } - template<> template<> - void lljointstate_object::test<2>() + TEST_CASE_METHOD(lljointstate_data, "LLJointState: values set as scalars read back through both views", "[LLJointState]") { // The two views of a channel are the same channel: written either // way, read either way. @@ -140,21 +132,20 @@ namespace state->setRotation(ROT_A); state->setScale(scale); - ensure_vec3_equals("position read back", state->getPosition(), pos); - ensure_quat_equals("rotation read back", state->getRotation(), ROT_A); - ensure_vec3_equals("scale read back", state->getScale(), scale); + require_vec3_near("position read back", state->getPosition(), pos); + require_same_rotation("rotation read back", state->getRotation(), ROT_A); + require_vec3_near("scale read back", state->getScale(), scale); - ensure_vec3_equals("position through the vector view", - LLVector3(state->getPositionV().getF32ptr()), pos); - ensure_vec3_equals("scale through the vector view", - LLVector3(state->getScaleV().getF32ptr()), scale); + require_vec3_near("position through the vector view", + LLVector3(state->getPositionV().getF32ptr()), pos); + require_vec3_near("scale through the vector view", + LLVector3(state->getScaleV().getF32ptr()), scale); LLQuaternion from_vector; state->getRotationQ().store(from_vector); - ensure_quat_equals("rotation through the vector view", from_vector, ROT_A); + require_same_rotation("rotation through the vector view", from_vector, ROT_A); } - template<> template<> - void lljointstate_object::test<3>() + TEST_CASE_METHOD(lljointstate_data, "LLJointState: values set through the vector view read back as scalars", "[LLJointState]") { // and written through the vector view, read through the scalar one LLPointer state = new LLJointState(&mA); @@ -169,185 +160,37 @@ namespace state->setRotation(LLQuaternion2(ROT_B)); state->setScale(scale); - ensure_vec3_equals("position set as a vector", state->getPosition(), LLVector3(-4.f, 5.f, 6.f)); - ensure_quat_equals("rotation set as a quaternion2", state->getRotation(), ROT_B); - ensure_vec3_equals("scale set as a vector", state->getScale(), LLVector3(0.5f, 0.25f, 0.125f)); + require_vec3_near("position set as a vector", state->getPosition(), LLVector3(-4.f, 5.f, 6.f)); + require_same_rotation("rotation set as a quaternion2", state->getRotation(), ROT_B); + require_vec3_near("scale set as a vector", state->getScale(), LLVector3(0.5f, 0.25f, 0.125f)); } //------------------------------------------------------------------------- // LLPose //------------------------------------------------------------------------- -namespace -{ struct llpose_data : public joints { LLPose mPose; }; - typedef test_group llpose_test; - typedef llpose_test::object llpose_object; - tut::llpose_test llpose_testcase("LLPose"); -} - - template<> template<> - void llpose_object::test<1>() - { - ensure_equals("empty pose has no states", mPose.getNumJointStates(), 0); - ensure("empty pose has nothing to iterate", mPose.getJointStates().empty()); - ensure("empty pose finds nothing by joint", mPose.findJointState(&mA) == nullptr); - ensure("empty pose finds nothing by name", mPose.findJointState(std::string("a")) == nullptr); - ensure_equals("empty pose weight", mPose.getWeight(), 0.f); - } - - template<> template<> - void llpose_object::test<2>() - { - LLPointer sa = make_state(&mA, LLJointState::ROT); - ensure("addJointState returns true", mPose.addJointState(sa)); - ensure_equals("one state", mPose.getNumJointStates(), 1); - ensure("find by joint", mPose.findJointState(&mA) == sa.get()); - ensure("find by name", mPose.findJointState(std::string("a")) == sa.get()); - ensure("other joint absent", mPose.findJointState(&mB) == nullptr); - ensure_equals("one state to iterate", mPose.getJointStates().size(), (size_t)1); - ensure("the state to iterate is the one added", mPose.getJointStates()[0].get() == sa.get()); - } - - template<> template<> - void llpose_object::test<3>() - { - // Two states on the same joint collapse to one; the first added wins. - LLPointer first = make_state(&mA, LLJointState::ROT); - LLPointer second = make_state(&mA, LLJointState::POS); - mPose.addJointState(first); - mPose.addJointState(second); - ensure_equals("same joint dedups", mPose.getNumJointStates(), 1); - ensure("first state kept", mPose.findJointState(&mA) == first.get()); - } - - template<> template<> - void llpose_object::test<4>() - { - LLPointer sa = make_state(&mA, LLJointState::ROT); - LLPointer sb = make_state(&mB, LLJointState::ROT); - mPose.addJointState(sa); - mPose.addJointState(sb); - ensure_equals("two states", mPose.getNumJointStates(), 2); - - mPose.removeJointState(sa); - ensure_equals("one left after remove", mPose.getNumJointStates(), 1); - ensure("removed state gone", mPose.findJointState(&mA) == nullptr); - ensure("other state stays", mPose.findJointState(&mB) == sb.get()); - - mPose.addJointState(sa); - ensure_equals("re-add after remove", mPose.getNumJointStates(), 2); - - mPose.removeAllJointStates(); - ensure_equals("removeAll empties", mPose.getNumJointStates(), 0); - ensure("nothing to iterate", mPose.getJointStates().empty()); - } - - template<> template<> - void llpose_object::test<5>() - { - LLPointer sa = make_state(&mA, LLJointState::ROT); - LLPointer sb = make_state(&mB, LLJointState::ROT); - LLPointer sc = make_state(&mC, LLJointState::ROT); - mPose.addJointState(sa); - mPose.addJointState(sb); - mPose.addJointState(sc); - - std::vector seen; - for (const LLPointer& state : mPose.getJointStates()) - { - seen.push_back(state.get()); - } - ensure_equals("iteration visits every state", seen.size(), (size_t)3); - ensure("iteration visits a", std::count(seen.begin(), seen.end(), sa.get()) == 1); - ensure("iteration visits b", std::count(seen.begin(), seen.end(), sb.get()) == 1); - ensure("iteration visits c", std::count(seen.begin(), seen.end(), sc.get()) == 1); - } - - template<> template<> - void llpose_object::test<6>() - { - LLPointer sa = make_state(&mA, LLJointState::ROT, 0.f); - LLPointer sb = make_state(&mB, LLJointState::ROT, 0.f); - mPose.addJointState(sa); - mPose.addJointState(sb); - - mPose.setWeight(0.5f); - ensure_equals("pose weight stored", mPose.getWeight(), 0.5f); - ensure_equals("weight reaches a", sa->getWeight(), 0.5f); - ensure_equals("weight reaches b", sb->getWeight(), 0.5f); - - // A state added after setWeight is brought up to the pose's weight. - // Every state carrying the pose weight is what lets setWeight skip - // the walk when the weight has not changed; left at its own weight, - // a late state would sit there until the next change. - LLPointer sc = make_state(&mC, LLJointState::ROT, 0.f); - mPose.addJointState(sc); - ensure_equals("late state receives the pose weight", sc->getWeight(), 0.5f); - - mPose.setWeight(1.f); - ensure_equals("second setWeight reaches a", sa->getWeight(), 1.f); - ensure_equals("second setWeight reaches b", sb->getWeight(), 1.f); - ensure_equals("second setWeight reaches c", sc->getWeight(), 1.f); - } - - template<> template<> - void llpose_object::test<7>() - { - // The poser's runtime path: find the state on a joint, remove it, add - // another for the same joint. Dedup is by joint, so two states on - // one joint are still one entry; find by name still reaches it; and - // removing a state that is not there is harmless. - LLPointer first = make_state(&mA, LLJointState::ROT); - LLPointer second = make_state(&mA, LLJointState::POS); - LLPointer other = make_state(&mB, LLJointState::ROT); - mPose.addJointState(first); - mPose.addJointState(second); - mPose.addJointState(other); - ensure_equals("two states on one joint are one entry", mPose.getNumJointStates(), 2); - ensure("found by joint", mPose.findJointState(&mA) == first.get()); - ensure("found by name", mPose.findJointState("a") == first.get()); - ensure("the other joint is found by name too", mPose.findJointState("b") == other.get()); - ensure("an unknown name finds nothing", mPose.findJointState("nobody") == nullptr); - - mPose.removeJointState(mPose.findJointState(&mA)); - ensure_equals("removed by the state found", mPose.getNumJointStates(), 1); - ensure("the joint is gone", mPose.findJointState(&mA) == nullptr); - mPose.removeJointState(second); - ensure_equals("removing an absent state changes nothing", mPose.getNumJointStates(), 1); - - mPose.addJointState(second); - ensure("the joint can be re-added with a different state", mPose.findJointState(&mA) == second.get()); - } //------------------------------------------------------------------------- // LLJointStateBlender //------------------------------------------------------------------------- -namespace -{ struct lljointstateblender_data : public joints { LLJointStateBlender mBlender; }; - typedef test_group lljointstateblender_test; - typedef lljointstateblender_test::object lljointstateblender_object; - tut::lljointstateblender_test lljointstateblender_testcase("LLJointStateBlender"); -} - template<> template<> - void lljointstateblender_object::test<1>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: addJointState refuses a state with no joint", "[LLJointStateBlender]") { LLPointer orphan = make_state(nullptr, LLJointState::ROT); - ensure("state without a joint is refused", !mBlender.addJointState(orphan, LLJoint::MEDIUM_PRIORITY, false)); + CHECK_FALSE(mBlender.addJointState(orphan, LLJoint::MEDIUM_PRIORITY, false)); LLPointer sa = make_state(&mA, LLJointState::ROT); - ensure("state with a joint is accepted", mBlender.addJointState(sa, LLJoint::MEDIUM_PRIORITY, false)); + CHECK(mBlender.addJointState(sa, LLJoint::MEDIUM_PRIORITY, false)); } - template<> template<> - void lljointstateblender_object::test<2>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: a lone full-weight state is applied to its joint", "[LLJointStateBlender]") { LLPointer sa = make_state(&mA, LLJointState::POS | LLJointState::ROT | LLJointState::SCALE); sa->setPosition(LLVector3(1.f, 2.f, 3.f)); @@ -357,13 +200,12 @@ namespace mBlender.blendJointStates(); - ensure_vec3_equals("single state position", mA.getPosition(), LLVector3(1.f, 2.f, 3.f)); - ensure_quat_equals("single state rotation", mA.getRotation(), ROT_A); - ensure_vec3_equals("single state scale", mA.getScale(), LLVector3(2.f, 2.f, 2.f)); + require_vec3_near("single state position", mA.getPosition(), LLVector3(1.f, 2.f, 3.f)); + require_same_rotation("single state rotation", mA.getRotation(), ROT_A); + require_vec3_near("single state scale", mA.getScale(), LLVector3(2.f, 2.f, 2.f)); } - template<> template<> - void lljointstateblender_object::test<3>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: a zero-weight state leaves the joint alone", "[LLJointStateBlender]") { LLPointer sa = make_state(&mA, LLJointState::POS | LLJointState::ROT, 0.f); sa->setPosition(LLVector3(1.f, 2.f, 3.f)); @@ -372,12 +214,11 @@ namespace mBlender.blendJointStates(); - ensure_vec3_equals("zero-weight state leaves position", mA.getPosition(), LLVector3::zero); - ensure_quat_equals("zero-weight state leaves rotation", mA.getRotation(), LLQuaternion::DEFAULT); + require_vec3_near("zero-weight state leaves position", mA.getPosition(), LLVector3::zero); + require_same_rotation("zero-weight state leaves rotation", mA.getRotation(), LLQuaternion::DEFAULT); } - template<> template<> - void lljointstateblender_object::test<4>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: higher priority wins and equal priority keeps the first", "[LLJointStateBlender]") { // Higher priority wins at full weight regardless of insertion order. LLPointer low = make_state(&mA, LLJointState::ROT); @@ -388,24 +229,23 @@ namespace mBlender.addJointState(low, LLJoint::LOW_PRIORITY, false); mBlender.addJointState(high, LLJoint::HIGH_PRIORITY, false); mBlender.blendJointStates(); - ensure_quat_equals("high after low", mA.getRotation(), ROT_B); + require_same_rotation("high after low", mA.getRotation(), ROT_B); mA.setRotation(LLQuaternion::DEFAULT); mBlender.addJointState(high, LLJoint::HIGH_PRIORITY, false); mBlender.addJointState(low, LLJoint::LOW_PRIORITY, false); mBlender.blendJointStates(); - ensure_quat_equals("low after high", mA.getRotation(), ROT_B); + require_same_rotation("low after high", mA.getRotation(), ROT_B); // At equal priority the state added first stays in front. mA.setRotation(LLQuaternion::DEFAULT); mBlender.addJointState(low, LLJoint::MEDIUM_PRIORITY, false); mBlender.addJointState(high, LLJoint::MEDIUM_PRIORITY, false); mBlender.blendJointStates(); - ensure_quat_equals("equal priority keeps the first", mA.getRotation(), ROT_A); + require_same_rotation("equal priority keeps the first", mA.getRotation(), ROT_A); } - template<> template<> - void lljointstateblender_object::test<5>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: two half-weight positions meet in the middle", "[LLJointStateBlender]") { // Two half-weight positions at the same priority meet in the middle. LLPointer first = make_state(&mA, LLJointState::POS, 0.5f); @@ -417,11 +257,10 @@ namespace mBlender.addJointState(second, LLJoint::MEDIUM_PRIORITY, false); mBlender.blendJointStates(); - ensure_vec3_equals("half and half", mA.getPosition(), LLVector3(1.f, 2.f, 0.f)); + require_vec3_near("half and half", mA.getPosition(), LLVector3(1.f, 2.f, 0.f)); } - template<> template<> - void lljointstateblender_object::test<6>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: an additive state adds position and scale and composes rotation", "[LLJointStateBlender]") { LLPointer base = make_state(&mA, LLJointState::POS | LLJointState::ROT | LLJointState::SCALE); base->setPosition(LLVector3(0.f, 1.f, 0.f)); @@ -437,55 +276,52 @@ namespace mBlender.addJointState(add, LLJoint::ADDITIVE_PRIORITY, true); mBlender.blendJointStates(); - ensure_vec3_equals("additive position sums", mA.getPosition(), LLVector3(1.f, 1.f, 0.f)); - ensure_quat_equals("additive rotation composes", mA.getRotation(), ROT_B * ROT_A); - ensure_vec3_equals("additive scale sums", mA.getScale(), LLVector3(1.5f, 1.f, 1.f)); + require_vec3_near("additive position sums", mA.getPosition(), LLVector3(1.f, 1.f, 0.f)); + require_same_rotation("additive rotation composes", mA.getRotation(), ROT_B * ROT_A); + require_vec3_near("additive scale sums", mA.getScale(), LLVector3(1.5f, 1.f, 1.f)); } - template<> template<> - void lljointstateblender_object::test<7>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: blending or clearing empties the queued states", "[LLJointStateBlender]") { // blendJointStates() consumes its slots: a second call with nothing added is a no-op. LLPointer sa = make_state(&mA, LLJointState::ROT); sa->setRotation(ROT_A); mBlender.addJointState(sa, LLJoint::MEDIUM_PRIORITY, false); mBlender.blendJointStates(); - ensure_quat_equals("first blend applies", mA.getRotation(), ROT_A); + require_same_rotation("first blend applies", mA.getRotation(), ROT_A); mA.setRotation(ROT_C); mBlender.blendJointStates(); - ensure_quat_equals("empty blend leaves the joint alone", mA.getRotation(), ROT_C); + require_same_rotation("empty blend leaves the joint alone", mA.getRotation(), ROT_C); // clear() drops queued states without applying them. mBlender.addJointState(sa, LLJoint::MEDIUM_PRIORITY, false); mBlender.clear(); mBlender.blendJointStates(); - ensure_quat_equals("cleared blend leaves the joint alone", mA.getRotation(), ROT_C); + require_same_rotation("cleared blend leaves the joint alone", mA.getRotation(), ROT_C); } - template<> template<> - void lljointstateblender_object::test<8>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: a full blender refuses lower priority and takes higher", "[LLJointStateBlender]") { std::vector> states; for (S32 i = 0; i < JSB_NUM_JOINT_STATES; ++i) { states.push_back(make_state(&mA, LLJointState::ROT)); - ensure("slot accepted", mBlender.addJointState(states.back(), LLJoint::MEDIUM_PRIORITY, false)); + REQUIRE(mBlender.addJointState(states.back(), LLJoint::MEDIUM_PRIORITY, false)); } LLPointer lower = make_state(&mA, LLJointState::ROT); - ensure("full blender refuses a lower priority", !mBlender.addJointState(lower, LLJoint::LOW_PRIORITY, false)); + CHECK_FALSE(mBlender.addJointState(lower, LLJoint::LOW_PRIORITY, false)); LLPointer higher = make_state(&mA, LLJointState::ROT); higher->setRotation(ROT_B); - ensure("full blender takes a higher priority", mBlender.addJointState(higher, LLJoint::HIGH_PRIORITY, false)); + CHECK(mBlender.addJointState(higher, LLJoint::HIGH_PRIORITY, false)); mBlender.blendJointStates(); - ensure_quat_equals("the higher priority lands in front", mA.getRotation(), ROT_B); + require_same_rotation("the higher priority lands in front", mA.getRotation(), ROT_B); } - template<> template<> - void lljointstateblender_object::test<9>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: a rotation-only blend does not dirty the position", "[LLJointStateBlender]") { // A channel no joint state contributes to holds the value read from // the joint, so writing it back is a no-op that still dirties the @@ -496,23 +332,21 @@ namespace const LLVector3 scale(2.f, 3.f, 4.f); mA.setScale(scale); mA.updateWorldMatrix(); - ensure("joint starts clean", mA.mDirtyFlags == 0); + UNSCOPED_INFO("joint starts clean"); + CHECK(mA.mDirtyFlags == 0); LLPointer rot_only = make_state(&mA, LLJointState::ROT); rot_only->setRotation(ROT_A); mBlender.addJointState(rot_only, LLJoint::MEDIUM_PRIORITY, false); mBlender.blendJointStates(); - ensure_vec3_equals("rotation-only blend leaves scale alone", mA.getScale(), scale); - ensure_quat_equals("rotation-only blend still applies rotation", mA.getRotation(), ROT_A); - ensure("rotation-only blend does not dirty position", - (mA.mDirtyFlags & LLJoint::POSITION_DIRTY) == 0); - ensure("rotation-only blend does dirty rotation", - (mA.mDirtyFlags & LLJoint::ROTATION_DIRTY) != 0); + require_vec3_near("rotation-only blend leaves scale alone", mA.getScale(), scale); + require_same_rotation("rotation-only blend still applies rotation", mA.getRotation(), ROT_A); + CHECK((mA.mDirtyFlags & LLJoint::POSITION_DIRTY) == 0); + CHECK((mA.mDirtyFlags & LLJoint::ROTATION_DIRTY) != 0); } - template<> template<> - void lljointstateblender_object::test<10>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: a scale-only state still writes the scale", "[LLJointStateBlender]") { // A state that does carry SCALE still writes, including when it is the // only channel in use. @@ -522,11 +356,10 @@ namespace mBlender.addJointState(scale_only, LLJoint::MEDIUM_PRIORITY, false); mBlender.blendJointStates(); - ensure_vec3_equals("scale-only blend applies scale", mA.getScale(), scale); + require_vec3_near("scale-only blend applies scale", mA.getScale(), scale); } - template<> template<> - void lljointstateblender_object::test<11>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: a non-finite scale never reaches the joint", "[LLJointStateBlender]") { // A non-finite scale contribution must never reach the joint. Two // layers enforce this, the reset in blendJointStates and the one in @@ -538,12 +371,11 @@ namespace mBlender.addJointState(bad_scale, LLJoint::MEDIUM_PRIORITY, false); mBlender.blendJointStates(); - ensure("joint scale stays finite", mA.getScale().isFinite()); - ensure_vec3_equals("non-finite scale is reset", mA.getScale(), LLVector3(1.f, 1.f, 1.f)); + CHECK(mA.getScale().isFinite()); + require_vec3_near("non-finite scale is reset", mA.getScale(), LLVector3(1.f, 1.f, 1.f)); } - template<> template<> - void lljointstateblender_object::test<12>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: holding a pose stops dirtying the joint", "[LLJointStateBlender]") { // The payoff of the scale skip and the rotation compare together: a // motion holding a pose blends the same values every frame, and must @@ -554,18 +386,19 @@ namespace mBlender.addJointState(held, LLJoint::MEDIUM_PRIORITY, false); mBlender.blendJointStates(); - ensure_quat_equals("first blend applies the pose", mA.getRotation(), ROT_A); + require_same_rotation("first blend applies the pose", mA.getRotation(), ROT_A); mA.updateWorldMatrix(); - ensure("joint is clean once the pose has been applied", mA.mDirtyFlags == 0); + UNSCOPED_INFO("joint is clean once the pose has been applied"); + CHECK(mA.mDirtyFlags == 0); mBlender.addJointState(held, LLJoint::MEDIUM_PRIORITY, false); mBlender.blendJointStates(); - ensure("holding the pose does not dirty the joint again", mA.mDirtyFlags == 0); + UNSCOPED_INFO("holding the pose does not dirty the joint again"); + CHECK(mA.mDirtyFlags == 0); } - template<> template<> - void lljointstateblender_object::test<13>() + TEST_CASE_METHOD(lljointstateblender_data, "LLJointStateBlender: a full blender refuses equal priority and blending frees the slots", "[LLJointStateBlender]") { // There are six slots. Filling them refuses anything of lower or // equal priority, and applying the blend hands them all back. @@ -574,36 +407,164 @@ namespace { states.push_back(make_state(&mA, LLJointState::ROT)); states.back()->setRotation(ROT_B); - ensure("a free slot takes the state", - mBlender.addJointState(states.back(), LLJoint::HIGH_PRIORITY, false)); + REQUIRE(mBlender.addJointState(states.back(), LLJoint::HIGH_PRIORITY, false)); } LLPointer equal = make_state(&mA, LLJointState::ROT); - ensure("a full blender refuses an equal priority", - !mBlender.addJointState(equal, LLJoint::HIGH_PRIORITY, false)); + CHECK_FALSE(mBlender.addJointState(equal, LLJoint::HIGH_PRIORITY, false)); LLPointer higher = make_state(&mA, LLJointState::ROT); higher->setRotation(ROT_A); - ensure("a full blender takes a higher priority", - mBlender.addJointState(higher, LLJoint::HIGHEST_PRIORITY, false)); + UNSCOPED_INFO("a full blender takes a higher priority"); + CHECK(mBlender.addJointState(higher, LLJoint::HIGHEST_PRIORITY, false)); mBlender.blendJointStates(); - ensure_quat_equals("the highest priority won", mA.getRotation(), ROT_A); + require_same_rotation("the highest priority won", mA.getRotation(), ROT_A); // Applying releases every slot, so the next frame starts empty rather // than inheriting six states that are already spoken for. LLPointer next = make_state(&mA, LLJointState::ROT); next->setRotation(ROT_C); - ensure("the slots came back", mBlender.addJointState(next, LLJoint::LOW_PRIORITY, false)); + UNSCOPED_INFO("the slots came back"); + CHECK(mBlender.addJointState(next, LLJoint::LOW_PRIORITY, false)); mBlender.blendJointStates(); - ensure_quat_equals("the next frame blends only its own state", mA.getRotation(), ROT_C); + require_same_rotation("the next frame blends only its own state", mA.getRotation(), ROT_C); + } + + TEST_CASE_METHOD(llpose_data, "LLPose: an empty pose has no states and no weight", "[LLPose]") + { + CHECK(mPose.getNumJointStates() == 0); + CHECK(mPose.getJointStates().empty()); + CHECK(mPose.findJointState(&mA) == nullptr); + CHECK(mPose.findJointState(std::string("a")) == nullptr); + CHECK(mPose.getWeight() == 0.f); + } + + TEST_CASE_METHOD(llpose_data, "LLPose: an added state is counted and found by joint and by name", "[LLPose]") + { + LLPointer sa = make_state(&mA, LLJointState::ROT); + CHECK(mPose.addJointState(sa)); + CHECK(mPose.getNumJointStates() == 1); + CHECK(mPose.findJointState(&mA) == sa.get()); + CHECK(mPose.findJointState(std::string("a")) == sa.get()); + CHECK(mPose.findJointState(&mB) == nullptr); + REQUIRE(mPose.getJointStates().size() == (size_t)1); + CHECK(mPose.getJointStates()[0].get() == sa.get()); + } + + TEST_CASE_METHOD(llpose_data, "LLPose: states on one joint collapse to the first added", "[LLPose]") + { + // Two states on the same joint collapse to one; the first added wins. + LLPointer first = make_state(&mA, LLJointState::ROT); + LLPointer second = make_state(&mA, LLJointState::POS); + mPose.addJointState(first); + mPose.addJointState(second); + CHECK(mPose.getNumJointStates() == 1); + CHECK(mPose.findJointState(&mA) == first.get()); + } + + TEST_CASE_METHOD(llpose_data, "LLPose: removeJointState and removeAllJointStates drop states", "[LLPose]") + { + LLPointer sa = make_state(&mA, LLJointState::ROT); + LLPointer sb = make_state(&mB, LLJointState::ROT); + mPose.addJointState(sa); + mPose.addJointState(sb); + CHECK(mPose.getNumJointStates() == 2); + + mPose.removeJointState(sa); + CHECK(mPose.getNumJointStates() == 1); + CHECK(mPose.findJointState(&mA) == nullptr); + CHECK(mPose.findJointState(&mB) == sb.get()); + + mPose.addJointState(sa); + CHECK(mPose.getNumJointStates() == 2); + + mPose.removeAllJointStates(); + CHECK(mPose.getNumJointStates() == 0); + CHECK(mPose.getJointStates().empty()); + } + + TEST_CASE_METHOD(llpose_data, "LLPose: iterating the states visits each once", "[LLPose]") + { + LLPointer sa = make_state(&mA, LLJointState::ROT); + LLPointer sb = make_state(&mB, LLJointState::ROT); + LLPointer sc = make_state(&mC, LLJointState::ROT); + mPose.addJointState(sa); + mPose.addJointState(sb); + mPose.addJointState(sc); + + std::vector seen; + for (const LLPointer& state : mPose.getJointStates()) + { + seen.push_back(state.get()); + } + CHECK(seen.size() == (size_t)3); + CHECK(std::count(seen.begin(), seen.end(), sa.get()) == 1); + CHECK(std::count(seen.begin(), seen.end(), sb.get()) == 1); + CHECK(std::count(seen.begin(), seen.end(), sc.get()) == 1); + } + + TEST_CASE_METHOD(llpose_data, "LLPose: setWeight reaches every state including one added later", "[LLPose]") + { + LLPointer sa = make_state(&mA, LLJointState::ROT, 0.f); + LLPointer sb = make_state(&mB, LLJointState::ROT, 0.f); + mPose.addJointState(sa); + mPose.addJointState(sb); + + mPose.setWeight(0.5f); + CHECK(mPose.getWeight() == 0.5f); + CHECK(sa->getWeight() == 0.5f); + CHECK(sb->getWeight() == 0.5f); + + // A state added after setWeight is brought up to the pose's weight. + // Every state carrying the pose weight is what lets setWeight skip + // the walk when the weight has not changed; left at its own weight, + // a late state would sit there until the next change. + LLPointer sc = make_state(&mC, LLJointState::ROT, 0.f); + mPose.addJointState(sc); + UNSCOPED_INFO("late state receives the pose weight"); + CHECK(sc->getWeight() == 0.5f); + + mPose.setWeight(1.f); + CHECK(sa->getWeight() == 1.f); + CHECK(sb->getWeight() == 1.f); + CHECK(sc->getWeight() == 1.f); + } + + TEST_CASE_METHOD(llpose_data, "LLPose: a state found on a joint can be removed and replaced", "[LLPose]") + { + // The poser's runtime path: find the state on a joint, remove it, add + // another for the same joint. Dedup is by joint, so two states on + // one joint are still one entry; find by name still reaches it; and + // removing a state that is not there is harmless. + LLPointer first = make_state(&mA, LLJointState::ROT); + LLPointer second = make_state(&mA, LLJointState::POS); + LLPointer other = make_state(&mB, LLJointState::ROT); + mPose.addJointState(first); + mPose.addJointState(second); + mPose.addJointState(other); + UNSCOPED_INFO("two states on one joint are one entry"); + CHECK(mPose.getNumJointStates() == 2); + CHECK(mPose.findJointState(&mA) == first.get()); + CHECK(mPose.findJointState("a") == first.get()); + CHECK(mPose.findJointState("b") == other.get()); + CHECK(mPose.findJointState("nobody") == nullptr); + + mPose.removeJointState(mPose.findJointState(&mA)); + CHECK(mPose.getNumJointStates() == 1); + CHECK(mPose.findJointState(&mA) == nullptr); + mPose.removeJointState(second); + UNSCOPED_INFO("removing an absent state changes nothing"); + CHECK(mPose.getNumJointStates() == 1); + + mPose.addJointState(second); + UNSCOPED_INFO("the joint can be re-added with a different state"); + CHECK(mPose.findJointState(&mA) == second.get()); } //------------------------------------------------------------------------- // LLPoseBlender //------------------------------------------------------------------------- -namespace -{ struct llposeblender_data : public joints { LLPoseBlender mBlender; @@ -616,13 +577,8 @@ namespace static void arm(ALTestMotion& motion) { motion.getPose()->setWeight(1.f); } }; - typedef test_group llposeblender_test; - typedef llposeblender_test::object llposeblender_object; - tut::llposeblender_test llposeblender_testcase("LLPoseBlender"); -} - template<> template<> - void llposeblender_object::test<1>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: a motion's joint states are applied to their joints", "[LLPoseBlender]") { ALTestMotion motion; LLPointer sa = motion.addJoint(&mA, LLJointState::ROT); @@ -631,15 +587,14 @@ namespace sb->setPosition(LLVector3(3.f, 2.f, 1.f)); arm(motion); - ensure("addMotion returns true", addMotion(&motion)); + CHECK(addMotion(&motion)); mBlender.blendAndApply(); - ensure_quat_equals("motion rotation applied", mA.getRotation(), ROT_A); - ensure_vec3_equals("motion position applied", mB.getPosition(), LLVector3(3.f, 2.f, 1.f)); + require_same_rotation("motion rotation applied", mA.getRotation(), ROT_A); + require_vec3_near("motion position applied", mB.getPosition(), LLVector3(3.f, 2.f, 1.f)); } - template<> template<> - void llposeblender_object::test<2>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: the higher priority motion wins in either order", "[LLPoseBlender]") { ALTestMotion low(LLUUID::generateNewID(), LLJoint::LOW_PRIORITY); low.addJoint(&mA, LLJointState::ROT)->setRotation(ROT_A); @@ -652,17 +607,16 @@ namespace addMotion(&low); addMotion(&high); mBlender.blendAndApply(); - ensure_quat_equals("higher motion priority wins, low first", mA.getRotation(), ROT_B); + require_same_rotation("higher motion priority wins, low first", mA.getRotation(), ROT_B); mA.setRotation(LLQuaternion::DEFAULT); addMotion(&high); addMotion(&low); mBlender.blendAndApply(); - ensure_quat_equals("higher motion priority wins, high first", mA.getRotation(), ROT_B); + require_same_rotation("higher motion priority wins, high first", mA.getRotation(), ROT_B); } - template<> template<> - void llposeblender_object::test<3>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: a joint state's own priority overrides its motion's", "[LLPoseBlender]") { // A joint state's own priority overrides its motion's. ALTestMotion low(LLUUID::generateNewID(), LLJoint::LOW_PRIORITY); @@ -676,11 +630,72 @@ namespace addMotion(&medium); addMotion(&low); mBlender.blendAndApply(); - ensure_quat_equals("joint state priority beats motion priority", mA.getRotation(), ROT_A); + require_same_rotation("joint state priority beats motion priority", mA.getRotation(), ROT_A); + } + + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: an additive motion composes ahead of the base and is masked behind", "[LLPoseBlender]") + { + // The controller feeds additive motions to the blender before normal + // ones. At equal priority the earlier state stays in front, and an + // additive state only contributes while the states ahead of it have + // not already saturated the weight. + ALTestMotion base(LLUUID::generateNewID(), LLJoint::MEDIUM_PRIORITY, LLMotion::NORMAL_BLEND); + base.addJoint(&mA, LLJointState::ROT)->setRotation(ROT_A); + arm(base); + + ALTestMotion additive(LLUUID::generateNewID(), LLJoint::MEDIUM_PRIORITY, LLMotion::ADDITIVE_BLEND); + additive.addJoint(&mA, LLJointState::ROT)->setRotation(ROT_B); + arm(additive); + + addMotion(&additive); + addMotion(&base); + mBlender.blendAndApply(); + require_same_rotation("additive ahead of the base composes onto it", mA.getRotation(), ROT_B * ROT_A); + + mA.setRotation(LLQuaternion::DEFAULT); + addMotion(&base); + addMotion(&additive); + mBlender.blendAndApply(); + require_same_rotation("additive behind a saturated base is masked", mA.getRotation(), ROT_A); } - template<> template<> - void llposeblender_object::test<7>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: blendAndApply and clearBlenders empty the queued motions", "[LLPoseBlender]") + { + ALTestMotion motion; + motion.addJoint(&mA, LLJointState::ROT)->setRotation(ROT_A); + arm(motion); + + addMotion(&motion); + mBlender.blendAndApply(); + require_same_rotation("first apply", mA.getRotation(), ROT_A); + + // The active list is consumed by blendAndApply(). + mA.setRotation(ROT_C); + mBlender.blendAndApply(); + require_same_rotation("second apply without addMotion is a no-op", mA.getRotation(), ROT_C); + + // clearBlenders() discards queued states. + addMotion(&motion); + mBlender.clearBlenders(); + mBlender.blendAndApply(); + require_same_rotation("cleared queue is a no-op", mA.getRotation(), ROT_C); + } + + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: a joint with no number is kept in the motion's pose", "[LLPoseBlender]") + { + // A joint that never received a joint number is kept in the pose but + // left out of the motion's joint signature. + LLJoint unnumbered; + unnumbered.setup("unnumbered"); + UNSCOPED_INFO("default joint number"); + CHECK(unnumbered.getJointNum() == -1); + + ALTestMotion motion; + motion.addJoint(&unnumbered, LLJointState::ROT); + CHECK(motion.getPose()->getNumJointStates() == 1); + } + + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: a joint with no number is skipped and the rest still blend", "[LLPoseBlender]") { // The pool is indexed by joint number. A joint that never received one // -- LLJoint::init leaves it at -1 -- is in the motion's pose, since @@ -689,7 +704,8 @@ namespace // is not. LLJoint unnumbered; unnumbered.setup("unnumbered"); - ensure_equals("a fresh joint has no number", unnumbered.getJointNum(), -1); + UNSCOPED_INFO("a fresh joint has no number"); + CHECK(unnumbered.getJointNum() == -1); const LLQuaternion before = unnumbered.getRotation(); ALTestMotion motion; @@ -699,12 +715,11 @@ namespace addMotion(&motion); mBlender.blendAndApply(); - ensure_quat_equals("the unnumbered joint is not blended", unnumbered.getRotation(), before); - ensure_quat_equals("the numbered joint in the same motion is", mA.getRotation(), ROT_A); + require_same_rotation("the unnumbered joint is not blended", unnumbered.getRotation(), before); + require_same_rotation("the numbered joint in the same motion is", mA.getRotation(), ROT_A); } - template<> template<> - void llposeblender_object::test<8>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: the first and the last pool slots both blend", "[LLPoseBlender]") { // Both ends of the pool: the first slot and the highest number a real // joint can carry. setJointNum refuses the top two, which belong to @@ -720,12 +735,11 @@ namespace addMotion(&motion); mBlender.blendAndApply(); - ensure_quat_equals("slot zero blends", mA.getRotation(), ROT_A); - ensure_quat_equals("the last slot blends", last.getRotation(), ROT_C); + require_same_rotation("slot zero blends", mA.getRotation(), ROT_A); + require_same_rotation("the last slot blends", last.getRotation(), ROT_C); } - template<> template<> - void llposeblender_object::test<9>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: blendAndCache holds the blend and interpolate moves toward it", "[LLPoseBlender]") { // The coarse clock's path: blend into the cache without touching the // joint, then interpolate the joint toward it. The cache used to be a @@ -739,25 +753,24 @@ namespace addMotion(&motion); mBlender.blendAndCache(true); - ensure_quat_equals("caching leaves the joint's rotation alone", mA.getRotation(), LLQuaternion::DEFAULT); - ensure_vec3_equals("caching leaves the joint's position alone", mA.getPosition(), LLVector3::zero); + require_same_rotation("caching leaves the joint's rotation alone", mA.getRotation(), LLQuaternion::DEFAULT); + require_vec3_near("caching leaves the joint's position alone", mA.getPosition(), LLVector3::zero); mBlender.interpolate(0.5f); - ensure_vec3_equals("half way toward the cached position", mA.getPosition(), LLVector3(1.f, 2.f, 3.f)); + require_vec3_near("half way toward the cached position", mA.getPosition(), LLVector3(1.f, 2.f, 3.f)); mBlender.interpolate(1.f); - ensure_vec3_equals("at the cached position", mA.getPosition(), LLVector3(2.f, 4.f, 6.f)); - ensure_quat_equals("at the cached rotation", mA.getRotation(), ROT_A); + require_vec3_near("at the cached position", mA.getPosition(), LLVector3(2.f, 4.f, 6.f)); + require_same_rotation("at the cached rotation", mA.getRotation(), ROT_A); // and once the blenders are cleared, interpolating moves nothing mBlender.clearBlenders(); mA.setPosition(LLVector3::zero); mBlender.interpolate(1.f); - ensure_vec3_equals("a cleared blender no longer drives the joint", mA.getPosition(), LLVector3::zero); + require_vec3_near("a cleared blender no longer drives the joint", mA.getPosition(), LLVector3::zero); } - template<> template<> - void llposeblender_object::test<10>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: interpolating a rotation-only cache does not dirty the position", "[LLPoseBlender]") { // Interpolating toward the cache moves only the channels the blend // wrote. A joint on the coarse clock is interpolated every frame, so @@ -770,80 +783,17 @@ namespace addMotion(&motion); mBlender.blendAndCache(true); mA.updateWorldMatrixChildren(); - ensure("the joint starts clean", mA.mDirtyFlags == 0); + UNSCOPED_INFO("the joint starts clean"); + CHECK(mA.mDirtyFlags == 0); mBlender.interpolate(0.5f); - ensure("the rotation moved, so the matrix is dirty", (mA.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); - ensure("an unchanged position and scale dirty nothing of their own", - (mA.mDirtyFlags & LLJoint::POSITION_DIRTY) == 0); - } - - template<> template<> - void llposeblender_object::test<4>() - { - // The controller feeds additive motions to the blender before normal - // ones. At equal priority the earlier state stays in front, and an - // additive state only contributes while the states ahead of it have - // not already saturated the weight. - ALTestMotion base(LLUUID::generateNewID(), LLJoint::MEDIUM_PRIORITY, LLMotion::NORMAL_BLEND); - base.addJoint(&mA, LLJointState::ROT)->setRotation(ROT_A); - arm(base); - - ALTestMotion additive(LLUUID::generateNewID(), LLJoint::MEDIUM_PRIORITY, LLMotion::ADDITIVE_BLEND); - additive.addJoint(&mA, LLJointState::ROT)->setRotation(ROT_B); - arm(additive); - - addMotion(&additive); - addMotion(&base); - mBlender.blendAndApply(); - ensure_quat_equals("additive ahead of the base composes onto it", mA.getRotation(), ROT_B * ROT_A); - - mA.setRotation(LLQuaternion::DEFAULT); - addMotion(&base); - addMotion(&additive); - mBlender.blendAndApply(); - ensure_quat_equals("additive behind a saturated base is masked", mA.getRotation(), ROT_A); - } - - template<> template<> - void llposeblender_object::test<5>() - { - ALTestMotion motion; - motion.addJoint(&mA, LLJointState::ROT)->setRotation(ROT_A); - arm(motion); - - addMotion(&motion); - mBlender.blendAndApply(); - ensure_quat_equals("first apply", mA.getRotation(), ROT_A); - - // The active list is consumed by blendAndApply(). - mA.setRotation(ROT_C); - mBlender.blendAndApply(); - ensure_quat_equals("second apply without addMotion is a no-op", mA.getRotation(), ROT_C); - - // clearBlenders() discards queued states. - addMotion(&motion); - mBlender.clearBlenders(); - mBlender.blendAndApply(); - ensure_quat_equals("cleared queue is a no-op", mA.getRotation(), ROT_C); - } - - template<> template<> - void llposeblender_object::test<6>() - { - // A joint that never received a joint number is kept in the pose but - // left out of the motion's joint signature. - LLJoint unnumbered; - unnumbered.setup("unnumbered"); - ensure_equals("default joint number", unnumbered.getJointNum(), -1); - - ALTestMotion motion; - motion.addJoint(&unnumbered, LLJointState::ROT); - ensure_equals("unnumbered joint stays in the pose", motion.getPose()->getNumJointStates(), 1); + UNSCOPED_INFO("the rotation moved, so the matrix is dirty"); + CHECK((mA.mDirtyFlags & LLJoint::MATRIX_DIRTY) != 0); + UNSCOPED_INFO("an unchanged position and scale dirty nothing of their own"); + CHECK((mA.mDirtyFlags & LLJoint::POSITION_DIRTY) == 0); } - template<> template<> - void llposeblender_object::test<11>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: zero-weight motions do not take a joint's slots", "[LLPoseBlender]") { // A joint has six blend slots and they go by priority. A motion at // zero weight contributes nothing whatever slot it lands in, so @@ -856,7 +806,8 @@ namespace silent.push_back(std::make_unique(LLUUID::generateNewID(), LLJoint::HIGH_PRIORITY)); silent.back()->addJoint(&mA, LLJointState::ROT)->setRotation(ROT_B); // left at the weight a pose starts and ends its life on - ensure_equals("a silent motion weighs nothing", silent.back()->getPose()->getWeight(), 0.f); + UNSCOPED_INFO("a silent motion weighs nothing"); + REQUIRE(silent.back()->getPose()->getWeight() == 0.f); addMotion(silent.back().get()); } @@ -866,11 +817,10 @@ namespace addMotion(&playing); mBlender.blendAndApply(); - ensure_quat_equals("the motion with a weight still reaches the joint", mA.getRotation(), ROT_A); + require_same_rotation("the motion with a weight still reaches the joint", mA.getRotation(), ROT_A); } - template<> template<> - void llposeblender_object::test<12>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: leaving out a saturated rotation gives the same pose", "[LLPoseBlender]") { // The differential the skip rests on. A motion at full weight claims // the whole of a joint's rotation, so a lower priority rotation @@ -902,12 +852,33 @@ namespace mBlender.blendAndApply(); const LLQuaternion left_out = mA.getRotation(); - ensure_quat_equals("blending it in gives the owner's rotation", blended_in, ROT_A); - ensure_quat_equals("and so does leaving it out", left_out, blended_in); + require_same_rotation("blending it in gives the owner's rotation", blended_in, ROT_A); + require_same_rotation("and so does leaving it out", left_out, blended_in); + } + + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: only rotation is left out under a saturated joint", "[LLPoseBlender]") + { + // Only a rotation is left out. A state that also moves or scales the + // joint is summed on its own account, whatever is claimed above it. + U8 saturated[LL_CHARACTER_MAX_ANIMATED_JOINTS] = {}; + saturated[mA.getJointNum()] = 0xff >> (7 - LLJoint::HIGHEST_PRIORITY); + + const LLVector3 offset(0.f, 0.f, 0.75f); + + ALTestMotion mover(LLUUID::generateNewID(), LLJoint::LOW_PRIORITY); + LLPointer state = mover.addJoint(&mA, LLJointState::ROT | LLJointState::POS); + state->setRotation(ROT_B); + state->setPosition(offset); + arm(mover); + + mBlender.addMotion(&mover, saturated); + mBlender.blendAndApply(); + + require_vec3_near("the position still reaches the joint", mA.getPosition(), offset); + require_same_rotation("and so does the rotation that came with it", mA.getRotation(), ROT_B); } - template<> template<> - void llposeblender_object::test<14>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: a half-faded owner does not mask what is underneath", "[LLPoseBlender]") { // Which is why the mask is built from motions at full weight and no // others. An owner only part way in has not claimed the joint, the one @@ -927,35 +898,11 @@ namespace mBlender.blendAndApply(); const LLQuaternion got = mA.getRotation(); - ensure("a half faded owner leaves the one underneath showing through", - fabsf(dot(got, ROT_A)) < 0.999f && fabsf(dot(got, ROT_B)) < 0.999f); - } - - template<> template<> - void llposeblender_object::test<13>() - { - // Only a rotation is left out. A state that also moves or scales the - // joint is summed on its own account, whatever is claimed above it. - U8 saturated[LL_CHARACTER_MAX_ANIMATED_JOINTS] = {}; - saturated[mA.getJointNum()] = 0xff >> (7 - LLJoint::HIGHEST_PRIORITY); - - const LLVector3 offset(0.f, 0.f, 0.75f); - - ALTestMotion mover(LLUUID::generateNewID(), LLJoint::LOW_PRIORITY); - LLPointer state = mover.addJoint(&mA, LLJointState::ROT | LLJointState::POS); - state->setRotation(ROT_B); - state->setPosition(offset); - arm(mover); - - mBlender.addMotion(&mover, saturated); - mBlender.blendAndApply(); - - ensure_vec3_equals("the position still reaches the joint", mA.getPosition(), offset); - ensure_quat_equals("and so does the rotation that came with it", mA.getRotation(), ROT_B); + CHECK(fabsf(dot(got, ROT_A)) < 0.999f); + CHECK(fabsf(dot(got, ROT_B)) < 0.999f); } - template<> template<> - void llposeblender_object::test<15>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: left-out rotations leave a slot for a motion that needs one", "[LLPoseBlender]") { // What leaving them out buys, beyond the work: a joint has six slots // and a state that would be interpolated away to nothing was taking @@ -983,12 +930,11 @@ namespace mBlender.addMotion(&mover, saturated); mBlender.blendAndApply(); - ensure_vec3_equals("the slots were there for the motion that needed one", - mA.getPosition(), offset); + require_vec3_near("the slots were there for the motion that needed one", + mA.getPosition(), offset); } - template<> template<> - void llposeblender_object::test<16>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: interpolate leaves channels nothing blended where they are", "[LLPoseBlender]") { // A rotation-only blend caches a rotation and nothing else. What sits // in the other two is the joint's own value, put there when the cache @@ -1010,13 +956,13 @@ namespace mA.setScale(rescaled); mBlender.interpolate(0.5f); - ensure_vec3_equals("a position nothing blended is left where it is", mA.getPosition(), moved); - ensure_vec3_equals("and so is a scale", mA.getScale(), rescaled); - ensure("the channel that was blended still moves", mA.getRotation() != LLQuaternion::DEFAULT); + require_vec3_near("a position nothing blended is left where it is", mA.getPosition(), moved); + require_vec3_near("and so is a scale", mA.getScale(), rescaled); + UNSCOPED_INFO("the channel that was blended still moves"); + CHECK(mA.getRotation() != LLQuaternion::DEFAULT); } - template<> template<> - void llposeblender_object::test<17>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: interpolate follows the arc between rotations", "[LLPoseBlender]") { // The frames between quanta follow the arc, like the blend they are // standing in for. A quantum is up to a quarter of a second, which is @@ -1033,13 +979,12 @@ namespace mBlender.blendAndCache(true); mBlender.interpolate(0.25f); - ensure_quat_equals("a quarter of the way along the arc, not across it", - mA.getRotation(), slerp(0.25f, LLQuaternion::DEFAULT, far_apart)); + require_same_rotation("a quarter of the way along the arc, not across it", + mA.getRotation(), slerp(0.25f, LLQuaternion::DEFAULT, far_apart)); } - template<> template<> - void llposeblender_object::test<18>() + TEST_CASE_METHOD(llposeblender_data, "LLPoseBlender: the pelvis fix still gets a slot under a landing's seven states", "[LLPoseBlender]") { // Landing from a run puts seven states on the pelvis at once: the two // adjust servos, both live for the frame the fly one hands over to the @@ -1100,8 +1045,8 @@ namespace // The animations between them claim 0.15 of the joint, so the crouch // arrives about a seventh as deep. Refuse the fix a slot and it // arrives at seven eighths of it. - ensure("the pelvis fix took up the weight the animations did not claim", - fabsf(mA.getPosition().mV[VZ]) < 0.5f * fabsf(crouch.mV[VZ])); + UNSCOPED_INFO("the pelvis fix took up the weight the animations did not claim"); + CHECK(fabsf(mA.getPosition().mV[VZ]) < 0.5f * fabsf(crouch.mV[VZ])); } } diff --git a/indra/llfilesystem/CMakeLists.txt b/indra/llfilesystem/CMakeLists.txt index dce31cb115..e47b2e9daf 100644 --- a/indra/llfilesystem/CMakeLists.txt +++ b/indra/llfilesystem/CMakeLists.txt @@ -32,6 +32,10 @@ target_precompile_headers(llfilesystem REUSE_FROM llprecompiled) # Add tests if(AL_BUILD_TESTS) - al_add_test(lldiriterator PROJECT llfilesystem LIBRARIES llfilesystem) - al_add_test(lldir PROJECT llfilesystem LIBRARIES llmath llcommon llfilesystem) + al_add_test_binary( + llfilesystem_tests + PROJECT llfilesystem + SOURCES tests/lldir_test.cpp tests/lldiriterator_test.cpp + LIBRARIES llfilesystem llmath llcommon + ) endif() diff --git a/indra/llfilesystem/tests/lldir_test.cpp b/indra/llfilesystem/tests/lldir_test.cpp index 3f243d1e3b..adc8c793a6 100644 --- a/indra/llfilesystem/tests/lldir_test.cpp +++ b/indra/llfilesystem/tests/lldir_test.cpp @@ -28,12 +28,13 @@ #include "linden_common.h" #include +#include #include "llstring.h" #include "tests/StringVec.h" #include "../lldir.h" #include "../lldiriterator.h" -#include "../test/lltut.h" +#include #include "../test/namedtempfile.h" #include "stringize.h" @@ -168,15 +169,15 @@ struct LLDir_Dummy: public LLDir } void clear_checked() { mChecked.clear(); } - void ensure_checked(const std::string& pathname) const + void require_checked(const std::string& pathname) const { - tut::ensure(STRINGIZE(pathname << " was not checked but should have been"), - mChecked.find(pathname) != mChecked.end()); + INFO(pathname << " was not checked but should have been"); + REQUIRE(mChecked.find(pathname) != mChecked.end()); } - void ensure_not_checked(const std::string& pathname) const + void require_not_checked(const std::string& pathname) const { - tut::ensure(STRINGIZE(pathname << " was checked but should not have been"), - mChecked.find(pathname) == mChecked.end()); + INFO(pathname << " was checked but should not have been"); + REQUIRE(mChecked.find(pathname) == mChecked.end()); } std::set mFilesystem; @@ -272,28 +273,14 @@ namespace } #endif // LL_LINUX -namespace tut -{ namespace { - struct LLDirTest - { - }; - typedef test_group LLDirTest_t; - typedef LLDirTest_t::object LLDirTest_object_t; - tut::LLDirTest_t tut_LLDirTest("LLDir"); -} - - template<> template<> - void LLDirTest_object_t::test<1>() - // getDirDelimiter + TEST_CASE("LLDir: getDirDelimiter is not empty", "[LLDir]") { - ensure("getDirDelimiter", !gDirUtilp->getDirDelimiter().empty()); + CHECK_FALSE(gDirUtilp->getDirDelimiter().empty()); } - template<> template<> - void LLDirTest_object_t::test<2>() - // getBaseFileName + TEST_CASE("LLDir: getBaseFileName with and without stripping the extension", "[LLDir]") { std::string delim = gDirUtilp->getDirDelimiter(); std::string rawFile = "foo"; @@ -307,94 +294,38 @@ namespace std::string dottedPathExt = dottedPathNoExt + ".eXt"; // foo[.bAr] - - ensure_equals("getBaseFileName/r-no-ext/no-strip-exten", - gDirUtilp->getBaseFileName(rawFile, false), - "foo"); - - ensure_equals("getBaseFileName/r-no-ext/strip-exten", - gDirUtilp->getBaseFileName(rawFile, true), - "foo"); - - ensure_equals("getBaseFileName/r-ext/no-strip-exten", - gDirUtilp->getBaseFileName(rawFileExt, false), - "foo.bAr"); - - ensure_equals("getBaseFileName/r-ext/strip-exten", - gDirUtilp->getBaseFileName(rawFileExt, true), - "foo"); + CHECK(gDirUtilp->getBaseFileName(rawFile, false) == "foo"); + CHECK(gDirUtilp->getBaseFileName(rawFile, true) == "foo"); + CHECK(gDirUtilp->getBaseFileName(rawFileExt, false) == "foo.bAr"); + CHECK(gDirUtilp->getBaseFileName(rawFileExt, true) == "foo"); // foo. - - ensure_equals("getBaseFileName/rn-no-ext/no-strip-exten", - gDirUtilp->getBaseFileName(rawFileNullExt, false), - "foo."); - - ensure_equals("getBaseFileName/rn-no-ext/strip-exten", - gDirUtilp->getBaseFileName(rawFileNullExt, true), - "foo"); + CHECK(gDirUtilp->getBaseFileName(rawFileNullExt, false) == "foo."); + CHECK(gDirUtilp->getBaseFileName(rawFileNullExt, true) == "foo"); // .bAr // interesting case - with no basename, this IS the basename, not the extension. - - ensure_equals("getBaseFileName/e-ext/no-strip-exten", - gDirUtilp->getBaseFileName(rawExt, false), - ".bAr"); - - ensure_equals("getBaseFileName/e-ext/strip-exten", - gDirUtilp->getBaseFileName(rawExt, true), - ".bAr"); + CHECK(gDirUtilp->getBaseFileName(rawExt, false) == ".bAr"); + CHECK(gDirUtilp->getBaseFileName(rawExt, true) == ".bAr"); // . - - ensure_equals("getBaseFileName/d/no-strip-exten", - gDirUtilp->getBaseFileName(rawDot, false), - "."); - - ensure_equals("getBaseFileName/d/strip-exten", - gDirUtilp->getBaseFileName(rawDot, true), - "."); + CHECK(gDirUtilp->getBaseFileName(rawDot, false) == "."); + CHECK(gDirUtilp->getBaseFileName(rawDot, true) == "."); // aa/bb/cc/dd/ee[.eXt] - - ensure_equals("getBaseFileName/no-ext/no-strip-exten", - gDirUtilp->getBaseFileName(pathNoExt, false), - "ee"); - - ensure_equals("getBaseFileName/no-ext/strip-exten", - gDirUtilp->getBaseFileName(pathNoExt, true), - "ee"); - - ensure_equals("getBaseFileName/ext/no-strip-exten", - gDirUtilp->getBaseFileName(pathExt, false), - "ee.eXt"); - - ensure_equals("getBaseFileName/ext/strip-exten", - gDirUtilp->getBaseFileName(pathExt, true), - "ee"); + CHECK(gDirUtilp->getBaseFileName(pathNoExt, false) == "ee"); + CHECK(gDirUtilp->getBaseFileName(pathNoExt, true) == "ee"); + CHECK(gDirUtilp->getBaseFileName(pathExt, false) == "ee.eXt"); + CHECK(gDirUtilp->getBaseFileName(pathExt, true) == "ee"); // aa/bb/cc.dd/ee[.eXt] - - ensure_equals("getBaseFileName/d-no-ext/no-strip-exten", - gDirUtilp->getBaseFileName(dottedPathNoExt, false), - "ee"); - - ensure_equals("getBaseFileName/d-no-ext/strip-exten", - gDirUtilp->getBaseFileName(dottedPathNoExt, true), - "ee"); - - ensure_equals("getBaseFileName/d-ext/no-strip-exten", - gDirUtilp->getBaseFileName(dottedPathExt, false), - "ee.eXt"); - - ensure_equals("getBaseFileName/d-ext/strip-exten", - gDirUtilp->getBaseFileName(dottedPathExt, true), - "ee"); + CHECK(gDirUtilp->getBaseFileName(dottedPathNoExt, false) == "ee"); + CHECK(gDirUtilp->getBaseFileName(dottedPathNoExt, true) == "ee"); + CHECK(gDirUtilp->getBaseFileName(dottedPathExt, false) == "ee.eXt"); + CHECK(gDirUtilp->getBaseFileName(dottedPathExt, true) == "ee"); } - template<> template<> - void LLDirTest_object_t::test<3>() - // getDirName + TEST_CASE("LLDir: getDirName drops the last path component", "[LLDir]") { std::string delim = gDirUtilp->getDirDelimiter(); std::string rawFile = "foo"; @@ -405,39 +336,19 @@ namespace std::string dottedPathExt = dottedPathNoExt + ".eXt"; // foo[.bAr] - - ensure_equals("getDirName/r-no-ext", - gDirUtilp->getDirName(rawFile), - ""); - - ensure_equals("getDirName/r-ext", - gDirUtilp->getDirName(rawFileExt), - ""); + CHECK(gDirUtilp->getDirName(rawFile) == ""); + CHECK(gDirUtilp->getDirName(rawFileExt) == ""); // aa/bb/cc/dd/ee[.eXt] - - ensure_equals("getDirName/no-ext", - gDirUtilp->getDirName(pathNoExt), - "aa" + delim + "bb" + delim + "cc" + delim + "dd"); - - ensure_equals("getDirName/ext", - gDirUtilp->getDirName(pathExt), - "aa" + delim + "bb" + delim + "cc" + delim + "dd"); + CHECK(gDirUtilp->getDirName(pathNoExt) == "aa" + delim + "bb" + delim + "cc" + delim + "dd"); + CHECK(gDirUtilp->getDirName(pathExt) == "aa" + delim + "bb" + delim + "cc" + delim + "dd"); // aa/bb/cc.dd/ee[.eXt] - - ensure_equals("getDirName/d-no-ext", - gDirUtilp->getDirName(dottedPathNoExt), - "aa" + delim + "bb" + delim + "cc.dd"); - - ensure_equals("getDirName/d-ext", - gDirUtilp->getDirName(dottedPathExt), - "aa" + delim + "bb" + delim + "cc.dd"); + CHECK(gDirUtilp->getDirName(dottedPathNoExt) == "aa" + delim + "bb" + delim + "cc.dd"); + CHECK(gDirUtilp->getDirName(dottedPathExt) == "aa" + delim + "bb" + delim + "cc.dd"); } - template<> template<> - void LLDirTest_object_t::test<4>() - // getExtension + TEST_CASE("LLDir: getExtension is the lower-cased extension of the basename", "[LLDir]") { std::string delim = gDirUtilp->getDirDelimiter(); std::string rawFile = "foo"; @@ -451,65 +362,37 @@ namespace std::string dottedPathExt = dottedPathNoExt + ".eXt"; // foo[.bAr] - - ensure_equals("getExtension/r-no-ext", - gDirUtilp->getExtension(rawFile), - ""); - - ensure_equals("getExtension/r-ext", - gDirUtilp->getExtension(rawFileExt), - "bar"); + CHECK(gDirUtilp->getExtension(rawFile) == ""); + CHECK(gDirUtilp->getExtension(rawFileExt) == "bar"); // foo. - - ensure_equals("getExtension/rn-no-ext", - gDirUtilp->getExtension(rawFileNullExt), - ""); + CHECK(gDirUtilp->getExtension(rawFileNullExt) == ""); // .bAr // interesting case - with no basename, this IS the basename, not the extension. - - ensure_equals("getExtension/e-ext", - gDirUtilp->getExtension(rawExt), - ""); + CHECK(gDirUtilp->getExtension(rawExt) == ""); // . - - ensure_equals("getExtension/d", - gDirUtilp->getExtension(rawDot), - ""); + CHECK(gDirUtilp->getExtension(rawDot) == ""); // aa/bb/cc/dd/ee[.eXt] - - ensure_equals("getExtension/no-ext", - gDirUtilp->getExtension(pathNoExt), - ""); - - ensure_equals("getExtension/ext", - gDirUtilp->getExtension(pathExt), - "ext"); + CHECK(gDirUtilp->getExtension(pathNoExt) == ""); + CHECK(gDirUtilp->getExtension(pathExt) == "ext"); // aa/bb/cc.dd/ee[.eXt] - - ensure_equals("getExtension/d-no-ext", - gDirUtilp->getExtension(dottedPathNoExt), - ""); - - ensure_equals("getExtension/d-ext", - gDirUtilp->getExtension(dottedPathExt), - "ext"); + CHECK(gDirUtilp->getExtension(dottedPathNoExt) == ""); + CHECK(gDirUtilp->getExtension(dottedPathExt) == "ext"); } -namespace -{ std::string makeTestFile( const std::string& dir, const std::string& file ) { std::string path = dir + file; LLFILE* handle = LLFile::fopen(path, LLFILE_MODE("w")); - ensure("failed to open test file '"+path+"'", handle != NULL ); + CAPTURE(path); + REQUIRE(handle != NULL); // Harbison & Steele, 4th ed., p. 366: "If an error occurs, fputs // returns EOF; otherwise, it returns some other, nonnegative value." - ensure("failed to write to test file '"+path+"'", EOF != fputs("test file", handle) ); + REQUIRE(fputs("test file", handle) != EOF); fclose(handle); return path; } @@ -531,8 +414,9 @@ namespace static const char* DirScanFilename[5] = { "file1.abc", "file2.abc", "file1.xyz", "file2.xyz", "file1.mno" }; - void scanTest(const std::string& directory, const std::string& pattern, bool correctResult[5]) + void require_scan_returns(const std::string& directory, const std::string& pattern, bool correctResult[5]) { + CAPTURE(directory, pattern); // Scan directory and see if any file1.* files are found std::string scanResult; @@ -552,31 +436,30 @@ namespace // check is now either 5 (not found) or the index of the matching name if (check < 5) { - ensure( "found file '"+(std::string)DirScanFilename[check]+"' twice", ! filesFound[check] ); + UNSCOPED_INFO("found file '" << DirScanFilename[check] << "' twice"); + REQUIRE_FALSE(filesFound[check]); filesFound[check] = true; } else // check is 5 - should not happen { - fail( "found unknown file '"+scanResult+"'"); + FAIL("found unknown file '" << scanResult << "'"); } } for (int i=0; i<5; i++) { + CAPTURE(DirScanFilename[i]); if (correctResult[i]) { - ensure("scan of '"+directory+"' using '"+pattern+"' did not return '"+DirScanFilename[i]+"'", filesFound[i]); + REQUIRE(filesFound[i]); } else { - ensure("scan of '"+directory+"' using '"+pattern+"' incorrectly returned '"+DirScanFilename[i]+"'", !filesFound[i]); + REQUIRE_FALSE(filesFound[i]); } } } -} - template<> template<> - void LLDirTest_object_t::test<5>() - // LLDirIterator::next + TEST_CASE("LLDir: LLDirIterator::next matches glob patterns", "[LLDir]") { std::string delim = gDirUtilp->getDirDelimiter(); std::string dirTemp = LLFile::tmpdir(); @@ -596,61 +479,61 @@ namespace // Scan dir1 and see if each of the 5 files is found exactly once bool expected1[5] = { true, true, true, true, true }; - scanTest(dir1, "*", expected1); + require_scan_returns(dir1, "*", expected1); // Scan dir2 and see if only the 2 *.xyz files are found bool expected2[5] = { false, false, true, true, false }; - scanTest(dir1, "*.xyz", expected2); + require_scan_returns(dir1, "*.xyz", expected2); // Scan dir2 and see if only the 1 *.mno file is found bool expected3[5] = { false, false, false, false, true }; - scanTest(dir2, "*.mno", expected3); + require_scan_returns(dir2, "*.mno", expected3); // Scan dir1 and see if any *.foo files are found bool expected4[5] = { false, false, false, false, false }; - scanTest(dir1, "*.foo", expected4); + require_scan_returns(dir1, "*.foo", expected4); // Scan dir1 and see if any file1.* files are found bool expected5[5] = { true, false, true, false, true }; - scanTest(dir1, "file1.*", expected5); + require_scan_returns(dir1, "file1.*", expected5); // Scan dir1 and see if any file1.* files are found bool expected6[5] = { true, true, false, false, false }; - scanTest(dir1, "file?.abc", expected6); + require_scan_returns(dir1, "file?.abc", expected6); // Scan dir2 and see if any file?.x?z files are found bool expected7[5] = { false, false, true, true, false }; - scanTest(dir2, "file?.x?z", expected7); + require_scan_returns(dir2, "file?.x?z", expected7); // Scan dir2 and see if any file?.??c files are found bool expected8[5] = { true, true, false, false, false }; - scanTest(dir2, "file?.??c", expected8); - scanTest(dir2, "*.??c", expected8); + require_scan_returns(dir2, "file?.??c", expected8); + require_scan_returns(dir2, "*.??c", expected8); // Scan dir1 and see if any *.?n? files are found bool expected9[5] = { false, false, false, false, true }; - scanTest(dir1, "*.?n?", expected9); + require_scan_returns(dir1, "*.?n?", expected9); // Scan dir1 and see if any *.???? files are found bool expected10[5] = { false, false, false, false, false }; - scanTest(dir1, "*.????", expected10); + require_scan_returns(dir1, "*.????", expected10); // Scan dir1 and see if any ?????.* files are found bool expected11[5] = { true, true, true, true, true }; - scanTest(dir1, "?????.*", expected11); + require_scan_returns(dir1, "?????.*", expected11); // Scan dir1 and see if any ??l??.xyz files are found bool expected12[5] = { false, false, true, true, false }; - scanTest(dir1, "??l??.xyz", expected12); + require_scan_returns(dir1, "??l??.xyz", expected12); bool expected13[5] = { true, false, true, false, false }; - scanTest(dir1, "file1.{abc,xyz}", expected13); + require_scan_returns(dir1, "file1.{abc,xyz}", expected13); bool expected14[5] = { true, true, false, false, false }; - scanTest(dir1, "file[0-9].abc", expected14); + require_scan_returns(dir1, "file[0-9].abc", expected14); bool expected15[5] = { true, true, false, false, false }; - scanTest(dir1, "file[!a-z].abc", expected15); + require_scan_returns(dir1, "file[!a-z].abc", expected15); // clean up all test files and directories for (int i=0; i<5; i++) @@ -662,60 +545,58 @@ namespace LLFile::remove(dir2); } - template<> template<> - void LLDirTest_object_t::test<6>() + TEST_CASE("LLDir: findSkinnedFilenames searches skins and languages and caches probes", "[LLDir]") { - set_test_name("findSkinnedFilenames()"); LLDir_Dummy lldir; /*------------------------ "default", "en" -------------------------*/ // Setting "default" means we shouldn't consider any "*/skins/steam" // directories; setting "en" means we shouldn't consider any "xui/fr" // directories. lldir.setSkinFolder("default", "en"); - ensure_equals(lldir.getSkinFolder(), "default"); - ensure_equals(lldir.getLanguage(), "en"); + CHECK(lldir.getSkinFolder() == "default"); + CHECK(lldir.getLanguage() == "en"); // top-level directory of a skin isn't localized - ensure_equals(lldir.findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS), + CHECK(lldir.findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS) == StringVec{ "install/skins/default/colors.xml", "user/skins/default/colors.xml" }); // We should not have needed to check for skins/default/en. We should // just "know" that SKINBASE is not localized. - lldir.ensure_not_checked("install/skins/default/en"); + lldir.require_not_checked("install/skins/default/en"); - ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_default.jpeg"), + CHECK(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_default.jpeg") == StringVec{ "install/skins/default/textures/only_default.jpeg" }); // Nor should we have needed to check skins/default/textures/en // because textures is known not to be localized. - lldir.ensure_not_checked("install/skins/default/textures/en"); + lldir.require_not_checked("install/skins/default/textures/en"); StringVec expected(StringVec{ "install/skins/default/xui/en/strings.xml", "user/skins/default/xui/en/strings.xml" }); - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS) == expected); // The first time, we had to probe to find out whether xui was localized. - lldir.ensure_checked("install/skins/default/xui/en"); + lldir.require_checked("install/skins/default/xui/en"); lldir.clear_checked(); // Now make the same call again -- should return same result -- - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS) == expected); // but this time it should remember that xui is localized. - lldir.ensure_not_checked("install/skins/default/xui/en"); + lldir.require_not_checked("install/skins/default/xui/en"); // localized subdir with "en-us" instead of "en" - ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"), StringVec{ "install/skins/default/html/en-us/welcome.html" }); - lldir.ensure_checked("install/skins/default/html/en"); - lldir.ensure_checked("install/skins/default/html/en-us"); + CHECK(lldir.findSkinnedFilenames("html", "welcome.html") == StringVec{ "install/skins/default/html/en-us/welcome.html" }); + lldir.require_checked("install/skins/default/html/en"); + lldir.require_checked("install/skins/default/html/en-us"); lldir.clear_checked(); - ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"), StringVec{ "install/skins/default/html/en-us/welcome.html" }); - lldir.ensure_not_checked("install/skins/default/html/en"); - lldir.ensure_not_checked("install/skins/default/html/en-us"); + CHECK(lldir.findSkinnedFilenames("html", "welcome.html") == StringVec{ "install/skins/default/html/en-us/welcome.html" }); + lldir.require_not_checked("install/skins/default/html/en"); + lldir.require_not_checked("install/skins/default/html/en-us"); - ensure_equals(lldir.findSkinnedFilenames("future", "somefile.txt"), StringVec{ "install/skins/default/future/somefile.txt" }); + CHECK(lldir.findSkinnedFilenames("future", "somefile.txt") == StringVec{ "install/skins/default/future/somefile.txt" }); // Test probing for an unrecognized unlocalized future subdir. - lldir.ensure_checked("install/skins/default/future/en"); + lldir.require_checked("install/skins/default/future/en"); lldir.clear_checked(); - ensure_equals(lldir.findSkinnedFilenames("future", "somefile.txt"), StringVec{ "install/skins/default/future/somefile.txt" }); + CHECK(lldir.findSkinnedFilenames("future", "somefile.txt") == StringVec{ "install/skins/default/future/somefile.txt" }); // Second time it should remember that future is unlocalized. - lldir.ensure_not_checked("install/skins/default/future/en"); + lldir.require_not_checked("install/skins/default/future/en"); // When language is set to "en", requesting an html file pulls up the // "en-us" version -- not because it magically matches those strings, @@ -724,68 +605,68 @@ namespace // make the default localization be "en" and allow "en-gb" (or // whatever) localizations, which would work much more the way you'd // expect. - ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"), StringVec{ "install/skins/default/html/en-us/welcome.html" }); + CHECK(lldir.findSkinnedFilenames("html", "welcome.html") == StringVec{ "install/skins/default/html/en-us/welcome.html" }); /*------------------------ "default", "fr" -------------------------*/ // We start being able to distinguish localized subdirs from // unlocalized when we ask for a non-English language. lldir.setSkinFolder("default", "fr"); - ensure_equals(lldir.getLanguage(), "fr"); + CHECK(lldir.getLanguage() == "fr"); // pass merge=true to request this filename in all relevant skins - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS) == StringVec{ "install/skins/default/xui/en/strings.xml", "install/skins/default/xui/fr/strings.xml", "user/skins/default/xui/en/strings.xml", "user/skins/default/xui/fr/strings.xml" }); // pass (or default) merge=false to request only most specific skin - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml") == StringVec{ "user/skins/default/xui/en/strings.xml", "user/skins/default/xui/fr/strings.xml" }); // Our dummy floater.xml has a user localization (for "fr") but no // English override. This is a case in which CURRENT_SKIN nonetheless // returns paths from two different skins. - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "floater.xml"), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "floater.xml") == StringVec{ "install/skins/default/xui/en/floater.xml", "user/skins/default/xui/fr/floater.xml" }); // Our dummy newfile.xml has an English override but no user // localization. This is another case in which CURRENT_SKIN // nonetheless returns paths from two different skins. - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "newfile.xml"), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "newfile.xml") == StringVec{ "user/skins/default/xui/en/newfile.xml", "install/skins/default/xui/fr/newfile.xml" }); - ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"), + CHECK(lldir.findSkinnedFilenames("html", "welcome.html") == StringVec{ "install/skins/default/html/en-us/welcome.html", "install/skins/default/html/fr/welcome.html" }); /*------------------------ "default", "zh" -------------------------*/ lldir.setSkinFolder("default", "zh"); // Because strings.xml has only a "fr" override but no "zh" override // in any skin, the most localized version we can find is "en". - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"), StringVec{ "user/skins/default/xui/en/strings.xml" }); + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml") == StringVec{ "user/skins/default/xui/en/strings.xml" }); /*------------------------- "steam", "en" --------------------------*/ lldir.setSkinFolder("steam", "en"); - ensure_equals(lldir.findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS), + CHECK(lldir.findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS) == StringVec{ "install/skins/default/colors.xml", "install/skins/steam/colors.xml", "user/skins/default/colors.xml", "user/skins/steam/colors.xml" }); - ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_default.jpeg"), + CHECK(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_default.jpeg") == StringVec{ "install/skins/default/textures/only_default.jpeg" }); - ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_steam.jpeg"), + CHECK(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_steam.jpeg") == StringVec{ "install/skins/steam/textures/only_steam.jpeg" }); - ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_user_default.jpeg"), + CHECK(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_user_default.jpeg") == StringVec{ "user/skins/default/textures/only_user_default.jpeg" }); - ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_user_steam.jpeg"), + CHECK(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_user_steam.jpeg") == StringVec{ "user/skins/steam/textures/only_user_steam.jpeg" }); // CURRENT_SKIN - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"), StringVec{ "user/skins/steam/xui/en/strings.xml" }); + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml") == StringVec{ "user/skins/steam/xui/en/strings.xml" }); // pass constraint=ALL_SKINS to request this filename in all relevant skins - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS) == StringVec{ "install/skins/default/xui/en/strings.xml", "install/skins/steam/xui/en/strings.xml", "user/skins/default/xui/en/strings.xml", "user/skins/steam/xui/en/strings.xml" }); @@ -793,127 +674,125 @@ namespace lldir.setSkinFolder("steam", "fr"); // pass CURRENT_SKIN to request only the most specialized files - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml") == StringVec{ "user/skins/steam/xui/en/strings.xml", "user/skins/steam/xui/fr/strings.xml" }); // pass ALL_SKINS to request this filename in all relevant skins - ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS), + CHECK(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS) == StringVec{ "install/skins/default/xui/en/strings.xml", "install/skins/default/xui/fr/strings.xml", "install/skins/steam/xui/en/strings.xml", "install/skins/steam/xui/fr/strings.xml", "user/skins/default/xui/en/strings.xml", "user/skins/default/xui/fr/strings.xml", "user/skins/steam/xui/en/strings.xml", "user/skins/steam/xui/fr/strings.xml" }); } - template<> template<> - void LLDirTest_object_t::test<7>() + TEST_CASE("LLDir: add joins with a single slash and skips an empty part", "[LLDir]") { - set_test_name("add()"); LLDir_Dummy lldir; - ensure_equals("both empty", lldir.add("", ""), ""); - ensure_equals("path empty", lldir.add("", "b"), "b"); - ensure_equals("name empty", lldir.add("a", ""), "a"); - ensure_equals("both simple", lldir.add("a", "b"), "a/b"); - ensure_equals("name leading slash", lldir.add("a", "/b"), "a/b"); - ensure_equals("path trailing slash", lldir.add("a/", "b"), "a/b"); - ensure_equals("both bring slashes", lldir.add("a/", "/b"), "a/b"); + CHECK(lldir.add("", "") == ""); + CHECK(lldir.add("", "b") == "b"); + CHECK(lldir.add("a", "") == "a"); + CHECK(lldir.add("a", "b") == "a/b"); + CHECK(lldir.add("a", "/b") == "a/b"); + CHECK(lldir.add("a/", "b") == "a/b"); + CHECK(lldir.add("a/", "/b") == "a/b"); } - template<> template<> - void LLDirTest_object_t::test<8>() + TEST_CASE("LLDir: relocateLegacyPath maps a moved legacy profile into its new dirs", "[LLDir]") { - set_test_name("relocateLegacyPath()"); LLDir_Relocated lldir; - ensure_equals("root", lldir.relocateLegacyPath("old"), "user"); - ensure_equals("root slash", lldir.relocateLegacyPath("old/"), "user"); - ensure_equals("account", lldir.relocateLegacyPath("old/bob_resident"), "user/bob_resident"); - ensure_equals("settings", lldir.relocateLegacyPath("old/user_settings/settings.xml"), "cfg/settings.xml"); - ensure_equals("logs", lldir.relocateLegacyPath("old/logs"), "state"); - ensure_equals("cache", lldir.relocateLegacyPath("old/cache/texturecache"), "cachedir/texturecache"); - ensure_equals("sibling", lldir.relocateLegacyPath("oldish/x"), "oldish/x"); - ensure_equals("elsewhere", lldir.relocateLegacyPath("chat/logs"), "chat/logs"); + CHECK(lldir.relocateLegacyPath("old") == "user"); + CHECK(lldir.relocateLegacyPath("old/") == "user"); + CHECK(lldir.relocateLegacyPath("old/bob_resident") == "user/bob_resident"); + CHECK(lldir.relocateLegacyPath("old/user_settings/settings.xml") == "cfg/settings.xml"); + CHECK(lldir.relocateLegacyPath("old/logs") == "state"); + CHECK(lldir.relocateLegacyPath("old/cache/texturecache") == "cachedir/texturecache"); + UNSCOPED_INFO("a sibling sharing the root's prefix"); + CHECK(lldir.relocateLegacyPath("oldish/x") == "oldish/x"); + CHECK(lldir.relocateLegacyPath("chat/logs") == "chat/logs"); lldir.buildFilesystem("old/bob_resident"); - ensure_equals("still there", lldir.relocateLegacyPath("old/bob_resident"), "old/bob_resident"); + UNSCOPED_INFO("a path still in the legacy root stays"); + CHECK(lldir.relocateLegacyPath("old/bob_resident") == "old/bob_resident"); LLDir_Dummy unmoved; - ensure_equals("no legacy root", unmoved.relocateLegacyPath("old/bob_resident"), "old/bob_resident"); + UNSCOPED_INFO("no legacy root"); + CHECK(unmoved.relocateLegacyPath("old/bob_resident") == "old/bob_resident"); } #if LL_LINUX - template<> template<> - void LLDirTest_object_t::test<9>() + TEST_CASE("LLDir: LLDirXDG::layout uses only absolute XDG variables and legacyLayout the old dirs", "[LLDir]") { - set_test_name("LLDirXDG::layout()"); ScratchHome scratch; LLDirXDG::Layout dirs = LLDirXDG::layout("/h", "app"); - ensure_equals("data default", dirs.data, "/h/.local/share/app"); - ensure_equals("config default", dirs.config, "/h/.config/app"); - ensure_equals("cache default", dirs.cache, "/h/.cache/app"); - ensure_equals("state default", dirs.state, "/h/.local/state/app"); + CHECK(dirs.data == "/h/.local/share/app"); + CHECK(dirs.config == "/h/.config/app"); + CHECK(dirs.cache == "/h/.cache/app"); + CHECK(dirs.state == "/h/.local/state/app"); ::setenv("XDG_DATA_HOME", "/d", 1); ::setenv("XDG_CONFIG_HOME", "relative/c", 1); ::setenv("XDG_CACHE_HOME", "", 1); ::setenv("XDG_STATE_HOME", "/s", 1); dirs = LLDirXDG::layout("/h", "app"); - ensure_equals("data set", dirs.data, "/d/app"); - ensure_equals("config relative ignored", dirs.config, "/h/.config/app"); - ensure_equals("cache empty ignored", dirs.cache, "/h/.cache/app"); - ensure_equals("state set", dirs.state, "/s/app"); + CHECK(dirs.data == "/d/app"); + UNSCOPED_INFO("a relative XDG_CONFIG_HOME is ignored"); + CHECK(dirs.config == "/h/.config/app"); + UNSCOPED_INFO("an empty XDG_CACHE_HOME is ignored"); + CHECK(dirs.cache == "/h/.cache/app"); + CHECK(dirs.state == "/s/app"); dirs = LLDirXDG::legacyLayout("/h/.app"); - ensure_equals("legacy data", dirs.data, "/h/.app"); - ensure_equals("legacy config", dirs.config, "/h/.app/user_settings"); - ensure_equals("legacy cache", dirs.cache, "/h/.app/cache"); - ensure_equals("legacy state", dirs.state, "/h/.app/logs"); + CHECK(dirs.data == "/h/.app"); + CHECK(dirs.config == "/h/.app/user_settings"); + CHECK(dirs.cache == "/h/.app/cache"); + CHECK(dirs.state == "/h/.app/logs"); } - template<> template<> - void LLDirTest_object_t::test<10>() + TEST_CASE("LLDir: LLDirXDG::migrate moves a legacy profile into the XDG dirs", "[LLDir]") { - set_test_name("LLDirXDG::migrate() moves a legacy profile"); ScratchHome scratch; std::vector notes; const LLDirXDG::Layout dirs = LLDirXDG::layout(scratch.home, "app"); - ensure("no legacy profile", LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::NONE); - ensure("nothing made for no profile", !scratch.exists(".local")); + CHECK(LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::NONE); + CHECK_FALSE(scratch.exists(".local")); scratch.writeLegacyProfile(); - ensure("moved", LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::MOVED); - ensure("legacy root gone", !scratch.exists(".app")); - ensure("settings", scratch.exists(".config/app/settings.xml")); - ensure("logs", scratch.exists(".local/state/app/Alchemy.log")); - ensure("cache", scratch.exists(".cache/app/texturecache/t")); - ensure("account", scratch.exists(".local/share/app/bob_resident/settings_per_account.xml")); - ensure("skins", scratch.exists(".local/share/app/skins/default/colors.xml")); - ensure("no settings left in data", !scratch.exists(".local/share/app/user_settings")); - ensure("no logs left in data", !scratch.exists(".local/share/app/logs")); - ensure("no cache left in data", !scratch.exists(".local/share/app/cache")); - ensure("base dirs private", - (fs::status(scratch.path(".local/state")).permissions() & fs::perms::all) == fs::perms::owner_all); + CHECK(LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::MOVED); + CHECK_FALSE(scratch.exists(".app")); + CHECK(scratch.exists(".config/app/settings.xml")); + CHECK(scratch.exists(".local/state/app/Alchemy.log")); + CHECK(scratch.exists(".cache/app/texturecache/t")); + CHECK(scratch.exists(".local/share/app/bob_resident/settings_per_account.xml")); + CHECK(scratch.exists(".local/share/app/skins/default/colors.xml")); + CHECK_FALSE(scratch.exists(".local/share/app/user_settings")); + CHECK_FALSE(scratch.exists(".local/share/app/logs")); + CHECK_FALSE(scratch.exists(".local/share/app/cache")); + UNSCOPED_INFO("the base dirs it makes are private"); + CHECK((fs::status(scratch.path(".local/state")).permissions() & fs::perms::all) == fs::perms::owner_all); // an older viewer run afterwards makes a new legacy root; it stays put scratch.write(".app/user_settings/settings.xml", "new"); - ensure("second legacy root left", LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::NONE); - ensure("second legacy root intact", scratch.exists(".app/user_settings/settings.xml")); + UNSCOPED_INFO("a legacy root made after the move is left"); + CHECK(LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::NONE); + CHECK(scratch.exists(".app/user_settings/settings.xml")); } - template<> template<> - void LLDirTest_object_t::test<11>() + TEST_CASE("LLDir: LLDirXDG::migrate leaves a profile it cannot move whole", "[LLDir]") { - set_test_name("LLDirXDG::migrate() leaves a profile it can't move whole"); std::vector notes; { ScratchHome scratch; const LLDirXDG::Layout dirs = LLDirXDG::layout(scratch.home, "app"); scratch.writeLegacyProfile(); scratch.write(".config/app/other.xml"); - ensure("config dir in the way", LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::FAILED); - ensure("put back", scratch.legacyProfileIntact()); - ensure("no data dir", !scratch.exists(".local/share/app")); - ensure("no state dir", !scratch.exists(".local/state/app")); - ensure("config dir untouched", scratch.exists(".config/app/other.xml")); + UNSCOPED_INFO("a config dir in the way"); + CHECK(LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::FAILED); + UNSCOPED_INFO("the profile is put back"); + CHECK(scratch.legacyProfileIntact()); + CHECK_FALSE(scratch.exists(".local/share/app")); + CHECK_FALSE(scratch.exists(".local/state/app")); + CHECK(scratch.exists(".config/app/other.xml")); } { ScratchHome scratch; @@ -921,11 +800,13 @@ namespace scratch.writeLegacyProfile(); scratch.write(".app/logs/Alchemy.exec_marker"); const int fd = ::open(scratch.path(".app/logs/Alchemy.exec_marker").c_str(), O_RDWR); - ensure("marker locked", fd != -1 && ::flock(fd, LOCK_EX | LOCK_NB) == 0); - ensure("viewer running", LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::FAILED); - ensure("left while running", scratch.legacyProfileIntact()); + UNSCOPED_INFO("the running viewer's marker is locked"); + REQUIRE((fd != -1 && ::flock(fd, LOCK_EX | LOCK_NB) == 0)); + CHECK(LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::FAILED); + CHECK(scratch.legacyProfileIntact()); ::close(fd); - ensure("moved once it exits", LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::MOVED); + UNSCOPED_INFO("moved once the viewer exits"); + CHECK(LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::MOVED); } { ScratchHome scratch; @@ -933,34 +814,33 @@ namespace scratch.writeLegacyProfile(); fs::rename(scratch.path(".app"), scratch.path("elsewhere")); fs::create_directory_symlink(scratch.path("elsewhere"), scratch.path(".app")); - ensure("symlink kept", LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::FAILED); - ensure("symlinked profile intact", scratch.legacyProfileIntact()); - ensure("symlink still a symlink", fs::is_symlink(scratch.path(".app"))); + UNSCOPED_INFO("a symlinked profile"); + CHECK(LLDirXDG::migrate(scratch.path(".app"), dirs, notes) == LLDirXDG::Migration::FAILED); + CHECK(scratch.legacyProfileIntact()); + CHECK(fs::is_symlink(scratch.path(".app"))); } } - template<> template<> - void LLDirTest_object_t::test<12>() + TEST_CASE("LLDir: LLDirXDG::adoptCache renames an old cache only where none is in the way", "[LLDir]") { - set_test_name("LLDirXDG::adoptCache()"); ScratchHome scratch; std::vector notes; const std::string old_cache = scratch.path(".cache/App"); const std::string cache = scratch.path(".cache/app"); LLDirXDG::adoptCache(old_cache, cache, notes); - ensure("nothing to adopt", !scratch.exists(".cache")); + CHECK_FALSE(scratch.exists(".cache")); scratch.write(".cache/App/texturecache/t"); LLDirXDG::adoptCache(old_cache, cache, notes); - ensure("adopted", scratch.exists(".cache/app/texturecache/t")); - ensure("old name gone", !scratch.exists(".cache/App")); + CHECK(scratch.exists(".cache/app/texturecache/t")); + CHECK_FALSE(scratch.exists(".cache/App")); scratch.write(".cache/App/texturecache/stale"); LLDirXDG::adoptCache(old_cache, cache, notes); - ensure("current cache kept", scratch.exists(".cache/app/texturecache/t")); - ensure("old cache left beside it", scratch.exists(".cache/App/texturecache/stale")); - ensure("old cache not merged in", !scratch.exists(".cache/app/texturecache/stale")); + CHECK(scratch.exists(".cache/app/texturecache/t")); + CHECK(scratch.exists(".cache/App/texturecache/stale")); + CHECK_FALSE(scratch.exists(".cache/app/texturecache/stale")); } #endif // LL_LINUX } diff --git a/indra/llfilesystem/tests/lldiriterator_test.cpp b/indra/llfilesystem/tests/lldiriterator_test.cpp index 15470bd966..6877e266b3 100644 --- a/indra/llfilesystem/tests/lldiriterator_test.cpp +++ b/indra/llfilesystem/tests/lldiriterator_test.cpp @@ -26,24 +26,19 @@ */ #include "linden_common.h" -#include "lltut.h" +#include #include "../lldiriterator.h" -namespace tut -{ - namespace { + struct LLDirIteratorFixture { LLDirIteratorFixture() { } }; - typedef test_group LLDirIteratorTest_factory; - typedef LLDirIteratorTest_factory::object LLDirIteratorTest_t; - LLDirIteratorTest_factory tf("LLDirIterator"); /* CHOP-662 was originally introduced to deal with crashes deleting files from @@ -57,10 +52,8 @@ namespace LLDirIterator iter1(".","))--@---bad-group-name2((?\?-??.*\\.txt"); LLDirIterator iter2(".","__^v--x)Cuide d sua vida(x--v^__?\?-??.*"); } -} - template<> template<> - void LLDirIteratorTest_t::test<1>() + TEST_CASE_METHOD(LLDirIteratorFixture, "LLDirIterator: glob patterns full of regex characters do not crash", "[LLDirIterator]") { test_chop_662(); } diff --git a/indra/llimage/CMakeLists.txt b/indra/llimage/CMakeLists.txt index 5d9d9642ab..9cd09b4b9d 100644 --- a/indra/llimage/CMakeLists.txt +++ b/indra/llimage/CMakeLists.txt @@ -59,5 +59,11 @@ target_precompile_headers(llimage REUSE_FROM llprecompiled) if(AL_BUILD_TESTS) # It stubs the image classes the library defines, so it compiles the one # file it tests instead of linking the library. - al_add_test(llimageworker UNIT PROJECT llimage) + al_add_test_binary( + llimageworker_test + PROJECT llimage + SOURCES tests/llimageworker_test.cpp + UNIT llimageworker.cpp + LIBRARIES llmath llcommon + ) endif() diff --git a/indra/llimage/tests/llimageworker_test.cpp b/indra/llimage/tests/llimageworker_test.cpp index 663142d79d..ec27e2e1cc 100644 --- a/indra/llimage/tests/llimageworker_test.cpp +++ b/indra/llimage/tests/llimageworker_test.cpp @@ -33,8 +33,8 @@ #include "../llcommon/lltimer.h" // for lltrace class #include "../llcommon/lltrace.h" -// Tut header -#include "../test/lltut.h" +// Catch2 header +#include // ------------------------------------------------------------------------------------------- // Stubbing: Declarations required to link and run the class being tested @@ -95,11 +95,9 @@ const std::string& LLImage::getLastThreadError() { static std::string msg; retur // ------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------- -// TUT +// Tests // ------------------------------------------------------------------------------------------- -namespace tut -{ namespace { // Test wrapper declarations @@ -149,11 +147,6 @@ namespace } }; - // Tut templating thingamagic: test group, object and test instance - typedef test_group imagedecodethread_t; - typedef imagedecodethread_t::object imagedecodethread_object_t; - tut::imagedecodethread_t tut_imagedecodethread("LLImageDecodeThread"); -} // --------------------------------------------------------------------------------------- // Test functions @@ -167,17 +160,16 @@ namespace // Test the LLImageDecodeThread interface // --------------------------------------------------------------------------------------- - template<> template<> - void imagedecodethread_object_t::test<1>() + TEST_CASE_METHOD(imagedecodethread_test, "LLImageDecodeThread: a threaded decodeImage calls the responder", "[LLImageDecodeThread]") { // Test a *threaded* instance of the class mThread = new LLImageDecodeThread(true); - ensure("LLImageDecodeThread: threaded constructor failed", mThread != NULL); + REQUIRE(mThread != NULL); // Insert something in the queue bool done = false; LLImageDecodeThread::handle_t decodeHandle = mThread->decodeImage(NULL, 0, false, new responder_test(&done)); // Verifies we get back a valid handle - ensure("LLImageDecodeThread: threaded decodeImage(), returned handle is null", decodeHandle != 0); + CHECK(decodeHandle != 0); // Wait till the thread has time to handle the work order (though it doesn't do much per work order...) const U32 INCREMENT_TIME = 500; // 500 milliseconds const U32 MAX_TIME = 20 * INCREMENT_TIME; // Do the loop 20 times max, i.e. wait 10 seconds but no more @@ -188,6 +180,6 @@ namespace total_time += INCREMENT_TIME; } // Verifies that the responder has now been called - ensure("LLImageDecodeThread: threaded work unit not processed", done == true); + CHECK(done); } } diff --git a/indra/llinventory/CMakeLists.txt b/indra/llinventory/CMakeLists.txt index 7bdd325ccb..fc80455bcf 100644 --- a/indra/llinventory/CMakeLists.txt +++ b/indra/llinventory/CMakeLists.txt @@ -57,15 +57,16 @@ target_precompile_headers(llinventory REUSE_FROM llprecompiled) # add unit tests if(AL_BUILD_TESTS) - foreach( - test - inventorymisc - llparcel - llpermissions - llsaleinfo - llsettingssky - lluserrelations + al_add_test_binary( + llinventory_tests + PROJECT llinventory + SOURCES + tests/inventorymisc_test.cpp + tests/llparcel_test.cpp + tests/llpermissions_test.cpp + tests/llsaleinfo_test.cpp + tests/llsettingssky_test.cpp + tests/lluserrelations_test.cpp + LIBRARIES llinventory llmath llcorehttp llfilesystem ) - al_add_test(${test} PROJECT llinventory LIBRARIES llinventory llmath llcorehttp llfilesystem) - endforeach() endif() diff --git a/indra/llinventory/tests/inventorymisc_test.cpp b/indra/llinventory/tests/inventorymisc_test.cpp index 1a7eeeb26b..d7c0cab69a 100644 --- a/indra/llinventory/tests/inventorymisc_test.cpp +++ b/indra/llinventory/tests/inventorymisc_test.cpp @@ -31,7 +31,7 @@ #include "llsdserialize.h" #include "../llinventory.h" -#include "../test/lltut.h" +#include #include "../test/namedtempfile.h" @@ -127,86 +127,71 @@ LLPointer create_random_inventory_cat() } } -namespace tut -{ namespace { - struct inventory_data - { - }; - typedef test_group inventory_test; - typedef inventory_test::object inventory_object; - tut::inventory_test inv("LLInventory"); -} - //***class LLInventoryType***// - template<> template<> - void inventory_object::test<1>() + TEST_CASE("LLInventory: LLInventoryType::lookup maps a name to its type", "[LLInventory]") { LLInventoryType::EType retType = LLInventoryType::lookup(std::string("sound")); - ensure("1.LLInventoryType::lookup(char*) failed", retType == LLInventoryType::IT_SOUND); + CHECK(retType == LLInventoryType::IT_SOUND); retType = LLInventoryType::lookup(std::string("snapshot")); - ensure("2.LLInventoryType::lookup(char*) failed", retType == LLInventoryType::IT_SNAPSHOT); + CHECK(retType == LLInventoryType::IT_SNAPSHOT); } - template<> template<> - void inventory_object::test<2>() + TEST_CASE("LLInventory: LLInventoryType::lookup maps a type to its name", "[LLInventory]") { static std::string retType = LLInventoryType::lookup(LLInventoryType::IT_CALLINGCARD); - ensure("1.LLInventoryType::lookup(EType) failed", (retType == "callcard")); + CHECK(retType == "callcard"); retType = LLInventoryType::lookup(LLInventoryType::IT_LANDMARK); - ensure("2.LLInventoryType::lookup(EType) failed", (retType == "landmark")); + CHECK(retType == "landmark"); } - template<> template<> - void inventory_object::test<3>() + TEST_CASE("LLInventory: lookupHumanReadable gives a type's display name", "[LLInventory]") { static std::string retType = LLInventoryType::lookupHumanReadable(LLInventoryType::IT_CALLINGCARD); - ensure("1.LLInventoryType::lookupHumanReadable(EType) failed", (retType == "calling card")); + CHECK(retType == "calling card"); retType = LLInventoryType::lookupHumanReadable(LLInventoryType::IT_LANDMARK); - ensure("2.LLInventoryType::lookupHumanReadable(EType) failed", (retType == "landmark")); + CHECK(retType == "landmark"); } - template<> template<> - void inventory_object::test<4>() + TEST_CASE("LLInventory: defaultForAssetType gives an asset type's inventory type", "[LLInventory]") { static LLInventoryType::EType retType = LLInventoryType::defaultForAssetType(LLAssetType::AT_TEXTURE); - ensure("1.LLInventoryType::defaultForAssetType(LLAssetType EType) failed", retType == LLInventoryType::IT_TEXTURE); + CHECK(retType == LLInventoryType::IT_TEXTURE); retType = LLInventoryType::defaultForAssetType(LLAssetType::AT_LANDMARK); - ensure("2.LLInventoryType::defaultForAssetType(LLAssetType EType) failed", retType == LLInventoryType::IT_LANDMARK); + CHECK(retType == LLInventoryType::IT_LANDMARK); } //*****class LLInventoryItem*****// - template<> template<> - void inventory_object::test<5>() + TEST_CASE("LLInventory: an item survives an LLSD round trip before and after changes", "[LLInventory]") { LLPointer src = create_random_inventory_item(); LLSD sd = ll_create_sd_from_inventory_item(src); //LL_INFOS() << "sd: " << *sd << LL_ENDL; LLPointer dst = new LLInventoryItem; bool successful_parse = dst->fromLLSD(sd); - ensure_equals("0.LLInventoryItem::fromLLSD()", successful_parse, true); - ensure_equals("1.item id::getUUID() failed", dst->getUUID(), src->getUUID()); - ensure_equals("2.parent::getParentUUID() failed", dst->getParentUUID(), src->getParentUUID()); - ensure_equals("3.name::getName() failed", dst->getName(), src->getName()); - ensure_equals("4.type::getType() failed", dst->getType(), src->getType()); - - ensure_equals("5.permissions::getPermissions() failed", dst->getPermissions(), src->getPermissions()); - ensure_equals("6.description::getDescription() failed", dst->getDescription(), src->getDescription()); - ensure_equals("7.sale type::getSaleType() failed", dst->getSaleInfo().getSaleType(), src->getSaleInfo().getSaleType()); - ensure_equals("8.sale price::getSalePrice() failed", dst->getSaleInfo().getSalePrice(), src->getSaleInfo().getSalePrice()); - ensure_equals("9.asset id::getAssetUUID() failed", dst->getAssetUUID(), src->getAssetUUID()); - ensure_equals("10.inventory type::getInventoryType() failed", dst->getInventoryType(), src->getInventoryType()); - ensure_equals("11.flags::getFlags() failed", dst->getFlags(), src->getFlags()); - ensure_equals("12.creation::getCreationDate() failed", dst->getCreationDate(), src->getCreationDate()); + CHECK(successful_parse); + CHECK(dst->getUUID() == src->getUUID()); + CHECK(dst->getParentUUID() == src->getParentUUID()); + CHECK(dst->getName() == src->getName()); + CHECK(dst->getType() == src->getType()); + + CHECK(dst->getPermissions() == src->getPermissions()); + CHECK(dst->getDescription() == src->getDescription()); + CHECK(dst->getSaleInfo().getSaleType() == src->getSaleInfo().getSaleType()); + CHECK(dst->getSaleInfo().getSalePrice() == src->getSaleInfo().getSalePrice()); + CHECK(dst->getAssetUUID() == src->getAssetUUID()); + CHECK(dst->getInventoryType() == src->getInventoryType()); + CHECK(dst->getFlags() == src->getFlags()); + CHECK(dst->getCreationDate() == src->getCreationDate()); LLUUID new_item_id, new_parent_id; new_item_id.generate(); @@ -258,24 +243,23 @@ namespace sd = ll_create_sd_from_inventory_item(src); //LL_INFOS() << "sd: " << *sd << LL_ENDL; successful_parse = dst->fromLLSD(sd); - ensure_equals("13.item id::getUUID() failed", dst->getUUID(), src->getUUID()); - ensure_equals("14.parent::getParentUUID() failed", dst->getParentUUID(), src->getParentUUID()); - ensure_equals("15.name::getName() failed", dst->getName(), src->getName()); - ensure_equals("16.type::getType() failed", dst->getType(), src->getType()); - - ensure_equals("17.permissions::getPermissions() failed", dst->getPermissions(), src->getPermissions()); - ensure_equals("18.description::getDescription() failed", dst->getDescription(), src->getDescription()); - ensure_equals("19.sale type::getSaleType() failed type", dst->getSaleInfo().getSaleType(), src->getSaleInfo().getSaleType()); - ensure_equals("20.sale price::getSalePrice() failed price", dst->getSaleInfo().getSalePrice(), src->getSaleInfo().getSalePrice()); - ensure_equals("21.asset id::getAssetUUID() failed id", dst->getAssetUUID(), src->getAssetUUID()); - ensure_equals("22.inventory type::getInventoryType() failed type", dst->getInventoryType(), src->getInventoryType()); - ensure_equals("23.flags::getFlags() failed", dst->getFlags(), src->getFlags()); - ensure_equals("24.creation::getCreationDate() failed", dst->getCreationDate(), src->getCreationDate()); + CHECK(dst->getUUID() == src->getUUID()); + CHECK(dst->getParentUUID() == src->getParentUUID()); + CHECK(dst->getName() == src->getName()); + CHECK(dst->getType() == src->getType()); + + CHECK(dst->getPermissions() == src->getPermissions()); + CHECK(dst->getDescription() == src->getDescription()); + CHECK(dst->getSaleInfo().getSaleType() == src->getSaleInfo().getSaleType()); + CHECK(dst->getSaleInfo().getSalePrice() == src->getSaleInfo().getSalePrice()); + CHECK(dst->getAssetUUID() == src->getAssetUUID()); + CHECK(dst->getInventoryType() == src->getInventoryType()); + CHECK(dst->getFlags() == src->getFlags()); + CHECK(dst->getCreationDate() == src->getCreationDate()); } - template<> template<> - void inventory_object::test<6>() + TEST_CASE("LLInventory: copyItem copies an item and generateUUID gives it a new id", "[LLInventory]") { LLPointer src = create_random_inventory_item(); @@ -331,32 +315,31 @@ namespace LLSD sd = ll_create_sd_from_inventory_item(src); LLPointer dst = new LLInventoryItem; bool successful_parse = dst->fromLLSD(sd); - ensure_equals("0.LLInventoryItem::fromLLSD()", successful_parse, true); + CHECK(successful_parse); LLPointer src1 = create_random_inventory_item(); src1->copyItem(src); - ensure_equals("1.item id::getUUID() failed", dst->getUUID(), src1->getUUID()); - ensure_equals("2.parent::getParentUUID() failed", dst->getParentUUID(), src1->getParentUUID()); - ensure_equals("3.name::getName() failed", dst->getName(), src1->getName()); - ensure_equals("4.type::getType() failed", dst->getType(), src1->getType()); + CHECK(dst->getUUID() == src1->getUUID()); + CHECK(dst->getParentUUID() == src1->getParentUUID()); + CHECK(dst->getName() == src1->getName()); + CHECK(dst->getType() == src1->getType()); - ensure_equals("5.permissions::getPermissions() failed", dst->getPermissions(), src1->getPermissions()); - ensure_equals("6.description::getDescription() failed", dst->getDescription(), src1->getDescription()); - ensure_equals("7.sale type::getSaleType() failed type", dst->getSaleInfo().getSaleType(), src1->getSaleInfo().getSaleType()); - ensure_equals("8.sale price::getSalePrice() failed price", dst->getSaleInfo().getSalePrice(), src1->getSaleInfo().getSalePrice()); - ensure_equals("9.asset id::getAssetUUID() failed id", dst->getAssetUUID(), src1->getAssetUUID()); - ensure_equals("10.inventory type::getInventoryType() failed type", dst->getInventoryType(), src1->getInventoryType()); - ensure_equals("11.flags::getFlags() failed", dst->getFlags(), src1->getFlags()); - ensure_equals("12.creation::getCreationDate() failed", dst->getCreationDate(), src1->getCreationDate()); + CHECK(dst->getPermissions() == src1->getPermissions()); + CHECK(dst->getDescription() == src1->getDescription()); + CHECK(dst->getSaleInfo().getSaleType() == src1->getSaleInfo().getSaleType()); + CHECK(dst->getSaleInfo().getSalePrice() == src1->getSaleInfo().getSalePrice()); + CHECK(dst->getAssetUUID() == src1->getAssetUUID()); + CHECK(dst->getInventoryType() == src1->getInventoryType()); + CHECK(dst->getFlags() == src1->getFlags()); + CHECK(dst->getCreationDate() == src1->getCreationDate()); // quick test to make sure generateUUID() really works src1->generateUUID(); - ensure_not_equals("13.item id::generateUUID() failed", src->getUUID(), src1->getUUID()); + CHECK(src->getUUID() != src1->getUUID()); } - template<> template<> - void inventory_object::test<7>() + TEST_CASE("LLInventory: an item survives asLLSD through a notation file", "[LLInventory]") { NamedTempFile temp_file("linden_file", "", ".dat"); std::string filename(temp_file.getPath().string()); @@ -395,22 +378,21 @@ namespace file.close(); - ensure_equals("1.item id::getUUID() failed", src1->getUUID(), src2->getUUID()); - ensure_equals("2.parent::getParentUUID() failed", src1->getParentUUID(), src2->getParentUUID()); - ensure_equals("3.permissions::getPermissions() failed", src1->getPermissions(), src2->getPermissions()); - ensure_equals("4.sale price::getSalePrice() failed price", src1->getSaleInfo().getSalePrice(), src2->getSaleInfo().getSalePrice()); - ensure_equals("5.asset id::getAssetUUID() failed id", src1->getAssetUUID(), src2->getAssetUUID()); - ensure_equals("6.type::getType() failed", src1->getType(), src2->getType()); - ensure_equals("7.inventory type::getInventoryType() failed type", src1->getInventoryType(), src2->getInventoryType()); - ensure_equals("8.name::getName() failed", src1->getName(), src2->getName()); - ensure_equals("9.description::getDescription() failed", src1->getDescription(), src2->getDescription()); - ensure_equals("10.creation::getCreationDate() failed", src1->getCreationDate(), src2->getCreationDate()); - ensure_equals("13.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); - ensure_equals("14.favorites::getIsFavorite() failed", src1->getIsFavorite(), src2->getIsFavorite()); + CHECK(src1->getUUID() == src2->getUUID()); + CHECK(src1->getParentUUID() == src2->getParentUUID()); + CHECK(src1->getPermissions() == src2->getPermissions()); + CHECK(src1->getSaleInfo().getSalePrice() == src2->getSaleInfo().getSalePrice()); + CHECK(src1->getAssetUUID() == src2->getAssetUUID()); + CHECK(src1->getType() == src2->getType()); + CHECK(src1->getInventoryType() == src2->getInventoryType()); + CHECK(src1->getName() == src2->getName()); + CHECK(src1->getDescription() == src2->getDescription()); + CHECK(src1->getCreationDate() == src2->getCreationDate()); + CHECK(src1->getThumbnailUUID() == src2->getThumbnailUUID()); + CHECK(src1->getIsFavorite() == src2->getIsFavorite()); } - template<> template<> - void inventory_object::test<8>() + TEST_CASE("LLInventory: an item survives the legacy stream but its favorite does not", "[LLInventory]") { LLPointer src1 = create_random_inventory_item(); @@ -421,76 +403,72 @@ namespace LLPointer src2 = new LLInventoryItem(); src2->importLegacyStream(istream); - ensure_equals("1.item id::getUUID() failed", src1->getUUID(), src2->getUUID()); - ensure_equals("2.parent::getParentUUID() failed", src1->getParentUUID(), src2->getParentUUID()); - ensure_equals("3.permissions::getPermissions() failed", src1->getPermissions(), src2->getPermissions()); - ensure_equals("4.sale price::getSalePrice() failed price", src1->getSaleInfo().getSalePrice(), src2->getSaleInfo().getSalePrice()); - ensure_equals("5.asset id::getAssetUUID() failed id", src1->getAssetUUID(), src2->getAssetUUID()); - ensure_equals("6.type::getType() failed", src1->getType(), src2->getType()); - ensure_equals("7.inventory type::getInventoryType() failed type", src1->getInventoryType(), src2->getInventoryType()); - ensure_equals("8.name::getName() failed", src1->getName(), src2->getName()); - ensure_equals("9.description::getDescription() failed", src1->getDescription(), src2->getDescription()); - ensure_equals("10.creation::getCreationDate() failed", src1->getCreationDate(), src2->getCreationDate()); - ensure_equals("11.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); - ensure_equals("12.favorites::getIsFavorite() failed", false, src2->getIsFavorite()); // not supposed to carry over + CHECK(src1->getUUID() == src2->getUUID()); + CHECK(src1->getParentUUID() == src2->getParentUUID()); + CHECK(src1->getPermissions() == src2->getPermissions()); + CHECK(src1->getSaleInfo().getSalePrice() == src2->getSaleInfo().getSalePrice()); + CHECK(src1->getAssetUUID() == src2->getAssetUUID()); + CHECK(src1->getType() == src2->getType()); + CHECK(src1->getInventoryType() == src2->getInventoryType()); + CHECK(src1->getName() == src2->getName()); + CHECK(src1->getDescription() == src2->getDescription()); + CHECK(src1->getCreationDate() == src2->getCreationDate()); + CHECK(src1->getThumbnailUUID() == src2->getThumbnailUUID()); + CHECK_FALSE(src2->getIsFavorite()); // not supposed to carry over } - template<> template<> - void inventory_object::test<9>() + TEST_CASE("LLInventory: placeholder for the removed XML export and import", "[LLInventory]") { // Deleted LLInventoryItem::exportFileXML() and LLInventoryItem::importXML() // because I can't find any non-test code references to it. 2009-05-04 JC } - template<> template<> - void inventory_object::test<11>() + TEST_CASE("LLInventory: an item survives asLLSD and fromLLSD", "[LLInventory]") { LLPointer src1 = create_random_inventory_item(); LLSD retSd = src1->asLLSD(); LLPointer src2 = new LLInventoryItem(); src2->fromLLSD(retSd); - ensure_equals("1.item id::getUUID() failed", src1->getUUID(), src2->getUUID()); - ensure_equals("2.parent::getParentUUID() failed", src1->getParentUUID(), src2->getParentUUID()); - ensure_equals("3.permissions::getPermissions() failed", src1->getPermissions(), src2->getPermissions()); - ensure_equals("4.asset id::getAssetUUID() failed id", src1->getAssetUUID(), src2->getAssetUUID()); - ensure_equals("5.type::getType() failed", src1->getType(), src2->getType()); - ensure_equals("6.inventory type::getInventoryType() failed type", src1->getInventoryType(), src2->getInventoryType()); - ensure_equals("7.flags::getFlags() failed", src1->getFlags(), src2->getFlags()); - ensure_equals("8.sale type::getSaleType() failed type", src1->getSaleInfo().getSaleType(), src2->getSaleInfo().getSaleType()); - ensure_equals("9.sale price::getSalePrice() failed price", src1->getSaleInfo().getSalePrice(), src2->getSaleInfo().getSalePrice()); - ensure_equals("10.name::getName() failed", src1->getName(), src2->getName()); - ensure_equals("11.description::getDescription() failed", src1->getDescription(), src2->getDescription()); - ensure_equals("12.creation::getCreationDate() failed", src1->getCreationDate(), src2->getCreationDate()); - ensure_equals("13.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); - ensure_equals("14.favorites::getIsFavorite() failed", src1->getIsFavorite(), src2->getIsFavorite()); + CHECK(src1->getUUID() == src2->getUUID()); + CHECK(src1->getParentUUID() == src2->getParentUUID()); + CHECK(src1->getPermissions() == src2->getPermissions()); + CHECK(src1->getAssetUUID() == src2->getAssetUUID()); + CHECK(src1->getType() == src2->getType()); + CHECK(src1->getInventoryType() == src2->getInventoryType()); + CHECK(src1->getFlags() == src2->getFlags()); + CHECK(src1->getSaleInfo().getSaleType() == src2->getSaleInfo().getSaleType()); + CHECK(src1->getSaleInfo().getSalePrice() == src2->getSaleInfo().getSalePrice()); + CHECK(src1->getName() == src2->getName()); + CHECK(src1->getDescription() == src2->getDescription()); + CHECK(src1->getCreationDate() == src2->getCreationDate()); + CHECK(src1->getThumbnailUUID() == src2->getThumbnailUUID()); + CHECK(src1->getIsFavorite() == src2->getIsFavorite()); } //******class LLInventoryCategory*******// - template<> template<> - void inventory_object::test<12>() + TEST_CASE("LLInventory: a category survives an LLSD round trip with its preferred type", "[LLInventory]") { LLPointer src = create_random_inventory_cat(); LLSD sd = ll_create_sd_from_inventory_category(src); LLPointer dst = ll_create_category_from_sd(sd); - ensure_equals("1.item id::getUUID() failed", dst->getUUID(), src->getUUID()); - ensure_equals("2.parent::getParentUUID() failed", dst->getParentUUID(), src->getParentUUID()); - ensure_equals("3.name::getName() failed", dst->getName(), src->getName()); - ensure_equals("4.type::getType() failed", dst->getType(), src->getType()); - ensure_equals("5.preferred type::getPreferredType() failed", dst->getPreferredType(), src->getPreferredType()); + CHECK(dst->getUUID() == src->getUUID()); + CHECK(dst->getParentUUID() == src->getParentUUID()); + CHECK(dst->getName() == src->getName()); + CHECK(dst->getType() == src->getType()); + CHECK(dst->getPreferredType() == src->getPreferredType()); src->setPreferredType( LLFolderType::FT_TEXTURE); sd = ll_create_sd_from_inventory_category(src); dst = ll_create_category_from_sd(sd); - ensure_equals("6.preferred type::getPreferredType() failed", dst->getPreferredType(), src->getPreferredType()); + CHECK(dst->getPreferredType() == src->getPreferredType()); } - template<> template<> - void inventory_object::test<13>() + TEST_CASE("LLInventory: a category survives exportLLSD through a notation file", "[LLInventory]") { NamedTempFile temp_file("linden_file", "", ".dat"); std::string filename(temp_file.getPath().string()); @@ -529,17 +507,16 @@ namespace LLPointer src2 = new LLInventoryCategory(); src2->importLLSDMap(s_item); - ensure_equals("1.item id::getUUID() failed", src1->getUUID(), src2->getUUID()); - ensure_equals("2.parent::getParentUUID() failed", src1->getParentUUID(), src2->getParentUUID()); - ensure_equals("3.type::getType() failed", src1->getType(), src2->getType()); - ensure_equals("4.preferred type::getPreferredType() failed", src1->getPreferredType(), src2->getPreferredType()); - ensure_equals("5.name::getName() failed", src1->getName(), src2->getName()); - ensure_equals("6.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); - ensure_equals("7.favorites::getIsFavorite() failed", src1->getIsFavorite(), src2->getIsFavorite()); + CHECK(src1->getUUID() == src2->getUUID()); + CHECK(src1->getParentUUID() == src2->getParentUUID()); + CHECK(src1->getType() == src2->getType()); + CHECK(src1->getPreferredType() == src2->getPreferredType()); + CHECK(src1->getName() == src2->getName()); + CHECK(src1->getThumbnailUUID() == src2->getThumbnailUUID()); + CHECK(src1->getIsFavorite() == src2->getIsFavorite()); } - template<> template<> - void inventory_object::test<14>() + TEST_CASE("LLInventory: a category survives the legacy stream but its favorite does not", "[LLInventory]") { LLPointer src1 = create_random_inventory_cat(); @@ -550,12 +527,12 @@ namespace LLPointer src2 = new LLInventoryCategory(); src2->importLegacyStream(istream); - ensure_equals("1.item id::getUUID() failed", src1->getUUID(), src2->getUUID()); - ensure_equals("2.parent::getParentUUID() failed", src1->getParentUUID(), src2->getParentUUID()); - ensure_equals("3.type::getType() failed", src1->getType(), src2->getType()); - ensure_equals("4.preferred type::getPreferredType() failed", src1->getPreferredType(), src2->getPreferredType()); - ensure_equals("5.name::getName() failed", src1->getName(), src2->getName()); - ensure_equals("13.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); - ensure_equals("14.favorites::getIsFavorite() failed", false, src2->getIsFavorite()); // currently not supposed to carry over + CHECK(src1->getUUID() == src2->getUUID()); + CHECK(src1->getParentUUID() == src2->getParentUUID()); + CHECK(src1->getType() == src2->getType()); + CHECK(src1->getPreferredType() == src2->getPreferredType()); + CHECK(src1->getName() == src2->getName()); + CHECK(src1->getThumbnailUUID() == src2->getThumbnailUUID()); + CHECK_FALSE(src2->getIsFavorite()); // currently not supposed to carry over } } diff --git a/indra/llinventory/tests/llparcel_test.cpp b/indra/llinventory/tests/llparcel_test.cpp index c32d17b449..50e90663fd 100644 --- a/indra/llinventory/tests/llparcel_test.cpp +++ b/indra/llinventory/tests/llparcel_test.cpp @@ -31,42 +31,29 @@ #include "../llparcel.h" -#include "../test/lltut.h" +#include -namespace tut -{ namespace { - struct llinventoryparcel_data - { - }; - typedef test_group llinventoryparcel_test; - typedef llinventoryparcel_test::object llinventoryparcel_object; - tut::llinventoryparcel_test llinventoryparcel("LLInventoryParcel"); -} - - template<> template<> - void llinventoryparcel_object::test<1>() + TEST_CASE("LLInventoryParcel: each category's string and UI string map back to it", "[LLInventoryParcel]") { for (S32 i=0; i +#include #include "linden_common.h" -#include "lltut.h" #include "message.h" #include "llpermissions.h" -namespace tut -{ namespace { - struct permission - { - }; - typedef test_group permission_t; - typedef permission_t::object permission_object_t; - tut::permission_t tut_permission("permission"); -} - - template<> template<> - void permission_object_t::test<1>() + TEST_CASE("permission: a default LLPermissions has null ids and full masks and no owner", "[permission]") { LLPermissions permissions; LLUUID uuid = permissions.getCreator(); @@ -56,15 +44,22 @@ namespace LLUUID uuid2 = permissions.getGroup(); LLUUID uuid3 = permissions.getLastOwner(); - ensure("LLPermission Get Functions failed", (uuid == LLUUID::null && uuid1 == LLUUID::null && - uuid2 == LLUUID::null && uuid3 == LLUUID::null)); - ensure("LLPermission Get Functions failed", (permissions.getMaskBase() == PERM_ALL && permissions.getMaskOwner() == PERM_ALL && - permissions.getMaskGroup() == PERM_ALL && permissions.getMaskEveryone() == PERM_ALL && permissions.getMaskNextOwner() == PERM_ALL)); - ensure("Ownership functions failed", ((! permissions.isGroupOwned()) && (! permissions.isOwned()))); + CHECK(uuid == LLUUID::null); + CHECK(uuid1 == LLUUID::null); + CHECK(uuid2 == LLUUID::null); + CHECK(uuid3 == LLUUID::null); + + CHECK(permissions.getMaskBase() == PERM_ALL); + CHECK(permissions.getMaskOwner() == PERM_ALL); + CHECK(permissions.getMaskGroup() == PERM_ALL); + CHECK(permissions.getMaskEveryone() == PERM_ALL); + CHECK(permissions.getMaskNextOwner() == PERM_ALL); + + CHECK_FALSE(permissions.isGroupOwned()); + CHECK_FALSE(permissions.isOwned()); } - template<> template<> - void permission_object_t::test<2>() + TEST_CASE("permission: init sets the creator and owner and last owner and group", "[permission]") { LLPermissions permissions; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -73,14 +68,13 @@ namespace LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); permissions.init(creator, owner, lastOwner, group); - ensure_equals("init/getCreator():failed to return the creator ", creator, permissions.getCreator()); - ensure_equals("init/getOwner():failed to return the owner ", owner, permissions.getOwner()); - ensure_equals("init/getLastOwner():failed to return the group ", lastOwner, permissions.getLastOwner()); - ensure_equals("init/getGroup():failed to return the group ", group, permissions.getGroup()); + CHECK(creator == permissions.getCreator()); + CHECK(owner == permissions.getOwner()); + CHECK(lastOwner == permissions.getLastOwner()); + CHECK(group == permissions.getGroup()); } - template<> template<> - void permission_object_t::test<3>() + TEST_CASE("permission: initMasks fixes the masks and the mask setters keep them as given", "[permission]") { LLPermissions permissions; U32 base = PERM_ALL; @@ -96,27 +90,27 @@ namespace U32 fixednext = PERM_NONE; permissions.initMasks(base, owner, everyone, group, next); // will fix perms if not allowed. - ensure_equals("initMasks/getMaskBase():failed to return the MaskBase ", fixedbase, permissions.getMaskBase()); - ensure_equals("initMasks/getMaskOwner():failed to return the MaskOwner ", fixedowner, permissions.getMaskOwner()); - ensure_equals("initMasks/getMaskEveryone():failed to return the MaskGroup ", fixedgroup, permissions.getMaskGroup()); - ensure_equals("initMasks/getMaskEveryone():failed to return the MaskEveryone ", fixedeveryone, permissions.getMaskEveryone()); - ensure_equals("initMasks/getMaskNextOwner():failed to return the MaskNext ", fixednext, permissions.getMaskNextOwner()); + CHECK(fixedbase == permissions.getMaskBase()); + CHECK(fixedowner == permissions.getMaskOwner()); + CHECK(fixedgroup == permissions.getMaskGroup()); + CHECK(fixedeveryone == permissions.getMaskEveryone()); + CHECK(fixednext == permissions.getMaskNextOwner()); // explictly set should maintain the values permissions.setMaskBase(base); //no fixing - ensure_equals("setMaskBase/getMaskBase():failed to return the MaskBase ", base, permissions.getMaskBase()); + CHECK(base == permissions.getMaskBase()); permissions.setMaskOwner(owner); - ensure_equals("setMaskOwner/getMaskOwner():failed to return the MaskOwner ", owner, permissions.getMaskOwner()); + CHECK(owner == permissions.getMaskOwner()); permissions.setMaskEveryone(everyone); - ensure_equals("setMaskEveryone/getMaskEveryone():failed to return the MaskEveryone ", everyone, permissions.getMaskEveryone()); + CHECK(everyone == permissions.getMaskEveryone()); permissions.setMaskGroup(group); - ensure_equals("setMaskGroup/getMaskEveryone():failed to return the MaskGroup ", group, permissions.getMaskGroup()); + CHECK(group == permissions.getMaskGroup()); permissions.setMaskNext(next); - ensure_equals("setMaskNext/getMaskNextOwner():failed to return the MaskNext ", next, permissions.getMaskNextOwner()); + CHECK(next == permissions.getMaskNextOwner()); // further tests can be added to ensure perms for owner/group/everyone etc. get properly fixed. // code however suggests that there is no explict check if the perms are correct and the user of this @@ -124,8 +118,7 @@ namespace // perm combinations. } - template<> template<> - void permission_object_t::test<4>() + TEST_CASE("permission: set copies the ids of another LLPermissions", "[permission]") { LLPermissions perm,perm1; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -134,12 +127,13 @@ namespace LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm1.init(creator,owner,lastOwner,group); perm.set(perm1); - ensure("set():failed to set ", (creator == perm.getCreator()) && (owner == perm.getOwner())&& - (lastOwner == perm.getLastOwner())&& (group == perm.getGroup())); - } + CHECK(creator == perm.getCreator()); + CHECK(owner == perm.getOwner()); + CHECK(lastOwner == perm.getLastOwner()); + CHECK(group == perm.getGroup()); + } - template<> template<> - void permission_object_t::test<5>() + TEST_CASE("permission: accumulate restricts the masks to those both allow", "[permission]") { LLPermissions perm,perm1; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -174,15 +168,14 @@ namespace U32 fixedeveryone = PERM_TRANSFER; U32 fixednext = PERM_NONE; - ensure_equals("accumulate failed ", fixedbase, perm.getMaskBase()); - ensure_equals("accumulate failed ", fixedowner, perm.getMaskOwner()); - ensure_equals("accumulate failed ", fixedgroup, perm.getMaskGroup()); - ensure_equals("accumulate failed ", fixedeveryone, perm.getMaskEveryone()); - ensure_equals("accumulate failed ", fixednext, perm.getMaskNextOwner()); + CHECK(fixedbase == perm.getMaskBase()); + CHECK(fixedowner == perm.getMaskOwner()); + CHECK(fixedgroup == perm.getMaskGroup()); + CHECK(fixedeveryone == perm.getMaskEveryone()); + CHECK(fixednext == perm.getMaskNextOwner()); } - template<> template<> - void permission_object_t::test<6>() + TEST_CASE("permission: getSafeOwner is the owner or the group where there is none", "[permission]") { LLPermissions perm; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -190,20 +183,19 @@ namespace LLUUID lastOwner("5e47a0dc-97bf-44e0-8b40-de06718cee9d"); LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm.init(creator,owner,lastOwner,group); - ensure_equals("getSafeOwner:failed ", owner,perm.getSafeOwner()); + CHECK(owner == perm.getSafeOwner()); ///NULL Owner perm.init(creator,LLUUID::null,lastOwner,group); - ensure_equals("getSafeOwner:failed ", group, perm.getSafeOwner()); + CHECK(group == perm.getSafeOwner()); } - template<> template<> - void permission_object_t::test<7>() + TEST_CASE("permission: getOwnership gives the owner or else the owning group", "[permission]") { LLPermissions perm1; LLUUID uuid; bool is_group_owned = false; - ensure("1:getOwnership:failed ", ! perm1.getOwnership(uuid,is_group_owned)); + CHECK_FALSE(perm1.getOwnership(uuid,is_group_owned)); LLPermissions perm; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -212,15 +204,16 @@ namespace LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm.init(creator,owner,lastOwner,group); perm.getOwnership(uuid,is_group_owned); - ensure("2:getOwnership:failed ", ((uuid == owner) && (! is_group_owned))); + CHECK(uuid == owner); + CHECK_FALSE(is_group_owned); perm.init(creator,LLUUID::null,lastOwner,group); perm.getOwnership(uuid,is_group_owned); - ensure("3:getOwnership:failed ", ((uuid == group) && is_group_owned)); + CHECK(uuid == group); + CHECK(is_group_owned); } - template<> template<> - void permission_object_t::test<8>() + TEST_CASE("permission: equal LLPermissions have the same CRC32", "[permission]") { LLPermissions perm,perm1; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -229,89 +222,83 @@ namespace LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm.init(creator,owner,lastOwner,group); perm1.init(creator,owner,lastOwner,group); - ensure_equals("getCRC32:failed ", perm.getCRC32(),perm1.getCRC32()); + CHECK(perm.getCRC32() == perm1.getCRC32()); } - template<> template<> - void permission_object_t::test<9>() + TEST_CASE("permission: setOwnerAndGroup needs the agent to be the current owner", "[permission]") { LLPermissions perm; LLUUID agent("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); LLUUID owner("68edcf47-ccd7-45b8-9f90-1649d7f12806"); LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); bool is_atomic = true; - ensure("setOwnerAndGroup():failed ", perm.setOwnerAndGroup(agent,owner,group,is_atomic)); + REQUIRE(perm.setOwnerAndGroup(agent,owner,group,is_atomic)); LLUUID owner2("68edcf47-ccd7-45b8-9f90-1649d7f12807"); LLUUID group2("9c8eca51-53d5-42a7-bb58-cef070395db9"); // cant change - agent need to be current owner - ensure("setOwnerAndGroup():failed ", ! perm.setOwnerAndGroup(agent,owner2,group2,is_atomic)); + CHECK_FALSE(perm.setOwnerAndGroup(agent,owner2,group2,is_atomic)); // should be able to change - agent and owner same as current owner - ensure("setOwnerAndGroup():failed ", perm.setOwnerAndGroup(owner,owner,group2,is_atomic)); + CHECK(perm.setOwnerAndGroup(owner,owner,group2,is_atomic)); } - template<> template<> - void permission_object_t::test<10>() + TEST_CASE("permission: the system can deed to a group", "[permission]") { LLPermissions perm; LLUUID agent; LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); - ensure("deedToGroup():failed ", perm.deedToGroup(agent,group)); + CHECK(perm.deedToGroup(agent,group)); } - template<> template<> - void permission_object_t::test<11>() + TEST_CASE("permission: the system but not a stranger sets base and owner bits", "[permission]") { LLPermissions perm; LLUUID agent; bool set = true; U32 bits = PERM_TRANSFER | PERM_MODIFY; - ensure("setBaseBits():failed ", perm.setBaseBits(agent, set, bits)); - ensure("setOwnerBits():failed ", perm.setOwnerBits(agent, set, bits)); + CHECK(perm.setBaseBits(agent, set, bits)); + CHECK(perm.setOwnerBits(agent, set, bits)); LLUUID agent1("9c8eca51-53d5-42a7-bb58-cef070395db8"); - ensure("setBaseBits():failed ", ! perm.setBaseBits(agent1, set, bits)); - ensure("setOwnerBits():failed ", ! perm.setOwnerBits(agent1, set, bits)); + CHECK_FALSE(perm.setBaseBits(agent1, set, bits)); + CHECK_FALSE(perm.setOwnerBits(agent1, set, bits)); } - template<> template<> - void permission_object_t::test<12>() + TEST_CASE("permission: the system but not a stranger sets group and everyone and next bits", "[permission]") { LLPermissions perm; LLUUID agent; LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); bool set = true; U32 bits = 10; - ensure("setGroupBits():failed ", perm.setGroupBits(agent,group, set, bits)); - ensure("setEveryoneBits():failed ", perm.setEveryoneBits(agent,group, set, bits)); - ensure("setNextOwnerBits():failed ", perm.setNextOwnerBits(agent,group, set, bits)); + CHECK(perm.setGroupBits(agent,group, set, bits)); + CHECK(perm.setEveryoneBits(agent,group, set, bits)); + CHECK(perm.setNextOwnerBits(agent,group, set, bits)); LLUUID agent1("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); - ensure("setGroupBits():failed ", ! perm.setGroupBits(agent1,group, set, bits)); - ensure("setEveryoneBits():failed ", ! perm.setEveryoneBits(agent1,group, set, bits)); - ensure("setNextOwnerBits():failed ", ! perm.setNextOwnerBits(agent1,group, set, bits)); + CHECK_FALSE(perm.setGroupBits(agent1,group, set, bits)); + CHECK_FALSE(perm.setEveryoneBits(agent1,group, set, bits)); + CHECK_FALSE(perm.setNextOwnerBits(agent1,group, set, bits)); } - template<> template<> - void permission_object_t::test<13>() + TEST_CASE("permission: allowOperationBy allows the system and a member of the group", "[permission]") { LLPermissions perm; LLUUID agent; LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); U32 bits = 10; - ensure("allowOperationBy():failed ", perm.allowOperationBy(bits,agent,group)); + CHECK(perm.allowOperationBy(bits,agent,group)); LLUUID agent1("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); LLUUID owner("68edcf47-ccd7-45b8-9f90-1649d7f12806"); LLUUID lastOwner("5e47a0dc-97bf-44e0-8b40-de06718cee9d"); perm.init(creator,owner,lastOwner,group); - ensure("allowOperationBy():failed ", perm.allowOperationBy(bits,agent1,group)); + CHECK(perm.allowOperationBy(bits,agent1,group)); } - template<> template<> - void permission_object_t::test<14>() + TEST_CASE("permission: allowModifyBy allows any agent under the default masks", "[permission]") { LLPermissions perm; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -320,16 +307,15 @@ namespace LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm.init(creator,owner,lastOwner,group); LLUUID agent; - ensure("1:allowModifyBy():failed ", perm.allowModifyBy(agent)); - ensure("2:allowModifyBy():failed ", perm.allowModifyBy(agent,group)); + CHECK(perm.allowModifyBy(agent)); + CHECK(perm.allowModifyBy(agent,group)); LLUUID agent1("9c8eca51-53d5-42a7-bb58-cef070395db8"); - ensure("3:allowModifyBy():failed ", perm.allowModifyBy(agent1)); - ensure("4:allowModifyBy():failed ", perm.allowModifyBy(agent1,group)); + CHECK(perm.allowModifyBy(agent1)); + CHECK(perm.allowModifyBy(agent1,group)); } - template<> template<> - void permission_object_t::test<15>() + TEST_CASE("permission: allowCopyBy allows any agent under the default masks", "[permission]") { LLPermissions perm; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -338,16 +324,15 @@ namespace LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm.init(creator,owner,lastOwner,group); LLUUID agent; - ensure("1:allowCopyBy():failed ", perm.allowModifyBy(agent)); - ensure("2:allowCopyBy():failed ", perm.allowModifyBy(agent,group)); + CHECK(perm.allowModifyBy(agent)); + CHECK(perm.allowModifyBy(agent,group)); LLUUID agent1("9c8eca51-53d5-42a7-bb58-cef070395db8"); - ensure("3:allowCopyBy():failed ", perm.allowCopyBy(agent1)); - ensure("4:allowCopyBy():failed ", perm.allowCopyBy(agent1,group)); + CHECK(perm.allowCopyBy(agent1)); + CHECK(perm.allowCopyBy(agent1,group)); } - template<> template<> - void permission_object_t::test<16>() + TEST_CASE("permission: allowMoveBy allows any agent under the default masks", "[permission]") { LLPermissions perm; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -356,16 +341,15 @@ namespace LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm.init(creator,owner,lastOwner,group); LLUUID agent; - ensure("1:allowMoveBy():failed ", perm.allowMoveBy(agent)); - ensure("2:allowMoveBy():failed ", perm.allowMoveBy(agent,group)); + CHECK(perm.allowMoveBy(agent)); + CHECK(perm.allowMoveBy(agent,group)); LLUUID agent1("9c8eca51-53d5-42a7-bb58-cef070395db8"); - ensure("3:allowMoveBy():failed ", perm.allowMoveBy(agent1)); - ensure("4:allowMoveBy():failed ", perm.allowMoveBy(agent1,group)); + CHECK(perm.allowMoveBy(agent1)); + CHECK(perm.allowMoveBy(agent1,group)); } - template<> template<> - void permission_object_t::test<17>() + TEST_CASE("permission: allowTransferTo allows the null agent while the owner is null", "[permission]") { LLPermissions perm; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -373,17 +357,16 @@ namespace LLUUID lastOwner("5e47a0dc-97bf-44e0-8b40-de06718cee9d"); LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); LLUUID agent; - ensure("1:allowMoveBy():failed ", perm.allowTransferTo(agent)); + CHECK(perm.allowTransferTo(agent)); perm.init(creator,owner,lastOwner,group); - ensure("2:allowMoveBy():failed ", perm.allowTransferTo(agent)); + CHECK(perm.allowTransferTo(agent)); } - template<> template<> - void permission_object_t::test<18>() + TEST_CASE("permission: operator== holds for defaults and after assignment", "[permission]") { LLPermissions perm,perm1; - ensure_equals("1:Operator==:failed ", perm, perm1); + CHECK(perm == perm1); LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); LLUUID owner("68edcf47-ccd7-45b8-9f90-1649d7f12806"); @@ -391,11 +374,10 @@ namespace LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm.init(creator,owner,lastOwner,group); perm = perm1; - ensure_equals("2:Operator==:failed ", perm, perm1); + CHECK(perm == perm1); } - template<> template<> - void permission_object_t::test<19>() + TEST_CASE("permission: operator!= tells an initialised LLPermissions from a default one", "[permission]") { LLPermissions perm,perm1; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -403,11 +385,10 @@ namespace LLUUID lastOwner("5e47a0dc-97bf-44e0-8b40-de06718cee9d"); LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); perm.init(creator,owner,lastOwner,group); - ensure_not_equals("2:Operator==:failed ", perm, perm1); + CHECK(perm != perm1); } - template<> template<> - void permission_object_t::test<20>() + TEST_CASE("permission: the legacy stream export and import round-trip", "[permission]") { LLPermissions perm,perm1; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -429,11 +410,10 @@ namespace std::istringstream istream(ostream.str()); perm1.importLegacyStream(istream); - ensure_equals("exportStream()/importStream():failed to export and import the data ", perm1, perm); + CHECK(perm1 == perm); } - template<> template<> - void permission_object_t::test<21>() + TEST_CASE("permission: operator<< writes equal LLPermissions alike", "[permission]") { LLPermissions perm,perm1; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -445,11 +425,10 @@ namespace stream1 << perm; perm1.init(creator,owner,lastOwner,group); stream2 << perm1; - ensure_equals("1:operator << failed", stream1.str(), stream2.str()); + CHECK(stream1.str() == stream2.str()); } - template<> template<> - void permission_object_t::test<22>() + TEST_CASE("permission: LLPermissions survive an LLSD round trip", "[permission]") { LLPermissions perm,perm1; LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); @@ -468,27 +447,26 @@ namespace LLSD sd = ll_create_sd_from_permissions(perm); perm1 = ll_permissions_from_sd(sd); - ensure_equals("ll_permissions_from_sd() and ll_create_sd_from_permissions()functions failed", perm, perm1); + CHECK(perm == perm1); } - template<> template<> - void permission_object_t::test<23>() + TEST_CASE("permission: LLAggregatePermissions aggregates masks and prints them", "[permission]") { LLAggregatePermissions AggrPermission; LLAggregatePermissions AggrPermission1; - ensure_equals("getU8() function failed", AggrPermission.getU8(), 0); - ensure("isEmpty() function failed", AggrPermission.isEmpty()); + CHECK(AggrPermission.getU8() == 0); + CHECK(AggrPermission.isEmpty()); AggrPermission.getValue(PERM_TRANSFER); - ensure_equals("getValue() function failed", AggrPermission.getValue(PERM_TRANSFER), 0x00); + CHECK(AggrPermission.getValue(PERM_TRANSFER) == 0x00); AggrPermission.aggregate(PERM_ITEM_UNRESTRICTED); - ensure("aggregate() function failed", ! AggrPermission.isEmpty()); + CHECK_FALSE(AggrPermission.isEmpty()); AggrPermission1.aggregate(AggrPermission); - ensure("aggregate() function failed", ! AggrPermission1.isEmpty()); + CHECK_FALSE(AggrPermission1.isEmpty()); std::ostringstream stream1; stream1 << AggrPermission; - ensure_equals("operator<< failed", stream1.str(), "{PI_COPY=All, PI_MODIFY=All, PI_TRANSFER=All}"); + CHECK(stream1.str() == "{PI_COPY=All, PI_MODIFY=All, PI_TRANSFER=All}"); } } diff --git a/indra/llinventory/tests/llsaleinfo_test.cpp b/indra/llinventory/tests/llsaleinfo_test.cpp index 44c47c2662..c41d938013 100644 --- a/indra/llinventory/tests/llsaleinfo_test.cpp +++ b/indra/llinventory/tests/llsaleinfo_test.cpp @@ -26,25 +26,14 @@ * $/LicenseInfo$ */ -#include +#include +#include #include "linden_common.h" -#include "lltut.h" #include "llsaleinfo.h" -namespace tut -{ namespace { - struct llsaleinfo_tut - { - }; - typedef test_group llsaleinfo_tut_t; - typedef llsaleinfo_tut_t::object llsaleinfo_test_t; - tut::llsaleinfo_tut_t tut_llsaleinfo_test("llsaleinfo"); -} - - template<> template<> - void llsaleinfo_test_t::test<1>() + TEST_CASE("llsaleinfo: each sale type sets its price and CRC32 and survives LLSD", "[llsaleinfo]") { //test case for getSaleType(), getSalePrice(), getCRC32() fn. //test case for setSaleType(), setSalePrice() fn. @@ -56,13 +45,13 @@ namespace LLSD llsd_obj1 = ll_create_sd_from_sale_info(llsaleinfo); LLSaleInfo saleinfo1 = ll_sale_info_from_sd(llsd_obj1); - ensure("1. The getSaleType() fn failed", LLSaleInfo::FS_COPY == llsaleinfo.getSaleType()); - ensure("2. LLSaleInfo::isForSale() fn failed", true == llsaleinfo.isForSale()); - ensure("3. The getSalePrice() fn failed", sale_price == llsaleinfo.getSalePrice()); - ensure("4. The getCRC32() fn failed", 235833404 == llsaleinfo.getCRC32()); - ensure("5. LLSaleInfo::lookup(const char* name) fn failed", LLSaleInfo::FS_COPY == llsaleinfo.lookup(sale)); - ensure_equals("6. ll_create_sd_from_sale_info() fn failed", llsaleinfo.getSalePrice(), saleinfo1.getSalePrice()); - ensure_equals("7. ll_create_sd_from_sale_info() fn failed", llsaleinfo.getSaleType(), saleinfo1.getSaleType()); + CHECK(llsaleinfo.getSaleType() == LLSaleInfo::FS_COPY); + CHECK(llsaleinfo.isForSale()); + CHECK(sale_price == llsaleinfo.getSalePrice()); + CHECK(llsaleinfo.getCRC32() == 235833404); + CHECK(llsaleinfo.lookup(sale) == LLSaleInfo::FS_COPY); + CHECK(llsaleinfo.getSalePrice() == saleinfo1.getSalePrice()); + CHECK(llsaleinfo.getSaleType() == saleinfo1.getSaleType()); llsaleinfo.setSalePrice(10000000); llsaleinfo.setSaleType(LLSaleInfo::FS_ORIGINAL); @@ -70,13 +59,13 @@ namespace llsd_obj1 = ll_create_sd_from_sale_info(llsaleinfo); saleinfo1 = ll_sale_info_from_sd(llsd_obj1); - ensure("8. The getSaleType() and setSaleType() fn failed", LLSaleInfo::FS_ORIGINAL == llsaleinfo.getSaleType()); - ensure("9. LLSaleInfo::isForSale() fn failed", true == llsaleinfo.isForSale()); - ensure("10. The getSalePrice() fn failed", 10000000 == llsaleinfo.getSalePrice()); - ensure("11. The getCRC32() fn failed", 127911702 == llsaleinfo.getCRC32()); - ensure("12. LLSaleInfo::lookup(const char* name) fn failed", LLSaleInfo::FS_CONTENTS == llsaleinfo.lookup(sale)); - ensure_equals("13. ll_create_sd_from_sale_info() fn failed", llsaleinfo.getSalePrice(), saleinfo1.getSalePrice()); - ensure_equals("14. ll_create_sd_from_sale_info() fn failed", llsaleinfo.getSaleType(), saleinfo1.getSaleType()); + CHECK(llsaleinfo.getSaleType() == LLSaleInfo::FS_ORIGINAL); + CHECK(llsaleinfo.isForSale()); + CHECK(llsaleinfo.getSalePrice() == 10000000); + CHECK(llsaleinfo.getCRC32() == 127911702); + CHECK(llsaleinfo.lookup(sale) == LLSaleInfo::FS_CONTENTS); + CHECK(llsaleinfo.getSalePrice() == saleinfo1.getSalePrice()); + CHECK(llsaleinfo.getSaleType() == saleinfo1.getSaleType()); llsaleinfo.setSalePrice(55000550); llsaleinfo.setSaleType(LLSaleInfo::FS_CONTENTS); @@ -84,13 +73,13 @@ namespace llsd_obj1 = ll_create_sd_from_sale_info(llsaleinfo); saleinfo1 = ll_sale_info_from_sd(llsd_obj1); - ensure("15. The getSaleType() and setSaleType() fn failed", LLSaleInfo::FS_CONTENTS == llsaleinfo.getSaleType()); - ensure("16. LLSaleInfo::isForSale() fn failed", true == llsaleinfo.isForSale()); - ensure("17. The getSalePrice() fn failed", 55000550 == llsaleinfo.getSalePrice()); - ensure("18. The getCRC32() fn failed", 408735656 == llsaleinfo.getCRC32()); - ensure("19. LLSaleInfo::lookup(const char* name) fn failed", LLSaleInfo::FS_ORIGINAL == llsaleinfo.lookup(sale)); - ensure_equals("20. ll_create_sd_from_sale_info() fn failed", llsaleinfo.getSalePrice(), saleinfo1.getSalePrice()); - ensure_equals("21. ll_create_sd_from_sale_info() fn failed", llsaleinfo.getSaleType(), saleinfo1.getSaleType()); + CHECK(llsaleinfo.getSaleType() == LLSaleInfo::FS_CONTENTS); + CHECK(llsaleinfo.isForSale()); + CHECK(llsaleinfo.getSalePrice() == 55000550); + CHECK(llsaleinfo.getCRC32() == 408735656); + CHECK(llsaleinfo.lookup(sale) == LLSaleInfo::FS_ORIGINAL); + CHECK(llsaleinfo.getSalePrice() == saleinfo1.getSalePrice()); + CHECK(llsaleinfo.getSaleType() == saleinfo1.getSaleType()); llsaleinfo.setSalePrice(-6432); llsaleinfo.setSaleType(LLSaleInfo::FS_NOT); @@ -98,17 +87,16 @@ namespace llsd_obj1 = ll_create_sd_from_sale_info(llsaleinfo); saleinfo1 = ll_sale_info_from_sd(llsd_obj1); - ensure("22. The getSaleType() and setSaleType() fn failed", LLSaleInfo::FS_NOT == llsaleinfo.getSaleType()); - ensure("23. LLSaleInfo::isForSale() fn failed", false == llsaleinfo.isForSale()); - ensure("24. The getSalePrice() fn failed", 0 == llsaleinfo.getSalePrice()); - ensure("25. The getCRC32() fn failed", 0 == llsaleinfo.getCRC32()); - ensure("26. LLSaleInfo::lookup(const char* name) fn failed", LLSaleInfo::FS_NOT == llsaleinfo.lookup(sale)); - ensure_equals("27. ll_create_sd_from_sale_info() fn failed", llsaleinfo.getSalePrice(), saleinfo1.getSalePrice()); - ensure_equals("28. ll_create_sd_from_sale_info() fn failed", llsaleinfo.getSaleType(), saleinfo1.getSaleType()); + CHECK(llsaleinfo.getSaleType() == LLSaleInfo::FS_NOT); + CHECK_FALSE(llsaleinfo.isForSale()); + CHECK(llsaleinfo.getSalePrice() == 0); + CHECK(llsaleinfo.getCRC32() == 0); + CHECK(llsaleinfo.lookup(sale) == LLSaleInfo::FS_NOT); + CHECK(llsaleinfo.getSalePrice() == saleinfo1.getSalePrice()); + CHECK(llsaleinfo.getSaleType() == saleinfo1.getSaleType()); } - template<> template<> - void llsaleinfo_test_t::test<2>() + TEST_CASE("llsaleinfo: the legacy stream export and import round-trip", "[llsaleinfo]") { S32 sale_price = 525452; LLSaleInfo llsaleinfo(LLSaleInfo::FS_ORIGINAL, sale_price); @@ -122,13 +110,11 @@ namespace bool has_perm_mask = false; llsaleinfo1.importLegacyStream(istream, has_perm_mask, perm_mask); - ensure("importStream() fn failed ", - llsaleinfo.getSalePrice() == llsaleinfo1.getSalePrice() && - llsaleinfo.getSaleType() == llsaleinfo1.getSaleType()); + CHECK(llsaleinfo.getSalePrice() == llsaleinfo1.getSalePrice()); + CHECK(llsaleinfo.getSaleType() == llsaleinfo1.getSaleType()); } - template<> template<> - void llsaleinfo_test_t::test<3>() + TEST_CASE("llsaleinfo: asLLSD and fromLLSD round-trip", "[llsaleinfo]") { S32 sale_price = 99000; LLSaleInfo saleinfo(LLSaleInfo::FS_ORIGINAL, sale_price); @@ -141,52 +127,48 @@ namespace LLSaleInfo saleinfo1; saleinfo1.fromLLSD( sd_result, has_perm_mask, perm_mask); - ensure_equals("asLLSD and fromLLSD failed", saleinfo.getSalePrice(), saleinfo1.getSalePrice()); - ensure_equals("asLLSD and fromLLSD failed", saleinfo.getSaleType(), saleinfo1.getSaleType()); + CHECK(saleinfo.getSalePrice() == saleinfo1.getSalePrice()); + CHECK(saleinfo.getSaleType() == saleinfo1.getSaleType()); } //static EForSale lookup(const char* name) fn test - template<> template<> - void llsaleinfo_test_t::test<4>() + TEST_CASE("llsaleinfo: lookup maps names to sale types and sale types to names", "[llsaleinfo]") { S32 sale_price = 233223; LLSaleInfo::EForSale ret_type = LLSaleInfo::lookup("orig"); - ensure_equals("lookup(const char* name) fn failed", ret_type, LLSaleInfo::FS_ORIGINAL); + CHECK(ret_type == LLSaleInfo::FS_ORIGINAL); LLSaleInfo saleinfo(LLSaleInfo::FS_COPY, sale_price); const char* result = LLSaleInfo::lookup(LLSaleInfo::FS_COPY); - ensure("char* lookup(EForSale type) fn failed", 0 == strcmp("copy", result)); + CHECK(std::string_view(result) == "copy"); } //void LLSaleInfo::accumulate(const LLSaleInfo& sale_info) fn test - template<> template<> - void llsaleinfo_test_t::test<5>() + TEST_CASE("llsaleinfo: accumulate adds the prices", "[llsaleinfo]") { S32 sale_price = 20; LLSaleInfo saleinfo(LLSaleInfo::FS_COPY, sale_price); LLSaleInfo saleinfo1(LLSaleInfo::FS_COPY, sale_price); saleinfo1.accumulate(saleinfo); - ensure_equals("LLSaleInfo::accumulate(const LLSaleInfo& sale_info) fn failed", saleinfo1.getSalePrice(), 40); + CHECK(saleinfo1.getSalePrice() == 40); } // test cases of bool operator==(const LLSaleInfo &rhs) fn // test case of bool operator!=(const LLSaleInfo &rhs) fn - template<> template<> - void llsaleinfo_test_t::test<6>() + TEST_CASE("llsaleinfo: operator== and operator!= tell sale infos apart by price", "[llsaleinfo]") { S32 sale_price = 55000; LLSaleInfo saleinfo(LLSaleInfo::FS_ORIGINAL, sale_price); LLSaleInfo saleinfoequal(LLSaleInfo::FS_ORIGINAL, sale_price); LLSaleInfo saleinfonotequal(LLSaleInfo::FS_ORIGINAL, sale_price*2); - ensure("operator == fn. failed", true == (saleinfo == saleinfoequal)); - ensure("operator != fn. failed", true == (saleinfo != saleinfonotequal)); + CHECK(saleinfo == saleinfoequal); + CHECK(saleinfo != saleinfonotequal); } - template<> template<> - void llsaleinfo_test_t::test<7>() + TEST_CASE("llsaleinfo: placeholder for the untested message packing", "[llsaleinfo]") { //TBD: void LLSaleInfo::packMessage(LLMessageSystem* msg) const diff --git a/indra/llinventory/tests/llsettingssky_test.cpp b/indra/llinventory/tests/llsettingssky_test.cpp index 9e64572853..a775f5a18b 100644 --- a/indra/llinventory/tests/llsettingssky_test.cpp +++ b/indra/llinventory/tests/llsettingssky_test.cpp @@ -28,7 +28,8 @@ #include "llsdutil.h" -#include "lltut.h" +#include +#include namespace { @@ -55,9 +56,10 @@ namespace return (F32)layer[key].asReal(); } - void ensure_close(const std::string& msg, F32 actual, F32 expected) + void require_close(const std::string& what, F32 actual, F32 expected) { - tut::ensure_approximately_equals_range(msg.c_str(), actual, expected, fabsf(expected) * 1.0e-5f + 1.0e-9f); + INFO(what); + REQUIRE_THAT(actual, Catch::Matchers::WithinAbs(expected, fabsf(expected) * 1.0e-5f + 1.0e-9f)); } LLSD layer(F32 width, F32 exp_term, F32 exp_scale, F32 linear, F32 constant, F32 aniso = 0.0f) @@ -66,52 +68,39 @@ namespace } } -namespace tut -{ namespace { - struct llsettingssky_data - { - }; - typedef test_group llsettingssky_group; - typedef llsettingssky_group::object llsettingssky_object; - tut::llsettingssky_group tut_llsettingssky("llsettingssky"); -} - // setMieAnisotropy changes the sky's g and the LLSD it saves. - template<> template<> - void llsettingssky_object::test<1>() + TEST_CASE("llsettingssky: setMieAnisotropy changes the sky's g and the LLSD it saves", "[llsettingssky]") { LLSettingsSky::ptr_t sky = make_sky(default_settings()); - ensure_close("default g", sky->getMieAnisotropy(), 0.8f); + require_close("default g", sky->getMieAnisotropy(), 0.8f); sky->setMieAnisotropy(0.6f); - ensure_close("g after the setter", sky->getMieAnisotropy(), 0.6f); - ensure_close("g in the saved LLSD", - term(sky->getSettings()[LLSettingsSky::SETTING_MIE_CONFIG][0], LLSettingsSky::SETTING_MIE_ANISOTROPY_FACTOR), 0.6f); + require_close("g after the setter", sky->getMieAnisotropy(), 0.6f); + require_close("g in the saved LLSD", + term(sky->getSettings()[LLSettingsSky::SETTING_MIE_CONFIG][0], LLSettingsSky::SETTING_MIE_ANISOTROPY_FACTOR), 0.6f); } // The default ozone profile is Bruneton's tent, 0 at 10 km, 1 at 25 km, 0 at 40 km, and it // survives validation, negative terms included. - template<> template<> - void llsettingssky_object::test<2>() + TEST_CASE("llsettingssky: the default ozone profile is Bruneton's tent and validates", "[llsettingssky]") { LLSettingsSky::ptr_t sky = make_sky(default_settings()); - ensure("the default sky validates", sky->validate()); + CHECK(sky->validate()); LLSD ozone = sky->getAbsorptionConfigs(); - ensure_equals("ozone layers", ozone.size(), size_t(2)); + REQUIRE(ozone.size() == size_t(2)); - ensure_close("lower width", term(ozone[0], LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH), 25000.0f); - ensure_close("lower linear", term(ozone[0], LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM), 1.0f / 15000.0f); - ensure_close("lower constant", term(ozone[0], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), -2.0f / 3.0f); - ensure_close("upper linear", term(ozone[1], LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM), -1.0f / 15000.0f); - ensure_close("upper constant", term(ozone[1], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 8.0f / 3.0f); + require_close("lower width", term(ozone[0], LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH), 25000.0f); + require_close("lower linear", term(ozone[0], LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM), 1.0f / 15000.0f); + require_close("lower constant", term(ozone[0], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), -2.0f / 3.0f); + require_close("upper linear", term(ozone[1], LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM), -1.0f / 15000.0f); + require_close("upper constant", term(ozone[1], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 8.0f / 3.0f); } // Blending mixes the profiles term by term, anisotropy included. - template<> template<> - void llsettingssky_object::test<3>() + TEST_CASE("llsettingssky: blending mixes the profiles term by term with anisotropy", "[llsettingssky]") { LLSD from = default_settings(); from[LLSettingsSky::SETTING_RAYLEIGH_CONFIG] = llsd::array(layer(0.0f, 1.0f, -1.0f / 8000.0f, 0.0f, 0.0f)); @@ -126,17 +115,16 @@ namespace sky->blend(end, 0.5); LLSD rayleigh = sky->getRayleighConfig(); - ensure_close("Rayleigh exp term", term(rayleigh, LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM), 1.5f); - ensure_close("Rayleigh exp scale", term(rayleigh, LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR), - 0.5f * (-1.0f / 8000.0f - 1.0f / 4000.0f)); - ensure_close("Rayleigh constant", term(rayleigh, LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.25f); - ensure_close("Mie g", sky->getMieAnisotropy(), 0.7f); + require_close("Rayleigh exp term", term(rayleigh, LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM), 1.5f); + require_close("Rayleigh exp scale", term(rayleigh, LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR), + 0.5f * (-1.0f / 8000.0f - 1.0f / 4000.0f)); + require_close("Rayleigh constant", term(rayleigh, LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.25f); + require_close("Mie g", sky->getMieAnisotropy(), 0.7f); } // A one-layer profile blended with a two-layer one is padded by repeating its layer, taking // the other profile's boundary, so at 0 it still describes the same atmosphere. - template<> template<> - void llsettingssky_object::test<4>() + TEST_CASE("llsettingssky: a one-layer profile blends with a two-layer one by repeating its layer", "[llsettingssky]") { LLSD from = default_settings(); from[LLSettingsSky::SETTING_ABSORPTION_CONFIG] = llsd::array(layer(0.0f, 0.0f, 0.0f, 0.0f, 0.2f)); @@ -150,36 +138,34 @@ namespace LLSettingsSky::ptr_t start = make_sky(from); start->blend(end, 0.0); LLSD at_start = start->getAbsorptionConfigs(); - ensure_equals("layers at 0", at_start.size(), size_t(2)); - ensure_close("boundary at 0", term(at_start[0], LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH), 25000.0f); - ensure_close("below the boundary at 0", term(at_start[0], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.2f); - ensure_close("above the boundary at 0", term(at_start[1], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.2f); + REQUIRE(at_start.size() == size_t(2)); + require_close("boundary at 0", term(at_start[0], LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH), 25000.0f); + require_close("below the boundary at 0", term(at_start[0], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.2f); + require_close("above the boundary at 0", term(at_start[1], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.2f); LLSettingsSky::ptr_t half = make_sky(from); half->blend(end, 0.5); LLSD at_half = half->getAbsorptionConfigs(); - ensure_close("boundary at 0.5", term(at_half[0], LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH), 25000.0f); - ensure_close("below the boundary at 0.5", term(at_half[0], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.3f); - ensure_close("above the boundary at 0.5", term(at_half[1], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.5f); + require_close("boundary at 0.5", term(at_half[0], LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH), 25000.0f); + require_close("below the boundary at 0.5", term(at_half[0], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.3f); + require_close("above the boundary at 0.5", term(at_half[1], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.5f); } // An unknown key inside a layer is stripped and the sky still validates; a sky that failed // would drop out of its day cycle. - template<> template<> - void llsettingssky_object::test<5>() + TEST_CASE("llsettingssky: an unknown key inside a layer is stripped and the sky still validates", "[llsettingssky]") { LLSD settings = default_settings(); settings[LLSettingsSky::SETTING_RAYLEIGH_CONFIG][0]["not_a_density_term"] = 1.0; LLSettingsSky::ptr_t sky = make_sky(settings); - ensure("the sky validates", sky->validate()); - ensure("the unknown key is stripped", !sky->getRayleighConfig().has("not_a_density_term")); + CHECK(sky->validate()); + CHECK_FALSE(sky->getRayleighConfig().has("not_a_density_term")); } // Skies saved by earlier Density tabs hold the absorption profile as [[layer], layer]. // Validation visits the layer after the nested array too, and the getters look through it. - template<> template<> - void llsettingssky_object::test<6>() + TEST_CASE("llsettingssky: an absorption profile nested by earlier Density tabs still reads", "[llsettingssky]") { LLSD settings = default_settings(); settings[LLSettingsSky::SETTING_ABSORPTION_CONFIG] = llsd::array(llsd::array(layer(25000.0f, 0.0f, 0.0f, 0.0f, 0.5f)), @@ -189,44 +175,41 @@ namespace sky->validate(); LLSD stored = sky->getAbsorptionConfigs(); - ensure_close("the layer after the nested array is clamped", term(stored[1], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 10.0f); + require_close("the layer after the nested array is clamped", term(stored[1], LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 10.0f); LLSD flat = LLSettingsSky::flattenDensityProfile(stored); - ensure_equals("flattened layers", flat.size(), size_t(2)); - ensure("flattened layers are maps", flat[0].isMap() && flat[1].isMap()); + REQUIRE(flat.size() == size_t(2)); + CHECK(flat[0].isMap()); + CHECK(flat[1].isMap()); LLSD first = sky->getAbsorptionConfig(); - ensure("the first layer is a map", first.isMap()); - ensure_close("the first layer's constant", term(first, LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.5f); + REQUIRE(first.isMap()); + require_close("the first layer's constant", term(first, LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM), 0.5f); } // Setting a profile marks the sky dirty, so its derived state is recomputed. - template<> template<> - void llsettingssky_object::test<7>() + TEST_CASE("llsettingssky: setting a profile marks the sky dirty", "[llsettingssky]") { LLSettingsSky::ptr_t sky = make_sky(default_settings()); sky->update(); - ensure("clean after update", !sky->isDirty()); + CHECK_FALSE(sky->isDirty()); sky->setRayleighConfigs(llsd::array(layer(0.0f, 1.0f, -1.0f / 4000.0f, 0.0f, 0.0f))); - ensure("dirty after setRayleighConfigs", sky->isDirty()); + CHECK(sky->isDirty()); } - // A profile with no layers has no first layer. - template<> template<> - void llsettingssky_object::test<8>() + TEST_CASE("llsettingssky: a profile with no layers has no first layer", "[llsettingssky]") { LLSD settings = default_settings(); settings[LLSettingsSky::SETTING_RAYLEIGH_CONFIG] = LLSD::emptyArray(); LLSettingsSky::ptr_t sky = make_sky(settings); - ensure("no first Rayleigh layer", sky->getRayleighConfig().isUndefined()); + CHECK(sky->getRayleighConfig().isUndefined()); } // Cloud cover fades in from a sky without a cloud texture toward the next sky's cover, and out toward none, // so neither end of the blend jumps. - template<> template<> - void llsettingssky_object::test<9>() + TEST_CASE("llsettingssky: cloud cover fades in from and out to a sky without a cloud texture", "[llsettingssky]") { const LLUUID cloud_texture("2bfd3884-7e27-69b9-ba3a-3e673f680004"); @@ -241,19 +224,23 @@ namespace LLSettingsBase::ptr_t to_cloudy = make_sky(cloudy); for (F64 t : { 0.25, 0.5, 1.0 }) { + CAPTURE(t); LLSettingsSky::ptr_t sky = make_sky(clear); sky->blend(to_cloudy, t); - ensure_close("fading in at " + std::to_string(t), sky->getCloudShadow(), 0.6f * (F32)t); - ensure("fading in takes the next sky's texture", sky->getCloudNoiseTextureId() == cloud_texture); + require_close("fading in", sky->getCloudShadow(), 0.6f * (F32)t); + UNSCOPED_INFO("fading in takes the next sky's texture"); + REQUIRE(sky->getCloudNoiseTextureId() == cloud_texture); } LLSettingsBase::ptr_t to_clear = make_sky(clear); for (F64 t : { 0.25, 0.5, 1.0 }) { + CAPTURE(t); LLSettingsSky::ptr_t sky = make_sky(cloudy); sky->blend(to_clear, t); - ensure_close("fading out at " + std::to_string(t), sky->getCloudShadow(), 0.6f * (1.f - (F32)t)); - ensure("fading out keeps its own texture", sky->getCloudNoiseTextureId() == cloud_texture); + require_close("fading out", sky->getCloudShadow(), 0.6f * (1.f - (F32)t)); + UNSCOPED_INFO("fading out keeps its own texture"); + REQUIRE(sky->getCloudNoiseTextureId() == cloud_texture); } LLSD cloudier = cloudy; @@ -261,13 +248,12 @@ namespace LLSettingsBase::ptr_t to_cloudier = make_sky(cloudier); LLSettingsSky::ptr_t sky = make_sky(cloudy); sky->blend(to_cloudier, 0.25); - ensure_close("between two textured skies", sky->getCloudShadow(), 0.7f); + require_close("between two textured skies", sky->getCloudShadow(), 0.7f); } // Flattening looks through nesting to any depth, as validation and the first-layer getters do, so the // anisotropy setter reaches a layer nested two arrays deep. - template<> template<> - void llsettingssky_object::test<10>() + TEST_CASE("llsettingssky: flattening and the anisotropy setter look through any depth of nesting", "[llsettingssky]") { LLSD settings = default_settings(); const LLSD deep = layer(0.0f, 1.0f, -1.0f / 1200.0f, 0.0f, 0.0f, 0.8f); @@ -275,13 +261,14 @@ namespace settings[LLSettingsSky::SETTING_MIE_CONFIG] = llsd::array(llsd::array(llsd::array(deep)), shallow); LLSD flat = LLSettingsSky::flattenDensityProfile(settings[LLSettingsSky::SETTING_MIE_CONFIG]); - ensure_equals("both layers flattened", flat.size(), size_t(2)); - ensure_close("the deep layer first", term(flat[0], LLSettingsSky::SETTING_MIE_ANISOTROPY_FACTOR), 0.8f); + REQUIRE(flat.size() == size_t(2)); + require_close("the deep layer first", term(flat[0], LLSettingsSky::SETTING_MIE_ANISOTROPY_FACTOR), 0.8f); LLSettingsSky::ptr_t sky = make_sky(settings); - ensure_close("g from the deep layer", sky->getMieAnisotropy(), 0.8f); + require_close("g from the deep layer", sky->getMieAnisotropy(), 0.8f); sky->setMieAnisotropy(0.6f); - ensure_close("g after the setter", sky->getMieAnisotropy(), 0.6f); - ensure_equals("the setter keeps both layers", sky->getMieConfigs().size(), size_t(2)); + require_close("g after the setter", sky->getMieAnisotropy(), 0.6f); + UNSCOPED_INFO("the setter keeps both layers"); + CHECK(sky->getMieConfigs().size() == size_t(2)); } } diff --git a/indra/llinventory/tests/lluserrelations_test.cpp b/indra/llinventory/tests/lluserrelations_test.cpp index 0b0706c9b5..5520fa62f8 100644 --- a/indra/llinventory/tests/lluserrelations_test.cpp +++ b/indra/llinventory/tests/lluserrelations_test.cpp @@ -26,128 +26,83 @@ * $/LicenseInfo$ */ -#include +#include #include "linden_common.h" #include "lluserrelations.h" -namespace tut -{ namespace { struct user_relationship { LLRelationship mRelationship; }; - typedef test_group user_relationship_t; - typedef user_relationship_t::object user_relationship_object_t; - tut::user_relationship_t tut_user_relationship("relationships"); -} - template<> template<> - void user_relationship_object_t::test<1>() + TEST_CASE_METHOD(user_relationship, "relationships: a default relationship grants nothing and is offline", "[relationships]") { // Test the default construction - ensure( - "No granted rights to", - !mRelationship.isRightGrantedTo( - LLRelationship::GRANT_ONLINE_STATUS)); - ensure( - "No granted rights from", - !mRelationship.isRightGrantedFrom( - LLRelationship::GRANT_ONLINE_STATUS)); - ensure("No online status",!mRelationship.isOnline()); + CHECK_FALSE(mRelationship.isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)); + CHECK_FALSE(mRelationship.isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS)); + CHECK_FALSE(mRelationship.isOnline()); } - template<> template<> - void user_relationship_object_t::test<2>() + TEST_CASE_METHOD(user_relationship, "relationships: grantRights grants rights to and from separately", "[relationships]") { // Test some granting mRelationship.grantRights( LLRelationship::GRANT_ONLINE_STATUS, LLRelationship::GRANT_MODIFY_OBJECTS); - ensure( - "Granted rights to has online", - mRelationship.isRightGrantedTo( - LLRelationship::GRANT_ONLINE_STATUS)); - ensure( - "Granted rights from does not have online", - !mRelationship.isRightGrantedFrom( - LLRelationship::GRANT_ONLINE_STATUS)); - ensure( - "Granted rights to does not have modify", - !mRelationship.isRightGrantedTo( - LLRelationship::GRANT_MODIFY_OBJECTS)); - ensure( - "Granted rights from has modify", - mRelationship.isRightGrantedFrom( - LLRelationship::GRANT_MODIFY_OBJECTS)); + CHECK(mRelationship.isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)); + CHECK_FALSE(mRelationship.isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS)); + CHECK_FALSE(mRelationship.isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)); + CHECK(mRelationship.isRightGrantedFrom(LLRelationship::GRANT_MODIFY_OBJECTS)); } - template<> template<> - void user_relationship_object_t::test<3>() + TEST_CASE_METHOD(user_relationship, "relationships: revokeRights removes only the rights it names", "[relationships]") { // Test revoking mRelationship.grantRights( LLRelationship::GRANT_ONLINE_STATUS | LLRelationship::GRANT_MAP_LOCATION, LLRelationship::GRANT_ONLINE_STATUS); - ensure( - "Granted rights to has online and map", - mRelationship.isRightGrantedTo( + CHECK(mRelationship.isRightGrantedTo( LLRelationship::GRANT_ONLINE_STATUS | LLRelationship::GRANT_MAP_LOCATION)); - ensure( - "Granted rights from has online", - mRelationship.isRightGrantedFrom( - LLRelationship::GRANT_ONLINE_STATUS)); + CHECK(mRelationship.isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS)); mRelationship.revokeRights( LLRelationship::GRANT_MAP_LOCATION, LLRelationship::GRANT_NONE); - ensure( - "Granted rights revoked map", - !mRelationship.isRightGrantedTo( + UNSCOPED_INFO("map revoked"); + CHECK_FALSE(mRelationship.isRightGrantedTo( LLRelationship::GRANT_ONLINE_STATUS | LLRelationship::GRANT_MAP_LOCATION)); - ensure( - "Granted rights revoked still has online", - mRelationship.isRightGrantedTo( - LLRelationship::GRANT_ONLINE_STATUS)); + UNSCOPED_INFO("online kept after revoking map"); + CHECK(mRelationship.isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)); mRelationship.grantRights( LLRelationship::GRANT_NONE, LLRelationship::GRANT_MODIFY_OBJECTS); - ensure( - "Granted rights from still has online", - mRelationship.isRightGrantedFrom( - LLRelationship::GRANT_ONLINE_STATUS)); - ensure( - "Granted rights from has full grant", - mRelationship.isRightGrantedFrom( + UNSCOPED_INFO("online kept after granting modify"); + CHECK(mRelationship.isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS)); + CHECK(mRelationship.isRightGrantedFrom( LLRelationship::GRANT_ONLINE_STATUS | LLRelationship::GRANT_MODIFY_OBJECTS)); mRelationship.revokeRights( LLRelationship::GRANT_NONE, LLRelationship::GRANT_MODIFY_OBJECTS); - ensure( - "Granted rights from still has online", - mRelationship.isRightGrantedFrom( - LLRelationship::GRANT_ONLINE_STATUS)); - ensure( - "Granted rights from no longer modify", - !mRelationship.isRightGrantedFrom( - LLRelationship::GRANT_MODIFY_OBJECTS)); + UNSCOPED_INFO("online kept after revoking modify"); + CHECK(mRelationship.isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS)); + CHECK_FALSE(mRelationship.isRightGrantedFrom(LLRelationship::GRANT_MODIFY_OBJECTS)); } - template<> template<> - void user_relationship_object_t::test<4>() + TEST_CASE_METHOD(user_relationship, "relationships: online sets the online status", "[relationships]") { - ensure("No online status", !mRelationship.isOnline()); + CHECK_FALSE(mRelationship.isOnline()); mRelationship.online(true); - ensure("Online status", mRelationship.isOnline()); + CHECK(mRelationship.isOnline()); mRelationship.online(false); - ensure("No online status", !mRelationship.isOnline()); + CHECK_FALSE(mRelationship.isOnline()); } /* diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index 109b537ef2..a5236ea781 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -57,10 +57,25 @@ target_precompile_headers(llprimitive REUSE_FROM llprecompiled) # add unit tests if(AL_BUILD_TESTS) - foreach(test llmediaentry llgltfmaterial llmaterialid llmaterial) - al_add_test(${test} PROJECT llprimitive LIBRARIES llprimitive) - endforeach() + # llgltfmaterial_test.cpp is the one file that defines tinygltf's and stb's + # implementations, which the library leaves to the viewer. + al_add_test_binary( + llprimitive_tests + PROJECT llprimitive + SOURCES + tests/llgltfmaterial_test.cpp + tests/llmaterial_test.cpp + tests/llmaterialid_test.cpp + tests/llmediaentry_test.cpp + LIBRARIES llprimitive + ) # This one stubs LLMaterialID and LLPrimTextureList, which the library # defines, so it compiles the one file it tests instead of linking it. - al_add_test(llprimitive UNIT PROJECT llprimitive LIBRARIES llmessage) + al_add_test_binary( + llprimitive_test + PROJECT llprimitive + SOURCES tests/llprimitive_test.cpp + UNIT llprimitive.cpp + LIBRARIES llmessage llmath llcommon + ) endif() diff --git a/indra/llprimitive/tests/llgltfmaterial_test.cpp b/indra/llprimitive/tests/llgltfmaterial_test.cpp index 479f3fc0cd..6d7c9f1a9b 100644 --- a/indra/llprimitive/tests/llgltfmaterial_test.cpp +++ b/indra/llprimitive/tests/llgltfmaterial_test.cpp @@ -24,7 +24,7 @@ */ #include "linden_common.h" -#include "lltut.h" +#include #include @@ -51,17 +51,8 @@ #include -namespace tut -{ namespace { - struct llgltfmaterial - { - }; - typedef test_group llgltfmaterial_t; - typedef llgltfmaterial_t::object llgltfmaterial_object_t; - tut::llgltfmaterial_t tut_llgltfmaterial("llgltfmaterial"); - // A positive 32-bit float with a long string representation constexpr F32 test_fraction = 1.09045365e-32f; // A larger positive 32-bit float for values that get zeroed if below a threshold @@ -118,26 +109,27 @@ namespace return material; } - void ensure_gltf_material_serialize(const std::string& ensure_suffix, const LLGLTFMaterial& material_in) + void require_json_round_trip(const std::string& what, const LLGLTFMaterial& material_in) { + INFO(what); const std::string json_in = material_in.asJSON(); LLGLTFMaterial material_out; std::string warn_msg; std::string error_msg; bool serialize_success = material_out.fromJSON(json_in, warn_msg, error_msg); - ensure_equals("LLGLTFMaterial serialization has no warnings: " + ensure_suffix, "", warn_msg); - ensure_equals("LLGLTFMaterial serialization has no errors: " + ensure_suffix, "", error_msg); - ensure("LLGLTFMaterial serializes successfully: " + ensure_suffix, serialize_success); - ensure("LLGLTFMaterial is preserved when deserialized: " + ensure_suffix, material_in == material_out); + REQUIRE(warn_msg == ""); + REQUIRE(error_msg == ""); + REQUIRE(serialize_success); + REQUIRE(material_in == material_out); const std::string json_out = material_out.asJSON(); - ensure_equals("LLGLTFMaterial is preserved when serialized: " + ensure_suffix, json_in, json_out); + REQUIRE(json_in == json_out); } - void ensure_gltf_material_trimmed(const std::string& material_json, const std::string& must_not_contain) + void require_trimmed(const std::string& material_json, const std::string& must_not_contain) { - ensure("LLGLTFMaterial serialization trims property '" + must_not_contain + "'", material_json.find(must_not_contain) == std::string::npos); + CAPTURE(must_not_contain); + REQUIRE(material_json.find(must_not_contain) == std::string::npos); } -} // Test that GLTF material fields have not changed since these tests were // written. The previous sizeof(LLGLTFMaterial) tripwire was removed when @@ -145,22 +137,19 @@ namespace // the hash snapshot/cache members (which aren't part of the hashed // payload), so it stopped being a useful regression check. test<12> // exercises the actual hash invariant directly. - template<> template<> - void llgltfmaterial_object_t::test<1>() + TEST_CASE("llgltfmaterial: there are four texture infos", "[llgltfmaterial]") { - ensure_equals("LLGLTFMaterial texture info count", (U32)LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT, 4); + CHECK((U32)LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT == 4); } // Test that occlusion and metallicRoughness are the same (They are different for asset validation. See lluploadmaterial.cpp) - template<> template<> - void llgltfmaterial_object_t::test<2>() + TEST_CASE("llgltfmaterial: occlusion and metallicRoughness share a texture info", "[llgltfmaterial]") { - ensure_equals("LLGLTFMaterial occlusion does not differ from metallic roughness", LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, LLGLTFMaterial::GLTF_TEXTURE_INFO_OCCLUSION); + CHECK(LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS == LLGLTFMaterial::GLTF_TEXTURE_INFO_OCCLUSION); } // Ensure double sided and alpha mode overrides serialize as expected - template<> template<> - void llgltfmaterial_object_t::test<3>() + TEST_CASE("llgltfmaterial: double sided and alpha mode overrides set their flag and serialize", "[llgltfmaterial]") { const bool doubleSideds[] { false, true }; const LLGLTFMaterial::AlphaMode alphaModes[] { LLGLTFMaterial::ALPHA_MODE_OPAQUE, LLGLTFMaterial::ALPHA_MODE_BLEND, LLGLTFMaterial::ALPHA_MODE_MASK }; @@ -170,11 +159,12 @@ namespace { for (bool forOverride : forOverrides) { + CAPTURE(doubleSided, forOverride); LLGLTFMaterial material; material.setDoubleSided(doubleSided, forOverride); const bool overrideBit = (doubleSided == false) && forOverride; - ensure_equals("LLGLTFMaterial: double sided = " + std::to_string(doubleSided) + " override bit when forOverride = " + std::to_string(forOverride), material.mOverrideDoubleSided, overrideBit); - ensure_gltf_material_serialize("double sided = " + std::to_string(doubleSided), material); + REQUIRE(material.mOverrideDoubleSided == overrideBit); + require_json_round_trip("double sided = " + std::to_string(doubleSided), material); } } @@ -182,18 +172,18 @@ namespace { for (bool forOverride : forOverrides) { + CAPTURE(alphaMode, forOverride); LLGLTFMaterial material; material.setAlphaMode(alphaMode, forOverride); const bool overrideBit = (alphaMode == LLGLTFMaterial::ALPHA_MODE_OPAQUE) && forOverride; - ensure_equals("LLGLTFMaterial: alpha mode = " + std::to_string(alphaMode) + " override bit when forOverride = " + std::to_string(forOverride), material.mOverrideAlphaMode, overrideBit); - ensure_gltf_material_serialize("alpha mode = " + std::to_string(alphaMode), material); + REQUIRE(material.mOverrideAlphaMode == overrideBit); + require_json_round_trip("alpha mode = " + std::to_string(alphaMode), material); } } } // Test that a GLTF material's transform components serialize as expected - template<> template<> - void llgltfmaterial_object_t::test<4>() + TEST_CASE("llgltfmaterial: a texture transform survives a JSON round trip", "[llgltfmaterial]") { LLGLTFMaterial material; LLGLTFMaterial::TextureTransform& transform = material.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]; @@ -202,83 +192,77 @@ namespace transform.mScale[VX] = 0.05f; transform.mScale[VY] = 100.f; transform.mRotation = 1.571f; - ensure_gltf_material_serialize("material with transform", material); + require_json_round_trip("material with transform", material); } // Test that a GLTF material avoids serializing a material unnecessarily - template<> template<> - void llgltfmaterial_object_t::test<5>() + TEST_CASE("llgltfmaterial: asJSON leaves out default properties and unset textures", "[llgltfmaterial]") { { const LLGLTFMaterial material; const std::string material_json = material.asJSON(); - ensure_gltf_material_trimmed(material_json, "pbrMetallicRoughness"); - ensure_gltf_material_trimmed(material_json, "normalTexture"); - ensure_gltf_material_trimmed(material_json, "emissiveTexture"); - ensure_gltf_material_trimmed(material_json, "occlusionTexture"); + require_trimmed(material_json, "pbrMetallicRoughness"); + require_trimmed(material_json, "normalTexture"); + require_trimmed(material_json, "emissiveTexture"); + require_trimmed(material_json, "occlusionTexture"); } { LLGLTFMaterial metallic_factor_material; metallic_factor_material.setMetallicFactor(0.5); const std::string metallic_factor_material_json = metallic_factor_material.asJSON(); - ensure_gltf_material_trimmed(metallic_factor_material_json, "baseColorTexture"); - ensure_gltf_material_trimmed(metallic_factor_material_json, "metallicRoughnessTexture"); + require_trimmed(metallic_factor_material_json, "baseColorTexture"); + require_trimmed(metallic_factor_material_json, "metallicRoughnessTexture"); } } // Test that a GLTF material preserves values on serialization - template<> template<> - void llgltfmaterial_object_t::test<6>() + TEST_CASE("llgltfmaterial: full and partial materials survive a JSON round trip", "[llgltfmaterial]") { { const LLGLTFMaterial full_material = create_test_material(); - ensure_gltf_material_serialize("full material", full_material); + require_json_round_trip("full material", full_material); } { LLGLTFMaterial texture_ids_only_material; apply_test_material_texture_ids(texture_ids_only_material); - ensure_gltf_material_serialize("material with texture IDs only", texture_ids_only_material); + require_json_round_trip("material with texture IDs only", texture_ids_only_material); } { LLGLTFMaterial texture_transforms_only_material; apply_test_material_texture_ids(texture_transforms_only_material); - ensure_gltf_material_serialize("material with texture transforms only", texture_transforms_only_material); + require_json_round_trip("material with texture transforms only", texture_transforms_only_material); } { LLGLTFMaterial factors_only_material; apply_test_material_factors(factors_only_material); - ensure_gltf_material_serialize("material with scaling/tint factors only", factors_only_material); + require_json_round_trip("material with scaling/tint factors only", factors_only_material); } } - // Test that sDefault is a no-op override - template<> template<> - void llgltfmaterial_object_t::test<7>() + TEST_CASE("llgltfmaterial: sDefault is a no-op override", "[llgltfmaterial]") { const LLGLTFMaterial material_asset = create_test_material(); LLGLTFMaterial render_material = material_asset; render_material.applyOverride(LLGLTFMaterial::sDefault); - ensure("LLGLTFMaterial: sDefault is a no-op override", material_asset == render_material); + CHECK(material_asset == render_material); } // Test application of transform overrides - template<> template<> - void llgltfmaterial_object_t::test<8>() + TEST_CASE("llgltfmaterial: applyOverride applies texture transform overrides", "[llgltfmaterial]") { LLGLTFMaterial override_material; apply_test_material_texture_transforms(override_material); LLGLTFMaterial render_material; render_material.applyOverride(override_material); - ensure("LLGLTFMaterial: transform overrides", render_material == override_material); + CHECK(render_material == override_material); } // Test application of flag-based overrides - template<> template<> - void llgltfmaterial_object_t::test<9>() + TEST_CASE("llgltfmaterial: applyOverride applies alpha mode and double sided overrides", "[llgltfmaterial]") { { LLGLTFMaterial override_material; @@ -289,7 +273,8 @@ namespace render_material.applyOverride(override_material); - ensure("LLGLTFMaterial: extra overrides with non-default values applied over default", render_material == override_material); + UNSCOPED_INFO("non-default overrides applied over defaults"); + CHECK(render_material == override_material); } { LLGLTFMaterial override_material; @@ -305,13 +290,13 @@ namespace override_material.mOverrideDoubleSided = false; override_material.mOverrideAlphaMode = false; - ensure("LLGLTFMaterial: extra overrides with default values applied over non-default", render_material == override_material); + UNSCOPED_INFO("default overrides applied over non-defaults"); + CHECK(render_material == override_material); } } // Test application of texture overrides - template<> template<> - void llgltfmaterial_object_t::test<10>() + TEST_CASE("llgltfmaterial: applyOverride replaces every texture ID with the override", "[llgltfmaterial]") { const U32 texture_count = 2; const LLUUID override_textures[texture_count] = { LLUUID::null, LLUUID::generateNewID() }; @@ -337,81 +322,84 @@ namespace for (LLGLTFMaterial::TextureInfo j = LLGLTFMaterial::TextureInfo(0); j < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; j = LLGLTFMaterial::TextureInfo(U32(j) + 1)) { const LLUUID& render_texture = render_material.mTextureId[j]; - ensure_equals("LLGLTFMaterial: Override texture ID " + override_texture.asString() + " replaces underlying texture ID " + asset_texture.asString(), render_texture, override_texture); + CAPTURE(override_texture, asset_texture); + REQUIRE(render_texture == override_texture); } } } // Test non-persistence of default value flags in overrides - template<> template<> - void llgltfmaterial_object_t::test<11>() + TEST_CASE("llgltfmaterial: setting a non-default value clears the override flag", "[llgltfmaterial]") { const S32 non_default_alpha_modes[] = { LLGLTFMaterial::ALPHA_MODE_BLEND, LLGLTFMaterial::ALPHA_MODE_MASK }; for (S32 non_default_alpha_mode : non_default_alpha_modes) { + CAPTURE(non_default_alpha_mode); LLGLTFMaterial material; // Set default alpha mode material.setAlphaMode(LLGLTFMaterial::ALPHA_MODE_OPAQUE, true); - ensure_equals("LLGLTFMaterial: alpha mode override flag set", material.mOverrideAlphaMode, true); + REQUIRE(material.mOverrideAlphaMode); // Set non-default alpha mode material.setAlphaMode(non_default_alpha_mode, true); - ensure_equals("LLGLTFMaterial: alpha mode override flag unset", material.mOverrideAlphaMode, false); + REQUIRE_FALSE(material.mOverrideAlphaMode); } { // Set default double sided LLGLTFMaterial material; material.setDoubleSided(false, true); - ensure_equals("LLGLTFMaterial: double sided override flag set", material.mOverrideDoubleSided, true); + CHECK(material.mOverrideDoubleSided); // Set non-default double sided material.setDoubleSided(true, true); - ensure_equals("LLGLTFMaterial: double sided override flag unset", material.mOverrideDoubleSided, false); + CHECK_FALSE(material.mOverrideDoubleSided); } } -namespace -{ + // the field must lie inside the material and the new value must differ template - void ensure_material_hash_pre(LLGLTFMaterial& material, T& material_field, const T new_value, const std::string& field_name) + void require_perturbable_field(LLGLTFMaterial& material, T& material_field, const T new_value) { - ensure("LLGLTFMaterial: Hash: Test field " + field_name + " is part of the test material object", ( - size_t(&material_field) >= size_t(&material) && - (size_t(&material_field) + sizeof(material_field)) <= (size_t(&material) + sizeof(material)) - )); - ensure("LLGLTFMaterial: Hash: " + field_name + " differs and will cause a perturbation worth hashing", material_field != new_value); + { + INFO("the field is part of the test material object"); + REQUIRE(size_t(&material_field) >= size_t(&material)); + REQUIRE((size_t(&material_field) + sizeof(material_field)) <= (size_t(&material) + sizeof(material))); + } + REQUIRE(material_field != new_value); } + // perturbing the field to the new value does not change the hash template - void ensure_material_hash_not_changed(LLGLTFMaterial& material, T& material_field, const T new_value, const std::string& field_name) + void require_hash_ignores(LLGLTFMaterial& material, T& material_field, const T new_value, const std::string& field_name) { - ensure_material_hash_pre(material, material_field, new_value, field_name); + CAPTURE(field_name); + require_perturbable_field(material, material_field, new_value); const LLGLTFMaterial old_material = material; material_field = new_value; // If this test fails, consult LLGLTFMaterial::getHash, and optionally consult http://www.catb.org/esr/structure-packing/ for guidance on optimal memory packing (effectiveness is platform-dependent) - ensure_equals(("LLGLTFMaterial: Hash: Perturbing " + field_name + " to new value does NOT change the hash").c_str(), material.getHash(), old_material.getHash()); + REQUIRE(material.getHash() == old_material.getHash()); } + // perturbing the field to the new value changes the hash template - void ensure_material_hash_changed(LLGLTFMaterial& material, T& material_field, const T new_value, const std::string& field_name) + void require_hash_covers(LLGLTFMaterial& material, T& material_field, const T new_value, const std::string& field_name) { - ensure_material_hash_pre(material, material_field, new_value, field_name); + CAPTURE(field_name); + require_perturbable_field(material, material_field, new_value); const LLGLTFMaterial old_material = material; material_field = new_value; // If this test fails, consult LLGLTFMaterial::getHash, and optionally consult http://www.catb.org/esr/structure-packing/ for guidance on optimal memory packing (effectiveness is platform-dependent) - ensure_not_equals(("LLGLTFMaterial: Hash: Perturbing " + field_name + " to new value changes the hash").c_str(), material.getHash(), old_material.getHash()); + REQUIRE(material.getHash() != old_material.getHash()); } -} -#define ENSURE_HASH_NOT_CHANGED(HASH_MAT, SOURCE_MAT, FIELD) ensure_material_hash_not_changed(HASH_MAT, HASH_MAT.FIELD, SOURCE_MAT.FIELD, #FIELD) -#define ENSURE_HASH_CHANGED(HASH_MAT, SOURCE_MAT, FIELD) ensure_material_hash_changed(HASH_MAT, HASH_MAT.FIELD, SOURCE_MAT.FIELD, #FIELD) +#define HASH_IGNORES(HASH_MAT, SOURCE_MAT, FIELD) require_hash_ignores(HASH_MAT, HASH_MAT.FIELD, SOURCE_MAT.FIELD, #FIELD) +#define HASH_COVERS(HASH_MAT, SOURCE_MAT, FIELD) require_hash_covers(HASH_MAT, HASH_MAT.FIELD, SOURCE_MAT.FIELD, #FIELD) // Test LLGLTFMaterial::getHash. The hash is now built from a canonical // packing of the meaningful members, so the previous structure-layout // fragility is gone. - template<> template<> - void llgltfmaterial_object_t::test<12>() + TEST_CASE("llgltfmaterial: getHash covers every field but the local texture tracking map", "[llgltfmaterial]") { // *NOTE: Due to direct manipulation of the fields of materials // throughout this test, the resulting modified materials may not be @@ -428,19 +416,19 @@ namespace LLGLTFMaterial hash_mat; - ENSURE_HASH_NOT_CHANGED(hash_mat, source_mat, mTrackingIdToLocalTexture); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mLocalTexDataDigest); - - ENSURE_HASH_CHANGED(hash_mat, source_mat, mTextureId); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mTextureTransform); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mBaseColor); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mEmissiveColor); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mMetallicFactor); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mRoughnessFactor); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mAlphaCutoff); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mAlphaMode); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mDoubleSided); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mOverrideDoubleSided); - ENSURE_HASH_CHANGED(hash_mat, source_mat, mOverrideAlphaMode); + HASH_IGNORES(hash_mat, source_mat, mTrackingIdToLocalTexture); + HASH_COVERS(hash_mat, source_mat, mLocalTexDataDigest); + + HASH_COVERS(hash_mat, source_mat, mTextureId); + HASH_COVERS(hash_mat, source_mat, mTextureTransform); + HASH_COVERS(hash_mat, source_mat, mBaseColor); + HASH_COVERS(hash_mat, source_mat, mEmissiveColor); + HASH_COVERS(hash_mat, source_mat, mMetallicFactor); + HASH_COVERS(hash_mat, source_mat, mRoughnessFactor); + HASH_COVERS(hash_mat, source_mat, mAlphaCutoff); + HASH_COVERS(hash_mat, source_mat, mAlphaMode); + HASH_COVERS(hash_mat, source_mat, mDoubleSided); + HASH_COVERS(hash_mat, source_mat, mOverrideDoubleSided); + HASH_COVERS(hash_mat, source_mat, mOverrideAlphaMode); } } diff --git a/indra/llprimitive/tests/llmaterial_test.cpp b/indra/llprimitive/tests/llmaterial_test.cpp index ce9168dea1..bf7b61fee3 100644 --- a/indra/llprimitive/tests/llmaterial_test.cpp +++ b/indra/llprimitive/tests/llmaterial_test.cpp @@ -19,20 +19,12 @@ */ #include "linden_common.h" -#include "lltut.h" +#include #include "../llmaterial.h" -namespace tut -{ namespace { - struct llmaterial_data { }; - typedef test_group llmaterial_t; - typedef llmaterial_t::object llmaterial_object_t; - tut::llmaterial_t tut_llmaterial("llmaterial"); -} - // Two distinct UUIDs used as before/after values for ID setters. static const LLUUID kIdA("11111111-2222-3333-4444-555555555555"); static const LLUUID kIdB("66666666-7777-8888-9999-aaaaaaaaaaaa"); @@ -42,36 +34,34 @@ namespace // hash, and (2) writing the first value back restores the original hash // (i.e. the hash is a pure function of state with no path dependence). template - static void ensure_setter_round_trip(const std::string& name, - Setter setter, - const Value& before_value, - const Value& after_value) + static void require_setter_round_trip(const std::string& name, + Setter setter, + const Value& before_value, + const Value& after_value) { + CAPTURE(name); LLMaterial m; (m.*setter)(before_value); const LLUUID before = m.getHash(); (m.*setter)(after_value); - ensure((name + ": new value changes hash").c_str(), - m.getHash() != before); + REQUIRE(m.getHash() != before); (m.*setter)(before_value); - ensure_equals((name + ": restoring value restores hash").c_str(), - m.getHash(), before); + REQUIRE(m.getHash() == before); } // Hash is deterministic for repeated reads with no mutation. The first // call populates the cache; subsequent calls hit it. - template<> template<> - void llmaterial_object_t::test<1>() + TEST_CASE("llmaterial: getHash is deterministic for repeated reads with no mutation", "[llmaterial]") { LLMaterial m; const LLUUID a = m.getHash(); const LLUUID b = m.getHash(); const LLUUID c = m.getHash(); - ensure_equals("repeated reads", a, b); - ensure_equals("repeated reads", b, c); - ensure("hash is not the null UUID", a.notNull()); + CHECK(a == b); + CHECK(b == c); + CHECK(a.notNull()); } // Every individual setter invalidates the cache AND changes the hash to @@ -79,57 +69,56 @@ namespace // when-set-back) proves the cache invalidation is wired up *and* that the // hash function is a pure function of the member state, not of mutation // history. - template<> template<> - void llmaterial_object_t::test<2>() + TEST_CASE("llmaterial: every setter changes the hash and setting back restores it", "[llmaterial]") { // ID setters. - ensure_setter_round_trip("setNormalID", + require_setter_round_trip("setNormalID", &LLMaterial::setNormalID, LLUUID::null, kIdA); - ensure_setter_round_trip("setSpecularID", + require_setter_round_trip("setSpecularID", &LLMaterial::setSpecularID, LLUUID::null, kIdB); // F32 setters (individual axes). - ensure_setter_round_trip("setNormalOffsetX", + require_setter_round_trip("setNormalOffsetX", &LLMaterial::setNormalOffsetX, 0.0f, 1.5f); - ensure_setter_round_trip("setNormalOffsetY", + require_setter_round_trip("setNormalOffsetY", &LLMaterial::setNormalOffsetY, 0.0f, 2.5f); - ensure_setter_round_trip("setNormalRepeatX", + require_setter_round_trip("setNormalRepeatX", &LLMaterial::setNormalRepeatX, 1.0f, 3.5f); - ensure_setter_round_trip("setNormalRepeatY", + require_setter_round_trip("setNormalRepeatY", &LLMaterial::setNormalRepeatY, 1.0f, 4.5f); - ensure_setter_round_trip("setNormalRotation", + require_setter_round_trip("setNormalRotation", &LLMaterial::setNormalRotation, 0.0f, 1.57f); - ensure_setter_round_trip("setSpecularOffsetX", + require_setter_round_trip("setSpecularOffsetX", &LLMaterial::setSpecularOffsetX, 0.0f, 5.5f); - ensure_setter_round_trip("setSpecularOffsetY", + require_setter_round_trip("setSpecularOffsetY", &LLMaterial::setSpecularOffsetY, 0.0f, 6.5f); - ensure_setter_round_trip("setSpecularRepeatX", + require_setter_round_trip("setSpecularRepeatX", &LLMaterial::setSpecularRepeatX, 1.0f, 7.5f); - ensure_setter_round_trip("setSpecularRepeatY", + require_setter_round_trip("setSpecularRepeatY", &LLMaterial::setSpecularRepeatY, 1.0f, 8.5f); - ensure_setter_round_trip("setSpecularRotation", + require_setter_round_trip("setSpecularRotation", &LLMaterial::setSpecularRotation, 0.0f, 2.71f); // LLColor4U setter. - ensure_setter_round_trip( + require_setter_round_trip( "setSpecularLightColor", &LLMaterial::setSpecularLightColor, LLColor4U(0, 0, 0, 0), LLColor4U(11, 22, 33, 44)); // U8 setters. - ensure_setter_round_trip( + require_setter_round_trip( "setSpecularLightExponent", &LLMaterial::setSpecularLightExponent, U8(0), U8(99)); - ensure_setter_round_trip( + require_setter_round_trip( "setEnvironmentIntensity", &LLMaterial::setEnvironmentIntensity, U8(0), U8(123)); - ensure_setter_round_trip( + require_setter_round_trip( "setDiffuseAlphaMode", &LLMaterial::setDiffuseAlphaMode, (U8)LLMaterial::DIFFUSE_ALPHA_MODE_BLEND, (U8)LLMaterial::DIFFUSE_ALPHA_MODE_MASK); - ensure_setter_round_trip( + require_setter_round_trip( "setAlphaMaskCutoff", &LLMaterial::setAlphaMaskCutoff, U8(0), U8(200)); } @@ -137,8 +126,7 @@ namespace // The paired-axis setters share the same underlying members as the X/Y // individual setters and must produce the identical hash for the same // logical state. This pins that the combined and split write paths agree. - template<> template<> - void llmaterial_object_t::test<3>() + TEST_CASE("llmaterial: paired-axis setters hash the same as the split X and Y setters", "[llmaterial]") { LLMaterial split; split.setNormalOffsetX(0.5f); @@ -156,57 +144,55 @@ namespace paired.setSpecularOffset(4.5f, 5.5f); paired.setSpecularRepeat(6.5f, 7.5f); - ensure_equals("paired vs. split writes agree", - split.getHash(), paired.getHash()); + CHECK(split.getHash() == paired.getHash()); } // Hash distinguishes which slot a value lives in. If two materials carry // the same scalar but in different members, the hashes must differ — // otherwise the packing order or hashing scheme has aliased two fields. - template<> template<> - void llmaterial_object_t::test<4>() + TEST_CASE("llmaterial: the hash distinguishes which slot a value lives in", "[llmaterial]") { // mNormalOffsetX vs mNormalOffsetY { LLMaterial x; x.setNormalOffsetX(1.0f); LLMaterial y; y.setNormalOffsetY(1.0f); - ensure("Normal offset X vs Y distinguishes", - x.getHash() != y.getHash()); + UNSCOPED_INFO("normal offset X vs Y"); + CHECK(x.getHash() != y.getHash()); } // mNormalRepeatX vs mNormalRepeatY { LLMaterial x; x.setNormalRepeatX(2.0f); LLMaterial y; y.setNormalRepeatY(2.0f); - ensure("Normal repeat X vs Y distinguishes", - x.getHash() != y.getHash()); + UNSCOPED_INFO("normal repeat X vs Y"); + CHECK(x.getHash() != y.getHash()); } // mSpecularOffsetX vs mSpecularOffsetY { LLMaterial x; x.setSpecularOffsetX(3.0f); LLMaterial y; y.setSpecularOffsetY(3.0f); - ensure("Specular offset X vs Y distinguishes", - x.getHash() != y.getHash()); + UNSCOPED_INFO("specular offset X vs Y"); + CHECK(x.getHash() != y.getHash()); } // mSpecularRepeatX vs mSpecularRepeatY { LLMaterial x; x.setSpecularRepeatX(4.0f); LLMaterial y; y.setSpecularRepeatY(4.0f); - ensure("Specular repeat X vs Y distinguishes", - x.getHash() != y.getHash()); + UNSCOPED_INFO("specular repeat X vs Y"); + CHECK(x.getHash() != y.getHash()); } // Same UUIDs swapped between mNormalID and mSpecularID. { LLMaterial ab; ab.setNormalID(kIdA); ab.setSpecularID(kIdB); LLMaterial ba; ba.setNormalID(kIdB); ba.setSpecularID(kIdA); - ensure("Normal/Specular ID swap distinguishes", - ab.getHash() != ba.getHash()); + UNSCOPED_INFO("normal and specular IDs swapped"); + CHECK(ab.getHash() != ba.getHash()); } // Normal vs specular rotation { LLMaterial n; n.setNormalRotation(1.0f); LLMaterial s; s.setSpecularRotation(1.0f); - ensure("Normal vs Specular rotation distinguishes", - n.getHash() != s.getHash()); + UNSCOPED_INFO("normal vs specular rotation"); + CHECK(n.getHash() != s.getHash()); } // Trailing U8 fields all share size/alignment — make sure the packer // doesn't accidentally read them as a single uniform word. @@ -215,19 +201,18 @@ namespace LLMaterial i; i.setEnvironmentIntensity(7); LLMaterial d; d.setDiffuseAlphaMode((U8)LLMaterial::DIFFUSE_ALPHA_MODE_MASK); LLMaterial c; c.setAlphaMaskCutoff(7); - ensure("exponent vs intensity distinguishes", - e.getHash() != i.getHash()); - ensure("intensity vs alpha mode distinguishes", - i.getHash() != d.getHash()); - ensure("alpha mode vs cutoff distinguishes", - d.getHash() != c.getHash()); + UNSCOPED_INFO("exponent vs intensity"); + CHECK(e.getHash() != i.getHash()); + UNSCOPED_INFO("intensity vs alpha mode"); + CHECK(i.getHash() != d.getHash()); + UNSCOPED_INFO("alpha mode vs cutoff"); + CHECK(d.getHash() != c.getHash()); } } // fromLLSD touches every member at once. Verify it invalidates the cache // and that hashes computed before vs. after differ. - template<> template<> - void llmaterial_object_t::test<5>() + TEST_CASE("llmaterial: fromLLSD invalidates the cache and round-trips the hash", "[llmaterial]") { LLMaterial m; const LLUUID before = m.getHash(); @@ -237,23 +222,20 @@ namespace LLMaterial back; back.setAlphaMaskCutoff(99); // make cache dirty + state altered back.fromLLSD(sd); - ensure_equals("fromLLSD round-trip yields baseline hash", - back.getHash(), before); + CHECK(back.getHash() == before); // Now perturb a field through LLSD and check the hash moves. LLMaterial perturbed; perturbed.fromLLSD(sd); - ensure_equals("post-fromLLSD matches baseline", - perturbed.getHash(), before); + CHECK(perturbed.getHash() == before); perturbed.setEnvironmentIntensity(42); - ensure("post-fromLLSD setter still invalidates", - perturbed.getHash() != before); + UNSCOPED_INFO("a setter after fromLLSD still invalidates"); + CHECK(perturbed.getHash() != before); } // Copy semantics: a copy carries the cached state, but mutations on the // copy invalidate only the copy's cache. - template<> template<> - void llmaterial_object_t::test<6>() + TEST_CASE("llmaterial: a copy carries the cached hash and its changes stay its own", "[llmaterial]") { LLMaterial src; src.setAlphaMaskCutoff(42); @@ -261,29 +243,24 @@ namespace // Copy after the source's cache has been populated. LLMaterial copy_warm(src); - ensure_equals("warm copy preserves hash", - copy_warm.getHash(), src_hash); + CHECK(copy_warm.getHash() == src_hash); // Copy from a source whose cache is dirty (never read). LLMaterial dirty_src; dirty_src.setEnvironmentIntensity(7); // marks dirty without reading LLMaterial copy_cold(dirty_src); - ensure_equals("cold copy still produces the right hash", - copy_cold.getHash(), dirty_src.getHash()); + CHECK(copy_cold.getHash() == dirty_src.getHash()); // Mutating the copy must not bleed back into the source. copy_warm.setEnvironmentIntensity(99); - ensure("mutating copy invalidates copy's hash", - copy_warm.getHash() != src_hash); - ensure_equals("source hash unchanged", - src.getHash(), src_hash); + CHECK(copy_warm.getHash() != src_hash); + CHECK(src.getHash() == src_hash); } // Redundantly writing the same value through a setter must still result // in the same hash on the next read (the cache may be marked dirty and // recomputed, but the recomputed value matches because state didn't move). - template<> template<> - void llmaterial_object_t::test<7>() + TEST_CASE("llmaterial: redundant setter writes keep the hash", "[llmaterial]") { LLMaterial m; m.setAlphaMaskCutoff(50); @@ -293,12 +270,11 @@ namespace m.setEnvironmentIntensity(0); // already the default m.setNormalRotation(0.0f); // already the default const LLUUID h2 = m.getHash(); - ensure_equals("redundant setter writes preserve hash", h1, h2); + CHECK(h1 == h2); } // operator== iff hashes match for materials of equal state. - template<> template<> - void llmaterial_object_t::test<8>() + TEST_CASE("llmaterial: operator== holds exactly when the hashes match", "[llmaterial]") { LLMaterial a, b; a.setNormalID(kIdA); @@ -311,13 +287,12 @@ namespace b.setSpecularLightColor(LLColor4U(10, 20, 30, 40)); b.setEnvironmentIntensity(50); - ensure("operator== for equal state", a == b); - ensure("hash equality follows operator==", a.getHash() == b.getHash()); + CHECK(a == b); + CHECK(a.getHash() == b.getHash()); // One-bit perturbation must break both equalities together. b.setEnvironmentIntensity(51); - ensure("operator!= after single-field change", a != b); - ensure("hash inequality after single-field change", - a.getHash() != b.getHash()); + CHECK(a != b); + CHECK(a.getHash() != b.getHash()); } } diff --git a/indra/llprimitive/tests/llmaterialid_test.cpp b/indra/llprimitive/tests/llmaterialid_test.cpp index 1060aaff41..fec11b22d8 100644 --- a/indra/llprimitive/tests/llmaterialid_test.cpp +++ b/indra/llprimitive/tests/llmaterialid_test.cpp @@ -18,91 +18,71 @@ */ #include "linden_common.h" -#include "lltut.h" +#include #include #include #include "../llmaterialid.h" -namespace tut -{ namespace { struct llmaterialid_data { }; - typedef test_group llmaterialid_t; - typedef llmaterialid_t::object llmaterialid_object_t; - tut::llmaterialid_t tut_llmaterialid("llmaterialid"); -} static const U8 kSampleBytes[MATERIAL_ID_SIZE] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; - // Default construction yields a zeroed, null-equal ID. - template<> template<> - void llmaterialid_object_t::test<1>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: default construction yields a zeroed null ID", "[llmaterialid]") { LLMaterialID id; - ensure("default-constructed isNull", id.isNull()); - ensure("default-constructed == LLMaterialID::null", id == LLMaterialID::null); + CHECK(id.isNull()); + CHECK(id == LLMaterialID::null); for (int i = 0; i < MATERIAL_ID_SIZE; ++i) { - ensure_equals("default byte zero", (int)id.get()[i], 0); + CAPTURE(i); + REQUIRE((int)id.get()[i] == 0); } } - // Raw-memory constructor copies the full 16 bytes. - template<> template<> - void llmaterialid_object_t::test<2>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: the raw-memory constructor copies the full 16 bytes", "[llmaterialid]") { LLMaterialID id(kSampleBytes); - ensure("bytes preserved", std::memcmp(id.get(), kSampleBytes, MATERIAL_ID_SIZE) == 0); - ensure("non-null sample is not null", !id.isNull()); + CHECK(std::memcmp(id.get(), kSampleBytes, MATERIAL_ID_SIZE) == 0); + CHECK_FALSE(id.isNull()); } - // LLUUID round-trip preserves the byte pattern in both directions. - template<> template<> - void llmaterialid_object_t::test<3>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: LLUUID round-trip preserves the byte pattern in both directions", "[llmaterialid]") { LLUUID uuid; std::memcpy(uuid.mData, kSampleBytes, UUID_BYTES); LLMaterialID id(uuid); - ensure("LLUUID -> LLMaterialID bytes preserved", - std::memcmp(id.get(), uuid.mData, MATERIAL_ID_SIZE) == 0); + CHECK(std::memcmp(id.get(), uuid.mData, MATERIAL_ID_SIZE) == 0); LLUUID back = id.asUUID(); - ensure_equals("asUUID round-trips", back, uuid); + CHECK(back == uuid); } - // LLSD::Binary constructor copies a well-formed 16-byte payload. - template<> template<> - void llmaterialid_object_t::test<4>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: LLSD::Binary constructor copies a well-formed 16-byte payload", "[llmaterialid]") { LLSD::Binary bin(kSampleBytes, kSampleBytes + MATERIAL_ID_SIZE); LLMaterialID id(bin); - ensure("LLSD::Binary bytes preserved", - std::memcmp(id.get(), bin.data(), MATERIAL_ID_SIZE) == 0); + CHECK(std::memcmp(id.get(), bin.data(), MATERIAL_ID_SIZE) == 0); } - // LLSD round-trip: asLLSD() produces a Binary that reconstructs the same ID. - template<> template<> - void llmaterialid_object_t::test<5>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: asLLSD produces a Binary that reconstructs the same ID", "[llmaterialid]") { LLMaterialID original(kSampleBytes); LLSD sd = original.asLLSD(); - ensure("asLLSD is binary", sd.isBinary()); - ensure_equals("asLLSD size is MATERIAL_ID_SIZE", - (int)sd.asBinary().size(), MATERIAL_ID_SIZE); + CHECK(sd.isBinary()); + REQUIRE((int)sd.asBinary().size() == MATERIAL_ID_SIZE); LLMaterialID round_trip(sd); - ensure("LLSD round-trip equals original", round_trip == original); + CHECK(round_trip == original); } - // Equality and inequality across copies and one-byte differences. - template<> template<> - void llmaterialid_object_t::test<6>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: equality and inequality across copies and a one-byte difference", "[llmaterialid]") { U8 a[MATERIAL_ID_SIZE]; U8 b[MATERIAL_ID_SIZE]; @@ -111,21 +91,18 @@ namespace b[MATERIAL_ID_SIZE - 1] ^= 0x01; LLMaterialID id_a(a), id_a_copy(a), id_b(b); - ensure("a == a_copy", id_a == id_a_copy); - ensure("!(a != a_copy)", !(id_a != id_a_copy)); - ensure("a != b (one bit differs)", id_a != id_b); - ensure("!(a == b)", !(id_a == id_b)); + CHECK(id_a == id_a_copy); + CHECK_FALSE(id_a != id_a_copy); + CHECK(id_a != id_b); + CHECK_FALSE(id_a == id_b); } - // Ordering is byte-lexicographic (not word-endian). - // // With the old reinterpret_cast comparison on a little-endian host, // {0x00, 0x01, 0,...} read as the word 0x00000100 (256) would compare // greater than {0x01, 0x00, 0,...} read as 0x00000001 (1) — reversing // the byte-lexicographic intuition. memcmp gets it right: the first // differing byte wins. - template<> template<> - void llmaterialid_object_t::test<7>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: ordering is byte-lexicographic (not word-endian)", "[llmaterialid]") { U8 low[MATERIAL_ID_SIZE] = {0}; U8 high[MATERIAL_ID_SIZE] = {0}; @@ -133,21 +110,20 @@ namespace high[0] = 0x01; LLMaterialID id_low(low), id_high(high); - ensure("byte-lex: id_low < id_high", id_low < id_high); - ensure("byte-lex: id_high > id_low", id_high > id_low); - ensure("byte-lex: id_low <= id_high", id_low <= id_high); - ensure("byte-lex: id_high >= id_low", id_high >= id_low); - ensure("byte-lex: !(id_low > id_high)", !(id_low > id_high)); - - ensure("reflexive: id <= id", id_low <= id_low); - ensure("reflexive: id >= id", id_low >= id_low); - ensure("irreflexive: !(id < id)", !(id_low < id_low)); - ensure("irreflexive: !(id > id)", !(id_low > id_low)); + CHECK(id_low < id_high); + CHECK(id_high > id_low); + CHECK(id_low <= id_high); + CHECK(id_high >= id_low); + CHECK_FALSE(id_low > id_high); + + CHECK(id_low <= id_low); + CHECK(id_low >= id_low); + CHECK_FALSE(id_low < id_low); + CHECK_FALSE(id_low > id_low); } // asString uses little-endian word formatting (preserved for log greppability). - template<> template<> - void llmaterialid_object_t::test<8>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: asString formats in little-endian words", "[llmaterialid]") { const U8 bytes[MATERIAL_ID_SIZE] = { 0xaa, 0xbb, 0xcc, 0xdd, @@ -156,51 +132,37 @@ namespace 0xff, 0xff, 0xff, 0xff }; LLMaterialID id(bytes); - ensure_equals("asString little-endian word format", - id.asString(), - std::string("ddccbbaa-44332211-01000000-ffffffff")); - - ensure_equals("null asString", - LLMaterialID::null.asString(), - std::string("00000000-00000000-00000000-00000000")); + CHECK(id.asString() == std::string("ddccbbaa-44332211-01000000-ffffffff")); + CHECK(LLMaterialID::null.asString() == std::string("00000000-00000000-00000000-00000000")); } - // set() then clear() round-trip. - template<> template<> - void llmaterialid_object_t::test<9>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: set() copies the bytes and clear() makes the ID null", "[llmaterialid]") { LLMaterialID id; id.set(kSampleBytes); - ensure("set copies bytes", - std::memcmp(id.get(), kSampleBytes, MATERIAL_ID_SIZE) == 0); - ensure("post-set is not null", !id.isNull()); + CHECK(std::memcmp(id.get(), kSampleBytes, MATERIAL_ID_SIZE) == 0); + CHECK_FALSE(id.isNull()); id.clear(); - ensure("clear yields null", id.isNull()); - ensure("clear == LLMaterialID::null", id == LLMaterialID::null); + CHECK(id.isNull()); + CHECK(id == LLMaterialID::null); } // Hash consistency: equal IDs hash the same and std::hash == boost hash_value. - template<> template<> - void llmaterialid_object_t::test<10>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: std::hash agrees for equal IDs and with hash_value", "[llmaterialid]") { LLMaterialID a(kSampleBytes), b(kSampleBytes); - 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("null hashes to 0", - std::hash{}(LLMaterialID::null), (size_t)0); + CHECK(std::hash{}(a) == std::hash{}(b)); + CHECK(std::hash{}(a) == hash_value(b)); + CHECK(std::hash{}(LLMaterialID::null) == (size_t)0); } - // Stream insertion delegates to asString. - template<> template<> - void llmaterialid_object_t::test<11>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: stream insertion delegates to asString", "[llmaterialid]") { LLMaterialID id(kSampleBytes); std::ostringstream os; os << id; - ensure_equals("operator<< matches asString", os.str(), id.asString()); + CHECK(os.str() == id.asString()); } // The remaining tests exercise defensive guards against malformed input. @@ -209,34 +171,30 @@ namespace #ifndef SHOW_ASSERT // parseFromBinary (via LLSD::Binary ctor) clears on wrong-size input // instead of overreading. - template<> template<> - void llmaterialid_object_t::test<12>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: parseFromBinary clears on wrong-size input instead of overreading", "[llmaterialid]") { LLSD::Binary too_short(MATERIAL_ID_SIZE - 1, 0xff); - ensure("too-short binary -> null", LLMaterialID(too_short).isNull()); + CHECK(LLMaterialID(too_short).isNull()); LLSD::Binary too_long(MATERIAL_ID_SIZE + 1, 0xff); - ensure("too-long binary -> null", LLMaterialID(too_long).isNull()); + CHECK(LLMaterialID(too_long).isNull()); LLSD::Binary empty; - ensure("empty binary -> null", LLMaterialID(empty).isNull()); + CHECK(LLMaterialID(empty).isNull()); } - // set(nullptr) does not invoke memcpy on a null source. - template<> template<> - void llmaterialid_object_t::test<13>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: set(nullptr) does not invoke memcpy on a null source", "[llmaterialid]") { LLMaterialID id(kSampleBytes); id.set(nullptr); - ensure("set(nullptr) clears", id.isNull()); + CHECK(id.isNull()); } // Non-binary LLSD (LLSD::asBinary() returns empty) clears instead of UB. - template<> template<> - void llmaterialid_object_t::test<14>() + TEST_CASE_METHOD(llmaterialid_data, "llmaterialid: a non-binary LLSD clears the ID instead of reading it", "[llmaterialid]") { LLSD not_binary("hello"); - ensure("non-binary LLSD -> null", LLMaterialID(not_binary).isNull()); + CHECK(LLMaterialID(not_binary).isNull()); } #endif } diff --git a/indra/llprimitive/tests/llmediaentry_test.cpp b/indra/llprimitive/tests/llmediaentry_test.cpp index cb4c17e061..dcfecacad6 100644 --- a/indra/llprimitive/tests/llmediaentry_test.cpp +++ b/indra/llprimitive/tests/llmediaentry_test.cpp @@ -25,7 +25,7 @@ */ #include "linden_common.h" -#include "lltut.h" +#include #include @@ -135,8 +135,6 @@ \n\ " -namespace tut -{ namespace { // this is fixture data that gets created before each test and destroyed @@ -158,33 +156,16 @@ namespace LLSD defaultMediaEntryLLSD; }; - typedef test_group factory; - typedef factory::object object; -} } namespace { - // this is for naming our tests to make pretty output - tut::factory tf("LLMediaEntry"); -} - -namespace tut -{ -namespace -{ - void ensure_llsd_equals(const std::string& msg, const LLSD& expected, const LLSD& actual) + void require_llsd_equals(const LLSD& expected, const LLSD& actual) { if (!llsd_equals(expected, actual)) { - std::string message = msg; - message += ": actual: "; - message += ll_pretty_print_sd(actual); - message += "\n expected: "; - message += ll_pretty_print_sd(expected); - message += "\n"; - ensure(message, false); + FAIL("actual: " << ll_pretty_print_sd(actual) << "\n expected: " << ll_pretty_print_sd(expected)); } } @@ -195,53 +176,37 @@ namespace entry.setWhiteList(tokens); } - void whitelist_test(int num, bool enable, const char *whitelist, const char *candidate_url, bool expected_pass) + void require_whitelist_check(int num, bool enable, const char *whitelist, const char *candidate_url, bool expected_pass) { - std::string message = "Whitelist test " + std::to_string(num); + CAPTURE(num, enable, whitelist, candidate_url); LLMediaEntry entry; entry.setWhiteListEnable(enable); set_whitelist(entry, whitelist); bool passed_whitelist = entry.checkCandidateUrl(candidate_url); - if (passed_whitelist != expected_pass) - { - message += " failed: expected "; - message += (expected_pass) ? "" : "NOT "; - message += "to match\nwhitelist = "; - message += whitelist; - message += "\ncandidate_url = "; - message += candidate_url; - } - ensure(message, expected_pass == passed_whitelist); + REQUIRE(expected_pass == passed_whitelist); } - void whitelist_test(int num, const char *whitelist, const char *candidate_url, bool expected_pass) + void require_whitelist_check(int num, const char *whitelist, const char *candidate_url, bool expected_pass) { - whitelist_test(num, true, whitelist, candidate_url, expected_pass); + require_whitelist_check(num, true, whitelist, candidate_url, expected_pass); } -} - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: a new entry has the default LLSD", "[LLMediaEntry]") { - set_test_name("Test LLMediaEntry Instantiation"); LLMediaEntry entry; - ensure_llsd_equals(get_test_name() + " failed", defaultMediaEntryLLSD, entry.asLLSD()); + require_llsd_equals(defaultMediaEntryLLSD, entry.asLLSD()); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: fromLLSD of an empty LLSD gives an empty entry", "[LLMediaEntry]") { - set_test_name("Test LLMediaEntry Instantiation from LLSD"); LLMediaEntry entry; LLSD sd; entry.fromLLSD(sd); - ensure_llsd_equals(get_test_name() + " failed", emptyMediaEntryLLSD, entry.asLLSD()); + require_llsd_equals(emptyMediaEntryLLSD, entry.asLLSD()); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: fromLLSD of a partial LLSD leaves the other keys empty", "[LLMediaEntry]") { - set_test_name("Test LLMediaEntry Partial Instantiation from LLSD"); LLMediaEntry entry; LLSD sd; sd[LLMediaEntry::CURRENT_URL_KEY] = "http://www.example.com"; @@ -249,13 +214,11 @@ namespace LLSD golden; std::istringstream p(PARTIAL_MEDIA_ENTRY("http://www.example.com")); LLSDSerialize::fromXML(golden,p); - ensure_llsd_equals(get_test_name() + " failed", golden, entry.asLLSD()); + require_llsd_equals(golden, entry.asLLSD()); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: asLLSD replaces what the LLSD already held", "[LLMediaEntry]") { - set_test_name("Test LLMediaEntry::asLLSD()"); LLMediaEntry entry; LLSD sd; // Put some cruft in the LLSD @@ -264,14 +227,12 @@ namespace whitelist.append("*.example.com"); sd[LLMediaEntry::WHITELIST_KEY] = whitelist; entry.asLLSD(sd); - ensure_llsd_equals(get_test_name() + " failed", defaultMediaEntryLLSD, sd); + require_llsd_equals(defaultMediaEntryLLSD, sd); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: fromLLSD of asLLSD output drops a whitelist the source lacks", "[LLMediaEntry]") { - set_test_name("Test LLMediaEntry::asLLSD() -> LLMediaEntry::fromLLSD()"); LLMediaEntry entry1, entry2; // Add a whitelist to entry2 std::vector whitelist; @@ -282,72 +243,56 @@ namespace entry1.asLLSD(sd); // "read" that LLSD into entry 2 entry2.fromLLSD(sd); - ensure_llsd_equals(get_test_name() + " failed", defaultMediaEntryLLSD, entry2.asLLSD()); + require_llsd_equals(defaultMediaEntryLLSD, entry2.asLLSD()); } -namespace -{ // limit tests const char *URL_OK = "http://www.example.com"; const char *URL_TOO_BIG = "http://www.example.com.qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"; -} - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: setCurrentURL accepts a short URL and rejects one over the limit", "[LLMediaEntry]") { - set_test_name("Test Limits on setting current URL"); LLMediaEntry entry; U32 status = entry.setCurrentURL(URL_OK); - ensure(get_test_name() + " ok failed", status == LSL_STATUS_OK); + CHECK(status == LSL_STATUS_OK); status = entry.setCurrentURL(URL_TOO_BIG); - ensure(get_test_name() + " ok failed", status == LSL_STATUS_BOUNDS_ERROR); + CHECK(status == LSL_STATUS_BOUNDS_ERROR); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: setHomeURL accepts a short URL and rejects one over the limit", "[LLMediaEntry]") { - set_test_name("Test Limits on setting home URL"); LLMediaEntry entry; U32 status = entry.setHomeURL(URL_OK); - ensure(get_test_name() + " ok failed", status == LSL_STATUS_OK); + CHECK(status == LSL_STATUS_OK); status = entry.setHomeURL(URL_TOO_BIG); - ensure(get_test_name() + " ok failed", status == LSL_STATUS_BOUNDS_ERROR); + CHECK(status == LSL_STATUS_BOUNDS_ERROR); } - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: setWhiteList accepts a valid list", "[LLMediaEntry]") { - set_test_name("Test Limits on setting whitelist"); - // Test a valid list LLMediaEntry entry; std::vector whitelist; whitelist.push_back(std::string(URL_OK)); S32 status = entry.setWhiteList(whitelist); - ensure(get_test_name() + " invalid result", status == LSL_STATUS_OK); - ensure(get_test_name() + " failed", whitelist == entry.getWhiteList()); + CHECK(status == LSL_STATUS_OK); + CHECK(whitelist == entry.getWhiteList()); } - template<> template<> - void object::test<9>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: setWhiteList rejects a list with an oversized URL and stays empty", "[LLMediaEntry]") { - set_test_name("Test Limits on setting whitelist too big"); - // Test an invalid list LLMediaEntry entry; std::vector whitelist, empty; whitelist.push_back(std::string(URL_OK)); whitelist.push_back(std::string(URL_TOO_BIG)); S32 status = entry.setWhiteList(whitelist); - ensure(get_test_name() + " invalid result", status == LSL_STATUS_BOUNDS_ERROR); - ensure(get_test_name() + " failed", empty == entry.getWhiteList()); + CHECK(status == LSL_STATUS_BOUNDS_ERROR); + CHECK(empty == entry.getWhiteList()); } - template<> template<> - void object::test<10>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: setWhiteList rejects too many entries and stays empty", "[LLMediaEntry]") { - set_test_name("Test Limits on setting whitelist too many"); - // Test an invalid list LLMediaEntry entry; std::vector whitelist, empty; @@ -355,33 +300,25 @@ namespace whitelist.push_back("Q"); } S32 status = entry.setWhiteList(whitelist); - ensure(get_test_name() + " invalid result", status == LSL_STATUS_BOUNDS_ERROR); - ensure(get_test_name() + " failed", empty == entry.getWhiteList()); + CHECK(status == LSL_STATUS_BOUNDS_ERROR); + CHECK(empty == entry.getWhiteList()); } - template<> template<> - void object::test<11>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: both setWhiteList overloads agree on a valid list", "[LLMediaEntry]") { - set_test_name("Test to make sure both setWhiteList() functions behave the same"); - // Test a valid list std::vector whitelist, empty; LLSD whitelist_llsd; whitelist.push_back(std::string(URL_OK)); whitelist_llsd.append(std::string(URL_OK)); LLMediaEntry entry1, entry2; - ensure(get_test_name() + " setWhiteList(s) don't match", - entry1.setWhiteList(whitelist) == LSL_STATUS_OK && - entry2.setWhiteList(whitelist_llsd)== LSL_STATUS_OK ); - ensure(get_test_name() + " failed", - entry1.getWhiteList() == entry2.getWhiteList()); + CHECK(entry1.setWhiteList(whitelist) == LSL_STATUS_OK); + CHECK(entry2.setWhiteList(whitelist_llsd) == LSL_STATUS_OK); + CHECK(entry1.getWhiteList() == entry2.getWhiteList()); } - template<> template<> - void object::test<12>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: both setWhiteList overloads reject an oversized URL", "[LLMediaEntry]") { - set_test_name("Test to make sure both setWhiteList() functions behave the same"); - // Test an invalid list std::vector whitelist, empty; LLSD whitelist_llsd; @@ -390,19 +327,14 @@ namespace whitelist_llsd.append(std::string(URL_OK)); whitelist_llsd.append(std::string(URL_TOO_BIG)); LLMediaEntry entry1, entry2; - ensure(get_test_name() + " setWhiteList(s) don't match", - entry1.setWhiteList(whitelist) == LSL_STATUS_BOUNDS_ERROR && - entry2.setWhiteList(whitelist_llsd) == LSL_STATUS_BOUNDS_ERROR); - ensure(get_test_name() + " failed", - empty == entry1.getWhiteList() && - empty == entry2.getWhiteList()); + CHECK(entry1.setWhiteList(whitelist) == LSL_STATUS_BOUNDS_ERROR); + CHECK(entry2.setWhiteList(whitelist_llsd) == LSL_STATUS_BOUNDS_ERROR); + CHECK(empty == entry1.getWhiteList()); + CHECK(empty == entry2.getWhiteList()); } - template<> template<> - void object::test<13>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: both setWhiteList overloads reject too many entries", "[LLMediaEntry]") { - set_test_name("Test to make sure both setWhiteList() functions behave the same"); - // Test an invalid list, too many std::vector whitelist, empty; LLSD whitelist_llsd; @@ -411,77 +343,74 @@ namespace whitelist_llsd.append("Q"); } LLMediaEntry entry1, entry2; - ensure(get_test_name() + " invalid result", - entry1.setWhiteList(whitelist) == LSL_STATUS_BOUNDS_ERROR && - entry2.setWhiteList(whitelist_llsd) == LSL_STATUS_BOUNDS_ERROR); - ensure(get_test_name() + " failed", - empty == entry1.getWhiteList() && - empty == entry2.getWhiteList()); + CHECK(entry1.setWhiteList(whitelist) == LSL_STATUS_BOUNDS_ERROR); + CHECK(entry2.setWhiteList(whitelist_llsd) == LSL_STATUS_BOUNDS_ERROR); + CHECK(empty == entry1.getWhiteList()); + CHECK(empty == entry2.getWhiteList()); } - template<> template<> - void object::test<14>() + TEST_CASE_METHOD(MediaEntry_test, "LLMediaEntry: checkCandidateUrl matches URLs against whitelist patterns", "[LLMediaEntry]") { // Whitelist check tests int n=0; // Check the "empty whitelist" case - whitelist_test(++n, "", "http://www.example.com", true); + require_whitelist_check(++n, "", "http://www.example.com", true); // Check the "missing scheme" case - whitelist_test(++n, "www.example.com", "http://www.example.com", true); + require_whitelist_check(++n, "www.example.com", "http://www.example.com", true); // Check the "exactly the same" case - whitelist_test(++n, "http://example.com", "http://example.com", true); + require_whitelist_check(++n, "http://example.com", "http://example.com", true); // Check the enable flag - whitelist_test(++n, false, "www.example.com", "http://www.secondlife.com", true); - whitelist_test(++n, true, "www.example.com", "http://www.secondlife.com", false); + require_whitelist_check(++n, false, "www.example.com", "http://www.secondlife.com", true); + require_whitelist_check(++n, true, "www.example.com", "http://www.secondlife.com", false); // Check permutations of trailing slash: - whitelist_test(++n, "http://www.example.com", "http://www.example.com/", true); - whitelist_test(++n, "http://www.example.com/", "http://www.example.com/", true); - whitelist_test(++n, "http://www.example.com/", "http://www.example.com", false); - whitelist_test(++n, "http://www.example.com", "http://www.example.com/foobar", true); - whitelist_test(++n, "http://www.example.com/", "http://www.example.com/foobar", false); + require_whitelist_check(++n, "http://www.example.com", "http://www.example.com/", true); + require_whitelist_check(++n, "http://www.example.com/", "http://www.example.com/", true); + require_whitelist_check(++n, "http://www.example.com/", "http://www.example.com", false); + require_whitelist_check(++n, "http://www.example.com", "http://www.example.com/foobar", true); + require_whitelist_check(++n, "http://www.example.com/", "http://www.example.com/foobar", false); // More cases... - whitelist_test(++n, "http://example.com", "http://example.com/wiki", true); - whitelist_test(++n, "www.example.com", "http://www.example.com/help", true); - whitelist_test(++n, "http://www.example.com", "http://wwwexample.com", false); - whitelist_test(++n, "http://www.example.com", "http://www.example.com/wiki", true); - whitelist_test(++n, "example.com", "http://wwwexample.com", false); - whitelist_test(++n, "http://www.example.com/", "http://www.amazon.com/wiki", false); - whitelist_test(++n, "www.example.com", "http://www.amazon.com", false); + require_whitelist_check(++n, "http://example.com", "http://example.com/wiki", true); + require_whitelist_check(++n, "www.example.com", "http://www.example.com/help", true); + require_whitelist_check(++n, "http://www.example.com", "http://wwwexample.com", false); + require_whitelist_check(++n, "http://www.example.com", "http://www.example.com/wiki", true); + require_whitelist_check(++n, "example.com", "http://wwwexample.com", false); + require_whitelist_check(++n, "http://www.example.com/", "http://www.amazon.com/wiki", false); + require_whitelist_check(++n, "www.example.com", "http://www.amazon.com", false); // regexp cases - whitelist_test(++n, "*.example.com", "http://www.example.com", true); - whitelist_test(++n, "*.example.com", "http://www.amazon.com", false); - whitelist_test(++n, "*.example.com", "http://www.example.com/foo/bar", true); - whitelist_test(++n, "*.example.com", "http:/example.com/foo/bar", false); - whitelist_test(++n, "*example.com", "http://example.com/foo/bar", true); - whitelist_test(++n, "*example.com", "http://my.virus.com/foo/bar?example.com", false); - whitelist_test(++n, "example.com", "http://my.virus.com/foo/bar?example.com", false); - whitelist_test(++n, "*example.com", "http://my.virus.com/foo/bar?*example.com", false); - whitelist_test(++n, "http://*example.com", "http://www.example.com", true); - whitelist_test(++n, "http://*.example.com", "http://www.example.com", true); - whitelist_test(++n, "http://*.e$?^.com", "http://www.e$?^.com", true); - whitelist_test(++n, "*.example.com/foo/bar", "http://www.example.com/", false); - whitelist_test(++n, "*.example.com/foo/bar", "http://example.com/foo/bar", false); - whitelist_test(++n, "http://*.example.com/foo/bar", "http://www.example.com", false); - whitelist_test(++n, "http://*.example.com", "https://www.example.com", false); - whitelist_test(++n, "http*://*.example.com", "rtsp://www.example.com", false); - whitelist_test(++n, "http*://*.example.com", "https://www.example.com", true); - whitelist_test(++n, "example.com", "http://www.example.com", false); - whitelist_test(++n, "www.example.com", "http://www.example.com:80", false); - whitelist_test(++n, "www.example.com", "http://www.example.com", true); - whitelist_test(++n, "www.example.com/", "http://www.example.com", false); - whitelist_test(++n, "www.example.com/foo/bar/*", "http://www.example.com/foo/bar/baz", true); + require_whitelist_check(++n, "*.example.com", "http://www.example.com", true); + require_whitelist_check(++n, "*.example.com", "http://www.amazon.com", false); + require_whitelist_check(++n, "*.example.com", "http://www.example.com/foo/bar", true); + require_whitelist_check(++n, "*.example.com", "http:/example.com/foo/bar", false); + require_whitelist_check(++n, "*example.com", "http://example.com/foo/bar", true); + require_whitelist_check(++n, "*example.com", "http://my.virus.com/foo/bar?example.com", false); + require_whitelist_check(++n, "example.com", "http://my.virus.com/foo/bar?example.com", false); + require_whitelist_check(++n, "*example.com", "http://my.virus.com/foo/bar?*example.com", false); + require_whitelist_check(++n, "http://*example.com", "http://www.example.com", true); + require_whitelist_check(++n, "http://*.example.com", "http://www.example.com", true); + require_whitelist_check(++n, "http://*.e$?^.com", "http://www.e$?^.com", true); + require_whitelist_check(++n, "*.example.com/foo/bar", "http://www.example.com/", false); + require_whitelist_check(++n, "*.example.com/foo/bar", "http://example.com/foo/bar", false); + require_whitelist_check(++n, "http://*.example.com/foo/bar", "http://www.example.com", false); + require_whitelist_check(++n, "http://*.example.com", "https://www.example.com", false); + require_whitelist_check(++n, "http*://*.example.com", "rtsp://www.example.com", false); + require_whitelist_check(++n, "http*://*.example.com", "https://www.example.com", true); + require_whitelist_check(++n, "example.com", "http://www.example.com", false); + require_whitelist_check(++n, "www.example.com", "http://www.example.com:80", false); + require_whitelist_check(++n, "www.example.com", "http://www.example.com", true); + require_whitelist_check(++n, "www.example.com/", "http://www.example.com", false); + require_whitelist_check(++n, "www.example.com/foo/bar/*", "http://www.example.com/foo/bar/baz", true); // Path only - whitelist_test(++n, "/foo/*/baz", "http://www.example.com/foo/bar/baz", true); - whitelist_test(++n, "/foo/*/baz", "http://www.example.com/foo/bar/", false); + require_whitelist_check(++n, "/foo/*/baz", "http://www.example.com/foo/bar/baz", true); + require_whitelist_check(++n, "/foo/*/baz", "http://www.example.com/foo/bar/", false); } } diff --git a/indra/llprimitive/tests/llprimitive_test.cpp b/indra/llprimitive/tests/llprimitive_test.cpp index d2008f43b0..71f91ba2a9 100644 --- a/indra/llprimitive/tests/llprimitive_test.cpp +++ b/indra/llprimitive/tests/llprimitive_test.cpp @@ -26,7 +26,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llprimitive.h" @@ -131,8 +131,6 @@ class PRIMITIVE_TEST_SETUP }; } -namespace tut -{ namespace { struct llprimitive @@ -140,138 +138,122 @@ namespace PRIMITIVE_TEST_SETUP setup_class; }; - typedef test_group llprimitive_t; - typedef llprimitive_t::object llprimitive_object_t; - tut::llprimitive_t tut_llprimitive("LLPrimitive"); -} - template<> template<> - void llprimitive_object_t::test<1>() + TEST_CASE_METHOD(llprimitive, "LLPrimitive: a default primitive constructs and destroys cleanly", "[LLPrimitive]") { - set_test_name("Test LLPrimitive Instantiation"); LLPrimitive test; } - template<> template<> - void llprimitive_object_t::test<2>() + TEST_CASE_METHOD(llprimitive, "LLPrimitive: getPCode starts at zero and returns what setPCode set", "[LLPrimitive]") { - set_test_name("Test LLPrimitive PCode setter and getter."); LLPrimitive test; - ensure_equals(test.getPCode(), 0); + CHECK(test.getPCode() == 0); LLPCode code = 1; test.setPCode(code); - ensure_equals(test.getPCode(), code); + CHECK(test.getPCode() == code); } - template<> template<> - void llprimitive_object_t::test<3>() + TEST_CASE_METHOD(llprimitive, "LLPrimitive: init_primitive sets the PCode", "[LLPrimitive]") { - set_test_name("Test llprimitive constructor and initer."); LLPCode code = 1; LLPrimitive primitive; primitive.init_primitive(code); - ensure_equals(primitive.getPCode(), code); + CHECK(primitive.getPCode() == code); } - template<> template<> - void llprimitive_object_t::test<4>() + TEST_CASE_METHOD(llprimitive, "LLPrimitive: createPrimitive makes a primitive with the PCode", "[LLPrimitive]") { - set_test_name("Test Static llprimitive constructor and initer."); LLPCode code = 1; LLPrimitive * primitive = LLPrimitive::createPrimitive(code); - ensure(primitive != NULL); - ensure_equals(primitive->getPCode(), code); + REQUIRE(primitive != NULL); + CHECK(primitive->getPCode() == code); } - template<> template<> - void llprimitive_object_t::test<5>() + TEST_CASE_METHOD(llprimitive, "LLPrimitive: setVolume of a unique volume replaces it only when params change", "[LLPrimitive]") { - set_test_name("Test setVolume creation of new unique volume."); LLPrimitive primitive; LLVolumeParams params; // Make sure volume starts off null - ensure(primitive.getVolume() == NULL); + CHECK(primitive.getVolume() == NULL); // Make sure we have no texture entries before setting the volume - ensure_equals(primitive.getNumTEs(), 0); + CHECK(primitive.getNumTEs() == 0); // Test that GEOMETRY has not been flagged as changed. - ensure(!primitive.isChanged(LLXform::GEOMETRY)); + CHECK_FALSE(primitive.isChanged(LLXform::GEOMETRY)); // Make sure setVolume returns true - ensure(primitive.setVolume(params, 0, true) == true); + CHECK(primitive.setVolume(params, 0, true)); LLVolume* new_volume = primitive.getVolume(); // make sure new volume was actually created - ensure(new_volume != NULL); + REQUIRE(new_volume != NULL); // Make sure that now that we've set the volume we have texture entries - ensure_not_equals(primitive.getNumTEs(), 0); + CHECK(primitive.getNumTEs() != 0); // Make sure that the number of texture entries equals the number of faces in the volume (should be 6) - ensure_equals(new_volume->getNumFaces(), 6); - ensure_equals(primitive.getNumTEs(), new_volume->getNumFaces()); + CHECK(new_volume->getNumFaces() == 6); + CHECK(primitive.getNumTEs() == new_volume->getNumFaces()); // Test that GEOMETRY has been flagged as changed. - ensure(primitive.isChanged(LLXform::GEOMETRY)); + CHECK(primitive.isChanged(LLXform::GEOMETRY)); // Run it twice to make sure it doesn't create a different one if params are the same - ensure(primitive.setVolume(params, 0, true) == false); - ensure(new_volume == primitive.getVolume()); + CHECK_FALSE(primitive.setVolume(params, 0, true)); + CHECK(new_volume == primitive.getVolume()); // Change the param definition and try setting it again. params.setRevolutions(4); - ensure(primitive.setVolume(params, 0, true) == true); + CHECK(primitive.setVolume(params, 0, true)); // Ensure that we now have a different volume - ensure(new_volume != primitive.getVolume()); + CHECK(new_volume != primitive.getVolume()); } - template<> template<> - void llprimitive_object_t::test<6>() + TEST_CASE_METHOD(llprimitive, "LLPrimitive: setVolume of a shared volume replaces it only when params change", "[LLPrimitive]") { - set_test_name("Test setVolume creation of new NOT-unique volume."); LLPrimitive primitive; LLVolumeParams params; // Make sure volume starts off null - ensure(primitive.getVolume() == NULL); + CHECK(primitive.getVolume() == NULL); // Make sure we have no texture entries before setting the volume - ensure_equals(primitive.getNumTEs(), 0); + CHECK(primitive.getNumTEs() == 0); // Test that GEOMETRY has not been flagged as changed. - ensure(!primitive.isChanged(LLXform::GEOMETRY)); + CHECK_FALSE(primitive.isChanged(LLXform::GEOMETRY)); // Make sure setVolume returns true - ensure(primitive.setVolume(params, 0, false) == true); + CHECK(primitive.setVolume(params, 0, false)); LLVolume* new_volume = primitive.getVolume(); // make sure new volume was actually created - ensure(new_volume != NULL); + REQUIRE(new_volume != NULL); // Make sure that now that we've set the volume we have texture entries - ensure_not_equals(primitive.getNumTEs(), 0); + CHECK(primitive.getNumTEs() != 0); // Make sure that the number of texture entries equals the number of faces in the volume (should be 6) - ensure_equals(new_volume->getNumFaces(), 6); - ensure_equals(primitive.getNumTEs(), new_volume->getNumFaces()); + CHECK(new_volume->getNumFaces() == 6); + CHECK(primitive.getNumTEs() == new_volume->getNumFaces()); // Test that GEOMETRY has been flagged as changed. - ensure(primitive.isChanged(LLXform::GEOMETRY)); + CHECK(primitive.isChanged(LLXform::GEOMETRY)); // Run it twice to make sure it doesn't create a different one if params are the same - ensure(primitive.setVolume(params, 0, false) == false); - ensure(new_volume == primitive.getVolume()); + CHECK_FALSE(primitive.setVolume(params, 0, false)); + CHECK(new_volume == primitive.getVolume()); // Change the param definition and try setting it again. params.setRevolutions(4); - ensure(primitive.setVolume(params, 0, false) == true); + CHECK(primitive.setVolume(params, 0, false)); // Ensure that we now have a different volume - ensure(new_volume != primitive.getVolume()); + CHECK(new_volume != primitive.getVolume()); } } diff --git a/indra/llxml/CMakeLists.txt b/indra/llxml/CMakeLists.txt index b1ca7f927f..7c65bbab89 100644 --- a/indra/llxml/CMakeLists.txt +++ b/indra/llxml/CMakeLists.txt @@ -25,7 +25,17 @@ target_precompile_headers(llxml REUSE_FROM llprecompiled) # tests if(AL_BUILD_TESTS) - al_add_test(llxmlnode PROJECT llxml LIBRARIES llxml llmath llcommon) - al_add_test(llcontrol PROJECT llxml LIBRARIES llxml llmath llcommon) - al_add_test(alxmllayermerge PROJECT llxml LIBRARIES llxml llmath llcommon) + # The state guard's checks on what llxml keeps for the process, for every + # test binary that links it. + add_library(llxml_test_checks OBJECT tests/llxml_test_checks.cpp) + target_link_libraries(llxml_test_checks PRIVATE al::flags al_test_support llxml) + set_target_properties(llxml_test_checks PROPERTIES FOLDER "Tests/llxml") + + al_add_test_binary( + llxml_tests + PROJECT llxml + SOURCES tests/alxmllayermerge_test.cpp tests/llcontrol_test.cpp tests/llxmlnode_test.cpp + LIBRARIES llxml llmath llcommon + SUPPORT llxml_test_checks + ) endif() diff --git a/indra/llxml/tests/alxmllayermerge_test.cpp b/indra/llxml/tests/alxmllayermerge_test.cpp index 16eb13f899..05865cbfbe 100644 --- a/indra/llxml/tests/alxmllayermerge_test.cpp +++ b/indra/llxml/tests/alxmllayermerge_test.cpp @@ -30,13 +30,11 @@ #include "llfile.h" #include "lluuid.h" -#include "../test/lltut.h" +#include #include #include -namespace tut -{ namespace { // Every event, as one line: the kind, the layer, and the element or @@ -237,60 +235,70 @@ namespace } }; - typedef test_group alxmllayermerge_test; - typedef alxmllayermerge_test::object alxmllayermerge_object; - tut::alxmllayermerge_test alxmllayermerge_testgroup("alxmllayermerge"); -} // One event per decision: repeated names in order, text overwritten // where the overlay has text and kept where it has none, a value= // against body text applied as the text, an attribute the base lacks // dropped, and two children that match nothing. - template<> template<> - void alxmllayermerge_object::test<1>() + TEST_CASE_METHOD(alxmllayermerge_data, "alxmllayermerge: merge applies an overlay and reports one event per decision", "[alxmllayermerge]") { LLXMLNodePtr base = parse(BASE); LLXMLNodePtr overlay = parse(OVERLAY); - ensure("fixture parses", base.notNull() && overlay.notNull()); + { + INFO("fixture parses"); + REQUIRE(base.notNull()); + REQUIRE(overlay.notNull()); + } RecordingObserver rec; ALXmlLayerMerge::merge(base, overlay, 1, &rec); - ensure_equals("title", attr(base, "", "title"), std::string("Titel")); - ensure_equals("the base's width is untouched", attr(base, "", "width"), std::string("100")); - ensure_equals("first of a repeated name", attr(base, "p/b", "label"), std::string("Erste")); - ensure_equals("second of a repeated name", attr(base, "p/b#1", "label"), std::string("Zweite")); - ensure_equals("text", attr(base, "p/t", "text()"), std::string("Text")); - ensure_equals("an item by value", attr(base, "p/c/v2", "label"), std::string("Zwei")); - ensure_equals("the other item is untouched", attr(base, "p/c/v1", "label"), std::string("One")); - ensure_equals("value= against body text becomes the text", attr(base, "p/u", "text()"), std::string("Wert")); - ensure_equals("and no value attribute appears", attr(base, "p/u", "value"), std::string("")); - ensure_equals("an overlay without text leaves the base's text", attr(base, "p/w", "text()"), std::string("Keep me too")); - ensure_equals("while its attribute applies", attr(base, "p/w", "font"), std::string("Serif")); - - ensure("the extra attribute was reported dropped", rec.has("dropped 1 extra=x")); - ensure_equals("one attribute dropped", rec.dropped, 1); - ensure("the value= was reported applied as text", rec.has("value-as-text 1 text[u]")); - ensure_equals("once", rec.valueAsText, 1); - ensure("the kept text was reported", rec.has("kept 1 text[w]")); - ensure_equals("one text kept", rec.kept, 1); - ensure("the missing child was reported", rec.has("nosibling 1 text[missing]")); - ensure("the unnamed child was reported", rec.has("unnamed 1 text[]")); - ensure_equals("two children unmatched", rec.unmatched, 2); + CHECK(attr(base, "", "title") == std::string("Titel")); + UNSCOPED_INFO("the base's width is untouched"); + CHECK(attr(base, "", "width") == std::string("100")); + UNSCOPED_INFO("first of a repeated name"); + CHECK(attr(base, "p/b", "label") == std::string("Erste")); + UNSCOPED_INFO("second of a repeated name"); + CHECK(attr(base, "p/b#1", "label") == std::string("Zweite")); + CHECK(attr(base, "p/t", "text()") == std::string("Text")); + UNSCOPED_INFO("an item by value"); + CHECK(attr(base, "p/c/v2", "label") == std::string("Zwei")); + UNSCOPED_INFO("the other item is untouched"); + CHECK(attr(base, "p/c/v1", "label") == std::string("One")); + UNSCOPED_INFO("value= against body text becomes the text"); + CHECK(attr(base, "p/u", "text()") == std::string("Wert")); + UNSCOPED_INFO("and no value attribute appears"); + CHECK(attr(base, "p/u", "value") == std::string("")); + UNSCOPED_INFO("an overlay without text leaves the base's text"); + CHECK(attr(base, "p/w", "text()") == std::string("Keep me too")); + UNSCOPED_INFO("while its attribute applies"); + CHECK(attr(base, "p/w", "font") == std::string("Serif")); + + CHECK(rec.has("dropped 1 extra=x")); + CHECK(rec.dropped == 1); + CHECK(rec.has("value-as-text 1 text[u]")); + CHECK(rec.valueAsText == 1); + CHECK(rec.has("kept 1 text[w]")); + CHECK(rec.kept == 1); + CHECK(rec.has("nosibling 1 text[missing]")); + CHECK(rec.has("unnamed 1 text[]")); + CHECK(rec.unmatched == 2); // p, b, b, t, u, w, c, and the item: eight matches. - ensure_equals("matches", rec.matched, 8); - ensure("the second button matched the second base button", rec.has("match 1 button[b]")); - ensure_equals("texts applied: t", rec.text, 1); + CHECK(rec.matched == 8); + UNSCOPED_INFO("the second button matched the second base button"); + CHECK(rec.has("match 1 button[b]")); + UNSCOPED_INFO("texts applied: t"); + CHECK(rec.text == 1); // title; label on b, b; font on w; value and label on the item. // Names are keys, and are not written. - ensure_equals("attributes applied", rec.applied, 6); - ensure("the name is never written", !rec.has("attr 1 name=f")); + CHECK(rec.applied == 6); + UNSCOPED_INFO("the name is never written"); + CHECK_FALSE(rec.has("attr 1 name=f")); } // With no observer the tree comes out the same: a rule that changes // changes in one place. - template<> template<> - void alxmllayermerge_object::test<2>() + TEST_CASE_METHOD(alxmllayermerge_data, "alxmllayermerge: merge gives the same tree with or without an observer", "[alxmllayermerge]") { LLXMLNodePtr observed_base = parse(BASE); LLXMLNodePtr observed_overlay = parse(OVERLAY); @@ -301,24 +309,9 @@ namespace LLXMLNodePtr silent_overlay = parse(OVERLAY); ALXmlLayerMerge::merge(silent_base, silent_overlay); - ensure_equals("the same tree with and without an observer", serialize(silent_base), serialize(observed_base)); - ensure("and the observer saw something", !rec.events.empty()); - } - - // A repeated name matches in document order whatever lies between: - // the second overlay child of a name takes the second base child of - // it, never the first again. - template<> template<> - void alxmllayermerge_object::test<4>() - { - LLXMLNodePtr base = parse( - "