From 0d0cac1e3aa4ef49d8729d1779bccfccbf3c3253 Mon Sep 17 00:00:00 2001 From: Rye Date: Thu, 8 Oct 2026 22:56:02 -0400 Subject: [PATCH 01/55] Catch2 comes from vcpkg when the tests are built A tests manifest feature asks for catch2, with its thread-safe assertions, since tests assert from workers and coroutines; AL_BUILD_TESTS turns the feature on, and ll::catch2 imports it. It is 3.16.0 from the builtin registry for now, until alchemy-registry carries 3.16.1, which fixes MSVC dropping its optimisation in files that include the benchmark headers. Co-Authored-By: Claude Opus 5.5 --- indra/CMakeLists.txt | 4 ++++ indra/cmake/Dependencies.cmake | 1 + indra/vcpkg.json | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index e623c8080c..2ae2cef7ae 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -291,6 +291,10 @@ endif() # vcpkg feature flags MUST be set before the first project() call below # ########################################################################## +if(AL_BUILD_TESTS) + list(APPEND VCPKG_MANIFEST_FEATURES "tests") +endif() + if(AL_USE_KDU) list(APPEND VCPKG_MANIFEST_FEATURES "kdu") endif() diff --git a/indra/cmake/Dependencies.cmake b/indra/cmake/Dependencies.cmake index 18a9f6ce52..d1636859a9 100644 --- a/indra/cmake/Dependencies.cmake +++ b/indra/cmake/Dependencies.cmake @@ -108,6 +108,7 @@ al_import(ll::tut INCLUDE ${INDRA_SOURCE_DIR}/externals/tut/) al_import(ll::nvapi PACKAGE unofficial-nvapi CONFIG TARGETS unofficial::nvapi::nvapi WHEN WINDOWS) al_import(ll::fontconfig PACKAGE Fontconfig TARGETS Fontconfig::Fontconfig WHEN LINUX) al_import(ll::faudio PACKAGE FAudio CONFIG TARGETS FAudio::FAudio DEFINES LL_FAUDIO=1 WHEN AL_USE_FAUDIO) +al_import(ll::catch2 PACKAGE Catch2 CONFIG TARGETS Catch2::Catch2 WHEN AL_BUILD_TESTS) al_import(ll::openal PACKAGE OpenAL CONFIG TARGETS OpenAL::OpenAL DEFINES LL_OPENAL=1 WHEN AL_USE_OPENAL) al_import(ll::openxr PACKAGE OpenXR CONFIG TARGETS OpenXR::headers OpenXR::openxr_loader WHEN AL_USE_OPENXR) al_import(ll::nsspellchecker LINK "-framework AppKit" "-framework Foundation" WHEN AL_USE_NSSPELLCHECKER) diff --git a/indra/vcpkg.json b/indra/vcpkg.json index 2b48f5dee4..c1c492cf93 100644 --- a/indra/vcpkg.json +++ b/indra/vcpkg.json @@ -268,6 +268,17 @@ } ] }, + "tests": { + "description": "Build the unit tests, on Catch2", + "dependencies": [ + { + "name": "catch2", + "features": [ + "thread-safe-assertions" + ] + } + ] + }, "tracy": { "description": "Build with Tracy profiler support", "dependencies": [ From 6c943650ff461f52d14dd7512e1c0db9d068a6a7 Mon Sep 17 00:00:00 2001 From: Rye Date: Thu, 8 Oct 2026 22:56:16 -0400 Subject: [PATCH 02/55] The Catch2 harness: a main, LL_ERRS as an exception, LOGFAIL, a state guard, an ordered teardown and LL matchers al_test_support, an OBJECT library each Catch2 test binary links directly: in a static archive, the listeners' registrations, which nothing references, would be dropped. - The main reads Catch2's command line and ours (--isa-level, --al-log; long names only, since Catch2 owns every short one), defaults to declared order as TUT ran, sets nothing up for a listing, then does what the TUT runner did: APR, LLError at DEBUG, LOGTEST and LOGFAIL, the LLTrace master recorder. It maps Catch2's "all skipped" to CTest's 125. - An LL_ERRS throws ll_test::FatalError, as it threw tut::failure, so a test that expects one can catch it. - LOGFAIL keeps a test case's log and prints it only if the case failed. LLError writes to stderr with fprintf, which Catch2's capture never sees. - The state guard snapshots process state before the run (the working directory, the environment, the FP control state, the C locale, the APR pool, the master recorder, the LLApp, LLError's fatal function) and compares after every test case, naming the case that changed it. A leak then fails where it happens, not in whichever later test trips over it, and the run fails, since a listener cannot fail a case. - Teardown runs what process-wide fixtures register, newest first, before APR goes, instead of leaving them to static destruction. - WithinBits and LLSDEquals are ensure_approximately_equals and the LLSD ensure_equals as matchers, the latter saying where two values differ. Co-Authored-By: Claude Opus 5.5 --- indra/test/CMakeLists.txt | 31 ++++ indra/test/altestfatal.cpp | 50 ++++++ indra/test/altestfatal.h | 52 ++++++ indra/test/altestlogreplay.cpp | 107 +++++++++++++ indra/test/altestlogreplay.h | 62 +++++++ indra/test/altestmain.cpp | 170 ++++++++++++++++++++ indra/test/altestmatchers.cpp | 149 +++++++++++++++++ indra/test/altestmatchers.h | 101 ++++++++++++ indra/test/alteststateguard.cpp | 275 ++++++++++++++++++++++++++++++++ indra/test/alteststateguard.h | 67 ++++++++ indra/test/altestteardown.cpp | 72 +++++++++ indra/test/altestteardown.h | 50 ++++++ 12 files changed, 1186 insertions(+) create mode 100644 indra/test/altestfatal.cpp create mode 100644 indra/test/altestfatal.h create mode 100644 indra/test/altestlogreplay.cpp create mode 100644 indra/test/altestlogreplay.h create mode 100644 indra/test/altestmain.cpp create mode 100644 indra/test/altestmatchers.cpp create mode 100644 indra/test/altestmatchers.h create mode 100644 indra/test/alteststateguard.cpp create mode 100644 indra/test/alteststateguard.h create mode 100644 indra/test/altestteardown.cpp create mode 100644 indra/test/altestteardown.h diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 6ed15048d8..404479a8ce 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -24,3 +24,34 @@ target_sources( target_link_libraries(lltut_runner_lib PRIVATE al::flags PUBLIC llcommon ll::tut) set_target_properties(lltut_runner_lib PROPERTIES FOLDER "Tests") + +# The Catch2 harness: the main, what an LL_ERRS does, LOGFAIL's replay, the +# state guard, the teardown and the matchers. An OBJECT library, which each +# test binary links directly: in a static archive the listeners' +# registrations, which nothing references, would be dropped by the linker. +add_library(al_test_support OBJECT) + +target_sources( + al_test_support + PRIVATE + altestfatal.cpp + altestlogreplay.cpp + altestmain.cpp + altestmatchers.cpp + alteststateguard.cpp + altestteardown.cpp +) + +target_sources( + al_test_support + PUBLIC + FILE_SET HEADERS + FILES altestfatal.h altestlogreplay.h altestmatchers.h alteststateguard.h altestteardown.h +) + +# The matchers compare as llmath's is_approx_equal_fraction() does, a header +# function, so the tests need llmath's headers but not its library. +target_include_directories(al_test_support PUBLIC ${INDRA_SOURCE_DIR}/llmath) +target_link_libraries(al_test_support PRIVATE al::flags PUBLIC llcommon ll::catch2) + +set_target_properties(al_test_support PROPERTIES FOLDER "Tests") diff --git a/indra/test/altestfatal.cpp b/indra/test/altestfatal.cpp new file mode 100644 index 0000000000..a3a477a334 --- /dev/null +++ b/indra/test/altestfatal.cpp @@ -0,0 +1,50 @@ +/** + * @file altestfatal.cpp + * @brief What an LL_ERRS does in a Catch2 test. + * + * $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 "altestfatal.h" + +#include "llerrorcontrol.h" + +namespace ll_test +{ + FatalError::FatalError(const std::string& message) + : std::runtime_error("LL_ERRS: " + message) + { + } + + namespace + { + void throwFatalError(const std::string& message) + { + throw FatalError(message); + } + } + + void installFatalFunction() + { + LLError::setFatalFunction(throwFatalError); + } +} diff --git a/indra/test/altestfatal.h b/indra/test/altestfatal.h new file mode 100644 index 0000000000..f078540602 --- /dev/null +++ b/indra/test/altestfatal.h @@ -0,0 +1,52 @@ +/** + * @file altestfatal.h + * @brief What an LL_ERRS does in a Catch2 test: it throws, so the test case fails, + * or catches it if it expects one. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AL_ALTESTFATAL_H +#define AL_ALTESTFATAL_H + +#include +#include + +namespace ll_test +{ + // Thrown by an LL_ERRS, or a failed llassert, in a test process. Uncaught, + // its test case fails with the message; a test that means to provoke one + // catches it. The TUT runner threw tut::failure the same way, and code + // under test that catches and rethrows exceptions -- a coroutine's + // top level, for one -- carries it to the test as it carried that. + class FatalError : public std::runtime_error + { + public: + explicit FatalError(const std::string& message); + }; + + // Makes every LL_ERRS from here on throw FatalError, on whichever thread + // it happens. Off the main thread nothing catches it, so the process + // ends there, and Catch2's signal handler names the test case that was + // running. + void installFatalFunction(); +} + +#endif // AL_ALTESTFATAL_H diff --git a/indra/test/altestlogreplay.cpp b/indra/test/altestlogreplay.cpp new file mode 100644 index 0000000000..6d2ab43869 --- /dev/null +++ b/indra/test/altestlogreplay.cpp @@ -0,0 +1,107 @@ +/** + * @file altestlogreplay.cpp + * @brief LOGFAIL: a failed test case's log, replayed. + * + * $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 "altestlogreplay.h" + +#include +#include +#include + +#include +#include + +namespace ll_test +{ + namespace + { + // Made by install(), and kept for the life of the process: LLError + // holds it as a recorder until then. + std::shared_ptr sReplay; + + class LogReplayListener : public Catch::EventListenerBase + { + public: + using Catch::EventListenerBase::EventListenerBase; + + // Partial: a test case with sections runs once per leaf section, + // and each run fails or passes on its own. + void testCasePartialStarting(Catch::TestCaseInfo const&, uint64_t) override + { + LogReplay::clear(); + } + + void testCasePartialEnded(Catch::TestCaseStats const& stats, uint64_t) override + { + if (stats.totals.assertions.failed > 0) + { + LogReplay::replay(std::cerr, stats.testInfo->name); + } + } + }; + } + + void LogReplay::install(LLError::ELevel level) + { + if (!sReplay) + { + sReplay = std::make_shared(); + LLError::addRecorder(sReplay); + } + LLError::setDefaultLevel(level); + } + + void LogReplay::clear() + { + if (sReplay) + { + std::lock_guard lock(sReplay->mMutex); + sReplay->mLines.clear(); + } + } + + void LogReplay::replay(std::ostream& out, const std::string& test_case) + { + if (!sReplay) + { + return; + } + std::lock_guard lock(sReplay->mMutex); + out << "---- log of \"" << test_case << "\" ----\n"; + for (const std::string& line : sReplay->mLines) + { + out << line << '\n'; + } + out << "---- end of log ----" << std::endl; + } + + void LogReplay::recordMessage(LLError::ELevel, const std::string& message) + { + std::lock_guard lock(mMutex); + mLines.push_back(message); + } +} + +CATCH_REGISTER_LISTENER(ll_test::LogReplayListener) diff --git a/indra/test/altestlogreplay.h b/indra/test/altestlogreplay.h new file mode 100644 index 0000000000..5a2e6ef7d8 --- /dev/null +++ b/indra/test/altestlogreplay.h @@ -0,0 +1,62 @@ +/** + * @file altestlogreplay.h + * @brief LOGFAIL: what LL code logged while a test case ran, kept, and printed only + * if the case failed. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AL_ALTESTLOGREPLAY_H +#define AL_ALTESTLOGREPLAY_H + +#include "llerrorcontrol.h" + +#include +#include +#include +#include + +namespace ll_test +{ + // LLError's stderr recorder writes with fprintf, which Catch2's own output + // capture, a swap of std::cout's and std::cerr's buffers, never sees. So + // the log is kept by a recorder of its own: cleared as each test case + // starts, printed to stderr if it failed, and dropped if it passed. + class LogReplay : public LLError::Recorder + { + public: + // From the main, once, when LOGFAIL names a level: everything at or + // above it is kept, and LLError's default level becomes it. + static void install(LLError::ELevel level); + + // The listener's, around each test case. Both do nothing when LOGFAIL + // was not set. + static void clear(); + static void replay(std::ostream& out, const std::string& test_case); + + void recordMessage(LLError::ELevel level, const std::string& message) override; + + private: + std::mutex mMutex; + std::vector mLines; + }; +} + +#endif // AL_ALTESTLOGREPLAY_H diff --git a/indra/test/altestmain.cpp b/indra/test/altestmain.cpp new file mode 100644 index 0000000000..b0895081e1 --- /dev/null +++ b/indra/test/altestmain.cpp @@ -0,0 +1,170 @@ +/** + * @file altestmain.cpp + * @brief The main of every Catch2 test binary: it reads Catch2's command line and + * ours, sets up what LL code expects of a process, runs the tests, and + * ends the binary's process-wide fixtures in order. + * + * $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 "altestfatal.h" +#include "altestlogreplay.h" +#include "alteststateguard.h" +#include "altestteardown.h" + +#include "llapp.h" +#include "llapr.h" +#include "llerrorcontrol.h" +#include "llprocessor.h" +#include "llstring.h" +#include "lltracethreadrecorder.h" + +#include + +#include +#include + +namespace +{ + // Whether the host can run a binary compiled for an x86-64 level. A level + // of 0 is the baseline, which anything the viewer builds for can run. + bool hostRunsIsaLevel(int level) + { + if (level <= 0) + { + return true; + } + LLProcessorInfo cpu; + switch (level) + { + case 2: return cpu.hasSSE42(); + case 3: return cpu.hasAVX2(); + case 4: return cpu.hasAVX512F(); + default: return false; + } + } + + // LOGTEST logs everything at its level to stderr as it happens. Otherwise + // LL code logs at DEBUG to nowhere but the --al-log file and, with LOGFAIL, + // to a buffer replayed for a test case that fails. Either way, LLError is + // set up as LL code expects to find it. + void setUpLogging(const char* executable, bool log_to_file) + { + const char* logtest = std::getenv("LOGTEST"); + if (logtest && *logtest) + { + LLError::initForApplication(".", ".", true /* log to stderr */); + LLError::setDefaultLevel(LLError::decodeLevel(logtest)); + } + else + { + LLError::initForApplication(".", ".", false /* do not log to stderr */); + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + const char* logfail = std::getenv("LOGFAIL"); + if (logfail && *logfail) + { + ll_test::LogReplay::install(LLError::decodeLevel(logfail)); + } + } + if (log_to_file) + { + // One file to a process: CTest runs a binary's test files side by + // side, each in a process of its own. + LLError::logToFile(llformat("%s.%d.log", executable, LLApp::getPid())); + } + } +} + +int main(int argc, char* argv[]) +{ + // Tracy first, so it allocates before anything else does + // (https://github.com/wolfpld/tracy/issues/196). + LL_PROFILER_FRAME_END; + LL_PROFILER_SET_THREAD_NAME("App"); + + Catch::Session session; + + // Long names only: Catch2 owns every short one. + int isa_level = 0; + bool log_to_file = false; + using namespace Catch::Clara; + session.cli(session.cli() + | Opt(isa_level, "level")["--isa-level"]( + "exit 125, which CTest reads as skipped, unless the host runs this x86-64 " + "level: 2 for SSE4.2, 3 for AVX2, 4 for AVX-512") + | Opt(log_to_file)["--al-log"]( + "also log everything, at DEBUG, to ..log")); + + // Declared order, as TUT ran a group's tests, until no test leans on the + // ones before it. --order on the command line still wins. + session.configData().runOrder = Catch::TestRunOrder::Declared; + + if (const int result = session.applyCommandLine(argc, argv); result != 0) + { + return result; + } + + // A listing, help or the like runs no test, and needs nothing set up: CTest + // and tools list a binary that may not be able to run its tests here. + const Catch::ConfigData& config = session.configData(); + if (config.showHelp || config.libIdentify || config.listTests || config.listTags + || config.listReporters || config.listListeners) + { + return session.run(); + } + + if (!hostRunsIsaLevel(isa_level)) + { + std::cout << "Skipped: this host does not run x86-64 level " << isa_level << std::endl; + return 125; + } + + ll_init_apr(); + setUpLogging(argv[0], log_to_file); + ll_test::installFatalFunction(); + + // LLTrace needs a master recorder before any thread records. Never freed, + // as the TUT runner never freed it: static destructors that record would + // find it gone. + LLTrace::set_master_thread_recorder(new LLTrace::ThreadRecorder()); + + ll_test::StateGuard::addCommonChecks(); + + int result = session.run(); + + ll_test::Teardown::runAll(); + ll_cleanup_apr(); + + // Every test file is a CTest entry of its own, all with 125 as their skip + // code; Catch2 says "all skipped" with 4. + if (result == Catch::AllTestsSkippedExitCode) + { + result = 125; + } + if (result == 0 && ll_test::StateGuard::changes() > 0) + { + std::cerr << "The state guard found " << ll_test::StateGuard::changes() + << " change(s) a test case left behind; see above." << std::endl; + result = 1; + } + return result; +} diff --git a/indra/test/altestmatchers.cpp b/indra/test/altestmatchers.cpp new file mode 100644 index 0000000000..012f80efa6 --- /dev/null +++ b/indra/test/altestmatchers.cpp @@ -0,0 +1,149 @@ +/** + * @file altestmatchers.cpp + * @brief Catch2 matchers for what LL's TUT helpers compared. + * + * $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 "altestmatchers.h" + +#include "llsdserialize.h" + +namespace ll_test +{ + namespace + { + // Where actual and expected first differ: a path from the top, as in + // ["key"][3], and what differs there. Empty "what" means equal. + struct Difference + { + std::string path; + std::string what; + }; + + Difference firstDifference(const LLSD& actual, const LLSD& expected, const std::string& path) + { + if (actual.type() != expected.type()) + { + return { path, "type" }; + } + bool same = true; + switch (actual.type()) + { + case LLSD::TypeUndefined: + break; + case LLSD::TypeBoolean: + same = actual.asBoolean() == expected.asBoolean(); + break; + case LLSD::TypeInteger: + same = actual.asInteger() == expected.asInteger(); + break; + case LLSD::TypeReal: + same = actual.asReal() == expected.asReal(); + break; + case LLSD::TypeString: + same = actual.asString() == expected.asString(); + break; + case LLSD::TypeUUID: + same = actual.asUUID() == expected.asUUID(); + break; + case LLSD::TypeDate: + same = actual.asDate() == expected.asDate(); + break; + case LLSD::TypeURI: + same = actual.asURI().asString() == expected.asURI().asString(); + break; + case LLSD::TypeBinary: + same = actual.asBinary() == expected.asBinary(); + break; + case LLSD::TypeMap: + { + if (actual.size() != expected.size()) + { + return { path, "map size" }; + } + LLSD::map_const_iterator expected_entry = expected.beginMap(); + for (LLSD::map_const_iterator entry = actual.beginMap(); entry != actual.endMap(); + ++entry, ++expected_entry) + { + if (entry->first != expected_entry->first) + { + return { path, "map keys" }; + } + Difference inner = firstDifference(entry->second, expected_entry->second, + path + "[\"" + entry->first + "\"]"); + if (!inner.what.empty()) + { + return inner; + } + } + break; + } + case LLSD::TypeArray: + { + if (actual.size() != expected.size()) + { + return { path, "array size" }; + } + for (LLSD::Integer index = 0; index < actual.size(); ++index) + { + Difference inner = firstDifference(actual[index], expected[index], + path + "[" + std::to_string(index) + "]"); + if (!inner.what.empty()) + { + return inner; + } + } + break; + } + default: + return { path, "type " + std::to_string(actual.type()) + ", which is not an LLSD type" }; + } + return { path, same ? std::string() : std::string("value") }; + } + } + + LLSDEqualsMatcher::LLSDEqualsMatcher(LLSD expected) + : mExpected(std::move(expected)) + { + } + + bool LLSDEqualsMatcher::match(const LLSD& actual) const + { + const Difference difference = firstDifference(actual, mExpected, ""); + mDifference = difference.what.empty() + ? std::string() + : (difference.path.empty() ? std::string("the top") : difference.path) + ": " + difference.what; + return mDifference.empty(); + } + + std::string LLSDEqualsMatcher::describe() const + { + std::ostringstream out; + out << "equals " << LLSDNotationStreamer(mExpected); + if (!mDifference.empty()) + { + out << " (they differ at " << mDifference << ")"; + } + return out.str(); + } +} diff --git a/indra/test/altestmatchers.h b/indra/test/altestmatchers.h new file mode 100644 index 0000000000..4e15d1c642 --- /dev/null +++ b/indra/test/altestmatchers.h @@ -0,0 +1,101 @@ +/** + * @file altestmatchers.h + * @brief Catch2 matchers for what LL's TUT helpers compared: floats to a number of + * bits, and LLSD deeply. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AL_ALTESTMATCHERS_H +#define AL_ALTESTMATCHERS_H + +#include "llmath.h" +#include "llsd.h" + +#include + +#include +#include + +namespace ll_test +{ + // ensure_approximately_equals: equal to frac_bits bits of the fraction, as + // is_approx_equal_fraction() decides, not to an epsilon. + template + class WithinBitsMatcher : public Catch::Matchers::MatcherBase + { + public: + WithinBitsMatcher(F expected, U32 frac_bits) + : mExpected(expected), mFracBits(frac_bits) + { + } + + bool match(const F& actual) const override + { + return is_approx_equal_fraction(actual, mExpected, mFracBits); + } + + std::string describe() const override + { + std::ostringstream out; + out << "is " << mExpected << " to " << mFracBits << " bits of the fraction"; + return out.str(); + } + + private: + F mExpected; + U32 mFracBits; + }; + + inline WithinBitsMatcher WithinBits(F32 expected, U32 frac_bits) + { + return WithinBitsMatcher(expected, frac_bits); + } + + inline WithinBitsMatcher WithinBits(F64 expected, U32 frac_bits) + { + return WithinBitsMatcher(expected, frac_bits); + } + + // ensure_equals on LLSD: the same type, and the same value all the way + // down, maps in key order and arrays in element order. A mismatch is + // described by where it is, as in ["key"][3] type, so a deep difference + // reads as more than "the two differ". + class LLSDEqualsMatcher : public Catch::Matchers::MatcherBase + { + public: + explicit LLSDEqualsMatcher(LLSD expected); + + bool match(const LLSD& actual) const override; + std::string describe() const override; + + private: + LLSD mExpected; + // What match() last found, for describe(), which Catch2 asks after. + mutable std::string mDifference; + }; + + inline LLSDEqualsMatcher LLSDEquals(LLSD expected) + { + return LLSDEqualsMatcher(std::move(expected)); + } +} + +#endif // AL_ALTESTMATCHERS_H diff --git a/indra/test/alteststateguard.cpp b/indra/test/alteststateguard.cpp new file mode 100644 index 0000000000..5d2149d300 --- /dev/null +++ b/indra/test/alteststateguard.cpp @@ -0,0 +1,275 @@ +/** + * @file alteststateguard.cpp + * @brief Process state every test case must leave as it found it. + * + * $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 "altestfatal.h" + +#include "llapp.h" +#include "llapr.h" +#include "llerrorcontrol.h" +#include "lltracethreadrecorder.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if LL_WINDOWS +#include "llwin32headers.h" +#include +#elif defined(__x86_64__) || defined(__i386__) +#include +#endif + +#if !LL_WINDOWS +extern char** environ; +#endif + +namespace ll_test +{ + namespace + { + struct Check + { + std::string name; + StateGuard::Describe describe; + StateGuard::Restore restore; + std::string last; + }; + + std::vector& checks() + { + static std::vector sChecks; + return sChecks; + } + + int sChanges = 0; + + std::string pointerText(const void* pointer) + { + std::ostringstream out; + out << pointer; + return out.str(); + } + + // A multi-line description is reported by its lines that went and + // came, not as two walls of text. + void reportDifference(std::ostream& out, const std::string& was, const std::string& now) + { + if (was.find('\n') == std::string::npos && now.find('\n') == std::string::npos) + { + out << " was: " << was << "\n now: " << now << "\n"; + return; + } + auto lines = [](const std::string& text) + { + std::set result; + std::istringstream in(text); + for (std::string line; std::getline(in, line);) + { + result.insert(line); + } + return result; + }; + const std::set before = lines(was); + const std::set after = lines(now); + for (const std::string& line : before) + { + if (!after.count(line)) + { + out << " gone: " << line << "\n"; + } + } + for (const std::string& line : after) + { + if (!before.count(line)) + { + out << " new: " << line << "\n"; + } + } + } + + std::string workingDirectory() + { + std::error_code error; + const std::filesystem::path path = std::filesystem::current_path(error); + return error ? "(unreadable: " + error.message() + ")" : path.string(); + } + + // Sorted, one variable a line, so a change reads as the lines it made. + std::string environment() + { + std::vector variables; +#if LL_WINDOWS + if (wchar_t* block = GetEnvironmentStringsW()) + { + for (const wchar_t* entry = block; *entry; entry += wcslen(entry) + 1) + { + variables.push_back(ll_convert_wide_to_string(entry)); + } + FreeEnvironmentStringsW(block); + } +#else + for (char** entry = environ; entry && *entry; ++entry) + { + variables.emplace_back(*entry); + } +#endif + std::sort(variables.begin(), variables.end()); + std::string text; + for (const std::string& variable : variables) + { + text += variable; + text += '\n'; + } + return text; + } + + // The control state only: the sticky exception flags change with any + // arithmetic and say nothing about what a test left behind. + std::string floatingPointControl() + { + std::ostringstream out; + out << std::hex; +#if LL_WINDOWS + out << "control 0x" << _controlfp(0, 0); +#elif defined(__aarch64__) + std::uint64_t fpcr; + __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + out << "FPCR 0x" << fpcr; +#elif defined(__x86_64__) || defined(__i386__) + out << "MXCSR 0x" << (_mm_getcsr() & ~0x3Fu); +#else + out << "unknown"; +#endif + return out.str(); + } + + std::string cLocale() + { + const char* locale = std::setlocale(LC_ALL, nullptr); + return locale ? locale : "(none)"; + } + + // A function's target, where it is a plain function, as an address; + // otherwise its type, which is what an override is told apart by. + std::string fatalFunction() + { + const LLError::FatalFunction function = LLError::getFatalFunction(); + if (!function) + { + return "(none)"; + } + using Plain = void (*)(const std::string&); + if (const Plain* plain = function.target()) + { + return pointerText(reinterpret_cast(*plain)); + } + return function.target_type().name(); + } + + // Catch2 hands every event to every listener; this one runs the + // guard's checks around each test case. + class StateGuardListener : public Catch::EventListenerBase + { + public: + using Catch::EventListenerBase::EventListenerBase; + + void testRunStarting(Catch::TestRunInfo const&) override + { + StateGuard::snapshot(); + } + + void testCaseEnded(Catch::TestCaseStats const& stats) override + { + StateGuard::check(stats.testInfo->name); + } + }; + } + + void StateGuard::add(std::string name, Describe describe, Restore restore) + { + checks().push_back({ std::move(name), std::move(describe), std::move(restore), {} }); + } + + void StateGuard::addCommonChecks() + { + add("the working directory", workingDirectory); + add("the environment", environment); + add("the floating-point control state", floatingPointControl); + add("the C locale", cLocale); + add("the APR pool", [] { return pointerText(gAPRPoolp); }); + add("the LLTrace master recorder", [] { return pointerText(LLTrace::get_master_thread_recorder()); }); + add("the LLApp", [] { return pointerText(LLApp::instance()); }); + add("LLError's fatal function", fatalFunction); + } + + void StateGuard::snapshot() + { + for (Check& check : checks()) + { + check.last = check.describe(); + } + } + + void StateGuard::check(const std::string& test_case) + { + for (Check& check : checks()) + { + std::string now = check.describe(); + if (now == check.last) + { + continue; + } + ++sChanges; + std::cerr << "State guard: test case \"" << test_case << "\" left " << check.name + << " changed\n"; + reportDifference(std::cerr, check.last, now); + if (check.restore) + { + check.restore(); + now = check.describe(); + } + check.last = std::move(now); + } + std::cerr << std::flush; + } + + int StateGuard::changes() + { + return sChanges; + } +} + +CATCH_REGISTER_LISTENER(ll_test::StateGuardListener) diff --git a/indra/test/alteststateguard.h b/indra/test/alteststateguard.h new file mode 100644 index 0000000000..bbfea4c2a3 --- /dev/null +++ b/indra/test/alteststateguard.h @@ -0,0 +1,67 @@ +/** + * @file alteststateguard.h + * @brief Process state every test case must leave as it found it, checked after + * each case, so a leak fails the test that made it. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AL_ALTESTSTATEGUARD_H +#define AL_ALTESTSTATEGUARD_H + +#include +#include + +namespace ll_test +{ + // Each check describes one piece of process state as text. The guard takes + // every description when the run starts and again after each test case; + // where one differs, it reports the case that changed it, then takes the + // new description as the one to compare with, so only that case is named. + // A check with a restore puts the state back after reporting, so a known + // leak cannot reach the next case. + // + // Today a binary runs one test file per process, so the guard catches what + // would otherwise only surface once the files share a process: in the + // combined run, as some later test failing for no reason of its own. + class StateGuard + { + public: + using Describe = std::function; + using Restore = std::function; + + static void add(std::string name, Describe describe, Restore restore = {}); + + // What every LL process has: the working directory, the environment, + // the floating-point control state, the C locale, the APR pool, the + // LLTrace master recorder, the LLApp and LLError's fatal function. + static void addCommonChecks(); + + // The listener's: before the first case, and after each. + static void snapshot(); + static void check(const std::string& test_case); + + // How many times a case left something changed. The main fails the run + // if any did, since a listener cannot fail a test case of its own. + static int changes(); + }; +} + +#endif // AL_ALTESTSTATEGUARD_H diff --git a/indra/test/altestteardown.cpp b/indra/test/altestteardown.cpp new file mode 100644 index 0000000000..3c00f43049 --- /dev/null +++ b/indra/test/altestteardown.cpp @@ -0,0 +1,72 @@ +/** + * @file altestteardown.cpp + * @brief The end of a test binary's process-wide fixtures, in order. + * + * $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 "altestteardown.h" + +#include +#include +#include + +namespace ll_test +{ + namespace + { + // Made on first use, so registering a step from any static + // initializer finds it ready. + std::vector>& steps() + { + static std::vector> sSteps; + return sSteps; + } + } + + void Teardown::add(std::function step) + { + steps().push_back(std::move(step)); + } + + void Teardown::runAll() + { + std::vector>& pending = steps(); + while (!pending.empty()) + { + std::function step = std::move(pending.back()); + pending.pop_back(); + try + { + step(); + } + catch (const std::exception& e) + { + std::cerr << "Teardown step threw: " << e.what() << std::endl; + } + catch (...) + { + std::cerr << "Teardown step threw a non-standard exception" << std::endl; + } + } + } +} diff --git a/indra/test/altestteardown.h b/indra/test/altestteardown.h new file mode 100644 index 0000000000..06ffd1478b --- /dev/null +++ b/indra/test/altestteardown.h @@ -0,0 +1,50 @@ +/** + * @file altestteardown.h + * @brief The end of a test binary's process-wide fixtures, in order, before the + * main lets LLError and APR go. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AL_ALTESTTEARDOWN_H +#define AL_ALTESTTEARDOWN_H + +#include + +namespace ll_test +{ + // A process-wide fixture -- a GL context, a headless UI, a messaging + // system -- is made by its accessor the first time a test asks for it, + // and registers its end here. The main runs the ends after the last test + // case, newest first, while APR and LLError are still up. Left to static + // destruction, they would run in whatever order the linker chose, after + // the main had already let APR go. + class Teardown + { + public: + static void add(std::function step); + + // The main's: runs each step once, newest first. A step that throws + // is reported and the rest still run. + static void runAll(); + }; +} + +#endif // AL_ALTESTTEARDOWN_H From 2c39ac29f0d65187290cd0c5ede43eeca8617c42 Mon Sep 17 00:00:00 2001 From: Rye Date: Thu, 8 Oct 2026 22:56:27 -0400 Subject: [PATCH 03/55] al_add_test_binary builds many Catch2 test files into one binary and runs each in a process of its own Each test file is a CTest entry, /, that runs that file's test cases alone, chosen by the tag --filenames-as-tags gives each case: process boundaries as one executable a test file had them, so moving files into one binary changes nothing about how they run. Hidden cases, such as benchmarks, stay out of those entries. A filter that matches nothing exits 2, so a file whose cases went missing fails rather than passing empty. /combined runs everything in one process. It is labelled combined_pending, which the default run leaves out, until the tests are known to share a process; COMBINED then labels it combined. Test files go straight into the executable, never a static archive the linker could drop them from, and one file belongs to one binary. GL, PYTHON, ISA_TIER and the timeouts mean what they mean to al_add_test, and RESOURCE_LOCK keeps entries that share a fixed resource apart. AL_TEST_PER_CASE registers each test case as an entry of its own instead, discovered when ctest runs, for finding a failure that comes from order or shared state. Co-Authored-By: Claude Opus 5.5 --- indra/CMakeLists.txt | 12 +- indra/cmake/ALAddTestBinary.cmake | 228 ++++++++++++++++++++++++++ indra/cmake/ConfigurationReport.cmake | 12 +- 3 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 indra/cmake/ALAddTestBinary.cmake diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 2ae2cef7ae..7ac29f6c06 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -41,6 +41,13 @@ cmake_dependent_option( "AL_BUILD_TESTS" OFF ) +cmake_dependent_option( + AL_TEST_PER_CASE + "Register a Catch2 binary's test cases one CTest entry each, found when ctest runs, in place of one entry a test file: for finding a failure that comes from order or shared state" + OFF + "AL_BUILD_TESTS" + OFF +) option(AL_BUILD_SHARED_LLCOMMON "Build llcommon as a shared library" OFF) option(AL_BUILD_PACKAGE "Add the package target: the CPack archive of the installed tree" ON) @@ -413,13 +420,15 @@ if(AL_BUILD_TESTS) # Set parallel level to decrease test runtime. Benchmarks are registered # as tests under their own label and left out of the default run, since # their numbers are their output and a shared parallel run would ruin them. + # A Catch2 binary's run of all its tests in one process stays out too until + # they are known to share one (combined_pending; see ALAddTestBinary.cmake). # Each run also writes its results as JUnit, which CI keeps. set( CMAKE_CTEST_ARGUMENTS -j${NUM_CORES} --output-on-failure -LE - benchmark + "benchmark|combined_pending" --output-junit "${CMAKE_BINARY_DIR}/Testing/test-results.xml" ) @@ -481,6 +490,7 @@ endforeach() # Add our test macros if(AL_BUILD_TESTS) include(LLAddBuildTest) + include(ALAddTestBinary) endif() # The build system's own files, for the IDE. diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake new file mode 100644 index 0000000000..9e98e0ea56 --- /dev/null +++ b/indra/cmake/ALAddTestBinary.cmake @@ -0,0 +1,228 @@ +# -*- cmake -*- +# +# Catch2 test binaries: many test files built into one executable, each file +# still run in a process of its own. + +include_guard() + +if(NOT AL_BUILD_TESTS) + return() +endif() + +# al_add_test_binary( PROJECT SOURCES ... +# [LIBRARIES ...] [SUPPORT ...] +# [INCLUDES ...] [DEFINES ...] [ENVIRONMENT ...] +# [GL] [PYTHON] [ISA_TIER ] [RESOURCE_LOCK ...] [COMBINED]) +# +# Builds the test files into one Catch2 executable, , with the harness +# (al_test_support: the main, the listeners and the matchers) and SUPPORT's +# objects, linked against LIBRARIES. Anything that registers a test case or a +# listener is a source of the executable or of an OBJECT library: in a static +# archive the linker drops what nothing references, and the registrations +# with it, so the tests would vanish without a word. DEFINES and INCLUDES are +# the binary's, for every file in it. +# +# Each test file is a CTest entry, /, that runs the file's +# test cases -- chosen by the tag Catch2's --filenames-as-tags gives each case +# from the name of its file -- in one process, as an executable to a test file +# ran them. Naming a tag would also choose the file's hidden cases, such as its +# benchmarks, so the filter leaves those out, as a run with no filter does. A +# filter that matches nothing exits 2, so a file whose test cases went missing +# fails rather than passing empty. A stem names one file in a binary, and a +# file belongs to one binary. +# +# /combined runs all of them in one process. It is labelled +# combined_pending, 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. +# +# 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 +# registered disabled without AL_ENABLE_GL_TESTS, a PYTHON binary is given the +# interpreter and registered disabled without one that has llsd, and an +# ISA_TIER binary is built at that x86-64 tier, named _, labelled +# isa, and skips on a host below it. RESOURCE_LOCK names what the binary's +# entries hold that no two may hold at once, such as a fixed port. +function(al_add_test_binary name) + cmake_parse_arguments( + PARSE_ARGV 1 + arg + "GL;PYTHON;COMBINED" + "PROJECT;ISA_TIER" + "SOURCES;LIBRARIES;SUPPORT;INCLUDES;DEFINES;ENVIRONMENT;RESOURCE_LOCK" + ) + if(NOT arg_PROJECT) + message(FATAL_ERROR "al_add_test_binary(${name}): PROJECT is required") + endif() + if(NOT arg_SOURCES) + message(FATAL_ERROR "al_add_test_binary(${name}): SOURCES is required") + endif() + if(arg_UNPARSED_ARGUMENTS) + message( + FATAL_ERROR + "al_add_test_binary(${name}): unexpected arguments: ${arg_UNPARSED_ARGUMENTS}" + ) + endif() + set(target ${name}) + if(arg_ISA_TIER) + if(NOT arg_ISA_TIER MATCHES "^(baseline|v2|v3|v4)$") + message( + FATAL_ERROR + "al_add_test_binary(${name}): ISA_TIER must be baseline, v2, v3 or v4, not ${arg_ISA_TIER}" + ) + endif() + set(target ${name}_${arg_ISA_TIER}) + endif() + + set(stems) + foreach(source IN LISTS arg_SOURCES) + get_filename_component(stem "${source}" NAME_WE) + if(stem IN_LIST stems) + message( + FATAL_ERROR + "al_add_test_binary(${name}): two test files are named ${stem}; the stem names a file's entry and its tag" + ) + endif() + list(APPEND stems ${stem}) + + get_filename_component(path "${source}" ABSOLUTE) + string(MD5 key "${path}") + get_property(owner GLOBAL PROPERTY AL_TEST_SOURCE_${key}) + if(owner) + message( + FATAL_ERROR + "al_add_test_binary(${name}): ${source} is already in ${owner}; a test file in two binaries would run twice" + ) + endif() + set_property(GLOBAL PROPERTY AL_TEST_SOURCE_${key} ${target}) + endforeach() + + # vcpkg's toolchain hangs an app-local copy step on every executable it + # sees declared; a test runs from the build tree, as al_add_test explains. + set(VCPKG_APPLOCAL_DEPS OFF) + add_executable(${target} ${arg_SOURCES}) + 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_DEFINES) + target_compile_definitions(${target} PRIVATE ${arg_DEFINES}) + endif() + set_target_properties(${target} PROPERTIES FOLDER "Tests/${arg_PROJECT}") + if(arg_ISA_TIER) + set_target_properties(${target} PROPERTIES AL_ISA_TIER ${arg_ISA_TIER}) + endif() + if(WINDOWS) + set_target_properties(${target} PROPERTIES AL_SKIP_RELEASE_DEBUG_INFO ON) + elseif(DARWIN) + # A test binary is run straight from the build tree, so it is ad-hoc signed. + set_target_properties( + ${target} + PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" BUILD_RPATH "${SHARED_LIB_STAGING_DIR}" + ) + endif() + + set(arguments) + set(environment ${arg_ENVIRONMENT}) + set(labels ${arg_PROJECT}) + set(disabled FALSE) + # As al_add_test's: five minutes a test file, fifteen with a Python peer. + set(timeout 300) + if(arg_ISA_TIER) + al_isa_level(${arg_ISA_TIER} ${CMAKE_SYSTEM_NAME} ${ARCH} level) + list(APPEND arguments --isa-level ${level}) + list(APPEND labels isa) + endif() + if(arg_PYTHON) + set(timeout 900) + list(APPEND labels python) + if(AL_PYTHON_PEERS) + list(APPEND environment "PYTHON=${Python3_EXECUTABLE}") + else() + set(disabled TRUE) + endif() + endif() + if(arg_GL) + list(APPEND labels gl) + if(NOT AL_ENABLE_GL_TESTS) + set(disabled TRUE) + endif() + endif() + + if(AL_TEST_PER_CASE) + include(Catch) + set( + per_case_properties + WORKING_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR} + TIMEOUT + ${timeout} + SKIP_RETURN_CODE + 125 + DISABLED + ${disabled} + ) + if(arg_RESOURCE_LOCK) + list(APPEND per_case_properties RESOURCE_LOCK "${arg_RESOURCE_LOCK}") + endif() + # The main makes Catch2's "all skipped" CTest's 125, so a skip is not + # left to the discovery's own code for it. + catch_discover_tests( + ${target} + TEST_PREFIX "${target}/" + EXTRA_ARGS ${arguments} + DISCOVERY_MODE PRE_TEST + SKIP_IS_FAILURE + ADD_TAGS_AS_LABELS + DL_PATHS ${SHARED_LIB_STAGING_DIR} + PROPERTIES ${per_case_properties} + ) + else() + foreach(stem IN LISTS stems) + add_test( + NAME ${target}/${stem} + COMMAND $ --filenames-as-tags "[#${stem}]~[.]" ${arguments} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties( + ${target}/${stem} + PROPERTIES + ENVIRONMENT "${environment}" + ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" + LABELS "${labels}" + DISABLED ${disabled} + SKIP_RETURN_CODE 125 + TIMEOUT ${timeout} + ) + if(arg_RESOURCE_LOCK) + set_tests_properties(${target}/${stem} PROPERTIES RESOURCE_LOCK "${arg_RESOURCE_LOCK}") + endif() + endforeach() + + list(LENGTH stems file_count) + math(EXPR combined_timeout "${timeout} * ${file_count}") + set(combined_label combined_pending) + 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() + + add_dependencies(BUILD_TESTS ${target}) +endfunction() diff --git a/indra/cmake/ConfigurationReport.cmake b/indra/cmake/ConfigurationReport.cmake index 566eafc5af..9766879be0 100644 --- a/indra/cmake/ConfigurationReport.cmake +++ b/indra/cmake/ConfigurationReport.cmake @@ -21,6 +21,7 @@ set( AL_BUILD_APPEARANCE_UTILITY AL_BUILD_TESTS AL_ENABLE_GL_TESTS + AL_TEST_PER_CASE AL_BUILD_DOCS AL_VCPKG_INSTALL AL_BUILD_DULLAHAN_EXAMPLE @@ -346,11 +347,14 @@ function(al_configuration_report) if(AL_BUILD_TESTS) _al_count_tests("${INDRA_SOURCE_DIR}" test_count) - if(AL_ENABLE_GL_TESTS) - _al_report_row("Tests" "on, ${test_count} registered") - else() - _al_report_row("Tests" "on, ${test_count} registered; the GL suites disabled") + set(tests "on, ${test_count} registered") + if(NOT AL_ENABLE_GL_TESTS) + string(APPEND tests "; the GL suites disabled") + endif() + if(AL_TEST_PER_CASE) + string(APPEND tests "; Catch2 test cases one entry each, found when ctest runs") endif() + _al_report_row("Tests" "${tests}") else() _al_report_row("Tests" "off") endif() From 218402e7799d6b8bef36382900460a7d3b51eb17 Mon Sep 17 00:00:00 2001 From: Rye Date: Thu, 8 Oct 2026 22:56:27 -0400 Subject: [PATCH 04/55] The Catch2 harness tests itself test_support_tests, the first al_add_test_binary binary: the matchers, an LL_ERRS throwing FatalError, and the teardown's order as test cases; and, through run_and_expect.cmake, what CTest sees from the harness, checked by exit code and output together: the state guard naming the case that left the environment changed, LOGFAIL replaying a failed case's log and not a passed one's, "all skipped" as 125, a filter matching nothing as 2, the ISA gate, and a listing that needs nothing set up. Writing them found two things the conversion must handle: a braced initializer in an assertion is two macro arguments, and so is an LL_ERRS, which expands to commas before Catch2 hands it on. Co-Authored-By: Claude Opus 5.5 --- indra/test/CMakeLists.txt | 76 ++++++++++++++ indra/test/run_and_expect.cmake | 30 ++++++ indra/test/tests/altestsupport_test.cpp | 128 ++++++++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 indra/test/run_and_expect.cmake create mode 100644 indra/test/tests/altestsupport_test.cpp diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 404479a8ce..1fe1b17221 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -55,3 +55,79 @@ target_include_directories(al_test_support PUBLIC ${INDRA_SOURCE_DIR}/llmath) target_link_libraries(al_test_support PRIVATE al::flags PUBLIC llcommon ll::catch2) set_target_properties(al_test_support PROPERTIES FOLDER "Tests") + +# The harness's own tests, the first Catch2 binary. Its hidden test cases are +# run one at a time by the entries below, which check the exit code and the +# output CTest sees from the harness: the state guard, LOGFAIL's replay, the +# skip and no-match codes, the ISA gate, and a listing needing no setup. +al_add_test_binary( + test_support_tests + PROJECT test + SOURCES tests/altestsupport_test.cpp + LIBRARIES llcommon + COMBINED +) + +function(al_expect_from_test_support name) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "ARGS;EXIT;OUTPUT;REJECT" "") + set( + defines + "-DEXE=$" + "-DARGS=${arg_ARGS}" + "-DEXPECT_EXIT=${arg_EXIT}" + ) + if(DEFINED arg_OUTPUT) + list(APPEND defines "-DEXPECT_OUTPUT=${arg_OUTPUT}") + endif() + if(DEFINED arg_REJECT) + list(APPEND defines "-DREJECT_OUTPUT=${arg_REJECT}") + endif() + add_test( + NAME test_support_tests/${name} + COMMAND ${CMAKE_COMMAND} ${defines} -P ${CMAKE_CURRENT_SOURCE_DIR}/run_and_expect.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + # LOGTEST, if a developer has it set, would print every log line and blur + # what LOGFAIL alone does. + set_tests_properties( + test_support_tests/${name} + PROPERTIES + ENVIRONMENT "LOGTEST=;LOGFAIL=DEBUG" + ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" + LABELS test + TIMEOUT 60 + ) +endfunction() + +al_expect_from_test_support( + guard_names_the_case + ARGS "[.guard-leak]" + EXIT 1 + OUTPUT "a test case that leaves the environment changed. left the environment changed.*AL_TEST_SUPPORT_LEAK=1" +) +al_expect_from_test_support( + replay_on_failure + ARGS "[.replay-fail]" + EXIT 42 + OUTPUT "log of .a failing test case that logged.*the line a failing case logged" +) +al_expect_from_test_support( + no_replay_on_pass + ARGS "[.replay-pass]" + EXIT 0 + REJECT "the line a passing case logged" +) +al_expect_from_test_support(all_skipped_is_125 ARGS "[.skip]" EXIT 125) +al_expect_from_test_support(nothing_matched_fails ARGS "no such test case" EXIT 2) +al_expect_from_test_support( + isa_gate_skips + ARGS "--isa-level|9" + EXIT 125 + OUTPUT "does not run x86-64 level 9" +) +al_expect_from_test_support( + listing_needs_no_setup + ARGS "--list-tests" + EXIT 0 + OUTPUT "LLSDEquals compares all the way down" +) diff --git a/indra/test/run_and_expect.cmake b/indra/test/run_and_expect.cmake new file mode 100644 index 0000000000..52f5f53bad --- /dev/null +++ b/indra/test/run_and_expect.cmake @@ -0,0 +1,30 @@ +# -*- cmake -*- +# +# Runs a test binary and checks what it did: its exit code, and what its output +# must and must not hold. For the Catch2 harness's own tests, whose exit codes +# and messages are its interface to CTest, which can check one of the two at a +# time. +# +# cmake -DEXE= -DARGS= -DEXPECT_EXIT= +# [-DEXPECT_OUTPUT=] [-DREJECT_OUTPUT=] -P run_and_expect.cmake +# +# ARGS separates its arguments with |, since CTest would split them on ;. + +string(REPLACE "|" ";" arguments "${ARGS}") +execute_process( + COMMAND ${EXE} ${arguments} + RESULT_VARIABLE exit_code + OUTPUT_VARIABLE output + ERROR_VARIABLE output +) +message("${output}") + +if(NOT exit_code STREQUAL EXPECT_EXIT) + message(FATAL_ERROR "Expected exit code ${EXPECT_EXIT}, got ${exit_code}") +endif() +if(DEFINED EXPECT_OUTPUT AND NOT output MATCHES "${EXPECT_OUTPUT}") + message(FATAL_ERROR "The output does not hold: ${EXPECT_OUTPUT}") +endif() +if(DEFINED REJECT_OUTPUT AND output MATCHES "${REJECT_OUTPUT}") + message(FATAL_ERROR "The output holds what it must not: ${REJECT_OUTPUT}") +endif() diff --git a/indra/test/tests/altestsupport_test.cpp b/indra/test/tests/altestsupport_test.cpp new file mode 100644 index 0000000000..5a20139421 --- /dev/null +++ b/indra/test/tests/altestsupport_test.cpp @@ -0,0 +1,128 @@ +/** + * @file altestsupport_test.cpp + * @brief Tests of the Catch2 harness itself: its matchers, what an LL_ERRS + * does, the teardown, and the cases that CTest entries in + * test/CMakeLists.txt run to check the guard, the log replay and the exit + * codes from outside. + * + * $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 "altestfatal.h" +#include "altestmatchers.h" +#include "altestteardown.h" + +#include "llerror.h" +#include "llsd.h" + +#include +#include +#include + +#include +#include + +namespace +{ + LLSD nested(F64 leaf) + { + LLSD sd; + sd["a"][2] = leaf; + sd["b"] = "text"; + return sd; + } +} + +TEST_CASE("WithinBits holds to the bits asked for, and no further", "[altestsupport]") +{ + // 2^-20 apart: equal to 16 bits of the fraction, not to 24. + const F32 one = 1.f; + const F32 near = 1.f + 1.f / (1 << 20); + CHECK_THAT(near, ll_test::WithinBits(one, 16)); + CHECK_FALSE(ll_test::WithinBits(one, 24).match(near)); + CHECK_FALSE(ll_test::WithinBits(one, 16).match(1.01f)); + CHECK_THAT(F64(near), ll_test::WithinBits(F64(one), 16)); +} + +TEST_CASE("LLSDEquals compares all the way down and says where two differ", "[altestsupport]") +{ + CHECK_THAT(nested(1.5), ll_test::LLSDEquals(nested(1.5))); + + const ll_test::LLSDEqualsMatcher deep = ll_test::LLSDEquals(nested(1.5)); + CHECK_FALSE(deep.match(nested(2.5))); + CHECK_THAT(deep.describe(), Catch::Matchers::ContainsSubstring("[\"a\"][2]: value")); + + const ll_test::LLSDEqualsMatcher top = ll_test::LLSDEquals(LLSD("1")); + CHECK_FALSE(top.match(LLSD(1))); + CHECK_THAT(top.describe(), Catch::Matchers::ContainsSubstring("the top: type")); +} + +TEST_CASE("An LL_ERRS throws FatalError, which a test can catch", "[altestsupport]") +{ + // Named, not written inside the assertion: Catch2 hands its argument on to + // a macro of its own after expanding it, and LL_ERRS expands to commas. + const auto deliberate = [] { LL_ERRS("altestsupport") << "deliberate" << LL_ENDL; }; + CHECK_THROWS_AS(deliberate(), ll_test::FatalError); + CHECK_THROWS_WITH(deliberate(), Catch::Matchers::ContainsSubstring("deliberate")); +} + +TEST_CASE("Teardown runs each step once, newest first", "[altestsupport]") +{ + std::vector order; + ll_test::Teardown::add([&order] { order.push_back(1); }); + ll_test::Teardown::add([&order] { order.push_back(2); }); + ll_test::Teardown::runAll(); + const std::vector newest_first{ 2, 1 }; + CHECK(order == newest_first); + ll_test::Teardown::runAll(); + CHECK(order.size() == 2); +} + +// Hidden: the CTest entries in test/CMakeLists.txt run these one at a time +// and check what the harness made of them. + +TEST_CASE("a test case that leaves the environment changed", "[.guard-leak]") +{ +#if LL_WINDOWS + _putenv_s("AL_TEST_SUPPORT_LEAK", "1"); +#else + setenv("AL_TEST_SUPPORT_LEAK", "1", 1); +#endif + SUCCEED(); +} + +TEST_CASE("a failing test case that logged", "[.replay-fail]") +{ + LL_INFOS("altestsupport") << "the line a failing case logged" << LL_ENDL; + FAIL("deliberately"); +} + +TEST_CASE("a passing test case that logged", "[.replay-pass]") +{ + LL_INFOS("altestsupport") << "the line a passing case logged" << LL_ENDL; + SUCCEED(); +} + +TEST_CASE("a test case that skips", "[.skip]") +{ + SKIP("deliberately"); +} From 1ac2e23bc41133847d090d05187820513b9ea04d Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 9 Oct 2026 00:04:50 -0400 Subject: [PATCH 05/55] Catch2 comes from alchemy-registry, at 3.16.1 catch2 joins the registry's package list, so it resolves to the registry's 3.16.1 instead of the builtin 3.16.0. 3.16.0 turns MSVC's optimisation off in every file that includes the benchmark headers, and compiles a file with many test cases in quadratic time under GCC; 3.16.1 fixes both. The port goes from the registry once the builtin one has caught up. Co-Authored-By: Claude Opus 5.5 --- indra/vcpkg-configuration.json | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/vcpkg-configuration.json b/indra/vcpkg-configuration.json index 3c915aec0a..322ea18b46 100644 --- a/indra/vcpkg-configuration.json +++ b/indra/vcpkg-configuration.json @@ -13,6 +13,7 @@ "apr", "boost-context", "boost-fiber", + "catch2", "cef-bin", "collada-dom", "corrosion", From 661328114589dc89987fe8aee3342dc3cb376447 Mon Sep 17 00:00:00 2001 From: Rye Date: Thu, 8 Oct 2026 23:52:42 -0400 Subject: [PATCH 06/55] The Catch2 harness names the running test case, names the failure it throws, and compares memory byte for byte Converted TUT tests need a counterpart for three things lltut.h and tut.hpp gave them: - get_test_name(), used as data (a process's description, a message prefix): ll_test::currentTestName(), kept by a listener, since Catch2's own way to ask is deprecated; - tut::failure, which a few tests catch to add to the report and rethrow: ll_test::Failure, so they do not each name Catch2's internal header; - ensure_memory_matches: ll_test::Memory and ll_test::MemoryMatches, which say at which byte two buffers part. The harness's own tests cover the matcher and the name. Co-Authored-By: Claude Opus 5.5 --- indra/test/CMakeLists.txt | 17 +++++-- indra/test/altestfailure.h | 41 ++++++++++++++++ indra/test/altestmatchers.cpp | 59 +++++++++++++++++++++++ indra/test/altestmatchers.h | 41 +++++++++++++++- indra/test/altestname.cpp | 62 +++++++++++++++++++++++++ indra/test/altestname.h | 38 +++++++++++++++ indra/test/tests/altestsupport_test.cpp | 19 ++++++++ 7 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 indra/test/altestfailure.h create mode 100644 indra/test/altestname.cpp create mode 100644 indra/test/altestname.h diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 1fe1b17221..3077b659f8 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -26,9 +26,10 @@ target_link_libraries(lltut_runner_lib PRIVATE al::flags PUBLIC llcommon ll::tut set_target_properties(lltut_runner_lib PROPERTIES FOLDER "Tests") # The Catch2 harness: the main, what an LL_ERRS does, LOGFAIL's replay, the -# state guard, the teardown and the matchers. An OBJECT library, which each -# test binary links directly: in a static archive the listeners' -# registrations, which nothing references, would be dropped by the linker. +# state guard, the teardown, the matchers, the running case's name and the +# failure type. An OBJECT library, which each test binary links directly: in +# a static archive the listeners' registrations, which nothing references, +# would be dropped by the linker. add_library(al_test_support OBJECT) target_sources( @@ -38,6 +39,7 @@ target_sources( altestlogreplay.cpp altestmain.cpp altestmatchers.cpp + altestname.cpp alteststateguard.cpp altestteardown.cpp ) @@ -46,7 +48,14 @@ target_sources( al_test_support PUBLIC FILE_SET HEADERS - FILES altestfatal.h altestlogreplay.h altestmatchers.h alteststateguard.h altestteardown.h + FILES + altestfailure.h + altestfatal.h + altestlogreplay.h + altestmatchers.h + altestname.h + alteststateguard.h + altestteardown.h ) # The matchers compare as llmath's is_approx_equal_fraction() does, a header diff --git a/indra/test/altestfailure.h b/indra/test/altestfailure.h new file mode 100644 index 0000000000..01c0f891ef --- /dev/null +++ b/indra/test/altestfailure.h @@ -0,0 +1,41 @@ +/** + * @file altestfailure.h + * @brief What a failed assertion throws, for a test that catches it to add to + * the report and rethrow, as tests caught tut::failure. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AL_ALTESTFAILURE_H +#define AL_ALTESTFAILURE_H + +#include + +namespace ll_test +{ + // Thrown by a failed REQUIRE or a FAIL, after Catch2 has recorded the + // failure: catching it can add to what the case reports, or tidy up, + // and must rethrow, since the failure stands either way. It carries no + // message; Catch2 has reported that already. One alias, so the tests + // that catch it do not each name Catch2's internal header. + using Failure = Catch::TestFailureException; +} + +#endif // AL_ALTESTFAILURE_H diff --git a/indra/test/altestmatchers.cpp b/indra/test/altestmatchers.cpp index 012f80efa6..2627cd7da0 100644 --- a/indra/test/altestmatchers.cpp +++ b/indra/test/altestmatchers.cpp @@ -26,8 +26,12 @@ #include "altestmatchers.h" +#include "llformat.h" #include "llsdserialize.h" +#include +#include + namespace ll_test { namespace @@ -146,4 +150,59 @@ namespace ll_test } return out.str(); } + + std::ostream& operator<<(std::ostream& out, const MemoryView& memory) + { + // The first bytes are what tell two buffers apart at a glance. + constexpr size_t SHOWN = 32; + const U8* bytes = static_cast(memory.data); + out << memory.size << " bytes"; + if (bytes && memory.size) + { + out << ":"; + for (size_t i = 0; i < std::min(memory.size, SHOWN); ++i) + { + out << llformat(" %02x", bytes[i]); + } + if (memory.size > SHOWN) + { + out << " ..."; + } + } + return out; + } + + bool MemoryMatchesMatcher::match(const MemoryView& actual) const + { + if (actual.size != mExpected.size) + { + mDifference = "the sizes"; + return false; + } + if (actual.size == 0 || std::memcmp(actual.data, mExpected.data, actual.size) == 0) + { + mDifference.clear(); + return true; + } + const U8* a = static_cast(actual.data); + const U8* e = static_cast(mExpected.data); + size_t at = 0; + while (a[at] == e[at]) + { + ++at; + } + mDifference = llformat("byte %zu", at); + return false; + } + + std::string MemoryMatchesMatcher::describe() const + { + std::ostringstream out; + out << "matches " << mExpected; + if (!mDifference.empty()) + { + out << " (they differ at " << mDifference << ")"; + } + return out.str(); + } } diff --git a/indra/test/altestmatchers.h b/indra/test/altestmatchers.h index 4e15d1c642..18a385148e 100644 --- a/indra/test/altestmatchers.h +++ b/indra/test/altestmatchers.h @@ -1,7 +1,7 @@ /** * @file altestmatchers.h * @brief Catch2 matchers for what LL's TUT helpers compared: floats to a number of - * bits, and LLSD deeply. + * bits, LLSD deeply, and memory byte for byte. * * $LicenseInfo:firstyear=2026&license=viewerlgpl$ * Alchemy Viewer Source Code @@ -31,6 +31,8 @@ #include +#include +#include #include #include @@ -96,6 +98,43 @@ namespace ll_test { return LLSDEqualsMatcher(std::move(expected)); } + + // ensure_memory_matches: so many bytes at an address, compared as bytes. + // The bytes are not copied; they must outlive the assertion. + struct MemoryView + { + const void* data; + size_t size; + }; + + inline MemoryView Memory(const void* data, size_t size) + { + return MemoryView{ data, size }; + } + + std::ostream& operator<<(std::ostream& out, const MemoryView& memory); + + class MemoryMatchesMatcher : public Catch::Matchers::MatcherBase + { + public: + explicit MemoryMatchesMatcher(MemoryView expected) + : mExpected(expected) + { + } + + bool match(const MemoryView& actual) const override; + std::string describe() const override; + + private: + MemoryView mExpected; + // Where match() last found the two to part, for describe(). + mutable std::string mDifference; + }; + + inline MemoryMatchesMatcher MemoryMatches(const void* expected, size_t size) + { + return MemoryMatchesMatcher(MemoryView{ expected, size }); + } } #endif // AL_ALTESTMATCHERS_H diff --git a/indra/test/altestname.cpp b/indra/test/altestname.cpp new file mode 100644 index 0000000000..b45748b0e5 --- /dev/null +++ b/indra/test/altestname.cpp @@ -0,0 +1,62 @@ +/** + * @file altestname.cpp + * @brief The name of the test case running. + * + * $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 "altestname.h" + +#include +#include +#include + +namespace ll_test +{ + namespace + { + std::string sCurrentTestName; + + class TestNameListener : public Catch::EventListenerBase + { + public: + using Catch::EventListenerBase::EventListenerBase; + + void testCaseStarting(Catch::TestCaseInfo const& info) override + { + sCurrentTestName = info.name; + } + + void testCaseEnded(Catch::TestCaseStats const&) override + { + sCurrentTestName.clear(); + } + }; + } + + const std::string& currentTestName() + { + return sCurrentTestName; + } +} + +CATCH_REGISTER_LISTENER(ll_test::TestNameListener) diff --git a/indra/test/altestname.h b/indra/test/altestname.h new file mode 100644 index 0000000000..14b701292b --- /dev/null +++ b/indra/test/altestname.h @@ -0,0 +1,38 @@ +/** + * @file altestname.h + * @brief The name of the test case running, where a TUT test asked for its own. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AL_ALTESTNAME_H +#define AL_ALTESTNAME_H + +#include + +namespace ll_test +{ + // The running test case's name, as TUT's get_test_name() gave a test its + // own: to describe what it starts, or to name a file it writes. Empty + // outside a test case. Catch2's own way to ask is deprecated. + const std::string& currentTestName(); +} + +#endif // AL_ALTESTNAME_H diff --git a/indra/test/tests/altestsupport_test.cpp b/indra/test/tests/altestsupport_test.cpp index 5a20139421..9245ae6067 100644 --- a/indra/test/tests/altestsupport_test.cpp +++ b/indra/test/tests/altestsupport_test.cpp @@ -29,6 +29,7 @@ #include "altestfatal.h" #include "altestmatchers.h" +#include "altestname.h" #include "altestteardown.h" #include "llerror.h" @@ -76,6 +77,24 @@ TEST_CASE("LLSDEquals compares all the way down and says where two differ", "[al CHECK_THAT(top.describe(), Catch::Matchers::ContainsSubstring("the top: type")); } +TEST_CASE("MemoryMatches compares byte for byte and says where two part", "[altestsupport]") +{ + const char same[] = "abcd"; + const char other[] = "abxd"; + CHECK_THAT(ll_test::Memory(same, 4), ll_test::MemoryMatches(same, 4)); + + const ll_test::MemoryMatchesMatcher differs = ll_test::MemoryMatches(other, 4); + CHECK_FALSE(differs.match(ll_test::Memory(same, 4))); + CHECK_THAT(differs.describe(), Catch::Matchers::ContainsSubstring("byte 2")); + + CHECK_FALSE(ll_test::MemoryMatches(same, 3).match(ll_test::Memory(same, 4))); +} + +TEST_CASE("currentTestName is the running test case's", "[altestsupport]") +{ + CHECK(ll_test::currentTestName() == "currentTestName is the running test case's"); +} + TEST_CASE("An LL_ERRS throws FatalError, which a test can catch", "[altestsupport]") { // Named, not written inside the assertion: Catch2 hands its argument on to From cfd217282fccdafb1ab4b28fe6ca594ed4745b3e Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 9 Oct 2026 02:16:18 -0400 Subject: [PATCH 07/55] tut2catch.py converts TUT test files to idiomatic Catch2, deciding each assertion by the types clang resolved TM16: the waves convert about 390 TUT files, and a conversion by text gets the overloads wrong. ensure_equals means a deep compare for two LLSDs, a compare of their strings for two LLURIs, and == otherwise, and only the argument types tell them apart. So the converter reads clang's own AST of each file, with the file's flags from compile_commands.json, and parses only the file's own declarations out of the multi-gigabyte JSON dump. Each test becomes a TEST_CASE_METHOD on the group's data struct, named ": " or ": #N", tagged with the group, and moved later where needed so that declaration order is TUT's numeric order. Each ensure* becomes one Catch2 assertion, with its message as UNSCOPED_INFO and braces where it was a lone statement. A condition Catch2 cannot decompose is parenthesised whole, not split, so every TUT call stays one assertion and parity can count them. What it cannot decide (a call in code this host does not compile whose conversion depends on types, a template whose instantiations disagree, a caught tut::failure whose message is used, a macro-made test) it leaves unconverted with a TM-CHECK mark and a non-zero exit. A dry run over all 396 test files finds 4,914 tests and 70 marks, in 13 files. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 2265 ++++++++++++++++++++++++++++++++++ 1 file changed, 2265 insertions(+) create mode 100644 scripts/testing/tut2catch.py diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py new file mode 100644 index 0000000000..fc9c2a69f1 --- /dev/null +++ b/scripts/testing/tut2catch.py @@ -0,0 +1,2265 @@ +#!/usr/bin/env python3 +"""Converts TUT test files to idiomatic Catch2 v3, in place. + +Each file is read through clang's own AST of it, so what a call means is +decided by the types clang resolved, not by its text: + + structure every test of a group becomes + TEST_CASE_METHOD(, ": ", "[]") + with the group's data struct as the fixture, built fresh for + each case as TUT built it for each test. The name is the + test's set_test_name literal, or #N when it has none; " #N" + is added to a name another case of the file has (ignoring + case). Tests declared out of TUT's numeric order are moved + later in the file, never earlier, so that declaration order, + Catch2's, is TUT's. The group's typedefs, its test_group + object and a class derived from test_group only to name it + go. + assertions ensure(c) -> REQUIRE(c); ensure(msg, c) -> + UNSCOPED_INFO(msg) then REQUIRE(c), braced where the call was + a lone statement of an if, else or loop; ensure_not -> + REQUIRE_FALSE; ensure_equals -> REQUIRE(a == b), except + where both values are LLSD (REQUIRE_THAT with + ll_test::LLSDEquals, the deep compare TUT's overload made) + or LLURI (their asString()s, as TUT compared them); + ensure_not_equals -> REQUIRE(a != b); ensure_distance and + ensure_approximately_equals_range -> WithinAbs; + ensure_approximately_equals -> ll_test::WithinBits, at the + precision of the overload TUT chose; ensure_contains, + _does_not_contain, _starts_with, _ends_with -> the string + matchers; ensure_memory_matches -> ll_test::MemoryMatches; + fail -> FAIL; skip -> SKIP; throw tut::failure(m) -> FAIL(m); + get_test_name() -> ll_test::currentTestName(); a catch of + tut::failure that rethrows -> ll_test::Failure. In a + destructor, where a throw would end the process, the CHECK + forms. Operands are parenthesised where a macro or an + operator would split or rebind them; a condition Catch2 + cannot decompose (&&, ||, ?:) is parenthesised whole rather + than split, so each TUT call stays one assertion. + includes lltut.h and tut.hpp give way to the Catch2 headers the file + now uses, and altestmatchers.h, altestname.h or + altestfailure.h where it uses them. + +Left as they are: #if guards (a test or call in a branch this host does not +compile is converted from its text where its types do not matter), code +under #if 0 and in comments, #include "*.cpp" lines, and every type the +production code names as a friend. + +What it cannot decide it leaves unconverted, marked // TM-CHECK: (or +/* TM-CHECK: */ inside a macro), and it exits 1 listing every mark; 2 if a +file could not be read at all. It never +drops a test or an assertion: --mapping writes one entry per test, with the +TUT-side assertion calls in its body and the Catch2 assertions now in it, +which scripts/testing/tut_parity.py checks. + +Needs the build tree's compile_commands.json (CMake writes it; the presets +turn it on) and the compiler it names, which must be clang: the AST comes +from clang++ -fsyntax-only -Xclang -ast-dump=json with the file's own flags. + +A test header #included by a test file (llcorehttp's) is read through the +file that includes it. TUT2CATCH_DUMP= writes the file's own AST +declarations there, as JSON, to see what the converter saw. A clang AST of a viewer test runs to gigabytes of +JSON; only the file's own declarations are parsed, but each file still +takes from seconds to a minute, so --jobs helps. + + python3 -I scripts/testing/tut2catch.py indra/llmath/tests/v3math_test.cpp --mapping /tmp/map.json + python3 -I scripts/testing/tut2catch.py --dry-run --jobs 6 indra/*/tests/*_test.cpp +""" +import argparse +import bisect +import concurrent.futures +import json +import os +import re +import shlex +import subprocess +import sys +import threading + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +DEFAULT_BUILD = "build-Darwin-ninja-os" +DEFAULT_CONFIG = "RelWithDebInfo" + +# TUT's free assertion functions, and the members of test_object a test calls. +TUT_FUNCTIONS = { + "ensure", "ensure_not", "ensure_equals", "ensure_not_equals", "ensure_distance", + "ensure_approximately_equals", "ensure_approximately_equals_range", "ensure_memory_matches", + "ensure_contains", "ensure_does_not_contain", "ensure_starts_with", "ensure_ends_with", + "ensure_errno", "fail", "skip", +} +TUT_MEMBERS = {"set_test_name", "get_test_name"} +# Whose conversion does not depend on the types of the arguments. +TYPE_FREE = {"ensure", "ensure_not", "fail", "skip"} +# What counts as an assertion on each side, for the parity count. +TUT_ASSERTING = TUT_FUNCTIONS +CATCH_ASSERTION = re.compile( + rb"(?:])(?:REQUIRE|REQUIRE_FALSE|REQUIRE_THAT|CHECK|CHECK_FALSE|CHECK_THAT|FAIL|FAIL_CHECK|SKIP)\s*\(") +TUT_INCLUDE = re.compile(rb'^[ \t]*#[ \t]*include[ \t]*[<"](?:[^>"]*/)?(?:lltut\.h|tut\.hpp)[>"][^\n]*\n?', re.M) + +IMPLICIT = {"ImplicitCastExpr", "MaterializeTemporaryExpr", "CXXBindTemporaryExpr", "ExprWithCleanups", + "ConstantExpr", "SubstNonTypeTemplateParmExpr", "FullExpr"} +BIN_PREC = { + ".*": 14, "->*": 14, "*": 13, "/": 13, "%": 13, "+": 12, "-": 12, "<<": 11, ">>": 11, "<=>": 10, + "<": 9, ">": 9, "<=": 9, ">=": 9, "==": 8, "!=": 8, "&": 7, "^": 6, "|": 5, "&&": 4, "||": 3, + "=": 2, "*=": 2, "/=": 2, "%=": 2, "+=": 2, "-=": 2, "<<=": 2, ">>=": 2, "&=": 2, "^=": 2, "|=": 2, + ",": 1, +} +STATEMENT_PARENTS = {"CompoundStmt", "CaseStmt", "DefaultStmt", "LabelStmt", "AttributedStmt"} +BODY_PARENTS = {"IfStmt", "ForStmt", "WhileStmt", "DoStmt", "CXXForRangeStmt"} +FUNCTION_KINDS = {"FunctionDecl", "CXXMethodDecl", "CXXConstructorDecl", "CXXDestructorDecl", + "CXXConversionDecl", "LambdaExpr"} + + +class ConvertError(Exception): + pass + + +# -------------------------------------------------------------------------- +# The source text +# -------------------------------------------------------------------------- +def mask_source(data): + """The text with comments blanked (newlines kept) and the insides of string + and character literals replaced, so a search of it finds only code. Every + offset means what it meant.""" + out = bytearray(data) + n = len(data) + i = 0 + while i < n: + c = data[i] + if c == 0x2F and i + 1 < n and data[i + 1] == 0x2F: # // + j = i + while j < n and data[j] != 0x0A: + if data[j] == 0x5C and j + 1 < n and data[j + 1] == 0x0A: + j += 2 + continue + j += 1 + for k in range(i, j): + if out[k] != 0x0A: + out[k] = 0x20 + i = j + elif c == 0x2F and i + 1 < n and data[i + 1] == 0x2A: # /* + j = data.find(b"*/", i + 2) + j = n if j < 0 else j + 2 + for k in range(i, j): + if out[k] != 0x0A: + out[k] = 0x20 + i = j + elif c == 0x22: # " + # A raw string: R"delim( ... )delim", after an optional prefix. + k = i - 1 + if k >= 0 and data[k] == ord("R"): + start = k + while start > 0 and chr(data[start - 1]) in "u8UL": + start -= 1 + if start == 0 or not (chr(data[start - 1]).isalnum() or data[start - 1] == 0x5F): + paren = data.find(b"(", i) + delim = data[i + 1:paren] + end = data.find(b")" + delim + b'"', paren) + if paren >= 0 and end >= 0 and len(delim) <= 16: + for m in range(i + 1, end + len(delim) + 1): + if out[m] != 0x0A: + out[m] = 0x5F + i = end + len(delim) + 2 + continue + j = i + 1 + while j < n and data[j] != 0x22 and data[j] != 0x0A: + if data[j] == 0x5C: + j += 1 + j += 1 + for k in range(i + 1, min(j, n)): + out[k] = 0x5F + i = j + 1 + elif c == 0x27: # ' + # A digit separator, inside a number, is no literal. + k = i - 1 + while k >= 0 and (chr(data[k]).isalnum() or data[k] in (0x5F, 0x27, 0x2E)): + k -= 1 + if k + 1 < i and chr(data[k + 1]).isdigit(): + i += 1 + continue + j = i + 1 + while j < n and data[j] != 0x27 and data[j] != 0x0A: + if data[j] == 0x5C: + j += 1 + j += 1 + for k in range(i + 1, min(j, n)): + out[k] = 0x5F + i = j + 1 + else: + i += 1 + return bytes(out) + + +def c_unescape(body): + """The value of the inside of a narrow string literal.""" + out = [] + i = 0 + simple = {"n": "\n", "t": "\t", "r": "\r", "0": "\0", "\\": "\\", '"': '"', "'": "'", "a": "\a", + "b": "\b", "f": "\f", "v": "\v", "?": "?"} + while i < len(body): + ch = body[i] + if ch == "\\" and i + 1 < len(body): + nxt = body[i + 1] + if nxt == "x": + m = re.match(r"[0-9A-Fa-f]+", body[i + 2:]) + out.append(chr(int(m.group(0), 16)) if m else "x") + i += 2 + (len(m.group(0)) if m else 0) + continue + if nxt in "01234567": + m = re.match(r"[0-7]{1,3}", body[i + 1:]) + out.append(chr(int(m.group(0), 8))) + i += 1 + len(m.group(0)) + continue + out.append(simple.get(nxt, nxt)) + i += 2 + continue + out.append(ch) + i += 1 + return "".join(out) + + +def c_escape(text): + return (text.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n").replace("\t", "\\t")) + + +class Source: + def __init__(self, path): + self.path = path + with open(path, "rb") as f: + self.data = f.read() + self.masked = mask_source(self.data) + self.line_starts = [0] + [m.end() for m in re.finditer(rb"\n", self.data)] + self.directives = [] # (start, end, keyword, rest) of each preprocessor line + for m in re.finditer(rb"^[ \t]*#[ \t]*(\w+)", self.masked, re.M): + end = m.end() + while True: + nl = self.masked.find(b"\n", end) + if nl < 0: + nl = len(self.masked) + break + if self.masked[nl - 1:nl] == b"\\": + end = nl + 1 + continue + break + self.directives.append((m.start(), nl, m.group(1).decode(), self.masked[m.end():nl].decode("latin-1"))) + self.directive_starts = [d[0] for d in self.directives] + self.regions = [] # (start, end, "dead" | "inactive") of conditional branches + + def line_of(self, off): + return bisect.bisect_right(self.line_starts, off) + + def line_start(self, off): + return self.line_starts[self.line_of(off) - 1] + + def line_end(self, off): + nl = self.data.find(b"\n", off) + return len(self.data) if nl < 0 else nl + + def indent_at(self, off): + start = self.line_start(off) + m = re.match(rb"[ \t]*", self.data[start:]) + return self.data[start:start + m.end()] + + def starts_line(self, off): + return self.data[self.line_start(off):off].strip() == b"" + + def directive_at(self, off): + i = bisect.bisect_right(self.directive_starts, off) - 1 + if i >= 0 and self.directives[i][0] <= off < self.directives[i][1]: + return self.directives[i] + return None + + def skip_space(self, off): + while off < len(self.masked) and self.masked[off] in b" \t\r\n": + off += 1 + return off + + def skip_space_back(self, off): + while off > 0 and self.masked[off - 1] in b" \t\r\n": + off -= 1 + return off + + def matching(self, off): + """The offset of the bracket that closes the one at off.""" + pairs = {ord("("): ord(")"), ord("["): ord("]"), ord("{"): ord("}")} + stack = [] + i = off + while i < len(self.masked): + c = self.masked[i] + if c in pairs: + stack.append(pairs[c]) + elif c in (0x29, 0x5D, 0x7D): + if not stack or stack.pop() != c: + return -1 + if not stack: + return i + i += 1 + return -1 + + def state(self, off): + """dead (under #if 0), inactive (a branch this host does not compile) or active.""" + result = "active" + for start, end, what in self.regions: + if start <= off < end: + if what == "dead": + return "dead" + result = "inactive" + return result + + def find_conditionals(self, active_lines): + """Each #if branch's extent, and whether it is dead or not compiled here.""" + stack = [] + branches = [] # (start, end, keyword, condition, nested ranges) + for start, end, kw, rest in self.directives: + if kw in ("if", "ifdef", "ifndef"): + stack.append({"branches": [], "open": (end, kw, rest.strip()), "dead_before": False}) + elif kw in ("elif", "elifdef", "elifndef", "else", "endif") and stack: + top = stack[-1] + bstart, bkw, brest = top["open"] + top["branches"].append((bstart, start, bkw, brest)) + if kw == "endif": + stack.pop() + branches.extend(top["branches"]) + else: + top["open"] = (end, kw, rest.strip()) + for bstart, bend, kw, cond in branches: + if kw in ("if", "elif") and re.fullmatch(r"\(?\s*(0|false)\s*\)?", cond): + self.regions.append((bstart, bend, "dead")) + continue + # A branch is compiled if a line of its own code reached the + # preprocessor's output. Lines of nested branches are theirs. + nested = [(s, e) for s, e, _, _ in branches if bstart < s and e <= bend] + has_code = False + compiled = False + for line in range(self.line_of(bstart) + 1, self.line_of(max(bstart, bend - 1)) + 1): + ls = self.line_starts[line - 1] + le = self.line_end(ls) + if any(s <= ls < e for s, e in nested) or self.directive_at(ls): + continue + if self.masked[ls:le].strip(): + has_code = True + if line in active_lines: + compiled = True + break + if has_code and not compiled: + self.regions.append((bstart, bend, "inactive")) + + +# -------------------------------------------------------------------------- +# The compile database and clang +# -------------------------------------------------------------------------- +class CompileDatabase: + """Each file's compile command in one configuration. A file compiled by + more than one target -- a TUT test and, mid-migration, its Catch2 + conversion -- is read as its TUT target compiles it, the one with TUT's + headers on its path.""" + + def __init__(self, build_dir, config): + path = os.path.join(build_dir, "compile_commands.json") + if not os.path.exists(path): + raise ConvertError(f"no {path}: configure the build tree first") + with open(path) as f: + entries = json.load(f) + self.entries = {} + tut = os.path.join("externals", "tut") + " " + for e in entries: + key = os.path.normpath(os.path.join(e["directory"], e["file"])) + command = e.get("command") or shlex.join(e["arguments"]) + # Ranked on the raw text: splitting thousands of commands costs + # seconds, and one is all a file needs. + rank = (f'-DCMAKE_INTDIR=\\"{config}\\"' in command or f'-DCMAKE_INTDIR="{config}"' in command, + tut in command or command.rstrip().endswith(tut.strip())) + have = self.entries.get(key) + if have is None or rank > have[2]: + self.entries[key] = (e["directory"], command, rank) + + def command(self, path): + """(directory, clang arguments, the file compiled): a header of tests, + such as llcorehttp's, is read through the file that includes it.""" + path = os.path.normpath(path) + entry = self.entries.get(path) + compiled = path + if entry is None: + folder = os.path.dirname(path) + include = re.compile(r'^\s*#\s*include\s*"(?:[^"]*/)?' + re.escape(os.path.basename(path)) + '"', re.M) + for candidate in sorted(self.entries): + if os.path.dirname(candidate) != folder or candidate == path: + continue + try: + with open(candidate, encoding="utf-8", errors="replace") as f: + if include.search(f.read()): + entry, compiled = self.entries[candidate], candidate + break + except OSError: + continue + if entry is None: + raise ConvertError(f"{path} is in no compile command: is it built by any target?") + directory, args = entry[0], shlex.split(entry[1]) + out = ["clang++"] + skip = False + for a in args[1:]: + if skip: + skip = False + continue + if a in ("-o", "-c", "-MF", "-MT", "-MQ"): + skip = True + continue + if a in ("-MD", "-MMD", "-fcolor-diagnostics") or a.startswith("-fdiagnostics-color"): + continue + if os.path.normpath(os.path.join(directory, a)) == compiled: + continue + out.append(a) + return directory, out, compiled + + +def split_ast_items(stream): + """The AST's top-level declarations, as JSON bytes, without parsing the + whole dump: they are the items of the translation unit's "inner" array, + and clang writes each between lines indented four spaces.""" + sep = b"\n },\n {\n" + buf = b"" + started = False + while True: + chunk = stream.read(1 << 24) + if not chunk: + break + buf += chunk + if not started: + k = buf.find(b' "inner": [\n {\n') + if k < 0: + continue + buf = buf[k + len(b' "inner": [\n'):] + started = True + pos = 0 + while True: + k = buf.find(sep, pos) + if k < 0: + break + yield buf[pos:k + 6] + pos = k + 7 + buf = buf[pos:] + if started: + k = buf.rfind(b"\n }\n") + if k >= 0: + yield buf[:k + 6] + + +FILE_ATTR = re.compile(rb'"file": "((?:[^"\\]|\\.)*)"') + + +def last_file(text, current): + """The file of the last location written in text: clang writes a + location's file only when it changes, so this is the file in force after + it. An includedFrom's file is not a location's.""" + end = len(text) + while True: + i = text.rfind(b'"file": "', 0, end) + if i < 0: + return current + j = i + while j > 0 and text[j - 1] in b" \t\r\n": + j -= 1 + if text.endswith(b'"includedFrom": {', 0, j): + end = i + continue + return json.loads(b'"' + FILE_ATTR.match(text, i).group(1) + b'"') + + +def item_files(text, current): + """The files of a top-level item's own location: spelling and expansion.""" + i = text.find(b'"loc": {') + if i < 0: + return set() + j = text.find(b'"range": ', i) + head = text[i + len(b'"loc": '):j if j > 0 else i + 4096].decode("utf-8", "replace") + loc, _ = json.JSONDecoder().raw_decode(head) + files = set() + state = [current] + + def visit(obj): + if "offset" in obj: + state[0] = obj.get("file", state[0]) + files.add(state[0]) + for key in ("spellingLoc", "expansionLoc"): + if key in obj: + visit(obj[key]) + visit(loc) + return files + + +def annotate(obj, state): + """Gives every location in obj its file, as "_f", walking in the order + clang wrote them.""" + if isinstance(obj, dict): + if "offset" in obj: + state[0] = obj.get("file", state[0]) + obj["_f"] = os.path.normpath(state[0]) if state[0] else None + for key, value in obj.items(): + if key == "includedFrom": + continue + if isinstance(value, (dict, list)): + annotate(value, state) + elif isinstance(obj, list): + for value in obj: + annotate(value, state) + + +TUT_DECL = re.compile( + rb'"id": "(0x[0-9a-f]+)",\s*"kind": "(?:FunctionDecl|CXXMethodDecl|FunctionTemplateDecl)",' + rb'(?:(?!"kind": ).)*?"name": "(\w+)"', re.S) + + +def is_tut_header(path): + path = os.path.normpath(path) + return path.endswith(os.path.join("test", "lltut.h")) or (os.sep + os.path.join("tut", "tut")) in path + + +def run_clang(db, path): + """The main file's top-level declarations, parsed, the ids of TUT's own + functions, and the lines of the main file the preprocessor kept.""" + directory, args, compiled = db.command(path) + main = os.path.normpath(path) + preprocess = subprocess.Popen(args + ["-E", compiled], cwd=directory, stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + dump = subprocess.Popen(args + ["-fsyntax-only", "-Xclang", "-ast-dump=json", compiled], cwd=directory, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + errors = [] + reader = threading.Thread(target=lambda: errors.append(dump.stderr.read())) + reader.start() + items = [] + tut_ids = {} + current = None + for text in split_ast_items(dump.stdout): + files = {os.path.normpath(f) for f in item_files(text, current) if f} + if main in files: + items.append((current, json.loads(text))) + elif any(is_tut_header(f) for f in files): + for m in TUT_DECL.finditer(text): + name = m.group(2).decode() + if name in TUT_FUNCTIONS or name in TUT_MEMBERS: + tut_ids[m.group(1).decode()] = name + current = last_file(text, current) + dump.wait() + reader.join() + if dump.returncode != 0: + stderr = b"".join(errors).decode("utf-8", "replace") + preprocess.kill() + preprocess.wait() + raise ConvertError(f"clang could not parse {compiled}:\n{stderr[-4000:]}") + active_lines = set() + marker = re.compile(rb'^# (\d+) "((?:[^"\\]|\\.)*)"') + line = 0 + in_main = False + for raw in preprocess.stdout.read().split(b"\n"): + m = marker.match(raw) + if m: + line = int(m.group(1)) + in_main = os.path.normpath(os.path.join(directory, m.group(2).decode("utf-8", "replace"))) == main + continue + if in_main and raw.strip(): + active_lines.add(line) + line += 1 + preprocess.wait() + for current_file, item in items: + annotate(item, [current_file]) + return [item for _, item in items], tut_ids, active_lines + + +def demangle(names): + if not names: + return {} + for tool in (["xcrun", "c++filt"], ["c++filt"], ["llvm-cxxfilt"]): + try: + out = subprocess.run(tool, input="\n".join(names), capture_output=True, text=True, check=True) + return dict(zip(names, out.stdout.splitlines())) + except (OSError, subprocess.CalledProcessError): + continue + raise ConvertError("no c++filt to demangle with") + + +# -------------------------------------------------------------------------- +# Reading the AST +# -------------------------------------------------------------------------- +def skip_implicit(node): + while True: + kind = node.get("kind") + inner = [c for c in node.get("inner", []) if isinstance(c, dict) and "kind" in c] + if kind in IMPLICIT and inner: + node = inner[0] + continue + if kind in ("CXXConstructExpr", "CXXMemberCallExpr") and len(inner) == 1 and \ + node.get("range") and inner[0].get("range") and \ + loc_key(node["range"]["begin"]) == loc_key(inner[0]["range"]["begin"]) and \ + loc_key(node["range"]["end"]) == loc_key(inner[0]["range"]["end"]): + node = inner[0] + continue + return node + + +def loc_key(loc): + if "spellingLoc" in loc: + loc = loc["spellingLoc"] + return (loc.get("_f"), loc.get("offset")) + + +def value_type(node): + t = node.get("type", {}) + text = t.get("desugaredQualType") or t.get("qualType") or "" + text = re.sub(r"\b(const|volatile)\b", "", text).replace("&", "") + return re.sub(r"\s+", " ", text).strip() + + +def precedence(node): + node = skip_implicit(node) + kind = node.get("kind") + if kind in ("BinaryOperator", "CompoundAssignOperator"): + return BIN_PREC.get(node.get("opcode"), 16) + if kind in ("ConditionalOperator", "BinaryConditionalOperator", "CXXThrowExpr"): + return 2 + if kind == "CXXRewrittenBinaryOperator": + return 8 + if kind == "CXXOperatorCallExpr": + inner = [c for c in node.get("inner", []) if "kind" in c] + callee = skip_implicit(inner[0]) if inner else {} + name = callee.get("referencedDecl", {}).get("name", "") + op = name[len("operator"):] if name.startswith("operator") else "" + if op in ("()", "[]", "->", "++", "--"): + return 16 + if len(inner) == 2: + return 15 + return BIN_PREC.get(op, 16) + if kind in ("UnaryOperator", "CStyleCastExpr", "CXXNewExpr", "CXXDeleteExpr", "UnaryExprOrTypeTraitExpr"): + return 15 + return 16 + + +def headers_for(name, kind): + """What a converted call needs included, beyond the test macros.""" + if kind == "equals-llsd" or name in ("ensure_approximately_equals", "ensure_memory_matches"): + return {'"altestmatchers.h"'} + if name in ("ensure_distance", "ensure_approximately_equals_range"): + return {""} + if name in ("ensure_contains", "ensure_does_not_contain", "ensure_starts_with", "ensure_ends_with"): + return {""} + return set() + + +def decomposable(node): + """Whether REQUIRE(node) compiles as Catch2 decomposes it: not &&, ||, + ?:, an assignment or a comma at the top, and one comparison at most.""" + node = skip_implicit(node) + p = precedence(node) + if p <= 4: + return False + if 5 <= p <= 9: + inner = [c for c in node.get("inner", []) if "kind" in c] + if node.get("kind") == "CXXOperatorCallExpr": + inner = inner[1:] + if inner and precedence(inner[0]) <= 9: + return False + return True + + +def is_null_message(node): + node = skip_implicit(node) + if node.get("kind") in ("GNUNullExpr", "CXXNullPtrLiteralExpr"): + return True + if node.get("kind") == "IntegerLiteral" and node.get("value") == "0": + return True + if node.get("kind") == "StringLiteral" and node.get("value") == '""': + return True + return False + + +class Location: + """Where a node's text is in the main file, if it is. how says what the + text is: file (plain), macro-arg (written in a macro's argument, so in + place), macro-body (written in a #define of the file) or foreign (made + by a macro defined elsewhere: only where it was used is known).""" + + def __init__(self, source, loc, main): + self.offset = None + self.length = 0 + self.how = None + self.expansion = None # (offset, length) where a macro was used + if not loc: + return + if "spellingLoc" in loc: + spelling, expansion = loc["spellingLoc"], loc["expansionLoc"] + if expansion.get("_f") == main and "offset" in expansion: + self.expansion = (expansion["offset"], expansion.get("tokLen", 0)) + if spelling.get("_f") == main and "offset" in spelling: + self.offset, self.length = spelling["offset"], spelling.get("tokLen", 0) + self.how = "macro-body" if source.directive_at(self.offset) else "macro-arg" + elif self.expansion: + self.offset, self.length = self.expansion + self.how = "foreign" + return + if loc.get("_f") == main and "offset" in loc: + self.offset, self.length = loc["offset"], loc.get("tokLen", 0) + self.how = "file" + + def in_text(self): + """Where the node starts in the text as written: a macro used in the + file counts from where it was used.""" + if self.how == "macro-body" and self.expansion: + return self.expansion[0] + return self.offset + + +# -------------------------------------------------------------------------- +# What the file holds +# -------------------------------------------------------------------------- +class Test: + def __init__(self): + self.n = None + self.data = None # the fixture, as clang names it + self.fixture = None # as the converted file spells it + self.group = None + self.start = None # template<> ... + self.header_end = None # after test() + self.body_start = None # { + self.body_end = None # after } + self.namespaces = () + self.names = [] # set_test_name calls: (call, literal text or None, top-level?) + self.inactive = False + self.macro = False + self.node = None + self.catch2_name = None + self.tut_name = "" + + +class Group: + def __init__(self, name, data, limit): + self.name = name + self.data = data + self.limit = limit + self.tests = [] + + +class Plan: + """A piece of the file replaced: [start, end) by what text() renders.""" + + def __init__(self, start, end, render): + self.start = start + self.end = end + self.render = render + + def text(self, conv): + return self.render(conv, self) + + +class Converter: + def __init__(self, source, items, tut_ids, active_lines, demangled): + self.src = source + self.main = os.path.normpath(source.path) + self.items = items + self.tut_ids = tut_ids + self.demangled = demangled + self.plans = [] + self.checks = [] # (offset, why) + self.tests = [] + self.groups = {} # data -> Group + self.typedefs = {} # name -> data type of a test_object / test_group typedef + self.removed_typedef_ids = set() + self.removals = [] # (start, end) + self.calls = {} # offset -> [(kind info)] + self.handled_nodes = set() + self.headers = {""} + self.notes = [] + self.counted = {} # offset of a TUT assertion call -> True + self.derived_groups = {} # class derived from test_group -> (data, limit, name) + self.removed_spans = [] + self.slot_at = {} # chunk start -> the plan that puts tests there + source.find_conditionals(active_lines) + + # ---- helpers ------------------------------------------------------- + def check(self, offset, why): + self.checks.append((offset, why)) + + def text(self, start, end): + return self.src.data[start:end].decode("utf-8", "surrogateescape") + + def loc(self, loc): + return Location(self.src, loc, self.main) + + def node_range(self, node): + """[start, end) of a node in the main file, or None.""" + rng = node.get("range") + if not rng: + return None + begin, end = self.loc(rng.get("begin")), self.loc(rng.get("end")) + if begin.offset is None or end.offset is None: + return None + return begin.offset, end.offset + end.length, begin.how, end.how + + # ---- the walk ------------------------------------------------------ + def analyse(self): + for item in self.items: + self.walk(item, []) + + def walk(self, node, parents): + kind = node.get("kind") + if kind == "CXXMethodDecl" and node.get("name") == "test" and "test_object" in node.get("mangledName", ""): + self.on_test(node, parents) + elif kind in ("TypedefDecl", "TypeAliasDecl"): + self.on_typedef(node, parents) + elif kind == "VarDecl": + self.on_var(node, parents) + elif kind == "CXXRecordDecl": + self.on_record(node, parents) + elif kind in ("CallExpr", "CXXMemberCallExpr"): + self.on_call(node, parents) + elif kind == "CXXThrowExpr": + self.on_throw(node, parents) + elif kind == "CXXCatchStmt": + self.on_catch(node, parents) + elif kind in ("DeclRefExpr", "MemberExpr", "UnresolvedLookupExpr") and id(node) not in self.handled_nodes: + self.on_reference(node, parents) + for child in node.get("inner", []): + if isinstance(child, dict): + self.walk(child, parents + [node]) + + def namespaces_of(self, parents): + return tuple(p.get("name", "") for p in parents if p.get("kind") == "NamespaceDecl") + + # ---- tests --------------------------------------------------------- + def on_test(self, node, parents): + body = next((c for c in node.get("inner", []) if c.get("kind") == "CompoundStmt"), None) + if body is None: + return + n = next((c.get("value") for c in node.get("inner", []) if c.get("kind") == "TemplateArgument"), None) + test = Test() + test.node = node + test.n = int(n) if n is not None else None + test.mangled = node.get("mangledName") + rng = self.node_range(node) + brng = self.node_range(body) + if rng is None or brng is None or rng[2] != "file" or brng[2] != "file": + test.macro = True + where = self.loc(node.get("range", {}).get("begin")).offset or self.loc(node.get("loc")).offset or 0 + test.start = where + self.check(where, f"test<{test.n}> is made by a macro: convert it by hand") + self.tests.append(test) + return + test.start, test.body_start, test.body_end = rng[0], brng[0], brng[1] + test.namespaces = self.namespaces_of(parents) + m = re.compile(rb"test\s*<\s*\d+\s*>\s*\(\s*\)").search(self.src.masked, test.start, test.body_start) + if not m: + raise ConvertError(f"{self.src.path}: cannot find test<{test.n}>() in its definition") + test.header_end = m.end() + self.tests.append(test) + + def on_typedef(self, node, parents): + t = node.get("type", {}) + full = t.get("desugaredQualType") or t.get("qualType") or "" + m = re.match(r"^(?:tut::)?test_(object|group)<(.*)>$", full) + if not m: + return + args = split_template_args(m.group(2)) + self.typedefs[node.get("name")] = args[0] + self.removed_typedef_ids.add(node.get("id")) + self.remove_decl(node) + + def on_var(self, node, parents): + t = node.get("type", {}) + full = t.get("desugaredQualType") or t.get("qualType") or "" + m = re.match(r"^(?:tut::)?test_group<(.*)>$", full) + derived = self.derived_groups.get(full) or self.derived_groups.get(full.rpartition("::")[2]) + if not m and derived is None: + return + if m: + args = split_template_args(m.group(1)) + data, limit = args[0], int(args[1]) if len(args) > 1 else 50 + literal = find_kind(node, "StringLiteral") + name = literal.get("value") if literal else None + else: + data, limit, name = derived + where = self.node_range(node)[0] if self.node_range(node) else 0 + if not name or not name.startswith('"'): + self.check(where, "a test_group whose name is not a string literal") + return + name = c_unescape(name[1:-1]) + if data in self.groups: + self.check(where, f"two groups run the tests of {data}") + self.groups[data] = Group(name, data, limit) + self.remove_decl(node) + + def on_record(self, node, parents): + """A class derived from test_group only to name the group in its + constructor, as llvocache's is.""" + for base in node.get("bases", []): + t = base.get("type", {}) + full = t.get("desugaredQualType") or t.get("qualType") or "" + m = re.match(r"^(?:tut::)?test_group<(.*)>$", full) + if not m: + continue + args = split_template_args(m.group(1)) + data = args[0] if "::" in args[0] else "tut::" + args[0] + literal = find_kind(node, "StringLiteral") + name = literal.get("value") if literal else None + qualified = "::".join([n for n in self.namespaces_of(parents) if n] + [node.get("name", "")]) + for key in (qualified, node.get("name", "")): + self.derived_groups[key] = (data, int(args[1]) if len(args) > 1 else 50, name) + self.remove_decl(node) + + def remove_decl(self, node): + rng = self.node_range(node) + if rng is None: + return + start, end = rng[0], rng[1] + after = self.src.skip_space(end) + if self.src.masked[after:after + 1] == b";": + end = after + 1 + self.removals.append((start, end, "decl")) + self.handled_nodes.add(id(node)) + for child in iter_nodes(node): + self.handled_nodes.add(id(child)) + + # ---- calls --------------------------------------------------------- + def callee_of(self, node): + inner = [c for c in node.get("inner", []) if isinstance(c, dict) and "kind" in c] + if not inner: + return None, None, [] + callee = inner[0] + stripped = callee + while stripped.get("kind") in ("ImplicitCastExpr", "ParenExpr") and stripped.get("inner"): + stripped = stripped["inner"][0] + args = [a for a in inner[1:] if a.get("kind") != "CXXDefaultArgExpr"] + if stripped.get("kind") == "DeclRefExpr": + ref = stripped.get("referencedDecl", {}) + if ref.get("id") in self.tut_ids and ref.get("name") in TUT_FUNCTIONS: + return ref.get("name"), stripped, args + elif stripped.get("kind") == "MemberExpr": + if stripped.get("referencedMemberDecl") in self.tut_ids and stripped.get("name") in TUT_MEMBERS: + return stripped.get("name"), stripped, args + elif stripped.get("kind") == "UnresolvedLookupExpr": + lookups = stripped.get("lookups", []) + if any(l.get("id") in self.tut_ids for l in lookups) and stripped.get("name") in TUT_FUNCTIONS: + return stripped.get("name"), stripped, args + elif stripped.get("kind") in ("CXXDependentScopeMemberExpr", "UnresolvedMemberExpr"): + if stripped.get("member", stripped.get("name")) in TUT_MEMBERS: + return stripped.get("member", stripped.get("name")), stripped, args + return None, None, [] + + def on_call(self, node, parents): + name, callee, args = self.callee_of(node) + if name is None: + return + self.handled_nodes.add(id(callee)) + for sub in iter_nodes(callee): + self.handled_nodes.add(id(sub)) + rng = self.node_range(node) + begin = self.loc(node.get("range", {}).get("begin")) + if rng is None or begin.how is None: + where = begin.offset if begin.offset is not None else 0 + self.check(where, f"{name}() comes from a macro defined elsewhere") + return + record = {"node": node, "name": name, "args": args, "parents": parents, "range": rng, + "how": begin.how, "callee": callee} + self.calls.setdefault(rng[0], []).append(record) + + def on_throw(self, node, parents): + inner = [c for c in node.get("inner", []) if "kind" in c] + if not inner: + return + value = skip_implicit(inner[0]) + if value_type(value) not in ("tut::failure", "failure"): + return + rng = self.node_range(node) + if rng is None: + return + for sub in iter_nodes(node): + self.handled_nodes.add(id(sub)) + if value.get("kind") not in ("CXXConstructExpr", "CXXFunctionalCastExpr", "CXXTemporaryObjectExpr"): + self.calls.setdefault(rng[0], []).append({"node": node, "name": "throw-held", "args": [], + "parents": parents, "range": rng, "how": "file", + "callee": None}) + return + # The message: the first node under the construction that starts + # after its opening parenthesis. + open_paren = self.src.masked.find(b"(", rng[0], rng[1]) + message = None + for sub in iter_nodes(value): + loc = self.loc(sub.get("range", {}).get("begin")) + if sub is not value and loc.offset is not None and loc.offset > open_paren: + message = sub + break + self.calls.setdefault(rng[0], []).append({"node": node, "name": "throw", + "args": [message] if message else [], + "parents": parents, "range": rng, "how": "file", + "callee": None}) + + def on_catch(self, node, parents): + inner = [c for c in node.get("inner", []) if "kind" in c] + if not inner or inner[0].get("kind") != "VarDecl": + return + decl = inner[0] + caught = re.sub(r"^tut::", "", value_type(decl)) + if caught not in ("failure", "tut_error", "skip_failure"): + return + rng = self.node_range(decl) + if rng is None: + return + body = inner[1] if len(inner) > 1 else {} + rethrows = any(n.get("kind") == "CXXThrowExpr" and not [c for c in n.get("inner", []) if "kind" in c] + for n in iter_nodes(body)) + for sub in iter_nodes(decl): + self.handled_nodes.add(id(sub)) + if caught != "failure": + self.check(rng[0], f"catches tut::{caught}, which Catch2 has no counterpart of") + return + if decl.get("name") and (decl.get("isReferenced") or decl.get("isUsed")): + self.check(rng[0], "uses the tut::failure it catches; Catch2's failure carries no message") + return + if not rethrows: + self.check(rng[0], "catches a tut::failure without rethrowing it; Catch2 has recorded the failure already") + return + m = re.compile(rb"(?:\btut\s*::\s*)?\bfailure\b").search(self.src.masked, rng[0], rng[1]) + if not m: + self.check(rng[0], "cannot find tut::failure in the catch") + return + self.headers.add('"altestfailure.h"') + self.plans.append(Plan(m.start(), m.end(), lambda conv, plan: "ll_test::Failure")) + + def on_reference(self, node, parents): + kind = node.get("kind") + ref = None + if kind == "DeclRefExpr": + ref = node.get("referencedDecl", {}).get("id") + elif kind == "MemberExpr": + ref = node.get("referencedMemberDecl") + elif kind == "UnresolvedLookupExpr": + ids = [l.get("id") for l in node.get("lookups", [])] + ref = next((i for i in ids if i in self.tut_ids), None) + if ref in self.tut_ids: + rng = self.node_range(node) + if rng: + self.check(rng[0], f"{self.tut_ids[ref]} is used other than by a call") + + # ---- deciding each call ------------------------------------------ + def resolve_calls(self): + for offset, records in sorted(self.calls.items()): + first = records[0] + name = first["name"] + if name in TUT_ASSERTING or name == "throw": + self.counted[offset] = True + how = first["how"] + if how == "macro-body": + self.convert_in_macro(offset, records) + continue + if how == "foreign": + self.check(offset, f"{name}() comes from a macro defined elsewhere") + continue + kinds = {self.conversion_kind(r) for r in records} + if len(kinds) > 1 and "dependent" in kinds: + # a template's own definition beside its instantiations + kinds.discard("dependent") + if len(kinds) > 1: + self.check(offset, f"{name}() means different things in different instantiations: " + + ", ".join(sorted(kinds))) + continue + kind = kinds.pop() + # an instantiation's record, where the types are known + first = next((r for r in records if self.conversion_kind(r) == kind), first) + self.convert_call(first, kind) + + def conversion_kind(self, record): + """How a call converts, from the types clang gave its arguments.""" + name, args = record["name"], record["args"] + callee = record["callee"] or {} + if callee.get("kind") in ("UnresolvedLookupExpr", "CXXDependentScopeMemberExpr", "UnresolvedMemberExpr") \ + and name not in TYPE_FREE and name not in TUT_MEMBERS: + return "dependent" + if name == "ensure_equals" and len(args) in (2, 3): + types = [value_type(a) for a in args[-2:]] + if types[0] == types[1] == "LLSD": + return "equals-llsd" + if types[0] == types[1] == "LLURI": + return "equals-uri" + return "equals" + if name == "ensure_approximately_equals": + return "bits-" + (self.parameter_float(record) or "?") + return name + + def parameter_float(self, record): + callee = record["callee"] or {} + qual = callee.get("referencedDecl", {}).get("type", {}).get("qualType", "") + m = re.match(r"^void \((.*)\)$", qual) + if not m: + return None + params = [p.strip() for p in split_template_args(m.group(1))] + expected = params[-2] if len(params) >= 3 else "" + return {"F32": "F32", "float": "F32", "F64": "F64", "double": "F64"}.get(expected) + + def context(self, record): + """block (a statement among others), body (the lone statement of an + if, else or loop) or expr, with the parent that decided it.""" + child = record["node"] + for parent in reversed(record["parents"]): + kind = parent.get("kind") + if kind in IMPLICIT: + child = parent + continue + if kind in STATEMENT_PARENTS: + return "block", parent + if kind in BODY_PARENTS: + inner = [c for c in parent.get("inner", []) if "kind" in c] + if kind == "IfStmt" and inner and child is not inner[0]: + return "body", parent + if kind == "DoStmt" and inner and child is inner[0]: + return "body", parent + if kind not in ("IfStmt", "DoStmt") and inner and child is inner[-1]: + return "body", parent + return "expr", parent + return "expr", None + + def in_destructor(self, record): + for parent in reversed(record["parents"]): + if parent.get("kind") == "LambdaExpr": + return False + if parent.get("kind") in FUNCTION_KINDS: + return parent.get("kind") == "CXXDestructorDecl" + return False + + def arg_ranges(self, record): + """[start, end) of each argument.""" + start, end = record["range"][0], record["range"][1] + if self.src.data[end - 1:end] != b")": + return None + if record["name"] == "throw": + open_paren = self.src.masked.find(b"(", start, end) + return [(self.src.skip_space(open_paren + 1), self.src.skip_space_back(end - 1))] + begins = [] + for arg in record["args"]: + begin = self.loc(arg.get("range", {}).get("begin")).in_text() + if begin is None or not (start < begin < end): + return None + begins.append(begin) + if begins != sorted(begins): + return None + ranges = [] + for i, b in enumerate(begins): + if i + 1 < len(begins): + stop = self.src.skip_space_back(begins[i + 1]) + if self.src.masked[stop - 1:stop] != b",": + return None + stop = self.src.skip_space_back(stop - 1) + else: + stop = self.src.skip_space_back(end - 1) + if stop <= b: + return None + ranges.append((b, stop)) + return ranges + + def separator(self, ranges, i): + """What stood between argument i and i + 1, without its comma: the line + break and comments, or a space.""" + text = self.text(ranges[i][1], ranges[i + 1][0]) + comma = self.src.masked.find(b",", ranges[i][1], ranges[i + 1][0]) - ranges[i][1] + text = text[:comma] + text[comma + 1:] + if text.strip() or "\n" in text: + return text if text[:1].isspace() else " " + text + return " " + + def operand(self, ranges, i, node, wrap_at, guard=False): + """Argument i's text, parenthesised if its operator binds no tighter + than wrap_at, or, with guard, if a macro taking it would split it.""" + text = self.render(ranges[i][0], ranges[i][1]) + if precedence(node) <= wrap_at or (guard and needs_macro_guard(self.src.masked[ranges[i][0]:ranges[i][1]])): + return "(" + text + ")" + return text + + def condition(self, span, node): + """A condition's text for REQUIRE, parenthesised where Catch2 cannot + decompose it (&&, ||, ?:, an assignment, a comparison of a + comparison). Parentheses the test wrote stay: what they hold is + evaluated as TUT evaluated it, and its operands are not printed, so a + type with no operator<< linked still links.""" + text = self.render(*span) + return text if decomposable(node) else "(" + text + ")" + + def convert_call(self, record, kind): + name = record["name"] + start, end = record["range"][0], record["range"][1] + args = record["args"] + if kind == "dependent": + self.check(start, f"{name}() in a template, and no instantiation shows its types") + return + if name == "get_test_name": + self.headers.add('"altestname.h"') + self.plans.append(Plan(start, end, lambda conv, plan: "ll_test::currentTestName()")) + return + if name == "set_test_name": + return # folded into the case's name by name_tests + if name == "ensure_errno": + self.check(start, "ensure_errno has no Catch2 counterpart") + return + if name == "throw-held": + self.check(start, "throws a tut::failure it holds, which has no Catch2 counterpart") + return + context, _ = self.context(record) + if context == "expr": + self.check(start, f"{name}() is part of an expression, where a Catch2 assertion, a statement, cannot go") + return + ranges = self.arg_ranges(record) if args or name == "throw" else [] + if ranges is None or (name == "throw" and not args): + self.check(start, f"cannot place the arguments of {name}(); a macro around them?") + return + check_form = self.in_destructor(record) + if check_form and name in ("fail", "skip", "throw"): + self.check(start, f"{name}() in a destructor would throw out of it") + return + if kind == "bits-?": + self.check(start, "cannot tell which ensure_approximately_equals overload was called") + return + build = self.builder(name, kind, record, ranges, "CHECK" if check_form else "REQUIRE") + if build is not None: + self.headers.update(headers_for(name, kind)) + if build is None: + self.check(start, f"{name}() with {len(args)} arguments is not a form this converter knows") + return + stmt_end = end + if context == "body": + after = self.src.skip_space(end) + if self.src.masked[after:after + 1] != b";": + self.check(start, f"{name}() as a lone statement is not followed by its semicolon") + return + stmt_end = after + 1 + + def render(conv, plan, build=build, context=context, start=start): + statements = build() + if context == "body": + if len(statements) > 1: + return "{ " + "; ".join(statements) + "; }" + return statements[0] + ";" + if conv.src.starts_line(start): + return (";\n" + conv.src.indent_at(start).decode()).join(statements) + return "; ".join(statements) + self.plans.append(Plan(start, stmt_end, render)) + + def builder(self, name, kind, record, ranges, req): + """A function giving the statements a call becomes, or None.""" + args = record["args"] + n = len(args) + + def message(i): + if is_null_message(args[i]): + return [] + text = self.render(*ranges[i]) + if precedence(args[i]) <= 11 or needs_macro_guard(self.src.masked[ranges[i][0]:ranges[i][1]]): + text = "(" + text + ")" + return [f"UNSCOPED_INFO({text})"] + + def streamed(i): + text = self.render(*ranges[i]) + return "(" + text + ")" if precedence(args[i]) <= 11 else text + + if name in ("ensure", "ensure_not"): + if n not in (1, 2): + return None + macro = req + ("_FALSE" if name == "ensure_not" else "") + + def build(): + return (message(0) if n == 2 else []) + [f"{macro}({self.condition(ranges[n - 1], args[n - 1])})"] + return build + if name in ("fail", "skip", "throw"): + if n != 1: + return None + macro = "SKIP" if name == "skip" else "FAIL" + return lambda: [f"{macro}({streamed(0)})"] + if name in ("ensure_equals", "ensure_not_equals"): + if n not in (2, 3): + return None + a, e = n - 2, n - 1 + + def build(): + msg = message(0) if n == 3 else [] + sep = self.separator(ranges, a) + if kind == "equals-llsd": + actual = self.operand(ranges, a, args[a], 1, guard=True) + expected = self.operand(ranges, e, args[e], 1) + return msg + [f"{req}_THAT({actual},{sep}ll_test::LLSDEquals({expected}))"] + if kind == "equals-uri": + actual = self.operand(ranges, a, args[a], 15) + expected = self.operand(ranges, e, args[e], 15) + return msg + [f"{req}({actual}.asString() =={sep}{expected}.asString())"] + op = "==" if name == "ensure_equals" else "!=" + actual = self.operand(ranges, a, args[a], 9) + expected = self.operand(ranges, e, args[e], 9) + return msg + [f"{req}({actual} {op}{sep}{expected})"] + return build + if name in ("ensure_distance", "ensure_approximately_equals_range"): + if n not in ((4,) if name == "ensure_approximately_equals_range" else (3, 4)): + return None + a = n - 3 + + def build(): + msg = message(0) if n == 4 else [] + actual = self.operand(ranges, a, args[a], 1, guard=True) + expected = self.operand(ranges, a + 1, args[a + 1], 1) + margin = self.operand(ranges, a + 2, args[a + 2], 1) + return msg + [f"{req}_THAT({actual},{self.separator(ranges, a)}Catch::Matchers::WithinAbs(" + f"{expected},{self.separator(ranges, a + 1)}{margin}))"] + return build + if name == "ensure_approximately_equals": + if n not in (3, 4): + return None + a = n - 3 + ftype = kind.split("-", 1)[1] + + def build(): + msg = message(0) if n == 4 else [] + actual = self.operand(ranges, a, args[a], 1, guard=True) + expected = self.operand(ranges, a + 1, args[a + 1], 1) + given = value_type(skip_implicit_casts(args[a + 1])) + if {"float": "F32", "double": "F64"}.get(given) != ftype: + expected = f"{ftype}({expected})" + bits = self.operand(ranges, a + 2, args[a + 2], 1) + return msg + [f"{req}_THAT({actual},{self.separator(ranges, a)}ll_test::WithinBits(" + f"{expected},{self.separator(ranges, a + 1)}{bits}))"] + return build + if name in ("ensure_contains", "ensure_does_not_contain", "ensure_starts_with", "ensure_ends_with"): + if n != 3: + return None + matcher = {"ensure_contains": "Catch::Matchers::ContainsSubstring", + "ensure_does_not_contain": "!Catch::Matchers::ContainsSubstring", + "ensure_starts_with": "Catch::Matchers::StartsWith", + "ensure_ends_with": "Catch::Matchers::EndsWith"}[name] + + def build(): + actual = self.operand(ranges, 1, args[1], 1, guard=True) + part = self.operand(ranges, 2, args[2], 1) + return message(0) + [f"{req}_THAT({actual},{self.separator(ranges, 1)}{matcher}({part}))"] + return build + if name == "ensure_memory_matches": + if n not in (4, 5): + return None + a = n - 4 + + def build(): + msg = message(0) if n == 5 else [] + parts = [self.operand(ranges, a + i, args[a + i], 1) for i in range(4)] + return msg + [f"{req}_THAT(ll_test::Memory({parts[0]}, {parts[1]}),{self.separator(ranges, a + 1)}" + f"ll_test::MemoryMatches({parts[2]}, {parts[3]}))"] + return build + return None + + def convert_in_macro(self, offset, records): + """A call written in a #define of this file: its arguments are the + macro's text, so they are split as text, and every expansion must + agree on what the call means and use it as a statement among others.""" + name = records[0]["name"] + kinds = {self.conversion_kind(r) for r in records} + if len(kinds) > 1 and "dependent" in kinds: + kinds.discard("dependent") + contexts = {self.context(r)[0] for r in records} + if len(kinds) != 1 or contexts != {"block"}: + self.check(offset, f"{name}() in a macro whose uses differ, or are not statements in a block") + return + kind = kinds.pop() + if kind in ("dependent", "bits-?") or name in TUT_MEMBERS or name == "ensure_errno": + self.check(offset, f"{name}() in a macro, as {kind}") + return + parts = self.text_call(offset) + if parts is None: + self.check(offset, f"cannot split the arguments of {name}() in its macro") + return + end, texts = parts + statements = text_statements(name, texts, macro=True, kind=kind) + if statements is None: + self.check(offset, f"{name}() with {len(texts)} arguments in a macro") + return + if any(self.in_destructor(r) for r in records): + statements = [re.sub(r"^REQUIRE", "CHECK", st) for st in statements] + self.headers.update(headers_for(name, kind)) + self.plans.append(Plan(offset, end, lambda conv, plan, s=statements: "; ".join(s))) + + def text_call(self, offset): + """(end, [argument texts]) of a call, read from its text, or None + where the text alone cannot tell its arguments apart.""" + open_paren = self.src.masked.find(b"(", offset) + close = self.src.matching(open_paren) if open_paren >= 0 else -1 + if close < 0: + return None + inside = self.src.masked[open_paren + 1:close] + depth = 0 + segments = [] + start = 0 + for i, c in enumerate(inside): + if c in b"([{": + depth += 1 + elif c in b")]}": + depth -= 1 + elif c == 0x2C and depth == 0: + segments.append((start, i)) + start = i + 1 + segments.append((start, len(inside))) + # A comma between template arguments looks like one between call + # arguments. + if len(segments) > 1 and b"<" in inside and b">" in inside: + return None + texts = [self.text(open_paren + 1 + s, open_paren + 1 + e).strip() for s, e in segments] + if texts == [""]: + texts = [] + return close + 1, texts + + # ---- what the AST does not hold ---------------------------------- + def convert_uncompiled(self): + """Tests and calls in branches this host does not compile: by their + text, where their types do not matter.""" + header = re.compile(rb"template\s*<\s*>\s*template\s*<\s*>\s*void\s+((?:::)?\w+(?:\s*::\s*\w+)*)" + rb"\s*::\s*test\s*<\s*(\d+)\s*>\s*\(\s*\)") + known = {t.start for t in self.tests} + for m in header.finditer(self.src.masked): + if m.start() in known or self.src.state(m.start()) != "inactive": + continue + parts = [p.decode() for p in re.split(rb"\s*::\s*", m.group(1)) if p] + cls = parts[-1] + if cls == "object" and len(parts) > 1: + cls = parts[-2] # test_group::object, by the group's typedef + data = self.typedefs.get(cls) or self.text_typedefs().get(cls) or self.text_groups().get(cls) + open_brace = self.src.skip_space(m.end()) + close = self.src.matching(open_brace) if self.src.masked[open_brace:open_brace + 1] == b"{" else -1 + test = Test() + test.n = int(m.group(2)) + test.inactive = True + test.start = m.start() + test.header_end = m.end() + test.body_start = open_brace + test.body_end = close + 1 if close >= 0 else None + test.data = data + test.namespaces = self.namespaces_at(m.start()) + if data is None or close < 0: + self.check(m.start(), f"test<{test.n}>, not compiled here, belongs to no group this converter found") + test.macro = True + self.tests.append(test) + names = "|".join(sorted(TUT_FUNCTIONS | TUT_MEMBERS, key=len, reverse=True)) + call = re.compile(rb"(?:])(?:tut\s*::\s*)?(" + names.encode() + rb")\s*\(") + throw = re.compile(rb"(?\s*(\w+)\s*;", masked): + remove(m) + if self.src.state(m.start()) == "inactive": + groups.add(m.group(1)) + for m in re.finditer(rb"typedef\s+(?:tut\s*::\s*)?(\w+)\s*::\s*object\s+\w+\s*;", masked): + if m.group(1) in groups: + remove(m) + for m in re.finditer(rb"(?:tut\s*::\s*)?(\w+)\s+\w+\s*\(\s*\"[^\"\n]*\"\s*\)\s*;", masked): + if m.group(1) in groups: + remove(m) + for m in re.finditer(rb"(?:tut\s*::\s*)?test_group\s*<[^;{}]*>\s*\w+\s*\(\s*\"[^\"\n]*\"\s*\)\s*;", masked): + remove(m) + + def convert_text_call(self, offset, name): + if name == "set_test_name": + return # folded into the case's name by name_tests + if name == "get_test_name": + end = self.src.matching(self.src.masked.find(b"(", offset)) + self.headers.add('"altestname.h"') + self.plans.append(Plan(offset, end + 1, lambda conv, plan: "ll_test::currentTestName()")) + return + if name in TUT_ASSERTING or name == "throw": + self.counted[offset] = True + if name not in TYPE_FREE and name != "throw": + self.check(offset, f"{name}(), in code not compiled here, converts by its arguments' types") + return + parts = self.text_call(offset) + if parts is None: + self.check(offset, f"cannot split the arguments of {name}(), in code not compiled here") + return + end, texts = parts + after = self.src.skip_space(end) + if self.src.masked[after:after + 1] != b";": + self.check(offset, f"{name}(), in code not compiled here, is not a statement") + return + statements = text_statements(name, texts, macro=False) + if statements is None: + self.check(offset, f"{name}() with {len(texts)} arguments, in code not compiled here") + return + # The lone statement of an if, else or loop, by what precedes it. + lead = self.src.masked[max(0, offset - 400):offset].decode("latin-1").rstrip() + lone = re.search(r"(\)|\belse|\bdo)$", lead) is not None + if len(statements) > 1 and lone: + text = "{ " + "; ".join(statements) + "; }" + self.plans.append(Plan(offset, after + 1, lambda conv, plan, t=text: t)) + else: + joiner = ";\n" + self.src.indent_at(offset).decode() if self.src.starts_line(offset) else "; " + text = joiner.join(statements) + self.plans.append(Plan(offset, end, lambda conv, plan, t=text: t)) + + def text_groups(self): + """typedef test_group G: G -> X, from the text.""" + found = {} + for m in re.finditer(rb"typedef\s+(?:tut\s*::\s*)?test_group\s*<\s*([\w:]+)\s*(?:,\s*\d+\s*)?>\s*(\w+)\s*;", + self.src.masked): + data = m.group(1).decode() + found[m.group(2).decode()] = data if "::" in data else "tut::" + data + return found + + def text_typedefs(self): + if hasattr(self, "_text_typedefs"): + return self._text_typedefs + found = {} + groups = {} + for m in re.finditer(rb"typedef\s+(?:tut\s*::\s*)?test_group\s*<\s*([\w:]+)\s*(?:,\s*\d+\s*)?>\s*(\w+)\s*;", + self.src.masked): + groups[m.group(2).decode()] = m.group(1).decode() + for m in re.finditer(rb"typedef\s+(?:tut\s*::\s*)?(\w+)\s*::\s*object\s+(\w+)\s*;", self.src.masked): + if m.group(1).decode() in groups: + found[m.group(2).decode()] = "tut::" + groups[m.group(1).decode()] + self._text_typedefs = found + return found + + def namespaces_at(self, offset): + stack = [] + for m in re.finditer(rb"\bnamespace\s*(\w*)\s*\{|[{}]", self.src.masked[:offset]): + if m.group(0).startswith(b"namespace"): + stack.append(m.group(1).decode()) + elif m.group(0) == b"{": + stack.append(None) + elif stack: + stack.pop() + return tuple(s for s in stack if s is not None) + + # ---- tests, named and placed --------------------------------------- + def resolve_tests(self): + demangled = self.demangled + for test in self.tests: + if test.data is None and getattr(test, "mangled", None): + full = demangled.get(test.mangled, "") + m = re.match(r"^void tut::test_object<(.*)>::test<\d+>\(\)$", full) + if m: + test.data = m.group(1) + group = self.find_group(test.data) if test.data else None + if group is None and test.data: + # a group only text can see + group = self.text_group(test.data) + test.group = group + if group is None: + if not test.macro: + self.check(test.start, f"test<{test.n}> belongs to no group this converter found") + test.macro = True + continue + group.tests.append(test) + test.fixture = self.spell_fixture(test.data, test.namespaces) + self.collect_names() + + def find_group(self, data): + """The group whose data is data, which the group may have named + without its namespaces.""" + plain = data.replace("(anonymous namespace)::", "") + for key, group in self.groups.items(): + k = key.replace("(anonymous namespace)::", "") + if k == plain or ("::" not in k and plain.rpartition("::")[2] == k): + return group + return None + + def text_group(self, data): + short = data.split("::")[-1] + for m in re.finditer(rb"typedef\s+(?:tut\s*::\s*)?test_group\s*<\s*([\w:]+)\s*(?:,\s*(\d+)\s*)?>\s*(\w+)\s*;", + self.src.masked): + if m.group(1).decode().split("::")[-1] != short: + continue + typedef = m.group(3).decode() + var = re.search(rb"(?:tut\s*::\s*)?" + typedef.encode() + rb"\s+(\w+)\s*\(\s*\"", self.src.masked) + if not var: + continue + q = var.end() - 1 + close = self.src.data.find(b'"', q + 1) + while close > 0 and self.src.data[close - 1:close] == b"\\": + close = self.src.data.find(b'"', close + 1) + name = c_unescape(self.text(q + 1, close)) + group = Group(name, data, int(m.group(2)) if m.group(2) else 50) + self.groups[data] = group + for start, end in ((m.start(), m.end()), (var.start(), self.src.masked.find(b";", var.end()) + 1)): + self.removals.append((start, end, "decl")) + return group + return None + + def spell_fixture(self, data, namespaces): + name = data.replace("(anonymous namespace)::", "") + prefix = "::".join(n for n in namespaces if n) + while prefix: + if name.startswith(prefix + "::"): + return name[len(prefix) + 2:] + prefix = prefix.rpartition("::")[0] + return name + + def collect_names(self): + """Each test's set_test_name, from the AST or, uncompiled, its text.""" + for offset, records in self.calls.items(): + for r in records: + if r["name"] != "set_test_name": + continue + test = self.test_containing(offset) + if test is None: + self.check(offset, "set_test_name outside a test's body") + continue + arg = r["args"][0] if r["args"] else None + literal = skip_implicit(arg) if arg else None + top = self.context(r)[0] == "block" and self.context(r)[1] is test_body(test) + test.names.append((offset, r, literal if literal and literal.get("kind") == "StringLiteral" + else None, top)) + name_call = re.compile(rb"(?])set_test_name\s*\(") + for test in self.tests: + if not test.inactive or test.body_end is None: + continue + for m in name_call.finditer(self.src.masked, test.body_start, test.body_end): + test.names.append((m.start(), None, "text", True)) + + def test_containing(self, offset): + for test in self.tests: + if test.body_start is not None and test.body_end is not None and test.body_start <= offset < test.body_end: + return test + return None + + def name_tests(self): + for data, group in self.groups.items(): + seen = set() + for test in sorted(group.tests, key=lambda t: t.n): + if test.macro: + test.tut_name = None # left as it is: no case, and parity says so + continue + text_name = None + if len(test.names) > 1: + self.check(test.start, f"test<{test.n}> names itself more than once") + elif test.names: + offset, record, literal, top = test.names[0] + if not top or literal is None: + self.check(offset, "set_test_name that is not a statement of the test's body, or not a literal") + else: + call_end = self.src.matching(self.src.masked.find(b"(", offset)) + inside = self.text(self.src.masked.find(b"(", offset) + 1, call_end) + pieces = re.findall(r'"((?:[^"\\\n]|\\.)*)"', inside) + stripped = re.sub(r'"((?:[^"\\\n]|\\.)*)"', "", inside).strip() + if not pieces or stripped: + self.check(offset, "set_test_name with a literal this converter cannot read") + else: + text_name = "".join(c_unescape(p) for p in pieces) + test.name_literal_inner = "".join(pieces) + self.remove_statement(offset, call_end + 1) + test.tut_name = text_name if text_name is not None else ("" if not test.names else None) + base = f"{group.name}: {text_name}" if text_name is not None else f"{group.name}: #{test.n}" + # Tests in different branches of an #if are never compiled + # together, so their names cannot clash. + branch = tuple((a, b) for a, b, what in self.src.regions if a <= test.start < b) + name = base + if (branch, name.lower()) in seen: + name = f"{base} #{test.n}" + seen.add((branch, name.lower())) + test.catch2_name = name + if text_name is not None and name == base: + test.catch2_literal = '"' + c_escape(group.name + ": ") + test.name_literal_inner + '"' + else: + test.catch2_literal = '"' + c_escape(name) + '"' + + def remove_statement(self, start, end): + after = self.src.skip_space(end) + if self.src.masked[after:after + 1] == b";": + end = after + 1 + self.removals.append((start, end, "statement")) + + # ---- the plans ---------------------------------------------------- + def plan_structure(self): + for test in self.tests: + if test.macro or test.group is None or test.catch2_name is None: + continue + if "," in test.fixture: + self.check(test.start, f"the fixture {test.fixture} has a comma, which TEST_CASE_METHOD would split") + continue + tag = group_tag(test.group.name) + header = f'TEST_CASE_METHOD({test.fixture}, {test.catch2_literal}, "[{tag}]")' + # A comment inside template<> template<> void X::test() stays, + # on a line of its own above the case. + indent = self.src.indent_at(test.start).decode() + comments = [c.strip() for c in self.comments_in(test.start, test.header_end)] + header = "".join(f"{c}\n{indent}" for c in comments) + header + self.plans.append(Plan(test.start, test.header_end, lambda conv, plan, h=header: h)) + if test.n > test.group.limit: + self.notes.append(f"{test.group.name} test<{test.n}> is past the group's limit of " + f"{test.group.limit}: TUT never ran it, Catch2 will") + spans = [] + for start, end, what in self.removals: + ls = self.src.line_start(start) + le = self.src.line_end(end) + if self.src.masked[ls:start].strip() == b"" and self.src.masked[end:le].strip() == b"": + start, end = ls, min(le + 1, len(self.src.data)) + if what == "decl": + start = self.comment_above(start) + spans.append((start, end)) + self.removed_spans = self.tidy_removals(spans) + self.remove_empty_namespaces() + self.removed_spans = self.tidy_removals(self.removed_spans) + for start, end in self.removed_spans: + self.plans.append(Plan(start, end, lambda conv, plan: "")) + + def comment_above(self, start): + """Where the comment lines right above a removed declaration begin, + if they stand apart from what precedes them, as a comment about the + group does: they go with it.""" + top = start + while top > 0: + prev = self.src.line_start(top - 1) + if self.src.masked[prev:top].strip() == b"" and self.src.data[prev:top].strip(): + top = prev + continue + break + if top == start: + return start + if top == 0: + return top + before = self.src.line_start(top - 1) + line = self.src.masked[before:top].strip() + return top if line == b"" or line.endswith(b"{") else start + + def comments_in(self, start, end): + """The comments between start and end, as written: a test's header + holds no string literal to mistake for one.""" + return [m.group(0).decode("utf-8", "surrogateescape") + for m in re.finditer(rb"//[^\n]*|/\*.*?\*/", self.src.data[start:end], re.S)] + + def tidy_removals(self, spans): + """Whole-line removals merged where only blank lines part them, and + the blank lines they leave doubled made single.""" + def blank(ls): + return ls < len(self.src.data) and self.src.data[ls:self.src.line_end(ls)].strip() == b"" + spans = sorted(spans) + merged = [] + for start, end in spans: + if merged: + ps, pe = merged[-1] + if start <= pe or (self.src.data[pe:start].strip() == b"" and pe == self.src.line_start(pe) + and start == self.src.line_start(start)): + merged[-1] = (ps, max(pe, end)) + continue + merged.append((start, end)) + out = [] + for start, end in merged: + whole = start == self.src.line_start(start) and (end == len(self.src.data) or + end == self.src.line_start(end)) + if whole and start > 0: + prev = self.src.line_start(start - 1) + prev_text = self.src.masked[prev:start].strip() + if (blank(prev) or prev_text.endswith(b"{")) and blank(end): + end = min(self.src.line_end(end) + 1, len(self.src.data)) + out.append((start, end)) + return out + + def remove_empty_namespaces(self): + """An anonymous namespace that held only the group goes with it.""" + for item in self.items: + for node in iter_nodes(item): + if node.get("kind") != "NamespaceDecl" or node.get("name"): + continue + rng = self.node_range(node) + if rng is None or rng[2] != "file": + continue + inner = [c for c in node.get("inner", []) if "kind" in c and not c.get("isImplicit")] + spans = [self.node_range(c) for c in inner] + if not inner or not all(s and any(a <= s[0] and s[1] <= b for a, b in self.removed_spans) + for s in spans): + continue + start, end = rng[0], rng[1] + le = self.src.line_end(end) + if self.src.masked[end:le].strip() == b"": + end = min(le + 1, len(self.src.data)) + ls = self.src.line_start(start) + if self.src.masked[ls:start].strip() == b"": + start = ls + self.removed_spans = [(a, b) for a, b in self.removed_spans if not (start <= a and b <= end)] + self.removed_spans.append((start, end)) + + def plan_order(self): + """Tests declared out of TUT's numeric order move later in the file, + never earlier, to where the tests numbered below them are: TUT ran a + group's tests by number, Catch2 runs them as declared.""" + for group in self.groups.values(): + # A test this host does not compile is in another #if branch: + # never declared beside these, it has no place in their order. + tests = [t for t in group.tests if not t.macro and t.body_end is not None and t.catch2_name + and not t.inactive] + by_position = sorted(tests, key=lambda t: t.start) + if [t.n for t in by_position] == sorted(t.n for t in by_position): + continue + slot = {} + latest = None + for test in sorted(tests, key=lambda t: t.n): + if latest is None or test.start > latest.start: + latest = test + slot[id(test)] = latest + stuck = [t for t in tests if slot[id(t)] is not t and + (t.namespaces != slot[id(t)].namespaces or + self.branch_of(t.start) != self.branch_of(slot[id(t)].start))] + if stuck: + for t in stuck: + self.check(t.start, f"test<{t.n}> is declared out of TUT's numeric order, and cannot move to " + f"its place across an #if or a namespace") + continue + assigned = {} + for test in sorted(tests, key=lambda t: t.n): + assigned.setdefault(id(slot[id(test)]), []).append(test) + chunks = {id(t): self.chunk(t) for t in tests} + for test in by_position: + members = assigned.get(id(test), []) + if members == [test]: + continue + parts = [chunks[id(m)] for m in members] + plan = Plan(chunks[id(test)][0], chunks[id(test)][1], None) + plan.render = lambda conv, plan, parts=parts: "\n".join( + conv.render(s, e, skip=conv.slot_at.get(s)) for s, e in parts) + self.slot_at[plan.start] = plan + self.plans.append(plan) + self.notes.append(f"{group.name}: tests moved into TUT's numeric order") + + def chunk(self, test): + """A test's text with the comment lines right above it, through the end + of its last line.""" + start = self.src.line_start(test.start) + while start > 0: + prev = self.src.line_start(start - 1) + if self.src.masked[prev:start].strip() == b"" and self.src.data[prev:start].strip(): + start = prev + continue + break + end = self.src.line_end(test.body_end - 1) + return start, min(end + 1, len(self.src.data)) + + def branch_of(self, offset): + """The #if branches around offset, by where each starts.""" + stack = [] + for start, end, kw, _ in self.src.directives: + if start > offset: + break + if kw in ("if", "ifdef", "ifndef"): + stack.append(end) + elif kw in ("elif", "else", "elifdef", "elifndef") and stack: + stack[-1] = end + elif kw == "endif" and stack: + stack.pop() + return tuple(stack) + + def plan_includes(self): + found = list(TUT_INCLUDE.finditer(self.src.data)) + found = [m for m in found if self.src.state(m.start()) != "dead"] + headers = sorted(h for h in self.headers if h.startswith("<")) + \ + sorted(h for h in self.headers if h.startswith('"')) + lines = "".join(f"#include {h}\n" for h in headers) + if found: + first = found[0] + self.plans.append(Plan(first.start(), first.end(), lambda conv, plan: lines)) + for m in found[1:]: + self.plans.append(Plan(m.start(), m.end(), lambda conv, plan: "")) + else: + includes = list(re.finditer(rb"^[ \t]*#[ \t]*include[^\n]*\n", self.src.data, re.M)) + first_code = min((t.start for t in self.tests if t.start is not None), default=len(self.src.data)) + before = [m for m in includes if m.end() <= first_code and self.src.state(m.start()) == "active"] + at = before[-1].end() if before else 0 + self.plans.append(Plan(at, at, lambda conv, plan: lines)) + self.notes.append("no direct include of lltut.h: the Catch2 headers go after the last include") + + def plan_checks(self): + by_line = {} + for offset, why in self.checks: + by_line.setdefault(self.src.line_start(offset), []).append((offset, why)) + for start, entries in by_line.items(): + whys = "; ".join(dict.fromkeys(w for _, w in entries)) + if self.src.directive_at(start): + offset = min(o for o, _ in entries) + self.plans.append(Plan(offset, offset, lambda conv, plan, w=whys: f"/* TM-CHECK: {w} */ ")) + else: + indent = self.src.indent_at(start).decode() + self.plans.append(Plan(start, start, lambda conv, plan, w=whys, i=indent: f"{i}// TM-CHECK: {w}\n")) + + # ---- rendering ----------------------------------------------------- + def render(self, start, end, skip=None): + """The text of [start, end) with every plan inside it carried out.""" + out = [] + pos = start + for plan in self.sorted_plans: + if plan is skip or plan.start < pos: + continue + if plan.start == plan.end: + if plan.start >= end: + continue + elif plan.end > end: + if plan.start >= end: + break + continue + out.append(self.text(pos, plan.start)) + out.append(plan.text(self)) + pos = plan.end + out.append(self.text(pos, end)) + return "".join(out) + + def finish(self): + self.sorted_plans = sorted(self.plans, key=lambda p: (p.start, 0 if p.start == p.end else 1, -p.end)) + stack = [] + for plan in self.sorted_plans: + if plan.start == plan.end: + continue + while stack and stack[-1].end <= plan.start: + stack.pop() + if stack and plan.end > stack[-1].end: + raise ConvertError(f"{self.src.path}: edits overlap at line {self.src.line_of(plan.start)}") + stack.append(plan) + return self.render(0, len(self.src.data)) + + def counts(self, converted): + """Per test: the TUT assertion calls in its body before, and the Catch2 + assertions in it after.""" + tut = {} + for test in self.tests: + if test.body_start is None or test.body_end is None: + continue + tut[id(test)] = sum(1 for o in self.counted if test.body_start <= o < test.body_end) + data = converted.encode("utf-8", "surrogateescape") + catch = {id(t): n for t, n in count_catch_assertions(data, [ + (t, f"TEST_CASE_METHOD({t.fixture}, {t.catch2_literal},") for t in self.tests + if t.catch2_name and not t.macro])} + return tut, catch + + +def count_catch_assertions(data, cases): + """For each (key, header text) in cases, the Catch2 assertions in the body + that follows the header in data: code under #if 0 left out.""" + masked = mask_source(data) + dead = dead_regions(masked) + out = [] + for key, header in cases: + at = data.find(header.encode("utf-8", "surrogateescape")) + if at < 0: + out.append((key, None)) + continue + brace = masked.find(b"{", at + len(header)) + depth = 0 + i = brace + while i < len(masked): + if masked[i] == 0x7B: + depth += 1 + elif masked[i] == 0x7D: + depth -= 1 + if depth == 0: + break + i += 1 + found = [m.start() for m in CATCH_ASSERTION.finditer(masked, brace, i + 1)] + out.append((key, sum(1 for f in found if not any(s <= f < e for s, e in dead)))) + return out + + +def dead_regions(masked): + """[start, end) of each #if 0 branch.""" + regions = [] + stack = [] + for m in re.finditer(rb"^[ \t]*#[ \t]*(if|ifdef|ifndef|elif|else|endif)\b([^\n]*)", masked, re.M): + kw, rest = m.group(1).decode(), m.group(2).decode("latin-1").strip() + if kw in ("if", "ifdef", "ifndef"): + stack.append([m.end(), kw == "if" and re.fullmatch(r"\(?\s*(0|false)\s*\)?", rest) is not None]) + elif stack: + start, dead = stack[-1] + if dead: + regions.append((start, m.start())) + if kw == "endif": + stack.pop() + else: + stack[-1] = [m.end(), kw == "elif" and re.fullmatch(r"\(?\s*(0|false)\s*\)?", rest) is not None] + return regions + + +def test_body(test): + return next((c for c in test.node.get("inner", []) if c.get("kind") == "CompoundStmt"), None) \ + if test.node else None + + +def skip_implicit_casts(node): + while node.get("kind") in ("ImplicitCastExpr", "MaterializeTemporaryExpr", "CXXBindTemporaryExpr") \ + and node.get("inner"): + node = node["inner"][0] + return node + + +def find_kind(node, kind): + for sub in iter_nodes(node): + if sub.get("kind") == kind: + return sub + return None + + +def iter_nodes(node): + stack = [node] + while stack: + cur = stack.pop() + if isinstance(cur, dict): + if "kind" in cur: + yield cur + stack.extend(reversed([c for c in cur.get("inner", []) if isinstance(c, dict)])) + + +def split_template_args(text): + out = [] + depth = 0 + cur = [] + for ch in text: + if ch in "<([{": + depth += 1 + elif ch in ">)]}": + depth -= 1 + if ch == "," and depth == 0: + out.append("".join(cur).strip()) + cur = [] + continue + cur.append(ch) + out.append("".join(cur).strip()) + return out + + +def needs_macro_guard(masked_text): + """Whether text, inside a macro that does not take __VA_ARGS__, would be + split: a comma outside parentheses, or a macro that could expand to one.""" + depth = 0 + for c in masked_text: + if c == 0x28: + depth += 1 + elif c == 0x29: + depth -= 1 + elif c == 0x2C and depth == 0: + return True + return bool(re.search(rb"\{|\b[A-Z][A-Z0-9_]{2,}\s*\(", masked_text)) + + +def text_statements(name, texts, macro, kind=None): + """The statements a call becomes, from its arguments' text alone: for + code no AST shows (only the calls whose types do not matter), and for a + call in a macro, whose kind the macro's uses decided and whose + arguments may be anything, so are parenthesised where they meet an + operator.""" + simple = re.compile(r'[\w.:]+(\(\s*\))?|"(?:[^"\\]|\\.)*"') + kind = kind or name + + def operand(t): + return t if simple.fullmatch(t) else f"({t})" + + def condition(t): + if macro: + return operand(t) + if re.search(r"&&|\|\||\?|[^=!<>]=[^=]|,", t) and not wrapped(t): + return f"({t})" + return t + + def message(t): + if t in ("NULL", "nullptr", "0", '""'): + return [] + return [f"UNSCOPED_INFO({t if simple.fullmatch(t) else f'({t})'})"] + n = len(texts) + if name in ("ensure", "ensure_not"): + macro_name = "REQUIRE_FALSE" if name == "ensure_not" else "REQUIRE" + if n not in (1, 2): + return None + return (message(texts[0]) if n == 2 else []) + [f"{macro_name}({condition(texts[-1])})"] + if name in ("fail", "skip", "throw"): + if n != 1: + return None + return [f"{'SKIP' if name == 'skip' else 'FAIL'}({operand(texts[0])})"] + if name in ("ensure_equals", "ensure_not_equals"): + if n not in (2, 3): + return None + msg = message(texts[0]) if n == 3 else [] + a, e = operand(texts[-2]), operand(texts[-1]) + if kind == "equals-llsd": + return msg + [f"REQUIRE_THAT({a}, ll_test::LLSDEquals({e}))"] + if kind == "equals-uri": + return msg + [f"REQUIRE({a}.asString() == {e}.asString())"] + return msg + [f"REQUIRE({a} {'==' if name == 'ensure_equals' else '!='} {e})"] + if name in ("ensure_contains", "ensure_does_not_contain", "ensure_starts_with", "ensure_ends_with"): + if n != 3: + return None + matcher = {"ensure_contains": "Catch::Matchers::ContainsSubstring", + "ensure_does_not_contain": "!Catch::Matchers::ContainsSubstring", + "ensure_starts_with": "Catch::Matchers::StartsWith", + "ensure_ends_with": "Catch::Matchers::EndsWith"}[name] + return message(texts[0]) + [f"REQUIRE_THAT({operand(texts[1])}, {matcher}({texts[2]}))"] + if name in ("ensure_distance", "ensure_approximately_equals_range"): + if n not in (3, 4): + return None + msg = message(texts[0]) if n == 4 else [] + a, e, d = texts[-3:] + return msg + [f"REQUIRE_THAT({operand(a)}, Catch::Matchers::WithinAbs({e}, {d}))"] + if name == "ensure_approximately_equals" and kind in ("bits-F32", "bits-F64"): + if n not in (3, 4): + return None + msg = message(texts[0]) if n == 4 else [] + a, e, bits = texts[-3:] + return msg + [f"REQUIRE_THAT({operand(a)}, ll_test::WithinBits({kind[5:]}({e}), {bits}))"] + return None + + +def wrapped(text): + """Whether text is one parenthesised whole.""" + if not (text.startswith("(") and text.endswith(")")): + return False + depth = 0 + for i, ch in enumerate(text): + depth += ch == "(" + depth -= ch == ")" + if depth == 0 and i < len(text) - 1: + return False + return True + + +def group_tag(name): + tag = re.sub(r"[^A-Za-z0-9_:+-]+", "_", name).strip("_") + if not tag or not tag[0].isalnum(): + tag = "tut_" + tag + return tag + + +# -------------------------------------------------------------------------- +# One file +# -------------------------------------------------------------------------- +def convert_file(path, build_dir, config, dry_run, output_dir=None): + db = CompileDatabase(build_dir, config) + source = Source(path) + if not TUT_INCLUDE.search(source.data) and b"test_group" not in source.data: + return {"file": path, "skipped": "no TUT here", "tests": [], "checks": [], "notes": []} + items, tut_ids, active_lines = run_clang(db, path) + if os.environ.get("TUT2CATCH_DUMP"): + with open(os.environ["TUT2CATCH_DUMP"], "w") as f: + json.dump(items, f, indent=1) + mangled = sorted({n.get("mangledName") for item in items for n in iter_nodes(item) + if n.get("kind") == "CXXMethodDecl" and n.get("name") == "test" + and "test_object" in n.get("mangledName", "")}) + conv = Converter(source, items, tut_ids, active_lines, demangle(mangled)) + sys.setrecursionlimit(max(sys.getrecursionlimit(), 20000)) + conv.analyse() + conv.resolve_calls() + conv.convert_uncompiled() + conv.resolve_tests() + conv.remove_uncompiled_groups() + conv.name_tests() + conv.plan_structure() + conv.plan_order() + conv.plan_includes() + conv.plan_checks() + converted = conv.finish() + tut_counts, catch_counts = conv.counts(converted) + if output_dir: + out = os.path.join(output_dir, os.path.relpath(path, ROOT)) + os.makedirs(os.path.dirname(out), exist_ok=True) + with open(out, "wb") as f: + f.write(converted.encode("utf-8", "surrogateescape")) + elif not dry_run: + with open(path, "wb") as f: + f.write(converted.encode("utf-8", "surrogateescape")) + tests = [] + for test in sorted(conv.tests, key=lambda t: ((t.group.name if t.group else ""), t.n or 0)): + tests.append({ + "file": os.path.relpath(path, ROOT), + "group": test.group.name if test.group else None, + "n": test.n, + "line": source.line_of(test.start) if test.start is not None else None, + "tut_name": test.tut_name, + "catch2_name": test.catch2_name, + "tut_assertions": tut_counts.get(id(test)), + "catch2_assertions": catch_counts.get(id(test)), + "not_compiled_here": test.inactive, + "past_group_limit": bool(test.group and test.n and test.n > test.group.limit), + }) + checks = [{"file": os.path.relpath(path, ROOT), "line": source.line_of(o), "why": w} + for o, w in sorted(conv.checks)] + return {"file": os.path.relpath(path, ROOT), "tests": tests, "checks": checks, "notes": conv.notes, + "groups": sorted(g.name for g in conv.groups.values())} + + +def convert_one(args): + path, build_dir, config, dry_run, output_dir = args + result = {} + + def work(): + try: + result["value"] = convert_file(path, build_dir, config, dry_run, output_dir) + except ConvertError as e: + result["value"] = {"file": os.path.relpath(path, ROOT), "error": str(e), "tests": [], "checks": [], + "notes": []} + # Deep ASTs recurse deeply. + threading.stack_size(512 * 1024 * 1024) + t = threading.Thread(target=work) + t.start() + t.join() + return result["value"] + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0], + formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__) + parser.add_argument("files", nargs="+", help="TUT test files to convert in place") + parser.add_argument("--build-dir", default=os.path.join(ROOT, DEFAULT_BUILD), + help="the build tree whose compile_commands.json gives each file's flags") + parser.add_argument("--config", default=DEFAULT_CONFIG, help="which configuration's flags to use") + parser.add_argument("--mapping", help="write the TUT-to-Catch2 mapping, as JSON, here") + parser.add_argument("--dry-run", action="store_true", help="convert nothing; report and map only") + parser.add_argument("--output-dir", help="write each converted file under here, at its path from the " + "repository's root, instead of over the original") + parser.add_argument("--jobs", type=int, default=1, help="files to convert at once") + parser.add_argument("--quiet", action="store_true", help="print only the marks and the totals") + args = parser.parse_args() + + files = [os.path.abspath(f) for f in args.files] + work = [(f, os.path.abspath(args.build_dir), args.config, args.dry_run, args.output_dir) for f in files] + if args.jobs > 1: + with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: + results = list(pool.map(convert_one, work)) + else: + results = [convert_one(w) for w in work] + + mapping = [] + total_checks = 0 + errors = 0 + for r in results: + if r.get("error"): + errors += 1 + print(f"{r['file']}: error: {r['error']}") + continue + if r.get("skipped"): + if not args.quiet: + print(f"{r['file']}: {r['skipped']}") + continue + mapping.extend(r["tests"]) + total_checks += len(r["checks"]) + if not args.quiet: + converted = sum(1 for t in r["tests"] if t["catch2_name"]) + print(f"{r['file']}: {converted} of {len(r['tests'])} tests in {len(r['groups'])} group(s), " + f"{len(r['checks'])} TM-CHECK") + for note in r["notes"]: + print(f" note: {note}") + for c in r["checks"]: + print(f"{c['file']}:{c['line']}: TM-CHECK: {c['why']}") + if args.mapping: + with open(args.mapping, "w") as f: + json.dump(mapping, f, indent=1) + print(f"{len(results)} file(s), {len(mapping)} test(s), {total_checks} TM-CHECK(s), {errors} error(s)") + return 2 if errors else (1 if total_checks else 0) + + +if __name__ == "__main__": + sys.exit(main()) From ebb18dcd68847877a9b6f7c2374d540346b2ab5d Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 9 Oct 2026 02:16:18 -0400 Subject: [PATCH 08/55] tut_parity.py proves a converted test file runs the same tests to the same results as its TUT original TM16's other half: a converted file must lose no test and no assertion, and change no result. Before conversion it runs the TUT executables verbose, through their CTest commands and environments, and keeps every [group, N: name] result line. After conversion it runs the file's Catch2 entry as XML and matches the two through tut2catch.py's mapping: one case for every TUT test, the same result for each, the same name, and as many assertions in each case as its TUT test had ensure, fail and skip calls, recounted from the converted file. What the Catch2 harness's state guard reports is listed apart: TUT never looked, so it is no difference the conversion made. On the 32 files of the TM16 proof (all of llmath; llcommon's llsd, llsdserialize, lleventcoro and llerror; llui's alcodeeditor and llurlentry; newview's llvocache and alscriptstudiowords; alscript's allsltoslua) parity holds for all 697 tests and 2,840 assertions. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 374 ++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 scripts/testing/tut_parity.py diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py new file mode 100644 index 0000000000..eb4e3f307f --- /dev/null +++ b/scripts/testing/tut_parity.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +"""Proves a TUT file and its Catch2 conversion test the same things. + +Two steps, either side of scripts/testing/tut2catch.py: + + record before converting: runs TUT test executables, verbose, through + the commands, working directories and environments their CTest + entries give, and keeps every line a test's end prints: + [group, N: name] ok | fail | exception: ... | skipped known failure | ... + A test TUT never reported (a dummy test slot) is not a test. + check after converting and building: runs each converted file's CTest + entry, /, as XML, and matches the cases to the + TUT results through tut2catch.py's mapping. + +Parity holds for a file when: + - every TUT test it reported has exactly one Catch2 case, and every case + of the file is one of its TUT tests; + - each pair agrees: ok is a pass, a TUT skip is a Catch2 skip, and every + TUT failure (fail, exception, abnormal end) is a Catch2 failure; and the + name TUT printed is the one the mapping folded into the case's; + - each case's assertions, counted in the converted file, number its + TUT test's ensure, fail and skip calls, as the mapping counted them in + the TUT file. + +A test the mapping says TUT never ran (past its group's limit) or this host +does not compile is listed, not counted against parity. So is what the +Catch2 harness's state guard says a case left changed: TUT never looked, so +it is a finding about the test, not a difference the conversion made; the +table's exit column shows the run's exit code. It prints a table, one row a +file, and exits 1 on any mismatch, listing each. + + python3 -I scripts/testing/tut_parity.py record --out /tmp/tut.json \\ + INTEGRATION_TEST_RUNNER_v3math PROJECT_llmath_TEST_v3color + python3 -I scripts/testing/tut2catch.py --mapping /tmp/map.json indra/llmath/tests/v3*_test.cpp + (build the Catch2 binary) + python3 -I scripts/testing/tut_parity.py check --tut /tmp/tut.json --mapping /tmp/map.json +""" +import argparse +import json +import os +import re +import subprocess +import sys +import tempfile +import xml.etree.ElementTree as ElementTree + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +DEFAULT_BUILD = "build-Darwin-ninja-os" +DEFAULT_CONFIG = "RelWithDebInfo" +RESULT_LINE = re.compile( + r"^\[(?P.+?), (?P\d+)(?:: (?P.*?))?\] " + r"(?Pok|fail|exception|test destructor throw|abnormal termination|skipped known failure|unknown)\b") +CATCH_ASSERTION = re.compile( + r"(?:])(?:REQUIRE|REQUIRE_FALSE|REQUIRE_THAT|CHECK|CHECK_FALSE|CHECK_THAT|FAIL|FAIL_CHECK|SKIP)\s*\(") + + +# -------------------------------------------------------------------------- +# CTest's view of a test +# -------------------------------------------------------------------------- +def ctest_tests(build_dir, config): + out = subprocess.run(["ctest", "--test-dir", build_dir, "-C", config, "--show-only=json-v1"], + capture_output=True, text=True, check=True) + tests = {} + for t in json.loads(out.stdout).get("tests", []): + props = {p["name"]: p["value"] for p in t.get("properties", [])} + tests[t["name"]] = {"command": t.get("command", []), "props": props} + return tests + + +def environment(props): + env = dict(os.environ) + for item in props.get("ENVIRONMENT", []): + key, _, value = item.partition("=") + env[key] = value + for item in props.get("ENVIRONMENT_MODIFICATION", []): + key, _, rest = item.partition("=") + op, _, value = rest.partition(":") + old = env.get(key, "") + if op == "reset": + env.pop(key, None) if key not in os.environ else env.__setitem__(key, os.environ[key]) + elif op == "set": + env[key] = value + elif op == "unset": + env.pop(key, None) + elif op == "string_append": + env[key] = old + value + elif op == "string_prepend": + env[key] = value + old + elif op == "path_list_append": + env[key] = old + os.pathsep + value if old else value + elif op == "path_list_prepend": + env[key] = value + os.pathsep + old if old else value + elif op == "cmake_list_append": + env[key] = old + ";" + value if old else value + elif op == "cmake_list_prepend": + env[key] = value + ";" + old if old else value + return env + + +def run(test, extra, timeout): + props = test["props"] + try: + proc = subprocess.run(test["command"] + extra, cwd=props.get("WORKING_DIRECTORY") or None, + env=environment(props), capture_output=True, timeout=timeout) + return proc.returncode, proc.stdout.decode("utf-8", "replace") + proc.stderr.decode("utf-8", "replace") + except subprocess.TimeoutExpired as e: + return "timeout", (e.stdout or b"").decode("utf-8", "replace") + + +def stem_of_tut(name): + """The test file a TUT CTest entry runs, by stem.""" + m = re.search(r"TEST_RUNNER_(\w+)$", name) or re.search(r"_TEST_(\w+)$", name) + if not m: + return name + return re.sub(r"_(?:baseline|v2|v3|v4)$", "", m.group(1)) + "_test" + + +# -------------------------------------------------------------------------- +# record +# -------------------------------------------------------------------------- +def record(args): + tests = ctest_tests(args.build_dir, args.config) + out = {} + if args.out and os.path.exists(args.out): + with open(args.out) as f: + out = json.load(f) + for name in args.tests: + test = tests.get(name) + if test is None: + print(f"{name}: no such CTest entry", file=sys.stderr) + return 2 + code, text = run(test, ["-v"], args.timeout) + results = [] + for line in text.splitlines(): + m = RESULT_LINE.match(line) + if not m: + continue + result = m.group("result") + results.append({"group": m.group("group"), "n": int(m.group("n")), "name": m.group("name") or "", + "result": {"ok": "ok", "skipped known failure": "skip"}.get(result, "fail"), + "said": line[m.start("result"):].strip()}) + out[stem_of_tut(name)] = {"ctest": name, "exit": code, "results": results} + counts = {} + for r in results: + counts[r["result"]] = counts.get(r["result"], 0) + 1 + print(f"{name}: exit {code}, " + ", ".join(f"{v} {k}" for k, v in sorted(counts.items()))) + if args.out: + with open(args.out, "w") as f: + json.dump(out, f, indent=1) + return 0 + + +# -------------------------------------------------------------------------- +# check +# -------------------------------------------------------------------------- +def catch2_results(test, timeout): + """{case name: pass | skip | fail} from running a Catch2 entry as XML.""" + with tempfile.TemporaryDirectory() as tmp: + report = os.path.join(tmp, "report.xml") + code, text = run(test, ["-r", f"xml::out={report}"], timeout) + cases = {} + try: + root = ElementTree.parse(report).getroot() + except (ElementTree.ParseError, FileNotFoundError): + return code, None, text + for case in root.iter("TestCase"): + result = case.find("OverallResult") + if result is None: + cases[case.get("name")] = "fail" + continue + if result.get("success") != "true": + cases[case.get("name")] = "fail" + elif int(result.get("skips", "0")) > 0: + cases[case.get("name")] = "skip" + else: + cases[case.get("name")] = "ok" + return code, cases, text + + +def recount(path, cases): + """The Catch2 assertions in each named case's body, from the file itself.""" + with open(path, "rb") as f: + text = f.read().decode("utf-8", "surrogateescape") + masked = mask(text) + counts = {} + for m in re.finditer(r'TEST_CASE_METHOD\s*\(', masked): + open_paren = m.end() - 1 + close = matching(masked, open_paren) + header = text[open_paren + 1:close] + literal = re.search(r',\s*((?:"(?:[^"\\]|\\.)*"\s*)+),', header) + if not literal: + continue + name = "".join(unescape(p) for p in re.findall(r'"((?:[^"\\]|\\.)*)"', literal.group(1))) + brace = masked.find("{", close) + end = matching(masked, brace) + counts[name] = len(CATCH_ASSERTION.findall(masked, brace, end)) + return {name: counts.get(name) for name in cases} + + +def mask(text): + """Comments blanked and literals' insides replaced, offsets kept.""" + out = list(text) + i, n = 0, len(text) + while i < n: + if text.startswith("//", i): + j = text.find("\n", i) + j = n if j < 0 else j + out[i:j] = " " * (j - i) + i = j + elif text.startswith("/*", i): + j = text.find("*/", i + 2) + j = n if j < 0 else j + 2 + out[i:j] = [c if c == "\n" else " " for c in text[i:j]] + i = j + elif text[i] in "\"'": + if text[i] == "'" and i > 0 and text[i - 1].isalnum() and re.match(r"\d", text[i - 1]): + i += 1 + continue + q = text[i] + j = i + 1 + while j < n and text[j] != q and text[j] != "\n": + j += 2 if text[j] == "\\" else 1 + out[i + 1:j] = "_" * (j - i - 1) + i = j + 1 + else: + i += 1 + return "".join(out) + + +def matching(text, at): + depth = 0 + for i in range(at, len(text)): + if text[i] in "({[": + depth += 1 + elif text[i] in ")}]": + depth -= 1 + if depth == 0: + return i + return len(text) + + +def unescape(body): + """The value of the inside of a narrow string literal.""" + simple = {"n": "\n", "t": "\t", "r": "\r", "0": "\0", "\\": "\\", '"': '"', "'": "'", "a": "\a", + "b": "\b", "f": "\f", "v": "\v", "?": "?"} + out = [] + i = 0 + while i < len(body): + ch = body[i] + if ch == "\\" and i + 1 < len(body): + nxt = body[i + 1] + if nxt == "x": + m = re.match(r"[0-9A-Fa-f]+", body[i + 2:]) + out.append(chr(int(m.group(0), 16)) if m else "x") + i += 2 + (len(m.group(0)) if m else 0) + continue + if nxt in "01234567": + m = re.match(r"[0-7]{1,3}", body[i + 1:]) + out.append(chr(int(m.group(0), 8))) + i += 1 + len(m.group(0)) + continue + out.append(simple.get(nxt, nxt)) + i += 2 + continue + out.append(ch) + i += 1 + return "".join(out) + + +def check(args): + with open(args.mapping) as f: + mapping = json.load(f) + with open(args.tut) as f: + tut = json.load(f) + tests = ctest_tests(args.build_dir, args.config) + by_file = {} + for entry in mapping: + by_file.setdefault(entry["file"], []).append(entry) + files = sorted(by_file) if not args.files else [os.path.relpath(os.path.abspath(f), ROOT) for f in args.files] + + rows = [] + problems = [] + for path in files: + entries = by_file.get(path, []) + stem = os.path.splitext(os.path.basename(path))[0] + recorded = tut.get(stem) + if recorded is None: + problems.append(f"{path}: no TUT results recorded for {stem}") + continue + entry_names = [n for n, t in tests.items() if n.endswith("/" + stem)] + if len(entry_names) != 1: + problems.append(f"{path}: {len(entry_names)} Catch2 CTest entries end in /{stem}") + continue + code, cases, output = catch2_results(tests[entry_names[0]], args.timeout) + if cases is None: + problems.append(f"{path}: {entry_names[0]} gave no XML (exit {code}):\n{output[-2000:]}") + continue + guard = [line for line in output.splitlines() if line.startswith("State guard:")] + tut_results = {(r["group"], r["n"]): r for r in recorded["results"]} + counted = recount(os.path.join(ROOT, path), [e["catch2_name"] for e in entries if e["catch2_name"]]) + matched = agree = counts_ok = 0 + listed = [] + seen_cases = set() + for e in entries: + key = (e["group"], e["n"]) + t = tut_results.pop(key, None) + name = e["catch2_name"] + if e.get("not_compiled_here") or (e.get("past_group_limit") and t is None): + listed.append(f"{e['group']} {e['n']}: " + + ("not compiled here" if e.get("not_compiled_here") else "past its group's limit")) + if name in cases: + seen_cases.add(name) + continue + if t is None: + problems.append(f"{path}: {e['group']} test<{e['n']}> did not run under TUT") + continue + if not name or name not in cases: + problems.append(f"{path}: {e['group']} test<{e['n']}> has no Catch2 case" + + (f" ({name!r})" if name else "")) + continue + seen_cases.add(name) + matched += 1 + if e.get("tut_name") is not None and e["tut_name"] != t["name"]: + problems.append(f"{path}: {e['group']} test<{e['n']}>: TUT named it {t['name']!r}, " + f"the mapping {e['tut_name']!r}") + if cases[name] == t["result"]: + agree += 1 + else: + problems.append(f"{path}: {name!r}: TUT {t['said']!r}, Catch2 {cases[name]}") + static = counted.get(name) + if e["tut_assertions"] == e["catch2_assertions"] == static: + counts_ok += 1 + else: + problems.append(f"{path}: {name!r}: {e['tut_assertions']} TUT assertion calls, " + f"{e['catch2_assertions']} Catch2 assertions mapped, {static} in the file") + for key, t in sorted(tut_results.items()): + problems.append(f"{path}: TUT ran [{key[0]}, {key[1]}], which the mapping does not have") + for name in sorted(set(cases) - seen_cases): + problems.append(f"{path}: Catch2 case {name!r} is no TUT test of the mapping") + clean = not any(p.startswith(path + ":") for p in problems) + rows.append((path, len(recorded["results"]), len(cases), matched, agree, counts_ok, len(listed), code, + "ok" if clean else "MISMATCH")) + for line in listed + guard: + print(f"{path}: listed: {line}") + + header = ("file", "TUT", "Catch2", "matched", "agree", "counts", "listed", "exit", "parity") + widths = [max(len(str(r[i])) for r in rows + [header]) for i in range(len(header))] + for r in [header] + rows: + print(" ".join(str(v).ljust(w) if i == 0 else str(v).rjust(w) for i, (v, w) in enumerate(zip(r, widths)))) + for p in problems: + print(f"mismatch: {p}") + return 1 if problems or any(r[-1] != "ok" for r in rows) else 0 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0], + formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__) + parser.add_argument("--build-dir", default=os.path.join(ROOT, DEFAULT_BUILD)) + parser.add_argument("--config", default=DEFAULT_CONFIG) + parser.add_argument("--timeout", type=float, default=900, help="seconds a run may take") + sub = parser.add_subparsers(dest="step", required=True) + rec = sub.add_parser("record", help="run TUT executables and keep their results") + rec.add_argument("tests", nargs="+", help="CTest names of TUT tests") + rec.add_argument("--out", required=True, help="JSON to add the results to") + chk = sub.add_parser("check", help="run the converted files' Catch2 entries and compare") + chk.add_argument("--tut", required=True, help="what record kept") + chk.add_argument("--mapping", required=True, help="tut2catch.py's mapping") + chk.add_argument("files", nargs="*", help="only these converted files (default: all the mapping has)") + args = parser.parse_args() + return record(args) if args.step == "record" else check(args) + + +if __name__ == "__main__": + sys.exit(main()) From e0a6e4ea2a6e2a1f85bd46560a3c1687120edba9 Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 9 Oct 2026 02:39:34 -0400 Subject: [PATCH 09/55] The test harness's state guard may read the FP control register AlIntrinsicCheck keeps intrinsics in the SIMD ops layer and the tests that compare against the raw instruction. The state guard reads MXCSR on x86 to see whether a test case left the floating-point control state changed, something only the raw instruction does, in a branch of its own for each architecture, so it joins the files allowed to. Co-Authored-By: Claude Opus 5.5 --- indra/cmake/AlIntrinsicCheck.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/cmake/AlIntrinsicCheck.cmake b/indra/cmake/AlIntrinsicCheck.cmake index 1fa2bb0eab..9e744a10d6 100644 --- a/indra/cmake/AlIntrinsicCheck.cmake +++ b/indra/cmake/AlIntrinsicCheck.cmake @@ -17,7 +17,9 @@ if(NOT DEFINED SOURCE_DIR) endif() # The files that are the backend, and the tests and bench that compare -# against the raw instruction on purpose. +# against the raw instruction on purpose. The test harness's state guard +# reads the floating-point control register, which nothing but the raw +# instruction does, one branch an architecture. set( allowed llcommon/alsimd.h @@ -26,6 +28,7 @@ set( llmath/alsimdkernels.inl llmath/tests/alsimdkernels_test.cpp llmath/tests/llsimd_bench.cpp + test/alteststateguard.cpp ) # _mm_, _mm256_ and _mm512_ for x86; for NEON the ACLE names, a mnemonic From 08a02a5095143a6c995cbe38144b072907f9e7d1 Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 9 Oct 2026 02:43:12 -0400 Subject: [PATCH 10/55] An ISA tier's test binary may share its files, and skips below the tree's tier too The variants of an ISA_TIER binary build the same test file at other x86-64 tiers, so a file is now one binary's at each tier, not one binary's only. A variant links the tree's libraries, built at the tree's tier, so it now skips on a host below either: --isa-level takes the higher of the two. The TUT runner's docs said as much, but passed only the variant's own. Co-Authored-By: Claude Opus 5.5 --- indra/cmake/ALAddTestBinary.cmake | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index 9e98e0ea56..b6e144e1ca 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -29,7 +29,7 @@ endif() # benchmarks, so the filter leaves those out, as a run with no filter does. A # filter that matches nothing exits 2, so a file whose test cases went missing # fails rather than passing empty. A stem names one file in a binary, and a -# file belongs to one binary. +# file belongs to one binary, or to one at each ISA tier. # # /combined runs all of them in one process. It is labelled # combined_pending, which the default run leaves out, until the binary's tests @@ -42,7 +42,8 @@ endif() # registered disabled without AL_ENABLE_GL_TESTS, a PYTHON binary is given the # interpreter and registered disabled without one that has llsd, and an # ISA_TIER binary is built at that x86-64 tier, named _, labelled -# isa, and skips on a host below it. RESOURCE_LOCK names what the binary's +# isa, and skips on a host below it -- or below the tree's own tier, since it +# links the tree's libraries, built at that one. RESOURCE_LOCK names what the binary's # entries hold that no two may hold at once, such as a fixed port. function(al_add_test_binary name) cmake_parse_arguments( @@ -86,8 +87,10 @@ function(al_add_test_binary name) endif() list(APPEND stems ${stem}) + # A file is in one binary at each tier: the variants of an ISA_TIER + # binary build the same files at the others. get_filename_component(path "${source}" ABSOLUTE) - string(MD5 key "${path}") + string(MD5 key "${path}${arg_ISA_TIER}") get_property(owner GLOBAL PROPERTY AL_TEST_SOURCE_${key}) if(owner) message( @@ -129,6 +132,10 @@ function(al_add_test_binary name) set(timeout 300) if(arg_ISA_TIER) al_isa_level(${arg_ISA_TIER} ${CMAKE_SYSTEM_NAME} ${ARCH} level) + al_isa_level(${AL_ISA_TIER} ${CMAKE_SYSTEM_NAME} ${ARCH} tree_level) + if(tree_level GREATER level) + set(level ${tree_level}) + endif() list(APPEND arguments --isa-level ${level}) list(APPEND labels isa) endif() From 40f4ffe733e91cecc10d08d8b50fb9e5e5018c10 Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 9 Oct 2026 02:43:20 -0400 Subject: [PATCH 11/55] CI runs the Catch2 binaries' pending combined runs, and reports them without failing A binary's run of all its tests in one process is combined_pending, left out of the default run, until it is known to share a process. That is learned on every CI row: a step after the build runs the pending ones, writes their results beside the rest, and never fails the job, so a binary's combined run can be relabelled once every row shows it green. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b4ca99cd5f..5fc27f25fd 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -514,6 +514,27 @@ jobs: esac fi + # Each Catch2 binary's run of all its tests in one process, while it is + # still combined_pending: the default run leaves those out until they + # are known to share a process, and this is where that is learned, on + # every row. It reports, and never fails the job. + - name: Run pending combined tests + if: ${{ !cancelled() && matrix.build_variant == 'Tests' }} + continue-on-error: true + shell: bash + env: + BUILD_DIRECTORY: ${{ steps.configure.outputs.build_directory }} + run: | + case "$BUILD_CONFIG" in + release) cmake_config=Release ;; + relwithdebinfo) cmake_config=RelWithDebInfo ;; + optdebug) cmake_config=OptDebug ;; + *) cmake_config=Debug ;; + esac + ctest --test-dir "$BUILD_DIRECTORY" -C "$cmake_config" -L combined_pending \ + --output-on-failure --no-tests=ignore \ + --output-junit "$BUILD_DIRECTORY/Testing/combined-pending-results.xml" + # What each test did, on a row whose tests failed as much as on one # whose tests passed: the JUnit file CTest writes and its own log. The # build step's artifact name is unset when a test fails, so this one @@ -525,6 +546,7 @@ jobs: name: "test-results-${{ runner.os }}-${{ matrix.arch }}" path: | ${{ steps.configure.outputs.build_directory }}/Testing/test-results.xml + ${{ steps.configure.outputs.build_directory }}/Testing/combined-pending-results.xml ${{ steps.configure.outputs.build_directory }}/Testing/Temporary/LastTest*.log if-no-files-found: warn From 8df7940bd15e717d37c158de8bd464c2bdc73731 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 10:28:36 -0400 Subject: [PATCH 12/55] tut2catch.py writes Catch2 that reports every failure and prints what it compared Its first output was TUT spelt in Catch2's macros: every assertion a REQUIRE, so a case stopped at its first failure; a third of them wrapped in parentheses Catch2 cannot look into, so a failure printed "false" and not the values; every case a fixture, most of them empty; names like "v3math_h: #7"; and namespace tut left around it all. - CHECK is the default, so a run reports every failure. REQUIRE stays where going on could only crash or bury the first failure: a guard (a pointer, a size, an emptiness, an LLSD's shape), a loop or a lambda, and a helper, which any caller may call in a loop. - ensure(a && b && c) is an assertion a term, each printing its operands; its message goes with all of them, in a scoped INFO. - Parentheses a test wrote around a whole go; a literal compared on the left moves right; x == true is CHECK(x), !x CHECK_FALSE(x); strcmp(a, b) == 0 compares std::string_views, which print. - A case is named from the first sentence of the comment written above its test, where there is one, or from --names, a JSON of names by group and number, which lets a person name the rest and keep the proof. - A group whose data struct holds nothing is plain TEST_CASEs, and the struct goes. namespace tut, once nothing names tut:: and no type production code befriends is in it, becomes an anonymous namespace. tut_parity.py expects as many assertions as the converter made, an && chain counting one a term, and reads plain TEST_CASEs. Its --recounted holds names and results to the mapping but only lists assertion counts, for files edited by hand after converting. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 574 +++++++++++++++++++++++++++++++--- scripts/testing/tut_parity.py | 26 +- 2 files changed, 551 insertions(+), 49 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index fc9c2a69f1..3585a5c875 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -7,22 +7,38 @@ structure every test of a group becomes TEST_CASE_METHOD(, ": ", "[]") with the group's data struct as the fixture, built fresh for - each case as TUT built it for each test. The name is the - test's set_test_name literal, or #N when it has none; " #N" - is added to a name another case of the file has (ignoring - case). Tests declared out of TUT's numeric order are moved - later in the file, never earlier, so that declaration order, - Catch2's, is TUT's. The group's typedefs, its test_group - object and a class derived from test_group only to name it - go. - assertions ensure(c) -> REQUIRE(c); ensure(msg, c) -> - UNSCOPED_INFO(msg) then REQUIRE(c), braced where the call was - a lone statement of an if, else or loop; ensure_not -> - REQUIRE_FALSE; ensure_equals -> REQUIRE(a == b), except - where both values are LLSD (REQUIRE_THAT with + each case as TUT built it for each test; or a plain + TEST_CASE(": ", "[]") where the struct + holds nothing and nothing else names it, and the struct goes. + The name is, first, one --names gives; then the test's + set_test_name literal; then the first sentence of the + comment written above the test, where that is prose; else + #N. " #N" is added to a name another case of the file has + (ignoring case). Tests declared out of TUT's numeric order + are moved later in the file, never earlier, so that + declaration order, Catch2's, is TUT's. The group's typedefs, + its test_group object and a class derived from test_group + only to name it go. namespace tut, once nothing names tut:: + and no type production code befriends as tut:: is in it, + becomes an anonymous namespace, taking in one directly inside + it. + assertions CHECK, which lets a case go on and so reports every failure + of a run; REQUIRE where going on could only crash or bury the + first failure: a condition that guards what follows (a + pointer, a size, an emptiness, a presence), in a loop or a + lambda, and in a helper outside a test's body. In a + destructor, where a throw would end the process, CHECK + always. With that macro (X below): + ensure(c) -> X(c); ensure(a && b && ...) -> X(a); X(b); ... + one assertion a term, so that each prints its operands; + ensure(msg, c) -> UNSCOPED_INFO(msg) then X(c), or, where c + split, { INFO(msg); X(a); X(b); } so the message goes with + each; braced where the call was a lone statement of an if, + else or loop; ensure_not -> X_FALSE; ensure_equals -> X(a == + b), except where both values are LLSD (X_THAT with ll_test::LLSDEquals, the deep compare TUT's overload made) or LLURI (their asString()s, as TUT compared them); - ensure_not_equals -> REQUIRE(a != b); ensure_distance and + ensure_not_equals -> X(a != b); ensure_distance and ensure_approximately_equals_range -> WithinAbs; ensure_approximately_equals -> ll_test::WithinBits, at the precision of the overload TUT chose; ensure_contains, @@ -30,12 +46,14 @@ matchers; ensure_memory_matches -> ll_test::MemoryMatches; fail -> FAIL; skip -> SKIP; throw tut::failure(m) -> FAIL(m); get_test_name() -> ll_test::currentTestName(); a catch of - tut::failure that rethrows -> ll_test::Failure. In a - destructor, where a throw would end the process, the CHECK - forms. Operands are parenthesised where a macro or an - operator would split or rebind them; a condition Catch2 - cannot decompose (&&, ||, ?:) is parenthesised whole rather - than split, so each TUT call stays one assertion. + tut::failure that rethrows -> ll_test::Failure. A condition + is written so that Catch2 decomposes it and prints its + operands: parentheses the test wrote around the whole go; a + literal compared on the left moves right; x == true is X(x) + and x == false X_FALSE(x); strcmp(a, b) == 0 compares + std::string_views. Operands are parenthesised where a macro + or an operator would split or rebind them; a condition + Catch2 cannot decompose (||, ?:) is parenthesised whole. includes lltut.h and tut.hpp give way to the Catch2 headers the file now uses, and altestmatchers.h, altestname.h or altestfailure.h where it uses them. @@ -108,6 +126,15 @@ BODY_PARENTS = {"IfStmt", "ForStmt", "WhileStmt", "DoStmt", "CXXForRangeStmt"} FUNCTION_KINDS = {"FunctionDecl", "CXXMethodDecl", "CXXConstructorDecl", "CXXDestructorDecl", "CXXConversionDecl", "LambdaExpr"} +LOOP_KINDS = {"ForStmt", "WhileStmt", "DoStmt", "CXXForRangeStmt"} +# What a condition asks of an object when what follows relies on the answer: +# that it holds something, or how much. +GUARD_MEMBERS = {"size", "empty", "length", "count", "isDefined", "isUndefined", "has", "isMap", "isArray", + "notNull", "isNull"} +LITERAL_KINDS = {"IntegerLiteral", "FloatingLiteral", "CXXBoolLiteralExpr", "CharacterLiteral", "StringLiteral", + "CXXNullPtrLiteralExpr", "GNUNullExpr", "FixedPointLiteral"} +# A comment that names nothing: a separator, or a note to whoever reads on. +NOT_A_NAME = re.compile(r"^(?:[-=*/#~_ ]*|(?:TODO|FIXME|XXX|HACK|NB|NOTE|BUG)\b.*)$", re.I) class ConvertError(Exception): @@ -660,6 +687,134 @@ def decomposable(node): return True +def unparenthesised(node): + """node, without the parentheses a test wrote around a whole Catch2 can + decompose, and without the implicit nodes around that.""" + node = skip_implicit(node) + while node.get("kind") == "ParenExpr": + inner = [c for c in node.get("inner", []) if "kind" in c] + if not inner or not decomposable(inner[0]): + break + node = skip_implicit(inner[0]) + return node + + +def and_terms(node): + """The terms of a && chain, each as the test wrote it, or [node].""" + node = skip_implicit(node) + while node.get("kind") == "ParenExpr": + inner = [c for c in node.get("inner", []) if "kind" in c] + if not inner: + break + inside = skip_implicit(inner[0]) + if inside.get("kind") != "BinaryOperator" or inside.get("opcode") != "&&": + break + node = inside + if node.get("kind") == "BinaryOperator" and node.get("opcode") == "&&": + inner = [c for c in node.get("inner", []) if "kind" in c] + if len(inner) == 2: + return and_terms(inner[0]) + and_terms(inner[1]) + return [node] + + +def operands(node): + """A comparison's two operands, implicit nodes and all, or None.""" + node = skip_implicit(node) + inner = [c for c in node.get("inner", []) if "kind" in c] + if node.get("kind") == "BinaryOperator" and len(inner) == 2: + return inner + return None + + +def is_literal(node): + node = skip_implicit(node) + if node.get("kind") == "UnaryOperator" and node.get("opcode") in ("-", "+"): + inner = [c for c in node.get("inner", []) if "kind" in c] + return bool(inner) and is_literal(inner[0]) + if node.get("kind") == "DeclRefExpr" and node.get("referencedDecl", {}).get("kind") == "EnumConstantDecl": + return True + return node.get("kind") in LITERAL_KINDS + + +def strcmp_args(node): + """The two arguments of a strcmp() call, or None.""" + node = skip_implicit(node) + if node.get("kind") != "CallExpr": + return None + inner = [c for c in node.get("inner", []) if "kind" in c] + if len(inner) != 3: + return None + callee = inner[0] + while callee.get("kind") in ("ImplicitCastExpr", "ParenExpr") and callee.get("inner"): + callee = callee["inner"][0] + if callee.get("kind") != "DeclRefExpr" or callee.get("referencedDecl", {}).get("name") not in \ + ("strcmp", "__builtin_strcmp"): + return None + return inner[1:] + + +def value_args(name, args): + """The arguments a TUT call asserts about, without its message.""" + if name in ("ensure", "ensure_not"): + return args[-1:] + if name in ("ensure_equals", "ensure_not_equals"): + return args[-2:] + if name in ("ensure_distance", "ensure_approximately_equals", "ensure_approximately_equals_range"): + return args[-3:] + if name in ("ensure_contains", "ensure_does_not_contain", "ensure_starts_with", "ensure_ends_with"): + return args[1:] + if name == "ensure_memory_matches": + return args[-4:] + return [] + + +def name_from_comment(text): + """A case's name from the comment above its test: the first sentence of + the comment's first paragraph, or None where the comment is not prose.""" + lines = [] + for raw in text.splitlines(): + s = raw.strip() + s = re.sub(r"^(?://+!?|/\*+!?|\*+(?!/))\s?", "", s) + s = re.sub(r"\*+/\s*$", "", s).strip() + s = re.sub(r"^@(?:brief|test|note)\s+", "", s) + lines.append(s) + paragraph = [] + for s in lines: + if not s: + if paragraph: + break + continue + paragraph.append(s) + text = re.sub(r"\s+", " ", " ".join(paragraph)).strip() + if not text or NOT_A_NAME.match(text): + return None + # Commented-out code, or a test's number, names nothing. + if re.search(r";\s*$|[{}]|==|->|::\w+\(|^\w+\s*\(.*\)\s*;?$|\btest\s*<\s*\d+\s*>|^#", text): + return None + if len(re.findall(r"[A-Za-z]{2,}", text)) < 2: + return None + # The first sentence: a full stop before a space or the end, not the one + # in "e.g." or after an initial. + sentence = text + for m in re.finditer(r"[.!?](?=\s|$)", text): + if re.search(r"(?:\be\.g|\bi\.e|\betc|\bvs|\bcf|\b[A-Za-z])$", text[:m.start()]): + continue + sentence = text[:m.start() + (1 if text[m.start()] in "!?" else 0)] + break + sentence = sentence.rstrip(" .:;,").strip() + if len(sentence) > 100: + cut = sentence[:100] + for sep in ("; ", ": ", " -- ", ", "): + i = cut.rfind(sep) + if i >= 40: + sentence = cut[:i] + break + else: + sentence = cut.rsplit(" ", 1)[0] + sentence = sentence.rstrip(" .:;,") + return sentence or None + + def is_null_message(node): node = skip_implicit(node) if node.get("kind") in ("GNUNullExpr", "CXXNullPtrLiteralExpr"): @@ -771,6 +926,11 @@ def __init__(self, source, items, tut_ids, active_lines, demangled): self.derived_groups = {} # class derived from test_group -> (data, limit, name) self.removed_spans = [] self.slot_at = {} # chunk start -> the plan that puts tests there + self.emitted = {} # offset of a converted call -> the Catch2 assertions it became + self.records = {} # qualified name of a class -> its CXXRecordDecl + self.name_overrides = {} # (group, n) -> a case's name, from --names + self.friend_names = set() # tut:: types production code befriends + self.tut_namespaces = [] # NamespaceDecl nodes named tut, with their parents source.find_conditionals(active_lines) # ---- helpers ------------------------------------------------------- @@ -807,7 +967,12 @@ def walk(self, node, parents): elif kind == "VarDecl": self.on_var(node, parents) elif kind == "CXXRecordDecl": + if node.get("completeDefinition") and node.get("name"): + spaces = [p.get("name") or "(anonymous namespace)" for p in parents if p.get("kind") == "NamespaceDecl"] + self.records.setdefault("::".join(spaces + [node["name"]]), node) self.on_record(node, parents) + elif kind == "NamespaceDecl" and node.get("name") == "tut": + self.tut_namespaces.append((node, parents)) elif kind in ("CallExpr", "CXXMemberCallExpr"): self.on_call(node, parents) elif kind == "CXXThrowExpr": @@ -1122,6 +1287,139 @@ def in_destructor(self, record): return parent.get("kind") == "CXXDestructorDecl" return False + def strength(self, record, nodes): + """CHECK, which lets the case go on past a failure and so reports every + failure of a run, or REQUIRE, which ends it, where going on could + only crash or bury the first failure under its consequences: + - in a destructor, CHECK, since a throw would end the process; + - outside a test's own body (a helper, which any caller may call + in a loop) and in a loop or lambda of one, REQUIRE, so that the + first element that fails is the one reported; + - where the condition guards what follows (a pointer not null, a + size, an emptiness, a presence), REQUIRE; + - otherwise CHECK.""" + if self.in_destructor(record): + return "CHECK" + test = self.test_containing(record["range"][0]) + body = test_body(test) if test else None + if body is None: + return "REQUIRE" + for parent in reversed(record["parents"]): + if parent is body: + break + if parent.get("kind") in LOOP_KINDS or parent.get("kind") == "LambdaExpr": + return "REQUIRE" + if any(self.guards(n) for n in nodes if n): + return "REQUIRE" + return "CHECK" + + @staticmethod + def guards(node): + """Whether a condition or value tells what the code after it relies + on: a pointer, compared with null or tested as a bool, or a member + that says how much an object holds or whether it holds anything. + Default arguments are not looked in: a NULL a callee defaults to is + nothing the test compared.""" + if value_type(unparenthesised(node)).endswith("*"): + return True + stack = [node] + while stack: + sub = stack.pop() + kind = sub.get("kind") + if kind == "CXXDefaultArgExpr": + continue + if kind == "ImplicitCastExpr" and sub.get("castKind") == "PointerToBoolean": + return True + if kind == "BinaryOperator" and sub.get("opcode") in ("==", "!="): + if any(skip_implicit(c).get("kind") in ("CXXNullPtrLiteralExpr", "GNUNullExpr") or + (c.get("kind") == "ImplicitCastExpr" and c.get("castKind") == "NullToPointer") + for c in sub.get("inner", []) if "kind" in c): + return True + if kind == "MemberExpr" and sub.get("name") in GUARD_MEMBERS: + # isNull() and notNull() guard only on what points: on a + # vector or a UUID they are values like any other. + if sub.get("name") not in ("isNull", "notNull"): + return True + base = [c for c in sub.get("inner", []) if "kind" in c] + if base and re.search(r"Pointer|ptr\b|Handle|\*", value_type(skip_implicit(base[0]))): + return True + stack.extend(c for c in sub.get("inner", []) if isinstance(c, dict) and "kind" in c) + return False + + def plain_span(self, node): + """[start, end) of a node written in this file's text, or None.""" + rng = self.node_range(node) + if rng is None or rng[2] not in ("file", "macro-arg") or rng[3] not in ("file", "macro-arg"): + return None + return rng[0], rng[1] + + def assertion(self, node, span, negate): + """(suffix, text) of the one assertion a condition is: "" or "_FALSE" + after CHECK or REQUIRE, and what goes between its parentheses. + Parentheses the test wrote around a whole Catch2 decomposes go, so + that a failure prints its operands; a literal compared on the left + moves right; a bool compared with true or false is asserted as + itself; and strcmp() is compared as std::string_view, which prints.""" + suffix = "_FALSE" if negate else "" + whole = self.render(*span) + node = unparenthesised(node) + nspan = self.plain_span(node) + if nspan is None: + return suffix, whole if decomposable(node) else "(" + whole + ")" + pair = operands(node) + op = node.get("opcode") if pair else None + if op in ("==", "!="): + left, right = (skip_implicit(p) for p in pair) + spans = [self.plain_span(p) for p in pair] + if None not in spans: + for lit, other, other_span in ((left, right, spans[1]), (right, left, spans[0])): + if lit.get("kind") == "CXXBoolLiteralExpr" and value_type(other) == "bool": + truth = (lit.get("value") is True) == (op == "==") + other_node = unparenthesised(other) + inner = self.render(*(self.plain_span(other_node) or other_span)) + if not decomposable(other_node) and not wrapped(inner): + inner = "(" + inner + ")" + return ("" if truth != negate else "_FALSE"), inner + for zero, call in ((left, right), (right, left)): + args = strcmp_args(call) + if args and zero.get("kind") == "IntegerLiteral" and zero.get("value") == "0": + text = self.string_views(args, op) + if text: + return suffix, text + if is_literal(left) and not is_literal(right): + return suffix, f"{self.render(*spans[1])} {op} {self.render(*spans[0])}" + if node.get("kind") == "UnaryOperator" and node.get("opcode") == "!": + inner = [c for c in node.get("inner", []) if "kind" in c] + args = strcmp_args(inner[0]) if inner else None + text = self.string_views(args, "==") if args else None + if text: + return suffix, text + # !x is x, asserted false, so that x prints. + operand = unparenthesised(inner[0]) if inner else None + ospan = self.plain_span(operand) if operand else None + if ospan: + text = self.render(*ospan) + return ("" if negate else "_FALSE"), text if decomposable(operand) else "(" + text + ")" + args = strcmp_args(node) + text = self.string_views(args, "!=") if args else None + if text: + return suffix, text + text = self.render(*nspan) + return suffix, text if decomposable(node) else "(" + text + ")" + + def string_views(self, args, op): + spans = [self.plain_span(a) for a in args] + if None in spans: + return None + texts = [] + for arg, span in zip(args, spans): + text = self.render(*span) + texts.append(text if skip_implicit(arg).get("kind") == "StringLiteral" else f"std::string_view({text})") + if not texts[0].startswith("std::string_view("): + texts.reverse() + self.headers.add("") + return f"{texts[0]} {op} {texts[1]}" + def arg_ranges(self, record): """[start, end) of each argument.""" start, end = record["range"][0], record["range"][1] @@ -1213,29 +1511,38 @@ def convert_call(self, record, kind): if kind == "bits-?": self.check(start, "cannot tell which ensure_approximately_equals overload was called") return - build = self.builder(name, kind, record, ranges, "CHECK" if check_form else "REQUIRE") + build = self.builder(name, kind, record, ranges, self.strength(record, value_args(name, args))) if build is not None: self.headers.update(headers_for(name, kind)) + if any(strcmp_args(sub) for a in args for sub in iter_nodes(a)): + self.headers.add("") if build is None: self.check(start, f"{name}() with {len(args)} arguments is not a form this converter knows") return + block = getattr(build, "block", False) stmt_end = end - if context == "body": + if context == "body" or block: after = self.src.skip_space(end) if self.src.masked[after:after + 1] != b";": - self.check(start, f"{name}() as a lone statement is not followed by its semicolon") + self.check(start, f"{name}() as a statement is not followed by its semicolon") return stmt_end = after + 1 - def render(conv, plan, build=build, context=context, start=start): + def render(conv, plan, build=build, context=context, start=start, block=block): statements = build() + flat = [s for st in statements for s in (st[1] if isinstance(st, tuple) else [st])] + conv.emitted[start] = sum(1 for s in flat if CATCH_ASSERTION.match(s.encode())) if context == "body": - if len(statements) > 1: - return "{ " + "; ".join(statements) + "; }" - return statements[0] + ";" + if len(flat) > 1: + return "{ " + "; ".join(flat) + "; }" + return flat[0] + ";" + indent = conv.src.indent_at(start).decode() + if block: + unit = "\t" if indent.startswith("\t") else " " + return "{\n" + "".join(f"{indent}{unit}{s};\n" for s in flat) + indent + "}" if conv.src.starts_line(start): - return (";\n" + conv.src.indent_at(start).decode()).join(statements) - return "; ".join(statements) + return (";\n" + indent).join(flat) + return "; ".join(flat) self.plans.append(Plan(start, stmt_end, render)) def builder(self, name, kind, record, ranges, req): @@ -1258,10 +1565,26 @@ def streamed(i): if name in ("ensure", "ensure_not"): if n not in (1, 2): return None - macro = req + ("_FALSE" if name == "ensure_not" else "") + condition = args[n - 1] + # A && chain is as many assertions, each printing its operands, + # each as strong as its own term needs. + terms = and_terms(condition) if name == "ensure" else [condition] + if len(terms) > 1 and any(self.plain_span(t) is None for t in terms): + terms = [condition] def build(): - return (message(0) if n == 2 else []) + [f"{macro}({self.condition(ranges[n - 1], args[n - 1])})"] + out = [] + for term in terms: + span = ranges[n - 1] if len(terms) == 1 else self.plain_span(term) + suffix, text = self.assertion(term, span, name == "ensure_not") + strength = req if len(terms) == 1 else self.strength(record, [term]) + out.append(f"{strength}{suffix}({text})") + msg = message(0) if n == 2 else [] + if msg and len(out) > 1: + # The message is about all of them: INFO, scoped to them. + return [("block", ["INFO" + msg[0][len("UNSCOPED_INFO"):]] + out)] + return msg + out + build.block = n == 2 and not is_null_message(args[0]) and len(terms) > 1 return build if name in ("fail", "skip", "throw"): if n != 1: @@ -1661,6 +1984,14 @@ def name_tests(self): test.name_literal_inner = "".join(pieces) self.remove_statement(offset, call_end + 1) test.tut_name = text_name if text_name is not None else ("" if not test.names else None) + # A name given in --names wins; then set_test_name's; then the + # first sentence of the comment written above the test. + given = self.name_overrides.get((group.name, test.n)) + if given is None and text_name is None: + given = self.comment_name(test) + if given is not None: + text_name = given + test.name_literal_inner = c_escape(given) base = f"{group.name}: {text_name}" if text_name is not None else f"{group.name}: #{test.n}" # Tests in different branches of an #if are never compiled # together, so their names cannot clash. @@ -1675,6 +2006,23 @@ def name_tests(self): else: test.catch2_literal = '"' + c_escape(name) + '"' + def comment_name(self, test): + """A name from the comment lines right above a test, or None.""" + if test.start is None: + return None + start = self.src.line_start(test.start) + top = start + while top > 0: + prev = self.src.line_start(top - 1) + if self.src.masked[prev:top].strip() == b"" and self.src.data[prev:top].strip(): + top = prev + continue + break + text = self.text(top, start) + if not text.strip(): + text = "\n".join(self.comments_in(test.start, test.header_end)) + return name_from_comment(text) + def remove_statement(self, start, end): after = self.src.skip_space(end) if self.src.masked[after:after + 1] == b";": @@ -1682,15 +2030,49 @@ def remove_statement(self, start, end): self.removals.append((start, end, "statement")) # ---- the plans ---------------------------------------------------- + def plan_fixtures(self): + """A group whose data struct holds nothing, and which nothing else in + the file names, is plain TEST_CASEs: the struct goes.""" + for data, group in self.groups.items(): + group.fixtureless = False + tests = [t for t in group.tests if not t.macro and t.catch2_name] + if not tests or any(t.inactive for t in group.tests): + continue + node = self.records.get(tests[0].data) or self.records.get(data) + if node is None: + short = data.replace("(anonymous namespace)::", "").rpartition("::")[2] + found = [n for k, n in self.records.items() if k.rpartition("::")[2] == short] + node = found[0] if len(found) == 1 else None + if node is None or node.get("bases") or node.get("name") in self.friend_names: + continue + if [c for c in node.get("inner", []) if "kind" in c and not c.get("isImplicit")]: + continue + rng = self.node_range(node) + if rng is None or rng[2] != "file": + continue + spans = [(rng[0], rng[1])] + [(s, e) for s, e, _ in self.removals] + \ + [(t.start, t.header_end) for t in group.tests if t.start is not None and t.header_end] + named = [m.start() for m in re.finditer(rb"\b" + re.escape(node["name"].encode()) + rb"\b", + self.src.masked)] + if any(not any(s <= at < e for s, e in spans) for at in named): + continue + group.fixtureless = True + self.remove_decl(node) + def plan_structure(self): for test in self.tests: if test.macro or test.group is None or test.catch2_name is None: continue - if "," in test.fixture: - self.check(test.start, f"the fixture {test.fixture} has a comma, which TEST_CASE_METHOD would split") - continue tag = group_tag(test.group.name) - header = f'TEST_CASE_METHOD({test.fixture}, {test.catch2_literal}, "[{tag}]")' + if getattr(test.group, "fixtureless", False): + test.header_text = f"TEST_CASE({test.catch2_literal}," + header = f'TEST_CASE({test.catch2_literal}, "[{tag}]")' + else: + if "," in test.fixture: + self.check(test.start, f"the fixture {test.fixture} has a comma, which TEST_CASE_METHOD would split") + continue + test.header_text = f"TEST_CASE_METHOD({test.fixture}, {test.catch2_literal}," + header = f'TEST_CASE_METHOD({test.fixture}, {test.catch2_literal}, "[{tag}]")' # A comment inside template<> template<> void X::test() stays, # on a line of its own above the case. indent = self.src.indent_at(test.start).decode() @@ -1715,6 +2097,62 @@ def plan_structure(self): for start, end in self.removed_spans: self.plans.append(Plan(start, end, lambda conv, plan: "")) + def can_unwrap(self, converted): + """Whether namespace tut is only TUT's left: nothing names tut::, no + type production code befriends as tut:: is here, and no call is left + unconverted that would look for TUT in it.""" + if self.checks or not self.tut_namespaces: + return False + masked = mask_source(converted.encode("utf-8", "surrogateescape")) + if re.search(rb"\btut\s*::|\busing\s+namespace\s+tut\b", masked): + return False + return not any(re.search(rb"\b" + re.escape(n.encode()) + rb"\b", masked) for n in self.friend_names) + + def plan_unwrap(self): + """namespace tut becomes an anonymous namespace, and an anonymous + namespace written directly inside it, on lines of its own, merges + into it.""" + taken = [(p.start, p.end) for p in self.plans if p.start != p.end] + + def free(s, e): + return not any(a < e and s < b for a, b in taken) + size = len(self.src.data) + for node, parents in self.tut_namespaces: + rng = self.node_range(node) + if rng is None or rng[2] != "file" or any(p.get("kind") == "NamespaceDecl" for p in parents): + continue + m = re.compile(rb"namespace\s+tut\b").match(self.src.masked, rng[0]) + if not m or not free(m.start(), m.end()): + continue + self.plans.append(Plan(m.start(), m.end(), lambda conv, plan: "namespace")) + # A comment on its closing brace that says whose it was says so no + # longer. + note = re.compile(rb"//[ \t]*(?:end[ \t]+(?:of[ \t]+)?)?(?:namespace[ \t]+)?tut\b[^\n]*").search( + self.src.data, rng[1], self.src.line_end(rng[1] - 1)) + if note and free(note.start(), note.end()): + self.plans.append(Plan(note.start(), note.end(), lambda conv, plan: "// anonymous namespace")) + for child in node.get("inner", []): + if child.get("kind") != "NamespaceDecl" or child.get("name"): + continue + crng = self.node_range(child) + if crng is None or crng[2] != "file": + continue + opener = self.src.masked.find(b"{", crng[0]) + close = crng[1] + if opener < 0 or not re.fullmatch(rb"namespace\s*", self.src.masked[crng[0]:opener]): + continue + os_, oe = self.src.line_start(crng[0]), self.src.line_end(opener) + cs, ce = self.src.line_start(close - 1), self.src.line_end(close - 1) + if self.src.masked[os_:crng[0]].strip() or self.src.masked[opener + 1:oe].strip() or \ + self.src.masked[cs:close - 1].strip() or self.src.masked[close:ce].strip(): + continue + oe, ce = min(oe + 1, size), min(ce + 1, size) + if not (free(os_, oe) and free(cs, ce)): + continue + self.plans.append(Plan(os_, oe, lambda conv, plan: "")) + self.plans.append(Plan(cs, ce, lambda conv, plan: "")) + taken += [(os_, oe), (cs, ce)] + def comment_above(self, start): """Where the comment lines right above a removed declaration begin, if they stand apart from what precedes them, as a comment about the @@ -1930,15 +2368,19 @@ def counts(self, converted): """Per test: the TUT assertion calls in its body before, and the Catch2 assertions in it after.""" tut = {} + expected = {} for test in self.tests: if test.body_start is None or test.body_end is None: continue - tut[id(test)] = sum(1 for o in self.counted if test.body_start <= o < test.body_end) + calls = [o for o in self.counted if test.body_start <= o < test.body_end] + tut[id(test)] = len(calls) + # A split && chain is more than one assertion for one TUT call. + expected[id(test)] = sum(self.emitted.get(o, 1) for o in calls) data = converted.encode("utf-8", "surrogateescape") catch = {id(t): n for t, n in count_catch_assertions(data, [ - (t, f"TEST_CASE_METHOD({t.fixture}, {t.catch2_literal},") for t in self.tests - if t.catch2_name and not t.macro])} - return tut, catch + (t, getattr(t, "header_text", f"TEST_CASE_METHOD({t.fixture}, {t.catch2_literal},")) + for t in self.tests if t.catch2_name and not t.macro])} + return tut, catch, expected def count_catch_assertions(data, cases): @@ -2137,7 +2579,40 @@ def group_tag(name): # -------------------------------------------------------------------------- # One file # -------------------------------------------------------------------------- -def convert_file(path, build_dir, config, dry_run, output_dir=None): +_FRIENDS = None + + +def tut_friends(): + """The tut:: types production code names as friends: they keep their + namespace, so a file holding one keeps namespace tut.""" + global _FRIENDS + if _FRIENDS is None: + _FRIENDS = set() + for top, dirs, files in os.walk(os.path.join(ROOT, "indra")): + dirs[:] = [d for d in dirs if d not in ("tests", "test", "externals")] + for f in files: + if f.endswith((".h", ".hpp", ".inl")): + with open(os.path.join(top, f), "rb") as fh: + for m in re.finditer(rb"friend\s+(?:struct|class)\s+tut\s*::\s*(\w+)", fh.read()): + _FRIENDS.add(m.group(1).decode()) + return _FRIENDS + + +def load_names(path): + """--names: {"": {"": {"": ""}}}.""" + if not path: + return {} + with open(path) as f: + data = json.load(f) + out = {} + for file, groups in data.items(): + for group, tests in groups.items(): + for n, name in tests.items(): + out[(os.path.normpath(file), group, int(n))] = name + return out + + +def convert_file(path, build_dir, config, dry_run, output_dir=None, names=None): db = CompileDatabase(build_dir, config) source = Source(path) if not TUT_INCLUDE.search(source.data) and b"test_group" not in source.data: @@ -2150,6 +2625,9 @@ def convert_file(path, build_dir, config, dry_run, output_dir=None): if n.get("kind") == "CXXMethodDecl" and n.get("name") == "test" and "test_object" in n.get("mangledName", "")}) conv = Converter(source, items, tut_ids, active_lines, demangle(mangled)) + rel = os.path.normpath(os.path.relpath(path, ROOT)) + conv.name_overrides = {(g, n): name for (f, g, n), name in (names or {}).items() if f == rel} + conv.friend_names = tut_friends() sys.setrecursionlimit(max(sys.getrecursionlimit(), 20000)) conv.analyse() conv.resolve_calls() @@ -2157,12 +2635,16 @@ def convert_file(path, build_dir, config, dry_run, output_dir=None): conv.resolve_tests() conv.remove_uncompiled_groups() conv.name_tests() + conv.plan_fixtures() conv.plan_structure() conv.plan_order() conv.plan_includes() conv.plan_checks() converted = conv.finish() - tut_counts, catch_counts = conv.counts(converted) + if conv.can_unwrap(converted): + conv.plan_unwrap() + converted = conv.finish() + tut_counts, catch_counts, expected_counts = conv.counts(converted) if output_dir: out = os.path.join(output_dir, os.path.relpath(path, ROOT)) os.makedirs(os.path.dirname(out), exist_ok=True) @@ -2181,6 +2663,7 @@ def convert_file(path, build_dir, config, dry_run, output_dir=None): "tut_name": test.tut_name, "catch2_name": test.catch2_name, "tut_assertions": tut_counts.get(id(test)), + "catch2_expected": expected_counts.get(id(test)), "catch2_assertions": catch_counts.get(id(test)), "not_compiled_here": test.inactive, "past_group_limit": bool(test.group and test.n and test.n > test.group.limit), @@ -2192,12 +2675,12 @@ def convert_file(path, build_dir, config, dry_run, output_dir=None): def convert_one(args): - path, build_dir, config, dry_run, output_dir = args + path, build_dir, config, dry_run, output_dir, names = args result = {} def work(): try: - result["value"] = convert_file(path, build_dir, config, dry_run, output_dir) + result["value"] = convert_file(path, build_dir, config, dry_run, output_dir, names) except ConvertError as e: result["value"] = {"file": os.path.relpath(path, ROOT), "error": str(e), "tests": [], "checks": [], "notes": []} @@ -2222,10 +2705,13 @@ def main(): "repository's root, instead of over the original") parser.add_argument("--jobs", type=int, default=1, help="files to convert at once") parser.add_argument("--quiet", action="store_true", help="print only the marks and the totals") + parser.add_argument("--names", help='cases\' names, as JSON: {"": {"": ' + '{"": ""}}}; each wins over the test\'s own') args = parser.parse_args() files = [os.path.abspath(f) for f in args.files] - work = [(f, os.path.abspath(args.build_dir), args.config, args.dry_run, args.output_dir) for f in files] + names = load_names(args.names) + work = [(f, os.path.abspath(args.build_dir), args.config, args.dry_run, args.output_dir, names) for f in files] if args.jobs > 1: with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: results = list(pool.map(convert_one, work)) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index eb4e3f307f..665f7adb8f 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -20,7 +20,10 @@ name TUT printed is the one the mapping folded into the case's; - each case's assertions, counted in the converted file, number its TUT test's ensure, fail and skip calls, as the mapping counted them in - the TUT file. + the TUT file, where a && chain the converter split counts one a term + (catch2_expected). After a file has been edited by hand past what the + converter wrote, --recounted lists the counts instead of holding them + to the mapping; names and results must still agree. A test the mapping says TUT never ran (past its group's limit) or this host does not compile is listed, not counted against parity. So is what the @@ -183,11 +186,14 @@ def recount(path, cases): text = f.read().decode("utf-8", "surrogateescape") masked = mask(text) counts = {} - for m in re.finditer(r'TEST_CASE_METHOD\s*\(', masked): + for m in re.finditer(r'\bTEST_CASE(_METHOD)?\s*\(', masked): open_paren = m.end() - 1 close = matching(masked, open_paren) header = text[open_paren + 1:close] - literal = re.search(r',\s*((?:"(?:[^"\\]|\\.)*"\s*)+),', header) + # The name is the first argument of a TEST_CASE, the second of a + # TEST_CASE_METHOD. + literal = re.search(r',\s*((?:"(?:[^"\\]|\\.)*"\s*)+),' if m.group(1) else + r'^\s*((?:"(?:[^"\\]|\\.)*"\s*)+),', header) if not literal: continue name = "".join(unescape(p) for p in re.findall(r'"((?:[^"\\]|\\.)*)"', literal.group(1))) @@ -328,11 +334,18 @@ def check(args): else: problems.append(f"{path}: {name!r}: TUT {t['said']!r}, Catch2 {cases[name]}") static = counted.get(name) - if e["tut_assertions"] == e["catch2_assertions"] == static: + # One TUT call is one Catch2 assertion, but for a && chain split + # into one assertion a term, which the mapping counted. + expected = e.get("catch2_expected", e["tut_assertions"]) + if expected == e["catch2_assertions"] == static: counts_ok += 1 + elif args.recounted and static is not None: + counts_ok += 1 + print(f"{path}: listed: {name!r}: {expected} assertions converted, {static} in the file now") else: problems.append(f"{path}: {name!r}: {e['tut_assertions']} TUT assertion calls, " - f"{e['catch2_assertions']} Catch2 assertions mapped, {static} in the file") + f"{expected} Catch2 assertions they became, {e['catch2_assertions']} mapped, " + f"{static} in the file") for key, t in sorted(tut_results.items()): problems.append(f"{path}: TUT ran [{key[0]}, {key[1]}], which the mapping does not have") for name in sorted(set(cases) - seen_cases): @@ -365,6 +378,9 @@ def main(): chk = sub.add_parser("check", help="run the converted files' Catch2 entries and compare") chk.add_argument("--tut", required=True, help="what record kept") chk.add_argument("--mapping", required=True, help="tut2catch.py's mapping") + chk.add_argument("--recounted", action="store_true", + help="the files were edited by hand since conversion: list each case's assertion count " + "rather than hold it to the mapping's (names and results still must match)") chk.add_argument("files", nargs="*", help="only these converted files (default: all the mapping has)") args = parser.parse_args() return record(args) if args.step == "record" else check(args) From 92f8c42a4da5e02306af64fa908698c30fe8edbd Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 10:28:36 -0400 Subject: [PATCH 13/55] The Catch2 harness prints every digit of a float Catch2 prints a float to 5 significant digits and a double to 10, so a failed comparison of two values a few units in the last place apart printed them alike. The harness sets both to max_digits10, as the TUT tests' own precise() helpers did, so a test no longer needs one. Co-Authored-By: Claude Opus 5.5 --- indra/test/altestmain.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/indra/test/altestmain.cpp b/indra/test/altestmain.cpp index b0895081e1..5a9a64ec49 100644 --- a/indra/test/altestmain.cpp +++ b/indra/test/altestmain.cpp @@ -39,9 +39,11 @@ #include "lltracethreadrecorder.h" #include +#include #include #include +#include namespace { @@ -118,6 +120,12 @@ int main(int argc, char* argv[]) // ones before it. --order on the command line still wins. session.configData().runOrder = Catch::TestRunOrder::Declared; + // Every digit a float or double holds, where Catch2 prints 5 and 10: two + // values a failure prints alike are then alike, which matters to tests + // that compare to the last bit. + Catch::StringMaker::precision = std::numeric_limits::max_digits10; + Catch::StringMaker::precision = std::numeric_limits::max_digits10; + if (const int result = session.applyCommandLine(argc, argv); result != 0) { return result; From 9a3d0d171a0bd5a3efebb6f5928d42dc045289d1 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 10:28:53 -0400 Subject: [PATCH 14/55] llmath's tests are Catch2, in one binary tut2catch.py converted the 23 test files: CHECK where a case can go on, an assertion a term where a condition was an && chain, LLSD compared deeply with LLSDEquals, floats to the bits TUT asked for with WithinBits, plain TEST_CASEs where the fixture held nothing, and no namespace tut. No file needed a hand fix. The 277 tests TUT knew only by number are named for what they check, given to the converter with --names; the rest take the first sentence of the comment above them. They build into one binary, llmath_tests, in place of 23 executables: 35 MiB against 758. Each file is still a CTest entry of its own, in a process of its own, as before. tut_parity.py proves nothing was lost: all 355 tests have one case each, in TUT's order, with the same results (m3math's one skip included), and as many assertions as the converter made of TUT's calls. The combined run, all of llmath in one process, passes too, under ten random orders and twenty repeats, with nothing the state guard flags; it stays combined_pending until CI has run it on every row. The batch kernels' ISA tier variants are simd_tests_, every tier but the tree's own, which llmath_tests already is. Co-Authored-By: Claude Opus 5.5 --- indra/llmath/CMakeLists.txt | 76 ++- indra/llmath/tests/alignment_test.cpp | 121 ++-- indra/llmath/tests/alplaneset_test.cpp | 131 ++-- indra/llmath/tests/alsimdkernels_test.cpp | 76 +-- indra/llmath/tests/llbbox_test.cpp | 268 ++++---- indra/llmath/tests/llbboxlocal_test.cpp | 151 ++--- indra/llmath/tests/llmatrix3a_test.cpp | 191 +++--- indra/llmath/tests/llmatrix4a_test.cpp | 363 ++++++----- indra/llmath/tests/llmodularmath_test.cpp | 30 +- indra/llmath/tests/llquaternion2_test.cpp | 178 +++--- indra/llmath/tests/llquaternion_test.cpp | 706 ++++++++++++---------- indra/llmath/tests/llrect_test.cpp | 500 +++++++++------ indra/llmath/tests/llsdutil_math_test.cpp | 63 +- indra/llmath/tests/llvector4a_test.cpp | 404 ++++++++----- indra/llmath/tests/m3math_test.cpp | 228 +++---- indra/llmath/tests/mathmisc_test.cpp | 276 +++++---- indra/llmath/tests/v2math_test.cpp | 347 +++++++---- indra/llmath/tests/v3color_test.cpp | 302 ++++++--- indra/llmath/tests/v3dmath_test.cpp | 545 +++++++++-------- indra/llmath/tests/v3math_test.cpp | 488 ++++++++++----- indra/llmath/tests/v4color_test.cpp | 370 ++++++++---- indra/llmath/tests/v4coloru_test.cpp | 308 ++++++---- indra/llmath/tests/v4math_test.cpp | 390 ++++++++---- indra/llmath/tests/xform_test.cpp | 200 +++--- 24 files changed, 4012 insertions(+), 2700 deletions(-) diff --git a/indra/llmath/CMakeLists.txt b/indra/llmath/CMakeLists.txt index a5dbe13fd9..6b51c8dcd9 100644 --- a/indra/llmath/CMakeLists.txt +++ b/indra/llmath/CMakeLists.txt @@ -103,42 +103,54 @@ target_precompile_headers(llmath REUSE_FROM llprecompiled) # Add tests if(AL_BUILD_TESTS) - # Each links the library rather than compiling a copy of the file it - # tests, so the code under test is the code the viewer runs. - foreach( - test - llbboxlocal - llmodularmath - llrect - v2math - v3color - v4color - v4coloru - alignment - alplaneset - llsdutil_math - mathmisc - m3math - llmatrix3a - llmatrix4a - llvector4a - llbbox - llquaternion - llquaternion2 - v3dmath - v3math - v4math - xform + # One Catch2 binary for llmath's tests, each test file run in a process of + # its own. Each links the library rather than compiling a copy of the file + # it tests, so the code under test is the code the viewer runs. + al_add_test_binary( + llmath_tests + PROJECT llmath + SOURCES + tests/alignment_test.cpp + tests/alplaneset_test.cpp + tests/alsimdkernels_test.cpp + tests/llbbox_test.cpp + tests/llbboxlocal_test.cpp + tests/llmatrix3a_test.cpp + tests/llmatrix4a_test.cpp + tests/llmodularmath_test.cpp + tests/llquaternion2_test.cpp + tests/llquaternion_test.cpp + tests/llrect_test.cpp + tests/llsdutil_math_test.cpp + tests/llvector4a_test.cpp + tests/m3math_test.cpp + tests/mathmisc_test.cpp + tests/v2math_test.cpp + tests/v3color_test.cpp + tests/v3dmath_test.cpp + tests/v3math_test.cpp + tests/v4color_test.cpp + tests/v4coloru_test.cpp + tests/v4math_test.cpp + tests/xform_test.cpp + LIBRARIES llmath llcommon ) - al_add_test(${test} PROJECT llmath LIBRARIES llmath llcommon) - endforeach() - # The batch kernels at every width, at the tree's tier and, where the - # tiers differ, at each of them; a variant the host cannot run skips. - al_add_test(alsimdkernels PROJECT llmath LIBRARIES llmath llcommon) + # The batch kernels again at each other x86-64 tier, one binary a tier: code + # the linker folds across files would otherwise run one tier's instructions + # in another's test. The tree's own tier is llmath_tests'; a variant the + # host cannot run skips. if(BUILD_TARGET_IS_X86_64 AND NOT DARWIN) foreach(tier baseline v2 v3 v4) - al_add_test(alsimdkernels PROJECT llmath LIBRARIES llmath llcommon ISA_TIER ${tier}) + if(NOT tier STREQUAL AL_ISA_TIER) + al_add_test_binary( + simd_tests + PROJECT llmath + SOURCES tests/alsimdkernels_test.cpp + LIBRARIES llmath llcommon + ISA_TIER ${tier} + ) + endif() endforeach() endif() diff --git a/indra/llmath/tests/alignment_test.cpp b/indra/llmath/tests/alignment_test.cpp index 3e4a25c66f..13d745a209 100644 --- a/indra/llmath/tests/alignment_test.cpp +++ b/indra/llmath/tests/alignment_test.cpp @@ -29,7 +29,7 @@ // Tests related to allocating objects with alignment constraints, particularly for SSE support. #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llmath.h" #include "../llsimdmath.h" #include "../llvector4a.h" @@ -38,20 +38,12 @@ #include -namespace tut +namespace { #define is_aligned(ptr,alignment) ((reinterpret_cast(ptr))%(alignment)==0) #define is_aligned_relative(ptr,base_ptr,alignment) ((reinterpret_cast(ptr)-reinterpret_cast(base_ptr))%(alignment)==0) -namespace -{ -struct alignment_test {}; - -typedef test_group alignment_test_t; -typedef alignment_test_t::object alignment_test_object_t; -tut::alignment_test_t tut_alignment_test("LLAlignment"); - // The SIMD types declare their alignment and nothing else: no operator new. // A plain new must place them, which the language guarantees up to the // default new alignment and, past it, through the aligned operator new. @@ -65,121 +57,140 @@ struct alignas(64) MyCacheLine { LLQuad mQ[4]; }; -} // Verify that aligned allocators perform as advertised. -template<> template<> -void alignment_test_object_t::test<1>() +TEST_CASE("LLAlignment: the aligned allocators return aligned memory", "[LLAlignment]") { const int num_tests = 7; void *align_ptr; for (int i=0; i template<> -void alignment_test_object_t::test<2>() +TEST_CASE("LLAlignment: over-aligned types on the stack singly and in arrays", "[LLAlignment]") { MyVector4a vec1; - ensure("LLAlignment vec1 unaligned", is_aligned(&vec1,16)); + UNSCOPED_INFO("LLAlignment vec1 unaligned"); + CHECK(is_aligned(&vec1,16)); MyVector4a veca[12]; - ensure("LLAlignment veca unaligned", is_aligned(veca,16)); + UNSCOPED_INFO("LLAlignment veca unaligned"); + CHECK(is_aligned(veca,16)); MyCacheLine line; - ensure("LLAlignment line unaligned", is_aligned(&line,64)); + UNSCOPED_INFO("LLAlignment line unaligned"); + CHECK(is_aligned(&line,64)); } // Heap allocation of objects and arrays. -template<> template<> -void alignment_test_object_t::test<3>() +TEST_CASE("LLAlignment: plain new aligns over-aligned objects and arrays", "[LLAlignment]") { const int ARR_SIZE = 7; for(int i=0; i template<> -void alignment_test_object_t::test<4>() +TEST_CASE("LLAlignment: LLVector4a and LLMatrix4a align on the heap and in containers", "[LLAlignment]") { - ensure_equals("LLVector4a size", sizeof(LLVector4a), size_t(16)); - ensure_equals("LLVector4a alignment", alignof(LLVector4a), size_t(16)); - ensure_equals("LLMatrix4a size", sizeof(LLMatrix4a), size_t(64)); - ensure_equals("LLMatrix4a alignment", alignof(LLMatrix4a), size_t(16)); + UNSCOPED_INFO("LLVector4a size"); + CHECK(sizeof(LLVector4a) == size_t(16)); + UNSCOPED_INFO("LLVector4a alignment"); + CHECK(alignof(LLVector4a) == size_t(16)); + UNSCOPED_INFO("LLMatrix4a size"); + CHECK(sizeof(LLMatrix4a) == size_t(64)); + UNSCOPED_INFO("LLMatrix4a alignment"); + CHECK(alignof(LLMatrix4a) == size_t(16)); LLVector4a *vecp = new LLVector4a; - ensure("LLVector4a unaligned", is_aligned(vecp,16)); + UNSCOPED_INFO("LLVector4a unaligned"); + CHECK(is_aligned(vecp,16)); delete vecp; LLMatrix4a *matp = new LLMatrix4a[5]; - ensure("LLMatrix4a array unaligned", is_aligned(matp,16)); + UNSCOPED_INFO("LLMatrix4a array unaligned"); + CHECK(is_aligned(matp,16)); delete [] matp; std::vector vecs(33); for (const LLVector4a& v : vecs) { - ensure("LLVector4a in a vector unaligned", is_aligned(&v,16)); + UNSCOPED_INFO("LLVector4a in a vector unaligned"); + REQUIRE(is_aligned(&v,16)); } std::vector mats(9); for (const LLMatrix4a& m : mats) { - ensure("LLMatrix4a in a vector unaligned", is_aligned(&m,16)); + UNSCOPED_INFO("LLMatrix4a in a vector unaligned"); + REQUIRE(is_aligned(&m,16)); } } // A face vertex holds its position and normal in the object itself: a // vector of them is one allocation, every vector in it placed. -template<> template<> -void alignment_test_object_t::test<5>() +TEST_CASE("LLAlignment: LLVolumeFace::VertexData aligns in a vector and copies whole", "[LLAlignment]") { - ensure_equals("VertexData size", sizeof(LLVolumeFace::VertexData), size_t(48)); - ensure_equals("VertexData alignment", alignof(LLVolumeFace::VertexData), size_t(16)); - ensure("VertexData trivially copyable", std::is_trivially_copyable::value); + UNSCOPED_INFO("VertexData size"); + CHECK(sizeof(LLVolumeFace::VertexData) == size_t(48)); + UNSCOPED_INFO("VertexData alignment"); + CHECK(alignof(LLVolumeFace::VertexData) == size_t(16)); + UNSCOPED_INFO("VertexData trivially copyable"); + CHECK(std::is_trivially_copyable::value); std::vector verts(17); for (size_t i = 0; i < verts.size(); ++i) { - ensure("VertexData position in a vector unaligned", is_aligned(&verts[i].getPosition(),16)); - ensure("VertexData normal in a vector unaligned", is_aligned(&verts[i].getNormal(),16)); + UNSCOPED_INFO("VertexData position in a vector unaligned"); + REQUIRE(is_aligned(&verts[i].getPosition(),16)); + UNSCOPED_INFO("VertexData normal in a vector unaligned"); + REQUIRE(is_aligned(&verts[i].getNormal(),16)); verts[i].setPosition(LLVector4a(F32(i), 1.f, 2.f)); verts[i].setNormal(LLVector4a(0.f, 0.f, 1.f)); verts[i].mTexCoord.set(F32(i) * 0.5f, 0.25f); @@ -188,13 +199,19 @@ void alignment_test_object_t::test<5>() std::vector copy = verts; for (size_t i = 0; i < copy.size(); ++i) { - ensure("copied position", copy[i].getPosition().equals3(LLVector4a(F32(i), 1.f, 2.f))); - ensure("copied normal", copy[i].getNormal().equals3(LLVector4a(0.f, 0.f, 1.f))); - ensure_equals("copied texcoord", copy[i].mTexCoord.mV[0], F32(i) * 0.5f); - ensure("copy equals source", copy[i] == verts[i]); + UNSCOPED_INFO("copied position"); + REQUIRE(copy[i].getPosition().equals3(LLVector4a(F32(i), 1.f, 2.f))); + UNSCOPED_INFO("copied normal"); + REQUIRE(copy[i].getNormal().equals3(LLVector4a(0.f, 0.f, 1.f))); + UNSCOPED_INFO("copied texcoord"); + REQUIRE(copy[i].mTexCoord.mV[0] == F32(i) * 0.5f); + UNSCOPED_INFO("copy equals source"); + REQUIRE(copy[i] == verts[i]); } - ensure("ordering", copy[0] < copy[1]); - ensure("ordering", !(copy[1] < copy[0])); + UNSCOPED_INFO("ordering"); + CHECK(copy[0] < copy[1]); + UNSCOPED_INFO("ordering"); + CHECK_FALSE(copy[1] < copy[0]); } } diff --git a/indra/llmath/tests/alplaneset_test.cpp b/indra/llmath/tests/alplaneset_test.cpp index f39e19482d..5b38446f45 100644 --- a/indra/llmath/tests/alplaneset_test.cpp +++ b/indra/llmath/tests/alplaneset_test.cpp @@ -30,7 +30,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llmath.h" #include "../llsimdmath.h" #include "../llvector4a.h" @@ -41,8 +41,6 @@ #include #include -namespace tut -{ namespace { // A camera at the origin looking down +X, with a frustum from the @@ -95,11 +93,8 @@ namespace boxes.push_back(near_touch); return boxes; } -} -} // namespace tut +} // anonymous namespace -namespace tut -{ namespace { struct alplaneset_data @@ -122,89 +117,104 @@ namespace return no_far ? mCamera.AABBInFrustumNoFarClip(b.center, b.radius, planes) : mCamera.AABBInFrustum(b.center, b.radius, planes); } }; - typedef test_group alplaneset_test; - typedef alplaneset_test::object alplaneset_object; - tut::alplaneset_test alplaneset_testcase("ALPlaneSet"); -} // An empty set contains everything; a set with one plane splits space. - template<> template<> - void alplaneset_object::test<1>() + TEST_CASE_METHOD(alplaneset_data, "ALPlaneSet: an empty set contains everything and one plane splits space", "[ALPlaneSet]") { ALPlaneSet empty; const LLVector4a somewhere(1e6f, -1e6f, 3.f, 0.f); const LLVector4a big(1e5f, 1e5f, 1e5f, 0.f); - ensure_equals("nothing is outside an empty set", empty.aabbTest(somewhere, big), 2); + UNSCOPED_INFO("nothing is outside an empty set"); + CHECK(empty.aabbTest(somewhere, big) == 2); // x <= 10 is inside: normal +x, constant -10 ALPlaneSet one; LLPlane plane(LLVector3(10.f, 0.f, 0.f), LLVector3(1.f, 0.f, 0.f)); one.set(3, plane, plane.calcPlaneMask()); - ensure_equals("well inside", one.aabbTest(LLVector4a(0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)), 2); - ensure_equals("well outside", one.aabbTest(LLVector4a(20.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)), 0); - ensure_equals("straddling", one.aabbTest(LLVector4a(10.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)), 1); - ensure_equals("the skipped lane does not count", one.aabbTest(LLVector4a(20.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f), 3), 2); + UNSCOPED_INFO("well inside"); + CHECK(one.aabbTest(LLVector4a(0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)) == 2); + UNSCOPED_INFO("well outside"); + CHECK(one.aabbTest(LLVector4a(20.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)) == 0); + UNSCOPED_INFO("straddling"); + CHECK(one.aabbTest(LLVector4a(10.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)) == 1); + UNSCOPED_INFO("the skipped lane does not count"); + CHECK(one.aabbTest(LLVector4a(20.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f), 3) == 2); one.disable(3); - ensure_equals("a disabled lane does not count", one.aabbTest(LLVector4a(20.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)), 2); + UNSCOPED_INFO("a disabled lane does not count"); + CHECK(one.aabbTest(LLVector4a(20.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)) == 2); } // The camera's set against its loop, over thousands of boxes, with and // without the far plane. - template<> template<> - void alplaneset_object::test<2>() + TEST_CASE_METHOD(alplaneset_data, "ALPlaneSet: the camera's set agrees with its loop with and without the far plane", "[ALPlaneSet]") { int outside = 0, crossing = 0, inside = 0; for (const Box& b : some_boxes(2, FAR_X)) { const S32 expected = by_loop(b); const S32 got = mCamera.AABBInFrustum(b.center, b.radius); - ensure_equals("AABBInFrustum at " + std::to_string(b.center[0]) + " " + std::to_string(b.center[1]) + " " + std::to_string(b.center[2]), got, expected); + UNSCOPED_INFO("AABBInFrustum at " + std::to_string(b.center[0]) + " " + std::to_string(b.center[1]) + " " + std::to_string(b.center[2])); + REQUIRE(got == expected); outside += expected == 0; crossing += expected == 1; inside += expected == 2; const S32 expected_no_far = by_loop(b, true); - ensure_equals("AABBInFrustumNoFarClip at " + std::to_string(b.center[0]), mCamera.AABBInFrustumNoFarClip(b.center, b.radius), expected_no_far); + UNSCOPED_INFO("AABBInFrustumNoFarClip at " + std::to_string(b.center[0])); + REQUIRE(mCamera.AABBInFrustumNoFarClip(b.center, b.radius) == expected_no_far); + } + { + INFO("the boxes fell on every side: " + std::to_string(outside) + " " + std::to_string(crossing) + " " + std::to_string(inside)); + CHECK(outside > 100); + CHECK(crossing > 100); + CHECK(inside > 100); } - ensure("the boxes fell on every side: " + std::to_string(outside) + " " + std::to_string(crossing) + " " + std::to_string(inside), - outside > 100 && crossing > 100 && inside > 100); // the obvious three - ensure_equals("a box in the middle is inside", mCamera.AABBInFrustum(LLVector4a(50.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)), 2); - ensure_equals("a box behind the camera is outside", mCamera.AABBInFrustum(LLVector4a(-50.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)), 0); - ensure_equals("a box across the far plane crosses", mCamera.AABBInFrustum(LLVector4a(FAR_X, 0.f, 0.f), LLVector4a(5.f, 5.f, 5.f)), 1); - ensure_equals("and is inside without the far plane", mCamera.AABBInFrustumNoFarClip(LLVector4a(FAR_X, 0.f, 0.f), LLVector4a(5.f, 5.f, 5.f)), 2); + UNSCOPED_INFO("a box in the middle is inside"); + CHECK(mCamera.AABBInFrustum(LLVector4a(50.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)) == 2); + UNSCOPED_INFO("a box behind the camera is outside"); + CHECK(mCamera.AABBInFrustum(LLVector4a(-50.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f)) == 0); + UNSCOPED_INFO("a box across the far plane crosses"); + CHECK(mCamera.AABBInFrustum(LLVector4a(FAR_X, 0.f, 0.f), LLVector4a(5.f, 5.f, 5.f)) == 1); + UNSCOPED_INFO("and is inside without the far plane"); + CHECK(mCamera.AABBInFrustumNoFarClip(LLVector4a(FAR_X, 0.f, 0.f), LLVector4a(5.f, 5.f, 5.f)) == 2); } // A user clip plane joins the set when enabled and leaves it when // disabled; an ignored plane leaves it. - template<> template<> - void alplaneset_object::test<3>() + TEST_CASE_METHOD(alplaneset_data, "ALPlaneSet: user clip planes join and leave the set and ignored planes leave it", "[ALPlaneSet]") { // z <= 5 is inside LLPlane clip(LLVector3(0.f, 0.f, 5.f), LLVector3(0.f, 0.f, 1.f)); mCamera.setUserClipPlane(clip); const Box above = {LLVector4a(50.f, 0.f, 20.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)}; - ensure_equals("above the clip plane is outside", mCamera.AABBInFrustum(above.center, above.radius), 0); - ensure_equals("and the loop agrees", by_loop(above), 0); + UNSCOPED_INFO("above the clip plane is outside"); + CHECK(mCamera.AABBInFrustum(above.center, above.radius) == 0); + UNSCOPED_INFO("and the loop agrees"); + CHECK(by_loop(above) == 0); for (const Box& b : some_boxes(3, FAR_X)) { - ensure_equals("with the clip plane", mCamera.AABBInFrustum(b.center, b.radius), by_loop(b)); + UNSCOPED_INFO("with the clip plane"); + REQUIRE(mCamera.AABBInFrustum(b.center, b.radius) == by_loop(b)); } mCamera.disableUserClipPlane(); - ensure_equals("above the disabled clip plane is inside", mCamera.AABBInFrustum(above.center, above.radius), 2); - ensure_equals("and the loop agrees", by_loop(above), 2); + UNSCOPED_INFO("above the disabled clip plane is inside"); + CHECK(mCamera.AABBInFrustum(above.center, above.radius) == 2); + UNSCOPED_INFO("and the loop agrees"); + CHECK(by_loop(above) == 2); mCamera.ignoreAgentFrustumPlane(LLCamera::AGENT_PLANE_FAR); const Box beyond = {LLVector4a(FAR_X + 50.f, 0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)}; - ensure_equals("beyond an ignored far plane is inside", mCamera.AABBInFrustum(beyond.center, beyond.radius), 2); - ensure_equals("and the loop agrees", by_loop(beyond), 2); + UNSCOPED_INFO("beyond an ignored far plane is inside"); + CHECK(mCamera.AABBInFrustum(beyond.center, beyond.radius) == 2); + UNSCOPED_INFO("and the loop agrees"); + CHECK(by_loop(beyond) == 2); } // The region set is the agent set shifted, far plane aside. - template<> template<> - void alplaneset_object::test<4>() + TEST_CASE_METHOD(alplaneset_data, "ALPlaneSet: the region set is the agent set shifted apart from the far plane", "[ALPlaneSet]") { const LLVector3 shift(-256.f, 128.f, 30.f); mCamera.calcRegionFrustumPlanes(shift, FAR_X); @@ -212,41 +222,50 @@ namespace { Box in_agent = b; in_agent.center.set(b.center[0] + shift.mV[0], b.center[1] + shift.mV[1], b.center[2] + shift.mV[2], 0.f); - ensure_equals("AABBInRegionFrustumNoFarClip at " + std::to_string(b.center[0]), - mCamera.AABBInRegionFrustumNoFarClip(b.center, b.radius), by_loop(in_agent, true)); + UNSCOPED_INFO("AABBInRegionFrustumNoFarClip at " + std::to_string(b.center[0])); + REQUIRE(mCamera.AABBInRegionFrustumNoFarClip(b.center, b.radius) == by_loop(in_agent, true)); } - ensure_equals("a region box past the region far clip is outside", - mCamera.AABBInRegionFrustum(LLVector4a(FAR_X + 50.f - shift.mV[0], -shift.mV[1], -shift.mV[2], 0.f), LLVector4a(1.f, 1.f, 1.f)), 0); + UNSCOPED_INFO("a region box past the region far clip is outside"); + CHECK(mCamera.AABBInRegionFrustum(LLVector4a(FAR_X + 50.f - shift.mV[0], -shift.mV[1], -shift.mV[2], 0.f), LLVector4a(1.f, 1.f, 1.f)) == 0); } // A plane replaced one at a time, the way the shadow passes move the // near plane back behind the casters, is what the set tests against. - template<> template<> - void alplaneset_object::test<5>() + TEST_CASE_METHOD(alplaneset_data, "ALPlaneSet: setAgentPlane replaces a plane the set tests against", "[ALPlaneSet]") { // between the eye and the near plane, within the side planes const Box behind = {LLVector4a(0.5f, 0.f, 0.f, 0.f), LLVector4a(0.1f, 0.1f, 0.1f, 0.f)}; - ensure_equals("behind the near plane is outside", mCamera.AABBInFrustum(behind.center, behind.radius), 0); + UNSCOPED_INFO("behind the near plane is outside"); + CHECK(mCamera.AABBInFrustum(behind.center, behind.radius) == 0); // the near plane moved back to x = -50, still facing back LLPlane moved(LLVector3(-50.f, 0.f, 0.f), LLVector3(-1.f, 0.f, 0.f)); mCamera.setAgentPlane(LLCamera::AGENT_PLANE_NEAR, moved); - ensure("the plane reads back", mCamera.getAgentPlane(LLCamera::AGENT_PLANE_NEAR).equal(moved)); - ensure_equals("behind the old near plane is now inside", mCamera.AABBInFrustum(behind.center, behind.radius), 2); - ensure_equals("and the loop agrees", by_loop(behind), 2); + UNSCOPED_INFO("the plane reads back"); + CHECK(mCamera.getAgentPlane(LLCamera::AGENT_PLANE_NEAR).equal(moved)); + UNSCOPED_INFO("behind the old near plane is now inside"); + CHECK(mCamera.AABBInFrustum(behind.center, behind.radius) == 2); + UNSCOPED_INFO("and the loop agrees"); + CHECK(by_loop(behind) == 2); for (const Box& b : some_boxes(5, FAR_X)) { - ensure_equals("with the near plane moved", mCamera.AABBInFrustum(b.center, b.radius), by_loop(b)); - ensure_equals("with the near plane moved, far ignored", mCamera.AABBInFrustumNoFarClip(b.center, b.radius), by_loop(b, true)); + UNSCOPED_INFO("with the near plane moved"); + REQUIRE(mCamera.AABBInFrustum(b.center, b.radius) == by_loop(b)); + UNSCOPED_INFO("with the near plane moved, far ignored"); + REQUIRE(mCamera.AABBInFrustumNoFarClip(b.center, b.radius) == by_loop(b, true)); } // a plane facing the other way takes its octant with it: x >= 50 is now outside LLPlane flipped(LLVector3(50.f, 0.f, 0.f), LLVector3(1.f, 0.f, 0.f)); mCamera.setAgentPlane(LLCamera::AGENT_PLANE_NEAR, flipped); const Box straddling = {LLVector4a(50.f, 0.f, 0.f, 0.f), LLVector4a(2.f, 1.f, 1.f, 0.f)}; - ensure_equals("a box across the flipped plane is partly in", mCamera.AABBInFrustum(straddling.center, straddling.radius), 1); - ensure_equals("and the loop agrees", by_loop(straddling), 1); - ensure_equals("beyond the flipped plane is outside", mCamera.AABBInFrustum(LLVector4a(70.f, 0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)), 0); - ensure_equals("and the loop agrees", by_loop({LLVector4a(70.f, 0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)}), 0); + UNSCOPED_INFO("a box across the flipped plane is partly in"); + CHECK(mCamera.AABBInFrustum(straddling.center, straddling.radius) == 1); + UNSCOPED_INFO("and the loop agrees"); + CHECK(by_loop(straddling) == 1); + UNSCOPED_INFO("beyond the flipped plane is outside"); + CHECK(mCamera.AABBInFrustum(LLVector4a(70.f, 0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)) == 0); + UNSCOPED_INFO("and the loop agrees"); + CHECK(by_loop({LLVector4a(70.f, 0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)}) == 0); } } diff --git a/indra/llmath/tests/alsimdkernels_test.cpp b/indra/llmath/tests/alsimdkernels_test.cpp index ccf24a3dea..3eecc700f2 100644 --- a/indra/llmath/tests/alsimdkernels_test.cpp +++ b/indra/llmath/tests/alsimdkernels_test.cpp @@ -35,7 +35,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "llthread.h" #include "../llmath.h" #include "../llsimdmath.h" @@ -57,8 +57,6 @@ using namespace alsimd; using namespace alsimd::kernels; -namespace tut -{ namespace { std::string precise(double v) @@ -82,9 +80,9 @@ namespace void ensure_same_bits(const std::string& what, const LLVector4a& got, const LLVector4a& expected) { - ensure(what + ": " + precise(got[0]) + " " + precise(got[1]) + " " + precise(got[2]) + " " + precise(got[3]) + - " vs " + precise(expected[0]) + " " + precise(expected[1]) + " " + precise(expected[2]) + " " + precise(expected[3]), - same_bits(got, expected)); + UNSCOPED_INFO(what + ": " + precise(got[0]) + " " + precise(got[1]) + " " + precise(got[2]) + " " + precise(got[3]) + + " vs " + precise(expected[0]) + " " + precise(expected[1]) + " " + precise(expected[2]) + " " + precise(expected[3])); + REQUIRE(same_bits(got, expected)); } // The error relative to the size of the terms that made the value, @@ -93,7 +91,8 @@ namespace { const double scale = llmax(1.0, llmax(std::fabs(expected), magnitude)); const double err = std::fabs((double)got - expected) / scale; - ensure(what + ": " + precise(got) + " is off " + precise(err) + " from " + precise(expected), err <= relative); + UNSCOPED_INFO(what + ": " + precise(got) + " is off " + precise(err) + " from " + precise(expected)); + REQUIRE(err <= relative); } // The counts that leave every tail a width can have. @@ -170,26 +169,14 @@ namespace State mSaved; }; -} -} // namespace tut +} // anonymous namespace -namespace tut -{ namespace { - struct alsimdkernels_data - { - }; - typedef test_group alsimdkernels_test; - typedef alsimdkernels_test::object alsimdkernels_object; - tut::alsimdkernels_test alsimdkernels_testcase("alsimdkernels"); -} - // Points, with w from the rows and with w set, against // affineTransform, bit for bit, at every count and width; nothing // written past the count. - template<> template<> - void alsimdkernels_object::test<1>() + TEST_CASE("alsimdkernels: transform_points against affineTransform at every count and width", "[alsimdkernels]") { const LLMatrix4a m = some_matrix(); for (size_t n : COUNTS) @@ -239,8 +226,7 @@ namespace } // Directions, with w from the rows and with w kept, against rotate. - template<> template<> - void alsimdkernels_object::test<2>() + TEST_CASE("alsimdkernels: transform_directions against rotate at every count and width", "[alsimdkernels]") { const LLMatrix4a m = some_matrix(); for (size_t n : COUNTS) @@ -277,8 +263,7 @@ namespace // Texture coordinates: two per vector, against the scalar arithmetic // in double within the two roundings the fused form takes, and every // width bit for bit against the first. - template<> template<> - void alsimdkernels_object::test<3>() + TEST_CASE("alsimdkernels: texture coordinates against double and every width against the first", "[alsimdkernels]") { const F32 cos_ang = 0.8f, sin_ang = 0.6f; const LLVector4a trans(-0.5f); @@ -321,8 +306,7 @@ namespace // Index offsets: bit for bit, wrapping, unaligned on both sides, every // count. - template<> template<> - void alsimdkernels_object::test<4>() + TEST_CASE("alsimdkernels: offset_indices_u16 wraps and writes only its unaligned range", "[alsimdkernels]") { std::mt19937 rng(4); std::uniform_int_distribution any(0, 65535); @@ -343,7 +327,8 @@ namespace offset_indices_u16_impl(src.data() + 1, dst.data() + 1, n, offset); for (size_t i = 0; i < n + 3; ++i) { - ensure_equals(std::string("offset_indices_u16 ") + width + " n " + std::to_string(n) + " offset " + std::to_string(offset) + " at " + std::to_string(i), dst[i], expected[i]); + UNSCOPED_INFO(std::string("offset_indices_u16 ") + width + " n " + std::to_string(n) + " offset " + std::to_string(offset) + " at " + std::to_string(i)); + REQUIRE(dst[i] == expected[i]); } }); } @@ -354,8 +339,7 @@ namespace // reference within the two roundings a tier without FMA takes, w from // bias, the last vertex read without its neighbour, every width bit // for bit. - template<> template<> - void alsimdkernels_object::test<5>() + TEST_CASE("alsimdkernels: dequantize_u16x3 against double with w from bias", "[alsimdkernels]") { std::mt19937 rng(5); std::uniform_int_distribution any(0, 65535); @@ -389,7 +373,8 @@ namespace const double expected = term + bias[lane]; ensure_close("dequantize_u16x3 n " + std::to_string(n) + " at " + std::to_string(i) + " lane " + std::to_string(lane), narrow[i][lane], expected, 5e-7, std::fabs(term) + std::fabs(bias[lane])); } - ensure_equals("dequantize_u16x3 w is bias", narrow[i][3], bias[3]); + UNSCOPED_INFO("dequantize_u16x3 w is bias"); + REQUIRE(narrow[i][3] == bias[3]); } ensure_same_bits("dequantize_u16x3 writes within n", narrow[n], SENTINEL); @@ -407,8 +392,7 @@ namespace // Two 16-bit values per vertex, two vertices per vector, the odd tail's // upper pair at bias. - template<> template<> - void alsimdkernels_object::test<6>() + TEST_CASE("alsimdkernels: dequantize_u16x2 packs two vertices a vector and an odd tail at bias", "[alsimdkernels]") { std::mt19937 rng(6); std::uniform_int_distribution any(0, 65535); @@ -441,8 +425,10 @@ namespace } if (n & 1) { - ensure_equals("odd tail upper u is bias", dst[vectors - 1][2], bias[2]); - ensure_equals("odd tail upper v is bias", dst[vectors - 1][3], bias[3]); + UNSCOPED_INFO("odd tail upper u is bias"); + REQUIRE(dst[vectors - 1][2] == bias[2]); + UNSCOPED_INFO("odd tail upper v is bias"); + REQUIRE(dst[vectors - 1][3] == bias[3]); } ensure_same_bits("dequantize_u16x2 writes within its vectors", dst[vectors], SENTINEL); } @@ -450,8 +436,7 @@ namespace // The skinning blend against a double reference, and the skinned points // against the blend and two transforms done one at a time. - template<> template<> - void alsimdkernels_object::test<7>() + TEST_CASE("alsimdkernels: skin_blend against double and skin_points against two transforms", "[alsimdkernels]") { constexpr U32 JOINTS = 6; LLMatrix4a palette[JOINTS]; @@ -544,8 +529,7 @@ namespace // Extents against the scalar minimum and maximum, bit for bit, every // count and width; an empty range leaves them alone. - template<> template<> - void alsimdkernels_object::test<8>() + TEST_CASE("alsimdkernels: extents against the scalar minimum and maximum bit for bit", "[alsimdkernels]") { for (size_t n : COUNTS) { @@ -584,8 +568,7 @@ namespace // fast estimate's twelve bits, since one ulp of input can cross a step // of its table. With and without a mask and clothing weights, a // degenerate binormal delta included. - template<> template<> - void alsimdkernels_object::test<9>() + TEST_CASE("alsimdkernels: morph_apply against the loop it replaces", "[alsimdkernels]") { constexpr size_t MORPH = 37; constexpr size_t MESH = 50; @@ -718,8 +701,7 @@ namespace // is a denormal float; under the flush-to-zero mode the render thread // runs in it has to come through every width and the public entry // untouched. - template<> template<> - void alsimdkernels_object::test<10>() + TEST_CASE("alsimdkernels: a denormal texture index in w survives flush-to-zero", "[alsimdkernels]") { FPModeRestorer restore_fp_mode; set_thread_fp_mode(); @@ -736,8 +718,8 @@ namespace transform_impl(m, src.data(), dst.data(), n, w); for (size_t i = 0; i < n; ++i) { - ensure_equals(std::string("index in w ") + width + " index " + std::to_string(index) + " n " + std::to_string(n) + " at " + std::to_string(i), - std::bit_cast(dst[i][3]), index); + UNSCOPED_INFO(std::string("index in w ") + width + " index " + std::to_string(index) + " n " + std::to_string(n) + " at " + std::to_string(i)); + REQUIRE(std::bit_cast(dst[i][3]) == index); } }); @@ -745,8 +727,8 @@ namespace alsimd::transform_points(m, src.data(), dst.data(), n, w); for (size_t i = 0; i < n; ++i) { - ensure_equals("index in w through the library index " + std::to_string(index) + " n " + std::to_string(n) + " at " + std::to_string(i), - std::bit_cast(dst[i][3]), index); + UNSCOPED_INFO("index in w through the library index " + std::to_string(index) + " n " + std::to_string(n) + " at " + std::to_string(i)); + REQUIRE(std::bit_cast(dst[i][3]) == index); } } } diff --git a/indra/llmath/tests/llbbox_test.cpp b/indra/llmath/tests/llbbox_test.cpp index 8b2c2bfaa2..6507db3afc 100644 --- a/indra/llmath/tests/llbbox_test.cpp +++ b/indra/llmath/tests/llbbox_test.cpp @@ -28,7 +28,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llbbox.h" @@ -36,28 +36,13 @@ #define ANGLE (3.14159265f / 2.0f) #define APPROX_EQUAL(a, b) (dist_vec_squared((a),(b)) < 1e-10) -namespace tut -{ namespace { - struct LLBBoxData - { - }; - - typedef test_group factory; - typedef factory::object object; -} } namespace { - tut::factory llbbox_test_factory("LLBBox"); -} - -namespace tut -{ - template<> template<> - void object::test<1>() + TEST_CASE("LLBBox: the default constructor gives an empty box at the origin", "[LLBBox]") { // // test the default constructor @@ -65,14 +50,17 @@ namespace tut LLBBox bbox1; - ensure_equals("Default bbox min", bbox1.getMinLocal(), LLVector3(0.0f, 0.0f, 0.0f)); - ensure_equals("Default bbox max", bbox1.getMaxLocal(), LLVector3(0.0f, 0.0f, 0.0f)); - ensure_equals("Default bbox pos agent", bbox1.getPositionAgent(), LLVector3(0.0f, 0.0f, 0.0f)); - ensure_equals("Default bbox rotation", bbox1.getRotation(), LLQuaternion(0.0f, 0.0f, 0.0f, 1.0f)); + UNSCOPED_INFO("Default bbox min"); + CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox max"); + CHECK(bbox1.getMaxLocal() == LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox pos agent"); + CHECK(bbox1.getPositionAgent() == LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox rotation"); + CHECK(bbox1.getRotation() == LLQuaternion(0.0f, 0.0f, 0.0f, 1.0f)); } - template<> template<> - void object::test<2>() + TEST_CASE("LLBBox: the full constructor stores its arguments", "[LLBBox]") { // // test the non-default constructor @@ -81,96 +69,105 @@ namespace tut LLBBox bbox2(LLVector3(1.0f, 2.0f, 3.0f), LLQuaternion(), LLVector3(2.0f, 3.0f, 4.0f), LLVector3(4.0f, 5.0f, 6.0f)); - ensure_equals("Custom bbox min", bbox2.getMinLocal(), LLVector3(2.0f, 3.0f, 4.0f)); - ensure_equals("Custom bbox max", bbox2.getMaxLocal(), LLVector3(4.0f, 5.0f, 6.0f)); - ensure_equals("Custom bbox pos agent", bbox2.getPositionAgent(), LLVector3(1.0f, 2.0f, 3.0f)); - ensure_equals("Custom bbox rotation", bbox2.getRotation(), LLQuaternion(0.0f, 0.0f, 0.0f, 1.0f)); + UNSCOPED_INFO("Custom bbox min"); + CHECK(bbox2.getMinLocal() == LLVector3(2.0f, 3.0f, 4.0f)); + UNSCOPED_INFO("Custom bbox max"); + CHECK(bbox2.getMaxLocal() == LLVector3(4.0f, 5.0f, 6.0f)); + UNSCOPED_INFO("Custom bbox pos agent"); + CHECK(bbox2.getPositionAgent() == LLVector3(1.0f, 2.0f, 3.0f)); + UNSCOPED_INFO("Custom bbox rotation"); + CHECK(bbox2.getRotation() == LLQuaternion(0.0f, 0.0f, 0.0f, 1.0f)); } - template<> template<> - void object::test<3>() + TEST_CASE("LLBBox: setMinLocal", "[LLBBox]") { // // test the setMinLocal() method // LLBBox bbox2; bbox2.setMinLocal(LLVector3(3.0f, 3.0f, 3.0f)); - ensure_equals("Custom bbox min (2)", bbox2.getMinLocal(), LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("Custom bbox min (2)"); + CHECK(bbox2.getMinLocal() == LLVector3(3.0f, 3.0f, 3.0f)); } - template<> template<> - void object::test<4>() + TEST_CASE("LLBBox: setMaxLocal", "[LLBBox]") { // // test the setMaxLocal() method // LLBBox bbox2; bbox2.setMaxLocal(LLVector3(5.0f, 5.0f, 5.0f)); - ensure_equals("Custom bbox max (2)", bbox2.getMaxLocal(), LLVector3(5.0f, 5.0f, 5.0f)); + UNSCOPED_INFO("Custom bbox max (2)"); + CHECK(bbox2.getMaxLocal() == LLVector3(5.0f, 5.0f, 5.0f)); } - template<> template<> - void object::test<5>() + TEST_CASE("LLBBox: getCenterLocal ignores position and rotation", "[LLBBox]") { // // test the getCenterLocal() method // - ensure_equals("Default bbox local center", LLBBox().getCenterLocal(), LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox local center"); + CHECK(LLBBox().getCenterLocal() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBox bbox1(LLVector3(1.0f, 2.0f, 3.0f), LLQuaternion(), LLVector3(2.0f, 4.0f, 6.0f), LLVector3(4.0f, 6.0f, 8.0f)); - ensure_equals("Custom bbox center local", bbox1.getCenterLocal(), LLVector3(3.0f, 5.0f, 7.0f)); + UNSCOPED_INFO("Custom bbox center local"); + CHECK(bbox1.getCenterLocal() == LLVector3(3.0f, 5.0f, 7.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)), LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 4.0f, 4.0f)); - ensure_equals("Custom bbox center local with rot", bbox2.getCenterLocal(), LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("Custom bbox center local with rot"); + CHECK(bbox2.getCenterLocal() == LLVector3(3.0f, 3.0f, 3.0f)); } - template<> template<> - void object::test<6>() + TEST_CASE("LLBBox: getCenterAgent applies position and rotation", "[LLBBox]") { // // test the getCenterAgent() // - ensure_equals("Default bbox agent center", LLBBox().getCenterAgent(), LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox agent center"); + CHECK(LLBBox().getCenterAgent() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBox bbox1(LLVector3(1.0f, 2.0f, 3.0f), LLQuaternion(), LLVector3(2.0f, 4.0f, 6.0f), LLVector3(4.0f, 6.0f, 8.0f)); - ensure_equals("Custom bbox center agent", bbox1.getCenterAgent(), LLVector3(4.0f, 7.0f, 10.0f)); + UNSCOPED_INFO("Custom bbox center agent"); + CHECK(bbox1.getCenterAgent() == LLVector3(4.0f, 7.0f, 10.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)), LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 4.0f, 4.0f)); - ensure("Custom bbox center agent with rot", APPROX_EQUAL(bbox2.getCenterAgent(), LLVector3(-2.0f, 4.0f, 4.0f))); + UNSCOPED_INFO("Custom bbox center agent with rot"); + CHECK(APPROX_EQUAL(bbox2.getCenterAgent(), LLVector3(-2.0f, 4.0f, 4.0f))); } - template<> template<> - void object::test<7>() + TEST_CASE("LLBBox: getExtentLocal", "[LLBBox]") { // // test the getExtentLocal() method // - ensure_equals("Default bbox local extent", LLBBox().getExtentLocal(), LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox local extent"); + CHECK(LLBBox().getExtentLocal() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBox bbox1(LLVector3(1.0f, 2.0f, 3.0f), LLQuaternion(), LLVector3(2.0f, 4.0f, 6.0f), LLVector3(4.0f, 6.0f, 8.0f)); - ensure_equals("Custom bbox extent local", bbox1.getExtentLocal(), LLVector3(2.0f, 2.0f, 2.0f)); + UNSCOPED_INFO("Custom bbox extent local"); + CHECK(bbox1.getExtentLocal() == LLVector3(2.0f, 2.0f, 2.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)), LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 4.0f, 4.0f)); - ensure_equals("Custom bbox extent local with rot", bbox1.getExtentLocal(), LLVector3(2.0f, 2.0f, 2.0f)); + UNSCOPED_INFO("Custom bbox extent local with rot"); + CHECK(bbox1.getExtentLocal() == LLVector3(2.0f, 2.0f, 2.0f)); } - template<> template<> - void object::test<8>() + TEST_CASE("LLBBox: addPointLocal grows the box", "[LLBBox]") { // // test the addPointLocal() method @@ -180,22 +177,28 @@ namespace tut bbox1.addPointLocal(LLVector3(1.0f, 1.0f, 1.0f)); bbox1.addPointLocal(LLVector3(3.0f, 3.0f, 3.0f)); - ensure_equals("addPointLocal center local (1)", bbox1.getCenterLocal(), LLVector3(2.0f, 2.0f, 2.0f)); - ensure_equals("addPointLocal center agent (1)", bbox1.getCenterAgent(), LLVector3(2.0f, 2.0f, 2.0f)); - ensure_equals("addPointLocal min (1)", bbox1.getMinLocal(), LLVector3(1.0f, 1.0f, 1.0f)); - ensure_equals("addPointLocal max (1)", bbox1.getMaxLocal(), LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("addPointLocal center local (1)"); + CHECK(bbox1.getCenterLocal() == LLVector3(2.0f, 2.0f, 2.0f)); + UNSCOPED_INFO("addPointLocal center agent (1)"); + CHECK(bbox1.getCenterAgent() == LLVector3(2.0f, 2.0f, 2.0f)); + UNSCOPED_INFO("addPointLocal min (1)"); + CHECK(bbox1.getMinLocal() == LLVector3(1.0f, 1.0f, 1.0f)); + UNSCOPED_INFO("addPointLocal max (1)"); + CHECK(bbox1.getMaxLocal() == LLVector3(3.0f, 3.0f, 3.0f)); bbox1.addPointLocal(LLVector3(0.0f, 0.0f, 0.0f)); bbox1.addPointLocal(LLVector3(1.0f, 1.0f, 1.0f)); bbox1.addPointLocal(LLVector3(2.0f, 2.0f, 2.0f)); - ensure_equals("addPointLocal center local (2)", bbox1.getCenterLocal(), LLVector3(1.5f, 1.5f, 1.5f)); - ensure_equals("addPointLocal min (2)", bbox1.getMinLocal(), LLVector3(0.0f, 0.0f, 0.0f)); - ensure_equals("addPointLocal max (2)", bbox1.getMaxLocal(), LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("addPointLocal center local (2)"); + CHECK(bbox1.getCenterLocal() == LLVector3(1.5f, 1.5f, 1.5f)); + UNSCOPED_INFO("addPointLocal min (2)"); + CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("addPointLocal max (2)"); + CHECK(bbox1.getMaxLocal() == LLVector3(3.0f, 3.0f, 3.0f)); } - template<> template<> - void object::test<9>() + TEST_CASE("LLBBox: addBBoxLocal grows the box", "[LLBBox]") { // // test the addBBoxLocal() method @@ -205,21 +208,27 @@ namespace tut bbox1.addBBoxLocal(LLBBox(LLVector3(), LLQuaternion(), LLVector3(0.0f, 0.0f, 0.0f), LLVector3(3.0f, 3.0f, 3.0f))); - ensure_equals("addPointLocal center local (3)", bbox1.getCenterLocal(), LLVector3(1.5f, 1.5f, 1.5f)); - ensure_equals("addPointLocal min (3)", bbox1.getMinLocal(), LLVector3(0.0f, 0.0f, 0.0f)); - ensure_equals("addPointLocal max (3)", bbox1.getMaxLocal(), LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("addPointLocal center local (3)"); + CHECK(bbox1.getCenterLocal() == LLVector3(1.5f, 1.5f, 1.5f)); + UNSCOPED_INFO("addPointLocal min (3)"); + CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("addPointLocal max (3)"); + CHECK(bbox1.getMaxLocal() == LLVector3(3.0f, 3.0f, 3.0f)); bbox1.addBBoxLocal(LLBBox(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(5.0f, 5.0f, 5.0f), LLVector3(10.0f, 10.0f, 10.0f))); - ensure_equals("addPointLocal center local (4)", bbox1.getCenterLocal(), LLVector3(5.0f, 5.0f, 5.0f)); - ensure_equals("addPointLocal center agent (4)", bbox1.getCenterAgent(), LLVector3(5.0f, 5.0f, 5.0f)); - ensure_equals("addPointLocal min (4)", bbox1.getMinLocal(), LLVector3(0.0f, 0.0f, 0.0f)); - ensure_equals("addPointLocal max (4)", bbox1.getMaxLocal(), LLVector3(10.0f, 10.0f, 10.0f)); + UNSCOPED_INFO("addPointLocal center local (4)"); + CHECK(bbox1.getCenterLocal() == LLVector3(5.0f, 5.0f, 5.0f)); + UNSCOPED_INFO("addPointLocal center agent (4)"); + CHECK(bbox1.getCenterAgent() == LLVector3(5.0f, 5.0f, 5.0f)); + UNSCOPED_INFO("addPointLocal min (4)"); + CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("addPointLocal max (4)"); + CHECK(bbox1.getMaxLocal() == LLVector3(10.0f, 10.0f, 10.0f)); } - template<> template<> - void object::test<10>() + TEST_CASE("LLBBox: addPointAgent on a rotated box", "[LLBBox]") { // // test the addPointAgent() method @@ -231,14 +240,17 @@ namespace tut bbox1.addPointAgent(LLVector3(1.0f, 1.0f, 1.0f)); bbox1.addPointAgent(LLVector3(3.0f, 3.0f, 3.0f)); - ensure_equals("addPointAgent center local (1)", bbox1.getCenterLocal(), LLVector3(2.0f, 2.0f, -2.0f)); - ensure_equals("addPointAgent center agent (1)", bbox1.getCenterAgent(), LLVector3(3.0f, 3.0f, 7.0f)); - ensure_equals("addPointAgent min (1)", bbox1.getMinLocal(), LLVector3(0.0f, 0.0f, -4.0f)); - ensure_equals("addPointAgent max (1)", bbox1.getMaxLocal(), LLVector3(4.0f, 4.0f, 0.0f)); + UNSCOPED_INFO("addPointAgent center local (1)"); + CHECK(bbox1.getCenterLocal() == LLVector3(2.0f, 2.0f, -2.0f)); + UNSCOPED_INFO("addPointAgent center agent (1)"); + CHECK(bbox1.getCenterAgent() == LLVector3(3.0f, 3.0f, 7.0f)); + UNSCOPED_INFO("addPointAgent min (1)"); + CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, -4.0f)); + UNSCOPED_INFO("addPointAgent max (1)"); + CHECK(bbox1.getMaxLocal() == LLVector3(4.0f, 4.0f, 0.0f)); } - template<> template<> - void object::test<11>() + TEST_CASE("LLBBox: addBBoxLocal after addPointAgent on a rotated box", "[LLBBox]") { // // test the addBBoxAgent() method @@ -253,14 +265,17 @@ namespace tut bbox1.addBBoxLocal(LLBBox(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(5.0f, 5.0f, 5.0f), LLVector3(10.0f, 10.0f, 10.0f))); - ensure_equals("addPointAgent center local (2)", bbox1.getCenterLocal(), LLVector3(5.0f, 5.0f, 3.0f)); - ensure_equals("addPointAgent center agent (2)", bbox1.getCenterAgent(), LLVector3(6.0f, -10.0f, 8.0f)); - ensure_equals("addPointAgent min (2)", bbox1.getMinLocal(), LLVector3(0.0f, 0.0f, -4.0f)); - ensure_equals("addPointAgent max (2)", bbox1.getMaxLocal(), LLVector3(10.0f, 10.0f, 10.0f)); + UNSCOPED_INFO("addPointAgent center local (2)"); + CHECK(bbox1.getCenterLocal() == LLVector3(5.0f, 5.0f, 3.0f)); + UNSCOPED_INFO("addPointAgent center agent (2)"); + CHECK(bbox1.getCenterAgent() == LLVector3(6.0f, -10.0f, 8.0f)); + UNSCOPED_INFO("addPointAgent min (2)"); + CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, -4.0f)); + UNSCOPED_INFO("addPointAgent max (2)"); + CHECK(bbox1.getMaxLocal() == LLVector3(10.0f, 10.0f, 10.0f)); } - template<> template<> - void object::test<12>() + TEST_CASE("LLBBox: expand by zero then a positive then a negative amount", "[LLBBox]") { // // test the expand() method @@ -269,32 +284,42 @@ namespace tut LLBBox bbox1; bbox1.expand(0.0); - ensure_equals("Zero-expanded Default BBox center", bbox1.getCenterLocal(), LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Zero-expanded Default BBox center"); + CHECK(bbox1.getCenterLocal() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); bbox2.expand(0.0); - ensure_equals("Zero-expanded center local", bbox2.getCenterLocal(), LLVector3(2.0f, 2.0f, 2.0f)); - ensure_equals("Zero-expanded center agent", bbox2.getCenterAgent(), LLVector3(3.0f, 3.0f, 3.0f)); - ensure_equals("Zero-expanded min", bbox2.getMinLocal(), LLVector3(1.0f, 1.0f, 1.0f)); - ensure_equals("Zero-expanded max", bbox2.getMaxLocal(), LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("Zero-expanded center local"); + CHECK(bbox2.getCenterLocal() == LLVector3(2.0f, 2.0f, 2.0f)); + UNSCOPED_INFO("Zero-expanded center agent"); + CHECK(bbox2.getCenterAgent() == LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("Zero-expanded min"); + CHECK(bbox2.getMinLocal() == LLVector3(1.0f, 1.0f, 1.0f)); + UNSCOPED_INFO("Zero-expanded max"); + CHECK(bbox2.getMaxLocal() == LLVector3(3.0f, 3.0f, 3.0f)); bbox2.expand(0.5); - ensure_equals("Positive-expanded center", bbox2.getCenterLocal(), LLVector3(2.0f, 2.0f, 2.0f)); - ensure_equals("Positive-expanded min", bbox2.getMinLocal(), LLVector3(0.5f, 0.5f, 0.5f)); - ensure_equals("Positive-expanded max", bbox2.getMaxLocal(), LLVector3(3.5f, 3.5f, 3.5f)); + UNSCOPED_INFO("Positive-expanded center"); + CHECK(bbox2.getCenterLocal() == LLVector3(2.0f, 2.0f, 2.0f)); + UNSCOPED_INFO("Positive-expanded min"); + CHECK(bbox2.getMinLocal() == LLVector3(0.5f, 0.5f, 0.5f)); + UNSCOPED_INFO("Positive-expanded max"); + CHECK(bbox2.getMaxLocal() == LLVector3(3.5f, 3.5f, 3.5f)); bbox2.expand(-1.0); - ensure_equals("Negative-expanded center", bbox2.getCenterLocal(), LLVector3(2.0f, 2.0f, 2.0f)); - ensure_equals("Negative-expanded min", bbox2.getMinLocal(), LLVector3(1.5f, 1.5f, 1.5f)); - ensure_equals("Negative-expanded max", bbox2.getMaxLocal(), LLVector3(2.5f, 2.5f, 2.5f)); + UNSCOPED_INFO("Negative-expanded center"); + CHECK(bbox2.getCenterLocal() == LLVector3(2.0f, 2.0f, 2.0f)); + UNSCOPED_INFO("Negative-expanded min"); + CHECK(bbox2.getMinLocal() == LLVector3(1.5f, 1.5f, 1.5f)); + UNSCOPED_INFO("Negative-expanded max"); + CHECK(bbox2.getMaxLocal() == LLVector3(2.5f, 2.5f, 2.5f)); } - template<> template<> - void object::test<13>() + TEST_CASE("LLBBox: localToAgent with and without rotation", "[LLBBox]") { // // test the localToAgent() method @@ -303,16 +328,17 @@ namespace tut LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); - ensure_equals("localToAgent(1,2,3)", bbox1.localToAgent(LLVector3(1.0f, 2.0f, 3.0f)), LLVector3(2.0f, 3.0f, 4.0f)); + UNSCOPED_INFO("localToAgent(1,2,3)"); + CHECK(bbox1.localToAgent(LLVector3(1.0f, 2.0f, 3.0f)) == LLVector3(2.0f, 3.0f, 4.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(1.0f, 0.0f, 0.0f)), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); - ensure("localToAgent(1,2,3) rot", APPROX_EQUAL(bbox2.localToAgent(LLVector3(1.0f, 2.0f, 3.0f)), LLVector3(2.0f, -2.0f, 3.0f))); + UNSCOPED_INFO("localToAgent(1,2,3) rot"); + CHECK(APPROX_EQUAL(bbox2.localToAgent(LLVector3(1.0f, 2.0f, 3.0f)), LLVector3(2.0f, -2.0f, 3.0f))); } - template<> template<> - void object::test<14>() + TEST_CASE("LLBBox: agentToLocal and its round trip through localToAgent", "[LLBBox]") { // // test the agentToLocal() method @@ -321,20 +347,23 @@ namespace tut LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); - ensure_equals("agentToLocal(1,2,3)", bbox1.agentToLocal(LLVector3(1.0f, 2.0f, 3.0f)), LLVector3(0.0f, 1.0f, 2.0f)); - ensure_equals("agentToLocal(localToAgent)", bbox1.agentToLocal(bbox1.localToAgent(LLVector3(1.0f, 2.0f, 3.0f))), + UNSCOPED_INFO("agentToLocal(1,2,3)"); + CHECK(bbox1.agentToLocal(LLVector3(1.0f, 2.0f, 3.0f)) == LLVector3(0.0f, 1.0f, 2.0f)); + UNSCOPED_INFO("agentToLocal(localToAgent)"); + CHECK(bbox1.agentToLocal(bbox1.localToAgent(LLVector3(1.0f, 2.0f, 3.0f))) == LLVector3(1.0f, 2.0f, 3.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(1.0f, 0.0f, 0.0f)), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); - ensure("agentToLocal(1,2,3) rot", APPROX_EQUAL(bbox2.agentToLocal(LLVector3(1.0f, 2.0f, 3.0f)), LLVector3(0.0f, 2.0f, -1.0f))); - ensure("agentToLocal(localToAgent) rot", APPROX_EQUAL(bbox2.agentToLocal(bbox2.localToAgent(LLVector3(1.0f, 2.0f, 3.0f))), + UNSCOPED_INFO("agentToLocal(1,2,3) rot"); + CHECK(APPROX_EQUAL(bbox2.agentToLocal(LLVector3(1.0f, 2.0f, 3.0f)), LLVector3(0.0f, 2.0f, -1.0f))); + UNSCOPED_INFO("agentToLocal(localToAgent) rot"); + CHECK(APPROX_EQUAL(bbox2.agentToLocal(bbox2.localToAgent(LLVector3(1.0f, 2.0f, 3.0f))), LLVector3(1.0f, 2.0f, 3.0f))); } - template<> template<> - void object::test<15>() + TEST_CASE("LLBBox: containsPointLocal is inclusive at min and max", "[LLBBox]") { // // test the containsPointLocal() method @@ -343,15 +372,19 @@ namespace tut LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 2.0f, 3.0f), LLVector3(3.0f, 4.0f, 5.0f)); - ensure("containsPointLocal(0,0,0)", bbox1.containsPointLocal(LLVector3(0.0f, 0.0f, 0.0f)) == false); - ensure("containsPointLocal(1,2,3)", bbox1.containsPointLocal(LLVector3(1.0f, 2.0f, 3.0f)) == true); - ensure("containsPointLocal(0.999,2,3)", bbox1.containsPointLocal(LLVector3(0.999f, 2.0f, 3.0f)) == false); - ensure("containsPointLocal(3,4,5)", bbox1.containsPointLocal(LLVector3(3.0f, 4.0f, 5.0f)) == true); - ensure("containsPointLocal(3,4,5.001)", bbox1.containsPointLocal(LLVector3(3.0f, 4.0f, 5.001f)) == false); + UNSCOPED_INFO("containsPointLocal(0,0,0)"); + CHECK_FALSE(bbox1.containsPointLocal(LLVector3(0.0f, 0.0f, 0.0f))); + UNSCOPED_INFO("containsPointLocal(1,2,3)"); + CHECK(bbox1.containsPointLocal(LLVector3(1.0f, 2.0f, 3.0f))); + UNSCOPED_INFO("containsPointLocal(0.999,2,3)"); + CHECK_FALSE(bbox1.containsPointLocal(LLVector3(0.999f, 2.0f, 3.0f))); + UNSCOPED_INFO("containsPointLocal(3,4,5)"); + CHECK(bbox1.containsPointLocal(LLVector3(3.0f, 4.0f, 5.0f))); + UNSCOPED_INFO("containsPointLocal(3,4,5.001)"); + CHECK_FALSE(bbox1.containsPointLocal(LLVector3(3.0f, 4.0f, 5.001f))); } - template<> template<> - void object::test<16>() + TEST_CASE("LLBBox: containsPointAgent is inclusive at min and max", "[LLBBox]") { // // test the containsPointAgent() method @@ -360,11 +393,16 @@ namespace tut LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 2.0f, 3.0f), LLVector3(3.0f, 4.0f, 5.0f)); - ensure("containsPointAgent(0,0,0)", bbox1.containsPointAgent(LLVector3(0.0f, 0.0f, 0.0f)) == false); - ensure("containsPointAgent(2,3,4)", bbox1.containsPointAgent(LLVector3(2.0f, 3.0f, 4.0f)) == true); - ensure("containsPointAgent(2,2.999,4)", bbox1.containsPointAgent(LLVector3(2.0f, 2.999f, 4.0f)) == false); - ensure("containsPointAgent(4,5,6)", bbox1.containsPointAgent(LLVector3(4.0f, 5.0f, 6.0f)) == true); - ensure("containsPointAgent(4,5.001,6)", bbox1.containsPointAgent(LLVector3(4.0f, 5.001f, 6.0f)) == false); + UNSCOPED_INFO("containsPointAgent(0,0,0)"); + CHECK_FALSE(bbox1.containsPointAgent(LLVector3(0.0f, 0.0f, 0.0f))); + UNSCOPED_INFO("containsPointAgent(2,3,4)"); + CHECK(bbox1.containsPointAgent(LLVector3(2.0f, 3.0f, 4.0f))); + UNSCOPED_INFO("containsPointAgent(2,2.999,4)"); + CHECK_FALSE(bbox1.containsPointAgent(LLVector3(2.0f, 2.999f, 4.0f))); + UNSCOPED_INFO("containsPointAgent(4,5,6)"); + CHECK(bbox1.containsPointAgent(LLVector3(4.0f, 5.0f, 6.0f))); + UNSCOPED_INFO("containsPointAgent(4,5.001,6)"); + CHECK_FALSE(bbox1.containsPointAgent(LLVector3(4.0f, 5.001f, 6.0f))); } } diff --git a/indra/llmath/tests/llbboxlocal_test.cpp b/indra/llmath/tests/llbboxlocal_test.cpp index 41d6326bc5..3a41f5f222 100644 --- a/indra/llmath/tests/llbboxlocal_test.cpp +++ b/indra/llmath/tests/llbboxlocal_test.cpp @@ -27,31 +27,16 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llbboxlocal.h" -namespace tut -{ namespace { - struct LLBBoxLocalData - { - }; - - typedef test_group factory; - typedef factory::object object; -} } namespace { - tut::factory llbboxlocal_test_factory("LLBBoxLocal"); -} - -namespace tut -{ - template<> template<> - void object::test<1>() + TEST_CASE("LLBBoxLocal: the default constructor gives an empty box at the origin", "[LLBBoxLocal]") { // // test the default constructor @@ -59,12 +44,13 @@ namespace tut LLBBoxLocal bbox1; - ensure_equals("Default bbox min", bbox1.getMin(), LLVector3(0.0f, 0.0f, 0.0f)); - ensure_equals("Default bbox max", bbox1.getMax(), LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox min"); + CHECK(bbox1.getMin() == LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox max"); + CHECK(bbox1.getMax() == LLVector3(0.0f, 0.0f, 0.0f)); } - template<> template<> - void object::test<2>() + TEST_CASE("LLBBoxLocal: the constructor stores min and max", "[LLBBoxLocal]") { // // test the non-default constructor @@ -72,12 +58,13 @@ namespace tut LLBBoxLocal bbox2(LLVector3(-1.0f, -2.0f, 0.0f), LLVector3(1.0f, 2.0f, 3.0f)); - ensure_equals("Custom bbox min", bbox2.getMin(), LLVector3(-1.0f, -2.0f, 0.0f)); - ensure_equals("Custom bbox max", bbox2.getMax(), LLVector3(1.0f, 2.0f, 3.0f)); + UNSCOPED_INFO("Custom bbox min"); + CHECK(bbox2.getMin() == LLVector3(-1.0f, -2.0f, 0.0f)); + UNSCOPED_INFO("Custom bbox max"); + CHECK(bbox2.getMax() == LLVector3(1.0f, 2.0f, 3.0f)); } - template<> template<> - void object::test<3>() + TEST_CASE("LLBBoxLocal: setMin", "[LLBBoxLocal]") { // // test the setMin() @@ -89,11 +76,11 @@ namespace tut LLBBoxLocal bbox2; bbox2.setMin(LLVector3(1.0f, 2.0f, 3.0f)); - ensure_equals("Custom bbox min (2)", bbox2.getMin(), LLVector3(1.0f, 2.0f, 3.0f)); + UNSCOPED_INFO("Custom bbox min (2)"); + CHECK(bbox2.getMin() == LLVector3(1.0f, 2.0f, 3.0f)); } - template<> template<> - void object::test<4>() + TEST_CASE("LLBBoxLocal: setMax", "[LLBBoxLocal]") { // // test the setMax() @@ -105,29 +92,31 @@ namespace tut LLBBoxLocal bbox2; bbox2.setMax(LLVector3(10.0f, 20.0f, 30.0f)); - ensure_equals("Custom bbox max (2)", bbox2.getMax(), LLVector3(10.0f, 20.0f, 30.0f)); + UNSCOPED_INFO("Custom bbox max (2)"); + CHECK(bbox2.getMax() == LLVector3(10.0f, 20.0f, 30.0f)); } - template<> template<> - void object::test<5>() + TEST_CASE("LLBBoxLocal: getCenter including a box with min above max", "[LLBBoxLocal]") { // // test the getCenter() method // - ensure_equals("Default bbox center", LLBBoxLocal().getCenter(), LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox center"); + CHECK(LLBBoxLocal().getCenter() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBoxLocal bbox1(LLVector3(-1.0f, -1.0f, -1.0f), LLVector3(0.0f, 0.0f, 0.0f)); - ensure_equals("Custom bbox center", bbox1.getCenter(), LLVector3(-0.5f, -0.5f, -0.5f)); + UNSCOPED_INFO("Custom bbox center"); + CHECK(bbox1.getCenter() == LLVector3(-0.5f, -0.5f, -0.5f)); LLBBoxLocal bbox2(LLVector3(0.0f, 0.0f, 0.0f), LLVector3(-1.0f, -1.0f, -1.0f)); - ensure_equals("Invalid bbox center", bbox2.getCenter(), LLVector3(-0.5f, -0.5f, -0.5f)); + UNSCOPED_INFO("Invalid bbox center"); + CHECK(bbox2.getCenter() == LLVector3(-0.5f, -0.5f, -0.5f)); } - template<> template<> - void object::test<6>() + TEST_CASE("LLBBoxLocal: getExtent", "[LLBBoxLocal]") { // // test the getExtent() method @@ -135,15 +124,16 @@ namespace tut LLBBoxLocal bbox2(LLVector3(0.0f, 0.0f, 0.0f), LLVector3(-1.0f, -1.0f, -1.0f)); - ensure_equals("Default bbox extent", LLBBoxLocal().getExtent(), LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Default bbox extent"); + CHECK(LLBBoxLocal().getExtent() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBoxLocal bbox3(LLVector3(-1.0f, -1.0f, -1.0f), LLVector3(1.0f, 2.0f, 0.0f)); - ensure_equals("Custom bbox extent", bbox3.getExtent(), LLVector3(2.0f, 3.0f, 1.0f)); + UNSCOPED_INFO("Custom bbox extent"); + CHECK(bbox3.getExtent() == LLVector3(2.0f, 3.0f, 1.0f)); } - template<> template<> - void object::test<7>() + TEST_CASE("LLBBoxLocal: addPoint grows the box only for points outside it", "[LLBBoxLocal]") { // // test the addPoint() method @@ -157,27 +147,35 @@ namespace tut bbox1.addPoint(LLVector3(-1.0f, -2.0f, -3.0f)); bbox1.addPoint(LLVector3(3.0f, 4.0f, 5.0f)); - ensure_equals("Custom BBox center (1)", bbox1.getCenter(), LLVector3(1.0f, 1.0f, 1.0f)); - ensure_equals("Custom BBox min (1)", bbox1.getMin(), LLVector3(-1.0f, -2.0f, -3.0f)); - ensure_equals("Custom BBox max (1)", bbox1.getMax(), LLVector3(3.0f, 4.0f, 5.0f)); + UNSCOPED_INFO("Custom BBox center (1)"); + CHECK(bbox1.getCenter() == LLVector3(1.0f, 1.0f, 1.0f)); + UNSCOPED_INFO("Custom BBox min (1)"); + CHECK(bbox1.getMin() == LLVector3(-1.0f, -2.0f, -3.0f)); + UNSCOPED_INFO("Custom BBox max (1)"); + CHECK(bbox1.getMax() == LLVector3(3.0f, 4.0f, 5.0f)); bbox1.addPoint(LLVector3(0.0f, 0.0f, 0.0f)); bbox1.addPoint(LLVector3(1.0f, 2.0f, 3.0f)); bbox1.addPoint(LLVector3(2.0f, 2.0f, 2.0f)); - ensure_equals("Custom BBox center (2)", bbox1.getCenter(), LLVector3(1.0f, 1.0f, 1.0f)); - ensure_equals("Custom BBox min (2)", bbox1.getMin(), LLVector3(-1.0f, -2.0f, -3.0f)); - ensure_equals("Custom BBox max (2)", bbox1.getMax(), LLVector3(3.0f, 4.0f, 5.0f)); + UNSCOPED_INFO("Custom BBox center (2)"); + CHECK(bbox1.getCenter() == LLVector3(1.0f, 1.0f, 1.0f)); + UNSCOPED_INFO("Custom BBox min (2)"); + CHECK(bbox1.getMin() == LLVector3(-1.0f, -2.0f, -3.0f)); + UNSCOPED_INFO("Custom BBox max (2)"); + CHECK(bbox1.getMax() == LLVector3(3.0f, 4.0f, 5.0f)); bbox1.addPoint(LLVector3(5.0f, 5.0f, 5.0f)); - ensure_equals("Custom BBox center (3)", bbox1.getCenter(), LLVector3(2.0f, 1.5f, 1.0f)); - ensure_equals("Custom BBox min (3)", bbox1.getMin(), LLVector3(-1.0f, -2.0f, -3.0f)); - ensure_equals("Custom BBox max (3)", bbox1.getMax(), LLVector3(5.0f, 5.0f, 5.0f)); + UNSCOPED_INFO("Custom BBox center (3)"); + CHECK(bbox1.getCenter() == LLVector3(2.0f, 1.5f, 1.0f)); + UNSCOPED_INFO("Custom BBox min (3)"); + CHECK(bbox1.getMin() == LLVector3(-1.0f, -2.0f, -3.0f)); + UNSCOPED_INFO("Custom BBox max (3)"); + CHECK(bbox1.getMax() == LLVector3(5.0f, 5.0f, 5.0f)); } - template<> template<> - void object::test<8>() + TEST_CASE("LLBBoxLocal: addBBox grows the box to cover both boxes", "[LLBBoxLocal]") { // // test the addBBox() methods @@ -190,19 +188,24 @@ namespace tut LLBBoxLocal bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLVector3(2.0f, 2.0f, 2.0f)); bbox2.addBBox(LLBBoxLocal(LLVector3(1.5f, 1.5f, 1.5f), LLVector3(3.0f, 3.0f, 3.0f))); - ensure_equals("Custom BBox center (4)", bbox2.getCenter(), LLVector3(2.0f, 2.0f, 2.0f)); - ensure_equals("Custom BBox min (4)", bbox2.getMin(), LLVector3(1.0f, 1.0f, 1.0f)); - ensure_equals("Custom BBox max (4)", bbox2.getMax(), LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("Custom BBox center (4)"); + CHECK(bbox2.getCenter() == LLVector3(2.0f, 2.0f, 2.0f)); + UNSCOPED_INFO("Custom BBox min (4)"); + CHECK(bbox2.getMin() == LLVector3(1.0f, 1.0f, 1.0f)); + UNSCOPED_INFO("Custom BBox max (4)"); + CHECK(bbox2.getMax() == LLVector3(3.0f, 3.0f, 3.0f)); bbox2.addBBox(LLBBoxLocal(LLVector3(-1.0f, -1.0f, -1.0f), LLVector3(0.0f, 0.0f, 0.0f))); - ensure_equals("Custom BBox center (5)", bbox2.getCenter(), LLVector3(1.0f, 1.0f, 1.0f)); - ensure_equals("Custom BBox min (5)", bbox2.getMin(), LLVector3(-1.0f, -1.0f, -1.0f)); - ensure_equals("Custom BBox max (5)", bbox2.getMax(), LLVector3(3.0f, 3.0f, 3.0f)); + UNSCOPED_INFO("Custom BBox center (5)"); + CHECK(bbox2.getCenter() == LLVector3(1.0f, 1.0f, 1.0f)); + UNSCOPED_INFO("Custom BBox min (5)"); + CHECK(bbox2.getMin() == LLVector3(-1.0f, -1.0f, -1.0f)); + UNSCOPED_INFO("Custom BBox max (5)"); + CHECK(bbox2.getMax() == LLVector3(3.0f, 3.0f, 3.0f)); } - template<> template<> - void object::test<9>() + TEST_CASE("LLBBoxLocal: expand by zero then a positive then a negative amount", "[LLBBoxLocal]") { // // test the expand() method @@ -211,25 +214,35 @@ namespace tut LLBBoxLocal bbox1; bbox1.expand(0.0f); - ensure_equals("Zero-expanded Default BBox center", bbox1.getCenter(), LLVector3(0.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("Zero-expanded Default BBox center"); + CHECK(bbox1.getCenter() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBoxLocal bbox2(LLVector3(1.0f, 2.0f, 3.0f), LLVector3(3.0f, 4.0f, 5.0f)); bbox2.expand(0.0f); - ensure_equals("Zero-expanded BBox center", bbox2.getCenter(), LLVector3(2.0f, 3.0f, 4.0f)); - ensure_equals("Zero-expanded BBox min", bbox2.getMin(), LLVector3(1.0f, 2.0f, 3.0f)); - ensure_equals("Zero-expanded BBox max", bbox2.getMax(), LLVector3(3.0f, 4.0f, 5.0f)); + UNSCOPED_INFO("Zero-expanded BBox center"); + CHECK(bbox2.getCenter() == LLVector3(2.0f, 3.0f, 4.0f)); + UNSCOPED_INFO("Zero-expanded BBox min"); + CHECK(bbox2.getMin() == LLVector3(1.0f, 2.0f, 3.0f)); + UNSCOPED_INFO("Zero-expanded BBox max"); + CHECK(bbox2.getMax() == LLVector3(3.0f, 4.0f, 5.0f)); bbox2.expand(0.5f); - ensure_equals("Positive-expanded BBox center", bbox2.getCenter(), LLVector3(2.0f, 3.0f, 4.0f)); - ensure_equals("Positive-expanded BBox min", bbox2.getMin(), LLVector3(0.5f, 1.5f, 2.5f)); - ensure_equals("Positive-expanded BBox max", bbox2.getMax(), LLVector3(3.5f, 4.5f, 5.5f)); + UNSCOPED_INFO("Positive-expanded BBox center"); + CHECK(bbox2.getCenter() == LLVector3(2.0f, 3.0f, 4.0f)); + UNSCOPED_INFO("Positive-expanded BBox min"); + CHECK(bbox2.getMin() == LLVector3(0.5f, 1.5f, 2.5f)); + UNSCOPED_INFO("Positive-expanded BBox max"); + CHECK(bbox2.getMax() == LLVector3(3.5f, 4.5f, 5.5f)); bbox2.expand(-1.0f); - ensure_equals("Negative-expanded BBox center", bbox2.getCenter(), LLVector3(2.0f, 3.0f, 4.0f)); - ensure_equals("Negative-expanded BBox min", bbox2.getMin(), LLVector3(1.5f, 2.5f, 3.5f)); - ensure_equals("Negative-expanded BBox max", bbox2.getMax(), LLVector3(2.5f, 3.5f, 4.5f)); + UNSCOPED_INFO("Negative-expanded BBox center"); + CHECK(bbox2.getCenter() == LLVector3(2.0f, 3.0f, 4.0f)); + UNSCOPED_INFO("Negative-expanded BBox min"); + CHECK(bbox2.getMin() == LLVector3(1.5f, 2.5f, 3.5f)); + UNSCOPED_INFO("Negative-expanded BBox max"); + CHECK(bbox2.getMax() == LLVector3(2.5f, 3.5f, 4.5f)); } } diff --git a/indra/llmath/tests/llmatrix3a_test.cpp b/indra/llmath/tests/llmatrix3a_test.cpp index b0da1591dd..e58a45e79c 100644 --- a/indra/llmath/tests/llmatrix3a_test.cpp +++ b/indra/llmath/tests/llmatrix3a_test.cpp @@ -30,7 +30,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llmath.h" #include "../llsimdmath.h" #include "../llvector4a.h" @@ -46,8 +46,6 @@ #include #include -namespace tut -{ namespace { std::string precise(double v) @@ -84,9 +82,9 @@ namespace { const double g = got.getRow(row)[col]; const double e = expected.m[row][col]; - ensure(what + " row " + std::to_string(row) + " column " + std::to_string(col) + ": " + - precise(g) + " vs " + precise(e), - std::fabs(g - e) <= tolerance * llmax(1.0, std::fabs(e))); + UNSCOPED_INFO(what + " row " + std::to_string(row) + " column " + std::to_string(col) + ": " + + precise(g) + " vs " + precise(e)); + REQUIRE(std::fabs(g - e) <= tolerance * llmax(1.0, std::fabs(e))); } } } @@ -143,48 +141,49 @@ namespace LLQuaternion(0.9f, LLVector3(0.577f, 0.577f, 0.577f)), LLQuaternion(3.14159f, LLVector3(-0.3f, 0.8f, 0.5f)), }; -} -} // namespace tut +} // anonymous namespace -namespace tut -{ namespace { - struct llmatrix3a_data - { - }; - typedef test_group llmatrix3a_test; - typedef llmatrix3a_test::object llmatrix3a_object; - tut::llmatrix3a_test llmatrix3a_testcase("LLMatrix3a"); -} - // Plain data of three registers; the identity; set and get by row and // by column. - template<> template<> - void llmatrix3a_object::test<1>() + TEST_CASE("LLMatrix3a: plain data of three registers; the identity; rows and columns", "[LLMatrix3a]") { - ensure_equals("sizeof LLMatrix3a", sizeof(LLMatrix3a), (size_t)48); - ensure_equals("alignof LLMatrix3a", alignof(LLMatrix3a), (size_t)16); - ensure_equals("sizeof LLRotation", sizeof(LLRotation), (size_t)48); - ensure("trivially copyable", std::is_trivially_copyable::value); + UNSCOPED_INFO("sizeof LLMatrix3a"); + CHECK(sizeof(LLMatrix3a) == (size_t)48); + UNSCOPED_INFO("alignof LLMatrix3a"); + CHECK(alignof(LLMatrix3a) == (size_t)16); + UNSCOPED_INFO("sizeof LLRotation"); + CHECK(sizeof(LLRotation) == (size_t)48); + UNSCOPED_INFO("trivially copyable"); + CHECK(std::is_trivially_copyable::value); const LLMatrix3a& identity = LLMatrix3a::getIdentity(); for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { - ensure_equals("identity", identity.getRow(row)[col], row == col ? 1.f : 0.f); + UNSCOPED_INFO("identity"); + REQUIRE(identity.getRow(row)[col] == (row == col ? 1.f : 0.f)); } } const LLMatrix3a m = odd_matrix(); - ensure_equals("getRow 1 y", m.getRow(1)[1], 5.5f); - ensure_equals("getRow 2 w", m.getRow(2)[3], 300.f); - ensure("getRow is getRow(N)", same_bits(m.getRow<0>(), m.getRow(0)) && same_bits(m.getRow<1>(), m.getRow(1)) && same_bits(m.getRow<2>(), m.getRow(2))); + UNSCOPED_INFO("getRow 1 y"); + CHECK(m.getRow(1)[1] == 5.5f); + UNSCOPED_INFO("getRow 2 w"); + CHECK(m.getRow(2)[3] == 300.f); + { + INFO("getRow is getRow(N)"); + CHECK(same_bits(m.getRow<0>(), m.getRow(0))); + CHECK(same_bits(m.getRow<1>(), m.getRow(1))); + CHECK(same_bits(m.getRow<2>(), m.getRow(2))); + } LLMatrix3a by_rows; by_rows.setRows(m.getRow<0>(), m.getRow<1>(), m.getRow<2>()); - ensure("setRows", same_bits(by_rows, m)); + UNSCOPED_INFO("setRows"); + CHECK(same_bits(by_rows, m)); LLMatrix3a by_columns; by_columns.setColumns(m.getRow<0>(), m.getRow<1>(), m.getRow<2>()); @@ -192,14 +191,14 @@ namespace { for (int col = 0; col < 3; ++col) { - ensure_equals("setColumns is the transpose of setRows", by_columns.getRow(row)[col], m.getRow(col)[row]); + UNSCOPED_INFO("setColumns is the transpose of setRows"); + REQUIRE(by_columns.getRow(row)[col] == m.getRow(col)[row]); } } } // loadu takes the scalar matrix row by row. - template<> template<> - void llmatrix3a_object::test<2>() + TEST_CASE("LLMatrix3a: loadu takes the scalar matrix row by row", "[LLMatrix3a]") { const F32 values[9] = {1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f}; const LLMatrix3 m3(values); @@ -209,16 +208,17 @@ namespace { for (int j = 0; j < 3; ++j) { - ensure_equals("loadu", m.getRow(i)[j], m3.mMatrix[i][j]); + UNSCOPED_INFO("loadu"); + REQUIRE(m.getRow(i)[j] == m3.mMatrix[i][j]); } - ensure_equals("loadu w is zero", m.getRow(i)[3], 0.f); + UNSCOPED_INFO("loadu w is zero"); + REQUIRE(m.getRow(i)[3] == 0.f); } } // The transpose swaps rows and columns, twice is the identity, and // the fourth lanes are what the header says they are. - template<> template<> - void llmatrix3a_object::test<3>() + TEST_CASE("LLMatrix3a: setTranspose swaps rows and columns and twice gives the matrix back", "[LLMatrix3a]") { const LLMatrix3a m = odd_matrix(); LLMatrix3a t; @@ -227,26 +227,31 @@ namespace { for (int col = 0; col < 3; ++col) { - ensure_equals("setTranspose", t.getRow(row)[col], m.getRow(col)[row]); + UNSCOPED_INFO("setTranspose"); + REQUIRE(t.getRow(row)[col] == m.getRow(col)[row]); } } - ensure_equals("transpose row 0 w", t.getRow(0)[3], m.getRow(2)[1]); - ensure_equals("transpose row 1 w", t.getRow(1)[3], m.getRow(2)[0]); - ensure_equals("transpose row 2 w", t.getRow(2)[3], m.getRow(2)[0]); + UNSCOPED_INFO("transpose row 0 w"); + CHECK(t.getRow(0)[3] == m.getRow(2)[1]); + UNSCOPED_INFO("transpose row 1 w"); + CHECK(t.getRow(1)[3] == m.getRow(2)[0]); + UNSCOPED_INFO("transpose row 2 w"); + CHECK(t.getRow(2)[3] == m.getRow(2)[0]); LLMatrix3a back; back.setTranspose(t); - ensure("transpose twice", same_bits3(back, m)); + UNSCOPED_INFO("transpose twice"); + CHECK(same_bits3(back, m)); // in place LLMatrix3a self = m; self.setTranspose(self); - ensure("setTranspose in place", same_bits(self, t)); + UNSCOPED_INFO("setTranspose in place"); + CHECK(same_bits(self, t)); } // rotate is the scalar type's v * M, and setRotated is rotate. - template<> template<> - void llmatrix3a_object::test<4>() + TEST_CASE("LLMatrix3a: rotate is the scalar type's v * M and setRotated is rotate", "[LLMatrix3a]") { const LLMatrix3a m = odd_matrix(); const Rows r = rows_of(m); @@ -257,20 +262,22 @@ namespace for (int col = 0; col < 3; ++col) { const double expected = v[0] * r.m[0][col] + v[1] * r.m[1][col] + v[2] * r.m[2][col]; - ensure("rotate lane " + std::to_string(col) + ": " + precise(rotated[col]) + " vs " + precise(expected), - std::fabs(rotated[col] - expected) <= 1e-6 * llmax(1.0, std::fabs(expected))); + UNSCOPED_INFO("rotate lane " + std::to_string(col) + ": " + precise(rotated[col]) + " vs " + precise(expected)); + REQUIRE(std::fabs(rotated[col] - expected) <= 1e-6 * llmax(1.0, std::fabs(expected))); } LLRotation rot; rot.setRows(m.getRow<0>(), m.getRow<1>(), m.getRow<2>()); LLVector4a set_rotated; set_rotated.setRotated(rot, v); - ensure("setRotated is rotate", same_bits(set_rotated, rotated)); + UNSCOPED_INFO("setRotated is rotate"); + CHECK(same_bits(set_rotated, rotated)); // in place LLVector4a self = v; m.rotate(self, self); - ensure("rotate in place", same_bits(self, rotated)); + UNSCOPED_INFO("rotate in place"); + CHECK(same_bits(self, rotated)); for (const LLQuaternion& q : ROTATIONS) { @@ -281,15 +288,15 @@ namespace const LLVector3 expected = v3 * m3; LLVector4a got; a.rotate(v, got); - ensure("rotate is v * LLMatrix3", got.equals3(LLVector4a(expected.mV[0], expected.mV[1], expected.mV[2]), 1e-5f)); + UNSCOPED_INFO("rotate is v * LLMatrix3"); + REQUIRE(got.equals3(LLVector4a(expected.mV[0], expected.mV[1], expected.mV[2]), 1e-5f)); } } // The product against double, the identity on both sides, and the // property that names it: rotating by the product is rotating by the // left one and then the right. - template<> template<> - void llmatrix3a_object::test<5>() + TEST_CASE("LLMatrix3a: setMul against double and the identity and as a then b", "[LLMatrix3a]") { const LLMatrix3a a = odd_matrix(); const LLMatrix3a b(LLVector4a(0.5f, -1.f, 2.f, 1.f), @@ -324,33 +331,38 @@ namespace const LLMatrix3 product3 = a3 * b3; LLMatrix3a from_scalar; from_scalar.loadu(product3); - ensure("setMul(a, b) is LLMatrix3 a * b", product.isApproximatelyEqual(from_scalar, 1e-4f)); + UNSCOPED_INFO("setMul(a, b) is LLMatrix3 a * b"); + CHECK(product.isApproximatelyEqual(from_scalar, 1e-4f)); LLMatrix3a left, right; left.setMul(LLMatrix3a::getIdentity(), a); right.setMul(a, LLMatrix3a::getIdentity()); - ensure("identity on the left", same_bits(left, a)); - ensure("identity on the right", same_bits3(right, a)); + UNSCOPED_INFO("identity on the left"); + CHECK(same_bits(left, a)); + UNSCOPED_INFO("identity on the right"); + CHECK(same_bits3(right, a)); // aliasing either operand LLMatrix3a alias = a; alias.setMul(alias, b); - ensure("setMul aliasing a", same_bits(alias, product)); + UNSCOPED_INFO("setMul aliasing a"); + CHECK(same_bits(alias, product)); alias = b; alias.setMul(a, alias); - ensure("setMul aliasing b", same_bits(alias, product)); + UNSCOPED_INFO("setMul aliasing b"); + CHECK(same_bits(alias, product)); const LLVector4a v(1.f, -2.f, 0.5f, 0.f); LLVector4a by_a, then_b, by_product; a.rotate(v, by_a); b.rotate(by_a, then_b); product.rotate(v, by_product); - ensure("v * (ab) is (v * a) * b", by_product.equals3(then_b, 1e-4f)); + UNSCOPED_INFO("v * (ab) is (v * a) * b"); + CHECK(by_product.equals3(then_b, 1e-4f)); } // The determinant, both forms, against double. - template<> template<> - void llmatrix3a_object::test<6>() + TEST_CASE("LLMatrix3a: getDeterminant in both forms against double", "[LLMatrix3a]") { const LLMatrix3a m = odd_matrix(); const Rows r = rows_of(m); @@ -360,56 +372,65 @@ namespace r.m[0][2] * (r.m[1][0] * r.m[2][1] - r.m[1][1] * r.m[2][0]); const F32 det = m.getDeterminant().getF32(); - ensure("getDeterminant: " + precise(det) + " vs " + precise(expected), std::fabs(det - expected) <= 1e-5 * std::fabs(expected)); + UNSCOPED_INFO("getDeterminant: " + precise(det) + " vs " + precise(expected)); + CHECK(std::fabs(det - expected) <= 1e-5 * std::fabs(expected)); LLVector4a all; m.getDeterminant(all); for (int i = 0; i < 4; ++i) { - ensure_equals("getDeterminant(LLVector4a) lane " + std::to_string(i), all[i], det); + UNSCOPED_INFO("getDeterminant(LLVector4a) lane " + std::to_string(i)); + REQUIRE(all[i] == det); } - ensure_equals("identity determinant", LLMatrix3a::getIdentity().getDeterminant().getF32(), 1.f); + UNSCOPED_INFO("identity determinant"); + CHECK(LLMatrix3a::getIdentity().getDeterminant().getF32() == 1.f); } // A rotation is one; a scale, a shear and a reflection are not. - template<> template<> - void llmatrix3a_object::test<7>() + TEST_CASE("LLMatrix3a: a rotation is one; a scale or a shear or a reflection is not", "[LLMatrix3a]") { for (const LLQuaternion& q : ROTATIONS) { LLRotation r; r.loadu(q.getMatrix3()); - ensure("a rotation isOkRotation", r.isOkRotation()); - ensure("a rotation is finite", r.isFinite()); + UNSCOPED_INFO("a rotation isOkRotation"); + REQUIRE(r.isOkRotation()); + UNSCOPED_INFO("a rotation is finite"); + REQUIRE(r.isFinite()); } LLRotation scaled; scaled.setRows(LLVector4a(2.f, 0.f, 0.f), LLVector4a(0.f, 2.f, 0.f), LLVector4a(0.f, 0.f, 2.f)); - ensure("a scale is not a rotation", !scaled.isOkRotation()); + UNSCOPED_INFO("a scale is not a rotation"); + CHECK_FALSE(scaled.isOkRotation()); LLRotation sheared; sheared.setRows(LLVector4a(1.f, 0.f, 0.f), LLVector4a(0.5f, 1.f, 0.f), LLVector4a(0.f, 0.f, 1.f)); - ensure("a shear is not a rotation", !sheared.isOkRotation()); + UNSCOPED_INFO("a shear is not a rotation"); + CHECK_FALSE(sheared.isOkRotation()); LLRotation reflected; reflected.setRows(LLVector4a(-1.f, 0.f, 0.f), LLVector4a(0.f, 1.f, 0.f), LLVector4a(0.f, 0.f, 1.f)); - ensure("a reflection is not a rotation", !reflected.isOkRotation()); + UNSCOPED_INFO("a reflection is not a rotation"); + CHECK_FALSE(reflected.isOkRotation()); LLRotation nan; nan.setRows(LLVector4a(std::numeric_limits::quiet_NaN(), 0.f, 0.f), LLVector4a(0.f, 1.f, 0.f), LLVector4a(0.f, 0.f, 1.f)); - ensure("a NaN is not finite", !nan.isFinite()); - ensure("a NaN is not a rotation", !nan.isOkRotation()); + UNSCOPED_INFO("a NaN is not finite"); + CHECK_FALSE(nan.isFinite()); + UNSCOPED_INFO("a NaN is not a rotation"); + CHECK_FALSE(nan.isOkRotation()); LLRotation inf_in_w; inf_in_w.setRows(LLVector4a(1.f, 0.f, 0.f, std::numeric_limits::infinity()), LLVector4a(0.f, 1.f, 0.f), LLVector4a(0.f, 0.f, 1.f)); - ensure("the fourth lane is not looked at", inf_in_w.isFinite()); + UNSCOPED_INFO("the fourth lane is not looked at"); + CHECK(inf_in_w.isFinite()); } // batchTransform is rotate over an array, every count, including the // odd one the loop peels. - template<> template<> - void llmatrix3a_object::test<8>() + TEST_CASE("LLMatrix3a: batchTransform is rotate over an array of every count", "[LLMatrix3a]") { LLRotation r; r.loadu(ROTATIONS[4].getMatrix3()); @@ -429,16 +450,16 @@ namespace { LLVector4a one; r.rotate(src[i], one); - ensure("batchTransform count " + std::to_string(count) + " element " + std::to_string(i), - same_bits(dst[i], one)); + UNSCOPED_INFO("batchTransform count " + std::to_string(count) + " element " + std::to_string(i)); + REQUIRE(same_bits(dst[i], one)); } - ensure("batchTransform stays within count", same_bits(dst[count], LLVector4a(9.f, 9.f, 9.f, 9.f))); + UNSCOPED_INFO("batchTransform stays within count"); + REQUIRE(same_bits(dst[count], LLVector4a(9.f, 9.f, 9.f, 9.f))); } } // setLerp and the tolerance compare. - template<> template<> - void llmatrix3a_object::test<9>() + TEST_CASE("LLMatrix3a: setLerp and the tolerance compare", "[LLMatrix3a]") { const LLMatrix3a a = odd_matrix(); const LLMatrix3a b(LLVector4a(0.5f, -1.f, 2.f, 1.f), @@ -449,8 +470,10 @@ namespace at_zero.setLerp(a, b, 0.f); at_one.setLerp(a, b, 1.f); at_quarter.setLerp(a, b, 0.25f); - ensure("setLerp at 0", same_bits(at_zero, a)); - ensure("setLerp at 1", at_one.isApproximatelyEqual(b, 1e-6f)); + UNSCOPED_INFO("setLerp at 0"); + CHECK(same_bits(at_zero, a)); + UNSCOPED_INFO("setLerp at 1"); + CHECK(at_one.isApproximatelyEqual(b, 1e-6f)); const Rows ra = rows_of(a); const Rows rb = rows_of(b); @@ -468,7 +491,9 @@ namespace LLVector4a nudged = nearby.getRow<1>(); nudged.add(LLVector4a(0.f, 1e-6f, 0.f, 50.f)); nearby.setRows(nearby.getRow<0>(), nudged, nearby.getRow<2>()); - ensure("isApproximatelyEqual within tolerance, w ignored", nearby.isApproximatelyEqual(a)); - ensure("isApproximatelyEqual outside tolerance", !nearby.isApproximatelyEqual(a, 1e-7f)); + UNSCOPED_INFO("isApproximatelyEqual within tolerance, w ignored"); + CHECK(nearby.isApproximatelyEqual(a)); + UNSCOPED_INFO("isApproximatelyEqual outside tolerance"); + CHECK_FALSE(nearby.isApproximatelyEqual(a, 1e-7f)); } } diff --git a/indra/llmath/tests/llmatrix4a_test.cpp b/indra/llmath/tests/llmatrix4a_test.cpp index 32192faead..0a0f54d2c8 100644 --- a/indra/llmath/tests/llmatrix4a_test.cpp +++ b/indra/llmath/tests/llmatrix4a_test.cpp @@ -24,7 +24,9 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include +#include "altestmatchers.h" #include "../llmath.h" #include "../llsimdmath.h" #include "../llvector4a.h" @@ -69,17 +71,8 @@ namespace }; } -namespace tut -{ namespace { - struct llmatrix4a_data - { - }; - typedef test_group llmatrix4a_test; - typedef llmatrix4a_test::object llmatrix4a_object; - tut::llmatrix4a_test llmatrix4a_testcase("LLMatrix4a"); - // A four by four in double, the rows of a row-vector matrix: the // reference the single-precision class is held to, with every product, // inverse and transform written out longhand. @@ -250,7 +243,8 @@ namespace const F32 scale = llmax(1.f, fabsf(a), fabsf(e)); std::ostringstream msg; msg << what << " at [" << i << "][" << j << "]: " << a << " vs " << e; - ensure_approximately_equals_range(msg.str().c_str(), a, e, tolerance * scale); + UNSCOPED_INFO(msg.str().c_str()); + REQUIRE_THAT(a, Catch::Matchers::WithinAbs(e, tolerance * scale)); } } } @@ -268,7 +262,8 @@ namespace const F32 scale = llmax(1.f, fabsf(actual[i]), fabsf(e)); std::ostringstream msg; msg << what << " at lane " << i << ": " << actual[i] << " vs " << e; - ensure_approximately_equals_range(msg.str().c_str(), actual[i], e, tolerance * scale); + UNSCOPED_INFO(msg.str().c_str()); + REQUIRE_THAT(actual[i], Catch::Matchers::WithinAbs(e, tolerance * scale)); } } @@ -295,7 +290,8 @@ namespace } std::ostringstream msg; msg << what << " at [" << row << "][" << col << "]"; - ensure_approximately_equals_range(msg.str().c_str(), product.mMatrix[row][col], row == col ? 1.f : 0.f, tolerance * (1.f + terms)); + UNSCOPED_INFO(msg.str().c_str()); + REQUIRE_THAT(product.mMatrix[row][col], Catch::Matchers::WithinAbs(row == col ? 1.f : 0.f, tolerance * (1.f + terms))); } } } @@ -309,10 +305,8 @@ namespace { return LLVector4a(v.mV[0], v.mV[1], v.mV[2], 0.f); } -} - template<> template<> - void llmatrix4a_object::test<1>() + TEST_CASE("LLMatrix4a: initAll agrees with LLMatrix4::initAll", "[LLMatrix4a]") { // initAll must agree with LLMatrix4::initAll, since the joint // transform path swapped one for the other. Not bit for bit: /fp:fast @@ -340,21 +334,21 @@ namespace std::ostringstream msg; msg << "initAll mismatch at [" << row << "][" << col << "] for case " << cases; - ensure_approximately_equals(msg.str().c_str(), - actual4.mMatrix[row][col], - expected.mMatrix[row][col], - 20); + UNSCOPED_INFO(msg.str().c_str()); + REQUIRE_THAT(actual4.mMatrix[row][col], + ll_test::WithinBits(expected.mMatrix[row][col], + 20)); } } ++cases; } } } - ensure("every combination was covered", cases == 6 * 5 * 3); + UNSCOPED_INFO("every combination was covered"); + CHECK(cases == 6 * 5 * 3); } - template<> template<> - void llmatrix4a_object::test<2>() + TEST_CASE("LLMatrix4a: initAll clears the fourth column of the upper rows", "[LLMatrix4a]") { // The fourth column of the upper rows must be zero, which is what the // scalar version leaves in place rather than writing. Start from a @@ -369,30 +363,63 @@ namespace m.initAll(LLVector3(1.f, 1.f, 1.f), LLQuaternion(), LLVector3(1.f, 2.f, 3.f)); const LLMatrix4 out = m.toMatrix4(); - ensure_equals("row 0 pad cleared", out.mMatrix[0][3], 0.f); - ensure_equals("row 1 pad cleared", out.mMatrix[1][3], 0.f); - ensure_equals("row 2 pad cleared", out.mMatrix[2][3], 0.f); - ensure_equals("row 3 w is one", out.mMatrix[3][3], 1.f); + UNSCOPED_INFO("row 0 pad cleared"); + CHECK(out.mMatrix[0][3] == 0.f); + UNSCOPED_INFO("row 1 pad cleared"); + CHECK(out.mMatrix[1][3] == 0.f); + UNSCOPED_INFO("row 2 pad cleared"); + CHECK(out.mMatrix[2][3] == 0.f); + UNSCOPED_INFO("row 3 w is one"); + CHECK(out.mMatrix[3][3] == 1.f); } - template<> template<> - void llmatrix4a_object::test<3>() + TEST_CASE("LLMatrix4a: an identity rotation with unit scale is a translation matrix", "[LLMatrix4a]") { // An identity rotation with unit scale is the translation matrix. LLMatrix4a m; m.initAll(LLVector3(1.f, 1.f, 1.f), LLQuaternion(), LLVector3(4.f, 5.f, 6.f)); const LLMatrix4 out = m.toMatrix4(); - ensure_equals("m00", out.mMatrix[0][0], 1.f); - ensure_equals("m11", out.mMatrix[1][1], 1.f); - ensure_equals("m22", out.mMatrix[2][2], 1.f); - ensure_equals("tx", out.mMatrix[3][0], 4.f); - ensure_equals("ty", out.mMatrix[3][1], 5.f); - ensure_equals("tz", out.mMatrix[3][2], 6.f); + UNSCOPED_INFO("m00"); + CHECK(out.mMatrix[0][0] == 1.f); + UNSCOPED_INFO("m11"); + CHECK(out.mMatrix[1][1] == 1.f); + UNSCOPED_INFO("m22"); + CHECK(out.mMatrix[2][2] == 1.f); + UNSCOPED_INFO("tx"); + CHECK(out.mMatrix[3][0] == 4.f); + UNSCOPED_INFO("ty"); + CHECK(out.mMatrix[3][1] == 5.f); + UNSCOPED_INFO("tz"); + CHECK(out.mMatrix[3][2] == 6.f); } - template<> template<> - void llmatrix4a_object::test<5>() + + + + + + + TEST_CASE("LLMatrix4a: each basis row's length is its axis' scale", "[LLMatrix4a]") + { + // Scale lands on the corresponding basis row, not the diagonal, so a + // rotated matrix keeps each row's length equal to that axis' scale. + const LLVector3 scale(2.f, 3.f, 4.f); + LLMatrix4a m; + m.initAll(scale, LLQuaternion(0.7f, LLVector3(0.f, 0.f, 1.f)), LLVector3()); + + const LLMatrix4 out = m.toMatrix4(); + for (S32 row = 0; row < 3; ++row) + { + const F32 len = LLVector3(out.mMatrix[row]).length(); + std::ostringstream msg; + msg << "row " << row << " length matches its scale"; + UNSCOPED_INFO(msg.str().c_str()); + REQUIRE_THAT(len, Catch::Matchers::WithinAbs(scale.mV[row], 1e-4f)); + } + } + + TEST_CASE("LLMatrix4a: set(LLMatrix4) is the inverse of toMatrix4", "[LLMatrix4a]") { // set(LLMatrix4) is the inverse of toMatrix4(). LLMatrix4 src; @@ -407,13 +434,13 @@ namespace { for (S32 col = 0; col < 4; ++col) { - ensure_equals("round trip", back.mMatrix[row][col], src.mMatrix[row][col]); + UNSCOPED_INFO("round trip"); + REQUIRE(back.mMatrix[row][col] == src.mMatrix[row][col]); } } } - template<> template<> - void llmatrix4a_object::test<6>() + TEST_CASE("LLMatrix4a: set(LLMatrix3) fills the basis and leaves an identity fourth row", "[LLMatrix4a]") { // set(LLMatrix3) fills the basis and leaves an identity fourth row. LLMatrix3 src; @@ -424,15 +451,19 @@ namespace LLMatrix4a m; m.set(src); const LLMatrix4 out = m.toMatrix4(); - ensure_equals("m00", out.mMatrix[0][0], 1.f); - ensure_equals("m12", out.mMatrix[1][2], 6.f); - ensure_equals("m22", out.mMatrix[2][2], 9.f); - ensure_equals("row 3 is the origin", out.mMatrix[3][0], 0.f); - ensure_equals("row 3 w is one", out.mMatrix[3][3], 1.f); + UNSCOPED_INFO("m00"); + CHECK(out.mMatrix[0][0] == 1.f); + UNSCOPED_INFO("m12"); + CHECK(out.mMatrix[1][2] == 6.f); + UNSCOPED_INFO("m22"); + CHECK(out.mMatrix[2][2] == 9.f); + UNSCOPED_INFO("row 3 is the origin"); + CHECK(out.mMatrix[3][0] == 0.f); + UNSCOPED_INFO("row 3 w is one"); + CHECK(out.mMatrix[3][3] == 1.f); } - template<> template<> - void llmatrix4a_object::test<7>() + TEST_CASE("LLMatrix4a: setMul by the identity and with aliasing matches setMulNoAlias", "[LLMatrix4a]") { LLMatrix4a a; a.initAll(LLVector3(2.f, 1.f, 3.f), @@ -442,9 +473,11 @@ namespace // Multiplying by the identity in either position is a no-op. LLMatrix4a r; r.setMul(a, LLMatrix4a::identity()); - ensure("a * identity == a", r == a); + UNSCOPED_INFO("a * identity == a"); + CHECK(r == a); r.setMul(LLMatrix4a::identity(), a); - ensure("identity * a == a", r == a); + UNSCOPED_INFO("identity * a == a"); + CHECK(r == a); LLMatrix4a b; b.initAll(LLVector3(1.f, 1.f, 1.f), @@ -455,21 +488,23 @@ namespace LLMatrix4a safe, fast; safe.setMul(a, b); fast.setMulNoAlias(a, b); - ensure("setMulNoAlias matches setMul", safe == fast); + UNSCOPED_INFO("setMulNoAlias matches setMul"); + CHECK(safe == fast); // setMul tolerates its result aliasing either input, which is why it // exists alongside setMulNoAlias. LLMatrix4a lhs = a; lhs.setMul(lhs, b); - ensure("result may alias the left operand", lhs == safe); + UNSCOPED_INFO("result may alias the left operand"); + CHECK(lhs == safe); LLMatrix4a rhs = b; rhs.setMul(a, rhs); - ensure("result may alias the right operand", rhs == safe); + UNSCOPED_INFO("result may alias the right operand"); + CHECK(rhs == safe); } - template<> template<> - void llmatrix4a_object::test<8>() + TEST_CASE("LLMatrix4a: affineTransform applies the translation and rotate does not", "[LLMatrix4a]") { // affineTransform applies the translation, rotate() does not. LLMatrix4a m; @@ -480,19 +515,24 @@ namespace LLVector4a moved; m.affineTransform(point, moved); - ensure_equals("affineTransform adds x", moved[0], 11.f); - ensure_equals("affineTransform adds y", moved[1], 22.f); - ensure_equals("affineTransform adds z", moved[2], 33.f); + UNSCOPED_INFO("affineTransform adds x"); + CHECK(moved[0] == 11.f); + UNSCOPED_INFO("affineTransform adds y"); + CHECK(moved[1] == 22.f); + UNSCOPED_INFO("affineTransform adds z"); + CHECK(moved[2] == 33.f); LLVector4a turned; m.rotate(point, turned); - ensure_equals("rotate leaves x", turned[0], 1.f); - ensure_equals("rotate leaves y", turned[1], 2.f); - ensure_equals("rotate leaves z", turned[2], 3.f); + UNSCOPED_INFO("rotate leaves x"); + CHECK(turned[0] == 1.f); + UNSCOPED_INFO("rotate leaves y"); + CHECK(turned[1] == 2.f); + UNSCOPED_INFO("rotate leaves z"); + CHECK(turned[2] == 3.f); } - template<> template<> - void llmatrix4a_object::test<9>() + TEST_CASE("LLMatrix4a: setTranslation and setRow change only what they name", "[LLMatrix4a]") { LLMatrix4a m; m.initAll(LLVector3(2.f, 2.f, 2.f), @@ -502,21 +542,26 @@ namespace const LLVector4a basis = m.getRow<0>(); m.setTranslation(LLVector3(5.f, 6.f, 7.f)); - ensure("setTranslation leaves the basis alone", m.getRow<0>() == basis); + UNSCOPED_INFO("setTranslation leaves the basis alone"); + CHECK(m.getRow<0>() == basis); const LLMatrix4 out = m.toMatrix4(); - ensure_equals("tx", out.mMatrix[3][0], 5.f); - ensure_equals("ty", out.mMatrix[3][1], 6.f); - ensure_equals("tz", out.mMatrix[3][2], 7.f); - ensure_equals("stays affine", out.mMatrix[3][3], 1.f); + UNSCOPED_INFO("tx"); + CHECK(out.mMatrix[3][0] == 5.f); + UNSCOPED_INFO("ty"); + CHECK(out.mMatrix[3][1] == 6.f); + UNSCOPED_INFO("tz"); + CHECK(out.mMatrix[3][2] == 7.f); + UNSCOPED_INFO("stays affine"); + CHECK(out.mMatrix[3][3] == 1.f); LLVector4a replacement; replacement.set(1.f, 2.f, 3.f, 4.f); m.setRow<2>(replacement); - ensure("setRow round trips", m.getRow<2>() == replacement); + UNSCOPED_INFO("setRow round trips"); + CHECK(m.getRow<2>() == replacement); } - template<> template<> - void llmatrix4a_object::test<10>() + TEST_CASE("LLMatrix4a: matMulBoundBox spans the same box as the eight corners", "[LLMatrix4a]") { // matMulBoundBox used to transform all eight corners and take their // min and max. It now grows the transformed centre by the half @@ -576,34 +621,14 @@ namespace // corner walk and the centre form round differently, and the // 1000x scale row amplifies whichever rounds first. const F32 magnitude = llmax(1.f, fabsf(expected[side][axis])); - ensure_approximately_equals_range(msg.str().c_str(), - out[side][axis], expected[side][axis], - magnitude * 1e-4f); + UNSCOPED_INFO(msg.str().c_str()); + REQUIRE_THAT(out[side][axis], Catch::Matchers::WithinAbs(expected[side][axis], + magnitude * 1e-4f)); } } } - template<> template<> - void llmatrix4a_object::test<4>() - { - // Scale lands on the corresponding basis row, not the diagonal, so a - // rotated matrix keeps each row's length equal to that axis' scale. - const LLVector3 scale(2.f, 3.f, 4.f); - LLMatrix4a m; - m.initAll(scale, LLQuaternion(0.7f, LLVector3(0.f, 0.f, 1.f)), LLVector3()); - - const LLMatrix4 out = m.toMatrix4(); - for (S32 row = 0; row < 3; ++row) - { - const F32 len = LLVector3(out.mMatrix[row]).length(); - std::ostringstream msg; - msg << "row " << row << " length matches its scale"; - ensure_approximately_equals_range(msg.str().c_str(), len, scale.mV[row], 1e-4f); - } - } - - template<> template<> - void llmatrix4a_object::test<11>() + TEST_CASE("LLMatrix4a: add and scale and setLerp element by element against LLMatrix4", "[LLMatrix4a]") { // The whole-matrix arithmetic, element by element against the // scalar matrix: add, scale, lerp at its ends and in between. @@ -637,29 +662,36 @@ namespace const F32 bv = b4.mMatrix[row][col]; std::ostringstream msg; msg << " at [" << row << "][" << col << "]"; - ensure_equals("add" + msg.str(), sum4.mMatrix[row][col], av + bv); - ensure_equals("setMul(F32)" + msg.str(), scaled4.mMatrix[row][col], av * -1.5f); - ensure_equals("setLerp at 0" + msg.str(), zero4.mMatrix[row][col], av); - ensure_approximately_equals_range(("setLerp at 1" + msg.str()).c_str(), one4.mMatrix[row][col], bv, 1e-6f * llmax(1.f, fabsf(bv))); + UNSCOPED_INFO("add" + msg.str()); + REQUIRE(sum4.mMatrix[row][col] == av + bv); + UNSCOPED_INFO("setMul(F32)" + msg.str()); + REQUIRE(scaled4.mMatrix[row][col] == av * -1.5f); + UNSCOPED_INFO("setLerp at 0" + msg.str()); + REQUIRE(zero4.mMatrix[row][col] == av); + UNSCOPED_INFO(("setLerp at 1" + msg.str()).c_str()); + REQUIRE_THAT(one4.mMatrix[row][col], Catch::Matchers::WithinAbs(bv, 1e-6f * llmax(1.f, fabsf(bv)))); const F32 expected = (F32)((double)av + ((double)bv - av) * 0.25); - ensure_approximately_equals_range(("setLerp at a quarter" + msg.str()).c_str(), quarter4.mMatrix[row][col], expected, 1e-6f * llmax(1.f, fabsf(expected))); + UNSCOPED_INFO(("setLerp at a quarter" + msg.str()).c_str()); + REQUIRE_THAT(quarter4.mMatrix[row][col], Catch::Matchers::WithinAbs(expected, 1e-6f * llmax(1.f, fabsf(expected)))); } } // the identity, both ways - ensure("identity equals itself", LLMatrix4a::identity() == LLMatrix4a::identity()); + UNSCOPED_INFO("identity equals itself"); + CHECK(LLMatrix4a::identity() == LLMatrix4a::identity()); LLMatrix4a fresh; fresh.setIdentity(); - ensure("setIdentity is the identity", fresh == LLMatrix4a::identity()); - ensure("a translation is not the identity", a != LLMatrix4a::identity()); + UNSCOPED_INFO("setIdentity is the identity"); + CHECK(fresh == LLMatrix4a::identity()); + UNSCOPED_INFO("a translation is not the identity"); + CHECK(a != LLMatrix4a::identity()); } // THE CONVENTIONS: the matrix takes a row vector on the left, so // affineTransform is the scalar matrix's v * M, a product applies its // first factor first, and a builder set before a matrix acts on the // point before the matrix does. - template<> template<> - void llmatrix4a_object::test<12>() + TEST_CASE("LLMatrix4a: row vectors on the left and a product applies its first factor first", "[LLMatrix4a]") { const F32 tolerance = 1e-5f; const LLVector3 p3(0.7f, -2.5f, 11.f); @@ -695,7 +727,8 @@ namespace } } } - ensure("every combination was covered", cases == 6 * 5 * 3); + UNSCOPED_INFO("every combination was covered"); + CHECK(cases == 6 * 5 * 3); // setMul(a, b) applies a first, then b LLMatrix4a a, b; @@ -745,8 +778,7 @@ namespace // transform4, transpose, determinant, the inverses and the normal // matrix, on affine and projective matrices, against the double // reference. - template<> template<> - void llmatrix4a_object::test<13>() + TEST_CASE("LLMatrix4a: transform4/transpose/determinant/inverses/normal matrix vs double", "[LLMatrix4a]") { const F32 tolerance = 1e-5f; @@ -787,14 +819,17 @@ namespace { for (S32 j = 0; j < 4; ++j) { - ensure_equals("setTranspose swaps the indices", t.mMatrix[i][j], m.mMatrix[j][i]); + UNSCOPED_INFO("setTranspose swaps the indices"); + REQUIRE(t.mMatrix[i][j] == m.mMatrix[j][i]); } } LLMatrix4a tt = m; tt.transpose(); - ensure("transpose in place", same_bytes(tt, t)); + UNSCOPED_INFO("transpose in place"); + REQUIRE(same_bytes(tt, t)); tt.transpose(); - ensure("transposed twice is itself", tt == m); + UNSCOPED_INFO("transposed twice is itself"); + REQUIRE(tt == m); // the determinant, to within the size of the terms that cancel in // it, and the general inverse @@ -803,14 +838,18 @@ namespace { terms *= fabsf(m.mMatrix[row][0]) + fabsf(m.mMatrix[row][1]) + fabsf(m.mMatrix[row][2]) + fabsf(m.mMatrix[row][3]); } - ensure_approximately_equals_range("determinant", m.determinant(), F32(r.determinant()), 1e-6f * (1.f + terms)); + UNSCOPED_INFO("determinant"); + REQUIRE_THAT(m.determinant(), Catch::Matchers::WithinAbs(F32(r.determinant()), 1e-6f * (1.f + terms))); LLMatrix4a inv; - ensure("setInverse succeeds", inv.setInverse(m)); + UNSCOPED_INFO("setInverse succeeds"); + REQUIRE(inv.setInverse(m)); ensure_matrix_close("setInverse matches the reference inverse", inv, r.inverse(), tolerance); LLMatrix4a in_place = m; - ensure("invert succeeds", in_place.invert()); - ensure("invert in place", in_place == inv); + UNSCOPED_INFO("invert succeeds"); + REQUIRE(in_place.invert()); + UNSCOPED_INFO("invert in place"); + REQUIRE(in_place == inv); ensure_product_is_identity("M * inverse(M)", m, inv, 1e-6f); ensure_product_is_identity("inverse(M) * M", inv, m, 1e-6f); } @@ -824,7 +863,8 @@ namespace LLMatrix4a far_away; far_away.initAll(LLVector3(1.f, 1.f, 1.f), ROTATIONS[5], LLVector3(-256.f, 4096.f, 0.125f)); LLMatrix4a far_inv; - ensure("setInverse of the far matrix succeeds", far_inv.setInverse(far_away)); + UNSCOPED_INFO("setInverse of the far matrix succeeds"); + CHECK(far_inv.setInverse(far_away)); ensure_product_is_identity("far M * inverse(M)", far_away, far_inv, 1e-6f); ensure_product_is_identity("far inverse(M) * M", far_inv, far_away, 1e-6f); @@ -836,11 +876,13 @@ namespace const Ref inverse = r.inverse(); LLMatrix4a inv; - ensure("setAffineInverse succeeds", inv.setAffineInverse(m)); + UNSCOPED_INFO("setAffineInverse succeeds"); + REQUIRE(inv.setAffineInverse(m)); ensure_matrix_close("setAffineInverse is the inverse of an affine matrix", inv, inverse, tolerance); LLMatrix4a n; - ensure("setNormalMatrix succeeds", n.setNormalMatrix(m)); + UNSCOPED_INFO("setNormalMatrix succeeds"); + REQUIRE(n.setNormalMatrix(m)); const Ref inverse_transpose = inverse.transposed(); for (S32 row = 0; row < 3; ++row) { @@ -849,11 +891,14 @@ namespace std::ostringstream msg; msg << "normal matrix at [" << row << "][" << col << "]"; const F32 e = F32(inverse_transpose.m[row][col]); - ensure_approximately_equals_range(msg.str().c_str(), n.mMatrix[row][col], e, tolerance * llmax(1.f, fabsf(e))); + UNSCOPED_INFO(msg.str().c_str()); + REQUIRE_THAT(n.mMatrix[row][col], Catch::Matchers::WithinAbs(e, tolerance * llmax(1.f, fabsf(e)))); } - ensure_equals("normal matrix row pad", n.mMatrix[row][3], 0.f); + UNSCOPED_INFO("normal matrix row pad"); + REQUIRE(n.mMatrix[row][3] == 0.f); } - ensure("normal matrix has no translation", n.mMatrix[3] == LLVector4a(0.f, 0.f, 0.f, 1.f)); + UNSCOPED_INFO("normal matrix has no translation"); + REQUIRE(n.mMatrix[3] == LLVector4a(0.f, 0.f, 0.f, 1.f)); // a normal stays perpendicular to a transformed surface LLVector4a tangent(1.f, 2.f, -0.5f, 0.f), normal; @@ -861,7 +906,8 @@ namespace LLVector4a tangent_out, normal_out; m.rotate(tangent, tangent_out); n.rotate(normal, normal_out); - ensure_approximately_equals_range("perpendicular after transform", tangent_out.dot3(normal_out).getF32(), 0.f, 1e-4f * tangent_out.getLength3().getF32() * normal_out.getLength3().getF32()); + UNSCOPED_INFO("perpendicular after transform"); + REQUIRE_THAT(tangent_out.dot3(normal_out).getF32(), Catch::Matchers::WithinAbs(0.f, 1e-4f * tangent_out.getLength3().getF32() * normal_out.getLength3().getF32())); } // singular: refused, untouched @@ -869,17 +915,22 @@ namespace singular.initAll(SCALES[4], ROTATIONS[1], POSITIONS[1]); LLMatrix4a untouched; untouched.setIdentity(); - ensure("setInverse of a singular matrix is refused", !untouched.setInverse(singular)); - ensure("and leaves the target alone", untouched == LLMatrix4a::identity()); - ensure("setAffineInverse of a singular matrix is refused", !untouched.setAffineInverse(singular)); - ensure("setNormalMatrix of a singular matrix is refused", !untouched.setNormalMatrix(singular)); - ensure("still untouched", untouched == LLMatrix4a::identity()); - ensure_equals("singular determinant", singular.determinant(), 0.f); + UNSCOPED_INFO("setInverse of a singular matrix is refused"); + CHECK_FALSE(untouched.setInverse(singular)); + UNSCOPED_INFO("and leaves the target alone"); + CHECK(untouched == LLMatrix4a::identity()); + UNSCOPED_INFO("setAffineInverse of a singular matrix is refused"); + CHECK_FALSE(untouched.setAffineInverse(singular)); + UNSCOPED_INFO("setNormalMatrix of a singular matrix is refused"); + CHECK_FALSE(untouched.setNormalMatrix(singular)); + UNSCOPED_INFO("still untouched"); + CHECK(untouched == LLMatrix4a::identity()); + UNSCOPED_INFO("singular determinant"); + CHECK(singular.determinant() == 0.f); } // The builders against the formulas, written out in double. - template<> template<> - void llmatrix4a_object::test<14>() + TEST_CASE("LLMatrix4a: builders against the formulas written out in double", "[LLMatrix4a]") { const F32 tolerance = 2e-6f; @@ -887,15 +938,19 @@ namespace LLMatrix4a expected; expected.setIdentity(); expected.mMatrix[3].set(4.f, -5.f, 6.f, 1.f); - ensure("translation", same_bytes(LLMatrix4a::translation(4.f, -5.f, 6.f), expected)); - ensure("translation from a vector ignores w", same_bytes(LLMatrix4a::translation(LLVector4a(4.f, -5.f, 6.f, 9.f)), expected)); + UNSCOPED_INFO("translation"); + CHECK(same_bytes(LLMatrix4a::translation(4.f, -5.f, 6.f), expected)); + UNSCOPED_INFO("translation from a vector ignores w"); + CHECK(same_bytes(LLMatrix4a::translation(LLVector4a(4.f, -5.f, 6.f, 9.f)), expected)); expected.setIdentity(); expected.mMatrix[0].set(2.f, 0.f, 0.f, 0.f); expected.mMatrix[1].set(0.f, 3.f, 0.f, 0.f); expected.mMatrix[2].set(0.f, 0.f, 0.5f, 0.f); - ensure("scaling", same_bytes(LLMatrix4a::scaling(2.f, 3.f, 0.5f), expected)); - ensure("scaling from a vector", same_bytes(LLMatrix4a::scaling(LLVector4a(2.f, 3.f, 0.5f, 9.f)), expected)); + UNSCOPED_INFO("scaling"); + CHECK(same_bytes(LLMatrix4a::scaling(2.f, 3.f, 0.5f), expected)); + UNSCOPED_INFO("scaling from a vector"); + CHECK(same_bytes(LLMatrix4a::scaling(LLVector4a(2.f, 3.f, 0.5f, 9.f)), expected)); } // a rotation about an axis: Rodrigues in double, and the tree's own @@ -962,7 +1017,8 @@ namespace r.m[3][0] = (1280.0 - 2.0 * (300.0 - 10.0)) / 5.0; r.m[3][1] = (720.0 - 2.0 * (400.0 - 20.0)) / 7.0; ensure_matrix_close("pick", LLMatrix4a::pick(300.f, 400.f, 5.f, 7.f, viewport), r, tolerance); - ensure("pick of nothing is the identity", LLMatrix4a::pick(300.f, 400.f, 0.f, 7.f, viewport) == LLMatrix4a::identity()); + UNSCOPED_INFO("pick of nothing is the identity"); + CHECK(LLMatrix4a::pick(300.f, 400.f, 0.f, 7.f, viewport) == LLMatrix4a::identity()); } // the view: the camera's basis as columns, the eye's position in it @@ -1012,8 +1068,7 @@ namespace // Quaternions from axes and matrices, the rotation from a quaternion, // and a scene node's TRS taken apart and put back. - template<> template<> - void llmatrix4a_object::test<15>() + TEST_CASE("LLMatrix4a: quaternions from axes and matrices and TRS taken apart and put back", "[LLMatrix4a]") { const F32 tolerance = 2e-6f; @@ -1024,11 +1079,13 @@ namespace LLQuaternion2 q; q.setAxisAngle(as_direction(axis3), angle); const LLQuaternion2 expected(LLQuaternion(angle, axis3)); - ensure("setAxisAngle is LLQuaternion(angle, axis)", q.equals(expected, 1e-6f)); + UNSCOPED_INFO("setAxisAngle is LLQuaternion(angle, axis)"); + REQUIRE(q.equals(expected, 1e-6f)); } LLQuaternion2 none; none.setAxisAngle(LLVector4a(0.f, 0.f, 0.f, 0.f), 1.f); - ensure("no axis is the identity", none.equals(LLQuaternion2::identity(), 1e-7f)); + UNSCOPED_INFO("no axis is the identity"); + CHECK(none.equals(LLQuaternion2::identity(), 1e-7f)); // setFromMatrix reads back what setRotation wrote, and agrees with the // scalar matrix's reading, up to the sign every rotation's two @@ -1044,12 +1101,14 @@ namespace LLVector4a negated; negated.setNeg(back.getVector4a()); const LLQuaternion2 flipped(negated); - ensure("setFromMatrix inverts setRotation", back.equals(LLQuaternion2(q), 1e-5f) || flipped.equals(LLQuaternion2(q), 1e-5f)); + UNSCOPED_INFO("setFromMatrix inverts setRotation"); + REQUIRE((back.equals(LLQuaternion2(q), 1e-5f) || flipped.equals(LLQuaternion2(q), 1e-5f))); LLMatrix3 m3; m3.setRows(LLVector3(m.mMatrix[0].getF32ptr()), LLVector3(m.mMatrix[1].getF32ptr()), LLVector3(m.mMatrix[2].getF32ptr())); const LLQuaternion2 scalar(m3.quaternion()); - ensure("and reads as LLMatrix3::quaternion does", back.equals(scalar, 1e-5f)); + UNSCOPED_INFO("and reads as LLMatrix3::quaternion does"); + REQUIRE(back.equals(scalar, 1e-5f)); } // TRS both ways @@ -1071,21 +1130,25 @@ namespace LLVector4a scale_out, translation_out; LLQuaternion2 rotation_out; - ensure("decompose succeeds", m.decompose(scale_out, rotation_out, translation_out)); + UNSCOPED_INFO("decompose succeeds"); + REQUIRE(m.decompose(scale_out, rotation_out, translation_out)); LLMatrix4a rebuilt; rebuilt.setTRS(translation_out, rotation_out, scale_out); ensure_matrix_close("decompose then setTRS is the matrix", rebuilt, m, 1e-4f); - ensure("translation comes back", translation_out.equals3(tr, 1e-5f * llmax(1.f, fabsf(tr[1])))); + UNSCOPED_INFO("translation comes back"); + REQUIRE(translation_out.equals3(tr, 1e-5f * llmax(1.f, fabsf(tr[1])))); // a right-handed scale comes back as itself; a left-handed // one as its negation, the rotation absorbing the flip const bool right_handed = sc[0] * sc[1] * sc[2] > 0.f; if (right_handed && sc[0] > 0.f && sc[1] > 0.f && sc[2] > 0.f) { - ensure("scale comes back", scale_out.equals3(sc, 1e-4f * llmax(1.f, fabsf(sc[1])))); + UNSCOPED_INFO("scale comes back"); + REQUIRE(scale_out.equals3(sc, 1e-4f * llmax(1.f, fabsf(sc[1])))); LLVector4a negated; negated.setNeg(rotation_out.getVector4a()); - ensure("rotation comes back, up to sign", rotation_out.equals(rotation, 1e-4f) || LLQuaternion2(negated).equals(rotation, 1e-4f)); + UNSCOPED_INFO("rotation comes back, up to sign"); + REQUIRE((rotation_out.equals(rotation, 1e-4f) || LLQuaternion2(negated).equals(rotation, 1e-4f))); } } } @@ -1094,18 +1157,19 @@ namespace // a projective or singular matrix is refused LLVector4a s, t; LLQuaternion2 r; - ensure("a projection does not decompose", !LLMatrix4a::perspective(1.f, 1.f, 1.f, 10.f).decompose(s, r, t)); + UNSCOPED_INFO("a projection does not decompose"); + CHECK_FALSE(LLMatrix4a::perspective(1.f, 1.f, 1.f, 10.f).decompose(s, r, t)); LLMatrix4a flat; flat.setTRS(translations[1], LLQuaternion2(ROTATIONS[2]), LLVector4a(1.f, 0.f, 1.f, 0.f)); - ensure("a flattened matrix does not decompose", !flat.decompose(s, r, t)); + UNSCOPED_INFO("a flattened matrix does not decompose"); + CHECK_FALSE(flat.decompose(s, r, t)); } // Through a projection to the window and back, both depth conventions, // against the formulas in double. The way back carries the window's // resolution scaled by the depth, so it is held to the scene's size // rather than the point's. - template<> template<> - void llmatrix4a_object::test<16>() + TEST_CASE("LLMatrix4a: project and unproject in both depth conventions against double", "[LLMatrix4a]") { const F32 tolerance = 1e-4f; const F32 back_tolerance = 5e-4f; @@ -1145,7 +1209,8 @@ namespace // the same through an inverse taken once LLMatrix4a inverse; inverse.setMul(modelview, projection); - ensure("the camera inverts", inverse.invert()); + UNSCOPED_INFO("the camera inverts"); + REQUIRE(inverse.invert()); ensure_vector_close("unproject through the inverse", alprojection::unproject(win, inverse, viewport), back, 1e-6f); ensure_vector_close("unproject_zo through the inverse", alprojection::unproject_zo(win_zo, inverse, viewport), back_zo, 1e-6f); } diff --git a/indra/llmath/tests/llmodularmath_test.cpp b/indra/llmath/tests/llmodularmath_test.cpp index c9d23c07e0..4b13077270 100644 --- a/indra/llmath/tests/llmodularmath_test.cpp +++ b/indra/llmath/tests/llmodularmath_test.cpp @@ -30,50 +30,40 @@ #include "../llmodularmath.h" -#include "../test/lltut.h" +#include -namespace tut -{ namespace { - struct modularmath_data - { - }; - typedef test_group modularmath_test; - typedef modularmath_test::object modularmath_object; - tut::modularmath_test modularmath_testcase("LLModularMath"); -} - - template<> template<> - void modularmath_object::test<1>() + TEST_CASE("LLModularMath: subtract wraps when lhs is less than rhs", "[LLModularMath]") { // lhs < rhs const U32 lhs = 0x000001; const U32 rhs = 0xFFFFFF; const U32 width = 24; U32 result = LLModularMath::subtract(lhs, rhs); - ensure_equals("diff(0x000001, 0xFFFFFF, 24)", result, 2); + UNSCOPED_INFO("diff(0x000001, 0xFFFFFF, 24)"); + CHECK(result == 2); } - template<> template<> - void modularmath_object::test<2>() + TEST_CASE("LLModularMath: subtract with lhs greater than rhs", "[LLModularMath]") { // lhs > rhs const U32 lhs = 0x000002; const U32 rhs = 0x000001; const U32 width = 24; U32 result = LLModularMath::subtract(lhs, rhs); - ensure_equals("diff(0x000002, 0x000001, 24)", result, 1); + UNSCOPED_INFO("diff(0x000002, 0x000001, 24)"); + CHECK(result == 1); } - template<> template<> - void modularmath_object::test<3>() + TEST_CASE("LLModularMath: subtract of equal values is zero", "[LLModularMath]") { // lhs == rhs const U32 lhs = 0xABCDEF; const U32 rhs = 0xABCDEF; const U32 width = 24; U32 result = LLModularMath::subtract(lhs, rhs); - ensure_equals("diff(0xABCDEF, 0xABCDEF, 24)", result, 0); + UNSCOPED_INFO("diff(0xABCDEF, 0xABCDEF, 24)"); + CHECK(result == 0); } } diff --git a/indra/llmath/tests/llquaternion2_test.cpp b/indra/llmath/tests/llquaternion2_test.cpp index db80e82076..07d7108cf5 100644 --- a/indra/llmath/tests/llquaternion2_test.cpp +++ b/indra/llmath/tests/llquaternion2_test.cpp @@ -24,7 +24,8 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include #include "../llmath.h" #include "../llsimdmath.h" #include "../llvector4a.h" @@ -75,7 +76,8 @@ namespace actual.store(got); for (S32 i = 0; i < 4; ++i) { - tut::ensure_approximately_equals_range((msg + " component").c_str(), got.mQ[i], expected.mQ[i], TOLERANCE); + UNSCOPED_INFO((msg + " component").c_str()); + REQUIRE_THAT(got.mQ[i], Catch::Matchers::WithinAbs(expected.mQ[i], TOLERANCE)); } } @@ -86,25 +88,19 @@ namespace actual.store(got); const F32 d = got.mQ[0] * expected.mQ[0] + got.mQ[1] * expected.mQ[1] + got.mQ[2] * expected.mQ[2] + got.mQ[3] * expected.mQ[3]; - tut::ensure_approximately_equals_range(msg.c_str(), fabsf(d), 1.f, TOLERANCE); + UNSCOPED_INFO(msg.c_str()); + REQUIRE_THAT(fabsf(d), Catch::Matchers::WithinAbs(1.f, TOLERANCE)); } } -namespace tut -{ namespace { struct llquaternion2_data { std::vector mRotations = rotations(); }; - typedef test_group llquaternion2_test; - typedef llquaternion2_test::object llquaternion2_object; - tut::llquaternion2_test llquaternion2_testcase("LLQuaternion2"); -} - template<> template<> - void llquaternion2_object::test<1>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: LLQuaternion round trip and the identity", "[LLQuaternion2]") { // Round trip through the scalar type, and the identity. for (const LLQuaternion& q : mRotations) @@ -117,8 +113,7 @@ namespace ensure_quat_equals("identity", LLQuaternion2::identity(), ident); } - template<> template<> - void llquaternion2_object::test<2>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setMul matches LLQuaternion operator* for every pair", "[LLQuaternion2]") { // setMul against LLQuaternion's operator*, for every ordered pair. for (const LLQuaternion& a : mRotations) @@ -132,8 +127,7 @@ namespace } } - template<> template<> - void llquaternion2_object::test<3>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: rotating by a product rotates by a and then by b", "[LLQuaternion2]") { // The product is what rotating by a and then by b does, which is the // property the joint hierarchy relies on. @@ -153,14 +147,14 @@ namespace const LLVector3 expected = (v * a) * b; for (S32 i = 0; i < 3; ++i) { - ensure_approximately_equals_range("rotate by the product", out[i], expected.mV[i], 1e-4f); + UNSCOPED_INFO("rotate by the product"); + REQUIRE_THAT(out[i], Catch::Matchers::WithinAbs(expected.mV[i], 1e-4f)); } } } } - template<> template<> - void llquaternion2_object::test<4>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: rotate matches LLVector3 operator* and carries w through", "[LLQuaternion2]") { // rotate() against LLVector3's operator*, and the w it was handed // comes back untouched. @@ -188,15 +182,16 @@ namespace const F32 scale = llmax(1.f, expected.magVec()); for (S32 i = 0; i < 3; ++i) { - ensure_approximately_equals_range("v * q", out[i], expected.mV[i], 1e-5f * scale); + UNSCOPED_INFO("v * q"); + REQUIRE_THAT(out[i], Catch::Matchers::WithinAbs(expected.mV[i], 1e-5f * scale)); } - ensure_approximately_equals_range("w is carried through", out[3], 7.f, TOLERANCE); + UNSCOPED_INFO("w is carried through"); + REQUIRE_THAT(out[3], Catch::Matchers::WithinAbs(7.f, TOLERANCE)); } } } - template<> template<> - void llquaternion2_object::test<5>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: dot matches the scalar dot including its sign", "[LLQuaternion2]") { // dot() against the scalar dot, sign included: the sign is what says // which way round the shorter interpolation goes. @@ -205,13 +200,13 @@ namespace for (const LLQuaternion& b : mRotations) { const LLSimdScalar d = LLQuaternion2(a).dot(LLQuaternion2(b)); - ensure_approximately_equals_range("dot", d.getF32(), dot(a, b), TOLERANCE); + UNSCOPED_INFO("dot"); + REQUIRE_THAT(d.getF32(), Catch::Matchers::WithinAbs(dot(a, b), TOLERANCE)); } } } - template<> template<> - void llquaternion2_object::test<6>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setLerp ends at its inputs and stays a rotation between", "[LLQuaternion2]") { // The ends of the interpolation are the ends, and everything in // between comes out normalized. @@ -231,14 +226,14 @@ namespace { LLQuaternion2 mid; mid.setLerp(LLQuaternion2(a), LLQuaternion2(b), u); - ensure("interpolated rotation is a rotation", mid.isOkRotation()); + UNSCOPED_INFO("interpolated rotation is a rotation"); + REQUIRE(mid.isOkRotation()); } } } } - template<> template<> - void llquaternion2_object::test<7>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setLerp takes the shorter way round", "[LLQuaternion2]") { // The interpolation takes the shorter way round. Half way between two // rotations a quarter turn apart is an eighth of a turn from each, @@ -259,16 +254,34 @@ namespace from_negated.store(got_negated); const F32 agreement = got_b.mQ[0] * got_negated.mQ[0] + got_b.mQ[1] * got_negated.mQ[1] + got_b.mQ[2] * got_negated.mQ[2] + got_b.mQ[3] * got_negated.mQ[3]; - ensure_approximately_equals_range("the negated end interpolates the same way", - fabsf(agreement), 1.f, TOLERANCE); + UNSCOPED_INFO("the negated end interpolates the same way"); + CHECK_THAT(fabsf(agreement), Catch::Matchers::WithinAbs(1.f, TOLERANCE)); // and it really is the eighth turn, not the seven eighths one const LLQuaternion eighth(F_PI * 0.25f, axis); ensure_same_rotation("half way is an eighth of a turn", from_b, eighth); } - template<> template<> - void llquaternion2_object::test<9>() + + + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: a quaternion times its conjugate is the identity", "[LLQuaternion2]") + { + // Conjugate undoes the rotation, and the product with it is identity. + for (const LLQuaternion& q : mRotations) + { + LLQuaternion2 simd(q); + LLQuaternion2 conj; + conj.setConjugate(simd); + + LLQuaternion2 product; + product.setMul(simd, conj); + + LLQuaternion ident; + ensure_same_rotation("q times its conjugate is identity", product, ident); + } + } + + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: rotate scales by an off-unit quaternion as LLQuaternion does", "[LLQuaternion2]") { // A quaternion that has drifted off unit length scales what it // rotates, and callers get the same answer from either type. The @@ -292,13 +305,13 @@ namespace const F32 scale = llmax(1.f, expected.magVec()); for (S32 i = 0; i < 3; ++i) { - ensure_approximately_equals_range("off-unit v * q", out[i], expected.mV[i], 1e-5f * scale); + UNSCOPED_INFO("off-unit v * q"); + REQUIRE_THAT(out[i], Catch::Matchers::WithinAbs(expected.mV[i], 1e-5f * scale)); } } } - template<> template<> - void llquaternion2_object::test<10>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setInverse undoes a rotation whatever its length", "[LLQuaternion2]") { // The inverse undoes the rotation whatever length it carries, where // the conjugate only does for one of unit length. A rotation that has @@ -327,32 +340,15 @@ namespace product.store(got); for (S32 i = 0; i < 3; ++i) { - ensure_approximately_equals_range("q times its inverse has no axis", got.mQ[i], 0.f, 1e-5f); + UNSCOPED_INFO("q times its inverse has no axis"); + REQUIRE_THAT(got.mQ[i], Catch::Matchers::WithinAbs(0.f, 1e-5f)); } - ensure_approximately_equals_range("q times its inverse is the identity", got.mQ[3], 1.f, 1e-5f); - } - } - - template<> template<> - void llquaternion2_object::test<8>() - { - // Conjugate undoes the rotation, and the product with it is identity. - for (const LLQuaternion& q : mRotations) - { - LLQuaternion2 simd(q); - LLQuaternion2 conj; - conj.setConjugate(simd); - - LLQuaternion2 product; - product.setMul(simd, conj); - - LLQuaternion ident; - ensure_same_rotation("q times its conjugate is identity", product, ident); + UNSCOPED_INFO("q times its inverse is the identity"); + REQUIRE_THAT(got.mQ[3], Catch::Matchers::WithinAbs(1.f, 1e-5f)); } } - template<> template<> - void llquaternion2_object::test<11>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setSlerp follows the free slerp along the arc", "[LLQuaternion2]") { // Against the free slerp, which is the second implementation this is // compared with rather than the same one written twice. @@ -365,7 +361,8 @@ namespace LLQuaternion expected = slerp(u, a, b); // q and -q are the same rotation. - tut::ensure_approximately_equals_range(what, fabsf(dot(got, expected)), 1.f, 1e-4f); + UNSCOPED_INFO(what); + REQUIRE_THAT(fabsf(dot(got, expected)), Catch::Matchers::WithinAbs(1.f, 1e-4f)); }; // A half turn apart, which is where a lerp is furthest out and where @@ -394,8 +391,7 @@ namespace } } - template<> template<> - void llquaternion2_object::test<12>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setSlerp ends at its inputs and handles equal or opposite pairs", "[LLQuaternion2]") { // The ends are the ends, whatever the pair. const LLQuaternion a(0.3f, LLVector3::x_axis); @@ -405,13 +401,15 @@ namespace at_start.setSlerp(LLQuaternion2(a), LLQuaternion2(b), 0.f); LLQuaternion start; at_start.store(start); - ensure_approximately_equals_range("nought is the first", fabsf(dot(start, a)), 1.f, 1e-5f); + UNSCOPED_INFO("nought is the first"); + CHECK_THAT(fabsf(dot(start, a)), Catch::Matchers::WithinAbs(1.f, 1e-5f)); LLQuaternion2 at_end; at_end.setSlerp(LLQuaternion2(a), LLQuaternion2(b), 1.f); LLQuaternion end; at_end.store(end); - ensure_approximately_equals_range("one is the second", fabsf(dot(end, b)), 1.f, 1e-5f); + UNSCOPED_INFO("one is the second"); + CHECK_THAT(fabsf(dot(end, b)), Catch::Matchers::WithinAbs(1.f, 1e-5f)); // Two rotations that are the same rotation stay put rather than // dividing by the sine of nothing. @@ -419,8 +417,10 @@ namespace same.setSlerp(LLQuaternion2(a), LLQuaternion2(a), 0.5f); LLQuaternion held; same.store(held); - ensure_approximately_equals_range("the same pair holds", fabsf(dot(held, a)), 1.f, 1e-5f); - ensure("and stays a rotation", same.isOkRotation()); + UNSCOPED_INFO("the same pair holds"); + CHECK_THAT(fabsf(dot(held, a)), Catch::Matchers::WithinAbs(1.f, 1e-5f)); + UNSCOPED_INFO("and stays a rotation"); + CHECK(same.isOkRotation()); // Handed the far side of the same rotation, it comes back the short // way rather than through the rotations neither end asked for. @@ -433,12 +433,11 @@ namespace shortest.setSlerp(LLQuaternion2(a), LLQuaternion2(negated), 0.5f); LLQuaternion midway; shortest.store(midway); - ensure_approximately_equals_range("a rotation and its negation are one place", - fabsf(dot(midway, a)), 1.f, 1e-4f); + UNSCOPED_INFO("a rotation and its negation are one place"); + CHECK_THAT(fabsf(dot(midway, a)), Catch::Matchers::WithinAbs(1.f, 1e-4f)); } - template<> template<> - void llquaternion2_object::test<13>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setSlerp of random pairs is a rotation matching slerp", "[LLQuaternion2]") { // Every result is a rotation, over a spread of pairs and positions. std::mt19937 rng(20260906u); @@ -461,18 +460,18 @@ namespace LLQuaternion2 blended; blended.setSlerp(LLQuaternion2(a), LLQuaternion2(b), u); - ensure("a blend of two rotations is a rotation", blended.isOkRotation()); + UNSCOPED_INFO("a blend of two rotations is a rotation"); + REQUIRE(blended.isOkRotation()); LLQuaternion got; blended.store(got); LLQuaternion expected = slerp(u, a, b); - ensure_approximately_equals_range("and it is the one slerp gives", - fabsf(dot(got, expected)), 1.f, 1e-3f); + UNSCOPED_INFO("and it is the one slerp gives"); + REQUIRE_THAT(fabsf(dot(got, expected)), Catch::Matchers::WithinAbs(1.f, 1e-3f)); } } - template<> template<> - void llquaternion2_object::test<14>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: quantize8 and quantize16 keep a rotation within a step", "[LLQuaternion2]") { // Quantizing to eight or sixteen bits keeps a rotation a rotation, // within the step the bits allow. @@ -480,30 +479,34 @@ namespace { LLQuaternion2 q8(q); q8.quantize8(); - ensure("quantize8 is a rotation", q8.isOkRotation()); + UNSCOPED_INFO("quantize8 is a rotation"); + REQUIRE(q8.isOkRotation()); LLQuaternion got8; q8.store(got8); - ensure_approximately_equals_range("quantize8 is the rotation it was given, to eight bits", fabsf(dot(got8, q)), 1.f, 1e-4f); + UNSCOPED_INFO("quantize8 is the rotation it was given, to eight bits"); + REQUIRE_THAT(fabsf(dot(got8, q)), Catch::Matchers::WithinAbs(1.f, 1e-4f)); for (S32 i = 0; i < 4; ++i) { - ensure("quantize8 component within a step", fabsf(got8.mQ[i] - q.mQ[i]) <= 2.f / 255.f); + UNSCOPED_INFO("quantize8 component within a step"); + REQUIRE(fabsf(got8.mQ[i] - q.mQ[i]) <= 2.f / 255.f); } LLQuaternion2 q16(q); q16.quantize16(); - ensure("quantize16 is a rotation", q16.isOkRotation()); + UNSCOPED_INFO("quantize16 is a rotation"); + REQUIRE(q16.isOkRotation()); ensure_same_rotation("quantize16 is the rotation it was given", q16, q); LLQuaternion got16; q16.store(got16); for (S32 i = 0; i < 4; ++i) { - ensure("quantize16 component within a step", fabsf(got16.mQ[i] - q.mQ[i]) <= 2.f / 65535.f); + UNSCOPED_INFO("quantize16 component within a step"); + REQUIRE(fabsf(got16.mQ[i] - q.mQ[i]) <= 2.f / 65535.f); } } } - template<> template<> - void llquaternion2_object::test<15>() + TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setConjugate flips the vector part alone", "[LLQuaternion2]") { // The conjugate flips the vector part alone, and the identity is // its own conjugate. @@ -512,13 +515,18 @@ namespace conj.setConjugate(LLQuaternion2(q)); LLQuaternion got; conj.store(got); - ensure_equals("conjugate x", got.mQ[VX], -q.mQ[VX]); - ensure_equals("conjugate y", got.mQ[VY], -q.mQ[VY]); - ensure_equals("conjugate z", got.mQ[VZ], -q.mQ[VZ]); - ensure_equals("conjugate w", got.mQ[VW], q.mQ[VW]); + UNSCOPED_INFO("conjugate x"); + CHECK(got.mQ[VX] == -q.mQ[VX]); + UNSCOPED_INFO("conjugate y"); + CHECK(got.mQ[VY] == -q.mQ[VY]); + UNSCOPED_INFO("conjugate z"); + CHECK(got.mQ[VZ] == -q.mQ[VZ]); + UNSCOPED_INFO("conjugate w"); + CHECK(got.mQ[VW] == q.mQ[VW]); LLQuaternion2 ident; ident.setConjugate(LLQuaternion2::identity()); - ensure("the identity is its own conjugate", ident.equals(LLQuaternion2::identity())); + UNSCOPED_INFO("the identity is its own conjugate"); + CHECK(ident.equals(LLQuaternion2::identity())); } } diff --git a/indra/llmath/tests/llquaternion_test.cpp b/indra/llmath/tests/llquaternion_test.cpp index a826eafa13..06bc9900fc 100644 --- a/indra/llmath/tests/llquaternion_test.cpp +++ b/indra/llmath/tests/llquaternion_test.cpp @@ -27,7 +27,8 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include #include "../v4math.h" #include "../v3math.h" @@ -36,32 +37,23 @@ #include "../m3math.h" #include "../llquaternion.h" -namespace tut -{ namespace { - struct llquat_test - { - }; - typedef test_group llquat_test_t; - typedef llquat_test_t::object llquat_test_object_t; - tut::llquat_test_t tut_llquat_test("LLQuaternion"); -} - //test case for LLQuaternion::LLQuaternion(void) fn. - template<> template<> - void llquat_test_object_t::test<1>() + TEST_CASE("LLQuaternion: default constructor gives the identity quaternion", "[LLQuaternion]") { LLQuaternion llquat; - ensure("LLQuaternion::LLQuaternion() failed", 0.f == llquat.mQ[0] && - 0.f == llquat.mQ[1] && - 0.f == llquat.mQ[2] && - 1.f == llquat.mQ[3]); + { + INFO("LLQuaternion::LLQuaternion() failed"); + CHECK(llquat.mQ[0] == 0.f); + CHECK(llquat.mQ[1] == 0.f); + CHECK(llquat.mQ[2] == 0.f); + CHECK(llquat.mQ[3] == 1.f); + } } //test case for explicit LLQuaternion(const LLMatrix4 &mat) fn. - template<> template<> - void llquat_test_object_t::test<2>() + TEST_CASE("LLQuaternion: LLMatrix4 initRows fills the matrix row by row", "[LLQuaternion]") { LLMatrix4 llmat; LLVector4 vector1(2.0f, 1.0f, 3.0f, 6.0f); @@ -70,26 +62,28 @@ namespace LLVector4 vector4(3.0f, 8.0f, 1.0f, 5.0f); llmat.initRows(vector1, vector2, vector3, vector4); - ensure("explicit LLQuaternion(const LLMatrix4 &mat) failed", 2.0f == llmat.mMatrix[0][0] && - 1.0f == llmat.mMatrix[0][1] && - 3.0f == llmat.mMatrix[0][2] && - 6.0f == llmat.mMatrix[0][3] && - 5.0f == llmat.mMatrix[1][0] && - 6.0f == llmat.mMatrix[1][1] && - 0.0f == llmat.mMatrix[1][2] && - 1.0f == llmat.mMatrix[1][3] && - 2.0f == llmat.mMatrix[2][0] && - 1.0f == llmat.mMatrix[2][1] && - 2.0f == llmat.mMatrix[2][2] && - 9.0f == llmat.mMatrix[2][3] && - 3.0f == llmat.mMatrix[3][0] && - 8.0f == llmat.mMatrix[3][1] && - 1.0f == llmat.mMatrix[3][2] && - 5.0f == llmat.mMatrix[3][3]); + { + INFO("explicit LLQuaternion(const LLMatrix4 &mat) failed"); + CHECK(llmat.mMatrix[0][0] == 2.0f); + CHECK(llmat.mMatrix[0][1] == 1.0f); + CHECK(llmat.mMatrix[0][2] == 3.0f); + CHECK(llmat.mMatrix[0][3] == 6.0f); + CHECK(llmat.mMatrix[1][0] == 5.0f); + CHECK(llmat.mMatrix[1][1] == 6.0f); + CHECK(llmat.mMatrix[1][2] == 0.0f); + CHECK(llmat.mMatrix[1][3] == 1.0f); + CHECK(llmat.mMatrix[2][0] == 2.0f); + CHECK(llmat.mMatrix[2][1] == 1.0f); + CHECK(llmat.mMatrix[2][2] == 2.0f); + CHECK(llmat.mMatrix[2][3] == 9.0f); + CHECK(llmat.mMatrix[3][0] == 3.0f); + CHECK(llmat.mMatrix[3][1] == 8.0f); + CHECK(llmat.mMatrix[3][2] == 1.0f); + CHECK(llmat.mMatrix[3][3] == 5.0f); + } } - template<> template<> - void llquat_test_object_t::test<3>() + TEST_CASE("LLQuaternion: LLMatrix3 setRows keeps each float exactly", "[LLQuaternion]") { LLMatrix3 llmat; @@ -98,20 +92,22 @@ namespace LLVector3 vect3(566.003034f, 12.98705f, 234.764423f); llmat.setRows(vect1, vect2, vect3); - ensure("LLMatrix3::setRows fn failed.", 3.4028234660000000f == llmat.mMatrix[0][0] && - 234.56f == llmat.mMatrix[0][1] && - 4234.442234f == llmat.mMatrix[0][2] && - 741.434f == llmat.mMatrix[1][0] && - 23.00034f == llmat.mMatrix[1][1] && - 6567.223423f == llmat.mMatrix[1][2] && - 566.003034f == llmat.mMatrix[2][0] && - 12.98705f == llmat.mMatrix[2][1] && - 234.764423f == llmat.mMatrix[2][2]); + { + INFO("LLMatrix3::setRows fn failed."); + CHECK(llmat.mMatrix[0][0] == 3.4028234660000000f); + CHECK(llmat.mMatrix[0][1] == 234.56f); + CHECK(llmat.mMatrix[0][2] == 4234.442234f); + CHECK(llmat.mMatrix[1][0] == 741.434f); + CHECK(llmat.mMatrix[1][1] == 23.00034f); + CHECK(llmat.mMatrix[1][2] == 6567.223423f); + CHECK(llmat.mMatrix[2][0] == 566.003034f); + CHECK(llmat.mMatrix[2][1] == 12.98705f); + CHECK(llmat.mMatrix[2][2] == 234.764423f); + } } //test case for LLQuaternion(F32 x, F32 y, F32 z, F32 w), setQuatInit() and normQuat() fns. - template<> template<> - void llquat_test_object_t::test<4>() + TEST_CASE("LLQuaternion: normQuat normalises and turns a zero quaternion into identity", "[LLQuaternion]") { F32 x_val = 3.0f; F32 y_val = 2.0f; @@ -122,11 +118,13 @@ namespace res_quat.setQuatInit(x_val, y_val, z_val, w_val); res_quat.normQuat(); - ensure("LLQuaternion::normQuat() fn failed", - is_approx_equal(0.42426407f, res_quat.mQ[0]) && - is_approx_equal(0.28284273f, res_quat.mQ[1]) && - is_approx_equal(0.84852815f, res_quat.mQ[2]) && - is_approx_equal(0.14142136f, res_quat.mQ[3])); + { + INFO("LLQuaternion::normQuat() fn failed"); + CHECK(is_approx_equal(0.42426407f, res_quat.mQ[0])); + CHECK(is_approx_equal(0.28284273f, res_quat.mQ[1])); + CHECK(is_approx_equal(0.84852815f, res_quat.mQ[2])); + CHECK(is_approx_equal(0.14142136f, res_quat.mQ[3])); + } x_val = 0.0f; y_val = 0.0f; @@ -136,23 +134,26 @@ namespace res_quat.setQuatInit(x_val, y_val, z_val, w_val); res_quat.normQuat(); - ensure("LLQuaternion::normQuat() fn. failed.", - is_approx_equal(0.0f, res_quat.mQ[0]) && - is_approx_equal(0.0f, res_quat.mQ[1]) && - is_approx_equal(0.0f, res_quat.mQ[2]) && - is_approx_equal(1.0f, res_quat.mQ[3])); - - - ensure("LLQuaternion::normQuat() fn. failed.", - is_approx_equal(0.0f, res_quat.mQ[0]) && - is_approx_equal(0.0f, res_quat.mQ[1]) && - is_approx_equal(0.0f, res_quat.mQ[2]) && - is_approx_equal(1.0f, res_quat.mQ[3])); + { + INFO("LLQuaternion::normQuat() fn. failed."); + CHECK(is_approx_equal(0.0f, res_quat.mQ[0])); + CHECK(is_approx_equal(0.0f, res_quat.mQ[1])); + CHECK(is_approx_equal(0.0f, res_quat.mQ[2])); + CHECK(is_approx_equal(1.0f, res_quat.mQ[3])); + } + + + { + INFO("LLQuaternion::normQuat() fn. failed."); + CHECK(is_approx_equal(0.0f, res_quat.mQ[0])); + CHECK(is_approx_equal(0.0f, res_quat.mQ[1])); + CHECK(is_approx_equal(0.0f, res_quat.mQ[2])); + CHECK(is_approx_equal(1.0f, res_quat.mQ[3])); + } } //test case for conjQuat() and transQuat() fns. - template<> template<> - void llquat_test_object_t::test<5>() + TEST_CASE("LLQuaternion: conjQuat and transQuat agree on the vector part", "[LLQuaternion]") { F32 x_val = 3.0f; F32 y_val = 2.0f; @@ -166,52 +167,57 @@ namespace result.conjQuat(); result1.transQuat(); - ensure("LLQuaternion::conjQuat and LLQuaternion::transQuat failed ", - is_approx_equal(result1.mQ[0], result.mQ[0]) && - is_approx_equal(result1.mQ[1], result.mQ[1]) && - is_approx_equal(result1.mQ[2], result.mQ[2])); + { + INFO("LLQuaternion::conjQuat and LLQuaternion::transQuat failed "); + CHECK(is_approx_equal(result1.mQ[0], result.mQ[0])); + CHECK(is_approx_equal(result1.mQ[1], result.mQ[1])); + CHECK(is_approx_equal(result1.mQ[2], result.mQ[2])); + } } //test case for dot(const LLQuaternion &a, const LLQuaternion &b) fn. - template<> template<> - void llquat_test_object_t::test<6>() + TEST_CASE("LLQuaternion: dot sums the products of the four components", "[LLQuaternion]") { LLQuaternion quat1(3.0f, 2.0f, 6.0f, 0.0f), quat2(1.0f, 1.0f, 1.0f, 1.0f); - ensure("1. The two values are different", ll_round(12.000000f, 2) == ll_round(dot(quat1, quat2), 2)); + UNSCOPED_INFO("1. The two values are different"); + CHECK(ll_round(12.000000f, 2) == ll_round(dot(quat1, quat2), 2)); LLQuaternion quat0(3.0f, 9.334f, 34.5f, 23.0f), quat(34.5f, 23.23f, 2.0f, 45.5f); - ensure("2. The two values are different", ll_round(1435.828807f, 2) == ll_round(dot(quat0, quat), 2)); + UNSCOPED_INFO("2. The two values are different"); + CHECK(ll_round(1435.828807f, 2) == ll_round(dot(quat0, quat), 2)); } //test case for LLQuaternion &LLQuaternion::constrain(F32 radians) fn. - template<> template<> - void llquat_test_object_t::test<7>() + TEST_CASE("LLQuaternion: constrain clamps only rotations wider than the cone angle", "[LLQuaternion]") { F32 radian = 60.0f; LLQuaternion quat(3.0f, 2.0f, 6.0f, 0.0f); LLQuaternion quat1; quat1 = quat.constrain(radian); - ensure("1. LLQuaternion::constrain(F32 radians) failed", - is_approx_equal_fraction(-0.423442f, quat1.mQ[0], 8) && - is_approx_equal_fraction(-0.282295f, quat1.mQ[1], 8) && - is_approx_equal_fraction(-0.846884f, quat1.mQ[2], 8) && - is_approx_equal_fraction(0.154251f, quat1.mQ[3], 8)); + { + INFO("1. LLQuaternion::constrain(F32 radians) failed"); + CHECK(is_approx_equal_fraction(-0.423442f, quat1.mQ[0], 8)); + CHECK(is_approx_equal_fraction(-0.282295f, quat1.mQ[1], 8)); + CHECK(is_approx_equal_fraction(-0.846884f, quat1.mQ[2], 8)); + CHECK(is_approx_equal_fraction(0.154251f, quat1.mQ[3], 8)); + } radian = 30.0f; LLQuaternion quat0(37.50f, 12.0f, 86.023f, 40.32f); quat1 = quat0.constrain(radian); - ensure("2. LLQuaternion::constrain(F32 radians) failed", - is_approx_equal_fraction(37.500000f, quat1.mQ[0], 8) && - is_approx_equal_fraction(12.0000f, quat1.mQ[1], 8) && - is_approx_equal_fraction(86.0230f, quat1.mQ[2], 8) && - is_approx_equal_fraction(40.320000f, quat1.mQ[3], 8)); + { + INFO("2. LLQuaternion::constrain(F32 radians) failed"); + CHECK(is_approx_equal_fraction(37.500000f, quat1.mQ[0], 8)); + CHECK(is_approx_equal_fraction(12.0000f, quat1.mQ[1], 8)); + CHECK(is_approx_equal_fraction(86.0230f, quat1.mQ[2], 8)); + CHECK(is_approx_equal_fraction(40.320000f, quat1.mQ[3], 8)); + } } - template<> template<> - void llquat_test_object_t::test<8>() + TEST_CASE("LLQuaternion: lerp and slerp and nlerp overloads interpolate", "[LLQuaternion]") { F32 value1 = 15.0f; LLQuaternion quat1(1.0f, 2.0f, 4.0f, 1.0f); @@ -226,43 +232,53 @@ namespace // Previous "expected" values were derived from a typo that read // q.mQ[VZ] for W instead of q.mQ[VW]. res_lerp = lerp(value1, quat1); - ensure("1. LLQuaternion lerp(F32 t, const LLQuaternion &q) failed", - is_approx_equal_fraction(0.218195f, res_lerp.mQ[0], 16) && - is_approx_equal_fraction(0.436390f, res_lerp.mQ[1], 16) && - is_approx_equal_fraction(0.872779f, res_lerp.mQ[2], 16) && - is_approx_equal_fraction(0.014546f, res_lerp.mQ[3], 16)); + { + INFO("1. LLQuaternion lerp(F32 t, const LLQuaternion &q) failed"); + CHECK(is_approx_equal_fraction(0.218195f, res_lerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.436390f, res_lerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.872779f, res_lerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.014546f, res_lerp.mQ[3], 16)); + } //test case for lerp(F32 t, const LLQuaternion &p, const LLQuaternion &q) fn. res_lerp = lerp(value1, quat1, quat2); - ensure("2. LLQuaternion lerp(F32 t, const LLQuaternion &p, const LLQuaternion &q) failed", - is_approx_equal_fraction(0.314306f, res_lerp.mQ[0], 16) && - is_approx_equal_fraction(0.116156f, res_lerp.mQ[1], 16) && - is_approx_equal_fraction(0.283559f, res_lerp.mQ[2], 16) && - is_approx_equal_fraction(0.898506f, res_lerp.mQ[3], 16)); + { + INFO("2. LLQuaternion lerp(F32 t, const LLQuaternion &p, const LLQuaternion &q) failed"); + CHECK(is_approx_equal_fraction(0.314306f, res_lerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.116156f, res_lerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.283559f, res_lerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.898506f, res_lerp.mQ[3], 16)); + } //test case for slerp( F32 u, const LLQuaternion &a, const LLQuaternion &b ) fn. res_slerp = slerp(value1, quat1, quat2); - ensure("3. LLQuaternion slerp( F32 u, const LLQuaternion &a, const LLQuaternion &b) failed", - is_approx_equal_fraction(46.000f, res_slerp.mQ[0], 16) && - is_approx_equal_fraction(17.00f, res_slerp.mQ[1], 16) && - is_approx_equal_fraction(41.5f, res_slerp.mQ[2], 16) && - is_approx_equal_fraction(131.5f, res_slerp.mQ[3], 16)); + { + INFO("3. LLQuaternion slerp( F32 u, const LLQuaternion &a, const LLQuaternion &b) failed"); + CHECK(is_approx_equal_fraction(46.000f, res_slerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(17.00f, res_slerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(41.5f, res_slerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(131.5f, res_slerp.mQ[3], 16)); + } //test case for nlerp(F32 t, const LLQuaternion &a, const LLQuaternion &b) fn. res_nlerp = nlerp(value1, quat1, quat2); - ensure("4. LLQuaternion nlerp(F32 t, const LLQuaternion &a, const LLQuaternion &b) failed", - is_approx_equal_fraction(0.314306f, res_nlerp.mQ[0], 16) && - is_approx_equal_fraction(0.116157f, res_nlerp.mQ[1], 16) && - is_approx_equal_fraction(0.283559f, res_nlerp.mQ[2], 16) && - is_approx_equal_fraction(0.898506f, res_nlerp.mQ[3], 16)); + { + INFO("4. LLQuaternion nlerp(F32 t, const LLQuaternion &a, const LLQuaternion &b) failed"); + CHECK(is_approx_equal_fraction(0.314306f, res_nlerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.116157f, res_nlerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.283559f, res_nlerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.898506f, res_nlerp.mQ[3], 16)); + } //test case for nlerp(F32 t, const LLQuaternion &q) fn. res_slerp = slerp(value1, quat1); - ensure("5. LLQuaternion slerp(F32 t, const LLQuaternion &q) failed", - is_approx_equal_fraction(1.0f, res_slerp.mQ[0], 16) && - is_approx_equal_fraction(2.0f, res_slerp.mQ[1], 16) && - is_approx_equal_fraction(4.0000f, res_slerp.mQ[2], 16) && - is_approx_equal_fraction(1.000f, res_slerp.mQ[3], 16)); + { + INFO("5. LLQuaternion slerp(F32 t, const LLQuaternion &q) failed"); + CHECK(is_approx_equal_fraction(1.0f, res_slerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(2.0f, res_slerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(4.0000f, res_slerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(1.000f, res_slerp.mQ[3], 16)); + } LLQuaternion quat3(2.0f, 1.0f, 5.5f, 10.5f); LLQuaternion res_nlerp1; @@ -273,11 +289,13 @@ namespace // pre-normalize r = (200, 100, 550, 951) (W = 1 + 100*(10.5 - 1) = 951) // |r| = sqrt(1256901) ~= 1121.116 // r/|r| ~= (0.178394, 0.089197, 0.490583, 0.848262) - ensure("6. LLQuaternion nlerp(F32 t, const LLQuaternion &q) failed", - is_approx_equal_fraction(0.178394f, res_nlerp1.mQ[0], 16) && - is_approx_equal_fraction(0.089197f, res_nlerp1.mQ[1], 16) && - is_approx_equal_fraction(0.490583f, res_nlerp1.mQ[2], 16) && - is_approx_equal_fraction(0.848262f, res_nlerp1.mQ[3], 16)); + { + INFO("6. LLQuaternion nlerp(F32 t, const LLQuaternion &q) failed"); + CHECK(is_approx_equal_fraction(0.178394f, res_nlerp1.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.089197f, res_nlerp1.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.490583f, res_nlerp1.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.848262f, res_nlerp1.mQ[3], 16)); + } //test case for lerp(F32 t, const LLQuaternion &q) fn. // value1 = 100, quat2 = (4, 3, 6.5, 9.7): @@ -285,25 +303,29 @@ namespace // |r| = sqrt(1431141) ~= 1196.303 // r/|r| ~= (0.334363, 0.250773, 0.543341, 0.728076) res_lerp = lerp(value1, quat2); - ensure("7. LLQuaternion lerp(F32 t, const LLQuaternion &q) failed", - is_approx_equal_fraction(0.334363f, res_lerp.mQ[0], 16) && - is_approx_equal_fraction(0.250773f, res_lerp.mQ[1], 16) && - is_approx_equal_fraction(0.543341f, res_lerp.mQ[2], 16) && - is_approx_equal_fraction(0.728076f, res_lerp.mQ[3], 16)); + { + INFO("7. LLQuaternion lerp(F32 t, const LLQuaternion &q) failed"); + CHECK(is_approx_equal_fraction(0.334363f, res_lerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.250773f, res_lerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.543341f, res_lerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.728076f, res_lerp.mQ[3], 16)); + } } - template<> template<> - void llquat_test_object_t::test<9>() + TEST_CASE("LLQuaternion: operator* and operator+ and operator- on two quaternions", "[LLQuaternion]") { //test case for LLQuaternion operator*(const LLQuaternion &a, const LLQuaternion &b) fn LLQuaternion quat1(1.0f, 2.5f, 3.5f, 5.5f); LLQuaternion quat2(4.0f, 3.0f, 5.0f, 1.0f); LLQuaternion result = quat1 * quat2; - ensure("1. LLQuaternion Operator* failed", (21.0f == result.mQ[0]) && - (10.0f == result.mQ[1]) && - (38.0f == result.mQ[2]) && - (-23.5f == result.mQ[3])); + { + INFO("1. LLQuaternion Operator* failed"); + CHECK(result.mQ[0] == 21.0f); + CHECK(result.mQ[1] == 10.0f); + CHECK(result.mQ[2] == 38.0f); + CHECK(result.mQ[3] == -23.5f); + } LLQuaternion quat3(2341.340f, 2352.345f, 233.25f, 7645.5f); LLQuaternion quat4(674.067f, 893.0897f, 578.0f, 231.0f); @@ -311,165 +333,178 @@ namespace // Products in the millions: the build contracts and reassociates // the arithmetic, so the last bit or two of each is the // machine's, not the one the constants were written on. - ensure("2. LLQuaternion Operator* failed", is_approx_equal_fraction(4543086.5f, result.mQ[0], 2) && - is_approx_equal_fraction(8567578.0f, result.mQ[1], 2) && - is_approx_equal_fraction(3967591.25f, result.mQ[2], 2) && - is_approx_equal_fraction(-2047783.25f, result.mQ[3], 2)); + { + INFO("2. LLQuaternion Operator* failed"); + CHECK(is_approx_equal_fraction(4543086.5f, result.mQ[0], 2)); + CHECK(is_approx_equal_fraction(8567578.0f, result.mQ[1], 2)); + CHECK(is_approx_equal_fraction(3967591.25f, result.mQ[2], 2)); + CHECK(is_approx_equal_fraction(-2047783.25f, result.mQ[3], 2)); + } //inline LLQuaternion operator+(const LLQuaternion &a, const LLQuaternion &b)fn. result = quat1 + quat2; - ensure("3. LLQuaternion operator+ failed", (5.0f == result.mQ[0]) && - (5.5f == result.mQ[1]) && - (8.5f == result.mQ[2]) && - (6.5f == result.mQ[3])); + { + INFO("3. LLQuaternion operator+ failed"); + CHECK(result.mQ[0] == 5.0f); + CHECK(result.mQ[1] == 5.5f); + CHECK(result.mQ[2] == 8.5f); + CHECK(result.mQ[3] == 6.5f); + } result = quat3 + quat4; - ensure( - "4. LLQuaternion operator+ failed", - is_approx_equal(3015.407227f, result.mQ[0]) && - is_approx_equal(3245.434570f, result.mQ[1]) && - (811.25f == result.mQ[2]) && - (7876.5f == result.mQ[3])); + { + INFO("4. LLQuaternion operator+ failed"); + CHECK(is_approx_equal(3015.407227f, result.mQ[0])); + CHECK(is_approx_equal(3245.434570f, result.mQ[1])); + CHECK(result.mQ[2] == 811.25f); + CHECK(result.mQ[3] == 7876.5f); + } //inline LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) fn result = quat1 - quat2; - ensure( - "5. LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) failed", - (-3.0f == result.mQ[0]) && - (-0.5f == result.mQ[1]) && - (-1.5f == result.mQ[2]) && - (4.5f == result.mQ[3])); + { + INFO("5. LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) failed"); + CHECK(result.mQ[0] == -3.0f); + CHECK(result.mQ[1] == -0.5f); + CHECK(result.mQ[2] == -1.5f); + CHECK(result.mQ[3] == 4.5f); + } result = quat3 - quat4; - ensure( - "6. LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) failed", - is_approx_equal(1667.273071f, result.mQ[0]) && - is_approx_equal(1459.255249f, result.mQ[1]) && - (-344.75f == result.mQ[2]) && - (7414.50f == result.mQ[3])); + { + INFO("6. LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) failed"); + CHECK(is_approx_equal(1667.273071f, result.mQ[0])); + CHECK(is_approx_equal(1459.255249f, result.mQ[1])); + CHECK(result.mQ[2] == -344.75f); + CHECK(result.mQ[3] == 7414.50f); + } } //test case for LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) fn. - template<> template<> - void llquat_test_object_t::test<10>() + TEST_CASE("LLQuaternion: operator* rotates an LLVector4 by a quaternion", "[LLQuaternion]") { LLVector4 vect(12.0f, 5.0f, 60.0f, 75.1f); LLQuaternion quat(2323.034f, 23.5f, 673.23f, 57667.5f); LLVector4 result = vect * quat; - ensure( - "1. LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) failed", - is_approx_equal(39928406016.0f, result.mV[0]) && - // gcc on x86 actually gives us more precision than we were expecting, verified with -ffloat-store - we forgive this - (1457802240.0f >= result.mV[1]) && // gcc+x86+linux - (1457800960.0f <= result.mV[1]) && // elsewhere - is_approx_equal(200580612096.0f, result.mV[2]) && - (75.099998f == result.mV[3])); + { + INFO("1. LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) failed"); + CHECK(is_approx_equal(39928406016.0f, result.mV[0])); + CHECK(1457802240.0f >= result.mV[1]); + CHECK(1457800960.0f <= result.mV[1]); + CHECK(is_approx_equal(200580612096.0f, result.mV[2])); + CHECK(result.mV[3] == 75.099998f); + } LLVector4 vect1(22.0f, 45.0f, 40.0f, 78.1f); LLQuaternion quat1(2.034f, 45.5f, 37.23f, 7.5f); result = vect1 * quat1; - ensure( - "2. LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) failed", - is_approx_equal(-58153.5390f, result.mV[0]) && - is_approx_equal(183787.8125f, result.mV[1]) && - is_approx_equal(116864.164063f, result.mV[2]) && - is_approx_equal(78.099998f, result.mV[3])); + { + INFO("2. LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) failed"); + CHECK(is_approx_equal(-58153.5390f, result.mV[0])); + CHECK(is_approx_equal(183787.8125f, result.mV[1])); + CHECK(is_approx_equal(116864.164063f, result.mV[2])); + CHECK(is_approx_equal(78.099998f, result.mV[3])); + } } //test case for LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) fn. - template<> template<> - void llquat_test_object_t::test<11>() + TEST_CASE("LLQuaternion: operator* rotates an LLVector3 by a quaternion", "[LLQuaternion]") { LLVector3 vect(12.0f, 5.0f, 60.0f); LLQuaternion quat(23.5f, 6.5f, 3.23f, 56.5f); LLVector3 result = vect * quat; - ensure( - "1. LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) failed", - is_approx_equal(97182.953125f,result.mV[0]) && - is_approx_equal(-135405.640625f, result.mV[1]) && - is_approx_equal(162986.140f, result.mV[2])); + { + INFO("1. LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) failed"); + CHECK(is_approx_equal(97182.953125f,result.mV[0])); + CHECK(is_approx_equal(-135405.640625f, result.mV[1])); + CHECK(is_approx_equal(162986.140f, result.mV[2])); + } LLVector3 vect1(5.0f, 40.0f, 78.1f); LLQuaternion quat1(2.034f, 45.5f, 37.23f, 7.5f); result = vect1 * quat1; - ensure( - "2. LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) failed", - is_approx_equal(33217.703f, result.mV[0]) && - is_approx_equal(295383.8125f, result.mV[1]) && - is_approx_equal(84718.140f, result.mV[2])); + { + INFO("2. LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) failed"); + CHECK(is_approx_equal(33217.703f, result.mV[0])); + CHECK(is_approx_equal(295383.8125f, result.mV[1])); + CHECK(is_approx_equal(84718.140f, result.mV[2])); + } } //test case for LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) fn. - template<> template<> - void llquat_test_object_t::test<12>() + TEST_CASE("LLQuaternion: operator* rotates an LLVector3d by a quaternion", "[LLQuaternion]") { LLVector3d vect(-2.0f, 5.0f, -6.0f); LLQuaternion quat(-3.5f, 4.5f, 3.5f, 6.5f); LLVector3d result = vect * quat; - ensure( - "1. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed ", - (-633.0f == result.mdV[0]) && - (-300.0f == result.mdV[1]) && - (-36.0f == result.mdV[2])); + { + INFO("1. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed "); + CHECK(result.mdV[0] == -633.0f); + CHECK(result.mdV[1] == -300.0f); + CHECK(result.mdV[2] == -36.0f); + } LLVector3d vect1(5.0f, -4.5f, 8.21f); LLQuaternion quat1(2.0f, 4.5f, -7.2f, 9.5f); result = vect1 * quat1; - ensure( - "2. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed", - is_approx_equal_fraction(-120.29f, (F32) result.mdV[0], 8) && - is_approx_equal_fraction(-1683.958f, (F32) result.mdV[1], 8) && - is_approx_equal_fraction(516.56f, (F32) result.mdV[2], 8)); + { + INFO("2. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed"); + CHECK(is_approx_equal_fraction(-120.29f, (F32) result.mdV[0], 8)); + CHECK(is_approx_equal_fraction(-1683.958f, (F32) result.mdV[1], 8)); + CHECK(is_approx_equal_fraction(516.56f, (F32) result.mdV[2], 8)); + } LLVector3d vect2(2.0f, 3.5f, 1.1f); LLQuaternion quat2(1.0f, 4.0f, 2.0f, 5.0f); result = vect2 * quat2; - ensure( - "3. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed", - is_approx_equal_fraction(18.400001f, (F32) result.mdV[0], 8) && - is_approx_equal_fraction(188.6f, (F32) result.mdV[1], 8) && - is_approx_equal_fraction(32.20f, (F32) result.mdV[2], 8)); + { + INFO("3. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed"); + CHECK(is_approx_equal_fraction(18.400001f, (F32) result.mdV[0], 8)); + CHECK(is_approx_equal_fraction(188.6f, (F32) result.mdV[1], 8)); + CHECK(is_approx_equal_fraction(32.20f, (F32) result.mdV[2], 8)); + } } //test case for inline LLQuaternion operator-(const LLQuaternion &a) fn. - template<> template<> - void llquat_test_object_t::test<13>() + TEST_CASE("LLQuaternion: unary operator- negates every component", "[LLQuaternion]") { LLQuaternion quat(23.5f, 34.5f, 16723.4f, 324.7f); LLQuaternion result = -quat; - ensure( - "1. LLQuaternion operator-(const LLQuaternion &a) failed", - (-23.5f == result.mQ[0]) && - (-34.5f == result.mQ[1]) && - (-16723.4f == result.mQ[2]) && - (-324.7f == result.mQ[3])); + { + INFO("1. LLQuaternion operator-(const LLQuaternion &a) failed"); + CHECK(result.mQ[0] == -23.5f); + CHECK(result.mQ[1] == -34.5f); + CHECK(result.mQ[2] == -16723.4f); + CHECK(result.mQ[3] == -324.7f); + } LLQuaternion quat1(-3.5f, -34.5f, -16.4f, -154.7f); result = -quat1; - ensure( - "2. LLQuaternion operator-(const LLQuaternion &a) failed.", - (3.5f == result.mQ[0]) && - (34.5f == result.mQ[1]) && - (16.4f == result.mQ[2]) && - (154.7f == result.mQ[3])); + { + INFO("2. LLQuaternion operator-(const LLQuaternion &a) failed."); + CHECK(result.mQ[0] == 3.5f); + CHECK(result.mQ[1] == 34.5f); + CHECK(result.mQ[2] == 16.4f); + CHECK(result.mQ[3] == 154.7f); + } } //test case for inline LLQuaternion operator*(F32 a, const LLQuaternion &q) and //inline LLQuaternion operator*(F32 a, const LLQuaternion &q) fns. - template<> template<> - void llquat_test_object_t::test<14>() + TEST_CASE("LLQuaternion: scalar operator* gives the same on either side", "[LLQuaternion]") { LLQuaternion quat_value(9.0f, 8.0f, 7.0f, 6.0f); F32 a =3.5f; LLQuaternion result = a * quat_value; LLQuaternion result1 = quat_value * a; - ensure( - "1. LLQuaternion operator* failed", - (result.mQ[0] == result1.mQ[0]) && - (result.mQ[1] == result1.mQ[1]) && - (result.mQ[2] == result1.mQ[2]) && - (result.mQ[3] == result1.mQ[3])); + { + INFO("1. LLQuaternion operator* failed"); + CHECK(result.mQ[0] == result1.mQ[0]); + CHECK(result.mQ[1] == result1.mQ[1]); + CHECK(result.mQ[2] == result1.mQ[2]); + CHECK(result.mQ[3] == result1.mQ[3]); + } LLQuaternion quat_val(9454.0f, 43568.3450f, 456343247.0343f, 2346.03434f); @@ -477,46 +512,45 @@ namespace result = a * quat_val; result1 = quat_val * a; - ensure( - "2. LLQuaternion operator* failed", - (result.mQ[0] == result1.mQ[0]) && - (result.mQ[1] == result1.mQ[1]) && - (result.mQ[2] == result1.mQ[2]) && - (result.mQ[3] == result1.mQ[3])); + { + INFO("2. LLQuaternion operator* failed"); + CHECK(result.mQ[0] == result1.mQ[0]); + CHECK(result.mQ[1] == result1.mQ[1]); + CHECK(result.mQ[2] == result1.mQ[2]); + CHECK(result.mQ[3] == result1.mQ[3]); + } } - template<> template<> - void llquat_test_object_t::test<15>() + TEST_CASE("LLQuaternion: operator~ conjugates and operator== matches equal quaternions", "[LLQuaternion]") { // test cases for inline LLQuaternion operator~(const LLQuaternion &a) LLQuaternion quat_val(2323.634f, -43535.4f, 3455.88f, -32232.45f); LLQuaternion result = ~quat_val; - ensure( - "1. LLQuaternion operator~(const LLQuaternion &a) failed ", - (-2323.634f == result.mQ[0]) && - (43535.4f == result.mQ[1]) && - (-3455.88f == result.mQ[2]) && - (-32232.45f == result.mQ[3])); + { + INFO("1. LLQuaternion operator~(const LLQuaternion &a) failed "); + CHECK(result.mQ[0] == -2323.634f); + CHECK(result.mQ[1] == 43535.4f); + CHECK(result.mQ[2] == -3455.88f); + CHECK(result.mQ[3] == -32232.45f); + } //test case for inline bool LLQuaternion::operator==(const LLQuaternion &b) const LLQuaternion quat_val1(2323.634f, -43535.4f, 3455.88f, -32232.45f); LLQuaternion quat_val2(2323.634f, -43535.4f, 3455.88f, -32232.45f); - ensure( - "2. LLQuaternion::operator==(const LLQuaternion &b) failed", - quat_val1 == quat_val2); + UNSCOPED_INFO("2. LLQuaternion::operator==(const LLQuaternion &b) failed"); + CHECK(quat_val1 == quat_val2); } - template<> template<> - void llquat_test_object_t::test<16>() + TEST_CASE("LLQuaternion: operator!= tells apart quaternions differing in one component", "[LLQuaternion]") { //test case for inline bool LLQuaternion::operator!=(const LLQuaternion &b) const LLQuaternion quat_val1(2323.634f, -43535.4f, 3455.88f, -32232.45f); LLQuaternion quat_val2(0, -43535.4f, 3455.88f, -32232.45f); - ensure("LLQuaternion::operator!=(const LLQuaternion &b) failed", quat_val1 != quat_val2); + UNSCOPED_INFO("LLQuaternion::operator!=(const LLQuaternion &b) failed"); + CHECK(quat_val1 != quat_val2); } - template<> template<> - void llquat_test_object_t::test<17>() + TEST_CASE("LLQuaternion: mayaQ builds the rotation for each axis order", "[LLQuaternion]") { //test case for LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) F32 x = 2.0f; @@ -524,145 +558,162 @@ namespace F32 z = 3.0f; LLQuaternion result = mayaQ(x, y, z, LLQuaternion::XYZ); - ensure( - "1. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XYZ", - is_approx_equal_fraction(0.0172174f, result.mQ[0], 16) && - is_approx_equal_fraction(0.009179f, result.mQ[1], 16) && - is_approx_equal_fraction(0.026020f, result.mQ[2], 16) && - is_approx_equal_fraction(0.999471f, result.mQ[3], 16)); + { + INFO("1. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XYZ"); + CHECK(is_approx_equal_fraction(0.0172174f, result.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.009179f, result.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026020f, result.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999471f, result.mQ[3], 16)); + } LLQuaternion result1 = mayaQ(x, y, z, LLQuaternion::YZX); - ensure( - "2. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XYZ", - is_approx_equal_fraction(0.017217f, result1.mQ[0], 16) && - is_approx_equal_fraction(0.008265f, result1.mQ[1], 16) && - is_approx_equal_fraction(0.026324f, result1.mQ[2], 16) && - is_approx_equal_fraction(0.999471f, result1.mQ[3], 16)); + { + INFO("2. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XYZ"); + CHECK(is_approx_equal_fraction(0.017217f, result1.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.008265f, result1.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026324f, result1.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999471f, result1.mQ[3], 16)); + } LLQuaternion result2 = mayaQ(x, y, z, LLQuaternion::ZXY); - ensure( - "3. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for ZXY", - is_approx_equal_fraction(0.017674f, result2.mQ[0], 16) && - is_approx_equal_fraction(0.008265f, result2.mQ[1], 16) && - is_approx_equal_fraction(0.026020f, result2.mQ[2], 16) && - is_approx_equal_fraction(0.999471f, result2.mQ[3], 16)); + { + INFO("3. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for ZXY"); + CHECK(is_approx_equal_fraction(0.017674f, result2.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.008265f, result2.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026020f, result2.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999471f, result2.mQ[3], 16)); + } LLQuaternion result3 = mayaQ(x, y, z, LLQuaternion::XZY); - ensure( - "4. TLLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XZY", - is_approx_equal_fraction(0.017674f, result3.mQ[0], 16) && - is_approx_equal_fraction(0.009179f, result3.mQ[1], 16) && - is_approx_equal_fraction(0.026020f, result3.mQ[2], 16) && - is_approx_equal_fraction(0.999463f, result3.mQ[3], 16)); + { + INFO("4. TLLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XZY"); + CHECK(is_approx_equal_fraction(0.017674f, result3.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.009179f, result3.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026020f, result3.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999463f, result3.mQ[3], 16)); + } LLQuaternion result4 = mayaQ(x, y, z, LLQuaternion::YXZ); - ensure( - "5. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for YXZ", - is_approx_equal_fraction(0.017217f, result4.mQ[0], 16) && - is_approx_equal_fraction(0.009179f, result4.mQ[1], 16) && - is_approx_equal_fraction(0.026324f, result4.mQ[2], 16) && - is_approx_equal_fraction(0.999463f, result4.mQ[3], 16)); + { + INFO("5. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for YXZ"); + CHECK(is_approx_equal_fraction(0.017217f, result4.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.009179f, result4.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026324f, result4.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999463f, result4.mQ[3], 16)); + } LLQuaternion result5 = mayaQ(x, y, z, LLQuaternion::ZYX); - ensure( - "6. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for ZYX", - is_approx_equal_fraction(0.017674f, result5.mQ[0], 16) && - is_approx_equal_fraction(0.008265f, result5.mQ[1], 16) && - is_approx_equal_fraction(0.026324f, result5.mQ[2], 16) && - is_approx_equal_fraction(0.999463f, result5.mQ[3], 16)); + { + INFO("6. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for ZYX"); + CHECK(is_approx_equal_fraction(0.017674f, result5.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.008265f, result5.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026324f, result5.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999463f, result5.mQ[3], 16)); + } } - template<> template<> - void llquat_test_object_t::test<18>() + TEST_CASE("LLQuaternion: operator<< prints the components in braces", "[LLQuaternion]") { // test case for friend std::ostream& operator<<(std::ostream &s, const LLQuaternion &a) fn LLQuaternion a(1.0f, 1.0f, 1.0f, 1.0f); std::ostringstream result_value; result_value << a; - ensure_equals("1. Operator << failed", result_value.str(), "{ 1, 1, 1, 1 }"); + UNSCOPED_INFO("1. Operator << failed"); + CHECK(result_value.str() == "{ 1, 1, 1, 1 }"); LLQuaternion b(-31.034f, 231.2340f, 3451.344320f, -341.0f); std::ostringstream result_value1; result_value1 << b; - ensure_equals("2. Operator << failed", result_value1.str(), "{ -31.034, 231.234, 3451.34, -341 }"); + UNSCOPED_INFO("2. Operator << failed"); + CHECK(result_value1.str() == "{ -31.034, 231.234, 3451.34, -341 }"); LLQuaternion c(1.0f, 2.2f, 3.3f, 4.4f); result_value << c; - ensure_equals("3. Operator << failed", result_value.str(), "{ 1, 1, 1, 1 }{ 1, 2.2, 3.3, 4.4 }"); + UNSCOPED_INFO("3. Operator << failed"); + CHECK(result_value.str() == "{ 1, 1, 1, 1 }{ 1, 2.2, 3.3, 4.4 }"); } - template<> template<> - void llquat_test_object_t::test<19>() + TEST_CASE("LLQuaternion: OrderToString names each axis order", "[LLQuaternion]") { //test case for const char *OrderToString( const LLQuaternion::Order order ) fn const char* result = OrderToString(LLQuaternion::XYZ); - ensure("1. OrderToString failed for XYZ", (0 == strcmp("XYZ", result))); + UNSCOPED_INFO("1. OrderToString failed for XYZ"); + CHECK(std::string_view(result) == "XYZ"); result = OrderToString(LLQuaternion::YZX); - ensure("2. OrderToString failed for YZX", (0 == strcmp("YZX", result))); + UNSCOPED_INFO("2. OrderToString failed for YZX"); + CHECK(std::string_view(result) == "YZX"); result = OrderToString(LLQuaternion::ZXY); - ensure( - "3. OrderToString failed for ZXY", - (0 == strcmp("ZXY", result)) && - (0 != strcmp("XYZ", result)) && - (0 != strcmp("YXZ", result)) && - (0 != strcmp("ZYX", result)) && - (0 != strcmp("XYZ", result))); + { + INFO("3. OrderToString failed for ZXY"); + CHECK(std::string_view(result) == "ZXY"); + CHECK(std::string_view(result) != "XYZ"); + CHECK(std::string_view(result) != "YXZ"); + CHECK(std::string_view(result) != "ZYX"); + CHECK(std::string_view(result) != "XYZ"); + } result = OrderToString(LLQuaternion::XZY); - ensure("4. OrderToString failed for XZY", (0 == strcmp("XZY", result))); + UNSCOPED_INFO("4. OrderToString failed for XZY"); + CHECK(std::string_view(result) == "XZY"); result = OrderToString(LLQuaternion::ZYX); - ensure("5. OrderToString failed for ZYX", (0 == strcmp("ZYX", result))); + UNSCOPED_INFO("5. OrderToString failed for ZYX"); + CHECK(std::string_view(result) == "ZYX"); result = OrderToString(LLQuaternion::YXZ); - ensure("6.OrderToString failed for YXZ", (0 == strcmp("YXZ", result))); + UNSCOPED_INFO("6.OrderToString failed for YXZ"); + CHECK(std::string_view(result) == "YXZ"); } - template<> template<> - void llquat_test_object_t::test<20>() + TEST_CASE("LLQuaternion: StringToOrder maps each name to its axis order", "[LLQuaternion]") { //test case for LLQuaternion::Order StringToOrder( const char *str ) fn int result = StringToOrder("XYZ"); - ensure("1. LLQuaternion::Order StringToOrder(const char *str ) failed for XYZ", 0 == result); + UNSCOPED_INFO("1. LLQuaternion::Order StringToOrder(const char *str ) failed for XYZ"); + CHECK(result == 0); result = StringToOrder("YZX"); - ensure("2. LLQuaternion::Order StringToOrder(const char *str) failed for YZX", 1 == result); + UNSCOPED_INFO("2. LLQuaternion::Order StringToOrder(const char *str) failed for YZX"); + CHECK(result == 1); result = StringToOrder("ZXY"); - ensure("3. LLQuaternion::Order StringToOrder(const char *str) failed for ZXY", 2 == result); + UNSCOPED_INFO("3. LLQuaternion::Order StringToOrder(const char *str) failed for ZXY"); + CHECK(result == 2); result = StringToOrder("XZY"); - ensure("4. LLQuaternion::Order StringToOrder(const char *str) failed for XZY", 3 == result); + UNSCOPED_INFO("4. LLQuaternion::Order StringToOrder(const char *str) failed for XZY"); + CHECK(result == 3); result = StringToOrder("YXZ"); - ensure("5. LLQuaternion::Order StringToOrder(const char *str) failed for YXZ", 4 == result); + UNSCOPED_INFO("5. LLQuaternion::Order StringToOrder(const char *str) failed for YXZ"); + CHECK(result == 4); result = StringToOrder("ZYX"); - ensure("6. LLQuaternion::Order StringToOrder(const char *str) failed for ZYX", 5 == result); + UNSCOPED_INFO("6. LLQuaternion::Order StringToOrder(const char *str) failed for ZYX"); + CHECK(result == 5); } - template<> template<> - void llquat_test_object_t::test<21>() + TEST_CASE("LLQuaternion: getAngleAxis recovers the angle and the normalised axis", "[LLQuaternion]") { //void LLQuaternion::getAngleAxis(F32* angle, LLVector3 &vec) const fn F32 angle_value = 90.0f; LLVector3 vect(12.0f, 4.0f, 1.0f); LLQuaternion llquat(angle_value, vect); llquat.getAngleAxis(&angle_value, vect); - ensure( - "LLQuaternion::getAngleAxis(F32* angle, LLVector3 &vec) failed", - is_approx_equal_fraction(2.035406f, angle_value, 16) && - is_approx_equal_fraction(0.315244f, vect.mV[1], 16) && - is_approx_equal_fraction(0.078811f, vect.mV[2], 16) && - is_approx_equal_fraction(0.945733f, vect.mV[0], 16)); + { + INFO("LLQuaternion::getAngleAxis(F32* angle, LLVector3 &vec) failed"); + CHECK(is_approx_equal_fraction(2.035406f, angle_value, 16)); + CHECK(is_approx_equal_fraction(0.315244f, vect.mV[1], 16)); + CHECK(is_approx_equal_fraction(0.078811f, vect.mV[2], 16)); + CHECK(is_approx_equal_fraction(0.945733f, vect.mV[0], 16)); + } } - template<> template<> - void llquat_test_object_t::test<22>() + TEST_CASE("LLQuaternion: getEulerAngles leaves an identity quaternion unchanged", "[LLQuaternion]") { //test case for void LLQuaternion::getEulerAngles(F32 *roll, F32 *pitch, F32 *yaw) const fn F32 roll = -12.0f; @@ -671,24 +722,25 @@ namespace LLQuaternion llquat; llquat.getEulerAngles(&roll, &pitch, &yaw); - ensure( - "LLQuaternion::getEulerAngles(F32 *roll, F32 *pitch, F32 *yaw) failed", - is_approx_equal(0.000f, llquat.mQ[0]) && - is_approx_equal(0.000f, llquat.mQ[1]) && - is_approx_equal(0.000f, llquat.mQ[2]) && - is_approx_equal(1.000f, llquat.mQ[3])); + { + INFO("LLQuaternion::getEulerAngles(F32 *roll, F32 *pitch, F32 *yaw) failed"); + CHECK(is_approx_equal(0.000f, llquat.mQ[0])); + CHECK(is_approx_equal(0.000f, llquat.mQ[1])); + CHECK(is_approx_equal(0.000f, llquat.mQ[2])); + CHECK(is_approx_equal(1.000f, llquat.mQ[3])); + } } // The product and the dot evaluate at compile time. - template<> template<> - void llquat_test_object_t::test<23>() + TEST_CASE("LLQuaternion: product and dot evaluate at compile time", "[LLQuaternion]") { constexpr LLQuaternion half_turn(0.f, 0.f, 1.f, 0.f); constexpr LLQuaternion full_turn = half_turn * half_turn; static_assert(full_turn == LLQuaternion(0.f, 0.f, 0.f, -1.f)); static_assert(dot(half_turn, half_turn) == 1.f); static_assert(dot(half_turn, full_turn) == 0.f); - ensure("compile-time quaternion agrees at run time", full_turn.mQ[VW] == -1.f); + UNSCOPED_INFO("compile-time quaternion agrees at run time"); + CHECK(full_turn.mQ[VW] == -1.f); } } diff --git a/indra/llmath/tests/llrect_test.cpp b/indra/llmath/tests/llrect_test.cpp index a98907140c..d15f96bc6d 100644 --- a/indra/llmath/tests/llrect_test.cpp +++ b/indra/llmath/tests/llrect_test.cpp @@ -27,31 +27,17 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../llrect.h" -namespace tut -{ namespace { - struct LLRectData - { - }; - - typedef test_group factory; - typedef factory::object object; -} } namespace { - tut::factory llrect_test_factory("LLRect"); -} - -namespace tut -{ - template<> template<> - void object::test<1>() + TEST_CASE("LLRect: default constructor gives an all-zero rect", "[LLRect]") { // // test the LLRect default constructor @@ -62,19 +48,27 @@ namespace tut // default constructor LLRect rect1; - ensure_equals("Empty rect", rect1.getValue(), zero); - ensure_equals("Empty rect left", rect1.mLeft, 0); - ensure_equals("Empty rect top", rect1.mTop, 0); - ensure_equals("Empty rect right", rect1.mRight, 0); - ensure_equals("Empty rect bottom", rect1.mBottom, 0); - ensure_equals("Empty rect width", rect1.getWidth(), 0); - ensure_equals("Empty rect height", rect1.getHeight(), 0); - ensure_equals("Empty rect centerx", rect1.getCenterX(), 0); - ensure_equals("Empty rect centery", rect1.getCenterY(), 0); + UNSCOPED_INFO("Empty rect"); + CHECK_THAT(rect1.getValue(), ll_test::LLSDEquals(zero)); + UNSCOPED_INFO("Empty rect left"); + CHECK(rect1.mLeft == 0); + UNSCOPED_INFO("Empty rect top"); + CHECK(rect1.mTop == 0); + UNSCOPED_INFO("Empty rect right"); + CHECK(rect1.mRight == 0); + UNSCOPED_INFO("Empty rect bottom"); + CHECK(rect1.mBottom == 0); + UNSCOPED_INFO("Empty rect width"); + CHECK(rect1.getWidth() == 0); + UNSCOPED_INFO("Empty rect height"); + CHECK(rect1.getHeight() == 0); + UNSCOPED_INFO("Empty rect centerx"); + CHECK(rect1.getCenterX() == 0); + UNSCOPED_INFO("Empty rect centery"); + CHECK(rect1.getCenterY() == 0); } - template<> template<> - void object::test<2>() + TEST_CASE("LLRect: LLRectf default constructor gives an all-zero rect", "[LLRect]") { // // test the LLRectf default constructor @@ -84,55 +78,77 @@ namespace tut zerof.append(0.0f); zerof.append(0.0f); zerof.append(0.0f); zerof.append(0.0f); LLRectf rect2; - ensure_equals("Empty rectf", rect2.getValue(), zerof); - ensure_equals("Empty rectf left", rect2.mLeft, 0.0f); - ensure_equals("Empty rectf top", rect2.mTop, 0.0f); - ensure_equals("Empty rectf right", rect2.mRight, 0.0f); - ensure_equals("Empty rectf bottom", rect2.mBottom, 0.0f); - ensure_equals("Empty rectf width", rect2.getWidth(), 0.0f); - ensure_equals("Empty rectf height", rect2.getHeight(), 0.0f); - ensure_equals("Empty rectf centerx", rect2.getCenterX(), 0.0f); - ensure_equals("Empty rectf centery", rect2.getCenterY(), 0.0f); + UNSCOPED_INFO("Empty rectf"); + CHECK_THAT(rect2.getValue(), ll_test::LLSDEquals(zerof)); + UNSCOPED_INFO("Empty rectf left"); + CHECK(rect2.mLeft == 0.0f); + UNSCOPED_INFO("Empty rectf top"); + CHECK(rect2.mTop == 0.0f); + UNSCOPED_INFO("Empty rectf right"); + CHECK(rect2.mRight == 0.0f); + UNSCOPED_INFO("Empty rectf bottom"); + CHECK(rect2.mBottom == 0.0f); + UNSCOPED_INFO("Empty rectf width"); + CHECK(rect2.getWidth() == 0.0f); + UNSCOPED_INFO("Empty rectf height"); + CHECK(rect2.getHeight() == 0.0f); + UNSCOPED_INFO("Empty rectf centerx"); + CHECK(rect2.getCenterX() == 0.0f); + UNSCOPED_INFO("Empty rectf centery"); + CHECK(rect2.getCenterY() == 0.0f); } - template<> template<> - void object::test<3>() + TEST_CASE("LLRect: constructor from another LLRect copies its edges", "[LLRect]") { // // test the LLRect constructor from another LLRect // LLRect rect3(LLRect(1, 6, 7, 2)); - ensure_equals("Default rect left", rect3.mLeft, 1); - ensure_equals("Default rect top", rect3.mTop, 6); - ensure_equals("Default rect right", rect3.mRight, 7); - ensure_equals("Default rect bottom", rect3.mBottom, 2); - ensure_equals("Default rect width", rect3.getWidth(), 6); - ensure_equals("Default rect height", rect3.getHeight(), 4); - ensure_equals("Default rect centerx", rect3.getCenterX(), 4); - ensure_equals("Default rect centery", rect3.getCenterY(), 4); + UNSCOPED_INFO("Default rect left"); + CHECK(rect3.mLeft == 1); + UNSCOPED_INFO("Default rect top"); + CHECK(rect3.mTop == 6); + UNSCOPED_INFO("Default rect right"); + CHECK(rect3.mRight == 7); + UNSCOPED_INFO("Default rect bottom"); + CHECK(rect3.mBottom == 2); + UNSCOPED_INFO("Default rect width"); + CHECK(rect3.getWidth() == 6); + UNSCOPED_INFO("Default rect height"); + CHECK(rect3.getHeight() == 4); + UNSCOPED_INFO("Default rect centerx"); + CHECK(rect3.getCenterX() == 4); + UNSCOPED_INFO("Default rect centery"); + CHECK(rect3.getCenterY() == 4); } - template<> template<> - void object::test<4>() + TEST_CASE("LLRect: LLRectf four-float constructor sets edges and size", "[LLRect]") { // // test the LLRectf four-float constructor // LLRectf rect4(1.0f, 5.0f, 6.0f, 2.0f); - ensure_equals("Default rectf left", rect4.mLeft, 1.0f); - ensure_equals("Default rectf top", rect4.mTop, 5.0f); - ensure_equals("Default rectf right", rect4.mRight, 6.0f); - ensure_equals("Default rectf bottom", rect4.mBottom, 2.0f); - ensure_equals("Default rectf width", rect4.getWidth(), 5.0f); - ensure_equals("Default rectf height", rect4.getHeight(), 3.0f); - ensure_equals("Default rectf centerx", rect4.getCenterX(), 3.5f); - ensure_equals("Default rectf centery", rect4.getCenterY(), 3.5f); + UNSCOPED_INFO("Default rectf left"); + CHECK(rect4.mLeft == 1.0f); + UNSCOPED_INFO("Default rectf top"); + CHECK(rect4.mTop == 5.0f); + UNSCOPED_INFO("Default rectf right"); + CHECK(rect4.mRight == 6.0f); + UNSCOPED_INFO("Default rectf bottom"); + CHECK(rect4.mBottom == 2.0f); + UNSCOPED_INFO("Default rectf width"); + CHECK(rect4.getWidth() == 5.0f); + UNSCOPED_INFO("Default rectf height"); + CHECK(rect4.getHeight() == 3.0f); + UNSCOPED_INFO("Default rectf centerx"); + CHECK(rect4.getCenterX() == 3.5f); + UNSCOPED_INFO("Default rectf centery"); + CHECK(rect4.getCenterY() == 3.5f); } - template<> template<> - void object::test<5>() + TEST_CASE("LLRect: LLRectf LLSD constructor sets edges and size", "[LLRect]") { // // test the LLRectf LLSD constructor @@ -141,18 +157,25 @@ namespace tut LLSD array; array.append(-1.0f); array.append(0.0f); array.append(0.0f); array.append(-1.0f); LLRectf rect5(array); - ensure_equals("LLSD rectf left", rect5.mLeft, -1.0f); - ensure_equals("LLSD rectf top", rect5.mTop, 0.0f); - ensure_equals("LLSD rectf right", rect5.mRight, 0.0f); - ensure_equals("LLSD rectf bottom", rect5.mBottom, -1.0f); - ensure_equals("LLSD rectf width", rect5.getWidth(), 1.0f); - ensure_equals("LLSD rectf height", rect5.getHeight(), 1.0f); - ensure_equals("LLSD rectf centerx", rect5.getCenterX(), -0.5f); - ensure_equals("LLSD rectf centery", rect5.getCenterY(), -0.5f); + UNSCOPED_INFO("LLSD rectf left"); + CHECK(rect5.mLeft == -1.0f); + UNSCOPED_INFO("LLSD rectf top"); + CHECK(rect5.mTop == 0.0f); + UNSCOPED_INFO("LLSD rectf right"); + CHECK(rect5.mRight == 0.0f); + UNSCOPED_INFO("LLSD rectf bottom"); + CHECK(rect5.mBottom == -1.0f); + UNSCOPED_INFO("LLSD rectf width"); + CHECK(rect5.getWidth() == 1.0f); + UNSCOPED_INFO("LLSD rectf height"); + CHECK(rect5.getHeight() == 1.0f); + UNSCOPED_INFO("LLSD rectf centerx"); + CHECK(rect5.getCenterX() == -0.5f); + UNSCOPED_INFO("LLSD rectf centery"); + CHECK(rect5.getCenterY() == -0.5f); } - template<> template<> - void object::test<6>() + TEST_CASE("LLRect: edges set directly give the matching size and center", "[LLRect]") { // // test directly setting the member variables for dimensions @@ -164,18 +187,25 @@ namespace tut rectf.mTop = 1.0f; rectf.mRight = 1.0f; rectf.mBottom = -1.0f; - ensure_equals("Member-set rectf left", rectf.mLeft, -1.0f); - ensure_equals("Member-set rectf top", rectf.mTop, 1.0f); - ensure_equals("Member-set rectf right", rectf.mRight, 1.0f); - ensure_equals("Member-set rectf bottom", rectf.mBottom, -1.0f); - ensure_equals("Member-set rectf width", rectf.getWidth(), 2.0f); - ensure_equals("Member-set rectf height", rectf.getHeight(), 2.0f); - ensure_equals("Member-set rectf centerx", rectf.getCenterX(), 0.0f); - ensure_equals("Member-set rectf centery", rectf.getCenterY(), 0.0f); + UNSCOPED_INFO("Member-set rectf left"); + CHECK(rectf.mLeft == -1.0f); + UNSCOPED_INFO("Member-set rectf top"); + CHECK(rectf.mTop == 1.0f); + UNSCOPED_INFO("Member-set rectf right"); + CHECK(rectf.mRight == 1.0f); + UNSCOPED_INFO("Member-set rectf bottom"); + CHECK(rectf.mBottom == -1.0f); + UNSCOPED_INFO("Member-set rectf width"); + CHECK(rectf.getWidth() == 2.0f); + UNSCOPED_INFO("Member-set rectf height"); + CHECK(rectf.getHeight() == 2.0f); + UNSCOPED_INFO("Member-set rectf centerx"); + CHECK(rectf.getCenterX() == 0.0f); + UNSCOPED_INFO("Member-set rectf centery"); + CHECK(rectf.getCenterY() == 0.0f); } - template<> template<> - void object::test<7>() + TEST_CASE("LLRect: setValue() reads edges from LLSD", "[LLRect]") { // // test the setValue() method @@ -186,18 +216,25 @@ namespace tut LLSD array; array.append(-1.0f); array.append(0.0f); array.append(0.0f); array.append(-1.0f); rectf.setValue(array); - ensure_equals("setValue() rectf left", rectf.mLeft, -1.0f); - ensure_equals("setValue() rectf top", rectf.mTop, 0.0f); - ensure_equals("setValue() rectf right", rectf.mRight, 0.0f); - ensure_equals("setValue() rectf bottom", rectf.mBottom, -1.0f); - ensure_equals("setValue() rectf width", rectf.getWidth(), 1.0f); - ensure_equals("setValue() rectf height", rectf.getHeight(), 1.0f); - ensure_equals("setValue() rectf centerx", rectf.getCenterX(), -0.5f); - ensure_equals("setValue() rectf centery", rectf.getCenterY(), -0.5f); + UNSCOPED_INFO("setValue() rectf left"); + CHECK(rectf.mLeft == -1.0f); + UNSCOPED_INFO("setValue() rectf top"); + CHECK(rectf.mTop == 0.0f); + UNSCOPED_INFO("setValue() rectf right"); + CHECK(rectf.mRight == 0.0f); + UNSCOPED_INFO("setValue() rectf bottom"); + CHECK(rectf.mBottom == -1.0f); + UNSCOPED_INFO("setValue() rectf width"); + CHECK(rectf.getWidth() == 1.0f); + UNSCOPED_INFO("setValue() rectf height"); + CHECK(rectf.getHeight() == 1.0f); + UNSCOPED_INFO("setValue() rectf centerx"); + CHECK(rectf.getCenterX() == -0.5f); + UNSCOPED_INFO("setValue() rectf centery"); + CHECK(rectf.getCenterY() == -0.5f); } - template<> template<> - void object::test<8>() + TEST_CASE("LLRect: set() sets edges and size", "[LLRect]") { // // test the set() method @@ -206,18 +243,25 @@ namespace tut LLRect rect; rect.set(10, 90, 70, 10); - ensure_equals("set() rectf left", rect.mLeft, 10); - ensure_equals("set() rectf top", rect.mTop, 90); - ensure_equals("set() rectf right", rect.mRight, 70); - ensure_equals("set() rectf bottom", rect.mBottom, 10); - ensure_equals("set() rectf width", rect.getWidth(), 60); - ensure_equals("set() rectf height", rect.getHeight(), 80); - ensure_equals("set() rectf centerx", rect.getCenterX(), 40); - ensure_equals("set() rectf centery", rect.getCenterY(), 50); + UNSCOPED_INFO("set() rectf left"); + CHECK(rect.mLeft == 10); + UNSCOPED_INFO("set() rectf top"); + CHECK(rect.mTop == 90); + UNSCOPED_INFO("set() rectf right"); + CHECK(rect.mRight == 70); + UNSCOPED_INFO("set() rectf bottom"); + CHECK(rect.mBottom == 10); + UNSCOPED_INFO("set() rectf width"); + CHECK(rect.getWidth() == 60); + UNSCOPED_INFO("set() rectf height"); + CHECK(rect.getHeight() == 80); + UNSCOPED_INFO("set() rectf centerx"); + CHECK(rect.getCenterX() == 40); + UNSCOPED_INFO("set() rectf centery"); + CHECK(rect.getCenterY() == 50); } - template<> template<> - void object::test<9>() + TEST_CASE("LLRect: setOriginAndSize() grows up and right from the origin", "[LLRect]") { // // test the setOriginAndSize() method @@ -226,18 +270,25 @@ namespace tut LLRectf rectf; rectf.setOriginAndSize(0.0f, 0.0f, 2.0f, 1.0f); - ensure_equals("setOriginAndSize() rectf left", rectf.mLeft, 0.0f); - ensure_equals("setOriginAndSize() rectf top", rectf.mTop, 1.0f); - ensure_equals("setOriginAndSize() rectf right", rectf.mRight, 2.0f); - ensure_equals("setOriginAndSize() rectf bottom", rectf.mBottom, 0.0f); - ensure_equals("setOriginAndSize() rectf width", rectf.getWidth(), 2.0f); - ensure_equals("setOriginAndSize() rectf height", rectf.getHeight(), 1.0f); - ensure_equals("setOriginAndSize() rectf centerx", rectf.getCenterX(), 1.0f); - ensure_equals("setOriginAndSize() rectf centery", rectf.getCenterY(), 0.5f); + UNSCOPED_INFO("setOriginAndSize() rectf left"); + CHECK(rectf.mLeft == 0.0f); + UNSCOPED_INFO("setOriginAndSize() rectf top"); + CHECK(rectf.mTop == 1.0f); + UNSCOPED_INFO("setOriginAndSize() rectf right"); + CHECK(rectf.mRight == 2.0f); + UNSCOPED_INFO("setOriginAndSize() rectf bottom"); + CHECK(rectf.mBottom == 0.0f); + UNSCOPED_INFO("setOriginAndSize() rectf width"); + CHECK(rectf.getWidth() == 2.0f); + UNSCOPED_INFO("setOriginAndSize() rectf height"); + CHECK(rectf.getHeight() == 1.0f); + UNSCOPED_INFO("setOriginAndSize() rectf centerx"); + CHECK(rectf.getCenterX() == 1.0f); + UNSCOPED_INFO("setOriginAndSize() rectf centery"); + CHECK(rectf.getCenterY() == 0.5f); } - template<> template<> - void object::test<10>() + TEST_CASE("LLRect: setLeftTopAndSize() grows down and right from the top-left", "[LLRect]") { // // test the setLeftTopAndSize() method @@ -246,18 +297,25 @@ namespace tut LLRectf rectf; rectf.setLeftTopAndSize(0.0f, 0.0f, 2.0f, 1.0f); - ensure_equals("setLeftTopAndSize() rectf left", rectf.mLeft, 0.0f); - ensure_equals("setLeftTopAndSize() rectf top", rectf.mTop, 0.0f); - ensure_equals("setLeftTopAndSize() rectf right", rectf.mRight, 2.0f); - ensure_equals("setLeftTopAndSize() rectf bottom", rectf.mBottom, -1.0f); - ensure_equals("setLeftTopAndSize() rectf width", rectf.getWidth(), 2.0f); - ensure_equals("setLeftTopAndSize() rectf height", rectf.getHeight(), 1.0f); - ensure_equals("setLeftTopAndSize() rectf centerx", rectf.getCenterX(), 1.0f); - ensure_equals("setLeftTopAndSize() rectf centery", rectf.getCenterY(), -0.5f); + UNSCOPED_INFO("setLeftTopAndSize() rectf left"); + CHECK(rectf.mLeft == 0.0f); + UNSCOPED_INFO("setLeftTopAndSize() rectf top"); + CHECK(rectf.mTop == 0.0f); + UNSCOPED_INFO("setLeftTopAndSize() rectf right"); + CHECK(rectf.mRight == 2.0f); + UNSCOPED_INFO("setLeftTopAndSize() rectf bottom"); + CHECK(rectf.mBottom == -1.0f); + UNSCOPED_INFO("setLeftTopAndSize() rectf width"); + CHECK(rectf.getWidth() == 2.0f); + UNSCOPED_INFO("setLeftTopAndSize() rectf height"); + CHECK(rectf.getHeight() == 1.0f); + UNSCOPED_INFO("setLeftTopAndSize() rectf centerx"); + CHECK(rectf.getCenterX() == 1.0f); + UNSCOPED_INFO("setLeftTopAndSize() rectf centery"); + CHECK(rectf.getCenterY() == -0.5f); } - template<> template<> - void object::test<11>() + TEST_CASE("LLRect: setCenterAndSize() spreads the size about the center", "[LLRect]") { // // test the setCenterAndSize() method @@ -266,18 +324,25 @@ namespace tut LLRectf rectf; rectf.setCenterAndSize(0.0f, 0.0f, 2.0f, 1.0f); - ensure_equals("setCenterAndSize() rectf left", rectf.mLeft, -1.0f); - ensure_equals("setCenterAndSize() rectf top", rectf.mTop, 0.5f); - ensure_equals("setCenterAndSize() rectf right", rectf.mRight, 1.0f); - ensure_equals("setCenterAndSize() rectf bottom", rectf.mBottom, -0.5f); - ensure_equals("setCenterAndSize() rectf width", rectf.getWidth(), 2.0f); - ensure_equals("setCenterAndSize() rectf height", rectf.getHeight(), 1.0f); - ensure_equals("setCenterAndSize() rectf centerx", rectf.getCenterX(), 0.0f); - ensure_equals("setCenterAndSize() rectf centery", rectf.getCenterY(), 0.0f); + UNSCOPED_INFO("setCenterAndSize() rectf left"); + CHECK(rectf.mLeft == -1.0f); + UNSCOPED_INFO("setCenterAndSize() rectf top"); + CHECK(rectf.mTop == 0.5f); + UNSCOPED_INFO("setCenterAndSize() rectf right"); + CHECK(rectf.mRight == 1.0f); + UNSCOPED_INFO("setCenterAndSize() rectf bottom"); + CHECK(rectf.mBottom == -0.5f); + UNSCOPED_INFO("setCenterAndSize() rectf width"); + CHECK(rectf.getWidth() == 2.0f); + UNSCOPED_INFO("setCenterAndSize() rectf height"); + CHECK(rectf.getHeight() == 1.0f); + UNSCOPED_INFO("setCenterAndSize() rectf centerx"); + CHECK(rectf.getCenterX() == 0.0f); + UNSCOPED_INFO("setCenterAndSize() rectf centery"); + CHECK(rectf.getCenterY() == 0.0f); } - template<> template<> - void object::test<12>() + TEST_CASE("LLRect: isValid() fails for an inverted rect until makeValid()", "[LLRect]") { // // test the validity checking method @@ -286,20 +351,23 @@ namespace tut LLRectf rectf; rectf.set(-1.0f, 1.0f, 1.0f, -1.0f); - ensure("BBox is valid", rectf.isValid()); + UNSCOPED_INFO("BBox is valid"); + CHECK(rectf.isValid()); rectf.mLeft = 2.0f; - ensure("BBox is not valid", ! rectf.isValid()); + UNSCOPED_INFO("BBox is not valid"); + CHECK_FALSE(rectf.isValid()); rectf.makeValid(); - ensure("BBox forced valid", rectf.isValid()); + UNSCOPED_INFO("BBox forced valid"); + CHECK(rectf.isValid()); rectf.set(-1.0f, -1.0f, -1.0f, -1.0f); - ensure("BBox(0,0,0,0) is valid", rectf.isValid()); + UNSCOPED_INFO("BBox(0,0,0,0) is valid"); + CHECK(rectf.isValid()); } - template<> template<> - void object::test<13>() + TEST_CASE("LLRect: isEmpty() and notEmpty() on full and zero-size rects", "[LLRect]") { // // test the null checking methods @@ -308,19 +376,22 @@ namespace tut LLRectf rectf; rectf.set(-1.0f, 1.0f, 1.0f, -1.0f); - ensure("BBox is not Null", ! rectf.isEmpty()); - ensure("BBox notNull", rectf.notEmpty()); + UNSCOPED_INFO("BBox is not Null"); + CHECK_FALSE(rectf.isEmpty()); + UNSCOPED_INFO("BBox notNull"); + CHECK(rectf.notEmpty()); rectf.mLeft = 2.0f; rectf.makeValid(); - ensure("BBox is now Null", rectf.isEmpty()); + UNSCOPED_INFO("BBox is now Null"); + CHECK(rectf.isEmpty()); rectf.set(-1.0f, -1.0f, -1.0f, -1.0f); - ensure("BBox(0,0,0,0) is Null", rectf.isEmpty()); + UNSCOPED_INFO("BBox(0,0,0,0) is Null"); + CHECK(rectf.isEmpty()); } - template<> template<> - void object::test<14>() + TEST_CASE("LLRect: equality and inequality operators", "[LLRect]") { // // test the (in)equality operators @@ -331,15 +402,18 @@ namespace tut rect1.set(-1.0f, 1.0f, 1.0f, -1.0f); rect2.set(-1.0f, 0.9f, 1.0f, -1.0f); - ensure("rect1 == rect2 (false)", ! (rect1 == rect2)); - ensure("rect1 != rect2 (true)", rect1 != rect2); + UNSCOPED_INFO("rect1 == rect2 (false)"); + CHECK_FALSE(rect1 == rect2); + UNSCOPED_INFO("rect1 != rect2 (true)"); + CHECK(rect1 != rect2); - ensure("rect1 == rect1 (true)", rect1 == rect1); - ensure("rect1 != rect1 (false)", ! (rect1 != rect1)); + UNSCOPED_INFO("rect1 == rect1 (true)"); + CHECK(rect1 == rect1); + UNSCOPED_INFO("rect1 != rect1 (false)"); + CHECK_FALSE(rect1 != rect1); } - template<> template<> - void object::test<15>() + TEST_CASE("LLRect: copy constructor gives an equal rect", "[LLRect]") { // // test the copy constructor @@ -347,12 +421,13 @@ namespace tut LLRectf rect1, rect2(rect1); - ensure("rect1 == rect2 (true)", rect1 == rect2); - ensure("rect1 != rect2 (false)", ! (rect1 != rect2)); + UNSCOPED_INFO("rect1 == rect2 (true)"); + CHECK(rect1 == rect2); + UNSCOPED_INFO("rect1 != rect2 (false)"); + CHECK_FALSE(rect1 != rect2); } - template<> template<> - void object::test<16>() + TEST_CASE("LLRect: translate() moves all four edges", "[LLRect]") { // // test the translate() method @@ -363,28 +438,32 @@ namespace tut rect1.translate(0.0f, 0.0f); - ensure("translate(0, 0)", rect1 == rect2); + UNSCOPED_INFO("translate(0, 0)"); + CHECK(rect1 == rect2); rect1.translate(100.0f, 100.0f); rect1.translate(-100.0f, -100.0f); - ensure("translate(100, 100) + translate(-100, -100)", rect1 == rect2); + UNSCOPED_INFO("translate(100, 100) + translate(-100, -100)"); + CHECK(rect1 == rect2); rect1.translate(10.0f, 0.0f); rect2.set(9.0f, 1.0f, 11.0f, -1.0f); - ensure("translate(10, 0)", rect1 == rect2); + UNSCOPED_INFO("translate(10, 0)"); + CHECK(rect1 == rect2); rect1.translate(0.0f, 10.0f); rect2.set(9.0f, 11.0f, 11.0f, 9.0f); - ensure("translate(0, 10)", rect1 == rect2); + UNSCOPED_INFO("translate(0, 10)"); + CHECK(rect1 == rect2); rect1.translate(-10.0f, -10.0f); rect2.set(-1.0f, 1.0f, 1.0f, -1.0f); - ensure("translate(-10, -10)", rect1 == rect2); + UNSCOPED_INFO("translate(-10, -10)"); + CHECK(rect1 == rect2); } - template<> template<> - void object::test<17>() + TEST_CASE("LLRect: stretch() moves the edges outward", "[LLRect]") { // // test the stretch() method @@ -394,23 +473,26 @@ namespace tut LLRectf rect2(rect1); rect1.stretch(0.0f); - ensure("stretch(0)", rect1 == rect2); + UNSCOPED_INFO("stretch(0)"); + CHECK(rect1 == rect2); rect1.stretch(0.0f, 0.0f); - ensure("stretch(0, 0)", rect1 == rect2); + UNSCOPED_INFO("stretch(0, 0)"); + CHECK(rect1 == rect2); rect1.stretch(10.0f); rect1.stretch(-10.0f); - ensure("stretch(10) + stretch(-10)", rect1 == rect2); + UNSCOPED_INFO("stretch(10) + stretch(-10)"); + CHECK(rect1 == rect2); rect1.stretch(2.0f, 1.0f); rect2.set(-3.0f, 2.0f, 3.0f, -2.0f); - ensure("stretch(2, 1)", rect1 == rect2); + UNSCOPED_INFO("stretch(2, 1)"); + CHECK(rect1 == rect2); } - template<> template<> - void object::test<18>() + TEST_CASE("LLRect: unionWith() covers both rects", "[LLRect]") { // // test the unionWith() method @@ -422,23 +504,25 @@ namespace tut rect2.set(-1.0f, 1.0f, 1.0f, -1.0f); rect3 = rect1; rect3.unionWith(rect2); - ensure_equals("union with self", rect3, rect1); + UNSCOPED_INFO("union with self"); + CHECK(rect3 == rect1); rect1.set(-1.0f, 1.0f, 1.0f, -1.0f); rect2.set(-2.0f, 2.0f, 0.0f, 0.0f); rect3 = rect1; rect3.unionWith(rect2); - ensure_equals("union - overlap", rect3, LLRectf(-2.0f, 2.0f, 1.0f, -1.0f)); + UNSCOPED_INFO("union - overlap"); + CHECK(rect3 == LLRectf(-2.0f, 2.0f, 1.0f, -1.0f)); rect1.set(-1.0f, 1.0f, 1.0f, -1.0f); rect2.set(5.0f, 10.0f, 10.0f, 5.0f); rect3 = rect1; rect3.unionWith(rect2); - ensure_equals("union - no overlap", rect3, LLRectf(-1.0f, 10.0f, 10.0f, -1.0f)); + UNSCOPED_INFO("union - no overlap"); + CHECK(rect3 == LLRectf(-1.0f, 10.0f, 10.0f, -1.0f)); } - template<> template<> - void object::test<19>() + TEST_CASE("LLRect: intersectWith() keeps only the overlap", "[LLRect]") { // // test the intersectWith() methods @@ -450,23 +534,25 @@ namespace tut rect2.set(-1.0f, 1.0f, 1.0f, -1.0f); rect3 = rect1; rect3.intersectWith(rect2); - ensure_equals("intersect with self", rect3, rect1); + UNSCOPED_INFO("intersect with self"); + CHECK(rect3 == rect1); rect1.set(-1.0f, 1.0f, 1.0f, -1.0f); rect2.set(-2.0f, 2.0f, 0.0f, 0.0f); rect3 = rect1; rect3.intersectWith(rect2); - ensure_equals("intersect - overlap", rect3, LLRectf(-1.0f, 1.0f, 0.0f, 0.0f)); + UNSCOPED_INFO("intersect - overlap"); + CHECK(rect3 == LLRectf(-1.0f, 1.0f, 0.0f, 0.0f)); rect1.set(-1.0f, 1.0f, 1.0f, -1.0f); rect2.set(5.0f, 10.0f, 10.0f, 5.0f); rect3 = rect1; rect3.intersectWith(rect2); - ensure("intersect - no overlap", rect3.isEmpty()); + UNSCOPED_INFO("intersect - no overlap"); + CHECK(rect3.isEmpty()); } - template<> template<> - void object::test<20>() + TEST_CASE("LLRect: pointInRect() includes the left and bottom edges only", "[LLRect]") { // // test the pointInRect() method @@ -474,17 +560,23 @@ namespace tut LLRectf rect(1.0f, 3.0f, 3.0f, 1.0f); - ensure("(0,0) not in rect", rect.pointInRect(0.0f, 0.0f) == false); - ensure("(2,2) in rect", rect.pointInRect(2.0f, 2.0f) == true); - ensure("(1,1) in rect", rect.pointInRect(1.0f, 1.0f) == true); - ensure("(3,3) not in rect", rect.pointInRect(3.0f, 3.0f) == false); - ensure("(2.999,2.999) in rect", rect.pointInRect(2.999f, 2.999f) == true); - ensure("(2.999,3.0) not in rect", rect.pointInRect(2.999f, 3.0f) == false); - ensure("(3.0,2.999) not in rect", rect.pointInRect(3.0f, 2.999f) == false); + UNSCOPED_INFO("(0,0) not in rect"); + CHECK_FALSE(rect.pointInRect(0.0f, 0.0f)); + UNSCOPED_INFO("(2,2) in rect"); + CHECK(rect.pointInRect(2.0f, 2.0f)); + UNSCOPED_INFO("(1,1) in rect"); + CHECK(rect.pointInRect(1.0f, 1.0f)); + UNSCOPED_INFO("(3,3) not in rect"); + CHECK_FALSE(rect.pointInRect(3.0f, 3.0f)); + UNSCOPED_INFO("(2.999,2.999) in rect"); + CHECK(rect.pointInRect(2.999f, 2.999f)); + UNSCOPED_INFO("(2.999,3.0) not in rect"); + CHECK_FALSE(rect.pointInRect(2.999f, 3.0f)); + UNSCOPED_INFO("(3.0,2.999) not in rect"); + CHECK_FALSE(rect.pointInRect(3.0f, 2.999f)); } - template<> template<> - void object::test<21>() + TEST_CASE("LLRect: localPointInRect() measures from the bottom-left corner", "[LLRect]") { // // test the localPointInRect() method @@ -492,17 +584,23 @@ namespace tut LLRectf rect(1.0f, 3.0f, 3.0f, 1.0f); - ensure("(0,0) in local rect", rect.localPointInRect(0.0f, 0.0f) == true); - ensure("(-0.0001,-0.0001) not in local rect", rect.localPointInRect(-0.0001f, -0.001f) == false); - ensure("(1,1) in local rect", rect.localPointInRect(1.0f, 1.0f) == true); - ensure("(2,2) not in local rect", rect.localPointInRect(2.0f, 2.0f) == false); - ensure("(1.999,1.999) in local rect", rect.localPointInRect(1.999f, 1.999f) == true); - ensure("(1.999,2.0) not in local rect", rect.localPointInRect(1.999f, 2.0f) == false); - ensure("(2.0,1.999) not in local rect", rect.localPointInRect(2.0f, 1.999f) == false); + UNSCOPED_INFO("(0,0) in local rect"); + CHECK(rect.localPointInRect(0.0f, 0.0f)); + UNSCOPED_INFO("(-0.0001,-0.0001) not in local rect"); + CHECK_FALSE(rect.localPointInRect(-0.0001f, -0.001f)); + UNSCOPED_INFO("(1,1) in local rect"); + CHECK(rect.localPointInRect(1.0f, 1.0f)); + UNSCOPED_INFO("(2,2) not in local rect"); + CHECK_FALSE(rect.localPointInRect(2.0f, 2.0f)); + UNSCOPED_INFO("(1.999,1.999) in local rect"); + CHECK(rect.localPointInRect(1.999f, 1.999f)); + UNSCOPED_INFO("(1.999,2.0) not in local rect"); + CHECK_FALSE(rect.localPointInRect(1.999f, 2.0f)); + UNSCOPED_INFO("(2.0,1.999) not in local rect"); + CHECK_FALSE(rect.localPointInRect(2.0f, 1.999f)); } - template<> template<> - void object::test<22>() + TEST_CASE("LLRect: clampPointToRect() clamps to the edges", "[LLRect]") { // // test the clampPointToRect() method @@ -513,17 +611,23 @@ namespace tut x = 2.0f; y = 2.0f; rect.clampPointToRect(x, y); - ensure_equals("clamp x-coord within rect", x, 2.0f); - ensure_equals("clamp y-coord within rect", y, 2.0f); + UNSCOPED_INFO("clamp x-coord within rect"); + CHECK(x == 2.0f); + UNSCOPED_INFO("clamp y-coord within rect"); + CHECK(y == 2.0f); x = -100.0f; y = 100.0f; rect.clampPointToRect(x, y); - ensure_equals("clamp x-coord outside rect", x, 1.0f); - ensure_equals("clamp y-coord outside rect", y, 3.0f); + UNSCOPED_INFO("clamp x-coord outside rect"); + CHECK(x == 1.0f); + UNSCOPED_INFO("clamp y-coord outside rect"); + CHECK(y == 3.0f); x = 3.0f; y = 1.0f; rect.clampPointToRect(x, y); - ensure_equals("clamp x-coord edge rect", x, 3.0f); - ensure_equals("clamp y-coord edge rect", y, 1.0f); + UNSCOPED_INFO("clamp x-coord edge rect"); + CHECK(x == 3.0f); + UNSCOPED_INFO("clamp y-coord edge rect"); + CHECK(y == 1.0f); } } diff --git a/indra/llmath/tests/llsdutil_math_test.cpp b/indra/llmath/tests/llsdutil_math_test.cpp index 8c79d002da..89154a480b 100644 --- a/indra/llmath/tests/llsdutil_math_test.cpp +++ b/indra/llmath/tests/llsdutil_math_test.cpp @@ -27,7 +27,8 @@ */ #include "linden_common.h" -#include "lltut.h" +#include +#include "altestmatchers.h" #include "m4math.h" #include "v2math.h" #include "v2math.h" @@ -43,8 +44,6 @@ #include #include -namespace tut -{ namespace { struct llsdutil_math_data @@ -64,7 +63,8 @@ namespace // This test is supposed to succeed. Comparing to the // empty string ensures that if the test fails, it will // display the string received so we can tell what failed. - ensure_equals("match", match, ""); + UNSCOPED_INFO("match"); + REQUIRE(match == ""); } else { @@ -72,85 +72,88 @@ namespace // the string 'match' will be empty, which doesn't tell us // much about which case went awry. So construct a more // detailed description string. - ensure(proto_key + " shouldn't match " + pi->first, ! match.empty()); + UNSCOPED_INFO(proto_key + " shouldn't match " + pi->first); + REQUIRE_FALSE(match.empty()); } } } }; - typedef test_group llsdutil_math_test; - typedef llsdutil_math_test::object llsdutil_math_object; - tut::llsdutil_math_test llsdutil_math_testcase("llsdutil_math"); -} - template<> template<> - void llsdutil_math_object::test<1>() + TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLVector3 round-trips through LLSD", "[llsdutil_math]") { LLSD sd; LLVector3 vec1(-1.0, 2.0, -3.0); sd = ll_sd_from_vector3(vec1); LLVector3 vec2 = ll_vector3_from_sd(sd); - ensure_equals("vector3 -> sd -> vector3: 1", vec1, vec2); + UNSCOPED_INFO("vector3 -> sd -> vector3: 1"); + CHECK(vec1 == vec2); LLVector3 vec3(sd); - ensure_equals("vector3 -> sd -> vector3: 2", vec1, vec3); + UNSCOPED_INFO("vector3 -> sd -> vector3: 2"); + CHECK(vec1 == vec3); sd.clear(); vec1.setVec(0., 0., 0.); sd = ll_sd_from_vector3(vec1); vec2 = ll_vector3_from_sd(sd); - ensure_equals("vector3 -> sd -> vector3: 3", vec1, vec2); + UNSCOPED_INFO("vector3 -> sd -> vector3: 3"); + CHECK(vec1 == vec2); sd.clear(); } - template<> template<> - void llsdutil_math_object::test<2>() + TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLVector3d round-trips through LLSD", "[llsdutil_math]") { LLSD sd; LLVector3d vec1((F64)(U64L(0xFEDCBA9876543210) << 2), -1., 0); sd = ll_sd_from_vector3d(vec1); LLVector3d vec2 = ll_vector3d_from_sd(sd); - ensure_equals("vector3d -> sd -> vector3d: 1", vec1, vec2); + UNSCOPED_INFO("vector3d -> sd -> vector3d: 1"); + CHECK(vec1 == vec2); LLVector3d vec3(sd); - ensure_equals("vector3d -> sd -> vector3d : 2", vec1, vec3); + UNSCOPED_INFO("vector3d -> sd -> vector3d : 2"); + CHECK(vec1 == vec3); } - template<> template<> - void llsdutil_math_object::test<3>() + TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLVector2 and its LLSD round-trip both ways", "[llsdutil_math]") { LLSD sd; LLVector2 vec((F32) -3., (F32) 4.2); sd = ll_sd_from_vector2(vec); LLVector2 vec1 = ll_vector2_from_sd(sd); - ensure_equals("vector2 -> sd -> vector2", vec, vec1); + UNSCOPED_INFO("vector2 -> sd -> vector2"); + CHECK(vec == vec1); LLSD sd2 = ll_sd_from_vector2(vec1); - ensure_equals("sd -> vector2 -> sd: 2", sd, sd2); + UNSCOPED_INFO("sd -> vector2 -> sd: 2"); + CHECK_THAT(sd, ll_test::LLSDEquals(sd2)); } - template<> template<> - void llsdutil_math_object::test<4>() + TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLQuaternion and its LLSD round-trip both ways", "[llsdutil_math]") { LLSD sd; LLQuaternion quat((F32) 1., (F32) -0.98, (F32) 2.3, (F32) 0xffff); sd = ll_sd_from_quaternion(quat); LLQuaternion quat1 = ll_quaternion_from_sd(sd); - ensure_equals("LLQuaternion -> sd -> LLQuaternion", quat, quat1); + UNSCOPED_INFO("LLQuaternion -> sd -> LLQuaternion"); + CHECK(quat == quat1); LLSD sd2 = ll_sd_from_quaternion(quat1); - ensure_equals("sd -> LLQuaternion -> sd ", sd, sd2); + UNSCOPED_INFO("sd -> LLQuaternion -> sd "); + CHECK_THAT(sd, ll_test::LLSDEquals(sd2)); } - template<> template<> - void llsdutil_math_object::test<5>() + TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLColor4 and its LLSD round-trip both ways", "[llsdutil_math]") { LLSD sd; LLColor4 c(1.0f, 2.2f, 4.0f, 7.f); sd = ll_sd_from_color4(c); LLColor4 c1 =ll_color4_from_sd(sd); - ensure_equals("LLColor4 -> sd -> LLColor4", c, c1); + UNSCOPED_INFO("LLColor4 -> sd -> LLColor4"); + CHECK(c == c1); LLSD sd1 = ll_sd_from_color4(c1); - ensure_equals("sd -> LLColor4 -> sd", sd, sd1); + UNSCOPED_INFO("sd -> LLColor4 -> sd"); + CHECK_THAT(sd, ll_test::LLSDEquals(sd1)); } } diff --git a/indra/llmath/tests/llvector4a_test.cpp b/indra/llmath/tests/llvector4a_test.cpp index 18b34bb974..c46fffb594 100644 --- a/indra/llmath/tests/llvector4a_test.cpp +++ b/indra/llmath/tests/llvector4a_test.cpp @@ -33,7 +33,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llmath.h" #include "../llsimdmath.h" #include "../llvector4a.h" @@ -49,8 +49,6 @@ #include #include -namespace tut -{ namespace { U32 bits_of(F32 f) @@ -87,7 +85,8 @@ namespace const U32 expected[4] = {x, y, z, w}; for (int i = 0; i < 4; ++i) { - ensure_equals(what + " lane " + std::to_string(i), bits_of(got[i]), expected[i]); + UNSCOPED_INFO(what + " lane " + std::to_string(i)); + REQUIRE(bits_of(got[i]) == expected[i]); } } @@ -99,7 +98,8 @@ namespace void ensure_ulps(const std::string& what, F32 got, F32 expected, U32 ulps) { const U32 d = ulps_between(got, expected); - ensure(what + ": " + precise(got) + " is " + std::to_string(d) + " ulps from " + precise(expected), d <= ulps); + UNSCOPED_INFO(what + ": " + precise(got) + " is " + std::to_string(d) + " ulps from " + precise(expected)); + REQUIRE(d <= ulps); } void ensure_ulps(const std::string& what, const LLVector4a& got, const F32* expected, U32 ulps, int lanes = 4) @@ -113,7 +113,8 @@ namespace void ensure_relative(const std::string& what, F32 got, double expected, double relative) { const double err = std::fabs((double)got - expected) / std::fabs(expected); - ensure(what + ": " + precise(got) + " is off " + precise(err) + " relative from " + precise(expected), err <= relative); + UNSCOPED_INFO(what + ": " + precise(got) + " is off " + precise(err) + " relative from " + precise(expected)); + REQUIRE(err <= relative); } void ensure_relative(const std::string& what, const LLVector4a& got, const double* expected, double relative, int lanes = 4) @@ -126,9 +127,12 @@ namespace void ensure_same_lanes(const std::string& what, const LLVector4a& v) { - ensure_equals(what + " lane 1", bits_of(v[1]), bits_of(v[0])); - ensure_equals(what + " lane 2", bits_of(v[2]), bits_of(v[0])); - ensure_equals(what + " lane 3", bits_of(v[3]), bits_of(v[0])); + UNSCOPED_INFO(what + " lane 1"); + REQUIRE(bits_of(v[1]) == bits_of(v[0])); + UNSCOPED_INFO(what + " lane 2"); + REQUIRE(bits_of(v[2]) == bits_of(v[0])); + UNSCOPED_INFO(what + " lane 3"); + REQUIRE(bits_of(v[3]) == bits_of(v[0])); } // Scalar references, every intermediate rounded to float in a volatile @@ -155,11 +159,8 @@ namespace const F32 INF = std::numeric_limits::infinity(); const F32 NAN_QUIET = std::numeric_limits::quiet_NaN(); const F32 DENORMAL = std::numeric_limits::denorm_min(); -} -} // namespace tut +} // anonymous namespace -namespace tut -{ namespace { struct llvector4a_data @@ -180,29 +181,29 @@ namespace LLVector4a n_a() const { return LLVector4a(na[0], na[1], na[2], na[3]); } LLVector4a n_b() const { return LLVector4a(nb[0], nb[1], nb[2], nb[3]); } }; - typedef test_group llvector4a_test; - typedef llvector4a_test::object llvector4a_object; - tut::llvector4a_test llvector4a_testcase("LLVector4a"); -} // Plain data of one register, and the two constants. - template<> template<> - void llvector4a_object::test<1>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: plain data of one register and the two constants", "[LLVector4a]") { - ensure_equals("sizeof LLVector4a", sizeof(LLVector4a), (size_t)16); - ensure_equals("alignof LLVector4a", alignof(LLVector4a), (size_t)16); - ensure_equals("sizeof LLSimdScalar", sizeof(LLSimdScalar), (size_t)16); - ensure_equals("sizeof LLVector4Logical", sizeof(LLVector4Logical), (size_t)16); - ensure("trivially copyable", std::is_trivially_copyable::value); - ensure("standard layout", std::is_standard_layout::value); + UNSCOPED_INFO("sizeof LLVector4a"); + CHECK(sizeof(LLVector4a) == (size_t)16); + UNSCOPED_INFO("alignof LLVector4a"); + CHECK(alignof(LLVector4a) == (size_t)16); + UNSCOPED_INFO("sizeof LLSimdScalar"); + CHECK(sizeof(LLSimdScalar) == (size_t)16); + UNSCOPED_INFO("sizeof LLVector4Logical"); + CHECK(sizeof(LLVector4Logical) == (size_t)16); + UNSCOPED_INFO("trivially copyable"); + CHECK(std::is_trivially_copyable::value); + UNSCOPED_INFO("standard layout"); + CHECK(std::is_standard_layout::value); ensure_exact("zero", LLVector4a::getZero(), 0.f, 0.f, 0.f, 0.f); ensure_exact("epsilon", LLVector4a::getEpsilon(), F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO); } // Loads, stores, set, splat and the lane accessors move bits. - template<> template<> - void llvector4a_object::test<2>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: loads and stores and splats and the lane accessors move bits", "[LLVector4a]") { alignas(16) F32 src[5] = {float_of(pa[0]), float_of(pa[1]), float_of(pa[2]), float_of(pa[3]), 99.f}; @@ -218,14 +219,19 @@ namespace alignas(16) F32 dst[4] = {1.f, 1.f, 1.f, 1.f}; a().store4a(dst); - ensure_equals("store4a x", bits_of(dst[0]), pa[0]); - ensure_equals("store4a y", bits_of(dst[1]), pa[1]); - ensure_equals("store4a z", bits_of(dst[2]), pa[2]); - ensure_equals("store4a w", bits_of(dst[3]), pa[3]); + UNSCOPED_INFO("store4a x"); + CHECK(bits_of(dst[0]) == pa[0]); + UNSCOPED_INFO("store4a y"); + CHECK(bits_of(dst[1]) == pa[1]); + UNSCOPED_INFO("store4a z"); + CHECK(bits_of(dst[2]) == pa[2]); + UNSCOPED_INFO("store4a w"); + CHECK(bits_of(dst[3]) == pa[3]); alignas(16) F32 copied[4]; LLVector4a::copy4a(copied, src); - ensure_equals("copy4a", bits_of(copied[2]), pa[2]); + UNSCOPED_INFO("copy4a"); + CHECK(bits_of(copied[2]) == pa[2]); v.set(1.f, 2.f, 3.f); ensure_exact("set defaults w to zero", v, 1.f, 2.f, 3.f, 0.f); @@ -252,14 +258,18 @@ namespace ensure_exact("explicit LLVector4a(F32)", one, 1.f, 1.f, 1.f, 1.f); const LLVector4a from_a = a(); - ensure_equals("operator[] 2", bits_of(from_a[2]), pa[2]); - ensure_equals("getScalarAt<1>", bits_of(from_a.getScalarAt<1>().getF32()), pa[1]); - ensure_equals("getScalarAt(0)", bits_of(from_a.getScalarAt(0).getF32()), pa[0]); + UNSCOPED_INFO("operator[] 2"); + CHECK(bits_of(from_a[2]) == pa[2]); + UNSCOPED_INFO("getScalarAt<1>"); + CHECK(bits_of(from_a.getScalarAt<1>().getF32()) == pa[1]); + UNSCOPED_INFO("getScalarAt(0)"); + CHECK(bits_of(from_a.getScalarAt(0).getF32()) == pa[0]); LLVector4a written = a(); written.getF32ptr()[1] = 7.f; ensure_bits("getF32ptr writes through", written, pa[0], bits_of(7.f), pa[2], pa[3]); - ensure_equals("getF32ptr reads", bits_of(written.getF32ptr()[3]), pa[3]); + UNSCOPED_INFO("getF32ptr reads"); + CHECK(bits_of(written.getF32ptr()[3]) == pa[3]); LLVector4a copy; copy = (LLQuad)a(); @@ -267,8 +277,7 @@ namespace } // The mask type: compares, gathered bits, any/all, invert, the elements. - template<> template<> - void llvector4a_object::test<3>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: compare masks and their queries and setSelectWithMask", "[LLVector4a]") { // the NaN comes through a volatile so the fast floating-point // contract cannot fold a compare of a value with itself @@ -277,37 +286,57 @@ namespace const LLVector4a y(2.f, 2.f, 2.f, nan_lane); const LLVector4a y_again(2.f, 2.f, 2.f, nan_lane); - ensure_equals("greaterThan", x.greaterThan(y).getGatheredBits(), (U32)(LLVector4Logical::MASK_Z)); - ensure_equals("lessThan", x.lessThan(y).getGatheredBits(), (U32)(LLVector4Logical::MASK_X)); - ensure_equals("greaterEqual", x.greaterEqual(y).getGatheredBits(), (U32)(LLVector4Logical::MASK_Y | LLVector4Logical::MASK_Z)); - ensure_equals("lessEqual", x.lessEqual(y).getGatheredBits(), (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_Y)); - ensure_equals("equal", x.equal(y).getGatheredBits(), (U32)(LLVector4Logical::MASK_Y)); - ensure_equals("NaN compares equal to nothing", y.equal(y_again).getGatheredBits(), (U32)(LLVector4Logical::MASK_XYZ)); + UNSCOPED_INFO("greaterThan"); + CHECK(x.greaterThan(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_Z)); + UNSCOPED_INFO("lessThan"); + CHECK(x.lessThan(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_X)); + UNSCOPED_INFO("greaterEqual"); + CHECK(x.greaterEqual(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_Y | LLVector4Logical::MASK_Z)); + UNSCOPED_INFO("lessEqual"); + CHECK(x.lessEqual(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_Y)); + UNSCOPED_INFO("equal"); + CHECK(x.equal(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_Y)); + UNSCOPED_INFO("NaN compares equal to nothing"); + CHECK(y.equal(y_again).getGatheredBits() == (U32)(LLVector4Logical::MASK_XYZ)); LLVector4Logical m = x.greaterEqual(y); - ensure("areAnySet", m.areAnySet()); - ensure("areAnySet(MASK_XYZ)", m.areAnySet(LLVector4Logical::MASK_XYZ)); - ensure("areAnySet(MASK_X) false", !m.areAnySet(LLVector4Logical::MASK_X)); - ensure("areAnySet(MASK_W) false", !m.areAnySet(LLVector4Logical::MASK_W)); - ensure("areAllSet false", !m.areAllSet()); - ensure("areAllSet(MASK_XYZ) false", !m.areAllSet(LLVector4Logical::MASK_XYZ)); - ensure("areAllSet(Y|Z)", m.areAllSet(LLVector4Logical::MASK_Y | LLVector4Logical::MASK_Z)); + UNSCOPED_INFO("areAnySet"); + CHECK(m.areAnySet()); + UNSCOPED_INFO("areAnySet(MASK_XYZ)"); + CHECK(m.areAnySet(LLVector4Logical::MASK_XYZ)); + UNSCOPED_INFO("areAnySet(MASK_X) false"); + CHECK_FALSE(m.areAnySet(LLVector4Logical::MASK_X)); + UNSCOPED_INFO("areAnySet(MASK_W) false"); + CHECK_FALSE(m.areAnySet(LLVector4Logical::MASK_W)); + UNSCOPED_INFO("areAllSet false"); + CHECK_FALSE(m.areAllSet()); + UNSCOPED_INFO("areAllSet(MASK_XYZ) false"); + CHECK_FALSE(m.areAllSet(LLVector4Logical::MASK_XYZ)); + UNSCOPED_INFO("areAllSet(Y|Z)"); + CHECK(m.areAllSet(LLVector4Logical::MASK_Y | LLVector4Logical::MASK_Z)); m.invert(); - ensure_equals("invert", m.getGatheredBits(), (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_W)); + UNSCOPED_INFO("invert"); + CHECK(m.getGatheredBits() == (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_W)); LLVector4Logical built; built.clear(); - ensure_equals("clear", built.getGatheredBits(), 0u); - ensure("clear areAnySet", !built.areAnySet()); + UNSCOPED_INFO("clear"); + CHECK(built.getGatheredBits() == 0u); + UNSCOPED_INFO("clear areAnySet"); + CHECK_FALSE(built.areAnySet()); built.setElement<0>(); built.setElement<2>(); - ensure_equals("setElement", built.getGatheredBits(), (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_Z)); + UNSCOPED_INFO("setElement"); + CHECK(built.getGatheredBits() == (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_Z)); built.setElement<1>(); - ensure("areAllSet(MASK_XYZ)", built.areAllSet(LLVector4Logical::MASK_XYZ)); - ensure("areAllSet still false", !built.areAllSet()); + UNSCOPED_INFO("areAllSet(MASK_XYZ)"); + CHECK(built.areAllSet(LLVector4Logical::MASK_XYZ)); + UNSCOPED_INFO("areAllSet still false"); + CHECK_FALSE(built.areAllSet()); built.setElement<3>(); - ensure("areAllSet", built.areAllSet()); + UNSCOPED_INFO("areAllSet"); + CHECK(built.areAllSet()); // select takes whole lanes, bits and all LLVector4Logical pick; @@ -321,8 +350,7 @@ namespace // Lane arithmetic is one rounding per lane, so it matches scalar float // bit for bit; the sign ops touch only the sign bit. - template<> template<> - void llvector4a_object::test<4>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: lane arithmetic matches scalar float bit for bit", "[LLVector4a]") { const LLVector4a p = n_a(); const LLVector4a q = n_b(); @@ -378,21 +406,23 @@ namespace // a NaN payload survives the sign ops unchanged r.setAbs(a()); - ensure_equals("setAbs keeps a NaN payload", bits_of(r[2]), pa[2]); + UNSCOPED_INFO("setAbs keeps a NaN payload"); + CHECK(bits_of(r[2]) == pa[2]); r.setNeg(a()); - ensure_equals("setNeg keeps a NaN payload", bits_of(r[2]), pa[2] | 0x80000000u); + UNSCOPED_INFO("setNeg keeps a NaN payload"); + CHECK(bits_of(r[2]) == (pa[2] | 0x80000000u)); // denormals pass through the arithmetic here, whatever the viewer // sets its own threads to const LLVector4a tiny(DENORMAL, DENORMAL, 0.f, 0.f); r.setAdd(tiny, tiny); - ensure_equals("denormal add", bits_of(r[0]), bits_of(DENORMAL) * 2); + UNSCOPED_INFO("denormal add"); + CHECK(bits_of(r[0]) == bits_of(DENORMAL) * 2); } // The dot products against double, the whole-register forms in every // lane, and the scalar forms in the lane that is read. - template<> template<> - void llvector4a_object::test<5>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: dot products and lengths against double", "[LLVector4a]") { const LLVector4a p = n_a(); const LLVector4a q = n_b(); @@ -424,8 +454,7 @@ namespace // The cross product against double, within the rounding the fused form // is allowed, and the identities that name it. - template<> template<> - void llvector4a_object::test<6>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: the cross product against double and its identities", "[LLVector4a]") { const LLVector4a p = n_a(); const LLVector4a q = n_b(); @@ -440,16 +469,20 @@ namespace LLVector4a r; r.setCross3(p, q); ensure_relative("setCross3", r, expected, 4e-7, 3); - ensure_equals("setCross3 w is zero", r[3], 0.f); + UNSCOPED_INFO("setCross3 w is zero"); + CHECK(r[3] == 0.f); ensure_relative("cross3", p.cross3(q), expected, 4e-7, 3); // perpendicular to both, and antisymmetric - ensure("cross is perpendicular to a", std::fabs(r.dot3(p).getF32()) < 1e-5f); - ensure("cross is perpendicular to b", std::fabs(r.dot3(q).getF32()) < 1e-5f); + UNSCOPED_INFO("cross is perpendicular to a"); + CHECK(std::fabs(r.dot3(p).getF32()) < 1e-5f); + UNSCOPED_INFO("cross is perpendicular to b"); + CHECK(std::fabs(r.dot3(q).getF32()) < 1e-5f); LLVector4a s; s.setCross3(q, p); s.negate(); - ensure("b x a is -(a x b)", s.equals3(r, 1e-6f)); + UNSCOPED_INFO("b x a is -(a x b)"); + CHECK(s.equals3(r, 1e-6f)); // the basis r.setCross3(LLVector4a(1.f, 0.f, 0.f), LLVector4a(0.f, 1.f, 0.f)); @@ -459,8 +492,7 @@ namespace // The normalize family: the full-precision forms within an ulp or two of // the double answer, the fast form within its promised 11 bits, and the // checked form's two escapes. - template<> template<> - void llvector4a_object::test<7>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: the normalize family against double and the checked form's escapes", "[LLVector4a]") { const F32 raw[4] = {3.f, -4.f, 12.f, 2.f}; const double len3 = 13.0; @@ -471,13 +503,16 @@ namespace LLVector4a v(raw[0], raw[1], raw[2], raw[3]); v.normalize3(); ensure_relative("normalize3", v, n3, 4e-7, 3); - ensure("normalize3 result isNormalized3", v.isNormalized3()); - ensure("normalize3 result isNormalized3 tight", v.isNormalized3(1e-6f)); + UNSCOPED_INFO("normalize3 result isNormalized3"); + CHECK(v.isNormalized3()); + UNSCOPED_INFO("normalize3 result isNormalized3 tight"); + CHECK(v.isNormalized3(1e-6f)); v.set(raw[0], raw[1], raw[2], raw[3]); v.normalize4(); ensure_relative("normalize4", v, n4, 4e-7); - ensure("normalize4 result isNormalized4", v.isNormalized4(1e-6f)); + UNSCOPED_INFO("normalize4 result isNormalized4"); + CHECK(v.isNormalized4(1e-6f)); v.set(raw[0], raw[1], raw[2], raw[3]); const F32 length = v.normalize3withLength().getF32(); @@ -500,11 +535,15 @@ namespace // isNormalized measures length, not squared length const LLVector4a off(1.0005f, 0.f, 0.f, 0.f); - ensure("isNormalized3 at the default tolerance", off.isNormalized3()); - ensure("isNormalized3 at a tight tolerance", !off.isNormalized3(1e-4f)); + UNSCOPED_INFO("isNormalized3 at the default tolerance"); + CHECK(off.isNormalized3()); + UNSCOPED_INFO("isNormalized3 at a tight tolerance"); + CHECK_FALSE(off.isNormalized3(1e-4f)); const LLVector4a off4(0.f, 0.f, 0.f, 1.0005f); - ensure("isNormalized4 sees w", off4.isNormalized4()); - ensure("isNormalized3 does not see w", !off4.isNormalized3()); + UNSCOPED_INFO("isNormalized4 sees w"); + CHECK(off4.isNormalized4()); + UNSCOPED_INFO("isNormalized3 does not see w"); + CHECK_FALSE(off4.isNormalized3()); // the checked form LLVector4a checked(3.f, -4.f, 12.f, 0.f); @@ -523,8 +562,7 @@ namespace // min, max, clamp and lerp: the first two on numbers alone, clamp with // the NaN it promises to leave alone. - template<> template<> - void llvector4a_object::test<8>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: setMin and setMax and update_min_max and clamp and setLerp", "[LLVector4a]") { const LLVector4a p = n_a(); const LLVector4a q = n_b(); @@ -545,7 +583,8 @@ namespace r.set(-0.5f, 0.5f, 3.f, NAN_QUIET); r.clamp(low, high); ensure_exact("clamp x y z", LLVector4a(r[0], r[1], r[2], 0.f), 0.f, 0.5f, 2.f, 0.f); - ensure("clamp leaves a NaN lane NaN", std::isnan(r[3])); + UNSCOPED_INFO("clamp leaves a NaN lane NaN"); + CHECK(std::isnan(r[3])); r.set(0.f, 1.f, -1.f, 0.f); r.clamp(low, high); ensure_exact("clamp at the bounds", r, 0.f, 1.f, -1.f, 0.f); @@ -561,50 +600,64 @@ namespace } // Finite means every exponent short of all ones, in the lanes asked. - template<> template<> - void llvector4a_object::test<9>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: isFinite3 and isFinite4 reject infinity and NaN in their lanes", "[LLVector4a]") { - ensure("numbers are finite", n_a().isFinite4()); - ensure("a denormal is finite", LLVector4a(DENORMAL, 0.f, 0.f, 0.f).isFinite4()); - ensure("the largest float is finite", LLVector4a(std::numeric_limits::max(), 0.f, 0.f, 0.f).isFinite4()); - ensure("an infinity in x", !LLVector4a(INF, 0.f, 0.f, 0.f).isFinite3()); - ensure("a negative infinity in z", !LLVector4a(0.f, 0.f, -INF, 0.f).isFinite3()); - ensure("a NaN in y", !LLVector4a(0.f, NAN_QUIET, 0.f, 0.f).isFinite3()); - ensure("a NaN in w passes isFinite3", LLVector4a(0.f, 0.f, 0.f, NAN_QUIET).isFinite3()); - ensure("a NaN in w fails isFinite4", !LLVector4a(0.f, 0.f, 0.f, NAN_QUIET).isFinite4()); - ensure("a NaN payload fails", !a().isFinite3()); + UNSCOPED_INFO("numbers are finite"); + CHECK(n_a().isFinite4()); + UNSCOPED_INFO("a denormal is finite"); + CHECK(LLVector4a(DENORMAL, 0.f, 0.f, 0.f).isFinite4()); + UNSCOPED_INFO("the largest float is finite"); + CHECK(LLVector4a(std::numeric_limits::max(), 0.f, 0.f, 0.f).isFinite4()); + UNSCOPED_INFO("an infinity in x"); + CHECK_FALSE(LLVector4a(INF, 0.f, 0.f, 0.f).isFinite3()); + UNSCOPED_INFO("a negative infinity in z"); + CHECK_FALSE(LLVector4a(0.f, 0.f, -INF, 0.f).isFinite3()); + UNSCOPED_INFO("a NaN in y"); + CHECK_FALSE(LLVector4a(0.f, NAN_QUIET, 0.f, 0.f).isFinite3()); + UNSCOPED_INFO("a NaN in w passes isFinite3"); + CHECK(LLVector4a(0.f, 0.f, 0.f, NAN_QUIET).isFinite3()); + UNSCOPED_INFO("a NaN in w fails isFinite4"); + CHECK_FALSE(LLVector4a(0.f, 0.f, 0.f, NAN_QUIET).isFinite4()); + UNSCOPED_INFO("a NaN payload fails"); + CHECK_FALSE(a().isFinite3()); } // The tolerance compares. - template<> template<> - void llvector4a_object::test<10>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: equals3 and equals4 compare within a tolerance", "[LLVector4a]") { const LLVector4a p = n_a(); LLVector4a nearby = p; nearby.add(LLVector4a(1e-7f, -1e-7f, 1e-7f, 1e-7f)); - ensure("equals4 within the default tolerance", p.equals4(nearby)); - ensure("operator== within the default tolerance", p == nearby); - ensure("equals4 outside a tight tolerance", !p.equals4(nearby, 1e-8f)); + UNSCOPED_INFO("equals4 within the default tolerance"); + CHECK(p.equals4(nearby)); + UNSCOPED_INFO("operator== within the default tolerance"); + CHECK(p == nearby); + UNSCOPED_INFO("equals4 outside a tight tolerance"); + CHECK_FALSE(p.equals4(nearby, 1e-8f)); LLVector4a w_off = p; w_off.add(LLVector4a(0.f, 0.f, 0.f, 1.f)); - ensure("equals3 ignores w", p.equals3(w_off)); - ensure("equals4 sees w", !p.equals4(w_off)); - ensure("operator!=", p != w_off); + UNSCOPED_INFO("equals3 ignores w"); + CHECK(p.equals3(w_off)); + UNSCOPED_INFO("equals4 sees w"); + CHECK_FALSE(p.equals4(w_off)); + UNSCOPED_INFO("operator!="); + CHECK(p != w_off); // Held against another value rather than itself: /fp:fast lets the // compiler fold x - x to 0, so a vector equals itself whatever it holds. const LLVector4a nan(NAN_QUIET, 0.f, 0.f, 0.f); - ensure("a NaN equals nothing", !nan.equals4(LLVector4a::getZero())); - ensure("and nothing equals a NaN", !LLVector4a::getZero().equals4(nan)); + UNSCOPED_INFO("a NaN equals nothing"); + CHECK_FALSE(nan.equals4(LLVector4a::getZero())); + UNSCOPED_INFO("and nothing equals a NaN"); + CHECK_FALSE(LLVector4a::getZero().equals4(nan)); } // Quantizing maps onto the stored integers and back: the result is // within half a step of the input, stable under a second pass, collapses // a flat channel onto its bound, and rounds anything within a step of // zero to zero. - template<> template<> - void llvector4a_object::test<11>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: quantize8 and quantize16 map onto the stored integers and back", "[LLVector4a]") { const LLVector4a low(-1.f, -2.f, 0.f, 5.f); const LLVector4a high(1.f, 2.f, 10.f, 5.f); @@ -628,37 +681,53 @@ namespace { const F32 clamped = llclamp(in[i], low[i], high[i]); const std::string lane = " lane " + std::to_string(i) + " of " + precise(in[i]); - ensure("quantize8 within half a step" + lane, std::fabs(v8[i] - clamped) <= delta[i] / 255.f * 0.5f + 1e-6f); - ensure("quantize16 within half a step" + lane, std::fabs(v16[i] - clamped) <= delta[i] / 65535.f * 0.5f + 1e-6f); + UNSCOPED_INFO("quantize8 within half a step" + lane); + REQUIRE(std::fabs(v8[i] - clamped) <= delta[i] / 255.f * 0.5f + 1e-6f); + UNSCOPED_INFO("quantize16 within half a step" + lane); + REQUIRE(std::fabs(v16[i] - clamped) <= delta[i] / 65535.f * 0.5f + 1e-6f); // the way back multiplies by the step and by the range, so a // bound can come back an ulp past itself const F32 slack = 1e-6f * llmax(1.f, std::fabs(high[i])); - ensure("quantize8 within the range" + lane, v8[i] >= low[i] - slack && v8[i] <= high[i] + slack); - ensure("quantize16 within the range" + lane, v16[i] >= low[i] - slack && v16[i] <= high[i] + slack); + { + INFO("quantize8 within the range" + lane); + REQUIRE(v8[i] >= low[i] - slack); + REQUIRE(v8[i] <= high[i] + slack); + } + { + INFO("quantize16 within the range" + lane); + REQUIRE(v16[i] >= low[i] - slack); + REQUIRE(v16[i] <= high[i] + slack); + } } - ensure_equals("quantize8 flat channel is low", v8[3], 5.f); - ensure_equals("quantize16 flat channel is low", v16[3], 5.f); + UNSCOPED_INFO("quantize8 flat channel is low"); + REQUIRE(v8[3] == 5.f); + UNSCOPED_INFO("quantize16 flat channel is low"); + REQUIRE(v16[3] == 5.f); LLVector4a again8 = v8; again8.quantize8(low, high); - ensure("quantize8 is stable", again8.equals4(v8, 1e-6f)); + UNSCOPED_INFO("quantize8 is stable"); + REQUIRE(again8.equals4(v8, 1e-6f)); LLVector4a again16 = v16; again16.quantize16(low, high); - ensure("quantize16 is stable", again16.equals4(v16, 1e-7f)); + UNSCOPED_INFO("quantize16 is stable"); + REQUIRE(again16.equals4(v16, 1e-7f)); } // a value within a step of zero becomes zero LLVector4a tiny(0.001f, -0.001f, 0.01f, 5.f); tiny.quantize8(low, high); - ensure_equals("quantize8 snaps x to zero", bits_of(tiny[0]), 0u); - ensure_equals("quantize8 snaps y to zero", bits_of(tiny[1]), 0u); - ensure_equals("quantize8 snaps z to zero", bits_of(tiny[2]), 0u); + UNSCOPED_INFO("quantize8 snaps x to zero"); + CHECK(bits_of(tiny[0]) == 0u); + UNSCOPED_INFO("quantize8 snaps y to zero"); + CHECK(bits_of(tiny[1]) == 0u); + UNSCOPED_INFO("quantize8 snaps z to zero"); + CHECK(bits_of(tiny[2]) == 0u); } // Rotation by a matrix and by a quaternion against the scalar types, // and the inverse forms undoing them. - template<> template<> - void llvector4a_object::test<12>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: rotation by a matrix and by a quaternion against the scalar types", "[LLVector4a]") { const LLQuaternion rotations[] = { LLQuaternion(), @@ -680,7 +749,8 @@ namespace const LLMatrix3 m3 = q.getMatrix3(); LLRotation rot; rot.loadu(m3); - ensure("the matrix is a rotation", rot.isOkRotation()); + UNSCOPED_INFO("the matrix is a rotation"); + REQUIRE(rot.isOkRotation()); LLQuaternion2 q2(q); @@ -695,21 +765,29 @@ namespace LLVector4a by_matrix; by_matrix.setRotated(rot, v); - ensure("setRotated(LLRotation) x", std::fabs(by_matrix[0] - expected.mV[0]) <= tolerance); - ensure("setRotated(LLRotation) y", std::fabs(by_matrix[1] - expected.mV[1]) <= tolerance); - ensure("setRotated(LLRotation) z", std::fabs(by_matrix[2] - expected.mV[2]) <= tolerance); + UNSCOPED_INFO("setRotated(LLRotation) x"); + REQUIRE(std::fabs(by_matrix[0] - expected.mV[0]) <= tolerance); + UNSCOPED_INFO("setRotated(LLRotation) y"); + REQUIRE(std::fabs(by_matrix[1] - expected.mV[1]) <= tolerance); + UNSCOPED_INFO("setRotated(LLRotation) z"); + REQUIRE(std::fabs(by_matrix[2] - expected.mV[2]) <= tolerance); LLVector4a by_quat; by_quat.setRotated(q2, v); - ensure("setRotated(LLQuaternion2) x", std::fabs(by_quat[0] - expected_q.mV[0]) <= tolerance); - ensure("setRotated(LLQuaternion2) y", std::fabs(by_quat[1] - expected_q.mV[1]) <= tolerance); - ensure("setRotated(LLQuaternion2) z", std::fabs(by_quat[2] - expected_q.mV[2]) <= tolerance); + UNSCOPED_INFO("setRotated(LLQuaternion2) x"); + REQUIRE(std::fabs(by_quat[0] - expected_q.mV[0]) <= tolerance); + UNSCOPED_INFO("setRotated(LLQuaternion2) y"); + REQUIRE(std::fabs(by_quat[1] - expected_q.mV[1]) <= tolerance); + UNSCOPED_INFO("setRotated(LLQuaternion2) z"); + REQUIRE(std::fabs(by_quat[2] - expected_q.mV[2]) <= tolerance); LLVector4a back; back.setRotatedInv(rot, by_matrix); - ensure("setRotatedInv(LLRotation) undoes it", back.equals3(v, tolerance)); + UNSCOPED_INFO("setRotatedInv(LLRotation) undoes it"); + REQUIRE(back.equals3(v, tolerance)); back.setRotatedInv(q2, by_quat); - ensure("setRotatedInv(LLQuaternion2) undoes it", back.equals3(v, tolerance)); + UNSCOPED_INFO("setRotatedInv(LLQuaternion2) undoes it"); + REQUIRE(back.equals3(v, tolerance)); } } } @@ -717,41 +795,57 @@ namespace // LLSimdScalar: the arithmetic on lane 0, and the compares. A NaN is // not compared: the fast floating-point contract this tree builds under // leaves a scalar compare of one unordered. - template<> template<> - void llvector4a_object::test<13>() + TEST_CASE_METHOD(llvector4a_data, "LLVector4a: LLSimdScalar arithmetic and compares", "[LLVector4a]") { const LLSimdScalar three(3.f); const LLSimdScalar minus_two(-2.f); - ensure_equals("getF32", three.getF32(), 3.f); - ensure_equals("operator+", (three + minus_two).getF32(), 1.f); - ensure_equals("operator-", (three - minus_two).getF32(), 5.f); - ensure_equals("operator*", (three * minus_two).getF32(), -6.f); - ensure_equals("operator/", (three / minus_two).getF32(), -1.5f); - ensure_equals("unary minus", (-three).getF32(), -3.f); - ensure_equals("getAbs", minus_two.getAbs().getF32(), 2.f); - ensure_equals("getZero", LLSimdScalar::getZero().getF32(), 0.f); + UNSCOPED_INFO("getF32"); + CHECK(three.getF32() == 3.f); + UNSCOPED_INFO("operator+"); + CHECK((three + minus_two).getF32() == 1.f); + UNSCOPED_INFO("operator-"); + CHECK((three - minus_two).getF32() == 5.f); + UNSCOPED_INFO("operator*"); + CHECK((three * minus_two).getF32() == -6.f); + UNSCOPED_INFO("operator/"); + CHECK((three / minus_two).getF32() == -1.5f); + UNSCOPED_INFO("unary minus"); + CHECK((-three).getF32() == -3.f); + UNSCOPED_INFO("getAbs"); + CHECK(minus_two.getAbs().getF32() == 2.f); + UNSCOPED_INFO("getZero"); + CHECK(LLSimdScalar::getZero().getF32() == 0.f); LLSimdScalar s = three; - s += minus_two; ensure_equals("+=", s.getF32(), 1.f); - s -= minus_two; ensure_equals("-=", s.getF32(), 3.f); - s *= minus_two; ensure_equals("*=", s.getF32(), -6.f); - s /= three; ensure_equals("/=", s.getF32(), -2.f); - s = 7.f; ensure_equals("=", s.getF32(), 7.f); + s += minus_two; UNSCOPED_INFO("+="); CHECK(s.getF32() == 1.f); + s -= minus_two; UNSCOPED_INFO("-="); CHECK(s.getF32() == 3.f); + s *= minus_two; UNSCOPED_INFO("*="); CHECK(s.getF32() == -6.f); + s /= three; UNSCOPED_INFO("/="); CHECK(s.getF32() == -2.f); + s = 7.f; UNSCOPED_INFO("="); CHECK(s.getF32() == 7.f); LLSimdScalar mm; - mm.setMax(three, minus_two); ensure_equals("setMax", mm.getF32(), 3.f); - mm.setMin(three, minus_two); ensure_equals("setMin", mm.getF32(), -2.f); - - ensure("<", minus_two < three); - ensure("<=", three <= three); - ensure(">", three > minus_two); - ensure(">=", minus_two >= minus_two); - ensure("==", three == LLSimdScalar(3.f)); - ensure("!=", three != minus_two); - ensure("isApproximatelyEqual", three.isApproximatelyEqual(LLSimdScalar(3.f + 1e-6f))); - ensure("isApproximatelyEqual outside", !three.isApproximatelyEqual(minus_two)); + mm.setMax(three, minus_two); UNSCOPED_INFO("setMax"); CHECK(mm.getF32() == 3.f); + mm.setMin(three, minus_two); UNSCOPED_INFO("setMin"); CHECK(mm.getF32() == -2.f); + + UNSCOPED_INFO("<"); + CHECK(minus_two < three); + UNSCOPED_INFO("<="); + CHECK(three <= three); + UNSCOPED_INFO(">"); + CHECK(three > minus_two); + UNSCOPED_INFO(">="); + CHECK(minus_two >= minus_two); + UNSCOPED_INFO("=="); + CHECK(three == LLSimdScalar(3.f)); + UNSCOPED_INFO("!="); + CHECK(three != minus_two); + UNSCOPED_INFO("isApproximatelyEqual"); + CHECK(three.isApproximatelyEqual(LLSimdScalar(3.f + 1e-6f))); + UNSCOPED_INFO("isApproximatelyEqual outside"); + CHECK_FALSE(three.isApproximatelyEqual(minus_two)); // a dot product comes back in the lane that is read - ensure_equals("a dot as scalar", n_a().dot3(LLVector4a(1.f, 0.f, 0.f)).getF32(), na[0]); + UNSCOPED_INFO("a dot as scalar"); + CHECK(n_a().dot3(LLVector4a(1.f, 0.f, 0.f)).getF32() == na[0]); } } diff --git a/indra/llmath/tests/m3math_test.cpp b/indra/llmath/tests/m3math_test.cpp index 6a9a841436..a105420f09 100644 --- a/indra/llmath/tests/m3math_test.cpp +++ b/indra/llmath/tests/m3math_test.cpp @@ -35,7 +35,7 @@ #include "../llquaternion.h" #include "../v3dmath.h" -#include "../test/lltut.h" +#include #if LL_WINDOWS // disable unreachable code warnings caused by usage of skip. @@ -47,76 +47,71 @@ #pragma warning(disable: 4702) #endif -namespace tut -{ namespace { - struct m3math_test - { - }; - typedef test_group m3math_test_t; - typedef m3math_test_t::object m3math_test_object_t; - tut::m3math_test_t tut_m3math_test("m3math_h"); -} - //test case for setIdentity() fn. - template<> template<> - void m3math_test_object_t::test<1>() + TEST_CASE("m3math_h: setIdentity makes the identity matrix", "[m3math_h]") { LLMatrix3 llmat3_obj; llmat3_obj.setIdentity(); - ensure("LLMatrix3::setIdentity failed", 1.f == llmat3_obj.mMatrix[0][0] && - 0.f == llmat3_obj.mMatrix[0][1] && - 0.f == llmat3_obj.mMatrix[0][2] && - 0.f == llmat3_obj.mMatrix[1][0] && - 1.f == llmat3_obj.mMatrix[1][1] && - 0.f == llmat3_obj.mMatrix[1][2] && - 0.f == llmat3_obj.mMatrix[2][0] && - 0.f == llmat3_obj.mMatrix[2][1] && - 1.f == llmat3_obj.mMatrix[2][2]); + { + INFO("LLMatrix3::setIdentity failed"); + CHECK(llmat3_obj.mMatrix[0][0] == 1.f); + CHECK(llmat3_obj.mMatrix[0][1] == 0.f); + CHECK(llmat3_obj.mMatrix[0][2] == 0.f); + CHECK(llmat3_obj.mMatrix[1][0] == 0.f); + CHECK(llmat3_obj.mMatrix[1][1] == 1.f); + CHECK(llmat3_obj.mMatrix[1][2] == 0.f); + CHECK(llmat3_obj.mMatrix[2][0] == 0.f); + CHECK(llmat3_obj.mMatrix[2][1] == 0.f); + CHECK(llmat3_obj.mMatrix[2][2] == 1.f); + } } //test case for LLMatrix3& setZero() fn. - template<> template<> - void m3math_test_object_t::test<2>() + TEST_CASE("m3math_h: setZero clears every element", "[m3math_h]") { LLMatrix3 llmat3_obj; llmat3_obj.setZero(); - ensure("LLMatrix3::setZero failed", 0.f == llmat3_obj.setZero().mMatrix[0][0] && - 0.f == llmat3_obj.setZero().mMatrix[0][1] && - 0.f == llmat3_obj.setZero().mMatrix[0][2] && - 0.f == llmat3_obj.setZero().mMatrix[1][0] && - 0.f == llmat3_obj.setZero().mMatrix[1][1] && - 0.f == llmat3_obj.setZero().mMatrix[1][2] && - 0.f == llmat3_obj.setZero().mMatrix[2][0] && - 0.f == llmat3_obj.setZero().mMatrix[2][1] && - 0.f == llmat3_obj.setZero().mMatrix[2][2]); + { + INFO("LLMatrix3::setZero failed"); + CHECK(llmat3_obj.setZero().mMatrix[0][0] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[0][1] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[0][2] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[1][0] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[1][1] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[1][2] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[2][0] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[2][1] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[2][2] == 0.f); + } } //test case for setRows(const LLVector3 &x_axis, const LLVector3 &y_axis, const LLVector3 &z_axis) fns. - template<> template<> - void m3math_test_object_t::test<3>() + TEST_CASE("m3math_h: setRows fills the matrix row by row", "[m3math_h]") { LLMatrix3 llmat3_obj; LLVector3 vect1(2, 1, 4); LLVector3 vect2(3, 5, 7); LLVector3 vect3(6, 9, 7); llmat3_obj.setRows(vect1, vect2, vect3); - ensure("LLVector3::setRows failed ", 2 == llmat3_obj.mMatrix[0][0] && - 1 == llmat3_obj.mMatrix[0][1] && - 4 == llmat3_obj.mMatrix[0][2] && - 3 == llmat3_obj.mMatrix[1][0] && - 5 == llmat3_obj.mMatrix[1][1] && - 7 == llmat3_obj.mMatrix[1][2] && - 6 == llmat3_obj.mMatrix[2][0] && - 9 == llmat3_obj.mMatrix[2][1] && - 7 == llmat3_obj.mMatrix[2][2]); + { + INFO("LLVector3::setRows failed "); + CHECK(llmat3_obj.mMatrix[0][0] == 2); + CHECK(llmat3_obj.mMatrix[0][1] == 1); + CHECK(llmat3_obj.mMatrix[0][2] == 4); + CHECK(llmat3_obj.mMatrix[1][0] == 3); + CHECK(llmat3_obj.mMatrix[1][1] == 5); + CHECK(llmat3_obj.mMatrix[1][2] == 7); + CHECK(llmat3_obj.mMatrix[2][0] == 6); + CHECK(llmat3_obj.mMatrix[2][1] == 9); + CHECK(llmat3_obj.mMatrix[2][2] == 7); + } } //test case for getFwdRow(), getLeftRow(), getUpRow() fns. - template<> template<> - void m3math_test_object_t::test<4>() + TEST_CASE("m3math_h: getFwdRow/getLeftRow/getUpRow return the rows set", "[m3math_h]") { LLMatrix3 llmat3_obj; LLVector3 vect1(2, 1, 4); @@ -124,14 +119,16 @@ namespace LLVector3 vect3(6, 9, 7); llmat3_obj.setRows(vect1, vect2, vect3); - ensure("LLVector3::getFwdRow failed ", vect1 == llmat3_obj.getFwdRow()); - ensure("LLVector3::getLeftRow failed ", vect2 == llmat3_obj.getLeftRow()); - ensure("LLVector3::getUpRow failed ", vect3 == llmat3_obj.getUpRow()); + UNSCOPED_INFO("LLVector3::getFwdRow failed "); + CHECK(vect1 == llmat3_obj.getFwdRow()); + UNSCOPED_INFO("LLVector3::getLeftRow failed "); + CHECK(vect2 == llmat3_obj.getLeftRow()); + UNSCOPED_INFO("LLVector3::getUpRow failed "); + CHECK(vect3 == llmat3_obj.getUpRow()); } //test case for operator*(const LLMatrix3 &a, const LLMatrix3 &b) - template<> template<> - void m3math_test_object_t::test<5>() + TEST_CASE("m3math_h: operator* multiplies two matrices", "[m3math_h]") { LLMatrix3 llmat_obj1; LLMatrix3 llmat_obj2; @@ -153,22 +150,23 @@ namespace llmat_obj2.setRows(llvec4, llvec5, llvec6); llmat_obj3.setRows(llvec7, llvec8, llvec9); llmat_obj3 = llmat_obj1 * llmat_obj2; - ensure("LLMatrix3::operator*(const LLMatrix3 &a, const LLMatrix3 &b) failed", - 50 == llmat_obj3.mMatrix[0][0] && - 49 == llmat_obj3.mMatrix[0][1] && - 39 == llmat_obj3.mMatrix[0][2] && - 29 == llmat_obj3.mMatrix[1][0] && - 45 == llmat_obj3.mMatrix[1][1] && - 65 == llmat_obj3.mMatrix[1][2] && - 94 == llmat_obj3.mMatrix[2][0] && - 94 == llmat_obj3.mMatrix[2][1] && - 86 == llmat_obj3.mMatrix[2][2]); + { + INFO("LLMatrix3::operator*(const LLMatrix3 &a, const LLMatrix3 &b) failed"); + CHECK(llmat_obj3.mMatrix[0][0] == 50); + CHECK(llmat_obj3.mMatrix[0][1] == 49); + CHECK(llmat_obj3.mMatrix[0][2] == 39); + CHECK(llmat_obj3.mMatrix[1][0] == 29); + CHECK(llmat_obj3.mMatrix[1][1] == 45); + CHECK(llmat_obj3.mMatrix[1][2] == 65); + CHECK(llmat_obj3.mMatrix[2][0] == 94); + CHECK(llmat_obj3.mMatrix[2][1] == 94); + CHECK(llmat_obj3.mMatrix[2][2] == 86); + } } //test case for operator*(const LLVector3 &a, const LLMatrix3 &b) - template<> template<> - void m3math_test_object_t::test<6>() + TEST_CASE("m3math_h: operator* transforms an LLVector3 by a matrix", "[m3math_h]") { LLMatrix3 llmat_obj1; @@ -184,12 +182,12 @@ namespace LLVector3 expected_result(30, 51, 53); - ensure("LLMatrix3::operator*(const LLVector3 &a, const LLMatrix3 &b) failed", res_vec == expected_result); + UNSCOPED_INFO("LLMatrix3::operator*(const LLVector3 &a, const LLMatrix3 &b) failed"); + CHECK(res_vec == expected_result); } //test case for operator*(const LLVector3d &a, const LLMatrix3 &b) - template<> template<> - void m3math_test_object_t::test<7>() + TEST_CASE("m3math_h: operator* transforms an LLVector3d by a matrix", "[m3math_h]") { LLMatrix3 llmat_obj1; LLVector3d llvec3d1; @@ -204,12 +202,12 @@ namespace LLVector3d expected_result(25, 30, 3); - ensure("LLMatrix3::operator*(const LLVector3 &a, const LLMatrix3 &b) failed", llvec3d1 == expected_result); + UNSCOPED_INFO("LLMatrix3::operator*(const LLVector3 &a, const LLMatrix3 &b) failed"); + CHECK(llvec3d1 == expected_result); } // test case for operator==(const LLMatrix3 &a, const LLMatrix3 &b) - template<> template<> - void m3math_test_object_t::test<8>() + TEST_CASE("m3math_h: operator== and operator!= compare matrices", "[m3math_h]") { LLMatrix3 llmat_obj1; LLMatrix3 llmat_obj2; @@ -220,15 +218,16 @@ namespace llmat_obj1.setRows(llvec1, llvec2, llvec3); llmat_obj2.setRows(llvec1, llvec2, llvec3); - ensure("LLMatrix3::operator==(const LLMatrix3 &a, const LLMatrix3 &b) failed", llmat_obj1 == llmat_obj2); + UNSCOPED_INFO("LLMatrix3::operator==(const LLMatrix3 &a, const LLMatrix3 &b) failed"); + CHECK(llmat_obj1 == llmat_obj2); llmat_obj2.setRows(llvec2, llvec2, llvec3); - ensure("LLMatrix3::operator!=(const LLMatrix3 &a, const LLMatrix3 &b) failed", llmat_obj1 != llmat_obj2); + UNSCOPED_INFO("LLMatrix3::operator!=(const LLMatrix3 &a, const LLMatrix3 &b) failed"); + CHECK(llmat_obj1 != llmat_obj2); } //test case for quaternion() fn. - template<> template<> - void m3math_test_object_t::test<9>() + TEST_CASE("m3math_h: quaternion converts the matrix to a quaternion", "[m3math_h]") { LLMatrix3 llmat_obj1; LLQuaternion llmat_quat; @@ -239,15 +238,17 @@ namespace llmat_obj1.setRows(llmat1, llmat2, llmat3); llmat_quat = llmat_obj1.quaternion(); - ensure("LLMatrix3::quaternion failed ", is_approx_equal(-0.66666669f, llmat_quat.mQ[0]) && - is_approx_equal(-0.83333337f, llmat_quat.mQ[1]) && - is_approx_equal(0.0f, llmat_quat.mQ[2]) && - is_approx_equal(1.5f, llmat_quat.mQ[3])); + { + INFO("LLMatrix3::quaternion failed "); + CHECK(is_approx_equal(-0.66666669f, llmat_quat.mQ[0])); + CHECK(is_approx_equal(-0.83333337f, llmat_quat.mQ[1])); + CHECK(is_approx_equal(0.0f, llmat_quat.mQ[2])); + CHECK(is_approx_equal(1.5f, llmat_quat.mQ[3])); + } } //test case for transpose() fn. - template<> template<> - void m3math_test_object_t::test<10>() + TEST_CASE("m3math_h: transpose swaps rows and columns", "[m3math_h]") { LLMatrix3 llmat_obj; @@ -264,12 +265,12 @@ namespace LLMatrix3 expectedllmat_obj; expectedllmat_obj.setRows(resllvec1, resllvec2, resllvec3); - ensure("LLMatrix3::transpose failed ", llmat_obj == expectedllmat_obj); + UNSCOPED_INFO("LLMatrix3::transpose failed "); + CHECK(llmat_obj == expectedllmat_obj); } //test case for determinant() fn. - template<> template<> - void m3math_test_object_t::test<11>() + TEST_CASE("m3math_h: determinant of a singular matrix is zero", "[m3math_h]") { LLMatrix3 llmat_obj1; @@ -277,12 +278,12 @@ namespace LLVector3 llvec2(3, 2, 1); LLVector3 llvec3(2, 2, 2); llmat_obj1.setRows(llvec1, llvec2, llvec3); - ensure("LLMatrix3::determinant failed ", 0.0f == llmat_obj1.determinant()); + UNSCOPED_INFO("LLMatrix3::determinant failed "); + CHECK(llmat_obj1.determinant() == 0.0f); } //test case for orthogonalize() fn. - template<> template<> - void m3math_test_object_t::test<12>() + TEST_CASE("m3math_h: orthogonalize makes the rows orthonormal", "[m3math_h]") { LLMatrix3 llmat_obj; @@ -290,26 +291,27 @@ namespace LLVector3 llvec2(1, 2, 0); LLVector3 llvec3(2, 4, 2); - skip("This test fails depending on architecture. Need to fix comparison operation, is_approx_equal, to work on more than one platform."); + SKIP("This test fails depending on architecture. Need to fix comparison operation, is_approx_equal, to work on more than one platform."); llmat_obj.setRows(llvec1, llvec2, llvec3); llmat_obj.orthogonalize(); - ensure("LLMatrix3::orthogonalize failed ", - is_approx_equal(0.19611614f, llmat_obj.mMatrix[0][0]) && - is_approx_equal(0.78446454f, llmat_obj.mMatrix[0][1]) && - is_approx_equal(0.58834841f, llmat_obj.mMatrix[0][2]) && - is_approx_equal(0.47628204f, llmat_obj.mMatrix[1][0]) && - is_approx_equal(0.44826545f, llmat_obj.mMatrix[1][1]) && - is_approx_equal(-0.75644795f, llmat_obj.mMatrix[1][2]) && - is_approx_equal(-0.85714286f, llmat_obj.mMatrix[2][0]) && - is_approx_equal(0.42857143f, llmat_obj.mMatrix[2][1]) && - is_approx_equal(-0.28571429f, llmat_obj.mMatrix[2][2])); + { + INFO("LLMatrix3::orthogonalize failed "); + CHECK(is_approx_equal(0.19611614f, llmat_obj.mMatrix[0][0])); + CHECK(is_approx_equal(0.78446454f, llmat_obj.mMatrix[0][1])); + CHECK(is_approx_equal(0.58834841f, llmat_obj.mMatrix[0][2])); + CHECK(is_approx_equal(0.47628204f, llmat_obj.mMatrix[1][0])); + CHECK(is_approx_equal(0.44826545f, llmat_obj.mMatrix[1][1])); + CHECK(is_approx_equal(-0.75644795f, llmat_obj.mMatrix[1][2])); + CHECK(is_approx_equal(-0.85714286f, llmat_obj.mMatrix[2][0])); + CHECK(is_approx_equal(0.42857143f, llmat_obj.mMatrix[2][1])); + CHECK(is_approx_equal(-0.28571429f, llmat_obj.mMatrix[2][2])); + } } //test case for adjointTranspose() fn. - template<> template<> - void m3math_test_object_t::test<13>() + TEST_CASE("m3math_h: adjointTranspose gives the expected cofactors", "[m3math_h]") { LLMatrix3 llmat_obj; @@ -320,22 +322,24 @@ namespace llmat_obj.setRows(llvec1, llvec2, llvec3); llmat_obj.adjointTranspose(); - ensure("LLMatrix3::adjointTranspose failed ", 10 == llmat_obj.mMatrix[0][0] && - -45 == llmat_obj.mMatrix[1][0] && - 30 == llmat_obj.mMatrix[2][0] && - -10 == llmat_obj.mMatrix[0][1] && - 21 == llmat_obj.mMatrix[1][1] && - -12 == llmat_obj.mMatrix[2][1] && - 0 == llmat_obj.mMatrix[0][2] && - 3 == llmat_obj.mMatrix[1][2] && - -6 == llmat_obj.mMatrix[2][2]); + { + INFO("LLMatrix3::adjointTranspose failed "); + CHECK(llmat_obj.mMatrix[0][0] == 10); + CHECK(llmat_obj.mMatrix[1][0] == -45); + CHECK(llmat_obj.mMatrix[2][0] == 30); + CHECK(llmat_obj.mMatrix[0][1] == -10); + CHECK(llmat_obj.mMatrix[1][1] == 21); + CHECK(llmat_obj.mMatrix[2][1] == -12); + CHECK(llmat_obj.mMatrix[0][2] == 0); + CHECK(llmat_obj.mMatrix[1][2] == 3); + CHECK(llmat_obj.mMatrix[2][2] == -6); + } } /* TBD: Need to add test cases for getEulerAngles() and setRot() functions */ // The field-only operations of both matrices evaluate at compile time. - template<> template<> - void m3math_test_object_t::test<14>() + TEST_CASE("m3math_h: field-only operations of both matrices evaluate at compile time", "[m3math_h]") { constexpr LLMatrix3 product = []() constexpr { @@ -401,6 +405,10 @@ namespace }(); static_assert(sum.mMatrix[0][0] == 3.f && sum.mMatrix[0][1] == 0.f && sum.mMatrix[3][3] == 3.f); - ensure("compile-time matrices agree at run time", product.mMatrix[2][2] == 47.f && inverse.getTranslation().mV[2] == -3.f); + { + INFO("compile-time matrices agree at run time"); + CHECK(product.mMatrix[2][2] == 47.f); + CHECK(inverse.getTranslation().mV[2] == -3.f); + } } } diff --git a/indra/llmath/tests/mathmisc_test.cpp b/indra/llmath/tests/mathmisc_test.cpp index c28d353ace..61559dbd4a 100644 --- a/indra/llmath/tests/mathmisc_test.cpp +++ b/indra/llmath/tests/mathmisc_test.cpp @@ -27,7 +27,7 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../test/alrandomreseed.h" #include "llcrc.h" @@ -40,214 +40,209 @@ #include -namespace tut -{ namespace { - struct math_data - { - }; - typedef test_group math_test; - typedef math_test::object math_object; - tut::math_test tm("BasicLindenMath"); -} - - template<> template<> - void math_object::test<1>() + TEST_CASE("BasicLindenMath: llabs of an S32", "[BasicLindenMath]") { S32 val = 89543; val = llabs(val); - ensure("integer absolute value 1", (89543 == val)); + UNSCOPED_INFO("integer absolute value 1"); + CHECK(val == 89543); val = -500; val = llabs(val); - ensure("integer absolute value 2", (500 == val)); + UNSCOPED_INFO("integer absolute value 2"); + CHECK(val == 500); } - template<> template<> - void math_object::test<2>() + TEST_CASE("BasicLindenMath: llabs of an F32", "[BasicLindenMath]") { F32 val = -2583.4f; val = llabs(val); - ensure("float absolute value 1", (2583.4f == val)); + UNSCOPED_INFO("float absolute value 1"); + CHECK(val == 2583.4f); val = 430903.f; val = llabs(val); - ensure("float absolute value 2", (430903.f == val)); + UNSCOPED_INFO("float absolute value 2"); + CHECK(val == 430903.f); } - template<> template<> - void math_object::test<3>() + TEST_CASE("BasicLindenMath: llabs of an F64", "[BasicLindenMath]") { F64 val = 387439393.987329839; val = llabs(val); - ensure("double absolute value 1", (387439393.987329839 == val)); + UNSCOPED_INFO("double absolute value 1"); + CHECK(val == 387439393.987329839); val = -8937843.9394878; val = llabs(val); - ensure("double absolute value 2", (8937843.9394878 == val)); + UNSCOPED_INFO("double absolute value 2"); + CHECK(val == 8937843.9394878); } - template<> template<> - void math_object::test<4>() + TEST_CASE("BasicLindenMath: lltrunc of an F32", "[BasicLindenMath]") { F32 val = 430903.9f; S32 val1 = lltrunc(val); - ensure("float truncate value 1", (430903 == val1)); + UNSCOPED_INFO("float truncate value 1"); + CHECK(val1 == 430903); val = -2303.9f; val1 = lltrunc(val); - ensure("float truncate value 2", (-2303 == val1)); + UNSCOPED_INFO("float truncate value 2"); + CHECK(val1 == -2303); } - template<> template<> - void math_object::test<5>() + TEST_CASE("BasicLindenMath: lltrunc of an F64", "[BasicLindenMath]") { F64 val = 387439393.987329839 ; S32 val1 = lltrunc(val); - ensure("float truncate value 1", (387439393 == val1)); + UNSCOPED_INFO("float truncate value 1"); + CHECK(val1 == 387439393); val = -387439393.987329839; val1 = lltrunc(val); - ensure("float truncate value 2", (-387439393 == val1)); + UNSCOPED_INFO("float truncate value 2"); + CHECK(val1 == -387439393); } - template<> template<> - void math_object::test<6>() + TEST_CASE("BasicLindenMath: llfloor of an F32", "[BasicLindenMath]") { F32 val = 430903.2f; S32 val1 = llfloor(val); - ensure("float llfloor value 1", (430903 == val1)); + UNSCOPED_INFO("float llfloor value 1"); + CHECK(val1 == 430903); val = -430903.9f; val1 = llfloor(val); - ensure("float llfloor value 2", (-430904 == val1)); + UNSCOPED_INFO("float llfloor value 2"); + CHECK(val1 == -430904); } - template<> template<> - void math_object::test<7>() + TEST_CASE("BasicLindenMath: llceil of an F32", "[BasicLindenMath]") { F32 val = 430903.2f; S32 val1 = llceil(val); - ensure("float llceil value 1", (430904 == val1)); + UNSCOPED_INFO("float llceil value 1"); + CHECK(val1 == 430904); val = -430903.9f; val1 = llceil(val); - ensure("float llceil value 2", (-430903 == val1)); + UNSCOPED_INFO("float llceil value 2"); + CHECK(val1 == -430903); } - template<> template<> - void math_object::test<8>() + TEST_CASE("BasicLindenMath: ll_round of an F32 to an integer", "[BasicLindenMath]") { F32 val = 430903.2f; S32 val1 = ll_round(val); - ensure("float ll_round value 1", (430903 == val1)); + UNSCOPED_INFO("float ll_round value 1"); + CHECK(val1 == 430903); val = -430903.9f; val1 = ll_round(val); - ensure("float ll_round value 2", (-430904 == val1)); + UNSCOPED_INFO("float ll_round value 2"); + CHECK(val1 == -430904); } - template<> template<> - void math_object::test<9>() + TEST_CASE("BasicLindenMath: ll_round of an F32 to the nearest multiple", "[BasicLindenMath]") { F32 val = 430905.2654f, nearest = 100.f; val = ll_round(val, nearest); - ensure("float ll_round value 1", (430900 == val)); + UNSCOPED_INFO("float ll_round value 1"); + CHECK(val == 430900); val = -430905.2654f, nearest = 10.f; val = ll_round(val, nearest); - ensure("float ll_round value 1", (-430910 == val)); + UNSCOPED_INFO("float ll_round value 1"); + CHECK(val == -430910); } - template<> template<> - void math_object::test<10>() + TEST_CASE("BasicLindenMath: ll_round of an F64 to the nearest multiple", "[BasicLindenMath]") { F64 val = 430905.2654, nearest = 100.0; val = ll_round(val, nearest); - ensure("double ll_round value 1", (430900 == val)); + UNSCOPED_INFO("double ll_round value 1"); + CHECK(val == 430900); val = -430905.2654, nearest = 10.0; val = ll_round(val, nearest); - ensure("double ll_round value 1", (-430910.00000 == val)); + UNSCOPED_INFO("double ll_round value 1"); + CHECK(val == -430910.00000); } - template<> template<> - void math_object::test<11>() + TEST_CASE("BasicLindenMath: llsimple_angle wraps an angle into -pi to pi", "[BasicLindenMath]") { const F32 F_PI = 3.1415926535897932384626433832795f; F32 angle = 3506.f; angle = llsimple_angle(angle); - ensure("llsimple_angle value 1", (angle <=F_PI && angle >= -F_PI)); + { + INFO("llsimple_angle value 1"); + CHECK(angle <=F_PI); + CHECK(angle >= -F_PI); + } angle = -431.f; angle = llsimple_angle(angle); - ensure("llsimple_angle value 1", (angle <=F_PI && angle >= -F_PI)); + { + INFO("llsimple_angle value 1"); + CHECK(angle <=F_PI); + CHECK(angle >= -F_PI); + } } } -namespace tut -{ namespace { struct uuid_data { LLUUID id; }; - typedef test_group uuid_test; - typedef uuid_test::object uuid_object; - tut::uuid_test tu("mathmisc LLUUID"); -} - template<> template<> - void uuid_object::test<1>() + TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: isNull by default and after setNull but not after generate", "[mathmisc_LLUUID]") { - ensure("uuid null", id.isNull()); + UNSCOPED_INFO("uuid null"); + CHECK(id.isNull()); id.generate(); - ensure("generate not null", id.notNull()); + UNSCOPED_INFO("generate not null"); + CHECK(id.notNull()); id.setNull(); - ensure("set null", id.isNull()); + UNSCOPED_INFO("set null"); + CHECK(id.isNull()); } - template<> template<> - void uuid_object::test<2>() + TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: copies and assignments compare equal", "[mathmisc_LLUUID]") { id.generate(); LLUUID a(id); - ensure_equals("copy equal", id, a); + UNSCOPED_INFO("copy equal"); + CHECK(id == a); a.generate(); - ensure_not_equals("generate not equal", id, a); + UNSCOPED_INFO("generate not equal"); + CHECK(id != a); a = id; - ensure_equals("assignment equal", id, a); + UNSCOPED_INFO("assignment equal"); + CHECK(id == a); } - template<> template<> - void uuid_object::test<3>() + TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: xor with a mask twice restores the id", "[mathmisc_LLUUID]") { id.generate(); LLUUID copy(id); LLUUID mask; mask.generate(); copy ^= mask; - ensure_not_equals("mask not equal", id, copy); + UNSCOPED_INFO("mask not equal"); + CHECK(id != copy); copy ^= mask; - ensure_equals("mask back", id, copy); + UNSCOPED_INFO("mask back"); + CHECK(id == copy); } - template<> template<> - void uuid_object::test<4>() + TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: asString round-trips through the string constructor", "[mathmisc_LLUUID]") { id.generate(); std::string id_str = id.asString(); LLUUID copy(id_str.c_str()); - ensure_equals("string serialization", id, copy); + UNSCOPED_INFO("string serialization"); + CHECK(id == copy); } } -namespace tut -{ namespace { - struct crc_data - { - }; - typedef test_group crc_test; - typedef crc_test::object crc_object; - tut::crc_test tc("LLCrc"); -} - - template<> template<> - void crc_object::test<1>() + TEST_CASE("LLCrc: a buffer update equals updating one char at a time", "[LLCrc]") { /* Test buffer update and individual char update */ const char TEST_BUFFER[] = "hello &#$)$&Nd0"; @@ -259,11 +254,11 @@ namespace c2.update(*rh); ++rh; } - ensure_equals("crc update 1", c1.getCRC(), c2.getCRC()); + UNSCOPED_INFO("crc update 1"); + CHECK(c1.getCRC() == c2.getCRC()); } - template<> template<> - void crc_object::test<2>() + TEST_CASE("LLCrc: buffer and char updates mixed in either order agree", "[LLCrc]") { /* Test mixing of buffer and individual char update */ const char TEST_BUFFER1[] = "Split Buffer one $^%$%#@$"; @@ -285,12 +280,11 @@ namespace } c2.update((U8*)TEST_BUFFER2, sizeof(TEST_BUFFER2) - 1); - ensure_equals("crc update 2", c1.getCRC(), c2.getCRC()); + UNSCOPED_INFO("crc update 2"); + CHECK(c1.getCRC() == c2.getCRC()); } } -namespace tut -{ namespace { F32 SMALL_RADIUS = 1.0f; @@ -302,13 +296,8 @@ namespace // Tests 1 and 3 seed the main thread's generator. ~line_data() { ll_test::reseedRandomFromEntropy(); } }; - typedef test_group line_test; - typedef line_test::object line_object; - tut::line_test tline("LLLine"); -} - template<> template<> - void line_object::test<1>() + TEST_CASE_METHOD(line_data, "LLLine: intersects finds points within a tolerance of the line", "[LLLine]") { // this is a test for LLLine::intersects(point) which returns true // if the line passes within some tolerance of point @@ -363,15 +352,16 @@ namespace // test that the intersection point is within MEDIUM_RADIUS + SMALL_RADIUS F32 test_radius = MEDIUM_RADIUS + SMALL_RADIUS; test_radius += (LARGE_RADIUS * allowable_relative_error); - ensure("line should pass near intersection point", line.intersects(point, test_radius)); + UNSCOPED_INFO("line should pass near intersection point"); + REQUIRE(line.intersects(point, test_radius)); test_radius = allowable_relative_error * (point - point_on_line).length(); - ensure("line should intersect point used to define it", line.intersects(point_on_line, test_radius)); + UNSCOPED_INFO("line should intersect point used to define it"); + REQUIRE(line.intersects(point_on_line, test_radius)); } } - template<> template<> - void line_object::test<2>() + TEST_CASE_METHOD(line_data, "LLLine: nearestApproach is disabled for DEV-16600", "[LLLine]") { /* These tests fail intermittently on all platforms - see DEV-16600 @@ -481,13 +471,9 @@ namespace */ } -namespace -{ F32 ALMOST_PARALLEL = 0.99f; -} - template<> template<> - void line_object::test<3>() + TEST_CASE_METHOD(line_data, "LLLine: getIntersectionBetweenTwoPlanes on axis and random planes", "[LLLine]") { // this is a test for LLLine::getIntersectionBetweenTwoPlanes() method @@ -504,9 +490,12 @@ namespace bool y_success = LLLine::getIntersectionBetweenTwoPlanes(y_line, yz_plane, xy_plane); bool z_success = LLLine::getIntersectionBetweenTwoPlanes(z_line, zx_plane, yz_plane); - ensure("xy and zx planes should intersect", x_success); - ensure("yz and xy planes should intersect", y_success); - ensure("zx and yz planes should intersect", z_success); + UNSCOPED_INFO("xy and zx planes should intersect"); + CHECK(x_success); + UNSCOPED_INFO("yz and xy planes should intersect"); + CHECK(y_success); + UNSCOPED_INFO("zx and yz planes should intersect"); + CHECK(z_success); // These directions come from normalize() of a cross product of unit // axes -- analytically exact (1, 0, 0) etc., but under -ffast-math the @@ -514,20 +503,26 @@ namespace // component drifts to ~0.99997. Use a tolerance instead of ==. constexpr F32 AXIS_EPS = 1e-4f; LLVector3 direction = x_line.getDirection(); - ensure("x_line should be parallel to x_axis", - fabs(fabs(direction.mV[VX]) - 1.f) < AXIS_EPS - && fabs(direction.mV[VY]) < AXIS_EPS - && fabs(direction.mV[VZ]) < AXIS_EPS); + { + INFO("x_line should be parallel to x_axis"); + CHECK(fabs(fabs(direction.mV[VX]) - 1.f) < AXIS_EPS); + CHECK(fabs(direction.mV[VY]) < AXIS_EPS); + CHECK(fabs(direction.mV[VZ]) < AXIS_EPS); + } direction = y_line.getDirection(); - ensure("y_line should be parallel to y_axis", - fabs(direction.mV[VX]) < AXIS_EPS - && fabs(fabs(direction.mV[VY]) - 1.f) < AXIS_EPS - && fabs(direction.mV[VZ]) < AXIS_EPS); + { + INFO("y_line should be parallel to y_axis"); + CHECK(fabs(direction.mV[VX]) < AXIS_EPS); + CHECK(fabs(fabs(direction.mV[VY]) - 1.f) < AXIS_EPS); + CHECK(fabs(direction.mV[VZ]) < AXIS_EPS); + } direction = z_line.getDirection(); - ensure("z_line should be parallel to z_axis", - fabs(direction.mV[VX]) < AXIS_EPS - && fabs(direction.mV[VY]) < AXIS_EPS - && fabs(fabs(direction.mV[VZ]) - 1.f) < AXIS_EPS); + { + INFO("z_line should be parallel to z_axis"); + CHECK(fabs(direction.mV[VX]) < AXIS_EPS); + CHECK(fabs(direction.mV[VY]) < AXIS_EPS); + CHECK(fabs(fabs(direction.mV[VZ]) - 1.f) < AXIS_EPS); + } // next some random tests F32 allowable_relative_error = 0.0001f; @@ -581,14 +576,15 @@ namespace first_plane, second_plane); - ensure("plane intersection should succeed", success); + UNSCOPED_INFO("plane intersection should succeed"); + REQUIRE(success); F32 dot = fabs(known_intersection.getDirection() * measured_intersection.getDirection()); - ensure("measured intersection should be parallel to known intersection", - dot > ALMOST_PARALLEL); + UNSCOPED_INFO("measured intersection should be parallel to known intersection"); + REQUIRE(dot > ALMOST_PARALLEL); - ensure("measured intersection should pass near known point", - measured_intersection.intersects(some_point, LARGE_RADIUS * allowable_relative_error)); + UNSCOPED_INFO("measured intersection should pass near known point"); + REQUIRE(measured_intersection.intersects(some_point, LARGE_RADIUS * allowable_relative_error)); } } @@ -597,27 +593,29 @@ namespace // in code; nothing may impose them on the whole process from the link // line. A denormal that survives a multiply here means no crtfastmath.o // was linked. - template<> template<> - void math_object::test<12>() + TEST_CASE("BasicLindenMath: the process default keeps denormals", "[BasicLindenMath]") { volatile float denormal = 1.0e-40f; volatile float one = 1.0f; float product = denormal * one; - ensure("denormals are not flushed by the process default", product != 0.0f); - ensure("a denormal is still a denormal", product == 1.0e-40f); + UNSCOPED_INFO("denormals are not flushed by the process default"); + CHECK(product != 0.0f); + UNSCOPED_INFO("a denormal is still a denormal"); + CHECK(product == 1.0e-40f); } // Optimised builds relax floating-point semantics to MSVC's /fp:fast // contract, which keeps NaN and infinity. -ffinite-math-only would fold // both of these guards to a constant. - template<> template<> - void math_object::test<13>() + TEST_CASE("BasicLindenMath: optimised builds keep NaN and infinity", "[BasicLindenMath]") { volatile float zero = 0.0f; float nan = zero / zero; float inf = 1.0f / zero; - ensure("isnan sees a NaN", std::isnan(nan)); - ensure("isinf sees an infinity", std::isinf(inf)); + UNSCOPED_INFO("isnan sees a NaN"); + CHECK(std::isnan(nan)); + UNSCOPED_INFO("isinf sees an infinity"); + CHECK(std::isinf(inf)); } } diff --git a/indra/llmath/tests/v2math_test.cpp b/indra/llmath/tests/v2math_test.cpp index 7bce7d5fe7..e45675c465 100644 --- a/indra/llmath/tests/v2math_test.cpp +++ b/indra/llmath/tests/v2math_test.cpp @@ -27,103 +27,133 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../v2math.h" -namespace tut -{ namespace { - struct v2math_data - { - }; - typedef test_group v2math_test; - typedef v2math_test::object v2math_object; - tut::v2math_test v2math_testcase("v2math_h"); -} - - template<> template<> - void v2math_object::test<1>() + TEST_CASE("v2math_h: constructors and clearVec/zeroVec", "[v2math_h]") { LLVector2 vec2; - ensure("LLVector2:Fail to initialize ", (0.f == vec2.mV[VX] && 0.f == vec2.mV[VY])); + { + INFO("LLVector2:Fail to initialize "); + CHECK(vec2.mV[VX] == 0.f); + CHECK(vec2.mV[VY] == 0.f); + } F32 x =2.0f, y = 3.2f ; LLVector2 vec3(x,y); - ensure("LLVector2(F32 x, F32 y):Fail to initialize ", (x == vec3.mV[VX]) && (y == vec3.mV[VY])); + { + INFO("LLVector2(F32 x, F32 y):Fail to initialize "); + CHECK(x == vec3.mV[VX]); + CHECK(y == vec3.mV[VY]); + } const F32 vec[2] = {3.2f, 4.5f}; LLVector2 vec4(vec); - ensure("LLVector2(const F32 *vec):Fail to initialize ", (vec[0] == vec4.mV[VX]) && (vec[1] == vec4.mV[VY])); + { + INFO("LLVector2(const F32 *vec):Fail to initialize "); + CHECK(vec[0] == vec4.mV[VX]); + CHECK(vec[1] == vec4.mV[VY]); + } vec4.clearVec(); - ensure("clearVec():Fail to clean the values ", (0.f == vec4.mV[VX] && 0.f == vec4.mV[VY])); + { + INFO("clearVec():Fail to clean the values "); + CHECK(vec4.mV[VX] == 0.f); + CHECK(vec4.mV[VY] == 0.f); + } vec3.zeroVec(); - ensure("zeroVec():Fail to fill the zero ", (0.f == vec3.mV[VX] && 0.f == vec3.mV[VY])); + { + INFO("zeroVec():Fail to fill the zero "); + CHECK(vec3.mV[VX] == 0.f); + CHECK(vec3.mV[VY] == 0.f); + } } - template<> template<> - void v2math_object::test<2>() + TEST_CASE("v2math_h: the setVec overloads", "[v2math_h]") { F32 x = 123.356f, y = 2387.453f; LLVector2 vec2,vec3; vec2.setVec(x, y); - ensure("1:setVec: Fail ", (x == vec2.mV[VX]) && (y == vec2.mV[VY])); + { + INFO("1:setVec: Fail "); + CHECK(x == vec2.mV[VX]); + CHECK(y == vec2.mV[VY]); + } vec3.setVec(vec2); - ensure("2:setVec: Fail " ,(vec2 == vec3)); + UNSCOPED_INFO("2:setVec: Fail "); + CHECK(vec2 == vec3); vec3.zeroVec(); const F32 vec[2] = {3.24653f, 457653.4f}; vec3.setVec(vec); - ensure("3:setVec: Fail ", (vec[0] == vec3.mV[VX]) && (vec[1] == vec3.mV[VY])); + { + INFO("3:setVec: Fail "); + CHECK(vec[0] == vec3.mV[VX]); + CHECK(vec[1] == vec3.mV[VY]); + } } - template<> template<> - void v2math_object::test<3>() + TEST_CASE("v2math_h: magVecSquared and magVec", "[v2math_h]") { F32 x = 2.2345f, y = 3.5678f ; LLVector2 vec2(x,y); - ensure("magVecSquared:Fail ", is_approx_equal(vec2.magVecSquared(), (x*x + y*y))); - ensure("magVec:Fail ", is_approx_equal(vec2.magVec(), (F32) sqrt(x*x + y*y))); + UNSCOPED_INFO("magVecSquared:Fail "); + CHECK(is_approx_equal(vec2.magVecSquared(), (x*x + y*y))); + UNSCOPED_INFO("magVec:Fail "); + CHECK(is_approx_equal(vec2.magVec(), (F32) sqrt(x*x + y*y))); } - template<> template<> - void v2math_object::test<4>() + TEST_CASE("v2math_h: abs then isNull on long and very short vectors", "[v2math_h]") { F32 x =-2.0f, y = -3.0f ; LLVector2 vec2(x,y); - ensure_equals("abs():Fail", vec2.abs(), true); - ensure("abs() x", is_approx_equal(vec2.mV[VX], 2.f)); - ensure("abs() y", is_approx_equal(vec2.mV[VY], 3.f)); + UNSCOPED_INFO("abs():Fail"); + CHECK(vec2.abs() == true); + UNSCOPED_INFO("abs() x"); + CHECK(is_approx_equal(vec2.mV[VX], 2.f)); + UNSCOPED_INFO("abs() y"); + CHECK(is_approx_equal(vec2.mV[VY], 3.f)); - ensure("isNull():Fail ", false == vec2.isNull()); //Returns true if vector has a _very_small_ length + UNSCOPED_INFO("isNull():Fail "); + CHECK_FALSE(vec2.isNull()); //Returns true if vector has a _very_small_ length x =.00000001f, y = .000001001f; vec2.setVec(x, y); - ensure("isNull(): Fail ", true == vec2.isNull()); + UNSCOPED_INFO("isNull(): Fail "); + CHECK(vec2.isNull()); } - template<> template<> - void v2math_object::test<5>() + TEST_CASE("v2math_h: scaleVec and isExactlyZero", "[v2math_h]") { F32 x =1.f, y = 2.f; LLVector2 vec2(x, y), vec3; vec3 = vec3.scaleVec(vec2); - ensure("scaleVec: Fail ", vec3.mV[VX] == 0. && vec3.mV[VY] == 0.); - ensure("isExactlyZero(): Fail", true == vec3.isExactlyZero()); + { + INFO("scaleVec: Fail "); + CHECK(vec3.mV[VX] == 0.); + CHECK(vec3.mV[VY] == 0.); + } + UNSCOPED_INFO("isExactlyZero(): Fail"); + CHECK(vec3.isExactlyZero()); vec3.setVec(2.f, 1.f); vec3 = vec3.scaleVec(vec2); - ensure("scaleVec: Fail ", (2.f == vec3.mV[VX]) && (2.f == vec3.mV[VY])); - ensure("isExactlyZero():Fail", false == vec3.isExactlyZero()); + { + INFO("scaleVec: Fail "); + CHECK(vec3.mV[VX] == 2.f); + CHECK(vec3.mV[VY] == 2.f); + } + UNSCOPED_INFO("isExactlyZero():Fail"); + CHECK_FALSE(vec3.isExactlyZero()); } - template<> template<> - void v2math_object::test<6>() + TEST_CASE("v2math_h: operator+ adds componentwise", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f, x2 = -2.3f, y2 = 1.11f; F32 val1, val2; @@ -131,7 +161,11 @@ namespace vec4 = vec2 + vec3 ; val1 = x1+x2; val2 = y1+y2; - ensure("1:operator+ failed",(val1 == vec4.mV[VX]) && ((val2 == vec4.mV[VY]))); + { + INFO("1:operator+ failed"); + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); + } vec2.clearVec(); vec3.clearVec(); @@ -141,11 +175,14 @@ namespace vec4 = vec2 + vec3; val1 = x1+x2; val2 = y1+y2; - ensure("2:operator+ failed",(val1 == vec4.mV[VX]) && ((val2 == vec4.mV[VY]))); + { + INFO("2:operator+ failed"); + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); + } } - template<> template<> - void v2math_object::test<7>() + TEST_CASE("v2math_h: operator- subtracts componentwise", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f, x2 = -2.3f, y2 = 1.11f; F32 val1, val2; @@ -153,7 +190,11 @@ namespace vec4 = vec2 - vec3 ; val1 = x1-x2; val2 = y1-y2; - ensure("1:operator- failed",(val1 == vec4.mV[VX]) && ((val2 == vec4.mV[VY]))); + { + INFO("1:operator- failed"); + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); + } vec2.clearVec(); vec3.clearVec(); @@ -164,33 +205,44 @@ namespace vec4 = vec2 - vec3; val1 = x1-x2; val2 = y1-y2; - ensure("2:operator- failed",(val1 == vec4.mV[VX]) && ((val2 == vec4.mV[VY]))); + { + INFO("2:operator- failed"); + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); + } } - template<> template<> - void v2math_object::test<8>() + TEST_CASE("v2math_h: operator* as dot product and scalar product", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f, x2 = -2.3f, y2 = 1.11f; F32 val1, val2; LLVector2 vec2(x1, y1), vec3(x2, y2); val1 = vec2 * vec3; val2 = x1*x2 + y1*y2; - ensure("1:operator* failed",(val1 == val2)); + UNSCOPED_INFO("1:operator* failed"); + CHECK(val1 == val2); vec3.clearVec(); F32 mulVal = 4.332f; vec3 = vec2 * mulVal; val1 = x1*mulVal; val2 = y1*mulVal; - ensure("2:operator* failed",(val1 == vec3.mV[VX]) && (val2 == vec3.mV[VY])); + { + INFO("2:operator* failed"); + CHECK(val1 == vec3.mV[VX]); + CHECK(val2 == vec3.mV[VY]); + } vec3.clearVec(); vec3 = mulVal * vec2; - ensure("3:operator* failed",(val1 == vec3.mV[VX]) && (val2 == vec3.mV[VY])); + { + INFO("3:operator* failed"); + CHECK(val1 == vec3.mV[VX]); + CHECK(val2 == vec3.mV[VY]); + } } - template<> template<> - void v2math_object::test<9>() + TEST_CASE("v2math_h: operator/ divides by a scalar", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f, div = 3.2f; F32 val1, val2; @@ -198,7 +250,11 @@ namespace vec3 = vec2 / div; val1 = x1 / div; val2 = y1 / div; - ensure("1:operator/ failed", is_approx_equal(val1, vec3.mV[VX]) && is_approx_equal(val2, vec3.mV[VY])); + { + INFO("1:operator/ failed"); + CHECK(is_approx_equal(val1, vec3.mV[VX])); + CHECK(is_approx_equal(val2, vec3.mV[VY])); + } vec3.clearVec(); x1 = -.235f, y1 = -24.32f, div = -2.2f; @@ -206,11 +262,14 @@ namespace vec3 = vec2 / div; val1 = x1 / div; val2 = y1 / div; - ensure("2:operator/ failed", is_approx_equal(val1, vec3.mV[VX]) && is_approx_equal(val2, vec3.mV[VY])); + { + INFO("2:operator/ failed"); + CHECK(is_approx_equal(val1, vec3.mV[VX])); + CHECK(is_approx_equal(val2, vec3.mV[VY])); + } } - template<> template<> - void v2math_object::test<10>() + TEST_CASE("v2math_h: operator% is the cross product", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f, x2 = -2.3f, y2 = 1.11f; F32 val1, val2; @@ -218,7 +277,11 @@ namespace vec4 = vec2 % vec3; val1 = x1*y2 - x2*y1; val2 = y1*x2 - y2*x1; - ensure("1:operator% failed",(val1 == vec4.mV[VX]) && (val2 == vec4.mV[VY])); + { + INFO("1:operator% failed"); + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); + } vec2.clearVec(); vec3.clearVec(); @@ -229,38 +292,43 @@ namespace vec4 = vec2 % vec3; val1 = x1*y2 - x2*y1; val2 = y1*x2 - y2*x1; - ensure("2:operator% failed",(val1 == vec4.mV[VX]) && (val2 == vec4.mV[VY])); + { + INFO("2:operator% failed"); + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); + } } - template<> template<> - void v2math_object::test<11>() + TEST_CASE("v2math_h: operator== for equal vectors", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f; LLVector2 vec2(x1, y1), vec3(x1, y1); - ensure("1:operator== failed",(vec2 == vec3)); + UNSCOPED_INFO("1:operator== failed"); + CHECK(vec2 == vec3); vec2.clearVec(); vec3.clearVec(); x1 = -.235f, y1 = -24.32f; vec2.setVec(x1, y1); vec3.setVec(vec2); - ensure("2:operator== failed",(vec2 == vec3)); + UNSCOPED_INFO("2:operator== failed"); + CHECK(vec2 == vec3); } - template<> template<> - void v2math_object::test<12>() + TEST_CASE("v2math_h: operator!= for different and equal vectors", "[v2math_h]") { F32 x1 = 1.f, y1 = 2.f,x2 = 2.332f, y2 = -1.23f; LLVector2 vec2(x1, y1), vec3(x2, y2); - ensure("1:operator!= failed",(vec2 != vec3)); + UNSCOPED_INFO("1:operator!= failed"); + CHECK(vec2 != vec3); vec2.clearVec(); vec3.clearVec(); vec2.setVec(x1, y1); vec3.setVec(vec2); - ensure("2:operator!= failed", (false == (vec2 != vec3))); + UNSCOPED_INFO("2:operator!= failed"); + CHECK_FALSE(vec2 != vec3); } - template<> template<> - void v2math_object::test<13>() + TEST_CASE("v2math_h: operator+= and operator-=", "[v2math_h]") { F32 x1 = 1.f, y1 = 2.f,x2 = 2.332f, y2 = -1.23f; F32 val1, val2; @@ -268,13 +336,21 @@ namespace vec2 +=vec3; val1 = x1+x2; val2 = y1+y2; - ensure("1:operator+= failed",(val1 == vec2.mV[VX]) && (val2 == vec2.mV[VY])); + { + INFO("1:operator+= failed"); + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); + } vec2.setVec(x1, y1); vec2 -=vec3; val1 = x1-x2; val2 = y1-y2; - ensure("2:operator-= failed",(val1 == vec2.mV[VX]) && (val2 == vec2.mV[VY])); + { + INFO("2:operator-= failed"); + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); + } vec2.clearVec(); vec3.clearVec(); @@ -284,17 +360,24 @@ namespace vec2 +=vec3; val1 = x1+x2; val2 = y1+y2; - ensure("3:operator+= failed",(val1 == vec2.mV[VX]) && (val2 == vec2.mV[VY])); + { + INFO("3:operator+= failed"); + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); + } vec2.setVec(x1, y1); vec2 -=vec3; val1 = x1-x2; val2 = y1-y2; - ensure("4:operator-= failed", is_approx_equal(val1, vec2.mV[VX]) && is_approx_equal(val2, vec2.mV[VY])); + { + INFO("4:operator-= failed"); + CHECK(is_approx_equal(val1, vec2.mV[VX])); + CHECK(is_approx_equal(val2, vec2.mV[VY])); + } } - template<> template<> - void v2math_object::test<14>() + TEST_CASE("v2math_h: operator/= divides by a scalar", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f; F32 val1, val2, mulVal = 4.332f; @@ -302,7 +385,11 @@ namespace vec2 /=mulVal; val1 = x1 / mulVal; val2 = y1 / mulVal; - ensure("1:operator/= failed", is_approx_equal(val1, vec2.mV[VX]) && is_approx_equal(val2, vec2.mV[VY])); + { + INFO("1:operator/= failed"); + CHECK(is_approx_equal(val1, vec2.mV[VX])); + CHECK(is_approx_equal(val2, vec2.mV[VY])); + } vec2.clearVec(); x1 = .213f, y1 = -2.34f, mulVal = -.23f; @@ -310,11 +397,14 @@ namespace vec2 /=mulVal; val1 = x1 / mulVal; val2 = y1 / mulVal; - ensure("2:operator/= failed", is_approx_equal(val1, vec2.mV[VX]) && is_approx_equal(val2, vec2.mV[VY])); + { + INFO("2:operator/= failed"); + CHECK(is_approx_equal(val1, vec2.mV[VX])); + CHECK(is_approx_equal(val2, vec2.mV[VY])); + } } - template<> template<> - void v2math_object::test<15>() + TEST_CASE("v2math_h: operator*= multiplies by a scalar", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f; F32 val1, val2, mulVal = 4.332f; @@ -322,7 +412,11 @@ namespace vec2 *=mulVal; val1 = x1*mulVal; val2 = y1*mulVal; - ensure("1:operator*= failed",(val1 == vec2.mV[VX]) && (val2 == vec2.mV[VY])); + { + INFO("1:operator*= failed"); + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); + } vec2.clearVec(); x1 = .213f, y1 = -2.34f, mulVal = -.23f; @@ -330,11 +424,14 @@ namespace vec2 *=mulVal; val1 = x1*mulVal; val2 = y1*mulVal; - ensure("2:operator*= failed",(val1 == vec2.mV[VX]) && (val2 == vec2.mV[VY])); + { + INFO("2:operator*= failed"); + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); + } } - template<> template<> - void v2math_object::test<16>() + TEST_CASE("v2math_h: operator%= matches the cross product", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f, x2 = -2.3f, y2 = 1.11f; F32 val1, val2; @@ -342,20 +439,23 @@ namespace vec2 %= vec3; val1 = x1*y2 - x2*y1; val2 = y1*x2 - y2*x1; - ensure("1:operator%= failed",(val1 == vec2.mV[VX]) && (val2 == vec2.mV[VY])); + { + INFO("1:operator%= failed"); + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); + } } - template<> template<> - void v2math_object::test<17>() + TEST_CASE("v2math_h: unary operator- negates", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f; LLVector2 vec2(x1, y1),vec3; vec3 = -vec2; - ensure("1:operator- failed",(-vec3 == vec2)); + UNSCOPED_INFO("1:operator- failed"); + CHECK(-vec3 == vec2); } - template<> template<> - void v2math_object::test<18>() + TEST_CASE("v2math_h: operator<< prints equal vectors alike", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f; std::ostringstream stream1, stream2; @@ -363,59 +463,65 @@ namespace stream1 << vec2; vec3.setVec(x1, y1); stream2 << vec3; - ensure("1:operator << failed",(stream1.str() == stream2.str())); + UNSCOPED_INFO("1:operator << failed"); + CHECK(stream1.str() == stream2.str()); } - template<> template<> - void v2math_object::test<19>() + TEST_CASE("v2math_h: operator< orders by x then y", "[v2math_h]") { F32 x1 =1.0f, y1 = 2.0f, x2 = -.32f, y2 = .2234f; LLVector2 vec2(x1, y1),vec3(x2, y2); - ensure("1:operator < failed",(vec3 < vec2)); + UNSCOPED_INFO("1:operator < failed"); + CHECK(vec3 < vec2); x1 = 1.0f, y1 = 2.0f, x2 = 1.0f, y2 = 3.2234f; vec2.setVec(x1, y1); vec3.setVec(x2, y2); - ensure("2:operator < failed", (false == (vec3 < vec2))); + UNSCOPED_INFO("2:operator < failed"); + CHECK_FALSE(vec3 < vec2); } - template<> template<> - void v2math_object::test<20>() + TEST_CASE("v2math_h: subscript operator reads each component", "[v2math_h]") { F32 x1 =1.0f, y1 = 2.0f; LLVector2 vec2(x1, y1); - ensure("1:operator [] failed",( x1 == vec2[0])); - ensure("2:operator [] failed",( y1 == vec2[1])); + UNSCOPED_INFO("1:operator [] failed"); + CHECK(x1 == vec2[0]); + UNSCOPED_INFO("2:operator [] failed"); + CHECK(y1 == vec2[1]); vec2.clearVec(); x1 = 23.0f, y1 = -.2361f; vec2.setVec(x1, y1); F32 ref1 = vec2[0]; - ensure("3:operator [] failed", ( ref1 == x1)); + UNSCOPED_INFO("3:operator [] failed"); + CHECK(ref1 == x1); F32 ref2 = vec2[1]; - ensure("4:operator [] failed", ( ref2 == y1)); + UNSCOPED_INFO("4:operator [] failed"); + CHECK(ref2 == y1); } - template<> template<> - void v2math_object::test<21>() + TEST_CASE("v2math_h: dist_vec and its squared and 2D forms", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f, x2 = -.32f, y2 = .2234f; F32 val1, val2; LLVector2 vec2(x1, y1),vec3(x2, y2); val1 = dist_vec_squared2D(vec2, vec3); val2 = (x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2); - ensure_equals("dist_vec_squared2D values are not equal",val2, val1); + UNSCOPED_INFO("dist_vec_squared2D values are not equal"); + CHECK(val2 == val1); val1 = dist_vec_squared(vec2, vec3); - ensure_equals("dist_vec_squared values are not equal",val2, val1); + UNSCOPED_INFO("dist_vec_squared values are not equal"); + CHECK(val2 == val1); val1 = dist_vec(vec2, vec3); val2 = (F32) sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2)); - ensure_equals("dist_vec values are not equal",val2, val1); + UNSCOPED_INFO("dist_vec values are not equal"); + CHECK(val2 == val1); } - template<> template<> - void v2math_object::test<22>() + TEST_CASE("v2math_h: lerp between two vectors", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f, x2 = -.32f, y2 = .2234f,fVal = .0121f; F32 val1, val2; @@ -423,11 +529,14 @@ namespace LLVector2 vec4 = lerp(vec2, vec3, fVal); val1 = x1 + (x2 - x1) * fVal; val2 = y1 + (y2 - y1) * fVal; - ensure("lerp values are not equal", ((val1 == vec4.mV[VX]) && (val2 == vec4.mV[VY]))); + { + INFO("lerp values are not equal"); + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); + } } - template<> template<> - void v2math_object::test<23>() + TEST_CASE("v2math_h: normVec normalises and zeroes a tiny vector", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f; F32 val1, val2; @@ -440,12 +549,22 @@ namespace val1 = x1 * oomag; val2 = y1 * oomag; - ensure("normVec failed", is_approx_equal(val1, vec2.mV[VX]) && is_approx_equal(val2, vec2.mV[VY]) && is_approx_equal(vecMag, mag)); + { + INFO("normVec failed"); + CHECK(is_approx_equal(val1, vec2.mV[VX])); + CHECK(is_approx_equal(val2, vec2.mV[VY])); + CHECK(is_approx_equal(vecMag, mag)); + } x1 =.00000001f, y1 = 0.f; vec2.setVec(x1, y1); vecMag = vec2.normVec(); - ensure("normVec failed should be 0.", 0. == vec2.mV[VX] && 0. == vec2.mV[VY] && vecMag == 0.); + { + INFO("normVec failed should be 0."); + CHECK(vec2.mV[VX] == 0.); + CHECK(vec2.mV[VY] == 0.); + CHECK(vecMag == 0.); + } } } diff --git a/indra/llmath/tests/v3color_test.cpp b/indra/llmath/tests/v3color_test.cpp index 0003c3cd60..6be134ff8e 100644 --- a/indra/llmath/tests/v3color_test.cpp +++ b/indra/llmath/tests/v3color_test.cpp @@ -27,80 +27,108 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../v3color.h" -namespace tut -{ namespace { - struct v3color_data - { - }; - typedef test_group v3color_test; - typedef v3color_test::object v3color_object; - tut::v3color_test v3color_testcase("v3color_h"); -} - - template<> template<> - void v3color_object::test<1>() + TEST_CASE("v3color_h: constructors take nothing or components or an array or a hex string", "[v3color_h]") { LLColor3 llcolor3; - ensure("1:LLColor3:Fail to default-initialize ", (0.0f == llcolor3.mV[0]) && (0.0f == llcolor3.mV[1]) && (0.0f == llcolor3.mV[2])); + { + INFO("1:LLColor3:Fail to default-initialize "); + CHECK(llcolor3.mV[0] == 0.0f); + CHECK(llcolor3.mV[1] == 0.0f); + CHECK(llcolor3.mV[2] == 0.0f); + } F32 r = 2.0f, g = 3.2f, b = 1.f; F32 v1,v2,v3; LLColor3 llcolor3a(r,g,b); - ensure("2:LLColor3:Fail to initialize " ,(2.0f == llcolor3a.mV[0]) && (3.2f == llcolor3a.mV[1]) && (1.f == llcolor3a.mV[2])); + { + INFO("2:LLColor3:Fail to initialize "); + CHECK(llcolor3a.mV[0] == 2.0f); + CHECK(llcolor3a.mV[1] == 3.2f); + CHECK(llcolor3a.mV[2] == 1.f); + } const F32 vec[3] = {2.0f, 3.2f,1.f}; LLColor3 llcolor3b(vec); - ensure("3:LLColor3:Fail to initialize " ,(2.0f == llcolor3b.mV[0]) && (3.2f == llcolor3b.mV[1]) && (1.f == llcolor3b.mV[2])); + { + INFO("3:LLColor3:Fail to initialize "); + CHECK(llcolor3b.mV[0] == 2.0f); + CHECK(llcolor3b.mV[1] == 3.2f); + CHECK(llcolor3b.mV[2] == 1.f); + } const char* str = "561122"; LLColor3 llcolor3c(str); v1 = (F32)86.0f/255.0f; // 0x56 = 86 v2 = (F32)17.0f/255.0f; // 0x11 = 17 v3 = (F32)34.0f/255.f; // 0x22 = 34 - ensure("4:LLColor3:Fail to initialize " , is_approx_equal(v1, llcolor3c.mV[0]) && is_approx_equal(v2, llcolor3c.mV[1]) && is_approx_equal(v3, llcolor3c.mV[2])); + { + INFO("4:LLColor3:Fail to initialize "); + CHECK(is_approx_equal(v1, llcolor3c.mV[0])); + CHECK(is_approx_equal(v2, llcolor3c.mV[1])); + CHECK(is_approx_equal(v3, llcolor3c.mV[2])); + } } - template<> template<> - void v3color_object::test<2>() + TEST_CASE("v3color_h: setToBlack and setToWhite", "[v3color_h]") { LLColor3 llcolor3; llcolor3.setToBlack(); - ensure("setToBlack:Fail to set black ", ((llcolor3.mV[0] == 0.f) && (llcolor3.mV[1] == 0.f) && (llcolor3.mV[2] == 0.f))); + { + INFO("setToBlack:Fail to set black "); + CHECK(llcolor3.mV[0] == 0.f); + CHECK(llcolor3.mV[1] == 0.f); + CHECK(llcolor3.mV[2] == 0.f); + } llcolor3.setToWhite(); - ensure("setToWhite:Fail to set white ", ((llcolor3.mV[0] == 1.f) && (llcolor3.mV[1] == 1.f) && (llcolor3.mV[2] == 1.f))); + { + INFO("setToWhite:Fail to set white "); + CHECK(llcolor3.mV[0] == 1.f); + CHECK(llcolor3.mV[1] == 1.f); + CHECK(llcolor3.mV[2] == 1.f); + } } - template<> template<> - void v3color_object::test<3>() + TEST_CASE("v3color_h: setVec from components or another color or an array", "[v3color_h]") { F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; LLColor3 llcolor3, llcolor3a; llcolor3.setVec(r,g,b); - ensure("1:setVec(r,g,b) Fail ",((r == llcolor3.mV[0]) && (g == llcolor3.mV[1]) && (b == llcolor3.mV[2]))); + { + INFO("1:setVec(r,g,b) Fail "); + CHECK(r == llcolor3.mV[0]); + CHECK(g == llcolor3.mV[1]); + CHECK(b == llcolor3.mV[2]); + } llcolor3a.setVec(llcolor3); - ensure_equals("2:setVec(LLColor3) Fail ", llcolor3,llcolor3a); + UNSCOPED_INFO("2:setVec(LLColor3) Fail "); + CHECK(llcolor3 == llcolor3a); F32 vec[3] = {1.2324f, 2.45634f, .234563f}; llcolor3.setToBlack(); llcolor3.setVec(vec); - ensure("3:setVec(F32*) Fail ",((vec[0] == llcolor3.mV[0]) && (vec[1] == llcolor3.mV[1]) && (vec[2] == llcolor3.mV[2]))); + { + INFO("3:setVec(F32*) Fail "); + CHECK(vec[0] == llcolor3.mV[0]); + CHECK(vec[1] == llcolor3.mV[1]); + CHECK(vec[2] == llcolor3.mV[2]); + } } - template<> template<> - void v3color_object::test<4>() + TEST_CASE("v3color_h: magVecSquared and magVec", "[v3color_h]") { F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; LLColor3 llcolor3(r,g,b); - ensure("magVecSquared:Fail ", is_approx_equal(llcolor3.magVecSquared(), (r*r + g*g + b*b))); - ensure("magVec:Fail ", is_approx_equal(llcolor3.magVec(), (F32) sqrt(r*r + g*g + b*b))); + UNSCOPED_INFO("magVecSquared:Fail "); + CHECK(is_approx_equal(llcolor3.magVecSquared(), (r*r + g*g + b*b))); + UNSCOPED_INFO("magVec:Fail "); + CHECK(is_approx_equal(llcolor3.magVec(), (F32) sqrt(r*r + g*g + b*b))); } - template<> template<> - void v3color_object::test<5>() + TEST_CASE("v3color_h: normVec normalizes and leaves black at zero", "[v3color_h]") { F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; F32 val1, val2,val3; @@ -111,15 +139,26 @@ namespace val1 = r * oomag; val2 = g * oomag; val3 = b * oomag; - ensure("1:normVec failed ", (is_approx_equal(val1, llcolor3.mV[0]) && is_approx_equal(val2, llcolor3.mV[1]) && is_approx_equal(val3, llcolor3.mV[2]) && is_approx_equal(vecMag, mag))); + { + INFO("1:normVec failed "); + CHECK(is_approx_equal(val1, llcolor3.mV[0])); + CHECK(is_approx_equal(val2, llcolor3.mV[1])); + CHECK(is_approx_equal(val3, llcolor3.mV[2])); + CHECK(is_approx_equal(vecMag, mag)); + } r = .000000000f, g = 0.f, b = 0.0f; llcolor3.setVec(r,g,b); vecMag = llcolor3.normVec(); - ensure("2:normVec failed should be 0. ", (0. == llcolor3.mV[0] && 0. == llcolor3.mV[1] && 0. == llcolor3.mV[2] && vecMag == 0.)); + { + INFO("2:normVec failed should be 0. "); + CHECK(llcolor3.mV[0] == 0.); + CHECK(llcolor3.mV[1] == 0.); + CHECK(llcolor3.mV[2] == 0.); + CHECK(vecMag == 0.); + } } - template<> template<> - void v3color_object::test<6>() + TEST_CASE("v3color_h: operator<< prints equal colors the same", "[v3color_h]") { F32 r = 2.3436212f, g = -1231.f, b = .7849321232f; std::ostringstream stream1, stream2; @@ -127,151 +166,217 @@ namespace stream1 << llcolor3; llcolor3a.setVec(r,g,b); stream2 << llcolor3a; - ensure("operator << failed ", (stream1.str() == stream2.str())); + UNSCOPED_INFO("operator << failed "); + CHECK(stream1.str() == stream2.str()); } - template<> template<> - void v3color_object::test<7>() + TEST_CASE("v3color_h: assignment gives an equal color", "[v3color_h]") { F32 r = 2.3436212f, g = -1231.f, b = .7849321232f; LLColor3 llcolor3(r,g,b),llcolor3a; llcolor3a = llcolor3; - ensure("operator == failed ", (llcolor3a == llcolor3)); + UNSCOPED_INFO("operator == failed "); + CHECK(llcolor3a == llcolor3); } - template<> template<> - void v3color_object::test<8>() + TEST_CASE("v3color_h: operator+ adds component-wise", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2),llcolor3b; llcolor3b = llcolor3 + llcolor3a ; - ensure("1:operator+ failed",is_approx_equal(r1+r2 ,llcolor3b.mV[0]) && is_approx_equal(g1+g2,llcolor3b.mV[1])&& is_approx_equal(b1+b2,llcolor3b.mV[2])); + { + INFO("1:operator+ failed"); + CHECK(is_approx_equal(r1+r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1+g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1+b2,llcolor3b.mV[2])); + } r1 = -.235f, g1 = -24.32f, b1 = 2.13f, r2 = -2.3f, g2 = 1.f, b2 = 34.21f; llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); llcolor3b = llcolor3 + llcolor3a; - ensure("2:operator+ failed",is_approx_equal(r1+r2 ,llcolor3b.mV[0]) && is_approx_equal(g1+g2,llcolor3b.mV[1])&& is_approx_equal(b1+b2,llcolor3b.mV[2])); + { + INFO("2:operator+ failed"); + CHECK(is_approx_equal(r1+r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1+g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1+b2,llcolor3b.mV[2])); + } } - template<> template<> - void v3color_object::test<9>() + TEST_CASE("v3color_h: operator- subtracts component-wise", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2),llcolor3b; llcolor3b = llcolor3 - llcolor3a ; - ensure("1:operator- failed",is_approx_equal(r1-r2 ,llcolor3b.mV[0]) && is_approx_equal(g1-g2,llcolor3b.mV[1])&& is_approx_equal(b1-b2,llcolor3b.mV[2])); + { + INFO("1:operator- failed"); + CHECK(is_approx_equal(r1-r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1-g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1-b2,llcolor3b.mV[2])); + } r1 = -.235f, g1 = -24.32f, b1 = 2.13f, r2 = -2.3f, g2 = 1.f, b2 = 34.21f; llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); llcolor3b = llcolor3 - llcolor3a; - ensure("2:operator- failed",is_approx_equal(r1-r2 ,llcolor3b.mV[0]) && is_approx_equal(g1-g2,llcolor3b.mV[1])&& is_approx_equal(b1-b2,llcolor3b.mV[2])); + { + INFO("2:operator- failed"); + CHECK(is_approx_equal(r1-r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1-g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1-b2,llcolor3b.mV[2])); + } } - template<> template<> - void v3color_object::test<10>() + TEST_CASE("v3color_h: operator* by a color and by a scalar on either side", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2),llcolor3b; llcolor3b = llcolor3 * llcolor3a; - ensure("1:operator* failed",is_approx_equal(r1*r2 ,llcolor3b.mV[0]) && is_approx_equal(g1*g2,llcolor3b.mV[1])&& is_approx_equal(b1*b2,llcolor3b.mV[2])); + { + INFO("1:operator* failed"); + CHECK(is_approx_equal(r1*r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1*g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1*b2,llcolor3b.mV[2])); + } llcolor3a.setToBlack(); F32 mulVal = 4.332f; llcolor3a = llcolor3 * mulVal; - ensure("2:operator* failed",is_approx_equal(r1*mulVal ,llcolor3a.mV[0]) && is_approx_equal(g1*mulVal,llcolor3a.mV[1])&& is_approx_equal(b1*mulVal,llcolor3a.mV[2])); + { + INFO("2:operator* failed"); + CHECK(is_approx_equal(r1*mulVal ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1*mulVal,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1*mulVal,llcolor3a.mV[2])); + } llcolor3a.setToBlack(); llcolor3a = mulVal * llcolor3; - ensure("3:operator* failed",is_approx_equal(r1*mulVal ,llcolor3a.mV[0]) && is_approx_equal(g1*mulVal,llcolor3a.mV[1])&& is_approx_equal(b1*mulVal,llcolor3a.mV[2])); + { + INFO("3:operator* failed"); + CHECK(is_approx_equal(r1*mulVal ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1*mulVal,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1*mulVal,llcolor3a.mV[2])); + } } - template<> template<> - void v3color_object::test<11>() + TEST_CASE("v3color_h: unary minus negates", "[v3color_h]") { F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; LLColor3 llcolor3(r,g,b),llcolor3a; llcolor3a = -llcolor3; - ensure("operator- failed ", (-llcolor3a == llcolor3)); + UNSCOPED_INFO("operator- failed "); + CHECK(-llcolor3a == llcolor3); } - template<> template<> - void v3color_object::test<12>() + TEST_CASE("v3color_h: operator== on equal colors", "[v3color_h]") { F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; LLColor3 llcolor3(r,g,b),llcolor3a(r,g,b); - ensure_equals("1:operator== failed",llcolor3a,llcolor3); + UNSCOPED_INFO("1:operator== failed"); + CHECK(llcolor3a == llcolor3); r = 13.3436212f, g = -11.f, b = .7849321232f; llcolor3.setVec(r,g,b); llcolor3a.setVec(r,g,b); - ensure_equals("2:operator== failed",llcolor3a,llcolor3); + UNSCOPED_INFO("2:operator== failed"); + CHECK(llcolor3a == llcolor3); } - template<> template<> - void v3color_object::test<13>() + TEST_CASE("v3color_h: operator!= on different and equal colors", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); - ensure("1:operator!= failed",(llcolor3 != llcolor3a)); + UNSCOPED_INFO("1:operator!= failed"); + CHECK(llcolor3 != llcolor3a); llcolor3.setToBlack(); llcolor3a.setVec(llcolor3); - ensure("2:operator!= failed", ( false == (llcolor3a != llcolor3))); + UNSCOPED_INFO("2:operator!= failed"); + CHECK_FALSE(llcolor3a != llcolor3); } - template<> template<> - void v3color_object::test<14>() + TEST_CASE("v3color_h: operator+= adds component-wise", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); llcolor3a += llcolor3; - ensure("1:operator+= failed",is_approx_equal(r1+r2 ,llcolor3a.mV[0]) && is_approx_equal(g1+g2,llcolor3a.mV[1])&& is_approx_equal(b1+b2,llcolor3a.mV[2])); + { + INFO("1:operator+= failed"); + CHECK(is_approx_equal(r1+r2 ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1+g2,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1+b2,llcolor3a.mV[2])); + } llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); llcolor3a += llcolor3; - ensure("2:operator+= failed",is_approx_equal(r1+r2 ,llcolor3a.mV[0]) && is_approx_equal(g1+g2,llcolor3a.mV[1])&& is_approx_equal(b1+b2,llcolor3a.mV[2])); + { + INFO("2:operator+= failed"); + CHECK(is_approx_equal(r1+r2 ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1+g2,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1+b2,llcolor3a.mV[2])); + } } - template<> template<> - void v3color_object::test<15>() + TEST_CASE("v3color_h: operator-= subtracts component-wise", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); llcolor3a -= llcolor3; - ensure("1:operator-= failed", is_approx_equal(r2-r1, llcolor3a.mV[0])); - ensure("2:operator-= failed", is_approx_equal(g2-g1, llcolor3a.mV[1])); - ensure("3:operator-= failed", is_approx_equal(b2-b1, llcolor3a.mV[2])); + UNSCOPED_INFO("1:operator-= failed"); + CHECK(is_approx_equal(r2-r1, llcolor3a.mV[0])); + UNSCOPED_INFO("2:operator-= failed"); + CHECK(is_approx_equal(g2-g1, llcolor3a.mV[1])); + UNSCOPED_INFO("3:operator-= failed"); + CHECK(is_approx_equal(b2-b1, llcolor3a.mV[2])); llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); llcolor3a -= llcolor3; - ensure("4:operator-= failed", is_approx_equal(r2-r1, llcolor3a.mV[0])); - ensure("5:operator-= failed", is_approx_equal(g2-g1, llcolor3a.mV[1])); - ensure("6:operator-= failed", is_approx_equal(b2-b1, llcolor3a.mV[2])); + UNSCOPED_INFO("4:operator-= failed"); + CHECK(is_approx_equal(r2-r1, llcolor3a.mV[0])); + UNSCOPED_INFO("5:operator-= failed"); + CHECK(is_approx_equal(g2-g1, llcolor3a.mV[1])); + UNSCOPED_INFO("6:operator-= failed"); + CHECK(is_approx_equal(b2-b1, llcolor3a.mV[2])); } - template<> template<> - void v3color_object::test<16>() + TEST_CASE("v3color_h: operator*= by a color and by a scalar", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); llcolor3a *= llcolor3; - ensure("1:operator*= failed",is_approx_equal(r1*r2 ,llcolor3a.mV[0]) && is_approx_equal(g1*g2,llcolor3a.mV[1])&& is_approx_equal(b1*b2,llcolor3a.mV[2])); + { + INFO("1:operator*= failed"); + CHECK(is_approx_equal(r1*r2 ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1*g2,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1*b2,llcolor3a.mV[2])); + } F32 mulVal = 4.332f; llcolor3 *=mulVal; - ensure("2:operator*= failed",is_approx_equal(r1*mulVal ,llcolor3.mV[0]) && is_approx_equal(g1*mulVal,llcolor3.mV[1])&& is_approx_equal(b1*mulVal,llcolor3.mV[2])); + { + INFO("2:operator*= failed"); + CHECK(is_approx_equal(r1*mulVal ,llcolor3.mV[0])); + CHECK(is_approx_equal(g1*mulVal,llcolor3.mV[1])); + CHECK(is_approx_equal(b1*mulVal,llcolor3.mV[2])); + } } - template<> template<> - void v3color_object::test<17>() + TEST_CASE("v3color_h: clamp limits components to the 0 to 1 range", "[v3color_h]") { F32 r = 2.3436212f, g = -1231.f, b = .7849321232f; LLColor3 llcolor3(r,g,b); llcolor3.clamp(); - ensure("1:clamp:Fail to clamp " ,(1.0f == llcolor3.mV[0]) && (0.f == llcolor3.mV[1]) && (b == llcolor3.mV[2])); + { + INFO("1:clamp:Fail to clamp "); + CHECK(llcolor3.mV[0] == 1.0f); + CHECK(llcolor3.mV[1] == 0.f); + CHECK(b == llcolor3.mV[2]); + } r = -2.3436212f, g = -1231.f, b = 67.7849321232f; llcolor3.setVec(r,g,b); llcolor3.clamp(); - ensure("2:clamp:Fail to clamp " ,(0.f == llcolor3.mV[0]) && (0.f == llcolor3.mV[1]) && (1.f == llcolor3.mV[2])); + { + INFO("2:clamp:Fail to clamp "); + CHECK(llcolor3.mV[0] == 0.f); + CHECK(llcolor3.mV[1] == 0.f); + CHECK(llcolor3.mV[2] == 1.f); + } } - template<> template<> - void v3color_object::test<18>() + TEST_CASE("v3color_h: lerp interpolates every component", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; F32 val = 2.3f,val1,val2,val3; @@ -280,23 +385,28 @@ namespace val2 = g1 + (g2 - g1)* val; val3 = b1 + (b2 - b1)* val; LLColor3 llcolor3b = lerp(llcolor3,llcolor3a,val); - ensure("lerp failed ", ((val1 ==llcolor3b.mV[0])&& (val2 ==llcolor3b.mV[1]) && (val3 ==llcolor3b.mV[2]))); + { + INFO("lerp failed "); + CHECK(val1 ==llcolor3b.mV[0]); + CHECK(val2 ==llcolor3b.mV[1]); + CHECK(val3 ==llcolor3b.mV[2]); + } } - template<> template<> - void v3color_object::test<19>() + TEST_CASE("v3color_h: distVec and distVec_squared", "[v3color_h]") { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); F32 val = distVec(llcolor3,llcolor3a); - ensure("distVec failed ", is_approx_equal((F32) sqrt((r1-r2)*(r1-r2) + (g1-g2)*(g1-g2) + (b1-b2)*(b1-b2)) ,val)); + UNSCOPED_INFO("distVec failed "); + CHECK(is_approx_equal((F32) sqrt((r1-r2)*(r1-r2) + (g1-g2)*(g1-g2) + (b1-b2)*(b1-b2)) ,val)); F32 val1 = distVec_squared(llcolor3,llcolor3a); - ensure("distVec_squared failed ", is_approx_equal(((r1-r2)*(r1-r2) + (g1-g2)*(g1-g2) + (b1-b2)*(b1-b2)) ,val1)); + UNSCOPED_INFO("distVec_squared failed "); + CHECK(is_approx_equal(((r1-r2)*(r1-r2) + (g1-g2)*(g1-g2) + (b1-b2)*(b1-b2)) ,val1)); } - template<> template<> - void v3color_object::test<20>() + TEST_CASE("v3color_h: getValue round-trips through setValue and the LLSD constructor", "[v3color_h]") { F32 r1 = 1.02223f, g1 = 22222.212f, b1 = 122222.00002f; LLColor3 llcolor31(r1,g1,b1); @@ -304,9 +414,11 @@ namespace LLSD sd = llcolor31.getValue(); LLColor3 llcolor32; llcolor32.setValue(sd); - ensure_equals("LLColor3::setValue/getValue failed", llcolor31, llcolor32); + UNSCOPED_INFO("LLColor3::setValue/getValue failed"); + CHECK(llcolor31 == llcolor32); LLColor3 llcolor33(sd); - ensure_equals("LLColor3(LLSD) failed", llcolor31, llcolor33); + UNSCOPED_INFO("LLColor3(LLSD) failed"); + CHECK(llcolor31 == llcolor33); } } diff --git a/indra/llmath/tests/v3dmath_test.cpp b/indra/llmath/tests/v3dmath_test.cpp index 10ee4c3523..215e505b50 100644 --- a/indra/llmath/tests/v3dmath_test.cpp +++ b/indra/llmath/tests/v3dmath_test.cpp @@ -28,7 +28,8 @@ #include "linden_common.h" #include "llsd.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../m3math.h" #include "../v4math.h" @@ -36,47 +37,51 @@ #include "../v3dmath.h" #include "../llquaternion.h" -namespace tut -{ namespace { - struct v3dmath_data - { - }; - typedef test_group v3dmath_test; - typedef v3dmath_test::object v3dmath_object; - tut::v3dmath_test v3dmath_testcase("v3dmath_h"); -} - - template<> template<> - void v3dmath_object::test<1>() + TEST_CASE("v3dmath_h: constructors set every component", "[v3dmath_h]") { LLVector3d vec3D; - ensure("1:LLVector3d:Fail to initialize ", ((0 == vec3D.mdV[VX]) && (0 == vec3D.mdV[VY]) && (0 == vec3D.mdV[VZ]))); + { + INFO("1:LLVector3d:Fail to initialize "); + CHECK(vec3D.mdV[VX] == 0); + CHECK(vec3D.mdV[VY] == 0); + CHECK(vec3D.mdV[VZ] == 0); + } F64 x = 2.32f, y = 1.212f, z = -.12f; LLVector3d vec3Da(x,y,z); - ensure("2:LLVector3d:Fail to initialize ", ((2.32f == vec3Da.mdV[VX]) && (1.212f == vec3Da.mdV[VY]) && (-.12f == vec3Da.mdV[VZ]))); + { + INFO("2:LLVector3d:Fail to initialize "); + CHECK(vec3Da.mdV[VX] == 2.32f); + CHECK(vec3Da.mdV[VY] == 1.212f); + CHECK(vec3Da.mdV[VZ] == -.12f); + } const F64 vec[3] = {1.2f ,3.2f, -4.2f}; LLVector3d vec3Db(vec); - ensure("3:LLVector3d:Fail to initialize ", ((1.2f == vec3Db.mdV[VX]) && (3.2f == vec3Db.mdV[VY]) && (-4.2f == vec3Db.mdV[VZ]))); + { + INFO("3:LLVector3d:Fail to initialize "); + CHECK(vec3Db.mdV[VX] == 1.2f); + CHECK(vec3Db.mdV[VY] == 3.2f); + CHECK(vec3Db.mdV[VZ] == -4.2f); + } LLVector3 vec3((F32)x,(F32)y,(F32)z); LLVector3d vec3Dc(vec3); - ensure_equals("4:LLVector3d Fail to initialize",vec3Da,vec3Dc); + UNSCOPED_INFO("4:LLVector3d Fail to initialize"); + CHECK(vec3Da == vec3Dc); } - template<> template<> - void v3dmath_object::test<2>() + TEST_CASE("v3dmath_h: LLSD constructor and getValue round trip", "[v3dmath_h]") { S32 a = -235; LLSD llsd(a); LLVector3d vec3d(llsd); LLSD sd = vec3d.getValue(); LLVector3d vec3da(sd); - ensure("1:getValue:Fail ", (vec3d == vec3da)); + UNSCOPED_INFO("1:getValue:Fail "); + CHECK(vec3d == vec3da); } - template<> template<> - void v3dmath_object::test<3>() + TEST_CASE("v3dmath_h: setValue and getValue round trip", "[v3dmath_h]") { F64 a = 232345521.411132; LLSD llsd(a); @@ -84,11 +89,11 @@ namespace vec3d.setValue(llsd); LLSD sd = vec3d.getValue(); LLVector3d vec3da(sd); - ensure("1:setValue:Fail to initialize ", (vec3d == vec3da)); + UNSCOPED_INFO("1:setValue:Fail to initialize "); + CHECK(vec3d == vec3da); } - template<> template<> - void v3dmath_object::test<4>() + TEST_CASE("v3dmath_h: cast from an LLSD array copies each element", "[v3dmath_h]") { F64 a[3] = {222231.43222, 12345.2343, -434343.33222}; LLSD llsd; @@ -97,183 +102,242 @@ namespace llsd[2] = a[2]; LLVector3d vec3D; vec3D = (LLVector3d)llsd; - ensure("1:operator=:Fail to initialize ", ((llsd[0].asReal()== vec3D.mdV[VX]) && (llsd[1].asReal() == vec3D.mdV[VY]) && (llsd[2].asReal() == vec3D.mdV[VZ]))); + { + INFO("1:operator=:Fail to initialize "); + CHECK(llsd[0].asReal()== vec3D.mdV[VX]); + CHECK(llsd[1].asReal() == vec3D.mdV[VY]); + CHECK(llsd[2].asReal() == vec3D.mdV[VZ]); + } } - template<> template<> - void v3dmath_object::test<5>() + TEST_CASE("v3dmath_h: clearVec/zeroVec and the setVec overloads", "[v3dmath_h]") { F64 x = 2.32f, y = 1.212f, z = -.12f; LLVector3d vec3D(x,y,z); vec3D.clearVec(); - ensure("1:clearVec:Fail to initialize ", ((0 == vec3D.mdV[VX]) && (0 == vec3D.mdV[VY]) && (0 == vec3D.mdV[VZ]))); + { + INFO("1:clearVec:Fail to initialize "); + CHECK(vec3D.mdV[VX] == 0); + CHECK(vec3D.mdV[VY] == 0); + CHECK(vec3D.mdV[VZ] == 0); + } vec3D.setVec(x,y,z); - ensure("2:setVec:Fail to initialize ", ((x == vec3D.mdV[VX]) && (y == vec3D.mdV[VY]) && (z == vec3D.mdV[VZ]))); + { + INFO("2:setVec:Fail to initialize "); + CHECK(x == vec3D.mdV[VX]); + CHECK(y == vec3D.mdV[VY]); + CHECK(z == vec3D.mdV[VZ]); + } vec3D.zeroVec(); - ensure("3:zeroVec:Fail to initialize ", ((0 == vec3D.mdV[VX]) && (0 == vec3D.mdV[VY]) && (0 == vec3D.mdV[VZ]))); + { + INFO("3:zeroVec:Fail to initialize "); + CHECK(vec3D.mdV[VX] == 0); + CHECK(vec3D.mdV[VY] == 0); + CHECK(vec3D.mdV[VZ] == 0); + } vec3D.clearVec(); LLVector3 vec3((F32)x,(F32)y,(F32)z); vec3D.setVec(vec3); - ensure("4:setVec:Fail to initialize ", ((x == vec3D.mdV[VX]) && (y == vec3D.mdV[VY]) && (z == vec3D.mdV[VZ]))); + { + INFO("4:setVec:Fail to initialize "); + CHECK(x == vec3D.mdV[VX]); + CHECK(y == vec3D.mdV[VY]); + CHECK(z == vec3D.mdV[VZ]); + } vec3D.clearVec(); const F64 vec[3] = {x,y,z}; vec3D.setVec(vec); - ensure("5:setVec:Fail to initialize ", ((x == vec3D.mdV[VX]) && (y == vec3D.mdV[VY]) && (z == vec3D.mdV[VZ]))); + { + INFO("5:setVec:Fail to initialize "); + CHECK(x == vec3D.mdV[VX]); + CHECK(y == vec3D.mdV[VY]); + CHECK(z == vec3D.mdV[VZ]); + } LLVector3d vec3Da; vec3Da.setVec(vec3D); - ensure_equals("6:setVec: Fail to initialize", vec3D, vec3Da); + UNSCOPED_INFO("6:setVec: Fail to initialize"); + CHECK(vec3D == vec3Da); } - template<> template<> - void v3dmath_object::test<6>() + TEST_CASE("v3dmath_h: abs then isNull and isExactlyZero", "[v3dmath_h]") { F64 x = -2.32, y = 1.212, z = -.12; LLVector3d vec3D(x,y,z); vec3D.abs(); - ensure("1:abs:Fail ", ((-x == vec3D.mdV[VX]) && (y == vec3D.mdV[VY]) && (-z == vec3D.mdV[VZ]))); - ensure("2:isNull():Fail ", (false == vec3D.isNull())); + { + INFO("1:abs:Fail "); + CHECK(-x == vec3D.mdV[VX]); + CHECK(y == vec3D.mdV[VY]); + CHECK(-z == vec3D.mdV[VZ]); + } + UNSCOPED_INFO("2:isNull():Fail "); + CHECK_FALSE(vec3D.isNull()); vec3D.clearVec(); x =.00000001, y = .000001001, z = .000001001; vec3D.setVec(x,y,z); - ensure("3:isNull():Fail ", (true == vec3D.isNull())); - ensure("4:isExactlyZero():Fail ", (false == vec3D.isExactlyZero())); + UNSCOPED_INFO("3:isNull():Fail "); + CHECK(vec3D.isNull()); + UNSCOPED_INFO("4:isExactlyZero():Fail "); + CHECK_FALSE(vec3D.isExactlyZero()); x =.0000000, y = .00000000, z = .00000000; vec3D.setVec(x,y,z); - ensure("5:isExactlyZero():Fail ", (true == vec3D.isExactlyZero())); + UNSCOPED_INFO("5:isExactlyZero():Fail "); + CHECK(vec3D.isExactlyZero()); } - template<> template<> - void v3dmath_object::test<7>() + TEST_CASE("v3dmath_h: subscript operator reads components and returns references", "[v3dmath_h]") { F64 x = -2.32, y = 1.212, z = -.12; LLVector3d vec3D(x,y,z); - ensure("1:operator [] failed",( x == vec3D[0])); - ensure("2:operator [] failed",( y == vec3D[1])); - ensure("3:operator [] failed",( z == vec3D[2])); + UNSCOPED_INFO("1:operator [] failed"); + CHECK(x == vec3D[0]); + UNSCOPED_INFO("2:operator [] failed"); + CHECK(y == vec3D[1]); + UNSCOPED_INFO("3:operator [] failed"); + CHECK(z == vec3D[2]); vec3D.clearVec(); x = 23.23, y = -.2361, z = 3.25; vec3D.setVec(x,y,z); F64 &ref1 = vec3D[0]; - ensure("4:operator [] failed",( ref1 == vec3D[0])); + UNSCOPED_INFO("4:operator [] failed"); + CHECK(ref1 == vec3D[0]); F64 &ref2 = vec3D[1]; - ensure("5:operator [] failed",( ref2 == vec3D[1])); + UNSCOPED_INFO("5:operator [] failed"); + CHECK(ref2 == vec3D[1]); F64 &ref3 = vec3D[2]; - ensure("6:operator [] failed",( ref3 == vec3D[2])); + UNSCOPED_INFO("6:operator [] failed"); + CHECK(ref3 == vec3D[2]); } - template<> template<> - void v3dmath_object::test<8>() + TEST_CASE("v3dmath_h: assignment from LLVector4", "[v3dmath_h]") { F32 x = 1.f, y = 2.f, z = -1.f; LLVector4 vec4(x,y,z); LLVector3d vec3D; vec3D = vec4; - ensure("1:operator=:Fail to initialize ", ((vec4.mV[VX] == vec3D.mdV[VX]) && (vec4.mV[VY] == vec3D.mdV[VY]) && (vec4.mV[VZ] == vec3D.mdV[VZ]))); + { + INFO("1:operator=:Fail to initialize "); + CHECK(vec4.mV[VX] == vec3D.mdV[VX]); + CHECK(vec4.mV[VY] == vec3D.mdV[VY]); + CHECK(vec4.mV[VZ] == vec3D.mdV[VZ]); + } } - template<> template<> - void v3dmath_object::test<9>() + TEST_CASE("v3dmath_h: operator+ and operator+=", "[v3dmath_h]") { F64 x1 = 1.78787878, y1 = 232322.2121, z1 = -12121.121212; F64 x2 = 1.2, y2 = 2.5, z2 = 1.; LLVector3d vec3D(x1,y1,z1),vec3Da(x2,y2,z2),vec3Db; vec3Db = vec3Da+ vec3D; - ensure("1:operator+:Fail to initialize ", ((x1+x2 == vec3Db.mdV[VX]) && (y1+y2 == vec3Db.mdV[VY]) && (z1+z2 == vec3Db.mdV[VZ]))); + { + INFO("1:operator+:Fail to initialize "); + CHECK(x1+x2 == vec3Db.mdV[VX]); + CHECK(y1+y2 == vec3Db.mdV[VY]); + CHECK(z1+z2 == vec3Db.mdV[VZ]); + } x1 = -2.45, y1 = 2.1, z1 = 3.0; vec3D.clearVec(); vec3Da.clearVec(); vec3D.setVec(x1,y1,z1); vec3Da += vec3D; - ensure_equals("2:operator+=: Fail to initialize", vec3Da,vec3D); + UNSCOPED_INFO("2:operator+=: Fail to initialize"); + CHECK(vec3Da == vec3D); vec3Da += vec3D; - ensure("3:operator+=:Fail to initialize ", ((2*x1 == vec3Da.mdV[VX]) && (2*y1 == vec3Da.mdV[VY]) && (2*z1 == vec3Da.mdV[VZ]))); + { + INFO("3:operator+=:Fail to initialize "); + CHECK(2*x1 == vec3Da.mdV[VX]); + CHECK(2*y1 == vec3Da.mdV[VY]); + CHECK(2*z1 == vec3Da.mdV[VZ]); + } } - template<> template<> - void v3dmath_object::test<10>() + TEST_CASE("v3dmath_h: operator- and operator-=", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; F64 x2 = 1.2, y2 = 2.5, z2 = 1.; LLVector3d vec3D(x1,y1,z1),vec3Da(x2,y2,z2),vec3Db; vec3Db = vec3Da - vec3D; - ensure("1:operator-:Fail to initialize ", ((x2-x1 == vec3Db.mdV[VX]) && (y2-y1 == vec3Db.mdV[VY]) && (z2-z1 == vec3Db.mdV[VZ]))); + { + INFO("1:operator-:Fail to initialize "); + CHECK(x2-x1 == vec3Db.mdV[VX]); + CHECK(y2-y1 == vec3Db.mdV[VY]); + CHECK(z2-z1 == vec3Db.mdV[VZ]); + } x1 = -2.45, y1 = 2.1, z1 = 3.0; vec3D.clearVec(); vec3Da.clearVec(); vec3D.setVec(x1,y1,z1); vec3Da -=vec3D; - ensure("2:operator-=:Fail to initialize ", ((2.45 == vec3Da.mdV[VX]) && (-2.1 == vec3Da.mdV[VY]) && (-3.0 == vec3Da.mdV[VZ]))); + { + INFO("2:operator-=:Fail to initialize "); + CHECK(vec3Da.mdV[VX] == 2.45); + CHECK(vec3Da.mdV[VY] == -2.1); + CHECK(vec3Da.mdV[VZ] == -3.0); + } vec3Da -= vec3D; - ensure("3:operator-=:Fail to initialize ", ((-2*x1 == vec3Da.mdV[VX]) && (-2*y1 == vec3Da.mdV[VY]) && (-2*z1 == vec3Da.mdV[VZ]))); + { + INFO("3:operator-=:Fail to initialize "); + CHECK(-2*x1 == vec3Da.mdV[VX]); + CHECK(-2*y1 == vec3Da.mdV[VY]); + CHECK(-2*z1 == vec3Da.mdV[VZ]); + } } - template<> template<> - void v3dmath_object::test<11>() + TEST_CASE("v3dmath_h: operator* dot and scalar products and operator*=", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; F64 x2 = 1.2, y2 = 2.5, z2 = 1.; LLVector3d vec3D(x1,y1,z1),vec3Da(x2,y2,z2); F64 res = vec3D * vec3Da; - ensure_approximately_equals( - "1:operator* failed", - res, - (x1*x2 + y1*y2 + z1*z2), - 8); + UNSCOPED_INFO("1:operator* failed"); + CHECK_THAT(res, + ll_test::WithinBits((x1*x2 + y1*y2 + z1*z2), + 8)); vec3Da.clearVec(); F64 mulVal = 4.2; vec3Da = vec3D * mulVal; - ensure_approximately_equals( - "2a:operator* failed", - vec3Da.mdV[VX], - x1*mulVal, - 8); - ensure_approximately_equals( - "2b:operator* failed", - vec3Da.mdV[VY], - y1*mulVal, - 8); - ensure_approximately_equals( - "2c:operator* failed", - vec3Da.mdV[VZ], - z1*mulVal, - 8); + UNSCOPED_INFO("2a:operator* failed"); + CHECK_THAT(vec3Da.mdV[VX], + ll_test::WithinBits(x1*mulVal, + 8)); + UNSCOPED_INFO("2b:operator* failed"); + CHECK_THAT(vec3Da.mdV[VY], + ll_test::WithinBits(y1*mulVal, + 8)); + UNSCOPED_INFO("2c:operator* failed"); + CHECK_THAT(vec3Da.mdV[VZ], + ll_test::WithinBits(z1*mulVal, + 8)); vec3Da.clearVec(); vec3Da = mulVal * vec3D; - ensure_approximately_equals( - "3a:operator* failed", - vec3Da.mdV[VX], - x1*mulVal, - 8); - ensure_approximately_equals( - "3b:operator* failed", - vec3Da.mdV[VY], - y1*mulVal, - 8); - ensure_approximately_equals( - "3c:operator* failed", - vec3Da.mdV[VZ], - z1*mulVal, - 8); + UNSCOPED_INFO("3a:operator* failed"); + CHECK_THAT(vec3Da.mdV[VX], + ll_test::WithinBits(x1*mulVal, + 8)); + UNSCOPED_INFO("3b:operator* failed"); + CHECK_THAT(vec3Da.mdV[VY], + ll_test::WithinBits(y1*mulVal, + 8)); + UNSCOPED_INFO("3c:operator* failed"); + CHECK_THAT(vec3Da.mdV[VZ], + ll_test::WithinBits(z1*mulVal, + 8)); vec3D *= mulVal; - ensure_approximately_equals( - "4a:operator*= failed", - vec3D.mdV[VX], - x1*mulVal, - 8); - ensure_approximately_equals( - "4b:operator*= failed", - vec3D.mdV[VY], - y1*mulVal, - 8); - ensure_approximately_equals( - "4c:operator*= failed", - vec3D.mdV[VZ], - z1*mulVal, - 8); + UNSCOPED_INFO("4a:operator*= failed"); + CHECK_THAT(vec3D.mdV[VX], + ll_test::WithinBits(x1*mulVal, + 8)); + UNSCOPED_INFO("4b:operator*= failed"); + CHECK_THAT(vec3D.mdV[VY], + ll_test::WithinBits(y1*mulVal, + 8)); + UNSCOPED_INFO("4c:operator*= failed"); + CHECK_THAT(vec3D.mdV[VZ], + ll_test::WithinBits(z1*mulVal, + 8)); } - template<> template<> - void v3dmath_object::test<12>() + TEST_CASE("v3dmath_h: operator% and operator%= cross products", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; F64 x2 = 1.2, y2 = 2.5, z2 = 1.; @@ -283,93 +347,92 @@ namespace val1 = y1*z2 - y2*z1; val2 = z1*x2 -z2*x1; val3 = x1*y2-x2*y1; - ensure("1:operator% failed",(val1 == vec3Db.mdV[VX]) && (val2 == vec3Db.mdV[VY]) && (val3 == vec3Db.mdV[VZ])); + { + INFO("1:operator% failed"); + CHECK(val1 == vec3Db.mdV[VX]); + CHECK(val2 == vec3Db.mdV[VY]); + CHECK(val3 == vec3Db.mdV[VZ]); + } vec3D %= vec3Da; - ensure("2:operator%= failed", - is_approx_equal(vec3D.mdV[VX],vec3Db.mdV[VX]) && - is_approx_equal(vec3D.mdV[VY],vec3Db.mdV[VY]) && - is_approx_equal(vec3D.mdV[VZ],vec3Db.mdV[VZ]) ); + { + INFO("2:operator%= failed"); + CHECK(is_approx_equal(vec3D.mdV[VX],vec3Db.mdV[VX])); + CHECK(is_approx_equal(vec3D.mdV[VY],vec3Db.mdV[VY])); + CHECK(is_approx_equal(vec3D.mdV[VZ],vec3Db.mdV[VZ])); + } } - template<> template<> - void v3dmath_object::test<13>() + TEST_CASE("v3dmath_h: operator/ and operator/= divide by a scalar", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1,div = 4.2; F64 t = 1.f / div; LLVector3d vec3D(x1,y1,z1), vec3Da; vec3Da = vec3D/div; - ensure_approximately_equals( - "1a:operator/ failed", - vec3Da.mdV[VX], - x1*t, - 8); - ensure_approximately_equals( - "1b:operator/ failed", - vec3Da.mdV[VY], - y1*t, - 8); - ensure_approximately_equals( - "1c:operator/ failed", - vec3Da.mdV[VZ], - z1*t, - 8); + UNSCOPED_INFO("1a:operator/ failed"); + CHECK_THAT(vec3Da.mdV[VX], + ll_test::WithinBits(x1*t, + 8)); + UNSCOPED_INFO("1b:operator/ failed"); + CHECK_THAT(vec3Da.mdV[VY], + ll_test::WithinBits(y1*t, + 8)); + UNSCOPED_INFO("1c:operator/ failed"); + CHECK_THAT(vec3Da.mdV[VZ], + ll_test::WithinBits(z1*t, + 8)); x1 = 1.23, y1 = 4., z1 = -2.32; vec3D.clearVec(); vec3Da.clearVec(); vec3D.setVec(x1,y1,z1); vec3Da = vec3D/div; - ensure_approximately_equals( - "2a:operator/ failed", - vec3Da.mdV[VX], - x1*t, - 8); - ensure_approximately_equals( - "2b:operator/ failed", - vec3Da.mdV[VY], - y1*t, - 8); - ensure_approximately_equals( - "2c:operator/ failed", - vec3Da.mdV[VZ], - z1*t, - 8); + UNSCOPED_INFO("2a:operator/ failed"); + CHECK_THAT(vec3Da.mdV[VX], + ll_test::WithinBits(x1*t, + 8)); + UNSCOPED_INFO("2b:operator/ failed"); + CHECK_THAT(vec3Da.mdV[VY], + ll_test::WithinBits(y1*t, + 8)); + UNSCOPED_INFO("2c:operator/ failed"); + CHECK_THAT(vec3Da.mdV[VZ], + ll_test::WithinBits(z1*t, + 8)); vec3D /= div; - ensure_approximately_equals( - "3a:operator/= failed", - vec3D.mdV[VX], - x1*t, - 8); - ensure_approximately_equals( - "3b:operator/= failed", - vec3D.mdV[VY], - y1*t, - 8); - ensure_approximately_equals( - "3c:operator/= failed", - vec3D.mdV[VZ], - z1*t, - 8); + UNSCOPED_INFO("3a:operator/= failed"); + CHECK_THAT(vec3D.mdV[VX], + ll_test::WithinBits(x1*t, + 8)); + UNSCOPED_INFO("3b:operator/= failed"); + CHECK_THAT(vec3D.mdV[VY], + ll_test::WithinBits(y1*t, + 8)); + UNSCOPED_INFO("3c:operator/= failed"); + CHECK_THAT(vec3D.mdV[VZ], + ll_test::WithinBits(z1*t, + 8)); } - template<> template<> - void v3dmath_object::test<14>() + TEST_CASE("v3dmath_h: operator== and operator!=", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; LLVector3d vec3D(x1,y1,z1), vec3Da; - ensure("1:operator!= failed",(true == (vec3D !=vec3Da))); + UNSCOPED_INFO("1:operator!= failed"); + CHECK(vec3D !=vec3Da); vec3Da = vec3D; - ensure("2:operator== failed",(vec3D ==vec3Da)); + UNSCOPED_INFO("2:operator== failed"); + CHECK(vec3D ==vec3Da); vec3D.clearVec(); vec3Da.clearVec(); x1 = .211, y1 = 21.111, z1 = 23.22; vec3D.setVec(x1,y1,z1); vec3Da.setVec(x1,y1,z1); - ensure("3:operator== failed",(vec3D ==vec3Da)); - ensure("4:operator!= failed",(false == (vec3D !=vec3Da))); + UNSCOPED_INFO("3:operator== failed"); + CHECK(vec3D ==vec3Da); + UNSCOPED_INFO("4:operator!= failed"); + CHECK_FALSE(vec3D !=vec3Da); } - template<> template<> - void v3dmath_object::test<15>() + TEST_CASE("v3dmath_h: operator<< prints equal vectors alike", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; LLVector3d vec3D(x1,y1,z1), vec3Da; @@ -377,84 +440,84 @@ namespace stream1 << vec3D; vec3Da.setVec(x1,y1,z1); stream2 << vec3Da; - ensure("1:operator << failed",(stream1.str() == stream2.str())); + UNSCOPED_INFO("1:operator << failed"); + CHECK(stream1.str() == stream2.str()); } - template<> template<> - void v3dmath_object::test<16>() + TEST_CASE("v3dmath_h: parseVector3d reads three numbers", "[v3dmath_h]") { F64 x1 = 1.23, y1 = 2.0, z1 = 4.; std::string buf("1.23 2. 4"); LLVector3d vec3D, vec3Da(x1,y1,z1); LLVector3d::parseVector3d(buf, &vec3D); - ensure_equals("1:parseVector3d: failed " , vec3D, vec3Da); + UNSCOPED_INFO("1:parseVector3d: failed "); + CHECK(vec3D == vec3Da); } - template<> template<> - void v3dmath_object::test<17>() + TEST_CASE("v3dmath_h: unary operator- negates", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; LLVector3d vec3D(x1,y1,z1), vec3Da; vec3Da = -vec3D; - ensure("1:operator- failed", (vec3D == - vec3Da)); + UNSCOPED_INFO("1:operator- failed"); + CHECK(vec3D == - vec3Da); } - template<> template<> - void v3dmath_object::test<18>() + TEST_CASE("v3dmath_h: magVecSquared and magVec", "[v3dmath_h]") { F64 x = 1., y = 2., z = -1.1; LLVector3d vec3D(x,y,z); F64 res = (x*x + y*y + z*z) - vec3D.magVecSquared(); - ensure("1:magVecSquared:Fail ", ((-F_APPROXIMATELY_ZERO <= res)&& (res <=F_APPROXIMATELY_ZERO))); + { + INFO("1:magVecSquared:Fail "); + CHECK(-F_APPROXIMATELY_ZERO <= res); + CHECK(res <=F_APPROXIMATELY_ZERO); + } res = (F32) sqrt(x*x + y*y + z*z) - vec3D.magVec(); - ensure("2:magVec: Fail ", ((-F_APPROXIMATELY_ZERO <= res)&& (res <=F_APPROXIMATELY_ZERO))); + { + INFO("2:magVec: Fail "); + CHECK(-F_APPROXIMATELY_ZERO <= res); + CHECK(res <=F_APPROXIMATELY_ZERO); + } } - template<> template<> - void v3dmath_object::test<19>() + TEST_CASE("v3dmath_h: normVec normalises long and tiny vectors", "[v3dmath_h]") { F64 x = 1., y = 2., z = -1.1; LLVector3d vec3D(x,y,z); F64 mag = vec3D.normVec(); mag = 1.f/ mag; - ensure_approximately_equals( - "1a:normVec: Fail ", - vec3D.mdV[VX], - x * mag, - 8); - ensure_approximately_equals( - "1b:normVec: Fail ", - vec3D.mdV[VY], - y * mag, - 8); - ensure_approximately_equals( - "1c:normVec: Fail ", - vec3D.mdV[VZ], - z * mag, - 8); + UNSCOPED_INFO("1a:normVec: Fail "); + CHECK_THAT(vec3D.mdV[VX], + ll_test::WithinBits(x * mag, + 8)); + UNSCOPED_INFO("1b:normVec: Fail "); + CHECK_THAT(vec3D.mdV[VY], + ll_test::WithinBits(y * mag, + 8)); + UNSCOPED_INFO("1c:normVec: Fail "); + CHECK_THAT(vec3D.mdV[VZ], + ll_test::WithinBits(z * mag, + 8)); x = 0.000000001, y = 0.000000001, z = 0.000000001; vec3D.clearVec(); vec3D.setVec(x,y,z); mag = vec3D.normVec(); - ensure_approximately_equals( - "2a:normVec: Fail ", - vec3D.mdV[VX], - x * mag, - 8); - ensure_approximately_equals( - "2b:normVec: Fail ", - vec3D.mdV[VY], - y * mag, - 8); - ensure_approximately_equals( - "2c:normVec: Fail ", - vec3D.mdV[VZ], - z * mag, - 8); + UNSCOPED_INFO("2a:normVec: Fail "); + CHECK_THAT(vec3D.mdV[VX], + ll_test::WithinBits(x * mag, + 8)); + UNSCOPED_INFO("2b:normVec: Fail "); + CHECK_THAT(vec3D.mdV[VY], + ll_test::WithinBits(y * mag, + 8)); + UNSCOPED_INFO("2c:normVec: Fail "); + CHECK_THAT(vec3D.mdV[VZ], + ll_test::WithinBits(z * mag, + 8)); } - template<> template<> - void v3dmath_object::test<20>() + TEST_CASE("v3dmath_h: parseVector3d reads large values exactly", "[v3dmath_h]") { F64 x1 = 1111.232222; F64 y1 = 2222222222.22; @@ -462,11 +525,11 @@ namespace std::string buf("1111.232222 2222222222.22 422222222222"); LLVector3d vec3Da, vec3Db(x1,y1,z1); LLVector3d::parseVector3d(buf, &vec3Da); - ensure_equals("1:parseVector3 failed", vec3Da, vec3Db); + UNSCOPED_INFO("1:parseVector3 failed"); + CHECK(vec3Da == vec3Db); } - template<> template<> - void v3dmath_object::test<21>() + TEST_CASE("v3dmath_h: lerp between two vectors", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; F64 x2 = 1.2, y2 = 2.5, z2 = 1.; @@ -476,42 +539,49 @@ namespace val3 = z1 + (z2 - z1)* val; LLVector3d vec3Da(x1,y1,z1),vec3Db(x2,y2,z2); LLVector3d vec3d = lerp(vec3Da,vec3Db,val); - ensure("1:lerp failed", ((val1 ==vec3d.mdV[VX])&& (val2 ==vec3d.mdV[VY]) && (val3 ==vec3d.mdV[VZ]))); + { + INFO("1:lerp failed"); + CHECK(val1 ==vec3d.mdV[VX]); + CHECK(val2 ==vec3d.mdV[VY]); + CHECK(val3 ==vec3d.mdV[VZ]); + } } - template<> template<> - void v3dmath_object::test<22>() + TEST_CASE("v3dmath_h: clamp reports a component out of range", "[v3dmath_h]") { F64 x = 2.32, y = 1.212, z = -.12; F64 min = 0.0001, max = 3.0; LLVector3d vec3d(x,y,z); - ensure("1:clamp:Fail ", (true == (vec3d.clamp(min, max)))); + UNSCOPED_INFO("1:clamp:Fail "); + CHECK(vec3d.clamp(min, max)); x = 0.000001f, z = 5.3f; vec3d.setVec(x,y,z); - ensure("2:clamp:Fail ", (true == (vec3d.clamp(min, max)))); + UNSCOPED_INFO("2:clamp:Fail "); + CHECK(vec3d.clamp(min, max)); } - template<> template<> - void v3dmath_object::test<23>() + TEST_CASE("v3dmath_h: are_parallel for equal and non-parallel vectors", "[v3dmath_h]") { F64 x = 10., y = 20., z = -15.; F64 epsilon = .23425; LLVector3d vec3Da(x,y,z), vec3Db(x,y,z); - ensure("1:are_parallel: Fail ", (true == are_parallel(vec3Da,vec3Db,epsilon))); + UNSCOPED_INFO("1:are_parallel: Fail "); + CHECK(are_parallel(vec3Da,vec3Db,epsilon)); F64 x1 = -12., y1 = -20., z1 = -100.; vec3Db.clearVec(); vec3Db.setVec(x1,y1,z1); - ensure("2:are_parallel: Fail ", (false == are_parallel(vec3Da,vec3Db,epsilon))); + UNSCOPED_INFO("2:are_parallel: Fail "); + CHECK_FALSE(are_parallel(vec3Da,vec3Db,epsilon)); } - template<> template<> - void v3dmath_object::test<24>() + TEST_CASE("v3dmath_h: angle_between is zero for equal vectors and matches acos", "[v3dmath_h]") { F64 x = 10., y = 20., z = -15.; F64 angle1, angle2; LLVector3d vec3Da(x,y,z), vec3Db(x,y,z); angle1 = angle_between(vec3Da, vec3Db); - ensure("1:angle_between: Fail ", (0 == angle1)); + UNSCOPED_INFO("1:angle_between: Fail "); + CHECK(angle1 == 0); F64 x1 = -1., y1 = -20., z1 = -1.; vec3Da.clearVec(); vec3Da.setVec(x1,y1,z1); @@ -520,6 +590,7 @@ namespace vec3Da.normVec(); F64 angle = vec3Db*vec3Da; angle = acos(angle); - ensure("2:angle_between: Fail ", (angle == angle2)); + UNSCOPED_INFO("2:angle_between: Fail "); + CHECK(angle == angle2); } } diff --git a/indra/llmath/tests/v3math_test.cpp b/indra/llmath/tests/v3math_test.cpp index 805d692114..b7810a622f 100644 --- a/indra/llmath/tests/v3math_test.cpp +++ b/indra/llmath/tests/v3math_test.cpp @@ -27,7 +27,7 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "llsd.h" #include "../v3dmath.h" @@ -38,151 +38,202 @@ #include "../llquantize.h" -namespace tut -{ namespace { - struct v3math_data - { - }; - typedef test_group v3math_test; - typedef v3math_test::object v3math_object; - tut::v3math_test v3math_testcase("v3math_h"); -} - - template<> template<> - void v3math_object::test<1>() + TEST_CASE("v3math_h: constructors set every component", "[v3math_h]") { LLVector3 vec3; - ensure("1:LLVector3:Fail to initialize ", ((0.f == vec3.mV[VX]) && (0.f == vec3.mV[VY]) && (0.f == vec3.mV[VZ]))); + { + INFO("1:LLVector3:Fail to initialize "); + CHECK(vec3.mV[VX] == 0.f); + CHECK(vec3.mV[VY] == 0.f); + CHECK(vec3.mV[VZ] == 0.f); + } F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3a(x,y,z); - ensure("2:LLVector3:Fail to initialize ", ((2.32f == vec3a.mV[VX]) && (1.212f == vec3a.mV[VY]) && (-.12f == vec3a.mV[VZ]))); + { + INFO("2:LLVector3:Fail to initialize "); + CHECK(vec3a.mV[VX] == 2.32f); + CHECK(vec3a.mV[VY] == 1.212f); + CHECK(vec3a.mV[VZ] == -.12f); + } const F32 vec[3] = {1.2f ,3.2f, -4.2f}; LLVector3 vec3b(vec); - ensure("3:LLVector3:Fail to initialize ", ((1.2f == vec3b.mV[VX]) && (3.2f == vec3b.mV[VY]) && (-4.2f == vec3b.mV[VZ]))); + { + INFO("3:LLVector3:Fail to initialize "); + CHECK(vec3b.mV[VX] == 1.2f); + CHECK(vec3b.mV[VY] == 3.2f); + CHECK(vec3b.mV[VZ] == -4.2f); + } } - template<> template<> - void v3math_object::test<2>() + TEST_CASE("v3math_h: conversion from LLVector3d and LLVector4", "[v3math_h]") { F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3(x,y,z); LLVector3d vector3d(vec3); LLVector3 vec3a(vector3d); - ensure("1:LLVector3:Fail to initialize ", vec3 == vec3a); + UNSCOPED_INFO("1:LLVector3:Fail to initialize "); + CHECK(vec3 == vec3a); LLVector4 vector4(vec3); LLVector3 vec3b(vector4); - ensure("2:LLVector3:Fail to initialize ", vec3 == vec3b); + UNSCOPED_INFO("2:LLVector3:Fail to initialize "); + CHECK(vec3 == vec3b); } - template<> template<> - void v3math_object::test<3>() + TEST_CASE("v3math_h: LLSD constructor and getValue round trip", "[v3math_h]") { S32 a = 231; LLSD llsd(a); LLVector3 vec3(llsd); LLSD sd = vec3.getValue(); LLVector3 vec3a(sd); - ensure("1:LLVector3:Fail to initialize ", (vec3 == vec3a)); + UNSCOPED_INFO("1:LLVector3:Fail to initialize "); + CHECK(vec3 == vec3a); } - template<> template<> - void v3math_object::test<4>() + TEST_CASE("v3math_h: assignment copies every component", "[v3math_h]") { S32 a = 231; LLSD llsd(a); LLVector3 vec3(llsd),vec3a; vec3a = vec3; - ensure("1:Operator= Fail to initialize " ,(vec3 == vec3a)); + UNSCOPED_INFO("1:Operator= Fail to initialize "); + CHECK(vec3 == vec3a); } - template<> template<> - void v3math_object::test<5>() + TEST_CASE("v3math_h: isFinite and the clearVec/setVec/zeroVec setters", "[v3math_h]") { F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3(x,y,z); - ensure("1:isFinite= Fail to initialize ", (true == vec3.isFinite()));//need more test cases: + UNSCOPED_INFO("1:isFinite= Fail to initialize "); + CHECK(vec3.isFinite());//need more test cases: vec3.clearVec(); - ensure("2:clearVec:Fail to set values ", ((0.f == vec3.mV[VX]) && (0.f == vec3.mV[VY]) && (0.f == vec3.mV[VZ]))); + { + INFO("2:clearVec:Fail to set values "); + CHECK(vec3.mV[VX] == 0.f); + CHECK(vec3.mV[VY] == 0.f); + CHECK(vec3.mV[VZ] == 0.f); + } vec3.setVec(x,y,z); - ensure("3:setVec:Fail to set values ", ((2.32f == vec3.mV[VX]) && (1.212f == vec3.mV[VY]) && (-.12f == vec3.mV[VZ]))); + { + INFO("3:setVec:Fail to set values "); + CHECK(vec3.mV[VX] == 2.32f); + CHECK(vec3.mV[VY] == 1.212f); + CHECK(vec3.mV[VZ] == -.12f); + } vec3.zeroVec(); - ensure("4:zeroVec:Fail to set values ", ((0.f == vec3.mV[VX]) && (0.f == vec3.mV[VY]) && (0.f == vec3.mV[VZ]))); + { + INFO("4:zeroVec:Fail to set values "); + CHECK(vec3.mV[VX] == 0.f); + CHECK(vec3.mV[VY] == 0.f); + CHECK(vec3.mV[VZ] == 0.f); + } } - template<> template<> - void v3math_object::test<6>() + TEST_CASE("v3math_h: abs and the setVec overloads", "[v3math_h]") { F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3(x,y,z),vec3a; vec3.abs(); - ensure("1:abs:Fail ", ((x == vec3.mV[VX]) && (y == vec3.mV[VY]) && (-z == vec3.mV[VZ]))); + { + INFO("1:abs:Fail "); + CHECK(x == vec3.mV[VX]); + CHECK(y == vec3.mV[VY]); + CHECK(-z == vec3.mV[VZ]); + } vec3a.setVec(vec3); - ensure("2:setVec:Fail to initialize ", (vec3a == vec3)); + UNSCOPED_INFO("2:setVec:Fail to initialize "); + CHECK(vec3a == vec3); const F32 vec[3] = {1.2f ,3.2f, -4.2f}; vec3.clearVec(); vec3.setVec(vec); - ensure("3:setVec:Fail to initialize ", ((1.2f == vec3.mV[VX]) && (3.2f == vec3.mV[VY]) && (-4.2f == vec3.mV[VZ]))); + { + INFO("3:setVec:Fail to initialize "); + CHECK(vec3.mV[VX] == 1.2f); + CHECK(vec3.mV[VY] == 3.2f); + CHECK(vec3.mV[VZ] == -4.2f); + } vec3a.clearVec(); LLVector3d vector3d(vec3); vec3a.setVec(vector3d); - ensure("4:setVec:Fail to initialize ", (vec3 == vec3a)); + UNSCOPED_INFO("4:setVec:Fail to initialize "); + CHECK(vec3 == vec3a); LLVector4 vector4(vec3); vec3a.clearVec(); vec3a.setVec(vector4); - ensure("5:setVec:Fail to initialize ", (vec3 == vec3a)); + UNSCOPED_INFO("5:setVec:Fail to initialize "); + CHECK(vec3 == vec3a); } - template<> template<> - void v3math_object::test<7>() + TEST_CASE("v3math_h: clamp limits components and reports a change", "[v3math_h]") { F32 x = 2.32f, y = 3.212f, z = -.12f; F32 min = 0.0001f, max = 3.0f; LLVector3 vec3(x,y,z); - ensure("1:clamp:Fail ", true == vec3.clamp(min, max) && x == vec3.mV[VX] && max == vec3.mV[VY] && min == vec3.mV[VZ]); + { + INFO("1:clamp:Fail "); + CHECK(vec3.clamp(min, max)); + CHECK(x == vec3.mV[VX]); + CHECK(max == vec3.mV[VY]); + CHECK(min == vec3.mV[VZ]); + } x = 1.f, y = 2.2f, z = 2.8f; vec3.setVec(x,y,z); - ensure("2:clamp:Fail ", false == vec3.clamp(min, max)); + UNSCOPED_INFO("2:clamp:Fail "); + CHECK_FALSE(vec3.clamp(min, max)); } - template<> template<> - void v3math_object::test<8>() + TEST_CASE("v3math_h: magVecSquared and magVec", "[v3math_h]") { F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3(x,y,z); - ensure("1:magVecSquared:Fail ", is_approx_equal(vec3.magVecSquared(), (x*x + y*y + z*z))); - ensure("2:magVec:Fail ", is_approx_equal(vec3.magVec(), (F32) sqrt(x*x + y*y + z*z))); + UNSCOPED_INFO("1:magVecSquared:Fail "); + CHECK(is_approx_equal(vec3.magVecSquared(), (x*x + y*y + z*z))); + UNSCOPED_INFO("2:magVec:Fail "); + CHECK(is_approx_equal(vec3.magVec(), (F32) sqrt(x*x + y*y + z*z))); } - template<> template<> - void v3math_object::test<9>() + TEST_CASE("v3math_h: abs then isNull on long and very short vectors", "[v3math_h]") { F32 x =-2.0f, y = -3.0f, z = 1.23f ; LLVector3 vec3(x,y,z); - ensure("1:abs():Fail ", (true == vec3.abs())); - ensure("2:isNull():Fail", (false == vec3.isNull())); //Returns true if vector has a _very_small_ length + UNSCOPED_INFO("1:abs():Fail "); + CHECK(vec3.abs()); + UNSCOPED_INFO("2:isNull():Fail"); + CHECK_FALSE(vec3.isNull()); //Returns true if vector has a _very_small_ length x =.00000001f, y = .000001001f, z = .000001001f; vec3.setVec(x,y,z); - ensure("3:isNull(): Fail ", (true == vec3.isNull())); + UNSCOPED_INFO("3:isNull(): Fail "); + CHECK(vec3.isNull()); } - template<> template<> - void v3math_object::test<10>() + TEST_CASE("v3math_h: isExactlyZero and scaleVec", "[v3math_h]") { F32 x =-2.0f, y = -3.0f, z = 1.f ; LLVector3 vec3(x,y,z),vec3a; - ensure("1:isExactlyZero():Fail ", (true == vec3a.isExactlyZero())); + UNSCOPED_INFO("1:isExactlyZero():Fail "); + CHECK(vec3a.isExactlyZero()); vec3a = vec3a.scaleVec(vec3); - ensure("2:scaleVec: Fail ", vec3a.mV[VX] == 0.f && vec3a.mV[VY] == 0.f && vec3a.mV[VZ] == 0.f); + { + INFO("2:scaleVec: Fail "); + CHECK(vec3a.mV[VX] == 0.f); + CHECK(vec3a.mV[VY] == 0.f); + CHECK(vec3a.mV[VZ] == 0.f); + } vec3a.setVec(x,y,z); vec3a = vec3a.scaleVec(vec3); - ensure("3:scaleVec: Fail ", ((4 == vec3a.mV[VX]) && (9 == vec3a.mV[VY]) &&(1 == vec3a.mV[VZ]))); - ensure("4:isExactlyZero():Fail ", (false == vec3.isExactlyZero())); + { + INFO("3:scaleVec: Fail "); + CHECK(vec3a.mV[VX] == 4); + CHECK(vec3a.mV[VY] == 9); + CHECK(vec3a.mV[VZ] == 1); + } + UNSCOPED_INFO("4:isExactlyZero():Fail "); + CHECK_FALSE(vec3.isExactlyZero()); } - template<> template<> - void v3math_object::test<11>() + TEST_CASE("v3math_h: rotVec by an angle about an axis is repeatable", "[v3math_h]") { F32 x =20.0f, y = 30.0f, z = 15.f ; F32 angle = 100.f; @@ -190,31 +241,36 @@ namespace vec3a = vec3a.rotVec(angle, vec3); LLVector3 vec3b(1.f,2.f,3.f); vec3b = vec3b.rotVec(angle, vec3); - ensure_equals("rotVec():Fail" ,vec3b,vec3a); + UNSCOPED_INFO("rotVec():Fail"); + CHECK(vec3b == vec3a); } - template<> template<> - void v3math_object::test<12>() + TEST_CASE("v3math_h: subscript operator reads components and returns references", "[v3math_h]") { F32 x =-2.0f, y = -3.0f, z = 1.f ; LLVector3 vec3(x,y,z); - ensure("1:operator [] failed",( x == vec3[0])); - ensure("2:operator [] failed",( y == vec3[1])); - ensure("3:operator [] failed",( z == vec3[2])); + UNSCOPED_INFO("1:operator [] failed"); + CHECK(x == vec3[0]); + UNSCOPED_INFO("2:operator [] failed"); + CHECK(y == vec3[1]); + UNSCOPED_INFO("3:operator [] failed"); + CHECK(z == vec3[2]); vec3.clearVec(); x = 23.f, y = -.2361f, z = 3.25; vec3.setVec(x,y,z); F32 &ref1 = vec3[0]; - ensure("4:operator [] failed",( ref1 == vec3[0])); + UNSCOPED_INFO("4:operator [] failed"); + CHECK(ref1 == vec3[0]); F32 &ref2 = vec3[1]; - ensure("5:operator [] failed",( ref2 == vec3[1])); + UNSCOPED_INFO("5:operator [] failed"); + CHECK(ref2 == vec3[1]); F32 &ref3 = vec3[2]; - ensure("6:operator [] failed",( ref3 == vec3[2])); + UNSCOPED_INFO("6:operator [] failed"); + CHECK(ref3 == vec3[2]); } - template<> template<> - void v3math_object::test<13>() + TEST_CASE("v3math_h: operator+ adds componentwise", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 = -2.3f, y2 = 1.11f, z2 = 1234.234f; F32 val1, val2, val3; @@ -223,7 +279,12 @@ namespace val1 = x1+x2; val2 = y1+y2; val3 = z1+z2; - ensure("1:operator+ failed",(val1 == vec3b.mV[VX]) && (val2 == vec3b.mV[VY]) && (val3 == vec3b.mV[VZ])); + { + INFO("1:operator+ failed"); + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); + } vec3.clearVec(); vec3a.clearVec(); @@ -235,11 +296,15 @@ namespace val1 = x1+x2; val2 = y1+y2; val3 = z1+z2; - ensure("2:operator+ failed",(val1 == vec3b.mV[VX]) && (val2 == vec3b.mV[VY]) && (val3 == vec3b.mV[VZ])); + { + INFO("2:operator+ failed"); + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); + } } - template<> template<> - void v3math_object::test<14>() + TEST_CASE("v3math_h: operator- subtracts componentwise", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 = -2.3f, y2 = 1.11f, z2 = 1234.234f; F32 val1, val2, val3; @@ -248,7 +313,12 @@ namespace val1 = x1-x2; val2 = y1-y2; val3 = z1-z2; - ensure("1:operator- failed",(val1 == vec3b.mV[VX]) && (val2 == vec3b.mV[VY]) && (val3 == vec3b.mV[VZ])); + { + INFO("1:operator- failed"); + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); + } vec3.clearVec(); vec3a.clearVec(); @@ -260,18 +330,23 @@ namespace val1 = x1-x2; val2 = y1-y2; val3 = z1-z2; - ensure("2:operator- failed",(val1 == vec3b.mV[VX]) && (val2 == vec3b.mV[VY]) && (val3 == vec3b.mV[VZ])); + { + INFO("2:operator- failed"); + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); + } } - template<> template<> - void v3math_object::test<15>() + TEST_CASE("v3math_h: operator* as dot product and scalar product", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 = -2.3f, y2 = 1.11f, z2 = 1234.234f; F32 val1, val2, val3; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); val1 = vec3 * vec3a; val2 = x1*x2 + y1*y2 + z1*z2; - ensure_equals("1:operator* failed",val1,val2); + UNSCOPED_INFO("1:operator* failed"); + CHECK(val1 == val2); vec3a.clearVec(); F32 mulVal = 4.332f; @@ -279,14 +354,23 @@ namespace val1 = x1*mulVal; val2 = y1*mulVal; val3 = z1*mulVal; - ensure("2:operator* failed",(val1 == vec3a.mV[VX]) && (val2 == vec3a.mV[VY])&& (val3 == vec3a.mV[VZ])); + { + INFO("2:operator* failed"); + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + } vec3a.clearVec(); vec3a = mulVal * vec3; - ensure("3:operator* failed ", (val1 == vec3a.mV[VX]) && (val2 == vec3a.mV[VY])&& (val3 == vec3a.mV[VZ])); + { + INFO("3:operator* failed "); + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + } } - template<> template<> - void v3math_object::test<16>() + TEST_CASE("v3math_h: operator% is the cross product", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 = -2.3f, y2 = 1.11f, z2 = 1234.234f; F32 val1, val2, val3; @@ -295,7 +379,12 @@ namespace val1 = y1*z2 - y2*z1; val2 = z1*x2 -z2*x1; val3 = x1*y2-x2*y1; - ensure("1:operator% failed",(val1 == vec3b.mV[VX]) && (val2 == vec3b.mV[VY]) && (val3 == vec3b.mV[VZ])); + { + INFO("1:operator% failed"); + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); + } vec3.clearVec(); vec3a.clearVec(); @@ -307,11 +396,15 @@ namespace val1 = y1*z2 - y2*z1; val2 = z1*x2 -z2*x1; val3 = x1*y2-x2*y1; - ensure("2:operator% failed ", (val1 == vec3b.mV[VX]) && (val2 == vec3b.mV[VY]) && (val3 == vec3b.mV[VZ])); + { + INFO("2:operator% failed "); + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); + } } - template<> template<> - void v3math_object::test<17>() + TEST_CASE("v3math_h: operator/ divides by a scalar", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, div = 3.2f; F32 t = 1.f / div, val1, val2, val3; @@ -320,7 +413,12 @@ namespace val1 = x1 * t; val2 = y1 * t; val3 = z1 *t; - ensure("1:operator/ failed",(val1 == vec3a.mV[VX]) && (val2 == vec3a.mV[VY]) && (val3 == vec3a.mV[VZ])); + { + INFO("1:operator/ failed"); + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + } vec3a.clearVec(); x1 = -.235f, y1 = -24.32f, z1 = .342f, div = -2.2f; @@ -330,15 +428,20 @@ namespace val1 = x1 * t; val2 = y1 * t; val3 = z1 *t; - ensure("2:operator/ failed",(val1 == vec3a.mV[VX]) && (val2 == vec3a.mV[VY]) && (val3 == vec3a.mV[VZ])); + { + INFO("2:operator/ failed"); + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + } } - template<> template<> - void v3math_object::test<18>() + TEST_CASE("v3math_h: operator== for equal vectors", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f; LLVector3 vec3(x1,y1,z1), vec3a(x1,y1,z1); - ensure("1:operator== failed",(vec3 == vec3a)); + UNSCOPED_INFO("1:operator== failed"); + CHECK(vec3 == vec3a); vec3a.clearVec(); x1 = -.235f, y1 = -24.32f, z1 = .342f; @@ -346,24 +449,25 @@ namespace vec3a.clearVec(); vec3.setVec(x1,y1,z1); vec3a.setVec(x1,y1,z1); - ensure("2:operator== failed ", (vec3 == vec3a)); + UNSCOPED_INFO("2:operator== failed "); + CHECK(vec3 == vec3a); } - template<> template<> - void v3math_object::test<19>() + TEST_CASE("v3math_h: operator!= for different and equal vectors", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.234f,z2 = 11.2f;; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); - ensure("1:operator!= failed",(vec3a != vec3)); + UNSCOPED_INFO("1:operator!= failed"); + CHECK(vec3a != vec3); vec3.clearVec(); vec3.clearVec(); vec3a.setVec(vec3); - ensure("2:operator!= failed", ( false == (vec3a != vec3))); + UNSCOPED_INFO("2:operator!= failed"); + CHECK_FALSE(vec3a != vec3); } - template<> template<> - void v3math_object::test<20>() + TEST_CASE("v3math_h: operator+= adds componentwise", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.2f,z2 = 11.2f;; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); @@ -372,11 +476,15 @@ namespace val1 = x1+x2; val2 = y1+y2; val3 = z1+z2; - ensure("1:operator+= failed",(val1 == vec3a.mV[VX]) && (val2 == vec3a.mV[VY])&& (val3 == vec3a.mV[VZ])); + { + INFO("1:operator+= failed"); + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + } } - template<> template<> - void v3math_object::test<21>() + TEST_CASE("v3math_h: operator-= subtracts componentwise", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.2f,z2 = 11.2f;; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); @@ -385,11 +493,15 @@ namespace val1 = x2-x1; val2 = y2-y1; val3 = z2-z1; - ensure("1:operator-= failed",(val1 == vec3a.mV[VX]) && (val2 == vec3a.mV[VY])&& (val3 == vec3a.mV[VZ])); + { + INFO("1:operator-= failed"); + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + } } - template<> template<> - void v3math_object::test<22>() + TEST_CASE("v3math_h: operator*= by a vector and by a scalar", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 = -2.3f, y2 = 1.11f, z2 = 1234.234f; F32 val1,val2,val3; @@ -398,28 +510,37 @@ namespace val1 = x1*x2; val2 = y1*y2; val3 = z1*z2; - ensure("1:operator*= failed",(val1 == vec3a.mV[VX]) && (val2 == vec3a.mV[VY])&& (val3 == vec3a.mV[VZ])); + { + INFO("1:operator*= failed"); + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + } F32 mulVal = 4.332f; vec3 *=mulVal; val1 = x1*mulVal; val2 = y1*mulVal; val3 = z1*mulVal; - ensure("2:operator*= failed ", is_approx_equal(val1, vec3.mV[VX]) && is_approx_equal(val2, vec3.mV[VY]) && is_approx_equal(val3, vec3.mV[VZ])); + { + INFO("2:operator*= failed "); + CHECK(is_approx_equal(val1, vec3.mV[VX])); + CHECK(is_approx_equal(val2, vec3.mV[VY])); + CHECK(is_approx_equal(val3, vec3.mV[VZ])); + } } - template<> template<> - void v3math_object::test<23>() + TEST_CASE("v3math_h: operator%= matches operator%", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 = -2.3f, y2 = 1.11f, z2 = 1234.234f; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2),vec3b; vec3b = vec3a % vec3; vec3a %= vec3; - ensure_equals("1:operator%= failed",vec3a,vec3b); + UNSCOPED_INFO("1:operator%= failed"); + CHECK(vec3a == vec3b); } - template<> template<> - void v3math_object::test<24>() + TEST_CASE("v3math_h: operator/= divides by a scalar", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, div = 3.2f; F32 t = 1.f / div, val1, val2, val3; @@ -428,20 +549,24 @@ namespace val1 = x1 * t; val2 = y1 * t; val3 = z1 *t; - ensure("1:operator/= failed",(val1 == vec3a.mV[VX]) && (val2 == vec3a.mV[VY]) && (val3 == vec3a.mV[VZ])); + { + INFO("1:operator/= failed"); + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + } } - template<> template<> - void v3math_object::test<25>() + TEST_CASE("v3math_h: unary operator- negates", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f; LLVector3 vec3(x1,y1,z1), vec3a; vec3a = -vec3; - ensure("1:operator- failed",(-vec3a == vec3)); + UNSCOPED_INFO("1:operator- failed"); + CHECK(-vec3a == vec3); } - template<> template<> - void v3math_object::test<26>() + TEST_CASE("v3math_h: operator<< prints equal vectors alike", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f; std::ostringstream stream1, stream2; @@ -449,51 +574,58 @@ namespace stream1 << vec3; vec3a.setVec(x1,y1,z1); stream2 << vec3a; - ensure("1:operator << failed",(stream1.str() == stream2.str())); + UNSCOPED_INFO("1:operator << failed"); + CHECK(stream1.str() == stream2.str()); } - template<> template<> - void v3math_object::test<27>() + TEST_CASE("v3math_h: operator< orders vectors by x first", "[v3math_h]") { F32 x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 1.11f, z2 = 1234.234f; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); - ensure("1:operator< failed", (true == (vec3 < vec3a))); + UNSCOPED_INFO("1:operator< failed"); + CHECK(vec3 < vec3a); x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 2.f, z2 = 1234.234f; vec3.setVec(x1,y1,z1); vec3a.setVec(x2,y2,z2); - ensure("2:operator< failed ", (true == (vec3 < vec3a))); + UNSCOPED_INFO("2:operator< failed "); + CHECK(vec3 < vec3a); x1 =2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, vec3.setVec(x1,y1,z1); vec3a.setVec(x2,y2,z2); - ensure("3:operator< failed ", (false == (vec3 < vec3a))); + UNSCOPED_INFO("3:operator< failed "); + CHECK_FALSE(vec3 < vec3a); } - template<> template<> - void v3math_object::test<28>() + TEST_CASE("v3math_h: parseVector3 reads three numbers", "[v3math_h]") { F32 x1 =1.23f, y1 = 2.f,z1 = 4.f; std::string buf("1.23 2. 4"); LLVector3 vec3, vec3a(x1,y1,z1); LLVector3::parseVector3(buf, &vec3); - ensure_equals("1:parseVector3 failed", vec3, vec3a); + UNSCOPED_INFO("1:parseVector3 failed"); + CHECK(vec3 == vec3a); } - template<> template<> - void v3math_object::test<29>() + TEST_CASE("v3math_h: scaleVec multiplies componentwise", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 4.f; LLVector3 vec3(x1,y1,z1),vec3a,vec3b; vec3a.setVec(1,1,1); vec3a.scaleVec(vec3); - ensure_equals("1:scaleVec failed", vec3, vec3a); + UNSCOPED_INFO("1:scaleVec failed"); + CHECK(vec3 == vec3a); vec3a.clearVec(); vec3a.setVec(x1,y1,z1); vec3a.scaleVec(vec3); - ensure("2:scaleVec failed", ((1.f ==vec3a.mV[VX])&& (4.f ==vec3a.mV[VY]) && (16.f ==vec3a.mV[VZ]))); + { + INFO("2:scaleVec failed"); + CHECK(vec3a.mV[VX] == 1.f); + CHECK(vec3a.mV[VY] == 4.f); + CHECK(vec3a.mV[VZ] == 16.f); + } } - template<> template<> - void v3math_object::test<30>() + TEST_CASE("v3math_h: lerp between two vectors", "[v3math_h]") { F32 x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 1.11f, z2 = 1234.234f; F32 val = 2.3f,val1,val2,val3; @@ -502,54 +634,74 @@ namespace val3 = z1 + (z2 - z1)* val; LLVector3 vec3(x1,y1,z1),vec3a(x2,y2,z2); LLVector3 vec3b = lerp(vec3,vec3a,val); - ensure("1:lerp failed", ((val1 ==vec3b.mV[VX])&& (val2 ==vec3b.mV[VY]) && (val3 ==vec3b.mV[VZ]))); + { + INFO("1:lerp failed"); + CHECK(val1 ==vec3b.mV[VX]); + CHECK(val2 ==vec3b.mV[VY]); + CHECK(val3 ==vec3b.mV[VZ]); + } } - template<> template<> - void v3math_object::test<31>() + TEST_CASE("v3math_h: dist_vec and its squared and 2D forms", "[v3math_h]") { F32 x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 1.f, z2 = 1.f; F32 val1,val2; LLVector3 vec3(x1,y1,z1),vec3a(x2,y2,z2); val1 = dist_vec(vec3,vec3a); val2 = (F32) sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2) + (z1 - z2)* (z1 -z2)); - ensure_equals("1:dist_vec: Fail ",val2, val1); + UNSCOPED_INFO("1:dist_vec: Fail "); + CHECK(val2 == val1); val1 = dist_vec_squared(vec3,vec3a); val2 =((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2) + (z1 - z2)* (z1 -z2)); - ensure_equals("2:dist_vec_squared: Fail ",val2, val1); + UNSCOPED_INFO("2:dist_vec_squared: Fail "); + CHECK(val2 == val1); val1 = dist_vec_squared2D(vec3, vec3a); val2 =(x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2); - ensure_equals("3:dist_vec_squared2D: Fail ",val2, val1); + UNSCOPED_INFO("3:dist_vec_squared2D: Fail "); + CHECK(val2 == val1); } - template<> template<> - void v3math_object::test<32>() + TEST_CASE("v3math_h: normVec normalises and zeroes a tiny vector", "[v3math_h]") { F32 x =12.3524f, y = -342.f,z = 4.126341f; LLVector3 vec3(x,y,z); F32 mag = vec3.normVec(); mag = 1.f/ mag; F32 val1 = x* mag, val2 = y* mag, val3 = z* mag; - ensure("1:normVec: Fail ", is_approx_equal(val1, vec3.mV[VX]) && is_approx_equal(val2, vec3.mV[VY]) && is_approx_equal(val3, vec3.mV[VZ])); + { + INFO("1:normVec: Fail "); + CHECK(is_approx_equal(val1, vec3.mV[VX])); + CHECK(is_approx_equal(val2, vec3.mV[VY])); + CHECK(is_approx_equal(val3, vec3.mV[VZ])); + } x = 0.000000001f, y = 0.f, z = 0.f; vec3.clearVec(); vec3.setVec(x,y,z); mag = vec3.normVec(); val1 = x* mag, val2 = y* mag, val3 = z* mag; - ensure("2:normVec: Fail ", (mag == 0.) && (0. == vec3.mV[VX]) && (0. == vec3.mV[VY])&& (0. == vec3.mV[VZ])); + { + INFO("2:normVec: Fail "); + CHECK(mag == 0.); + CHECK(vec3.mV[VX] == 0.); + CHECK(vec3.mV[VY] == 0.); + CHECK(vec3.mV[VZ] == 0.); + } } - template<> template<> - void v3math_object::test<33>() + TEST_CASE("v3math_h: snap rounds to the given decimal places", "[v3math_h]") { F32 x = -202.23412f, y = 123.2312f, z = -89.f; LLVector3 vec(x,y,z); vec.snap(2); - ensure("1:snap: Fail ", is_approx_equal(-202.23f, vec.mV[VX]) && is_approx_equal(123.23f, vec.mV[VY]) && is_approx_equal(-89.f, vec.mV[VZ])); + { + INFO("1:snap: Fail "); + CHECK(is_approx_equal(-202.23f, vec.mV[VX])); + CHECK(is_approx_equal(123.23f, vec.mV[VY])); + CHECK(is_approx_equal(-89.f, vec.mV[VZ])); + } } - template<> template<> - void v3math_object::test<34>() + TEST_CASE("v3math_h: quantize16 and quantize8 match the U16 and U8 round trips", "[v3math_h]") { F32 x = 10.f, y = 20.f, z = -15.f; F32 x1, y1, z1; @@ -559,30 +711,54 @@ namespace x1 = U16_to_F32(F32_to_U16(x, lowerxy, upperxy), lowerxy, upperxy); y1 = U16_to_F32(F32_to_U16(y, lowerxy, upperxy), lowerxy, upperxy); z1 = U16_to_F32(F32_to_U16(z, lowerz, upperz), lowerz, upperz); - ensure("1:quantize16: Fail ", is_approx_equal(x1, vec3.mV[VX]) && is_approx_equal(y1, vec3.mV[VY]) && is_approx_equal(z1, vec3.mV[VZ])); + { + INFO("1:quantize16: Fail "); + CHECK(is_approx_equal(x1, vec3.mV[VX])); + CHECK(is_approx_equal(y1, vec3.mV[VY])); + CHECK(is_approx_equal(z1, vec3.mV[VZ])); + } LLVector3 vec3a(x,y,z); vec3a.quantize8(lowerxy,upperxy,lowerz,upperz); x1 = U8_to_F32(F32_to_U8(x, lowerxy, upperxy), lowerxy, upperxy); y1 = U8_to_F32(F32_to_U8(y, lowerxy, upperxy), lowerxy, upperxy); z1 = U8_to_F32(F32_to_U8(z, lowerz, upperz), lowerz, upperz); - ensure("2:quantize8: Fail ", is_approx_equal(x1, vec3a.mV[VX]) && is_approx_equal(y1, vec3a.mV[VY]) && is_approx_equal(z1, vec3a.mV[VZ])); + { + INFO("2:quantize8: Fail "); + CHECK(is_approx_equal(x1, vec3a.mV[VX])); + CHECK(is_approx_equal(y1, vec3a.mV[VY])); + CHECK(is_approx_equal(z1, vec3a.mV[VZ])); + } } - template<> template<> - void v3math_object::test<35>() + TEST_CASE("v3math_h: LLSD arrays shorter than three leave the rest zero", "[v3math_h]") { LLSD sd = LLSD::emptyArray(); sd[0] = 1.f; LLVector3 parsed_1(sd); - ensure("1:LLSD parse: Fail ", is_approx_equal(parsed_1.mV[VX], 1.f) && is_approx_equal(parsed_1.mV[VY], 0.f) && is_approx_equal(parsed_1.mV[VZ], 0.f)); + { + INFO("1:LLSD parse: Fail "); + CHECK(is_approx_equal(parsed_1.mV[VX], 1.f)); + CHECK(is_approx_equal(parsed_1.mV[VY], 0.f)); + CHECK(is_approx_equal(parsed_1.mV[VZ], 0.f)); + } sd[1] = 2.f; LLVector3 parsed_2(sd); - ensure("2:LLSD parse: Fail ", is_approx_equal(parsed_2.mV[VX], 1.f) && is_approx_equal(parsed_2.mV[VY], 2.f) && is_approx_equal(parsed_2.mV[VZ], 0.f)); + { + INFO("2:LLSD parse: Fail "); + CHECK(is_approx_equal(parsed_2.mV[VX], 1.f)); + CHECK(is_approx_equal(parsed_2.mV[VY], 2.f)); + CHECK(is_approx_equal(parsed_2.mV[VZ], 0.f)); + } sd[2] = 3.f; LLVector3 parsed_3(sd); - ensure("3:LLSD parse: Fail ", is_approx_equal(parsed_3.mV[VX], 1.f) && is_approx_equal(parsed_3.mV[VY], 2.f) && is_approx_equal(parsed_3.mV[VZ], 3.f)); + { + INFO("3:LLSD parse: Fail "); + CHECK(is_approx_equal(parsed_3.mV[VX], 1.f)); + CHECK(is_approx_equal(parsed_3.mV[VY], 2.f)); + CHECK(is_approx_equal(parsed_3.mV[VZ], 3.f)); + } } } diff --git a/indra/llmath/tests/v4color_test.cpp b/indra/llmath/tests/v4color_test.cpp index b70a43f62f..5789100b67 100644 --- a/indra/llmath/tests/v4color_test.cpp +++ b/indra/llmath/tests/v4color_test.cpp @@ -28,7 +28,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../v4coloru.h" #include "llsd.h" @@ -36,133 +36,204 @@ #include "../v4color.h" -namespace tut -{ namespace { - struct v4color_data - { - }; - typedef test_group v4color_test; - typedef v4color_test::object v4color_object; - tut::v4color_test v4color_testcase("v4color_h"); -} - - template<> template<> - void v4color_object::test<1>() + TEST_CASE("v4color_h: constructors from values or an array or LLColor3 or LLSD or LLColor4U", "[v4color_h]") { LLColor4 llcolor4; - ensure("1:LLColor4:Fail to initialize ", ((0 == llcolor4.mV[VRED]) && (0 == llcolor4.mV[VGREEN]) && (0 == llcolor4.mV[VBLUE])&& (1.0f == llcolor4.mV[VALPHA]))); + { + INFO("1:LLColor4:Fail to initialize "); + CHECK(llcolor4.mV[VRED] == 0); + CHECK(llcolor4.mV[VGREEN] == 0); + CHECK(llcolor4.mV[VBLUE] == 0); + CHECK(llcolor4.mV[VALPHA] == 1.0f); + } F32 r = 0x20, g = 0xFFFF, b = 0xFF, a = 0xAF; LLColor4 llcolor4a(r,g,b); - ensure("2:LLColor4:Fail to initialize ", ((r == llcolor4a.mV[VRED]) && (g == llcolor4a.mV[VGREEN]) && (b == llcolor4a.mV[VBLUE])&& (1.0f == llcolor4a.mV[VALPHA]))); + { + INFO("2:LLColor4:Fail to initialize "); + CHECK(r == llcolor4a.mV[VRED]); + CHECK(g == llcolor4a.mV[VGREEN]); + CHECK(b == llcolor4a.mV[VBLUE]); + CHECK(llcolor4a.mV[VALPHA] == 1.0f); + } LLColor4 llcolor4b(r,g,b,a); - ensure("3:LLColor4:Fail to initialize ", ((r == llcolor4b.mV[VRED]) && (g == llcolor4b.mV[VGREEN]) && (b == llcolor4b.mV[VBLUE])&& (a == llcolor4b.mV[VALPHA]))); + { + INFO("3:LLColor4:Fail to initialize "); + CHECK(r == llcolor4b.mV[VRED]); + CHECK(g == llcolor4b.mV[VGREEN]); + CHECK(b == llcolor4b.mV[VBLUE]); + CHECK(a == llcolor4b.mV[VALPHA]); + } const F32 vec[4] = {.112f ,23.2f, -4.2f, -.0001f}; LLColor4 llcolor4c(vec); - ensure("4:LLColor4:Fail to initialize ", ((vec[0] == llcolor4c.mV[VRED]) && (vec[1] == llcolor4c.mV[VGREEN]) && (vec[2] == llcolor4c.mV[VBLUE])&& (vec[3] == llcolor4c.mV[VALPHA]))); + { + INFO("4:LLColor4:Fail to initialize "); + CHECK(vec[0] == llcolor4c.mV[VRED]); + CHECK(vec[1] == llcolor4c.mV[VGREEN]); + CHECK(vec[2] == llcolor4c.mV[VBLUE]); + CHECK(vec[3] == llcolor4c.mV[VALPHA]); + } LLColor3 llcolor3(-2.23f,1.01f,42.3f); F32 val = -.1f; LLColor4 llcolor4d(llcolor3,val); - ensure("5:LLColor4:Fail to initialize ", ((llcolor3.mV[VRED] == llcolor4d.mV[VRED]) && (llcolor3.mV[VGREEN] == llcolor4d.mV[VGREEN]) && (llcolor3.mV[VBLUE] == llcolor4d.mV[VBLUE])&& (val == llcolor4d.mV[VALPHA]))); + { + INFO("5:LLColor4:Fail to initialize "); + CHECK(llcolor3.mV[VRED] == llcolor4d.mV[VRED]); + CHECK(llcolor3.mV[VGREEN] == llcolor4d.mV[VGREEN]); + CHECK(llcolor3.mV[VBLUE] == llcolor4d.mV[VBLUE]); + CHECK(val == llcolor4d.mV[VALPHA]); + } LLSD sd = llcolor4d.getValue(); LLColor4 llcolor4e(sd); - ensure_equals("6:LLColor4:(LLSD) failed ", llcolor4d, llcolor4e); + UNSCOPED_INFO("6:LLColor4:(LLSD) failed "); + CHECK(llcolor4d == llcolor4e); U8 r1 = 0xF2, g1 = 0xFA, b1 = 0xBF; LLColor4U color4u(r1,g1,b1); LLColor4 llcolor4g(color4u); const F32 SCALE = 1.f/255.f; F32 r2 = r1*SCALE, g2 = g1* SCALE, b2 = b1* SCALE; - ensure("7:LLColor4:Fail to initialize ", ((r2 == llcolor4g.mV[VRED]) && (g2 == llcolor4g.mV[VGREEN]) && (b2 == llcolor4g.mV[VBLUE]))); + { + INFO("7:LLColor4:Fail to initialize "); + CHECK(r2 == llcolor4g.mV[VRED]); + CHECK(g2 == llcolor4g.mV[VGREEN]); + CHECK(b2 == llcolor4g.mV[VBLUE]); + } } - template<> template<> - void v4color_object::test<2>() + TEST_CASE("v4color_h: setValue and getValue round-trip through LLSD", "[v4color_h]") { LLColor4 llcolor(1.0, 2.0, 3.0, 4.0); LLSD llsd = llcolor.getValue(); LLColor4 llcolor4(llsd), llcolor4a; llcolor4a.setValue(llsd); - ensure("setValue: failed", (llcolor4 == llcolor4a)); + UNSCOPED_INFO("setValue: failed"); + CHECK(llcolor4 == llcolor4a); LLSD sd = llcolor4a.getValue(); LLColor4 llcolor4b(sd); - ensure("getValue: Failed ", (llcolor4b == llcolor4a)); + UNSCOPED_INFO("getValue: Failed "); + CHECK(llcolor4b == llcolor4a); } - template<> template<> - void v4color_object::test<3>() + TEST_CASE("v4color_h: setToBlack and setToWhite leave alpha at 1", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF,a = 0xAF; LLColor4 llcolor4(r,g,b,a); llcolor4.setToBlack(); - ensure("setToBlack:Fail to set the black ", ((0 == llcolor4.mV[VRED]) && (0 == llcolor4.mV[VGREEN]) && (0 == llcolor4.mV[VBLUE])&& (1.0f == llcolor4.mV[VALPHA]))); + { + INFO("setToBlack:Fail to set the black "); + CHECK(llcolor4.mV[VRED] == 0); + CHECK(llcolor4.mV[VGREEN] == 0); + CHECK(llcolor4.mV[VBLUE] == 0); + CHECK(llcolor4.mV[VALPHA] == 1.0f); + } llcolor4.setToWhite(); - ensure("setToWhite:Fail to set the white ", ((1.f == llcolor4.mV[VRED]) && (1.f == llcolor4.mV[VGREEN]) && (1.f == llcolor4.mV[VBLUE])&& (1.0f == llcolor4.mV[VALPHA]))); + { + INFO("setToWhite:Fail to set the white "); + CHECK(llcolor4.mV[VRED] == 1.f); + CHECK(llcolor4.mV[VGREEN] == 1.f); + CHECK(llcolor4.mV[VBLUE] == 1.f); + CHECK(llcolor4.mV[VALPHA] == 1.0f); + } } - template<> template<> - void v4color_object::test<4>() + TEST_CASE("v4color_h: setVec from values or colors or an array or LLColor4U", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF, a = 0xAF; LLColor4 llcolor4; llcolor4.setVec(r,g,b); - ensure("1:setVec:Fail to set the values ", ((r == llcolor4.mV[VRED]) && (g == llcolor4.mV[VGREEN]) && (b == llcolor4.mV[VBLUE])&& (1.f == llcolor4.mV[VALPHA]))); + { + INFO("1:setVec:Fail to set the values "); + CHECK(r == llcolor4.mV[VRED]); + CHECK(g == llcolor4.mV[VGREEN]); + CHECK(b == llcolor4.mV[VBLUE]); + CHECK(llcolor4.mV[VALPHA] == 1.f); + } llcolor4.setVec(r,g,b,a); - ensure("2:setVec:Fail to set the values ", ((r == llcolor4.mV[VRED]) && (g == llcolor4.mV[VGREEN]) && (b == llcolor4.mV[VBLUE])&& (a == llcolor4.mV[VALPHA]))); + { + INFO("2:setVec:Fail to set the values "); + CHECK(r == llcolor4.mV[VRED]); + CHECK(g == llcolor4.mV[VGREEN]); + CHECK(b == llcolor4.mV[VBLUE]); + CHECK(a == llcolor4.mV[VALPHA]); + } LLColor4 llcolor4a; llcolor4a.setVec(llcolor4); - ensure_equals("3:setVec:Fail to set the values ", llcolor4a,llcolor4); + UNSCOPED_INFO("3:setVec:Fail to set the values "); + CHECK(llcolor4a == llcolor4); LLColor3 llcolor3(-2.23f,1.01f,42.3f); llcolor4a.setVec(llcolor3); - ensure("4:setVec:Fail to set the values ", ((llcolor3.mV[VRED] == llcolor4a.mV[VRED]) && (llcolor3.mV[VGREEN] == llcolor4a.mV[VGREEN]) && (llcolor3.mV[VBLUE] == llcolor4a.mV[VBLUE]))); + { + INFO("4:setVec:Fail to set the values "); + CHECK(llcolor3.mV[VRED] == llcolor4a.mV[VRED]); + CHECK(llcolor3.mV[VGREEN] == llcolor4a.mV[VGREEN]); + CHECK(llcolor3.mV[VBLUE] == llcolor4a.mV[VBLUE]); + } F32 val = -.33f; llcolor4a.setVec(llcolor3,val); - ensure("4:setVec:Fail to set the values ", ((llcolor3.mV[VRED] == llcolor4a.mV[VRED]) && (llcolor3.mV[VGREEN] == llcolor4a.mV[VGREEN]) && (llcolor3.mV[VBLUE] == llcolor4a.mV[VBLUE]) && (val == llcolor4a.mV[VALPHA]))); + { + INFO("4:setVec:Fail to set the values "); + CHECK(llcolor3.mV[VRED] == llcolor4a.mV[VRED]); + CHECK(llcolor3.mV[VGREEN] == llcolor4a.mV[VGREEN]); + CHECK(llcolor3.mV[VBLUE] == llcolor4a.mV[VBLUE]); + CHECK(val == llcolor4a.mV[VALPHA]); + } const F32 vec[4] = {.112f ,23.2f, -4.2f, -.0001f}; LLColor4 llcolor4c; llcolor4c.setVec(vec); - ensure("5:setVec:Fail to initialize ", ((vec[0] == llcolor4c.mV[VRED]) && (vec[1] == llcolor4c.mV[VGREEN]) && (vec[2] == llcolor4c.mV[VBLUE])&& (vec[3] == llcolor4c.mV[VALPHA]))); + { + INFO("5:setVec:Fail to initialize "); + CHECK(vec[0] == llcolor4c.mV[VRED]); + CHECK(vec[1] == llcolor4c.mV[VGREEN]); + CHECK(vec[2] == llcolor4c.mV[VBLUE]); + CHECK(vec[3] == llcolor4c.mV[VALPHA]); + } U8 r1 = 0xF2, g1 = 0xFA, b1= 0xBF; LLColor4U color4u(r1,g1,b1); llcolor4.setVec(color4u); const F32 SCALE = 1.f/255.f; F32 r2 = r1*SCALE, g2 = g1* SCALE, b2 = b1* SCALE; - ensure("6:setVec:Fail to initialize ", ((r2 == llcolor4.mV[VRED]) && (g2 == llcolor4.mV[VGREEN]) && (b2 == llcolor4.mV[VBLUE]))); + { + INFO("6:setVec:Fail to initialize "); + CHECK(r2 == llcolor4.mV[VRED]); + CHECK(g2 == llcolor4.mV[VGREEN]); + CHECK(b2 == llcolor4.mV[VBLUE]); + } } - template<> template<> - void v4color_object::test<5>() + TEST_CASE("v4color_h: setAlpha sets the alpha", "[v4color_h]") { F32 alpha = 0xAF; LLColor4 llcolor4; llcolor4.setAlpha(alpha); - ensure("setAlpha:Fail to initialize ", (alpha == llcolor4.mV[VALPHA])); + UNSCOPED_INFO("setAlpha:Fail to initialize "); + CHECK(alpha == llcolor4.mV[VALPHA]); } - template<> template<> - void v4color_object::test<6>() + TEST_CASE("v4color_h: magVecSquared and magVec", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF; LLColor4 llcolor4(r,g,b); - ensure("magVecSquared:Fail ", is_approx_equal(llcolor4.magVecSquared(), (r*r + g*g + b*b))); - ensure("magVec:Fail ", is_approx_equal(llcolor4.magVec(), (F32) sqrt(r*r + g*g + b*b))); + UNSCOPED_INFO("magVecSquared:Fail "); + CHECK(is_approx_equal(llcolor4.magVecSquared(), (r*r + g*g + b*b))); + UNSCOPED_INFO("magVec:Fail "); + CHECK(is_approx_equal(llcolor4.magVec(), (F32) sqrt(r*r + g*g + b*b))); } - template<> template<> - void v4color_object::test<7>() + TEST_CASE("v4color_h: normVec normalizes the color and returns its magnitude", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF; LLColor4 llcolor4(r,g,b); @@ -170,56 +241,73 @@ namespace F32 mag = (F32) sqrt(r*r + g*g + b*b); F32 oomag = 1.f / mag; F32 val1 = r * oomag, val2 = g * oomag, val3 = b * oomag; - ensure("1:normVec failed ", (is_approx_equal(val1, llcolor4.mV[0]) && is_approx_equal(val2, llcolor4.mV[1]) && is_approx_equal(val3, llcolor4.mV[2]) && is_approx_equal(vecMag, mag))); + { + INFO("1:normVec failed "); + CHECK(is_approx_equal(val1, llcolor4.mV[0])); + CHECK(is_approx_equal(val2, llcolor4.mV[1])); + CHECK(is_approx_equal(val3, llcolor4.mV[2])); + CHECK(is_approx_equal(vecMag, mag)); + } } - template<> template<> - void v4color_object::test<8>() + TEST_CASE("v4color_h: isOpaque only when alpha is exactly 1", "[v4color_h]") { LLColor4 llcolor4; - ensure("1:isOpaque failed ",(1 == llcolor4.isOpaque())); + UNSCOPED_INFO("1:isOpaque failed "); + CHECK(llcolor4.isOpaque() == 1); F32 r = 0x20, g = 0xFFFF, b = 0xFF,a = 1.f; llcolor4.setVec(r,g,b,a); - ensure("2:isOpaque failed ",(1 == llcolor4.isOpaque())); + UNSCOPED_INFO("2:isOpaque failed "); + CHECK(llcolor4.isOpaque() == 1); a = 2.f; llcolor4.setVec(r,g,b,a); - ensure("3:isOpaque failed ",(0 == llcolor4.isOpaque())); + UNSCOPED_INFO("3:isOpaque failed "); + CHECK(llcolor4.isOpaque() == 0); } - template<> template<> - void v4color_object::test<9>() + TEST_CASE("v4color_h: subscript operator reads components and gives references", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF; LLColor4 llcolor4(r,g,b); - ensure("1:operator [] failed",( r == llcolor4[0])); - ensure("2:operator [] failed",( g == llcolor4[1])); - ensure("3:operator [] failed",( b == llcolor4[2])); + UNSCOPED_INFO("1:operator [] failed"); + CHECK(r == llcolor4[0]); + UNSCOPED_INFO("2:operator [] failed"); + CHECK(g == llcolor4[1]); + UNSCOPED_INFO("3:operator [] failed"); + CHECK(b == llcolor4[2]); r = 0xA20, g = 0xFBFF, b = 0xFFF; llcolor4.setVec(r,g,b); F32 &ref1 = llcolor4[0]; - ensure("4:operator [] failed",( ref1 == llcolor4[0])); + UNSCOPED_INFO("4:operator [] failed"); + CHECK(ref1 == llcolor4[0]); F32 &ref2 = llcolor4[1]; - ensure("5:operator [] failed",( ref2 == llcolor4[1])); + UNSCOPED_INFO("5:operator [] failed"); + CHECK(ref2 == llcolor4[1]); F32 &ref3 = llcolor4[2]; - ensure("6:operator [] failed",( ref3 == llcolor4[2])); + UNSCOPED_INFO("6:operator [] failed"); + CHECK(ref3 == llcolor4[2]); } - template<> template<> - void v4color_object::test<10>() + TEST_CASE("v4color_h: assignment from LLColor3 and construction from LLSD", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF; LLColor3 llcolor3(r,g,b); LLColor4 llcolor4a,llcolor4b; llcolor4a = llcolor3; - ensure("Operator=:Fail to initialize ", ((llcolor3.mV[0] == llcolor4a.mV[VRED]) && (llcolor3.mV[1] == llcolor4a.mV[VGREEN]) && (llcolor3.mV[2] == llcolor4a.mV[VBLUE]))); + { + INFO("Operator=:Fail to initialize "); + CHECK(llcolor3.mV[0] == llcolor4a.mV[VRED]); + CHECK(llcolor3.mV[1] == llcolor4a.mV[VGREEN]); + CHECK(llcolor3.mV[2] == llcolor4a.mV[VBLUE]); + } LLSD sd = llcolor4a.getValue(); llcolor4b = LLColor4(sd); - ensure_equals("Operator= LLSD:Fail ", llcolor4a, llcolor4b); + UNSCOPED_INFO("Operator= LLSD:Fail "); + CHECK(llcolor4a == llcolor4b); } - template<> template<> - void v4color_object::test<11>() + TEST_CASE("v4color_h: operator<< prints equal colors the same", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF; std::ostringstream stream1, stream2; @@ -227,136 +315,204 @@ namespace stream1 << llcolor4a; llcolor4b.setVec(r,g,b); stream2 << llcolor4b; - ensure("operator << failed ", (stream1.str() == stream2.str())); + UNSCOPED_INFO("operator << failed "); + CHECK(stream1.str() == stream2.str()); } - template<> template<> - void v4color_object::test<12>() + TEST_CASE("v4color_h: operator+ and operator+=", "[v4color_h]") { F32 r1 = 0x20, g1 = 0xFFFF, b1 = 0xFF; F32 r2 = 0xABF, g2 = 0xFB, b2 = 0xFFF; LLColor4 llcolor4a(r1,g1,b1),llcolor4b(r2,g2,b2),llcolor4c; llcolor4c = llcolor4b + llcolor4a; - ensure("operator+:Fail to Add the values ", (is_approx_equal(r1+r2,llcolor4c.mV[VRED]) && is_approx_equal(g1+g2,llcolor4c.mV[VGREEN]) && is_approx_equal(b1+b2,llcolor4c.mV[VBLUE]))); + { + INFO("operator+:Fail to Add the values "); + CHECK(is_approx_equal(r1+r2,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1+g2,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1+b2,llcolor4c.mV[VBLUE])); + } llcolor4b += llcolor4a; - ensure("operator+=:Fail to Add the values ", (is_approx_equal(r1+r2,llcolor4b.mV[VRED]) && is_approx_equal(g1+g2,llcolor4b.mV[VGREEN]) && is_approx_equal(b1+b2,llcolor4b.mV[VBLUE]))); + { + INFO("operator+=:Fail to Add the values "); + CHECK(is_approx_equal(r1+r2,llcolor4b.mV[VRED])); + CHECK(is_approx_equal(g1+g2,llcolor4b.mV[VGREEN])); + CHECK(is_approx_equal(b1+b2,llcolor4b.mV[VBLUE])); + } } - template<> template<> - void v4color_object::test<13>() + TEST_CASE("v4color_h: operator- and operator-=", "[v4color_h]") { F32 r1 = 0x20, g1 = 0xFFFF, b1 = 0xFF; F32 r2 = 0xABF, g2 = 0xFB, b2 = 0xFFF; LLColor4 llcolor4a(r1,g1,b1),llcolor4b(r2,g2,b2),llcolor4c; llcolor4c = llcolor4a - llcolor4b; - ensure("operator-:Fail to subtract the values ", (is_approx_equal(r1-r2,llcolor4c.mV[VRED]) && is_approx_equal(g1-g2,llcolor4c.mV[VGREEN]) && is_approx_equal(b1-b2,llcolor4c.mV[VBLUE]))); + { + INFO("operator-:Fail to subtract the values "); + CHECK(is_approx_equal(r1-r2,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1-g2,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1-b2,llcolor4c.mV[VBLUE])); + } llcolor4a -= llcolor4b; - ensure("operator-=:Fail to subtract the values ", (is_approx_equal(r1-r2,llcolor4a.mV[VRED]) && is_approx_equal(g1-g2,llcolor4a.mV[VGREEN]) && is_approx_equal(b1-b2,llcolor4a.mV[VBLUE]))); + { + INFO("operator-=:Fail to subtract the values "); + CHECK(is_approx_equal(r1-r2,llcolor4a.mV[VRED])); + CHECK(is_approx_equal(g1-g2,llcolor4a.mV[VGREEN])); + CHECK(is_approx_equal(b1-b2,llcolor4a.mV[VBLUE])); + } } - template<> template<> - void v4color_object::test<14>() + TEST_CASE("v4color_h: operator* and operator*= by a color and by a scalar", "[v4color_h]") { F32 r1 = 0x20, g1 = 0xFFFF, b1 = 0xFF; F32 r2 = 0xABF, g2 = 0xFB, b2 = 0xFFF; LLColor4 llcolor4a(r1,g1,b1),llcolor4b(r2,g2,b2),llcolor4c; llcolor4c = llcolor4a * llcolor4b; - ensure("1:operator*:Fail to multiply the values", (is_approx_equal(r1*r2,llcolor4c.mV[VRED]) && is_approx_equal(g1*g2,llcolor4c.mV[VGREEN]) && is_approx_equal(b1*b2,llcolor4c.mV[VBLUE]))); + { + INFO("1:operator*:Fail to multiply the values"); + CHECK(is_approx_equal(r1*r2,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1*g2,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1*b2,llcolor4c.mV[VBLUE])); + } F32 mulVal = 3.33f; llcolor4c = llcolor4a * mulVal; - ensure("2:operator*:Fail ", (is_approx_equal(r1*mulVal,llcolor4c.mV[VRED]) && is_approx_equal(g1*mulVal,llcolor4c.mV[VGREEN]) && is_approx_equal(b1*mulVal,llcolor4c.mV[VBLUE]))); + { + INFO("2:operator*:Fail "); + CHECK(is_approx_equal(r1*mulVal,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1*mulVal,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1*mulVal,llcolor4c.mV[VBLUE])); + } llcolor4c = mulVal * llcolor4a; - ensure("3:operator*:Fail to multiply the values", (is_approx_equal(r1*mulVal,llcolor4c.mV[VRED]) && is_approx_equal(g1*mulVal,llcolor4c.mV[VGREEN]) && is_approx_equal(b1*mulVal,llcolor4c.mV[VBLUE]))); + { + INFO("3:operator*:Fail to multiply the values"); + CHECK(is_approx_equal(r1*mulVal,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1*mulVal,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1*mulVal,llcolor4c.mV[VBLUE])); + } llcolor4a *= mulVal; - ensure("4:operator*=:Fail to multiply the values ", (is_approx_equal(r1*mulVal,llcolor4a.mV[VRED]) && is_approx_equal(g1*mulVal,llcolor4a.mV[VGREEN]) && is_approx_equal(b1*mulVal,llcolor4a.mV[VBLUE]))); + { + INFO("4:operator*=:Fail to multiply the values "); + CHECK(is_approx_equal(r1*mulVal,llcolor4a.mV[VRED])); + CHECK(is_approx_equal(g1*mulVal,llcolor4a.mV[VGREEN])); + CHECK(is_approx_equal(b1*mulVal,llcolor4a.mV[VBLUE])); + } LLColor4 llcolor4d(r1,g1,b1),llcolor4e(r2,g2,b2); llcolor4e *= llcolor4d; - ensure("5:operator*=:Fail to multiply the values ", (is_approx_equal(r1*r2,llcolor4e.mV[VRED]) && is_approx_equal(g1*g2,llcolor4e.mV[VGREEN]) && is_approx_equal(b1*b2,llcolor4e.mV[VBLUE]))); + { + INFO("5:operator*=:Fail to multiply the values "); + CHECK(is_approx_equal(r1*r2,llcolor4e.mV[VRED])); + CHECK(is_approx_equal(g1*g2,llcolor4e.mV[VGREEN])); + CHECK(is_approx_equal(b1*b2,llcolor4e.mV[VBLUE])); + } } - template<> template<> - void v4color_object::test<15>() + TEST_CASE("v4color_h: operator% and operator%= scale only the alpha", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF,a = 0x30; F32 div = 12.345f; LLColor4 llcolor4a(r,g,b,a),llcolor4b; llcolor4b = llcolor4a % div;//chnage only alpha value nor r,g,b; - ensure("1operator%:Fail ", (is_approx_equal(r,llcolor4b.mV[VRED]) && is_approx_equal(g,llcolor4b.mV[VGREEN]) && is_approx_equal(b,llcolor4b.mV[VBLUE])&& is_approx_equal(div*a,llcolor4b.mV[VALPHA]))); + { + INFO("1operator%:Fail "); + CHECK(is_approx_equal(r,llcolor4b.mV[VRED])); + CHECK(is_approx_equal(g,llcolor4b.mV[VGREEN])); + CHECK(is_approx_equal(b,llcolor4b.mV[VBLUE])); + CHECK(is_approx_equal(div*a,llcolor4b.mV[VALPHA])); + } llcolor4b = div % llcolor4a; - ensure("2operator%:Fail ", (is_approx_equal(r,llcolor4b.mV[VRED]) && is_approx_equal(g,llcolor4b.mV[VGREEN]) && is_approx_equal(b,llcolor4b.mV[VBLUE])&& is_approx_equal(div*a,llcolor4b.mV[VALPHA]))); + { + INFO("2operator%:Fail "); + CHECK(is_approx_equal(r,llcolor4b.mV[VRED])); + CHECK(is_approx_equal(g,llcolor4b.mV[VGREEN])); + CHECK(is_approx_equal(b,llcolor4b.mV[VBLUE])); + CHECK(is_approx_equal(div*a,llcolor4b.mV[VALPHA])); + } llcolor4a %= div; - ensure("operator%=:Fail ", (is_approx_equal(a*div,llcolor4a.mV[VALPHA]))); + UNSCOPED_INFO("operator%=:Fail "); + CHECK(is_approx_equal(a*div,llcolor4a.mV[VALPHA])); } - template<> template<> - void v4color_object::test<16>() + TEST_CASE("v4color_h: operator== and operator!= against LLColor4 and LLColor3", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF,a = 0x30; LLColor4 llcolor4a(r,g,b,a),llcolor4b; llcolor4b = llcolor4a; - ensure("1:operator== failed to ensure the equality ", (llcolor4b == llcolor4a)); + UNSCOPED_INFO("1:operator== failed to ensure the equality "); + CHECK(llcolor4b == llcolor4a); F32 r1 = 0x2, g1 = 0xFF, b1 = 0xFA; LLColor3 llcolor3(r1,g1,b1); llcolor4b = llcolor3; - ensure("2:operator== failed to ensure the equality ", (llcolor4b == llcolor3)); - ensure("2:operator!= failed to ensure the equality ", (llcolor4a != llcolor3)); + UNSCOPED_INFO("2:operator== failed to ensure the equality "); + CHECK(llcolor4b == llcolor3); + UNSCOPED_INFO("2:operator!= failed to ensure the equality "); + CHECK(llcolor4a != llcolor3); } - template<> template<> - void v4color_object::test<17>() + TEST_CASE("v4color_h: vec4to3 and vec3to4", "[v4color_h]") { F32 r = 0x20, g = 0xFFFF, b = 0xFF; LLColor4 llcolor4a(r,g,b),llcolor4b; LLColor3 llcolor3 = vec4to3(llcolor4a); - ensure("vec4to3:Fail to convert vec4 to vec3 ", (is_approx_equal(llcolor3.mV[VRED],llcolor4a.mV[VRED]) && is_approx_equal(llcolor3.mV[VGREEN],llcolor4a.mV[VGREEN]) && is_approx_equal(llcolor3.mV[VBLUE],llcolor4a.mV[VBLUE]))); + { + INFO("vec4to3:Fail to convert vec4 to vec3 "); + CHECK(is_approx_equal(llcolor3.mV[VRED],llcolor4a.mV[VRED])); + CHECK(is_approx_equal(llcolor3.mV[VGREEN],llcolor4a.mV[VGREEN])); + CHECK(is_approx_equal(llcolor3.mV[VBLUE],llcolor4a.mV[VBLUE])); + } llcolor4b = vec3to4(llcolor3); - ensure_equals("vec3to4:Fail to convert vec3 to vec4 ", llcolor4b, llcolor4a); + UNSCOPED_INFO("vec3to4:Fail to convert vec3 to vec4 "); + CHECK(llcolor4b == llcolor4a); } - template<> template<> - void v4color_object::test<18>() + TEST_CASE("v4color_h: lerp interpolates every component", "[v4color_h]") { F32 r1 = 0x20, g1 = 0xFFFF, b1 = 0xFF, val = 0x20; F32 r2 = 0xABF, g2 = 0xFB, b2 = 0xFFF; LLColor4 llcolor4a(r1,g1,b1),llcolor4b(r2,g2,b2),llcolor4c; llcolor4c = lerp(llcolor4a,llcolor4b,val); - ensure("lerp:Fail ", (is_approx_equal(r1 + (r2 - r1)* val,llcolor4c.mV[VRED]) && is_approx_equal(g1 + (g2 - g1)* val,llcolor4c.mV[VGREEN]) && is_approx_equal(b1 + (b2 - b1)* val,llcolor4c.mV[VBLUE]))); + { + INFO("lerp:Fail "); + CHECK(is_approx_equal(r1 + (r2 - r1)* val,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1 + (g2 - g1)* val,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1 + (b2 - b1)* val,llcolor4c.mV[VBLUE])); + } } - template<> template<> - void v4color_object::test<19>() + TEST_CASE("v4color_h: parseColor reads color names and 0 to 255 components", "[v4color_h]") { F32 r = 12.0f, g = -2.3f, b = 1.32f, a = 5.0f; LLColor4 llcolor4a(r,g,b,a),llcolor4b; std::string color("red"); LLColor4::parseColor(color, &llcolor4b); - ensure_equals("1:parseColor() failed to parse the color value ", llcolor4b, LLColor4::red); + UNSCOPED_INFO("1:parseColor() failed to parse the color value "); + CHECK(llcolor4b == LLColor4::red); color = "12.0, -2.3, 1.32, 5.0"; LLColor4::parseColor(color, &llcolor4b); llcolor4a = llcolor4a * (1.f / 255.f); - ensure_equals("2:parseColor() failed to parse the color value ", llcolor4a,llcolor4b); + UNSCOPED_INFO("2:parseColor() failed to parse the color value "); + CHECK(llcolor4a == llcolor4b); color = "yellow5"; llcolor4a.setVec(r,g,b); LLColor4::parseColor(color, &llcolor4a); - ensure_equals("3:parseColor() failed to parse the color value ", llcolor4a, LLColor4::yellow5); + UNSCOPED_INFO("3:parseColor() failed to parse the color value "); + CHECK(llcolor4a == LLColor4::yellow5); } - template<> template<> - void v4color_object::test<20>() + TEST_CASE("v4color_h: parseColor4 reads four floats", "[v4color_h]") { F32 r = 12.0f, g = -2.3f, b = 1.32f, a = 5.0f; LLColor4 llcolor4a(r,g,b,a),llcolor4b; std::string color("12.0, -2.3, 1.32, 5.0"); LLColor4::parseColor4(color, &llcolor4b); - ensure_equals("parseColor4() failed to parse the color value ", llcolor4a, llcolor4b); + UNSCOPED_INFO("parseColor4() failed to parse the color value "); + CHECK(llcolor4a == llcolor4b); } } diff --git a/indra/llmath/tests/v4coloru_test.cpp b/indra/llmath/tests/v4coloru_test.cpp index 1dd48d801a..61d94b8207 100644 --- a/indra/llmath/tests/v4coloru_test.cpp +++ b/indra/llmath/tests/v4coloru_test.cpp @@ -28,121 +28,164 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "llsd.h" #include "../v4coloru.h" -namespace tut -{ namespace { - struct v4coloru_data - { - }; - typedef test_group v4coloru_test; - typedef v4coloru_test::object v4coloru_object; - tut::v4coloru_test v4coloru_testcase("v4coloru_h"); -} - - template<> template<> - void v4coloru_object::test<1>() + TEST_CASE("v4coloru_h: constructors set components and default alpha to 255", "[v4coloru_h]") { LLColor4U llcolor4u; - ensure("1:LLColor4u:Fail to initialize ", ((0 == llcolor4u.mV[VRED]) && (0 == llcolor4u.mV[VGREEN]) && (0 == llcolor4u.mV[VBLUE])&& (255 == llcolor4u.mV[VALPHA]))); + { + INFO("1:LLColor4u:Fail to initialize "); + CHECK(llcolor4u.mV[VRED] == 0); + CHECK(llcolor4u.mV[VGREEN] == 0); + CHECK(llcolor4u.mV[VBLUE] == 0); + CHECK(llcolor4u.mV[VALPHA] == 255); + } U8 r = 0x12, g = 0xFF, b = 0xAF, a = 0x23; LLColor4U llcolor4u1(r,g,b); - ensure("2:LLColor4u:Fail to initialize ", ((r == llcolor4u1.mV[VRED]) && (g == llcolor4u1.mV[VGREEN]) && (b == llcolor4u1.mV[VBLUE])&& (255 == llcolor4u1.mV[VALPHA]))); + { + INFO("2:LLColor4u:Fail to initialize "); + CHECK(r == llcolor4u1.mV[VRED]); + CHECK(g == llcolor4u1.mV[VGREEN]); + CHECK(b == llcolor4u1.mV[VBLUE]); + CHECK(llcolor4u1.mV[VALPHA] == 255); + } LLColor4U llcolor4u2(r,g,b,a); - ensure("3:LLColor4u:Fail to initialize ", ((r == llcolor4u2.mV[VRED]) && (g == llcolor4u2.mV[VGREEN]) && (b == llcolor4u2.mV[VBLUE])&& (a == llcolor4u2.mV[VALPHA]))); + { + INFO("3:LLColor4u:Fail to initialize "); + CHECK(r == llcolor4u2.mV[VRED]); + CHECK(g == llcolor4u2.mV[VGREEN]); + CHECK(b == llcolor4u2.mV[VBLUE]); + CHECK(a == llcolor4u2.mV[VALPHA]); + } const U8 vec[4] = {0x12,0xFF,0xAF,0x23}; LLColor4U llcolor4u3(vec); - ensure("4:LLColor4u:Fail to initialize ", ((vec[0] == llcolor4u3.mV[VRED]) && (vec[1] == llcolor4u3.mV[VGREEN]) && (vec[2] == llcolor4u3.mV[VBLUE])&& (vec[3] == llcolor4u3.mV[VALPHA]))); + { + INFO("4:LLColor4u:Fail to initialize "); + CHECK(vec[0] == llcolor4u3.mV[VRED]); + CHECK(vec[1] == llcolor4u3.mV[VGREEN]); + CHECK(vec[2] == llcolor4u3.mV[VBLUE]); + CHECK(vec[3] == llcolor4u3.mV[VALPHA]); + } LLSD sd = llcolor4u3.getValue(); LLColor4U llcolor4u4(sd); - ensure_equals("5:LLColor4u (LLSD) Failed ", llcolor4u4, llcolor4u3); + UNSCOPED_INFO("5:LLColor4u (LLSD) Failed "); + CHECK(llcolor4u4 == llcolor4u3); } - template<> template<> - void v4coloru_object::test<2>() + TEST_CASE("v4coloru_h: setValue reads back what getValue wrote", "[v4coloru_h]") { LLColor4U llcolor4ua(1, 2, 3, 4); LLSD sd = llcolor4ua.getValue(); LLColor4U llcolor4u; llcolor4u.setValue(sd); - ensure_equals("setValue(LLSD)/getValue Failed ", llcolor4u, llcolor4ua); + UNSCOPED_INFO("setValue(LLSD)/getValue Failed "); + CHECK(llcolor4u == llcolor4ua); } - template<> template<> - void v4coloru_object::test<3>() + TEST_CASE("v4coloru_h: setToBlack and setToWhite leave alpha at 255", "[v4coloru_h]") { U8 r = 0x12, g = 0xFF, b = 0xAF, a = 0x23; LLColor4U llcolor4u(r,g,b,a); llcolor4u.setToBlack(); - ensure("setToBlack:Fail to set black ", ((0 == llcolor4u.mV[VRED]) && (0 == llcolor4u.mV[VGREEN]) && (0 == llcolor4u.mV[VBLUE])&& (255 == llcolor4u.mV[VALPHA]))); + { + INFO("setToBlack:Fail to set black "); + CHECK(llcolor4u.mV[VRED] == 0); + CHECK(llcolor4u.mV[VGREEN] == 0); + CHECK(llcolor4u.mV[VBLUE] == 0); + CHECK(llcolor4u.mV[VALPHA] == 255); + } llcolor4u.setToWhite(); - ensure("setToWhite:Fail to white ", ((255 == llcolor4u.mV[VRED]) && (255 == llcolor4u.mV[VGREEN]) && (255 == llcolor4u.mV[VBLUE])&& (255 == llcolor4u.mV[VALPHA]))); + { + INFO("setToWhite:Fail to white "); + CHECK(llcolor4u.mV[VRED] == 255); + CHECK(llcolor4u.mV[VGREEN] == 255); + CHECK(llcolor4u.mV[VBLUE] == 255); + CHECK(llcolor4u.mV[VALPHA] == 255); + } } - template<> template<> - void v4coloru_object::test<4>() + TEST_CASE("v4coloru_h: cast from LLSD reads back what getValue wrote", "[v4coloru_h]") { U8 r = 0x12, g = 0xFF, b = 0xAF, a = 0x23; LLColor4U llcolor4ua(r,g,b,a); LLSD sd = llcolor4ua.getValue(); LLColor4U llcolor4u = (LLColor4U)sd; - ensure_equals("Operator=(LLSD) Failed ", llcolor4u, llcolor4ua); + UNSCOPED_INFO("Operator=(LLSD) Failed "); + CHECK(llcolor4u == llcolor4ua); } - template<> template<> - void v4coloru_object::test<5>() + TEST_CASE("v4coloru_h: setVec from values or another color or an array", "[v4coloru_h]") { U8 r = 0x12, g = 0xFF, b = 0xAF, a = 0x23; LLColor4U llcolor4u; llcolor4u.setVec(r,g,b,a); - ensure("1:setVec:Fail to set the values ", ((r == llcolor4u.mV[VRED]) && (g == llcolor4u.mV[VGREEN]) && (b == llcolor4u.mV[VBLUE])&& (a == llcolor4u.mV[VALPHA]))); + { + INFO("1:setVec:Fail to set the values "); + CHECK(r == llcolor4u.mV[VRED]); + CHECK(g == llcolor4u.mV[VGREEN]); + CHECK(b == llcolor4u.mV[VBLUE]); + CHECK(a == llcolor4u.mV[VALPHA]); + } llcolor4u.setToBlack(); llcolor4u.setVec(r,g,b); - ensure("2:setVec:Fail to set the values ", ((r == llcolor4u.mV[VRED]) && (g == llcolor4u.mV[VGREEN]) && (b == llcolor4u.mV[VBLUE])&& (255 == llcolor4u.mV[VALPHA]))); + { + INFO("2:setVec:Fail to set the values "); + CHECK(r == llcolor4u.mV[VRED]); + CHECK(g == llcolor4u.mV[VGREEN]); + CHECK(b == llcolor4u.mV[VBLUE]); + CHECK(llcolor4u.mV[VALPHA] == 255); + } LLColor4U llcolor4u1; llcolor4u1.setVec(llcolor4u); - ensure_equals("3:setVec:Fail to set the values ", llcolor4u1,llcolor4u); + UNSCOPED_INFO("3:setVec:Fail to set the values "); + CHECK(llcolor4u1 == llcolor4u); const U8 vec[4] = {0x12,0xFF,0xAF,0x23}; LLColor4U llcolor4u2; llcolor4u2.setVec(vec); - ensure("4:setVec:Fail to set the values ", ((vec[0] == llcolor4u2.mV[VRED]) && (vec[1] == llcolor4u2.mV[VGREEN]) && (vec[2] == llcolor4u2.mV[VBLUE])&& (vec[3] == llcolor4u2.mV[VALPHA]))); + { + INFO("4:setVec:Fail to set the values "); + CHECK(vec[0] == llcolor4u2.mV[VRED]); + CHECK(vec[1] == llcolor4u2.mV[VGREEN]); + CHECK(vec[2] == llcolor4u2.mV[VBLUE]); + CHECK(vec[3] == llcolor4u2.mV[VALPHA]); + } } - template<> template<> - void v4coloru_object::test<6>() + TEST_CASE("v4coloru_h: setAlpha sets the alpha", "[v4coloru_h]") { U8 alpha = 0x12; LLColor4U llcolor4u; llcolor4u.setAlpha(alpha); - ensure("setAlpha:Fail to set alpha value ", (alpha == llcolor4u.mV[VALPHA])); + UNSCOPED_INFO("setAlpha:Fail to set alpha value "); + CHECK(alpha == llcolor4u.mV[VALPHA]); } - template<> template<> - void v4coloru_object::test<7>() + TEST_CASE("v4coloru_h: magVecSquared and magVec", "[v4coloru_h]") { U8 r = 0x12, g = 0xFF, b = 0xAF; LLColor4U llcolor4u(r,g,b); - ensure("magVecSquared:Fail ", is_approx_equal(llcolor4u.magVecSquared(), (F32)(r*r + g*g + b*b))); - ensure("magVec:Fail ", is_approx_equal(llcolor4u.magVec(), (F32) sqrt((F32) (r*r + g*g + b*b)))); + UNSCOPED_INFO("magVecSquared:Fail "); + CHECK(is_approx_equal(llcolor4u.magVecSquared(), (F32)(r*r + g*g + b*b))); + UNSCOPED_INFO("magVec:Fail "); + CHECK(is_approx_equal(llcolor4u.magVec(), (F32) sqrt((F32) (r*r + g*g + b*b)))); } - template<> template<> - void v4coloru_object::test<8>() + TEST_CASE("v4coloru_h: operator<< prints equal colors the same", "[v4coloru_h]") { U8 r = 0x12, g = 0xFF, b = 0xAF; std::ostringstream stream1, stream2; @@ -150,154 +193,141 @@ namespace stream1 << llcolor4u1; llcolor4u2.setVec(r,g,b); stream2 << llcolor4u2; - ensure("operator << failed ", (stream1.str() == stream2.str())); + UNSCOPED_INFO("operator << failed "); + CHECK(stream1.str() == stream2.str()); } - template<> template<> - void v4coloru_object::test<9>() + TEST_CASE("v4coloru_h: operator+ and operator+= wrap modulo 256", "[v4coloru_h]") { U8 r1 = 0x12, g1 = 0xFF, b1 = 0xAF; U8 r2 = 0x1C, g2 = 0x9A, b2 = 0x1B; LLColor4U llcolor4u1(r1,g1,b1), llcolor4u2(r2,g2,b2),llcolor4u3; llcolor4u3 = llcolor4u1 + llcolor4u2; - ensure_equals( - "1a.operator+:Fail to Add the values ", - llcolor4u3.mV[VRED], + UNSCOPED_INFO("1a.operator+:Fail to Add the values "); + CHECK(llcolor4u3.mV[VRED] == (U8)(r1+r2)); - ensure_equals( - "1b.operator+:Fail to Add the values ", - llcolor4u3.mV[VGREEN], + UNSCOPED_INFO("1b.operator+:Fail to Add the values "); + CHECK(llcolor4u3.mV[VGREEN] == (U8)(g1+g2)); - ensure_equals( - "1c.operator+:Fail to Add the values ", - llcolor4u3.mV[VBLUE], + UNSCOPED_INFO("1c.operator+:Fail to Add the values "); + CHECK(llcolor4u3.mV[VBLUE] == (U8)(b1+b2)); llcolor4u2 += llcolor4u1; - ensure_equals( - "2a.operator+=:Fail to Add the values ", - llcolor4u2.mV[VRED], + UNSCOPED_INFO("2a.operator+=:Fail to Add the values "); + CHECK(llcolor4u2.mV[VRED] == (U8)(r1+r2)); - ensure_equals( - "2b.operator+=:Fail to Add the values ", - llcolor4u2.mV[VGREEN], + UNSCOPED_INFO("2b.operator+=:Fail to Add the values "); + CHECK(llcolor4u2.mV[VGREEN] == (U8)(g1+g2)); - ensure_equals( - "2c.operator+=:Fail to Add the values ", - llcolor4u2.mV[VBLUE], + UNSCOPED_INFO("2c.operator+=:Fail to Add the values "); + CHECK(llcolor4u2.mV[VBLUE] == (U8)(b1+b2)); } - template<> template<> - void v4coloru_object::test<10>() + TEST_CASE("v4coloru_h: operator- and operator-= wrap modulo 256", "[v4coloru_h]") { U8 r1 = 0x12, g1 = 0xFF, b1 = 0xAF; U8 r2 = 0x1C, g2 = 0x9A, b2 = 0x1B; LLColor4U llcolor4u1(r1,g1,b1), llcolor4u2(r2,g2,b2),llcolor4u3; llcolor4u3 = llcolor4u1 - llcolor4u2; - ensure_equals( - "1a. operator-:Fail to Add the values ", - llcolor4u3.mV[VRED], + UNSCOPED_INFO("1a. operator-:Fail to Add the values "); + CHECK(llcolor4u3.mV[VRED] == (U8)(r1-r2)); - ensure_equals( - "1b. operator-:Fail to Add the values ", - llcolor4u3.mV[VGREEN], + UNSCOPED_INFO("1b. operator-:Fail to Add the values "); + CHECK(llcolor4u3.mV[VGREEN] == (U8)(g1-g2)); - ensure_equals( - "1c. operator-:Fail to Add the values ", - llcolor4u3.mV[VBLUE], + UNSCOPED_INFO("1c. operator-:Fail to Add the values "); + CHECK(llcolor4u3.mV[VBLUE] == (U8)(b1-b2)); llcolor4u1 -= llcolor4u2; - ensure_equals( - "2a. operator-=:Fail to Add the values ", - llcolor4u1.mV[VRED], + UNSCOPED_INFO("2a. operator-=:Fail to Add the values "); + CHECK(llcolor4u1.mV[VRED] == (U8)(r1-r2)); - ensure_equals( - "2b. operator-=:Fail to Add the values ", - llcolor4u1.mV[VGREEN], + UNSCOPED_INFO("2b. operator-=:Fail to Add the values "); + CHECK(llcolor4u1.mV[VGREEN] == (U8)(g1-g2)); - ensure_equals( - "2c. operator-=:Fail to Add the values ", - llcolor4u1.mV[VBLUE], + UNSCOPED_INFO("2c. operator-=:Fail to Add the values "); + CHECK(llcolor4u1.mV[VBLUE] == (U8)(b1-b2)); } - template<> template<> - void v4coloru_object::test<11>() + TEST_CASE("v4coloru_h: operator* by a color and operator*= by a scalar wrap modulo 256", "[v4coloru_h]") { U8 r1 = 0x12, g1 = 0xFF, b1 = 0xAF; U8 r2 = 0x1C, g2 = 0x9A, b2 = 0x1B; LLColor4U llcolor4u1(r1,g1,b1), llcolor4u2(r2,g2,b2),llcolor4u3; llcolor4u3 = llcolor4u1 * llcolor4u2; - ensure_equals( - "1a. operator*:Fail to multiply the values", - llcolor4u3.mV[VRED], + UNSCOPED_INFO("1a. operator*:Fail to multiply the values"); + CHECK(llcolor4u3.mV[VRED] == (U8)(r1*r2)); - ensure_equals( - "1b. operator*:Fail to multiply the values", - llcolor4u3.mV[VGREEN], + UNSCOPED_INFO("1b. operator*:Fail to multiply the values"); + CHECK(llcolor4u3.mV[VGREEN] == (U8)(g1*g2)); - ensure_equals( - "1c. operator*:Fail to multiply the values", - llcolor4u3.mV[VBLUE], + UNSCOPED_INFO("1c. operator*:Fail to multiply the values"); + CHECK(llcolor4u3.mV[VBLUE] == (U8)(b1*b2)); U8 mulVal = 123; llcolor4u1 *= mulVal; - ensure_equals( - "2a. operator*=:Fail to multiply the values", - llcolor4u1.mV[VRED], + UNSCOPED_INFO("2a. operator*=:Fail to multiply the values"); + CHECK(llcolor4u1.mV[VRED] == (U8)(r1*mulVal)); - ensure_equals( - "2b. operator*=:Fail to multiply the values", - llcolor4u1.mV[VGREEN], + UNSCOPED_INFO("2b. operator*=:Fail to multiply the values"); + CHECK(llcolor4u1.mV[VGREEN] == (U8)(g1*mulVal)); - ensure_equals( - "2c. operator*=:Fail to multiply the values", - llcolor4u1.mV[VBLUE], + UNSCOPED_INFO("2c. operator*=:Fail to multiply the values"); + CHECK(llcolor4u1.mV[VBLUE] == (U8)(b1*mulVal)); } - template<> template<> - void v4coloru_object::test<12>() + TEST_CASE("v4coloru_h: operator== and operator!=", "[v4coloru_h]") { U8 r = 0x12, g = 0xFF, b = 0xAF; LLColor4U llcolor4u(r,g,b),llcolor4u1; llcolor4u1 = llcolor4u; - ensure("operator== failed to ensure the equality ", (llcolor4u1 == llcolor4u)); + UNSCOPED_INFO("operator== failed to ensure the equality "); + CHECK(llcolor4u1 == llcolor4u); llcolor4u1.setToBlack(); - ensure("operator!= failed to ensure the equality ", (llcolor4u1 != llcolor4u)); + UNSCOPED_INFO("operator!= failed to ensure the equality "); + CHECK(llcolor4u1 != llcolor4u); } - template<> template<> - void v4coloru_object::test<13>() + TEST_CASE("v4coloru_h: operator%= multiplies the alpha", "[v4coloru_h]") { U8 r = 0x12, g = 0xFF, b = 0xAF, a = 12; LLColor4U llcolor4u(r,g,b,a); U8 modVal = 45; llcolor4u %= modVal; - ensure_equals("operator%=:Fail ", llcolor4u.mV[VALPHA], (U8)(a * modVal)); + UNSCOPED_INFO("operator%=:Fail "); + CHECK(llcolor4u.mV[VALPHA] == (U8)(a * modVal)); } - template<> template<> - void v4coloru_object::test<14>() + TEST_CASE("v4coloru_h: parseColor4U reads four values and rejects fewer", "[v4coloru_h]") { U8 r = 0x12, g = 0xFF, b = 0xAF, a = 12; LLColor4U llcolor4u1(r,g,b,a); std::string color("12, 23, 132, 50"); LLColor4U::parseColor4U(color, &llcolor4u1); - ensure("parseColor4U() failed to parse the color value ", ((12 == llcolor4u1.mV[VRED]) && (23 == llcolor4u1.mV[VGREEN]) && (132 == llcolor4u1.mV[VBLUE])&& (50 == llcolor4u1.mV[VALPHA]))); + { + INFO("parseColor4U() failed to parse the color value "); + CHECK(llcolor4u1.mV[VRED] == 12); + CHECK(llcolor4u1.mV[VGREEN] == 23); + CHECK(llcolor4u1.mV[VBLUE] == 132); + CHECK(llcolor4u1.mV[VALPHA] == 50); + } color = "12, 23, 132"; - ensure("2:parseColor4U() failed to parse the color value ", (false == LLColor4U::parseColor4U(color, &llcolor4u1))); + UNSCOPED_INFO("2:parseColor4U() failed to parse the color value "); + CHECK_FALSE(LLColor4U::parseColor4U(color, &llcolor4u1)); color = "12"; - ensure("2:parseColor4U() failed to parse the color value ", (false == LLColor4U::parseColor4U(color, &llcolor4u1))); + UNSCOPED_INFO("2:parseColor4U() failed to parse the color value "); + CHECK_FALSE(LLColor4U::parseColor4U(color, &llcolor4u1)); } - template<> template<> - void v4coloru_object::test<15>() + TEST_CASE("v4coloru_h: multAll rounds and clamps each channel", "[v4coloru_h]") { U8 r = 12, g = 123, b = 3, a = 2; LLColor4U llcolor4u(r,g,b,a),llcolor4u1; @@ -307,31 +337,43 @@ namespace // Previously the test used (U8)ll_round(...) directly, which wraps // modulo 256 for products > 255 (e.g. 123 * 3 = 369 -> 113), locking // in the wrap bug. multAll now clamps, so green saturates at 255. - ensure("multAll:Fail to multiply ", - ((U8)llclampb(ll_round(r * fVal)) == llcolor4u1.mV[VRED]) - && ((U8)llclampb(ll_round(g * fVal)) == llcolor4u1.mV[VGREEN]) - && ((U8)llclampb(ll_round(b * fVal)) == llcolor4u1.mV[VBLUE]) - && ((U8)llclampb(ll_round(a * fVal)) == llcolor4u1.mV[VALPHA])); + { + INFO("multAll:Fail to multiply "); + CHECK((U8)llclampb(ll_round(r * fVal)) == llcolor4u1.mV[VRED]); + CHECK((U8)llclampb(ll_round(g * fVal)) == llcolor4u1.mV[VGREEN]); + CHECK((U8)llclampb(ll_round(b * fVal)) == llcolor4u1.mV[VBLUE]); + CHECK((U8)llclampb(ll_round(a * fVal)) == llcolor4u1.mV[VALPHA]); + } } - template<> template<> - void v4coloru_object::test<16>() + TEST_CASE("v4coloru_h: addClampMax adds and clamps each channel at 255", "[v4coloru_h]") { U8 r1 = 12, g1 = 123, b1 = 3, a1 = 2; U8 r2 = 23, g2 = 230, b2 = 124, a2 = 255; LLColor4U llcolor4u(r1,g1,b1,a1),llcolor4u1(r2,g2,b2,a2); llcolor4u1 = llcolor4u1.addClampMax(llcolor4u); - ensure("1:addClampMax():Fail to add the value ", ((r1+r2 == llcolor4u1.mV[VRED]) && (255 == llcolor4u1.mV[VGREEN]) && (b1+b2 == llcolor4u1.mV[VBLUE])&& (255 == llcolor4u1.mV[VALPHA]))); + { + INFO("1:addClampMax():Fail to add the value "); + CHECK(r1+r2 == llcolor4u1.mV[VRED]); + CHECK(llcolor4u1.mV[VGREEN] == 255); + CHECK(b1+b2 == llcolor4u1.mV[VBLUE]); + CHECK(llcolor4u1.mV[VALPHA] == 255); + } r1 = 132, g1 = 3, b1 = 3, a1 = 2; r2 = 123, g2 = 230, b2 = 154, a2 = 25; LLColor4U llcolor4u2(r1,g1,b1,a1),llcolor4u3(r2,g2,b2,a2); llcolor4u3 = llcolor4u3.addClampMax(llcolor4u2); - ensure("2:addClampMax():Fail to add the value ", ((255 == llcolor4u3.mV[VRED]) && (g1+g2 == llcolor4u3.mV[VGREEN]) && (b1+b2 == llcolor4u3.mV[VBLUE])&& (a1+a2 == llcolor4u3.mV[VALPHA]))); + { + INFO("2:addClampMax():Fail to add the value "); + CHECK(llcolor4u3.mV[VRED] == 255); + CHECK(g1+g2 == llcolor4u3.mV[VGREEN]); + CHECK(b1+b2 == llcolor4u3.mV[VBLUE]); + CHECK(a1+a2 == llcolor4u3.mV[VALPHA]); + } } - template<> template<> - void v4coloru_object::test<17>() + TEST_CASE("v4coloru_h: setVecScaleClamp scales the brightest channel to 255 and clamps at 0", "[v4coloru_h]") { F32 r = 23.f, g = 12.32f, b = -12.3f; LLColor3 color3(r,g,b); @@ -341,6 +383,12 @@ namespace F32 color_scale_factor = MAX_COLOR/r; S32 r2 = ll_round(r * color_scale_factor); S32 g2 = ll_round(g * color_scale_factor); - ensure("setVecScaleClamp():Fail to add the value ", ((r2 == llcolor4u.mV[VRED]) && (g2 == llcolor4u.mV[VGREEN]) && (0 == llcolor4u.mV[VBLUE])&& (255 == llcolor4u.mV[VALPHA]))); + { + INFO("setVecScaleClamp():Fail to add the value "); + CHECK(r2 == llcolor4u.mV[VRED]); + CHECK(g2 == llcolor4u.mV[VGREEN]); + CHECK(llcolor4u.mV[VBLUE] == 0); + CHECK(llcolor4u.mV[VALPHA] == 255); + } } } diff --git a/indra/llmath/tests/v4math_test.cpp b/indra/llmath/tests/v4math_test.cpp index aba953563f..d49589bd3e 100644 --- a/indra/llmath/tests/v4math_test.cpp +++ b/indra/llmath/tests/v4math_test.cpp @@ -27,142 +27,237 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "llsd.h" #include "../m4math.h" #include "../v4math.h" #include "../llquaternion.h" -namespace tut -{ namespace { - struct v4math_data - { - }; - typedef test_group v4math_test; - typedef v4math_test::object v4math_object; - tut::v4math_test v4math_testcase("v4math_h"); -} - - template<> template<> - void v4math_object::test<1>() + TEST_CASE("v4math_h: constructors set every component and default w to 1", "[v4math_h]") { LLVector4 vec4; - ensure("1:LLVector4:Fail to initialize " ,((0 == vec4.mV[VX]) && (0 == vec4.mV[VY]) && (0 == vec4.mV[VZ])&& (1.0f == vec4.mV[VW]))); + { + INFO("1:LLVector4:Fail to initialize "); + CHECK(vec4.mV[VX] == 0); + CHECK(vec4.mV[VY] == 0); + CHECK(vec4.mV[VZ] == 0); + CHECK(vec4.mV[VW] == 1.0f); + } F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4a(x,y,z); - ensure("2:LLVector4:Fail to initialize " ,((x == vec4a.mV[VX]) && (y == vec4a.mV[VY]) && (z == vec4a.mV[VZ])&& (1.0f == vec4a.mV[VW]))); + { + INFO("2:LLVector4:Fail to initialize "); + CHECK(x == vec4a.mV[VX]); + CHECK(y == vec4a.mV[VY]); + CHECK(z == vec4a.mV[VZ]); + CHECK(vec4a.mV[VW] == 1.0f); + } LLVector4 vec4b(x,y,z,w); - ensure("3:LLVector4:Fail to initialize " ,((x == vec4b.mV[VX]) && (y == vec4b.mV[VY]) && (z == vec4b.mV[VZ])&& (w == vec4b.mV[VW]))); + { + INFO("3:LLVector4:Fail to initialize "); + CHECK(x == vec4b.mV[VX]); + CHECK(y == vec4b.mV[VY]); + CHECK(z == vec4b.mV[VZ]); + CHECK(w == vec4b.mV[VW]); + } const F32 vec[4] = {.112f ,23.2f, -4.2f, -.0001f}; LLVector4 vec4c(vec); - ensure("4:LLVector4:Fail to initialize " ,((vec[0] == vec4c.mV[VX]) && (vec[1] == vec4c.mV[VY]) && (vec[2] == vec4c.mV[VZ])&& (vec[3] == vec4c.mV[VW]))); + { + INFO("4:LLVector4:Fail to initialize "); + CHECK(vec[0] == vec4c.mV[VX]); + CHECK(vec[1] == vec4c.mV[VY]); + CHECK(vec[2] == vec4c.mV[VZ]); + CHECK(vec[3] == vec4c.mV[VW]); + } LLVector3 vec3(-2.23f,1.01f,42.3f); LLVector4 vec4d(vec3); - ensure("5:LLVector4:Fail to initialize " ,((vec3.mV[VX] == vec4d.mV[VX]) && (vec3.mV[VY] == vec4d.mV[VY]) && (vec3.mV[VZ] == vec4d.mV[VZ])&& (1.f == vec4d.mV[VW]))); + { + INFO("5:LLVector4:Fail to initialize "); + CHECK(vec3.mV[VX] == vec4d.mV[VX]); + CHECK(vec3.mV[VY] == vec4d.mV[VY]); + CHECK(vec3.mV[VZ] == vec4d.mV[VZ]); + CHECK(vec4d.mV[VW] == 1.f); + } F32 w1 = -.234f; LLVector4 vec4e(vec3,w1); - ensure("6:LLVector4:Fail to initialize " ,((vec3.mV[VX] == vec4e.mV[VX]) && (vec3.mV[VY] == vec4e.mV[VY]) && (vec3.mV[VZ] == vec4e.mV[VZ])&& (w1 == vec4e.mV[VW]))); + { + INFO("6:LLVector4:Fail to initialize "); + CHECK(vec3.mV[VX] == vec4e.mV[VX]); + CHECK(vec3.mV[VY] == vec4e.mV[VY]); + CHECK(vec3.mV[VZ] == vec4e.mV[VZ]); + CHECK(w1 == vec4e.mV[VW]); + } } - template<> template<> - void v4math_object::test<2>() + TEST_CASE("v4math_h: setVec sets components and clearVec/zeroVec reset them", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4; vec4.setVec(x,y,z); - ensure("1:setVec:Fail to initialize " ,((x == vec4.mV[VX]) && (y == vec4.mV[VY]) && (z == vec4.mV[VZ])&& (1.0f == vec4.mV[VW]))); + { + INFO("1:setVec:Fail to initialize "); + CHECK(x == vec4.mV[VX]); + CHECK(y == vec4.mV[VY]); + CHECK(z == vec4.mV[VZ]); + CHECK(vec4.mV[VW] == 1.0f); + } vec4.clearVec(); - ensure("2:clearVec:Fail " ,((0 == vec4.mV[VX]) && (0 == vec4.mV[VY]) && (0 == vec4.mV[VZ])&& (1.0f == vec4.mV[VW]))); + { + INFO("2:clearVec:Fail "); + CHECK(vec4.mV[VX] == 0); + CHECK(vec4.mV[VY] == 0); + CHECK(vec4.mV[VZ] == 0); + CHECK(vec4.mV[VW] == 1.0f); + } vec4.setVec(x,y,z,w); - ensure("3:setVec:Fail to initialize " ,((x == vec4.mV[VX]) && (y == vec4.mV[VY]) && (z == vec4.mV[VZ])&& (w == vec4.mV[VW]))); + { + INFO("3:setVec:Fail to initialize "); + CHECK(x == vec4.mV[VX]); + CHECK(y == vec4.mV[VY]); + CHECK(z == vec4.mV[VZ]); + CHECK(w == vec4.mV[VW]); + } vec4.zeroVec(); - ensure("4:zeroVec:Fail " ,((0 == vec4.mV[VX]) && (0 == vec4.mV[VY]) && (0 == vec4.mV[VZ])&& (0 == vec4.mV[VW]))); + { + INFO("4:zeroVec:Fail "); + CHECK(vec4.mV[VX] == 0); + CHECK(vec4.mV[VY] == 0); + CHECK(vec4.mV[VZ] == 0); + CHECK(vec4.mV[VW] == 0); + } LLVector3 vec3(-2.23f,1.01f,42.3f); vec4.clearVec(); vec4.setVec(vec3); - ensure("5:setVec:Fail to initialize " ,((vec3.mV[VX] == vec4.mV[VX]) && (vec3.mV[VY] == vec4.mV[VY]) && (vec3.mV[VZ] == vec4.mV[VZ])&& (1.f == vec4.mV[VW]))); + { + INFO("5:setVec:Fail to initialize "); + CHECK(vec3.mV[VX] == vec4.mV[VX]); + CHECK(vec3.mV[VY] == vec4.mV[VY]); + CHECK(vec3.mV[VZ] == vec4.mV[VZ]); + CHECK(vec4.mV[VW] == 1.f); + } F32 w1 = -.234f; vec4.zeroVec(); vec4.setVec(vec3,w1); - ensure("6:setVec:Fail to initialize " ,((vec3.mV[VX] == vec4.mV[VX]) && (vec3.mV[VY] == vec4.mV[VY]) && (vec3.mV[VZ] == vec4.mV[VZ])&& (w1 == vec4.mV[VW]))); + { + INFO("6:setVec:Fail to initialize "); + CHECK(vec3.mV[VX] == vec4.mV[VX]); + CHECK(vec3.mV[VY] == vec4.mV[VY]); + CHECK(vec3.mV[VZ] == vec4.mV[VZ]); + CHECK(w1 == vec4.mV[VW]); + } const F32 vec[4] = {.112f ,23.2f, -4.2f, -.0001f}; LLVector4 vec4a; vec4a.setVec(vec); - ensure("7:setVec:Fail to initialize " ,((vec[0] == vec4a.mV[VX]) && (vec[1] == vec4a.mV[VY]) && (vec[2] == vec4a.mV[VZ])&& (vec[3] == vec4a.mV[VW]))); + { + INFO("7:setVec:Fail to initialize "); + CHECK(vec[0] == vec4a.mV[VX]); + CHECK(vec[1] == vec4a.mV[VY]); + CHECK(vec[2] == vec4a.mV[VZ]); + CHECK(vec[3] == vec4a.mV[VW]); + } } - template<> template<> - void v4math_object::test<3>() + TEST_CASE("v4math_h: magVec and magVecSquared", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f; LLVector4 vec4(x,y,z); - ensure("magVec:Fail ", is_approx_equal(vec4.magVec(), (F32) sqrt(x*x + y*y + z*z))); - ensure("magVecSquared:Fail ", is_approx_equal(vec4.magVecSquared(), (x*x + y*y + z*z))); + UNSCOPED_INFO("magVec:Fail "); + CHECK(is_approx_equal(vec4.magVec(), (F32) sqrt(x*x + y*y + z*z))); + UNSCOPED_INFO("magVecSquared:Fail "); + CHECK(is_approx_equal(vec4.magVecSquared(), (x*x + y*y + z*z))); } - template<> template<> - void v4math_object::test<4>() + TEST_CASE("v4math_h: normVec normalizes and zeroes a near-zero vector", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f; LLVector4 vec4(x,y,z); F32 mag = vec4.normVec(); mag = 1.f/ mag; - ensure("1:normVec: Fail " ,is_approx_equal(mag*x,vec4.mV[VX]) && is_approx_equal(mag*y, vec4.mV[VY])&& is_approx_equal(mag*z, vec4.mV[VZ])); + { + INFO("1:normVec: Fail "); + CHECK(is_approx_equal(mag*x,vec4.mV[VX])); + CHECK(is_approx_equal(mag*y, vec4.mV[VY])); + CHECK(is_approx_equal(mag*z, vec4.mV[VZ])); + } x = 0.000000001f, y = 0.000000001f, z = 0.000000001f; vec4.clearVec(); vec4.setVec(x,y,z); mag = vec4.normVec(); - ensure("2:normVec: Fail " ,is_approx_equal(mag*x,vec4.mV[VX]) && is_approx_equal(mag*y, vec4.mV[VY])&& is_approx_equal(mag*z, vec4.mV[VZ])); + { + INFO("2:normVec: Fail "); + CHECK(is_approx_equal(mag*x,vec4.mV[VX])); + CHECK(is_approx_equal(mag*y, vec4.mV[VY])); + CHECK(is_approx_equal(mag*z, vec4.mV[VZ])); + } } - template<> template<> - void v4math_object::test<5>() + TEST_CASE("v4math_h: abs drops signs; isExactlyClear and isExactlyZero", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4(x,y,z,w); vec4.abs(); - ensure("abs:Fail " ,((x == vec4.mV[VX]) && (-y == vec4.mV[VY]) && (-z == vec4.mV[VZ])&& (-w == vec4.mV[VW]))); + { + INFO("abs:Fail "); + CHECK(x == vec4.mV[VX]); + CHECK(-y == vec4.mV[VY]); + CHECK(-z == vec4.mV[VZ]); + CHECK(-w == vec4.mV[VW]); + } vec4.clearVec(); - ensure("isExactlyClear:Fail " ,(true == vec4.isExactlyClear())); + UNSCOPED_INFO("isExactlyClear:Fail "); + CHECK(vec4.isExactlyClear()); vec4.zeroVec(); - ensure("isExactlyZero:Fail " ,(true == vec4.isExactlyZero())); + UNSCOPED_INFO("isExactlyZero:Fail "); + CHECK(vec4.isExactlyZero()); } - template<> template<> - void v4math_object::test<6>() + TEST_CASE("v4math_h: scaleVec multiplies component-wise", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4(x,y,z,w),vec4a; vec4a = vec4.scaleVec(vec4); - ensure("scaleVec:Fail " ,(is_approx_equal(x*x, vec4a.mV[VX]) && is_approx_equal(y*y, vec4a.mV[VY]) && is_approx_equal(z*z, vec4a.mV[VZ])&& is_approx_equal(w*w, vec4a.mV[VW]))); + { + INFO("scaleVec:Fail "); + CHECK(is_approx_equal(x*x, vec4a.mV[VX])); + CHECK(is_approx_equal(y*y, vec4a.mV[VY])); + CHECK(is_approx_equal(z*z, vec4a.mV[VZ])); + CHECK(is_approx_equal(w*w, vec4a.mV[VW])); + } } - template<> template<> - void v4math_object::test<7>() + TEST_CASE("v4math_h: subscript operator reads components and gives references", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4(x,y,z,w); - ensure("1:operator [] failed " ,( x == vec4[0])); - ensure("2:operator [] failed " ,( y == vec4[1])); - ensure("3:operator [] failed " ,( z == vec4[2])); - ensure("4:operator [] failed " ,( w == vec4[3])); + UNSCOPED_INFO("1:operator [] failed "); + CHECK(x == vec4[0]); + UNSCOPED_INFO("2:operator [] failed "); + CHECK(y == vec4[1]); + UNSCOPED_INFO("3:operator [] failed "); + CHECK(z == vec4[2]); + UNSCOPED_INFO("4:operator [] failed "); + CHECK(w == vec4[3]); x = 23.f, y = -.2361f, z = 3.25; vec4.setVec(x,y,z); F32 &ref1 = vec4[0]; - ensure("5:operator [] failed " ,( ref1 == vec4[0])); + UNSCOPED_INFO("5:operator [] failed "); + CHECK(ref1 == vec4[0]); F32 &ref2 = vec4[1]; - ensure("6:operator [] failed " ,( ref2 == vec4[1])); + UNSCOPED_INFO("6:operator [] failed "); + CHECK(ref2 == vec4[1]); F32 &ref3 = vec4[2]; - ensure("7:operator [] failed " ,( ref3 == vec4[2])); + UNSCOPED_INFO("7:operator [] failed "); + CHECK(ref3 == vec4[2]); F32 &ref4 = vec4[3]; - ensure("8:operator [] failed " ,( ref4 == vec4[3])); + UNSCOPED_INFO("8:operator [] failed "); + CHECK(ref4 == vec4[3]); } - template<> template<> - void v4math_object::test<8>() + TEST_CASE("v4math_h: rotVec by a matrix and by a quaternion is repeatable", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; const F32 val[16] = { @@ -176,18 +271,19 @@ namespace vec4.rotVec(mat); vec4a.setVec(x,y,z,w); vec4a.rotVec(mat); - ensure_equals("1:rotVec: Fail " ,vec4a, vec4); + UNSCOPED_INFO("1:rotVec: Fail "); + CHECK(vec4a == vec4); F32 a = 2.32f, b = -23.2f, c = -34.1112f, d = 1.010112f; LLQuaternion q(a,b,c,d); LLVector4 vec4b(a,b,c,d),vec4c; vec4b.rotVec(q); vec4c.setVec(a, b, c, d); vec4c.rotVec(q); - ensure_equals("2:rotVec: Fail " ,vec4b, vec4c); + UNSCOPED_INFO("2:rotVec: Fail "); + CHECK(vec4b == vec4c); } - template<> template<> - void v4math_object::test<9>() + TEST_CASE("v4math_h: operator<< prints equal vectors the same", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4(x,y,z,w),vec4a;; @@ -195,126 +291,182 @@ namespace stream1 << vec4; vec4a.setVec(x,y,z,w); stream2 << vec4a; - ensure("operator << failed",(stream1.str() == stream2.str())); + UNSCOPED_INFO("operator << failed"); + CHECK(stream1.str() == stream2.str()); } - template<> template<> - void v4math_object::test<10>() + TEST_CASE("v4math_h: operator+ and operator+=", "[v4math_h]") { F32 x1 = 1.f, y1 = 2.f, z1 = -1.1f, w1 = .23f; F32 x2 = 1.2f, y2 = 2.5f, z2 = 1.f, w2 = 1.3f; LLVector4 vec4(x1,y1,z1,w1),vec4a(x2,y2,z2,w2),vec4b; vec4b = vec4a + vec4; - ensure("1:operator+:Fail to initialize " ,(is_approx_equal(x1+x2,vec4b.mV[VX]) && is_approx_equal(y1+y2,vec4b.mV[VY]) && is_approx_equal(z1+z2,vec4b.mV[VZ]))); + { + INFO("1:operator+:Fail to initialize "); + CHECK(is_approx_equal(x1+x2,vec4b.mV[VX])); + CHECK(is_approx_equal(y1+y2,vec4b.mV[VY])); + CHECK(is_approx_equal(z1+z2,vec4b.mV[VZ])); + } x1 = -2.45f, y1 = 2.1f, z1 = 3.0f; vec4.clearVec(); vec4a.clearVec(); vec4.setVec(x1,y1,z1); vec4a +=vec4; - ensure_equals("2:operator+=: Fail to initialize", vec4a,vec4); + UNSCOPED_INFO("2:operator+=: Fail to initialize"); + CHECK(vec4a == vec4); vec4a += vec4; - ensure("3:operator+=:Fail to initialize " ,(is_approx_equal(2*x1,vec4a.mV[VX]) && is_approx_equal(2*y1,vec4a.mV[VY]) && is_approx_equal(2*z1,vec4a.mV[VZ]))); + { + INFO("3:operator+=:Fail to initialize "); + CHECK(is_approx_equal(2*x1,vec4a.mV[VX])); + CHECK(is_approx_equal(2*y1,vec4a.mV[VY])); + CHECK(is_approx_equal(2*z1,vec4a.mV[VZ])); + } } - template<> template<> - void v4math_object::test<11>() + TEST_CASE("v4math_h: operator- and operator-=", "[v4math_h]") { F32 x1 = 1.f, y1 = 2.f, z1 = -1.1f, w1 = .23f; F32 x2 = 1.2f, y2 = 2.5f, z2 = 1.f, w2 = 1.3f; LLVector4 vec4(x1,y1,z1,w1),vec4a(x2,y2,z2,w2),vec4b; vec4b = vec4a - vec4; - ensure("1:operator-:Fail to initialize " ,(is_approx_equal(x2-x1,vec4b.mV[VX]) && is_approx_equal(y2-y1,vec4b.mV[VY]) && is_approx_equal(z2-z1,vec4b.mV[VZ]))); + { + INFO("1:operator-:Fail to initialize "); + CHECK(is_approx_equal(x2-x1,vec4b.mV[VX])); + CHECK(is_approx_equal(y2-y1,vec4b.mV[VY])); + CHECK(is_approx_equal(z2-z1,vec4b.mV[VZ])); + } x1 = -2.45f, y1 = 2.1f, z1 = 3.0f; vec4.clearVec(); vec4a.clearVec(); vec4.setVec(x1,y1,z1); vec4a -=vec4; - ensure_equals("2:operator-=: Fail to initialize" , vec4a,-vec4); + UNSCOPED_INFO("2:operator-=: Fail to initialize"); + CHECK(vec4a == -vec4); vec4a -=vec4; - ensure("3:operator-=:Fail to initialize " ,(is_approx_equal(-2*x1,vec4a.mV[VX]) && is_approx_equal(-2*y1,vec4a.mV[VY]) && is_approx_equal(-2*z1,vec4a.mV[VZ]))); + { + INFO("3:operator-=:Fail to initialize "); + CHECK(is_approx_equal(-2*x1,vec4a.mV[VX])); + CHECK(is_approx_equal(-2*y1,vec4a.mV[VY])); + CHECK(is_approx_equal(-2*z1,vec4a.mV[VZ])); + } } - template<> template<> - void v4math_object::test<12>() + TEST_CASE("v4math_h: dot product and scalar multiplication", "[v4math_h]") { F32 x1 = 1.f, y1 = 2.f, z1 = -1.1f; F32 x2 = 1.2f, y2 = 2.5f, z2 = 1.f; LLVector4 vec4(x1,y1,z1),vec4a(x2,y2,z2); F32 res = vec4 * vec4a; - ensure("1:operator* failed " ,is_approx_equal(res, x1*x2 + y1*y2 + z1*z2)); + UNSCOPED_INFO("1:operator* failed "); + CHECK(is_approx_equal(res, x1*x2 + y1*y2 + z1*z2)); vec4a.clearVec(); F32 mulVal = 4.2f; vec4a = vec4 * mulVal; - ensure("2:operator* failed " ,is_approx_equal(x1*mulVal,vec4a.mV[VX]) && is_approx_equal(y1*mulVal, vec4a.mV[VY])&& is_approx_equal(z1*mulVal, vec4a.mV[VZ])); + { + INFO("2:operator* failed "); + CHECK(is_approx_equal(x1*mulVal,vec4a.mV[VX])); + CHECK(is_approx_equal(y1*mulVal, vec4a.mV[VY])); + CHECK(is_approx_equal(z1*mulVal, vec4a.mV[VZ])); + } vec4a.clearVec(); vec4a = mulVal * vec4 ; - ensure("3:operator* failed " ,is_approx_equal(x1*mulVal, vec4a.mV[VX]) && is_approx_equal(y1*mulVal, vec4a.mV[VY])&& is_approx_equal(z1*mulVal, vec4a.mV[VZ])); + { + INFO("3:operator* failed "); + CHECK(is_approx_equal(x1*mulVal, vec4a.mV[VX])); + CHECK(is_approx_equal(y1*mulVal, vec4a.mV[VY])); + CHECK(is_approx_equal(z1*mulVal, vec4a.mV[VZ])); + } vec4 *= mulVal; - ensure("4:operator*= failed " ,is_approx_equal(x1*mulVal, vec4.mV[VX]) && is_approx_equal(y1*mulVal, vec4.mV[VY])&& is_approx_equal(z1*mulVal, vec4.mV[VZ])); + { + INFO("4:operator*= failed "); + CHECK(is_approx_equal(x1*mulVal, vec4.mV[VX])); + CHECK(is_approx_equal(y1*mulVal, vec4.mV[VY])); + CHECK(is_approx_equal(z1*mulVal, vec4.mV[VZ])); + } } - template<> template<> - void v4math_object::test<13>() + TEST_CASE("v4math_h: cross product operator% and operator%=", "[v4math_h]") { F32 x1 = 1.f, y1 = 2.f, z1 = -1.1f; F32 x2 = 1.2f, y2 = 2.5f, z2 = 1.f; LLVector4 vec4(x1,y1,z1),vec4a(x2,y2,z2),vec4b; vec4b = vec4 % vec4a; - ensure("1:operator% failed " ,is_approx_equal(y1*z2 - y2*z1, vec4b.mV[VX]) && is_approx_equal(z1*x2 -z2*x1, vec4b.mV[VY]) && is_approx_equal(x1*y2-x2*y1, vec4b.mV[VZ])); + { + INFO("1:operator% failed "); + CHECK(is_approx_equal(y1*z2 - y2*z1, vec4b.mV[VX])); + CHECK(is_approx_equal(z1*x2 -z2*x1, vec4b.mV[VY])); + CHECK(is_approx_equal(x1*y2-x2*y1, vec4b.mV[VZ])); + } vec4 %= vec4a; - ensure_equals("operator%= failed " ,vec4,vec4b); + UNSCOPED_INFO("operator%= failed "); + CHECK(vec4 == vec4b); } - template<> template<> - void v4math_object::test<14>() + TEST_CASE("v4math_h: operator/ and operator/= by a scalar", "[v4math_h]") { F32 x = 1.f, y = 2.f, z = -1.1f,div = 4.2f; F32 t = 1.f / div; LLVector4 vec4(x,y,z), vec4a; vec4a = vec4/div; - ensure("1:operator/ failed " ,is_approx_equal(x*t, vec4a.mV[VX]) && is_approx_equal(y*t, vec4a.mV[VY])&& is_approx_equal(z*t, vec4a.mV[VZ])); + { + INFO("1:operator/ failed "); + CHECK(is_approx_equal(x*t, vec4a.mV[VX])); + CHECK(is_approx_equal(y*t, vec4a.mV[VY])); + CHECK(is_approx_equal(z*t, vec4a.mV[VZ])); + } x = 1.23f, y = 4.f, z = -2.32f; vec4.clearVec(); vec4a.clearVec(); vec4.setVec(x,y,z); vec4a = vec4/div; - ensure("2:operator/ failed " ,is_approx_equal(x*t, vec4a.mV[VX]) && is_approx_equal(y*t, vec4a.mV[VY])&& is_approx_equal(z*t, vec4a.mV[VZ])); + { + INFO("2:operator/ failed "); + CHECK(is_approx_equal(x*t, vec4a.mV[VX])); + CHECK(is_approx_equal(y*t, vec4a.mV[VY])); + CHECK(is_approx_equal(z*t, vec4a.mV[VZ])); + } vec4 /= div; - ensure("3:operator/ failed " ,is_approx_equal(x*t, vec4.mV[VX]) && is_approx_equal(y*t, vec4.mV[VY])&& is_approx_equal(z*t, vec4.mV[VZ])); + { + INFO("3:operator/ failed "); + CHECK(is_approx_equal(x*t, vec4.mV[VX])); + CHECK(is_approx_equal(y*t, vec4.mV[VY])); + CHECK(is_approx_equal(z*t, vec4.mV[VZ])); + } } - template<> template<> - void v4math_object::test<15>() + TEST_CASE("v4math_h: operator== and operator!=", "[v4math_h]") { F32 x = 1.f, y = 2.f, z = -1.1f; LLVector4 vec4(x,y,z), vec4a; - ensure("operator!= failed " ,(vec4 != vec4a)); + UNSCOPED_INFO("operator!= failed "); + CHECK(vec4 != vec4a); vec4a = vec4; - ensure("operator== failed " ,(vec4 ==vec4a)); + UNSCOPED_INFO("operator== failed "); + CHECK(vec4 ==vec4a); } - template<> template<> - void v4math_object::test<16>() + TEST_CASE("v4math_h: unary minus negates", "[v4math_h]") { F32 x = 1.f, y = 2.f, z = -1.1f; LLVector4 vec4(x,y,z), vec4a; vec4a = - vec4; - ensure("operator- failed " , (vec4 == - vec4a)); + UNSCOPED_INFO("operator- failed "); + CHECK(vec4 == - vec4a); } - template<> template<> - void v4math_object::test<17>() + TEST_CASE("v4math_h: are_parallel within an epsilon", "[v4math_h]") { F32 x = 1.f, y = 2.f, z = -1.1f,epsilon = .23425f; LLVector4 vec4(x,y,z), vec4a(x,y,z); - ensure("1:are_parallel: Fail " ,(true == are_parallel(vec4a,vec4,epsilon))); + UNSCOPED_INFO("1:are_parallel: Fail "); + CHECK(are_parallel(vec4a,vec4,epsilon)); x = 21.f, y = 12.f, z = -123.1f; vec4a.clearVec(); vec4a.setVec(x,y,z); - ensure("2:are_parallel: Fail " ,(false == are_parallel(vec4a,vec4,epsilon))); + UNSCOPED_INFO("2:are_parallel: Fail "); + CHECK_FALSE(are_parallel(vec4a,vec4,epsilon)); } - template<> template<> - void v4math_object::test<18>() + TEST_CASE("v4math_h: angle_between matches acos of the normalized dot product", "[v4math_h]") { F32 x = 1.f, y = 2.f, z = -1.1f; F32 angle1, angle2; @@ -323,32 +475,34 @@ namespace vec4.normVec(); vec4a.normVec(); angle2 = acos(vec4 * vec4a); - ensure_approximately_equals("1:angle_between: Fail " ,angle1,angle2,8); + UNSCOPED_INFO("1:angle_between: Fail "); + CHECK_THAT(angle1, ll_test::WithinBits(angle2, 8)); F32 x1 = 21.f, y1 = 2.23f, z1 = -1.1f; LLVector4 vec4b(x,y,z), vec4c(x1,y1,z1); angle1 = angle_between(vec4b, vec4c); vec4b.normVec(); vec4c.normVec(); angle2 = acos(vec4b * vec4c); - ensure_approximately_equals("2:angle_between: Fail " ,angle1,angle2,8); + UNSCOPED_INFO("2:angle_between: Fail "); + CHECK_THAT(angle1, ll_test::WithinBits(angle2, 8)); } - template<> template<> - void v4math_object::test<19>() + TEST_CASE("v4math_h: dist_vec and dist_vec_squared", "[v4math_h]") { F32 x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 1.f, z2 = 1.f; F32 val1,val2; LLVector4 vec4(x1,y1,z1),vec4a(x2,y2,z2); val1 = dist_vec(vec4,vec4a); val2 = (F32) sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2) + (z1 - z2)* (z1 -z2)); - ensure_equals("dist_vec: Fail ",val2, val1); + UNSCOPED_INFO("dist_vec: Fail "); + CHECK(val2 == val1); val1 = dist_vec_squared(vec4,vec4a); val2 =((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2) + (z1 - z2)* (z1 -z2)); - ensure_equals("dist_vec_squared: Fail ",val2, val1); + UNSCOPED_INFO("dist_vec_squared: Fail "); + CHECK(val2 == val1); } - template<> template<> - void v4math_object::test<20>() + TEST_CASE("v4math_h: lerp interpolates every component", "[v4math_h]") { F32 x1 =-2.3f, y1 = 2.f,z1 = 1.2f, w1 = -.23f, x2 = 1.3f, y2 = 1.f, z2 = 1.f,w2 = .12f; F32 val = 2.3f,val1,val2,val3,val4; @@ -359,28 +513,34 @@ namespace val4 = w1 + (w2 - w1)* val; LLVector4 vec4b = lerp(vec4,vec4a,val); LLVector4 check(val1, val2, val3, val4); - ensure_equals("lerp failed", check, vec4b); + UNSCOPED_INFO("lerp failed"); + CHECK(check == vec4b); } - template<> template<> - void v4math_object::test<21>() + TEST_CASE("v4math_h: vec4to3 and vec3to4", "[v4math_h]") { F32 x = 1.f, y = 2.f, z = -1.1f; LLVector4 vec4(x,y,z); LLVector3 vec3 = vec4to3(vec4); - ensure("vec4to3 failed", ((x == vec3.mV[VX])&& (y == vec3.mV[VY]) && (z == vec3.mV[VZ]))); + { + INFO("vec4to3 failed"); + CHECK(x == vec3.mV[VX]); + CHECK(y == vec3.mV[VY]); + CHECK(z == vec3.mV[VZ]); + } LLVector4 vec4a = vec3to4(vec3); - ensure_equals("vec3to4 failed",vec4a,vec4); + UNSCOPED_INFO("vec3to4 failed"); + CHECK(vec4a == vec4); } - template<> template<> - void v4math_object::test<22>() + TEST_CASE("v4math_h: getValue round-trips through LLVector3", "[v4math_h]") { F32 x = 1.f, y = 2.f, z = -1.1f; LLVector4 vec4(x,y,z); LLSD llsd = vec4.getValue(); LLVector3 vec3(llsd); LLVector4 vec4a = vec3to4(vec3); - ensure_equals("getValue failed",vec4a,vec4); + UNSCOPED_INFO("getValue failed"); + CHECK(vec4a == vec4); } } diff --git a/indra/llmath/tests/xform_test.cpp b/indra/llmath/tests/xform_test.cpp index 39a72a1cc5..a9959d29e2 100644 --- a/indra/llmath/tests/xform_test.cpp +++ b/indra/llmath/tests/xform_test.cpp @@ -27,37 +27,30 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../xform.h" -namespace tut -{ namespace { - struct xform_test - { - }; - typedef test_group xform_test_t; - typedef xform_test_t::object xform_test_object_t; - tut::xform_test_t tut_xform_test("LLXForm"); -} - //test case for init(), getParent(), getRotation(), getPositionW(), getWorldRotation() fns. - template<> template<> - void xform_test_object_t::test<1>() + TEST_CASE("LLXForm: a default LLXform is an unparented identity transform", "[LLXForm]") { LLXform xform_obj; LLVector3 emptyVec(0.f,0.f,0.f); LLVector3 initialScaleVec(1.f,1.f,1.f); - ensure("LLXform empty constructor failed: ", !xform_obj.getParent() && !xform_obj.isChanged() && - xform_obj.getPosition() == emptyVec && - (xform_obj.getRotation()).isIdentity() && - xform_obj.getScale() == initialScaleVec && - xform_obj.getPositionW() == emptyVec && - (xform_obj.getWorldRotation()).isIdentity() && - !xform_obj.getScaleChildOffset()); + { + INFO("LLXform empty constructor failed: "); + REQUIRE_FALSE(xform_obj.getParent()); + CHECK_FALSE(xform_obj.isChanged()); + CHECK(xform_obj.getPosition() == emptyVec); + CHECK((xform_obj.getRotation()).isIdentity()); + CHECK(xform_obj.getScale() == initialScaleVec); + CHECK(xform_obj.getPositionW() == emptyVec); + CHECK((xform_obj.getWorldRotation()).isIdentity()); + CHECK_FALSE(xform_obj.getScaleChildOffset()); + } } // test cases for @@ -66,8 +59,7 @@ namespace // setRotation(const F32 x, const F32 y, const F32 z) // setPosition(const F32 x, const F32 y, const F32 z) // getLocalMat4(LLMatrix4 &mat) - template<> template<> - void xform_test_object_t::test<2>() + TEST_CASE("LLXForm: scale/position/rotation setters store what they are given", "[LLXForm]") { LLMatrix4 llmat4; LLXform xform_obj; @@ -80,10 +72,12 @@ namespace LLVector3 vec(x, y, z); xform_obj.setScale(x, y, z); xform_obj.setPosition(x, y, posz); - ensure("setScale failed: ", xform_obj.getScale() == vec); + UNSCOPED_INFO("setScale failed: "); + CHECK(xform_obj.getScale() == vec); vec.setVec(x, y, posz); - ensure("getPosition failed: ", xform_obj.getPosition() == vec); + UNSCOPED_INFO("getPosition failed: "); + CHECK(xform_obj.getPosition() == vec); x = x * 2.f; y = y + 2.3f; @@ -92,30 +86,37 @@ namespace xform_obj.setPositionX(x); xform_obj.setPositionY(y); xform_obj.setPositionZ(z); - ensure("setPositionX/Y/Z failed: ", xform_obj.getPosition() == vec); + UNSCOPED_INFO("setPositionX/Y/Z failed: "); + CHECK(xform_obj.getPosition() == vec); xform_obj.setScaleChildOffset(true); - ensure("setScaleChildOffset failed: ", xform_obj.getScaleChildOffset()); + UNSCOPED_INFO("setScaleChildOffset failed: "); + CHECK(xform_obj.getScaleChildOffset()); vec.setVec(x, y, z); xform_obj.addPosition(vec); vec += vec; - ensure("addPosition failed: ", xform_obj.getPosition() == vec); + UNSCOPED_INFO("addPosition failed: "); + CHECK(xform_obj.getPosition() == vec); xform_obj.setScale(vec); - ensure("setScale vector failed: ", xform_obj.getScale() == vec); + UNSCOPED_INFO("setScale vector failed: "); + CHECK(xform_obj.getScale() == vec); LLQuaternion quat(x, y, z, w); xform_obj.setRotation(quat); - ensure("setRotation quat failed: ", xform_obj.getRotation() == quat); + UNSCOPED_INFO("setRotation quat failed: "); + CHECK(xform_obj.getRotation() == quat); xform_obj.setRotation(x, y, z, w); - ensure("getRotation 2 failed: ", xform_obj.getRotation() == quat); + UNSCOPED_INFO("getRotation 2 failed: "); + CHECK(xform_obj.getRotation() == quat); xform_obj.setRotation(x, y, z); quat.setQuat(x,y,z); - ensure("setRotation xyz failed: ", xform_obj.getRotation() == quat); + UNSCOPED_INFO("setRotation xyz failed: "); + CHECK(xform_obj.getRotation() == quat); // LLXform::setRotation(const F32 x, const F32 y, const F32 z) // Does normalization @@ -125,90 +126,127 @@ namespace } // test cases for inline bool setParent(LLXform *parent) and getParent() fn. - template<> template<> - void xform_test_object_t::test<3>() + TEST_CASE("LLXForm: setParent builds a chain that getRoot and isRoot follow", "[LLXForm]") { LLXform xform_obj; LLXform par; LLXform grandpar; xform_obj.setParent(&par); par.setParent(&grandpar); - ensure("setParent/getParent failed: ", &par == xform_obj.getParent()); - ensure("getRoot failed: ", &grandpar == xform_obj.getRoot()); - ensure("isRoot failed: ", grandpar.isRoot() && !par.isRoot() && !xform_obj.isRoot()); - ensure("isRootEdit failed: ", grandpar.isRootEdit() && !par.isRootEdit() && !xform_obj.isRootEdit()); + UNSCOPED_INFO("setParent/getParent failed: "); + CHECK(&par == xform_obj.getParent()); + UNSCOPED_INFO("getRoot failed: "); + CHECK(&grandpar == xform_obj.getRoot()); + { + INFO("isRoot failed: "); + CHECK(grandpar.isRoot()); + CHECK_FALSE(par.isRoot()); + CHECK_FALSE(xform_obj.isRoot()); + } + { + INFO("isRootEdit failed: "); + CHECK(grandpar.isRootEdit()); + CHECK_FALSE(par.isRootEdit()); + CHECK_FALSE(xform_obj.isRootEdit()); + } } - template<> template<> - void xform_test_object_t::test<4>() + TEST_CASE("LLXForm: setters raise their changed flags and clearChanged clears them", "[LLXForm]") { LLXform xform_obj; xform_obj.setChanged(LLXform::TRANSLATED | LLXform::ROTATED | LLXform::SCALED); - ensure("setChanged/isChanged failed: ", xform_obj.isChanged()); + UNSCOPED_INFO("setChanged/isChanged failed: "); + CHECK(xform_obj.isChanged()); xform_obj.clearChanged(LLXform::TRANSLATED | LLXform::ROTATED | LLXform::SCALED); - ensure("clearChanged failed: ", !xform_obj.isChanged()); + UNSCOPED_INFO("clearChanged failed: "); + CHECK_FALSE(xform_obj.isChanged()); LLVector3 llvect3(12.4f, -5.6f, 0.34f); xform_obj.setScale(llvect3); - ensure("setScale did not set SCALED flag: ", xform_obj.isChanged(LLXform::SCALED)); + UNSCOPED_INFO("setScale did not set SCALED flag: "); + CHECK(xform_obj.isChanged(LLXform::SCALED)); xform_obj.setPosition(1.2f, 2.3f, 3.4f); - ensure("setScale did not set TRANSLATED flag: ", xform_obj.isChanged(LLXform::TRANSLATED)); - ensure("TRANSLATED reset SCALED flag: ", xform_obj.isChanged(LLXform::TRANSLATED | LLXform::SCALED)); + UNSCOPED_INFO("setScale did not set TRANSLATED flag: "); + CHECK(xform_obj.isChanged(LLXform::TRANSLATED)); + UNSCOPED_INFO("TRANSLATED reset SCALED flag: "); + CHECK(xform_obj.isChanged(LLXform::TRANSLATED | LLXform::SCALED)); xform_obj.clearChanged(LLXform::SCALED); - ensure("reset SCALED failed: ", !xform_obj.isChanged(LLXform::SCALED)); + UNSCOPED_INFO("reset SCALED failed: "); + CHECK_FALSE(xform_obj.isChanged(LLXform::SCALED)); xform_obj.setRotation(1, 2, 3, 4); - ensure("ROTATION flag not set ", xform_obj.isChanged(LLXform::TRANSLATED | LLXform::ROTATED)); + UNSCOPED_INFO("ROTATION flag not set "); + CHECK(xform_obj.isChanged(LLXform::TRANSLATED | LLXform::ROTATED)); xform_obj.setScale(llvect3); - ensure("ROTATION flag not set ", xform_obj.isChanged(LLXform::MOVED)); + UNSCOPED_INFO("ROTATION flag not set "); + CHECK(xform_obj.isChanged(LLXform::MOVED)); } //to test init() and getWorldMatrix() fns. - template<> template<> - void xform_test_object_t::test<5>() + TEST_CASE("LLXForm: LLXformMatrix init leaves an identity world matrix", "[LLXForm]") { LLXformMatrix formMatrix_obj; formMatrix_obj.init(); LLMatrix4 mat4_obj; - ensure("1. The value is not NULL", 1.f == formMatrix_obj.getWorldMatrix().mMatrix[0][0]); - ensure("2. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[0][1]); - ensure("3. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[0][2]); - ensure("4. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[0][3]); - ensure("5. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[1][0]); - ensure("6. The value is not NULL", 1.f == formMatrix_obj.getWorldMatrix().mMatrix[1][1]); - ensure("7. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[1][2]); - ensure("8. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[1][3]); - ensure("9. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[2][0]); - ensure("10. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[2][1]); - ensure("11. The value is not NULL", 1.f == formMatrix_obj.getWorldMatrix().mMatrix[2][2]); - ensure("12. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[2][3]); - ensure("13. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[3][0]); - ensure("14. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[3][1]); - ensure("15. The value is not NULL", 0.f == formMatrix_obj.getWorldMatrix().mMatrix[3][2]); - ensure("16. The value is not NULL", 1.f == formMatrix_obj.getWorldMatrix().mMatrix[3][3]); + UNSCOPED_INFO("1. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[0][0] == 1.f); + UNSCOPED_INFO("2. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[0][1] == 0.f); + UNSCOPED_INFO("3. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[0][2] == 0.f); + UNSCOPED_INFO("4. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[0][3] == 0.f); + UNSCOPED_INFO("5. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[1][0] == 0.f); + UNSCOPED_INFO("6. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[1][1] == 1.f); + UNSCOPED_INFO("7. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[1][2] == 0.f); + UNSCOPED_INFO("8. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[1][3] == 0.f); + UNSCOPED_INFO("9. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[2][0] == 0.f); + UNSCOPED_INFO("10. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[2][1] == 0.f); + UNSCOPED_INFO("11. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[2][2] == 1.f); + UNSCOPED_INFO("12. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[2][3] == 0.f); + UNSCOPED_INFO("13. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[3][0] == 0.f); + UNSCOPED_INFO("14. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[3][1] == 0.f); + UNSCOPED_INFO("15. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[3][2] == 0.f); + UNSCOPED_INFO("16. The value is not NULL"); + CHECK(formMatrix_obj.getWorldMatrix().mMatrix[3][3] == 1.f); } //to test mMin.clearVec() and mMax.clearVec() fns - template<> template<> - void xform_test_object_t::test<6>() + TEST_CASE("LLXForm: getMinMax after init puts the minimum corner at the origin", "[LLXForm]") { LLXformMatrix formMatrix_obj; formMatrix_obj.init(); LLVector3 llmin_vec3; LLVector3 llmax_vec3; formMatrix_obj.getMinMax(llmin_vec3, llmax_vec3); - ensure("1. The value is not NULL", 0.f == llmin_vec3.mV[0]); - ensure("2. The value is not NULL", 0.f == llmin_vec3.mV[1]); - ensure("3. The value is not NULL", 0.f == llmin_vec3.mV[2]); - ensure("4. The value is not NULL", 0.f == llmin_vec3.mV[0]); - ensure("5. The value is not NULL", 0.f == llmin_vec3.mV[1]); - ensure("6. The value is not NULL", 0.f == llmin_vec3.mV[2]); + UNSCOPED_INFO("1. The value is not NULL"); + CHECK(llmin_vec3.mV[0] == 0.f); + UNSCOPED_INFO("2. The value is not NULL"); + CHECK(llmin_vec3.mV[1] == 0.f); + UNSCOPED_INFO("3. The value is not NULL"); + CHECK(llmin_vec3.mV[2] == 0.f); + UNSCOPED_INFO("4. The value is not NULL"); + CHECK(llmin_vec3.mV[0] == 0.f); + UNSCOPED_INFO("5. The value is not NULL"); + CHECK(llmin_vec3.mV[1] == 0.f); + UNSCOPED_INFO("6. The value is not NULL"); + CHECK(llmin_vec3.mV[2] == 0.f); } //test case of update() fn. - template<> template<> - void xform_test_object_t::test<7>() + TEST_CASE("LLXForm: update composes world position and rotation with the parent", "[LLXForm]") { LLXformMatrix formMatrix_obj; @@ -238,11 +276,15 @@ namespace LLQuaternion worldRot = quat * quatparent; - ensure("getWorldPosition failed: ", formMatrix_obj.getWorldPosition() == worldPos); - ensure("getWorldRotation failed: ", formMatrix_obj.getWorldRotation() == worldRot); + UNSCOPED_INFO("getWorldPosition failed: "); + CHECK(formMatrix_obj.getWorldPosition() == worldPos); + UNSCOPED_INFO("getWorldRotation failed: "); + CHECK(formMatrix_obj.getWorldRotation() == worldRot); - ensure("getWorldPosition for parent failed: ", parent.getWorldPosition() == llvecpospar); - ensure("getWorldRotation for parent failed: ", parent.getWorldRotation() == quatparent); + UNSCOPED_INFO("getWorldPosition for parent failed: "); + CHECK(parent.getWorldPosition() == llvecpospar); + UNSCOPED_INFO("getWorldRotation for parent failed: "); + CHECK(parent.getWorldRotation() == quatparent); } } From bae1eeb31a9025b6da4f7772c996feb7ed15e2b1 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 10:29:47 -0400 Subject: [PATCH 15/55] llmath's tests read as Catch2 a person would write What the converter cannot judge, done file by file over its output: - TUT's messages that only said the assertion again ("1:LLVector3:Fail to initialize", "operator!= failed") are gone, and the INFO blocks around split chains with them: a failure prints the expression and its values now. Messages that tell what the expression does not are kept, some reworded; 1,194 messages are 202 messages and captures. - Messages built from std::to_string to say which element failed are CAPTURE(width, n, i) and the like. - The asserting helpers carry what they do: require_same_bits, require_close, require_quat_near, require_rows_near, and so on, each with a scoped INFO. - A REQUIRE that guarded nothing is a CHECK: a vector's isNull(), an LLRect's isValid(), a NULL that was only a callee's default argument. - Names the converter cut from comments read whole. Every case keeps its name and its assertions: tut_parity.py finds the same 355 cases, results and assertion counts as for the converter's output, and the combined run passes ten random orders and twenty repeats. Found and left, since a conversion keeps behaviour: subscript checks that compare an element with itself (v3math, v3dmath, v4math, v4color); vectors round-tripped through a scalar LLSD, so through zero (v3math, v3dmath); rotVec cases that never compare against an expected rotation; llbbox's rotated-extent check testing the unrotated box, and addBBoxAgent never tested; xform's isChanged checks that any bit satisfies, and llmax_vec3 never checked; llquaternion cases that only test LLMatrix4::initRows and LLMatrix3::setRows, and a getEulerAngles case that never checks the angles. Co-Authored-By: Claude Opus 5.5 --- indra/llmath/tests/alignment_test.cpp | 38 +- indra/llmath/tests/alplaneset_test.cpp | 26 +- indra/llmath/tests/alsimdkernels_test.cpp | 96 ++-- indra/llmath/tests/llbbox_test.cpp | 73 +-- indra/llmath/tests/llbboxlocal_test.cpp | 37 +- indra/llmath/tests/llmatrix3a_test.cpp | 78 +--- indra/llmath/tests/llmatrix4a_test.cpp | 207 +++------ indra/llmath/tests/llmodularmath_test.cpp | 3 - indra/llmath/tests/llquaternion2_test.cpp | 63 ++- indra/llmath/tests/llquaternion_test.cpp | 522 ++++++++-------------- indra/llmath/tests/llrect_test.cpp | 142 +----- indra/llmath/tests/llsdutil_math_test.cpp | 14 +- indra/llmath/tests/llvector4a_test.cpp | 284 ++++-------- indra/llmath/tests/m3math_test.cpp | 147 +++--- indra/llmath/tests/mathmisc_test.cpp | 49 +- indra/llmath/tests/v2math_test.cpp | 253 +++-------- indra/llmath/tests/v3color_test.cpp | 240 +++------- indra/llmath/tests/v3dmath_test.cpp | 354 +++++---------- indra/llmath/tests/v3math_test.cpp | 379 +++++----------- indra/llmath/tests/v4color_test.cpp | 316 +++++-------- indra/llmath/tests/v4coloru_test.cpp | 239 +++------- indra/llmath/tests/v4math_test.cpp | 322 +++++-------- indra/llmath/tests/xform_test.cpp | 84 +--- 23 files changed, 1207 insertions(+), 2759 deletions(-) diff --git a/indra/llmath/tests/alignment_test.cpp b/indra/llmath/tests/alignment_test.cpp index 13d745a209..c3c1f9ca7d 100644 --- a/indra/llmath/tests/alignment_test.cpp +++ b/indra/llmath/tests/alignment_test.cpp @@ -66,17 +66,17 @@ TEST_CASE("LLAlignment: the aligned allocators return aligned memory", "[LLAlign for (int i=0; i vecs(33); for (const LLVector4a& v : vecs) { - UNSCOPED_INFO("LLVector4a in a vector unaligned"); REQUIRE(is_aligned(&v,16)); } std::vector mats(9); for (const LLMatrix4a& m : mats) { - UNSCOPED_INFO("LLMatrix4a in a vector unaligned"); REQUIRE(is_aligned(&m,16)); } } @@ -177,19 +162,15 @@ TEST_CASE("LLAlignment: LLVector4a and LLMatrix4a align on the heap and in conta // vector of them is one allocation, every vector in it placed. TEST_CASE("LLAlignment: LLVolumeFace::VertexData aligns in a vector and copies whole", "[LLAlignment]") { - UNSCOPED_INFO("VertexData size"); CHECK(sizeof(LLVolumeFace::VertexData) == size_t(48)); - UNSCOPED_INFO("VertexData alignment"); CHECK(alignof(LLVolumeFace::VertexData) == size_t(16)); - UNSCOPED_INFO("VertexData trivially copyable"); CHECK(std::is_trivially_copyable::value); std::vector verts(17); for (size_t i = 0; i < verts.size(); ++i) { - UNSCOPED_INFO("VertexData position in a vector unaligned"); + CAPTURE(i); REQUIRE(is_aligned(&verts[i].getPosition(),16)); - UNSCOPED_INFO("VertexData normal in a vector unaligned"); REQUIRE(is_aligned(&verts[i].getNormal(),16)); verts[i].setPosition(LLVector4a(F32(i), 1.f, 2.f)); verts[i].setNormal(LLVector4a(0.f, 0.f, 1.f)); @@ -199,18 +180,13 @@ TEST_CASE("LLAlignment: LLVolumeFace::VertexData aligns in a vector and copies w std::vector copy = verts; for (size_t i = 0; i < copy.size(); ++i) { - UNSCOPED_INFO("copied position"); + CAPTURE(i); REQUIRE(copy[i].getPosition().equals3(LLVector4a(F32(i), 1.f, 2.f))); - UNSCOPED_INFO("copied normal"); REQUIRE(copy[i].getNormal().equals3(LLVector4a(0.f, 0.f, 1.f))); - UNSCOPED_INFO("copied texcoord"); REQUIRE(copy[i].mTexCoord.mV[0] == F32(i) * 0.5f); - UNSCOPED_INFO("copy equals source"); REQUIRE(copy[i] == verts[i]); } - UNSCOPED_INFO("ordering"); CHECK(copy[0] < copy[1]); - UNSCOPED_INFO("ordering"); CHECK_FALSE(copy[1] < copy[0]); } diff --git a/indra/llmath/tests/alplaneset_test.cpp b/indra/llmath/tests/alplaneset_test.cpp index 5b38446f45..ce418629b3 100644 --- a/indra/llmath/tests/alplaneset_test.cpp +++ b/indra/llmath/tests/alplaneset_test.cpp @@ -151,24 +151,20 @@ namespace int outside = 0, crossing = 0, inside = 0; for (const Box& b : some_boxes(2, FAR_X)) { + CAPTURE(b.center); const S32 expected = by_loop(b); const S32 got = mCamera.AABBInFrustum(b.center, b.radius); - UNSCOPED_INFO("AABBInFrustum at " + std::to_string(b.center[0]) + " " + std::to_string(b.center[1]) + " " + std::to_string(b.center[2])); REQUIRE(got == expected); outside += expected == 0; crossing += expected == 1; inside += expected == 2; const S32 expected_no_far = by_loop(b, true); - UNSCOPED_INFO("AABBInFrustumNoFarClip at " + std::to_string(b.center[0])); REQUIRE(mCamera.AABBInFrustumNoFarClip(b.center, b.radius) == expected_no_far); } - { - INFO("the boxes fell on every side: " + std::to_string(outside) + " " + std::to_string(crossing) + " " + std::to_string(inside)); - CHECK(outside > 100); - CHECK(crossing > 100); - CHECK(inside > 100); - } + CHECK(outside > 100); + CHECK(crossing > 100); + CHECK(inside > 100); // the obvious three UNSCOPED_INFO("a box in the middle is inside"); @@ -191,25 +187,22 @@ namespace const Box above = {LLVector4a(50.f, 0.f, 20.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)}; UNSCOPED_INFO("above the clip plane is outside"); CHECK(mCamera.AABBInFrustum(above.center, above.radius) == 0); - UNSCOPED_INFO("and the loop agrees"); CHECK(by_loop(above) == 0); for (const Box& b : some_boxes(3, FAR_X)) { - UNSCOPED_INFO("with the clip plane"); + CAPTURE(b.center); REQUIRE(mCamera.AABBInFrustum(b.center, b.radius) == by_loop(b)); } mCamera.disableUserClipPlane(); UNSCOPED_INFO("above the disabled clip plane is inside"); CHECK(mCamera.AABBInFrustum(above.center, above.radius) == 2); - UNSCOPED_INFO("and the loop agrees"); CHECK(by_loop(above) == 2); mCamera.ignoreAgentFrustumPlane(LLCamera::AGENT_PLANE_FAR); const Box beyond = {LLVector4a(FAR_X + 50.f, 0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)}; UNSCOPED_INFO("beyond an ignored far plane is inside"); CHECK(mCamera.AABBInFrustum(beyond.center, beyond.radius) == 2); - UNSCOPED_INFO("and the loop agrees"); CHECK(by_loop(beyond) == 2); } @@ -222,7 +215,7 @@ namespace { Box in_agent = b; in_agent.center.set(b.center[0] + shift.mV[0], b.center[1] + shift.mV[1], b.center[2] + shift.mV[2], 0.f); - UNSCOPED_INFO("AABBInRegionFrustumNoFarClip at " + std::to_string(b.center[0])); + CAPTURE(b.center); REQUIRE(mCamera.AABBInRegionFrustumNoFarClip(b.center, b.radius) == by_loop(in_agent, true)); } UNSCOPED_INFO("a region box past the region far clip is outside"); @@ -241,17 +234,14 @@ namespace // the near plane moved back to x = -50, still facing back LLPlane moved(LLVector3(-50.f, 0.f, 0.f), LLVector3(-1.f, 0.f, 0.f)); mCamera.setAgentPlane(LLCamera::AGENT_PLANE_NEAR, moved); - UNSCOPED_INFO("the plane reads back"); CHECK(mCamera.getAgentPlane(LLCamera::AGENT_PLANE_NEAR).equal(moved)); UNSCOPED_INFO("behind the old near plane is now inside"); CHECK(mCamera.AABBInFrustum(behind.center, behind.radius) == 2); - UNSCOPED_INFO("and the loop agrees"); CHECK(by_loop(behind) == 2); for (const Box& b : some_boxes(5, FAR_X)) { - UNSCOPED_INFO("with the near plane moved"); + CAPTURE(b.center); REQUIRE(mCamera.AABBInFrustum(b.center, b.radius) == by_loop(b)); - UNSCOPED_INFO("with the near plane moved, far ignored"); REQUIRE(mCamera.AABBInFrustumNoFarClip(b.center, b.radius) == by_loop(b, true)); } @@ -261,11 +251,9 @@ namespace const Box straddling = {LLVector4a(50.f, 0.f, 0.f, 0.f), LLVector4a(2.f, 1.f, 1.f, 0.f)}; UNSCOPED_INFO("a box across the flipped plane is partly in"); CHECK(mCamera.AABBInFrustum(straddling.center, straddling.radius) == 1); - UNSCOPED_INFO("and the loop agrees"); CHECK(by_loop(straddling) == 1); UNSCOPED_INFO("beyond the flipped plane is outside"); CHECK(mCamera.AABBInFrustum(LLVector4a(70.f, 0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)) == 0); - UNSCOPED_INFO("and the loop agrees"); CHECK(by_loop({LLVector4a(70.f, 0.f, 0.f, 0.f), LLVector4a(1.f, 1.f, 1.f, 0.f)}) == 0); } } diff --git a/indra/llmath/tests/alsimdkernels_test.cpp b/indra/llmath/tests/alsimdkernels_test.cpp index 3eecc700f2..d20a597d4b 100644 --- a/indra/llmath/tests/alsimdkernels_test.cpp +++ b/indra/llmath/tests/alsimdkernels_test.cpp @@ -78,20 +78,20 @@ namespace return true; } - void ensure_same_bits(const std::string& what, const LLVector4a& got, const LLVector4a& expected) + void require_same_bits(const std::string& what, const LLVector4a& got, const LLVector4a& expected) { - UNSCOPED_INFO(what + ": " + precise(got[0]) + " " + precise(got[1]) + " " + precise(got[2]) + " " + precise(got[3]) + - " vs " + precise(expected[0]) + " " + precise(expected[1]) + " " + precise(expected[2]) + " " + precise(expected[3])); + INFO(what << ": " << precise(got[0]) << " " << precise(got[1]) << " " << precise(got[2]) << " " << precise(got[3]) + << " vs " << precise(expected[0]) << " " << precise(expected[1]) << " " << precise(expected[2]) << " " << precise(expected[3])); REQUIRE(same_bits(got, expected)); } // The error relative to the size of the terms that made the value, // which is where the rounding happened, not to the value itself. - void ensure_close(const std::string& what, F32 got, double expected, double relative, double magnitude = 0.0) + void require_close(const std::string& what, F32 got, double expected, double relative, double magnitude = 0.0) { const double scale = llmax(1.0, llmax(std::fabs(expected), magnitude)); const double err = std::fabs((double)got - expected) / scale; - UNSCOPED_INFO(what + ": " + precise(got) + " is off " + precise(err) + " from " + precise(expected)); + INFO(what << ": " << precise(got) << " is off " << precise(err) << " from " << precise(expected)); REQUIRE(err <= relative); } @@ -197,14 +197,16 @@ namespace transform_impl(m, src.data(), dst.data(), n, 0.f); for (size_t i = 0; i <= n; ++i) { - ensure_same_bits(std::string("transform_points ") + width + " n " + std::to_string(n) + " at " + std::to_string(i), dst[i], expected[i]); + CAPTURE(width, n, i); + require_same_bits("transform_points", dst[i], expected[i]); } std::vector dst_w(n + 1, SENTINEL); transform_impl(m, src.data(), dst_w.data(), n, 7.5f); for (size_t i = 0; i <= n; ++i) { - ensure_same_bits(std::string("transform_points with w ") + width + " n " + std::to_string(n) + " at " + std::to_string(i), dst_w[i], expected_w[i]); + CAPTURE(width, n, i); + require_same_bits("transform_points with w", dst_w[i], expected_w[i]); } }); } @@ -220,7 +222,8 @@ namespace m.affineTransform(src[i], expected[i]); for (int lane = 0; lane < 4; ++lane) { - ensure_close("public transform_points at " + std::to_string(i) + " lane " + std::to_string(lane), dst[i][lane], expected[i][lane], 4e-7, 1000.0); + CAPTURE(i, lane); + require_close("public transform_points", dst[i][lane], expected[i][lane], 4e-7, 1000.0); } } } @@ -247,14 +250,16 @@ namespace transform_impl(m, src.data(), dst.data(), n, 0.f); for (size_t i = 0; i <= n; ++i) { - ensure_same_bits(std::string("transform_directions ") + width + " n " + std::to_string(n) + " at " + std::to_string(i), dst[i], expected[i]); + CAPTURE(width, n, i); + require_same_bits("transform_directions", dst[i], expected[i]); } std::vector dst_w(n + 1, SENTINEL); transform_impl(m, src.data(), dst_w.data(), n, 0.f); for (size_t i = 0; i <= n; ++i) { - ensure_same_bits(std::string("transform_directions_keep_w ") + width + " n " + std::to_string(n) + " at " + std::to_string(i), dst_w[i], expected_w[i]); + CAPTURE(width, n, i); + require_same_bits("transform_directions_keep_w", dst_w[i], expected_w[i]); } }); } @@ -286,11 +291,12 @@ namespace const double t = (double)src[i][2 * pair + 1] - 0.5; const double es = (s * cos_ang + t * sin_ang) * 2.0 + 0.75; const double et = (s * -sin_ang + t * cos_ang) * 0.25 - 1.5; - ensure_close("texcoord s n " + std::to_string(n) + " at " + std::to_string(i), narrow[i][2 * pair], es, 1e-6, 400.0); - ensure_close("texcoord t n " + std::to_string(n) + " at " + std::to_string(i), narrow[i][2 * pair + 1], et, 1e-6, 400.0); + CAPTURE(n, i, pair); + require_close("texcoord s", narrow[i][2 * pair], es, 1e-6, 400.0); + require_close("texcoord t", narrow[i][2 * pair + 1], et, 1e-6, 400.0); } } - ensure_same_bits("texcoords write within n", narrow[n], SENTINEL); + require_same_bits("texcoords write within n", narrow[n], SENTINEL); for_each_width([&](const char* width) { @@ -298,7 +304,8 @@ namespace transform_texcoords_impl(reinterpret_cast(src.data()), reinterpret_cast(dst.data()), n, trans, rot0, rot1, scale, offset); for (size_t i = 0; i <= n; ++i) { - ensure_same_bits(std::string("texcoords ") + width + " n " + std::to_string(n) + " at " + std::to_string(i), dst[i], narrow[i]); + CAPTURE(width, n, i); + require_same_bits("texcoords", dst[i], narrow[i]); } }); } @@ -327,7 +334,7 @@ namespace offset_indices_u16_impl(src.data() + 1, dst.data() + 1, n, offset); for (size_t i = 0; i < n + 3; ++i) { - UNSCOPED_INFO(std::string("offset_indices_u16 ") + width + " n " + std::to_string(n) + " offset " + std::to_string(offset) + " at " + std::to_string(i)); + CAPTURE(width, n, offset, i); REQUIRE(dst[i] == expected[i]); } }); @@ -371,12 +378,13 @@ namespace { const double term = (double)values[3 * i + lane] * scale[lane]; const double expected = term + bias[lane]; - ensure_close("dequantize_u16x3 n " + std::to_string(n) + " at " + std::to_string(i) + " lane " + std::to_string(lane), narrow[i][lane], expected, 5e-7, std::fabs(term) + std::fabs(bias[lane])); + CAPTURE(n, i, lane); + require_close("dequantize_u16x3", narrow[i][lane], expected, 5e-7, std::fabs(term) + std::fabs(bias[lane])); } - UNSCOPED_INFO("dequantize_u16x3 w is bias"); + CAPTURE(n, i); REQUIRE(narrow[i][3] == bias[3]); } - ensure_same_bits("dequantize_u16x3 writes within n", narrow[n], SENTINEL); + require_same_bits("dequantize_u16x3 writes within n", narrow[n], SENTINEL); for_each_width([&](const char* width) { @@ -384,7 +392,8 @@ namespace dequantize_u16x3_impl(bytes.data(), n, scale, bias, dst.data()); for (size_t i = 0; i <= n; ++i) { - ensure_same_bits(std::string("dequantize_u16x3 ") + width + " n " + std::to_string(n) + " at " + std::to_string(i), dst[i], narrow[i]); + CAPTURE(width, n, i); + require_same_bits("dequantize_u16x3", dst[i], narrow[i]); } }); } @@ -420,7 +429,8 @@ namespace { const double term = (double)values[2 * i + lane] * scale[base + lane]; const double expected = term + bias[base + lane]; - ensure_close("dequantize_u16x2 n " + std::to_string(n) + " at " + std::to_string(i) + " lane " + std::to_string(lane), v[base + lane], expected, 5e-7, std::fabs(term) + std::fabs(bias[base + lane])); + CAPTURE(n, i, lane); + require_close("dequantize_u16x2", v[base + lane], expected, 5e-7, std::fabs(term) + std::fabs(bias[base + lane])); } } if (n & 1) @@ -430,7 +440,7 @@ namespace UNSCOPED_INFO("odd tail upper v is bias"); REQUIRE(dst[vectors - 1][3] == bias[3]); } - ensure_same_bits("dequantize_u16x2 writes within its vectors", dst[vectors], SENTINEL); + require_same_bits("dequantize_u16x2 writes within its vectors", dst[vectors], SENTINEL); } } @@ -482,7 +492,8 @@ namespace { expected += frac[k] / sum * palette[idx[k]].getF32ptr()[e]; } - ensure_close("skin_blend element " + std::to_string(e) + " of " + precise(w[0]), got[e], expected, 2e-6, 100.0); + INFO("element " << e << " of " << precise(w[0])); + require_close("skin_blend", got[e], expected, 2e-6, 100.0); } for_each_width([&](const char* width) @@ -491,7 +502,8 @@ namespace skin_blend_impl(w, palette, JOINTS, wide); for (int r = 0; r < 4; ++r) { - ensure_same_bits(std::string("skin_blend ") + width + " row " + std::to_string(r), wide.mMatrix[r], narrow.mMatrix[r]); + CAPTURE(width, r); + require_same_bits("skin_blend", wide.mMatrix[r], narrow.mMatrix[r]); } }); } @@ -521,7 +533,8 @@ namespace skin_points_impl(vertex_weights.data(), palette, JOINTS, bind, src.data(), dst.data(), n); for (size_t i = 0; i <= n; ++i) { - ensure_same_bits(std::string("skin_points ") + width + " n " + std::to_string(n) + " at " + std::to_string(i), dst[i], expected[i]); + CAPTURE(width, n, i); + require_same_bits("skin_points", dst[i], expected[i]); } }); } @@ -550,13 +563,15 @@ namespace extents_impl(src.data(), n, lo, hi); if (n == 0) { - ensure_same_bits(std::string("extents ") + width + " of nothing leaves min", lo, SENTINEL); - ensure_same_bits(std::string("extents ") + width + " of nothing leaves max", hi, SENTINEL); + CAPTURE(width); + require_same_bits("extents of nothing leaves min", lo, SENTINEL); + require_same_bits("extents of nothing leaves max", hi, SENTINEL); } else { - ensure_same_bits(std::string("extents ") + width + " min n " + std::to_string(n), lo, expected_min); - ensure_same_bits(std::string("extents ") + width + " max n " + std::to_string(n), hi, expected_max); + CAPTURE(width, n); + require_same_bits("extents min", lo, expected_min); + require_same_bits("extents max", hi, expected_max); } }); } @@ -679,20 +694,22 @@ namespace apply.tex_coords = got.tex.data(); morph_apply_impl(apply); - const std::string tag = " variant " + std::to_string(variant); + CAPTURE(variant); for (size_t v = 0; v < MESH; ++v) { + CAPTURE(v); for (int lane = 0; lane < 4; ++lane) { - ensure_close("coords" + tag + " at " + std::to_string(v), got.coords[v][lane], expected.coords[v][lane], 2e-6, 4.0); - ensure_close("scaled normals" + tag, got.scaled_normals[v][lane], expected.scaled_normals[v][lane], 2e-6, 4.0); - ensure_close("normals" + tag, got.normals[v][lane], expected.normals[v][lane], 6e-4, 1.0); - ensure_close("scaled binormals" + tag, got.scaled_binormals[v][lane], expected.scaled_binormals[v][lane], 2e-6, 4.0); - ensure_close("binormals" + tag, got.binormals[v][lane], expected.binormals[v][lane], 6e-4, 1.0); - ensure_close("clothing" + tag, got.clothing[v][lane], expected.clothing[v][lane], 2e-6, 4.0); + CAPTURE(lane); + require_close("coords", got.coords[v][lane], expected.coords[v][lane], 2e-6, 4.0); + require_close("scaled normals", got.scaled_normals[v][lane], expected.scaled_normals[v][lane], 2e-6, 4.0); + require_close("normals", got.normals[v][lane], expected.normals[v][lane], 6e-4, 1.0); + require_close("scaled binormals", got.scaled_binormals[v][lane], expected.scaled_binormals[v][lane], 2e-6, 4.0); + require_close("binormals", got.binormals[v][lane], expected.binormals[v][lane], 6e-4, 1.0); + require_close("clothing", got.clothing[v][lane], expected.clothing[v][lane], 2e-6, 4.0); } - ensure_close("tex u" + tag, got.tex[v].mV[0], expected.tex[v].mV[0], 2e-6, 4.0); - ensure_close("tex v" + tag, got.tex[v].mV[1], expected.tex[v].mV[1], 2e-6, 4.0); + require_close("tex u", got.tex[v].mV[0], expected.tex[v].mV[0], 2e-6, 4.0); + require_close("tex v", got.tex[v].mV[1], expected.tex[v].mV[1], 2e-6, 4.0); } } } @@ -718,7 +735,7 @@ namespace transform_impl(m, src.data(), dst.data(), n, w); for (size_t i = 0; i < n; ++i) { - UNSCOPED_INFO(std::string("index in w ") + width + " index " + std::to_string(index) + " n " + std::to_string(n) + " at " + std::to_string(i)); + CAPTURE(width, index, n, i); REQUIRE(std::bit_cast(dst[i][3]) == index); } }); @@ -727,7 +744,8 @@ namespace alsimd::transform_points(m, src.data(), dst.data(), n, w); for (size_t i = 0; i < n; ++i) { - UNSCOPED_INFO("index in w through the library index " + std::to_string(index) + " n " + std::to_string(n) + " at " + std::to_string(i)); + INFO("through the library"); + CAPTURE(index, n, i); REQUIRE(std::bit_cast(dst[i][3]) == index); } } diff --git a/indra/llmath/tests/llbbox_test.cpp b/indra/llmath/tests/llbbox_test.cpp index 6507db3afc..726bf6d8ff 100644 --- a/indra/llmath/tests/llbbox_test.cpp +++ b/indra/llmath/tests/llbbox_test.cpp @@ -50,13 +50,9 @@ namespace LLBBox bbox1; - UNSCOPED_INFO("Default bbox min"); CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, 0.0f)); - UNSCOPED_INFO("Default bbox max"); CHECK(bbox1.getMaxLocal() == LLVector3(0.0f, 0.0f, 0.0f)); - UNSCOPED_INFO("Default bbox pos agent"); CHECK(bbox1.getPositionAgent() == LLVector3(0.0f, 0.0f, 0.0f)); - UNSCOPED_INFO("Default bbox rotation"); CHECK(bbox1.getRotation() == LLQuaternion(0.0f, 0.0f, 0.0f, 1.0f)); } @@ -69,13 +65,9 @@ namespace LLBBox bbox2(LLVector3(1.0f, 2.0f, 3.0f), LLQuaternion(), LLVector3(2.0f, 3.0f, 4.0f), LLVector3(4.0f, 5.0f, 6.0f)); - UNSCOPED_INFO("Custom bbox min"); CHECK(bbox2.getMinLocal() == LLVector3(2.0f, 3.0f, 4.0f)); - UNSCOPED_INFO("Custom bbox max"); CHECK(bbox2.getMaxLocal() == LLVector3(4.0f, 5.0f, 6.0f)); - UNSCOPED_INFO("Custom bbox pos agent"); CHECK(bbox2.getPositionAgent() == LLVector3(1.0f, 2.0f, 3.0f)); - UNSCOPED_INFO("Custom bbox rotation"); CHECK(bbox2.getRotation() == LLQuaternion(0.0f, 0.0f, 0.0f, 1.0f)); } @@ -86,7 +78,6 @@ namespace // LLBBox bbox2; bbox2.setMinLocal(LLVector3(3.0f, 3.0f, 3.0f)); - UNSCOPED_INFO("Custom bbox min (2)"); CHECK(bbox2.getMinLocal() == LLVector3(3.0f, 3.0f, 3.0f)); } @@ -97,7 +88,6 @@ namespace // LLBBox bbox2; bbox2.setMaxLocal(LLVector3(5.0f, 5.0f, 5.0f)); - UNSCOPED_INFO("Custom bbox max (2)"); CHECK(bbox2.getMaxLocal() == LLVector3(5.0f, 5.0f, 5.0f)); } @@ -107,19 +97,17 @@ namespace // test the getCenterLocal() method // - UNSCOPED_INFO("Default bbox local center"); CHECK(LLBBox().getCenterLocal() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBox bbox1(LLVector3(1.0f, 2.0f, 3.0f), LLQuaternion(), LLVector3(2.0f, 4.0f, 6.0f), LLVector3(4.0f, 6.0f, 8.0f)); - UNSCOPED_INFO("Custom bbox center local"); CHECK(bbox1.getCenterLocal() == LLVector3(3.0f, 5.0f, 7.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)), LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 4.0f, 4.0f)); - UNSCOPED_INFO("Custom bbox center local with rot"); + UNSCOPED_INFO("rotated box"); CHECK(bbox2.getCenterLocal() == LLVector3(3.0f, 3.0f, 3.0f)); } @@ -129,19 +117,17 @@ namespace // test the getCenterAgent() // - UNSCOPED_INFO("Default bbox agent center"); CHECK(LLBBox().getCenterAgent() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBox bbox1(LLVector3(1.0f, 2.0f, 3.0f), LLQuaternion(), LLVector3(2.0f, 4.0f, 6.0f), LLVector3(4.0f, 6.0f, 8.0f)); - UNSCOPED_INFO("Custom bbox center agent"); CHECK(bbox1.getCenterAgent() == LLVector3(4.0f, 7.0f, 10.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)), LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 4.0f, 4.0f)); - UNSCOPED_INFO("Custom bbox center agent with rot"); + UNSCOPED_INFO("rotated box"); CHECK(APPROX_EQUAL(bbox2.getCenterAgent(), LLVector3(-2.0f, 4.0f, 4.0f))); } @@ -151,19 +137,16 @@ namespace // test the getExtentLocal() method // - UNSCOPED_INFO("Default bbox local extent"); CHECK(LLBBox().getExtentLocal() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBox bbox1(LLVector3(1.0f, 2.0f, 3.0f), LLQuaternion(), LLVector3(2.0f, 4.0f, 6.0f), LLVector3(4.0f, 6.0f, 8.0f)); - UNSCOPED_INFO("Custom bbox extent local"); CHECK(bbox1.getExtentLocal() == LLVector3(2.0f, 2.0f, 2.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)), LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 4.0f, 4.0f)); - UNSCOPED_INFO("Custom bbox extent local with rot"); CHECK(bbox1.getExtentLocal() == LLVector3(2.0f, 2.0f, 2.0f)); } @@ -177,24 +160,17 @@ namespace bbox1.addPointLocal(LLVector3(1.0f, 1.0f, 1.0f)); bbox1.addPointLocal(LLVector3(3.0f, 3.0f, 3.0f)); - UNSCOPED_INFO("addPointLocal center local (1)"); CHECK(bbox1.getCenterLocal() == LLVector3(2.0f, 2.0f, 2.0f)); - UNSCOPED_INFO("addPointLocal center agent (1)"); CHECK(bbox1.getCenterAgent() == LLVector3(2.0f, 2.0f, 2.0f)); - UNSCOPED_INFO("addPointLocal min (1)"); CHECK(bbox1.getMinLocal() == LLVector3(1.0f, 1.0f, 1.0f)); - UNSCOPED_INFO("addPointLocal max (1)"); CHECK(bbox1.getMaxLocal() == LLVector3(3.0f, 3.0f, 3.0f)); bbox1.addPointLocal(LLVector3(0.0f, 0.0f, 0.0f)); bbox1.addPointLocal(LLVector3(1.0f, 1.0f, 1.0f)); bbox1.addPointLocal(LLVector3(2.0f, 2.0f, 2.0f)); - UNSCOPED_INFO("addPointLocal center local (2)"); CHECK(bbox1.getCenterLocal() == LLVector3(1.5f, 1.5f, 1.5f)); - UNSCOPED_INFO("addPointLocal min (2)"); CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, 0.0f)); - UNSCOPED_INFO("addPointLocal max (2)"); CHECK(bbox1.getMaxLocal() == LLVector3(3.0f, 3.0f, 3.0f)); } @@ -208,23 +184,16 @@ namespace bbox1.addBBoxLocal(LLBBox(LLVector3(), LLQuaternion(), LLVector3(0.0f, 0.0f, 0.0f), LLVector3(3.0f, 3.0f, 3.0f))); - UNSCOPED_INFO("addPointLocal center local (3)"); CHECK(bbox1.getCenterLocal() == LLVector3(1.5f, 1.5f, 1.5f)); - UNSCOPED_INFO("addPointLocal min (3)"); CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, 0.0f)); - UNSCOPED_INFO("addPointLocal max (3)"); CHECK(bbox1.getMaxLocal() == LLVector3(3.0f, 3.0f, 3.0f)); bbox1.addBBoxLocal(LLBBox(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(5.0f, 5.0f, 5.0f), LLVector3(10.0f, 10.0f, 10.0f))); - UNSCOPED_INFO("addPointLocal center local (4)"); CHECK(bbox1.getCenterLocal() == LLVector3(5.0f, 5.0f, 5.0f)); - UNSCOPED_INFO("addPointLocal center agent (4)"); CHECK(bbox1.getCenterAgent() == LLVector3(5.0f, 5.0f, 5.0f)); - UNSCOPED_INFO("addPointLocal min (4)"); CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, 0.0f)); - UNSCOPED_INFO("addPointLocal max (4)"); CHECK(bbox1.getMaxLocal() == LLVector3(10.0f, 10.0f, 10.0f)); } @@ -240,13 +209,9 @@ namespace bbox1.addPointAgent(LLVector3(1.0f, 1.0f, 1.0f)); bbox1.addPointAgent(LLVector3(3.0f, 3.0f, 3.0f)); - UNSCOPED_INFO("addPointAgent center local (1)"); CHECK(bbox1.getCenterLocal() == LLVector3(2.0f, 2.0f, -2.0f)); - UNSCOPED_INFO("addPointAgent center agent (1)"); CHECK(bbox1.getCenterAgent() == LLVector3(3.0f, 3.0f, 7.0f)); - UNSCOPED_INFO("addPointAgent min (1)"); CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, -4.0f)); - UNSCOPED_INFO("addPointAgent max (1)"); CHECK(bbox1.getMaxLocal() == LLVector3(4.0f, 4.0f, 0.0f)); } @@ -265,13 +230,9 @@ namespace bbox1.addBBoxLocal(LLBBox(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(5.0f, 5.0f, 5.0f), LLVector3(10.0f, 10.0f, 10.0f))); - UNSCOPED_INFO("addPointAgent center local (2)"); CHECK(bbox1.getCenterLocal() == LLVector3(5.0f, 5.0f, 3.0f)); - UNSCOPED_INFO("addPointAgent center agent (2)"); CHECK(bbox1.getCenterAgent() == LLVector3(6.0f, -10.0f, 8.0f)); - UNSCOPED_INFO("addPointAgent min (2)"); CHECK(bbox1.getMinLocal() == LLVector3(0.0f, 0.0f, -4.0f)); - UNSCOPED_INFO("addPointAgent max (2)"); CHECK(bbox1.getMaxLocal() == LLVector3(10.0f, 10.0f, 10.0f)); } @@ -284,38 +245,27 @@ namespace LLBBox bbox1; bbox1.expand(0.0); - UNSCOPED_INFO("Zero-expanded Default BBox center"); CHECK(bbox1.getCenterLocal() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); bbox2.expand(0.0); - UNSCOPED_INFO("Zero-expanded center local"); CHECK(bbox2.getCenterLocal() == LLVector3(2.0f, 2.0f, 2.0f)); - UNSCOPED_INFO("Zero-expanded center agent"); CHECK(bbox2.getCenterAgent() == LLVector3(3.0f, 3.0f, 3.0f)); - UNSCOPED_INFO("Zero-expanded min"); CHECK(bbox2.getMinLocal() == LLVector3(1.0f, 1.0f, 1.0f)); - UNSCOPED_INFO("Zero-expanded max"); CHECK(bbox2.getMaxLocal() == LLVector3(3.0f, 3.0f, 3.0f)); bbox2.expand(0.5); - UNSCOPED_INFO("Positive-expanded center"); CHECK(bbox2.getCenterLocal() == LLVector3(2.0f, 2.0f, 2.0f)); - UNSCOPED_INFO("Positive-expanded min"); CHECK(bbox2.getMinLocal() == LLVector3(0.5f, 0.5f, 0.5f)); - UNSCOPED_INFO("Positive-expanded max"); CHECK(bbox2.getMaxLocal() == LLVector3(3.5f, 3.5f, 3.5f)); bbox2.expand(-1.0); - UNSCOPED_INFO("Negative-expanded center"); CHECK(bbox2.getCenterLocal() == LLVector3(2.0f, 2.0f, 2.0f)); - UNSCOPED_INFO("Negative-expanded min"); CHECK(bbox2.getMinLocal() == LLVector3(1.5f, 1.5f, 1.5f)); - UNSCOPED_INFO("Negative-expanded max"); CHECK(bbox2.getMaxLocal() == LLVector3(2.5f, 2.5f, 2.5f)); } @@ -328,13 +278,12 @@ namespace LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); - UNSCOPED_INFO("localToAgent(1,2,3)"); CHECK(bbox1.localToAgent(LLVector3(1.0f, 2.0f, 3.0f)) == LLVector3(2.0f, 3.0f, 4.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(1.0f, 0.0f, 0.0f)), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); - UNSCOPED_INFO("localToAgent(1,2,3) rot"); + UNSCOPED_INFO("rotated box"); CHECK(APPROX_EQUAL(bbox2.localToAgent(LLVector3(1.0f, 2.0f, 3.0f)), LLVector3(2.0f, -2.0f, 3.0f))); } @@ -347,18 +296,16 @@ namespace LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); - UNSCOPED_INFO("agentToLocal(1,2,3)"); CHECK(bbox1.agentToLocal(LLVector3(1.0f, 2.0f, 3.0f)) == LLVector3(0.0f, 1.0f, 2.0f)); - UNSCOPED_INFO("agentToLocal(localToAgent)"); CHECK(bbox1.agentToLocal(bbox1.localToAgent(LLVector3(1.0f, 2.0f, 3.0f))) == LLVector3(1.0f, 2.0f, 3.0f)); LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(1.0f, 0.0f, 0.0f)), LLVector3(1.0f, 1.0f, 1.0f), LLVector3(3.0f, 3.0f, 3.0f)); - UNSCOPED_INFO("agentToLocal(1,2,3) rot"); + UNSCOPED_INFO("rotated box"); CHECK(APPROX_EQUAL(bbox2.agentToLocal(LLVector3(1.0f, 2.0f, 3.0f)), LLVector3(0.0f, 2.0f, -1.0f))); - UNSCOPED_INFO("agentToLocal(localToAgent) rot"); + UNSCOPED_INFO("rotated box"); CHECK(APPROX_EQUAL(bbox2.agentToLocal(bbox2.localToAgent(LLVector3(1.0f, 2.0f, 3.0f))), LLVector3(1.0f, 2.0f, 3.0f))); } @@ -372,15 +319,10 @@ namespace LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 2.0f, 3.0f), LLVector3(3.0f, 4.0f, 5.0f)); - UNSCOPED_INFO("containsPointLocal(0,0,0)"); CHECK_FALSE(bbox1.containsPointLocal(LLVector3(0.0f, 0.0f, 0.0f))); - UNSCOPED_INFO("containsPointLocal(1,2,3)"); CHECK(bbox1.containsPointLocal(LLVector3(1.0f, 2.0f, 3.0f))); - UNSCOPED_INFO("containsPointLocal(0.999,2,3)"); CHECK_FALSE(bbox1.containsPointLocal(LLVector3(0.999f, 2.0f, 3.0f))); - UNSCOPED_INFO("containsPointLocal(3,4,5)"); CHECK(bbox1.containsPointLocal(LLVector3(3.0f, 4.0f, 5.0f))); - UNSCOPED_INFO("containsPointLocal(3,4,5.001)"); CHECK_FALSE(bbox1.containsPointLocal(LLVector3(3.0f, 4.0f, 5.001f))); } @@ -393,15 +335,10 @@ namespace LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(), LLVector3(1.0f, 2.0f, 3.0f), LLVector3(3.0f, 4.0f, 5.0f)); - UNSCOPED_INFO("containsPointAgent(0,0,0)"); CHECK_FALSE(bbox1.containsPointAgent(LLVector3(0.0f, 0.0f, 0.0f))); - UNSCOPED_INFO("containsPointAgent(2,3,4)"); CHECK(bbox1.containsPointAgent(LLVector3(2.0f, 3.0f, 4.0f))); - UNSCOPED_INFO("containsPointAgent(2,2.999,4)"); CHECK_FALSE(bbox1.containsPointAgent(LLVector3(2.0f, 2.999f, 4.0f))); - UNSCOPED_INFO("containsPointAgent(4,5,6)"); CHECK(bbox1.containsPointAgent(LLVector3(4.0f, 5.0f, 6.0f))); - UNSCOPED_INFO("containsPointAgent(4,5.001,6)"); CHECK_FALSE(bbox1.containsPointAgent(LLVector3(4.0f, 5.001f, 6.0f))); } } diff --git a/indra/llmath/tests/llbboxlocal_test.cpp b/indra/llmath/tests/llbboxlocal_test.cpp index 3a41f5f222..df9694e905 100644 --- a/indra/llmath/tests/llbboxlocal_test.cpp +++ b/indra/llmath/tests/llbboxlocal_test.cpp @@ -44,9 +44,7 @@ namespace LLBBoxLocal bbox1; - UNSCOPED_INFO("Default bbox min"); CHECK(bbox1.getMin() == LLVector3(0.0f, 0.0f, 0.0f)); - UNSCOPED_INFO("Default bbox max"); CHECK(bbox1.getMax() == LLVector3(0.0f, 0.0f, 0.0f)); } @@ -58,9 +56,7 @@ namespace LLBBoxLocal bbox2(LLVector3(-1.0f, -2.0f, 0.0f), LLVector3(1.0f, 2.0f, 3.0f)); - UNSCOPED_INFO("Custom bbox min"); CHECK(bbox2.getMin() == LLVector3(-1.0f, -2.0f, 0.0f)); - UNSCOPED_INFO("Custom bbox max"); CHECK(bbox2.getMax() == LLVector3(1.0f, 2.0f, 3.0f)); } @@ -76,7 +72,6 @@ namespace LLBBoxLocal bbox2; bbox2.setMin(LLVector3(1.0f, 2.0f, 3.0f)); - UNSCOPED_INFO("Custom bbox min (2)"); CHECK(bbox2.getMin() == LLVector3(1.0f, 2.0f, 3.0f)); } @@ -92,7 +87,6 @@ namespace LLBBoxLocal bbox2; bbox2.setMax(LLVector3(10.0f, 20.0f, 30.0f)); - UNSCOPED_INFO("Custom bbox max (2)"); CHECK(bbox2.getMax() == LLVector3(10.0f, 20.0f, 30.0f)); } @@ -102,17 +96,15 @@ namespace // test the getCenter() method // - UNSCOPED_INFO("Default bbox center"); CHECK(LLBBoxLocal().getCenter() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBoxLocal bbox1(LLVector3(-1.0f, -1.0f, -1.0f), LLVector3(0.0f, 0.0f, 0.0f)); - UNSCOPED_INFO("Custom bbox center"); CHECK(bbox1.getCenter() == LLVector3(-0.5f, -0.5f, -0.5f)); LLBBoxLocal bbox2(LLVector3(0.0f, 0.0f, 0.0f), LLVector3(-1.0f, -1.0f, -1.0f)); - UNSCOPED_INFO("Invalid bbox center"); + UNSCOPED_INFO("min above max"); CHECK(bbox2.getCenter() == LLVector3(-0.5f, -0.5f, -0.5f)); } @@ -124,12 +116,10 @@ namespace LLBBoxLocal bbox2(LLVector3(0.0f, 0.0f, 0.0f), LLVector3(-1.0f, -1.0f, -1.0f)); - UNSCOPED_INFO("Default bbox extent"); CHECK(LLBBoxLocal().getExtent() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBoxLocal bbox3(LLVector3(-1.0f, -1.0f, -1.0f), LLVector3(1.0f, 2.0f, 0.0f)); - UNSCOPED_INFO("Custom bbox extent"); CHECK(bbox3.getExtent() == LLVector3(2.0f, 3.0f, 1.0f)); } @@ -147,31 +137,22 @@ namespace bbox1.addPoint(LLVector3(-1.0f, -2.0f, -3.0f)); bbox1.addPoint(LLVector3(3.0f, 4.0f, 5.0f)); - UNSCOPED_INFO("Custom BBox center (1)"); CHECK(bbox1.getCenter() == LLVector3(1.0f, 1.0f, 1.0f)); - UNSCOPED_INFO("Custom BBox min (1)"); CHECK(bbox1.getMin() == LLVector3(-1.0f, -2.0f, -3.0f)); - UNSCOPED_INFO("Custom BBox max (1)"); CHECK(bbox1.getMax() == LLVector3(3.0f, 4.0f, 5.0f)); bbox1.addPoint(LLVector3(0.0f, 0.0f, 0.0f)); bbox1.addPoint(LLVector3(1.0f, 2.0f, 3.0f)); bbox1.addPoint(LLVector3(2.0f, 2.0f, 2.0f)); - UNSCOPED_INFO("Custom BBox center (2)"); CHECK(bbox1.getCenter() == LLVector3(1.0f, 1.0f, 1.0f)); - UNSCOPED_INFO("Custom BBox min (2)"); CHECK(bbox1.getMin() == LLVector3(-1.0f, -2.0f, -3.0f)); - UNSCOPED_INFO("Custom BBox max (2)"); CHECK(bbox1.getMax() == LLVector3(3.0f, 4.0f, 5.0f)); bbox1.addPoint(LLVector3(5.0f, 5.0f, 5.0f)); - UNSCOPED_INFO("Custom BBox center (3)"); CHECK(bbox1.getCenter() == LLVector3(2.0f, 1.5f, 1.0f)); - UNSCOPED_INFO("Custom BBox min (3)"); CHECK(bbox1.getMin() == LLVector3(-1.0f, -2.0f, -3.0f)); - UNSCOPED_INFO("Custom BBox max (3)"); CHECK(bbox1.getMax() == LLVector3(5.0f, 5.0f, 5.0f)); } @@ -188,20 +169,14 @@ namespace LLBBoxLocal bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLVector3(2.0f, 2.0f, 2.0f)); bbox2.addBBox(LLBBoxLocal(LLVector3(1.5f, 1.5f, 1.5f), LLVector3(3.0f, 3.0f, 3.0f))); - UNSCOPED_INFO("Custom BBox center (4)"); CHECK(bbox2.getCenter() == LLVector3(2.0f, 2.0f, 2.0f)); - UNSCOPED_INFO("Custom BBox min (4)"); CHECK(bbox2.getMin() == LLVector3(1.0f, 1.0f, 1.0f)); - UNSCOPED_INFO("Custom BBox max (4)"); CHECK(bbox2.getMax() == LLVector3(3.0f, 3.0f, 3.0f)); bbox2.addBBox(LLBBoxLocal(LLVector3(-1.0f, -1.0f, -1.0f), LLVector3(0.0f, 0.0f, 0.0f))); - UNSCOPED_INFO("Custom BBox center (5)"); CHECK(bbox2.getCenter() == LLVector3(1.0f, 1.0f, 1.0f)); - UNSCOPED_INFO("Custom BBox min (5)"); CHECK(bbox2.getMin() == LLVector3(-1.0f, -1.0f, -1.0f)); - UNSCOPED_INFO("Custom BBox max (5)"); CHECK(bbox2.getMax() == LLVector3(3.0f, 3.0f, 3.0f)); } @@ -214,35 +189,25 @@ namespace LLBBoxLocal bbox1; bbox1.expand(0.0f); - UNSCOPED_INFO("Zero-expanded Default BBox center"); CHECK(bbox1.getCenter() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBoxLocal bbox2(LLVector3(1.0f, 2.0f, 3.0f), LLVector3(3.0f, 4.0f, 5.0f)); bbox2.expand(0.0f); - UNSCOPED_INFO("Zero-expanded BBox center"); CHECK(bbox2.getCenter() == LLVector3(2.0f, 3.0f, 4.0f)); - UNSCOPED_INFO("Zero-expanded BBox min"); CHECK(bbox2.getMin() == LLVector3(1.0f, 2.0f, 3.0f)); - UNSCOPED_INFO("Zero-expanded BBox max"); CHECK(bbox2.getMax() == LLVector3(3.0f, 4.0f, 5.0f)); bbox2.expand(0.5f); - UNSCOPED_INFO("Positive-expanded BBox center"); CHECK(bbox2.getCenter() == LLVector3(2.0f, 3.0f, 4.0f)); - UNSCOPED_INFO("Positive-expanded BBox min"); CHECK(bbox2.getMin() == LLVector3(0.5f, 1.5f, 2.5f)); - UNSCOPED_INFO("Positive-expanded BBox max"); CHECK(bbox2.getMax() == LLVector3(3.5f, 4.5f, 5.5f)); bbox2.expand(-1.0f); - UNSCOPED_INFO("Negative-expanded BBox center"); CHECK(bbox2.getCenter() == LLVector3(2.0f, 3.0f, 4.0f)); - UNSCOPED_INFO("Negative-expanded BBox min"); CHECK(bbox2.getMin() == LLVector3(1.5f, 2.5f, 3.5f)); - UNSCOPED_INFO("Negative-expanded BBox max"); CHECK(bbox2.getMax() == LLVector3(2.5f, 3.5f, 4.5f)); } } diff --git a/indra/llmath/tests/llmatrix3a_test.cpp b/indra/llmath/tests/llmatrix3a_test.cpp index e58a45e79c..6f3b709edc 100644 --- a/indra/llmath/tests/llmatrix3a_test.cpp +++ b/indra/llmath/tests/llmatrix3a_test.cpp @@ -41,20 +41,12 @@ #include #include -#include #include #include #include namespace { - std::string precise(double v) - { - char buffer[32]; - std::snprintf(buffer, sizeof(buffer), "%.9g", v); - return buffer; - } - // The 3x3 as doubles, m[row][col] struct Rows { @@ -74,16 +66,16 @@ namespace return r; } - void ensure_rows(const std::string& what, const LLMatrix3a& got, const Rows& expected, double tolerance) + void require_rows_near(const std::string& what, const LLMatrix3a& got, const Rows& expected, double tolerance) { + INFO(what); for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { const double g = got.getRow(row)[col]; const double e = expected.m[row][col]; - UNSCOPED_INFO(what + " row " + std::to_string(row) + " column " + std::to_string(col) + ": " + - precise(g) + " vs " + precise(e)); + CAPTURE(row, col, g, e); REQUIRE(std::fabs(g - e) <= tolerance * llmax(1.0, std::fabs(e))); } } @@ -149,13 +141,9 @@ namespace // by column. TEST_CASE("LLMatrix3a: plain data of three registers; the identity; rows and columns", "[LLMatrix3a]") { - UNSCOPED_INFO("sizeof LLMatrix3a"); CHECK(sizeof(LLMatrix3a) == (size_t)48); - UNSCOPED_INFO("alignof LLMatrix3a"); CHECK(alignof(LLMatrix3a) == (size_t)16); - UNSCOPED_INFO("sizeof LLRotation"); CHECK(sizeof(LLRotation) == (size_t)48); - UNSCOPED_INFO("trivially copyable"); CHECK(std::is_trivially_copyable::value); const LLMatrix3a& identity = LLMatrix3a::getIdentity(); @@ -163,26 +151,20 @@ namespace { for (int col = 0; col < 3; ++col) { - UNSCOPED_INFO("identity"); + CAPTURE(row, col); REQUIRE(identity.getRow(row)[col] == (row == col ? 1.f : 0.f)); } } const LLMatrix3a m = odd_matrix(); - UNSCOPED_INFO("getRow 1 y"); CHECK(m.getRow(1)[1] == 5.5f); - UNSCOPED_INFO("getRow 2 w"); CHECK(m.getRow(2)[3] == 300.f); - { - INFO("getRow is getRow(N)"); - CHECK(same_bits(m.getRow<0>(), m.getRow(0))); - CHECK(same_bits(m.getRow<1>(), m.getRow(1))); - CHECK(same_bits(m.getRow<2>(), m.getRow(2))); - } + CHECK(same_bits(m.getRow<0>(), m.getRow(0))); + CHECK(same_bits(m.getRow<1>(), m.getRow(1))); + CHECK(same_bits(m.getRow<2>(), m.getRow(2))); LLMatrix3a by_rows; by_rows.setRows(m.getRow<0>(), m.getRow<1>(), m.getRow<2>()); - UNSCOPED_INFO("setRows"); CHECK(same_bits(by_rows, m)); LLMatrix3a by_columns; @@ -191,13 +173,12 @@ namespace { for (int col = 0; col < 3; ++col) { - UNSCOPED_INFO("setColumns is the transpose of setRows"); + CAPTURE(row, col); REQUIRE(by_columns.getRow(row)[col] == m.getRow(col)[row]); } } } - // loadu takes the scalar matrix row by row. TEST_CASE("LLMatrix3a: loadu takes the scalar matrix row by row", "[LLMatrix3a]") { const F32 values[9] = {1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f}; @@ -206,12 +187,12 @@ namespace m.loadu(m3); for (int i = 0; i < 3; ++i) { + CAPTURE(i); for (int j = 0; j < 3; ++j) { - UNSCOPED_INFO("loadu"); + CAPTURE(j); REQUIRE(m.getRow(i)[j] == m3.mMatrix[i][j]); } - UNSCOPED_INFO("loadu w is zero"); REQUIRE(m.getRow(i)[3] == 0.f); } } @@ -227,26 +208,21 @@ namespace { for (int col = 0; col < 3; ++col) { - UNSCOPED_INFO("setTranspose"); + CAPTURE(row, col); REQUIRE(t.getRow(row)[col] == m.getRow(col)[row]); } } - UNSCOPED_INFO("transpose row 0 w"); CHECK(t.getRow(0)[3] == m.getRow(2)[1]); - UNSCOPED_INFO("transpose row 1 w"); CHECK(t.getRow(1)[3] == m.getRow(2)[0]); - UNSCOPED_INFO("transpose row 2 w"); CHECK(t.getRow(2)[3] == m.getRow(2)[0]); LLMatrix3a back; back.setTranspose(t); - UNSCOPED_INFO("transpose twice"); CHECK(same_bits3(back, m)); // in place LLMatrix3a self = m; self.setTranspose(self); - UNSCOPED_INFO("setTranspose in place"); CHECK(same_bits(self, t)); } @@ -262,7 +238,7 @@ namespace for (int col = 0; col < 3; ++col) { const double expected = v[0] * r.m[0][col] + v[1] * r.m[1][col] + v[2] * r.m[2][col]; - UNSCOPED_INFO("rotate lane " + std::to_string(col) + ": " + precise(rotated[col]) + " vs " + precise(expected)); + CAPTURE(col, rotated[col], expected); REQUIRE(std::fabs(rotated[col] - expected) <= 1e-6 * llmax(1.0, std::fabs(expected))); } @@ -270,13 +246,11 @@ namespace rot.setRows(m.getRow<0>(), m.getRow<1>(), m.getRow<2>()); LLVector4a set_rotated; set_rotated.setRotated(rot, v); - UNSCOPED_INFO("setRotated is rotate"); CHECK(same_bits(set_rotated, rotated)); // in place LLVector4a self = v; m.rotate(self, self); - UNSCOPED_INFO("rotate in place"); CHECK(same_bits(self, rotated)); for (const LLQuaternion& q : ROTATIONS) @@ -288,7 +262,6 @@ namespace const LLVector3 expected = v3 * m3; LLVector4a got; a.rotate(v, got); - UNSCOPED_INFO("rotate is v * LLMatrix3"); REQUIRE(got.equals3(LLVector4a(expected.mV[0], expected.mV[1], expected.mV[2]), 1e-5f)); } } @@ -316,7 +289,7 @@ namespace LLMatrix3a product; product.setMul(a, b); - ensure_rows("setMul", product, expected, 1e-6); + require_rows_near("setMul", product, expected, 1e-6); // the scalar type agrees on the order LLMatrix3 a3, b3; @@ -337,19 +310,15 @@ namespace LLMatrix3a left, right; left.setMul(LLMatrix3a::getIdentity(), a); right.setMul(a, LLMatrix3a::getIdentity()); - UNSCOPED_INFO("identity on the left"); CHECK(same_bits(left, a)); - UNSCOPED_INFO("identity on the right"); CHECK(same_bits3(right, a)); // aliasing either operand LLMatrix3a alias = a; alias.setMul(alias, b); - UNSCOPED_INFO("setMul aliasing a"); CHECK(same_bits(alias, product)); alias = b; alias.setMul(a, alias); - UNSCOPED_INFO("setMul aliasing b"); CHECK(same_bits(alias, product)); const LLVector4a v(1.f, -2.f, 0.5f, 0.f); @@ -372,18 +341,17 @@ namespace r.m[0][2] * (r.m[1][0] * r.m[2][1] - r.m[1][1] * r.m[2][0]); const F32 det = m.getDeterminant().getF32(); - UNSCOPED_INFO("getDeterminant: " + precise(det) + " vs " + precise(expected)); + CAPTURE(det, expected); CHECK(std::fabs(det - expected) <= 1e-5 * std::fabs(expected)); LLVector4a all; m.getDeterminant(all); for (int i = 0; i < 4; ++i) { - UNSCOPED_INFO("getDeterminant(LLVector4a) lane " + std::to_string(i)); + CAPTURE(i); REQUIRE(all[i] == det); } - UNSCOPED_INFO("identity determinant"); CHECK(LLMatrix3a::getIdentity().getDeterminant().getF32() == 1.f); } @@ -394,32 +362,25 @@ namespace { LLRotation r; r.loadu(q.getMatrix3()); - UNSCOPED_INFO("a rotation isOkRotation"); REQUIRE(r.isOkRotation()); - UNSCOPED_INFO("a rotation is finite"); REQUIRE(r.isFinite()); } LLRotation scaled; scaled.setRows(LLVector4a(2.f, 0.f, 0.f), LLVector4a(0.f, 2.f, 0.f), LLVector4a(0.f, 0.f, 2.f)); - UNSCOPED_INFO("a scale is not a rotation"); CHECK_FALSE(scaled.isOkRotation()); LLRotation sheared; sheared.setRows(LLVector4a(1.f, 0.f, 0.f), LLVector4a(0.5f, 1.f, 0.f), LLVector4a(0.f, 0.f, 1.f)); - UNSCOPED_INFO("a shear is not a rotation"); CHECK_FALSE(sheared.isOkRotation()); LLRotation reflected; reflected.setRows(LLVector4a(-1.f, 0.f, 0.f), LLVector4a(0.f, 1.f, 0.f), LLVector4a(0.f, 0.f, 1.f)); - UNSCOPED_INFO("a reflection is not a rotation"); CHECK_FALSE(reflected.isOkRotation()); LLRotation nan; nan.setRows(LLVector4a(std::numeric_limits::quiet_NaN(), 0.f, 0.f), LLVector4a(0.f, 1.f, 0.f), LLVector4a(0.f, 0.f, 1.f)); - UNSCOPED_INFO("a NaN is not finite"); CHECK_FALSE(nan.isFinite()); - UNSCOPED_INFO("a NaN is not a rotation"); CHECK_FALSE(nan.isOkRotation()); LLRotation inf_in_w; @@ -437,6 +398,7 @@ namespace for (int count : {0, 1, 2, 3, 7, 16}) { + CAPTURE(count); std::vector src(count + 1), dst(count + 1); for (int i = 0; i < count; ++i) { @@ -450,7 +412,7 @@ namespace { LLVector4a one; r.rotate(src[i], one); - UNSCOPED_INFO("batchTransform count " + std::to_string(count) + " element " + std::to_string(i)); + CAPTURE(i); REQUIRE(same_bits(dst[i], one)); } UNSCOPED_INFO("batchTransform stays within count"); @@ -458,7 +420,6 @@ namespace } } - // setLerp and the tolerance compare. TEST_CASE("LLMatrix3a: setLerp and the tolerance compare", "[LLMatrix3a]") { const LLMatrix3a a = odd_matrix(); @@ -470,9 +431,7 @@ namespace at_zero.setLerp(a, b, 0.f); at_one.setLerp(a, b, 1.f); at_quarter.setLerp(a, b, 0.25f); - UNSCOPED_INFO("setLerp at 0"); CHECK(same_bits(at_zero, a)); - UNSCOPED_INFO("setLerp at 1"); CHECK(at_one.isApproximatelyEqual(b, 1e-6f)); const Rows ra = rows_of(a); @@ -485,7 +444,7 @@ namespace expected.m[row][col] = ra.m[row][col] + (rb.m[row][col] - ra.m[row][col]) * 0.25; } } - ensure_rows("setLerp at a quarter", at_quarter, expected, 1e-6); + require_rows_near("setLerp at a quarter", at_quarter, expected, 1e-6); LLMatrix3a nearby = a; LLVector4a nudged = nearby.getRow<1>(); @@ -493,7 +452,6 @@ namespace nearby.setRows(nearby.getRow<0>(), nudged, nearby.getRow<2>()); UNSCOPED_INFO("isApproximatelyEqual within tolerance, w ignored"); CHECK(nearby.isApproximatelyEqual(a)); - UNSCOPED_INFO("isApproximatelyEqual outside tolerance"); CHECK_FALSE(nearby.isApproximatelyEqual(a, 1e-7f)); } } diff --git a/indra/llmath/tests/llmatrix4a_test.cpp b/indra/llmath/tests/llmatrix4a_test.cpp index 0a0f54d2c8..d667ab8b16 100644 --- a/indra/llmath/tests/llmatrix4a_test.cpp +++ b/indra/llmath/tests/llmatrix4a_test.cpp @@ -39,7 +39,6 @@ #include #include -#include #include namespace @@ -232,8 +231,9 @@ namespace // two, or of one: single precision against the double reference, and // two single-precision implementations against each other, agree to a // few ulps under /fp:fast, not bit for bit. - void ensure_matrix_close(const std::string& what, const LLMatrix4a& actual, const Ref& expected, F32 tolerance) + void require_matrix_close(const std::string& what, const LLMatrix4a& actual, const Ref& expected, F32 tolerance) { + INFO(what); for (S32 i = 0; i < 4; ++i) { for (S32 j = 0; j < 4; ++j) @@ -241,42 +241,40 @@ namespace const F32 a = actual.mMatrix[i][j]; const F32 e = F32(expected.m[i][j]); const F32 scale = llmax(1.f, fabsf(a), fabsf(e)); - std::ostringstream msg; - msg << what << " at [" << i << "][" << j << "]: " << a << " vs " << e; - UNSCOPED_INFO(msg.str().c_str()); + CAPTURE(i, j); REQUIRE_THAT(a, Catch::Matchers::WithinAbs(e, tolerance * scale)); } } } - void ensure_matrix_close(const std::string& what, const LLMatrix4a& actual, const LLMatrix4a& expected, F32 tolerance) + void require_matrix_close(const std::string& what, const LLMatrix4a& actual, const LLMatrix4a& expected, F32 tolerance) { - ensure_matrix_close(what, actual, Ref::of(expected), tolerance); + require_matrix_close(what, actual, Ref::of(expected), tolerance); } - void ensure_vector_close(const std::string& what, const LLVector4a& actual, const double expected[4], F32 tolerance, S32 lanes = 4) + void require_vector_close(const std::string& what, const LLVector4a& actual, const double expected[4], F32 tolerance, S32 lanes = 4) { + INFO(what); for (S32 i = 0; i < lanes; ++i) { const F32 e = F32(expected[i]); const F32 scale = llmax(1.f, fabsf(actual[i]), fabsf(e)); - std::ostringstream msg; - msg << what << " at lane " << i << ": " << actual[i] << " vs " << e; - UNSCOPED_INFO(msg.str().c_str()); + CAPTURE(i); REQUIRE_THAT(actual[i], Catch::Matchers::WithinAbs(e, tolerance * scale)); } } - void ensure_vector_close(const std::string& what, const LLVector4a& actual, const LLVector4a& expected, F32 tolerance, S32 lanes = 4) + void require_vector_close(const std::string& what, const LLVector4a& actual, const LLVector4a& expected, F32 tolerance, S32 lanes = 4) { const double e[4] = { expected[0], expected[1], expected[2], expected[3] }; - ensure_vector_close(what, actual, e, tolerance, lanes); + require_vector_close(what, actual, e, tolerance, lanes); } // The product a * b is the identity to within tolerance of the size of // the terms that cancel in each element. - void ensure_product_is_identity(const std::string& what, const LLMatrix4a& a, const LLMatrix4a& b, F32 tolerance) + void require_product_is_identity(const std::string& what, const LLMatrix4a& a, const LLMatrix4a& b, F32 tolerance) { + INFO(what); LLMatrix4a product; product.setMul(a, b); for (S32 row = 0; row < 4; ++row) @@ -288,9 +286,7 @@ namespace { terms += fabsf(a.mMatrix[row][k] * b.mMatrix[k][col]); } - std::ostringstream msg; - msg << what << " at [" << row << "][" << col << "]"; - UNSCOPED_INFO(msg.str().c_str()); + CAPTURE(row, col); REQUIRE_THAT(product.mMatrix[row][col], Catch::Matchers::WithinAbs(row == col ? 1.f : 0.f, tolerance * (1.f + terms))); } } @@ -331,13 +327,8 @@ namespace { for (S32 col = 0; col < 4; ++col) { - std::ostringstream msg; - msg << "initAll mismatch at [" << row << "][" << col - << "] for case " << cases; - UNSCOPED_INFO(msg.str().c_str()); - REQUIRE_THAT(actual4.mMatrix[row][col], - ll_test::WithinBits(expected.mMatrix[row][col], - 20)); + CAPTURE(cases, row, col); + REQUIRE_THAT(actual4.mMatrix[row][col], ll_test::WithinBits(expected.mMatrix[row][col], 20)); } } ++cases; @@ -363,13 +354,9 @@ namespace m.initAll(LLVector3(1.f, 1.f, 1.f), LLQuaternion(), LLVector3(1.f, 2.f, 3.f)); const LLMatrix4 out = m.toMatrix4(); - UNSCOPED_INFO("row 0 pad cleared"); CHECK(out.mMatrix[0][3] == 0.f); - UNSCOPED_INFO("row 1 pad cleared"); CHECK(out.mMatrix[1][3] == 0.f); - UNSCOPED_INFO("row 2 pad cleared"); CHECK(out.mMatrix[2][3] == 0.f); - UNSCOPED_INFO("row 3 w is one"); CHECK(out.mMatrix[3][3] == 1.f); } @@ -380,26 +367,14 @@ namespace m.initAll(LLVector3(1.f, 1.f, 1.f), LLQuaternion(), LLVector3(4.f, 5.f, 6.f)); const LLMatrix4 out = m.toMatrix4(); - UNSCOPED_INFO("m00"); CHECK(out.mMatrix[0][0] == 1.f); - UNSCOPED_INFO("m11"); CHECK(out.mMatrix[1][1] == 1.f); - UNSCOPED_INFO("m22"); CHECK(out.mMatrix[2][2] == 1.f); - UNSCOPED_INFO("tx"); CHECK(out.mMatrix[3][0] == 4.f); - UNSCOPED_INFO("ty"); CHECK(out.mMatrix[3][1] == 5.f); - UNSCOPED_INFO("tz"); CHECK(out.mMatrix[3][2] == 6.f); } - - - - - - TEST_CASE("LLMatrix4a: each basis row's length is its axis' scale", "[LLMatrix4a]") { // Scale lands on the corresponding basis row, not the diagonal, so a @@ -412,9 +387,7 @@ namespace for (S32 row = 0; row < 3; ++row) { const F32 len = LLVector3(out.mMatrix[row]).length(); - std::ostringstream msg; - msg << "row " << row << " length matches its scale"; - UNSCOPED_INFO(msg.str().c_str()); + CAPTURE(row); REQUIRE_THAT(len, Catch::Matchers::WithinAbs(scale.mV[row], 1e-4f)); } } @@ -434,7 +407,7 @@ namespace { for (S32 col = 0; col < 4; ++col) { - UNSCOPED_INFO("round trip"); + CAPTURE(row, col); REQUIRE(back.mMatrix[row][col] == src.mMatrix[row][col]); } } @@ -451,15 +424,10 @@ namespace LLMatrix4a m; m.set(src); const LLMatrix4 out = m.toMatrix4(); - UNSCOPED_INFO("m00"); CHECK(out.mMatrix[0][0] == 1.f); - UNSCOPED_INFO("m12"); CHECK(out.mMatrix[1][2] == 6.f); - UNSCOPED_INFO("m22"); CHECK(out.mMatrix[2][2] == 9.f); - UNSCOPED_INFO("row 3 is the origin"); CHECK(out.mMatrix[3][0] == 0.f); - UNSCOPED_INFO("row 3 w is one"); CHECK(out.mMatrix[3][3] == 1.f); } @@ -473,10 +441,8 @@ namespace // Multiplying by the identity in either position is a no-op. LLMatrix4a r; r.setMul(a, LLMatrix4a::identity()); - UNSCOPED_INFO("a * identity == a"); CHECK(r == a); r.setMul(LLMatrix4a::identity(), a); - UNSCOPED_INFO("identity * a == a"); CHECK(r == a); LLMatrix4a b; @@ -488,19 +454,16 @@ namespace LLMatrix4a safe, fast; safe.setMul(a, b); fast.setMulNoAlias(a, b); - UNSCOPED_INFO("setMulNoAlias matches setMul"); CHECK(safe == fast); // setMul tolerates its result aliasing either input, which is why it // exists alongside setMulNoAlias. LLMatrix4a lhs = a; lhs.setMul(lhs, b); - UNSCOPED_INFO("result may alias the left operand"); CHECK(lhs == safe); LLMatrix4a rhs = b; rhs.setMul(a, rhs); - UNSCOPED_INFO("result may alias the right operand"); CHECK(rhs == safe); } @@ -515,20 +478,14 @@ namespace LLVector4a moved; m.affineTransform(point, moved); - UNSCOPED_INFO("affineTransform adds x"); CHECK(moved[0] == 11.f); - UNSCOPED_INFO("affineTransform adds y"); CHECK(moved[1] == 22.f); - UNSCOPED_INFO("affineTransform adds z"); CHECK(moved[2] == 33.f); LLVector4a turned; m.rotate(point, turned); - UNSCOPED_INFO("rotate leaves x"); CHECK(turned[0] == 1.f); - UNSCOPED_INFO("rotate leaves y"); CHECK(turned[1] == 2.f); - UNSCOPED_INFO("rotate leaves z"); CHECK(turned[2] == 3.f); } @@ -542,14 +499,10 @@ namespace const LLVector4a basis = m.getRow<0>(); m.setTranslation(LLVector3(5.f, 6.f, 7.f)); - UNSCOPED_INFO("setTranslation leaves the basis alone"); CHECK(m.getRow<0>() == basis); const LLMatrix4 out = m.toMatrix4(); - UNSCOPED_INFO("tx"); CHECK(out.mMatrix[3][0] == 5.f); - UNSCOPED_INFO("ty"); CHECK(out.mMatrix[3][1] == 6.f); - UNSCOPED_INFO("tz"); CHECK(out.mMatrix[3][2] == 7.f); UNSCOPED_INFO("stays affine"); CHECK(out.mMatrix[3][3] == 1.f); @@ -557,7 +510,6 @@ namespace LLVector4a replacement; replacement.set(1.f, 2.f, 3.f, 4.f); m.setRow<2>(replacement); - UNSCOPED_INFO("setRow round trips"); CHECK(m.getRow<2>() == replacement); } @@ -614,14 +566,12 @@ namespace for (U32 side = 0; side < 2; ++side) for (U32 axis = 0; axis < 3; ++axis) { - std::ostringstream msg; - msg << "box " << (&box - BOXES) << " scale " << scale << " pos " << pos - << (side ? " max" : " min") << " axis " << axis; // Absolute slack scaled to the magnitudes in play: the // corner walk and the centre form round differently, and the // 1000x scale row amplifies whichever rounds first. const F32 magnitude = llmax(1.f, fabsf(expected[side][axis])); - UNSCOPED_INFO(msg.str().c_str()); + UNSCOPED_INFO("box " << (&box - BOXES) << " scale " << scale << " pos " << pos + << (side ? " max" : " min") << " axis " << axis); REQUIRE_THAT(out[side][axis], Catch::Matchers::WithinAbs(expected[side][axis], magnitude * 1e-4f)); } @@ -660,28 +610,20 @@ namespace { const F32 av = a4.mMatrix[row][col]; const F32 bv = b4.mMatrix[row][col]; - std::ostringstream msg; - msg << " at [" << row << "][" << col << "]"; - UNSCOPED_INFO("add" + msg.str()); + CAPTURE(row, col); REQUIRE(sum4.mMatrix[row][col] == av + bv); - UNSCOPED_INFO("setMul(F32)" + msg.str()); REQUIRE(scaled4.mMatrix[row][col] == av * -1.5f); - UNSCOPED_INFO("setLerp at 0" + msg.str()); REQUIRE(zero4.mMatrix[row][col] == av); - UNSCOPED_INFO(("setLerp at 1" + msg.str()).c_str()); REQUIRE_THAT(one4.mMatrix[row][col], Catch::Matchers::WithinAbs(bv, 1e-6f * llmax(1.f, fabsf(bv)))); const F32 expected = (F32)((double)av + ((double)bv - av) * 0.25); - UNSCOPED_INFO(("setLerp at a quarter" + msg.str()).c_str()); REQUIRE_THAT(quarter4.mMatrix[row][col], Catch::Matchers::WithinAbs(expected, 1e-6f * llmax(1.f, fabsf(expected)))); } } // the identity, both ways - UNSCOPED_INFO("identity equals itself"); CHECK(LLMatrix4a::identity() == LLMatrix4a::identity()); LLMatrix4a fresh; fresh.setIdentity(); - UNSCOPED_INFO("setIdentity is the identity"); CHECK(fresh == LLMatrix4a::identity()); UNSCOPED_INFO("a translation is not the identity"); CHECK(a != LLMatrix4a::identity()); @@ -712,16 +654,16 @@ namespace const double p[4] = { p3.mV[0], p3.mV[1], p3.mV[2], 1.0 }; double expected[4]; r.transform(p, expected); - ensure_vector_close("affineTransform is the row vector through every row", point, expected, tolerance, 3); + require_vector_close("affineTransform is the row vector through every row", point, expected, tolerance, 3); const LLVector3 scalar = p3 * m.toMatrix4(); - ensure_vector_close("and the scalar matrix's v * M", point, as_point(scalar), tolerance, 3); + require_vector_close("and the scalar matrix's v * M", point, as_point(scalar), tolerance, 3); // a direction leaves the translation out LLVector4a direction; m.rotate(as_direction(p3), direction); const double d[4] = { p3.mV[0], p3.mV[1], p3.mV[2], 0.0 }; r.transform(d, expected); - ensure_vector_close("rotate is the direction through the upper rows", direction, expected, tolerance, 3); + require_vector_close("rotate is the direction through the upper rows", direction, expected, tolerance, 3); ++cases; } @@ -736,17 +678,17 @@ namespace b.initAll(SCALES[3], ROTATIONS[4], POSITIONS[2]); LLMatrix4a ab; ab.setMul(a, b); - ensure_matrix_close("setMul is the rows of a through b", ab, Ref::of(a).then(Ref::of(b)), tolerance); + require_matrix_close("setMul is the rows of a through b", ab, Ref::of(a).then(Ref::of(b)), tolerance); LLMatrix4 scalar_ab = a.toMatrix4(); scalar_ab *= b.toMatrix4(); - ensure_matrix_close("and the scalar matrix's a *= b", ab, LLMatrix4a(scalar_ab), tolerance); + require_matrix_close("and the scalar matrix's a *= b", ab, LLMatrix4a(scalar_ab), tolerance); LLVector4a through_ab, via_a, via_b; const LLVector4a p = as_point(LLVector3(1.f, 2.f, 3.f)); ab.affineTransform(p, through_ab); a.affineTransform(p, via_a); b.affineTransform(via_a, via_b); - ensure_vector_close("setMul(a, b) is a's transform then b's", through_ab, via_b, tolerance, 3); + require_vector_close("setMul(a, b) is a's transform then b's", through_ab, via_b, tolerance, 3); // a builder ahead of a matrix acts on the point first const LLVector3 t3(4.f, -5.f, 6.f); @@ -755,7 +697,7 @@ namespace LLVector4a moved_then_a, through_translated; a.affineTransform(as_point(LLVector3(1.f, 2.f, 3.f) + t3), moved_then_a); translated.affineTransform(p, through_translated); - ensure_vector_close("setMul(translation, m) moves the point before m", through_translated, moved_then_a, tolerance, 3); + require_vector_close("setMul(translation, m) moves the point before m", through_translated, moved_then_a, tolerance, 3); const LLVector3 s3(2.f, 3.f, 0.5f); LLMatrix4a scaled; @@ -763,7 +705,7 @@ namespace LLVector4a scaled_then_a, through_scaled; a.affineTransform(as_point(LLVector3(1.f, 2.f, 3.f).scaledVec(s3)), scaled_then_a); scaled.affineTransform(p, through_scaled); - ensure_vector_close("setMul(scaling, m) scales the point before m", through_scaled, scaled_then_a, tolerance, 3); + require_vector_close("setMul(scaling, m) scales the point before m", through_scaled, scaled_then_a, tolerance, 3); const F32 angle = 0.8f; const LLVector3 axis(0.267f, -0.535f, 0.802f); @@ -772,7 +714,7 @@ namespace LLVector4a rotated_then_a, through_rotated; a.affineTransform(as_point(LLVector3(1.f, 2.f, 3.f) * LLQuaternion(angle, axis)), rotated_then_a); rotated.affineTransform(p, through_rotated); - ensure_vector_close("setMul(rotation, m) is the tree's rotation of the point before m", through_rotated, rotated_then_a, tolerance, 3); + require_vector_close("setMul(rotation, m) is the tree's rotation of the point before m", through_rotated, rotated_then_a, tolerance, 3); } // transform4, transpose, determinant, the inverses and the normal @@ -802,7 +744,7 @@ namespace const double vd[4] = { 0.3, -7.0, 2.5, 0.75 }; double expected[4]; r.transform(vd, expected); - ensure_vector_close("transform4 is v through every row", out, expected, tolerance); + require_vector_close("transform4 is v through every row", out, expected, tolerance); // perspectiveTransform is a point through every row, over w LLVector4a ndc; @@ -810,7 +752,7 @@ namespace const double pd[4] = { 0.3, -7.0, 2.5, 1.0 }; r.transform(pd, expected); const double divided[4] = { expected[0] / expected[3], expected[1] / expected[3], expected[2] / expected[3], 1.0 }; - ensure_vector_close("perspectiveTransform divides by w", ndc, divided, tolerance); + require_vector_close("perspectiveTransform divides by w", ndc, divided, tolerance); // transpose is a data movement: bit for bit LLMatrix4a t; @@ -819,16 +761,14 @@ namespace { for (S32 j = 0; j < 4; ++j) { - UNSCOPED_INFO("setTranspose swaps the indices"); + CAPTURE(i, j); REQUIRE(t.mMatrix[i][j] == m.mMatrix[j][i]); } } LLMatrix4a tt = m; tt.transpose(); - UNSCOPED_INFO("transpose in place"); REQUIRE(same_bytes(tt, t)); tt.transpose(); - UNSCOPED_INFO("transposed twice is itself"); REQUIRE(tt == m); // the determinant, to within the size of the terms that cancel in @@ -838,20 +778,16 @@ namespace { terms *= fabsf(m.mMatrix[row][0]) + fabsf(m.mMatrix[row][1]) + fabsf(m.mMatrix[row][2]) + fabsf(m.mMatrix[row][3]); } - UNSCOPED_INFO("determinant"); REQUIRE_THAT(m.determinant(), Catch::Matchers::WithinAbs(F32(r.determinant()), 1e-6f * (1.f + terms))); LLMatrix4a inv; - UNSCOPED_INFO("setInverse succeeds"); REQUIRE(inv.setInverse(m)); - ensure_matrix_close("setInverse matches the reference inverse", inv, r.inverse(), tolerance); + require_matrix_close("setInverse matches the reference inverse", inv, r.inverse(), tolerance); LLMatrix4a in_place = m; - UNSCOPED_INFO("invert succeeds"); REQUIRE(in_place.invert()); - UNSCOPED_INFO("invert in place"); REQUIRE(in_place == inv); - ensure_product_is_identity("M * inverse(M)", m, inv, 1e-6f); - ensure_product_is_identity("inverse(M) * M", inv, m, 1e-6f); + require_product_is_identity("M * inverse(M)", m, inv, 1e-6f); + require_product_is_identity("inverse(M) * M", inv, m, 1e-6f); } // A region-sized translation is a matrix whose inverse two @@ -863,10 +799,9 @@ namespace LLMatrix4a far_away; far_away.initAll(LLVector3(1.f, 1.f, 1.f), ROTATIONS[5], LLVector3(-256.f, 4096.f, 0.125f)); LLMatrix4a far_inv; - UNSCOPED_INFO("setInverse of the far matrix succeeds"); CHECK(far_inv.setInverse(far_away)); - ensure_product_is_identity("far M * inverse(M)", far_away, far_inv, 1e-6f); - ensure_product_is_identity("far inverse(M) * M", far_inv, far_away, 1e-6f); + require_product_is_identity("far M * inverse(M)", far_away, far_inv, 1e-6f); + require_product_is_identity("far inverse(M) * M", far_inv, far_away, 1e-6f); // the affine inverse and the normal matrix, on the affine ones for (const LLMatrix4a* mp : { &affine, &rigid }) @@ -876,25 +811,21 @@ namespace const Ref inverse = r.inverse(); LLMatrix4a inv; - UNSCOPED_INFO("setAffineInverse succeeds"); REQUIRE(inv.setAffineInverse(m)); - ensure_matrix_close("setAffineInverse is the inverse of an affine matrix", inv, inverse, tolerance); + require_matrix_close("setAffineInverse is the inverse of an affine matrix", inv, inverse, tolerance); LLMatrix4a n; - UNSCOPED_INFO("setNormalMatrix succeeds"); REQUIRE(n.setNormalMatrix(m)); const Ref inverse_transpose = inverse.transposed(); for (S32 row = 0; row < 3; ++row) { + CAPTURE(row); for (S32 col = 0; col < 3; ++col) { - std::ostringstream msg; - msg << "normal matrix at [" << row << "][" << col << "]"; + CAPTURE(col); const F32 e = F32(inverse_transpose.m[row][col]); - UNSCOPED_INFO(msg.str().c_str()); REQUIRE_THAT(n.mMatrix[row][col], Catch::Matchers::WithinAbs(e, tolerance * llmax(1.f, fabsf(e)))); } - UNSCOPED_INFO("normal matrix row pad"); REQUIRE(n.mMatrix[row][3] == 0.f); } UNSCOPED_INFO("normal matrix has no translation"); @@ -915,21 +846,16 @@ namespace singular.initAll(SCALES[4], ROTATIONS[1], POSITIONS[1]); LLMatrix4a untouched; untouched.setIdentity(); - UNSCOPED_INFO("setInverse of a singular matrix is refused"); CHECK_FALSE(untouched.setInverse(singular)); - UNSCOPED_INFO("and leaves the target alone"); + UNSCOPED_INFO("a refused inverse leaves the target alone"); CHECK(untouched == LLMatrix4a::identity()); - UNSCOPED_INFO("setAffineInverse of a singular matrix is refused"); CHECK_FALSE(untouched.setAffineInverse(singular)); - UNSCOPED_INFO("setNormalMatrix of a singular matrix is refused"); CHECK_FALSE(untouched.setNormalMatrix(singular)); UNSCOPED_INFO("still untouched"); CHECK(untouched == LLMatrix4a::identity()); - UNSCOPED_INFO("singular determinant"); CHECK(singular.determinant() == 0.f); } - // The builders against the formulas, written out in double. TEST_CASE("LLMatrix4a: builders against the formulas written out in double", "[LLMatrix4a]") { const F32 tolerance = 2e-6f; @@ -938,7 +864,6 @@ namespace LLMatrix4a expected; expected.setIdentity(); expected.mMatrix[3].set(4.f, -5.f, 6.f, 1.f); - UNSCOPED_INFO("translation"); CHECK(same_bytes(LLMatrix4a::translation(4.f, -5.f, 6.f), expected)); UNSCOPED_INFO("translation from a vector ignores w"); CHECK(same_bytes(LLMatrix4a::translation(LLVector4a(4.f, -5.f, 6.f, 9.f)), expected)); @@ -947,9 +872,7 @@ namespace expected.mMatrix[0].set(2.f, 0.f, 0.f, 0.f); expected.mMatrix[1].set(0.f, 3.f, 0.f, 0.f); expected.mMatrix[2].set(0.f, 0.f, 0.5f, 0.f); - UNSCOPED_INFO("scaling"); CHECK(same_bytes(LLMatrix4a::scaling(2.f, 3.f, 0.5f), expected)); - UNSCOPED_INFO("scaling from a vector"); CHECK(same_bytes(LLMatrix4a::scaling(LLVector4a(2.f, 3.f, 0.5f, 9.f)), expected)); } @@ -966,15 +889,15 @@ namespace r.m[1][0] = t * ax * ay - s * az; r.m[1][1] = c + t * ay * ay; r.m[1][2] = t * ay * az + s * ax; r.m[2][0] = t * ax * az + s * ay; r.m[2][1] = t * ay * az - s * ax; r.m[2][2] = c + t * az * az; const LLMatrix4a rotation = LLMatrix4a::rotation(angle, as_direction(axis3)); - ensure_matrix_close("rotation about an axis", rotation, r, tolerance); - ensure_matrix_close("and from the quaternion of the same rotation", rotation, LLMatrix4a::rotation(LLQuaternion2(LLQuaternion(angle, axis3))), tolerance); + require_matrix_close("rotation about an axis", rotation, r, tolerance); + require_matrix_close("and from the quaternion of the same rotation", rotation, LLMatrix4a::rotation(LLQuaternion2(LLQuaternion(angle, axis3))), tolerance); } for (const LLQuaternion& q : ROTATIONS) { LLMatrix4a expected; expected.initAll(LLVector3(1.f, 1.f, 1.f), q, LLVector3()); - ensure_matrix_close("rotation from a quaternion is initAll's", LLMatrix4a::rotation(LLQuaternion2(q)), expected, tolerance); + require_matrix_close("rotation from a quaternion is initAll's", LLMatrix4a::rotation(LLQuaternion2(q)), expected, tolerance); } // the projections @@ -988,10 +911,10 @@ namespace r.m[2][3] = -1.0; r.m[3][2] = -(2.0 * f * n) / (f - n); r.m[3][3] = 0.0; - ensure_matrix_close("perspective", LLMatrix4a::perspective(1.1f, 1.777f, 0.25f, 1024.f), r, tolerance); + require_matrix_close("perspective", LLMatrix4a::perspective(1.1f, 1.777f, 0.25f, 1024.f), r, tolerance); r.m[2][2] = f / (n - f); r.m[3][2] = -(f * n) / (f - n); - ensure_matrix_close("perspectiveZO", LLMatrix4a::perspectiveZO(1.1f, 1.777f, 0.25f, 1024.f), r, tolerance); + require_matrix_close("perspectiveZO", LLMatrix4a::perspectiveZO(1.1f, 1.777f, 0.25f, 1024.f), r, tolerance); } { const double l = -3.0, rt = 5.0, b = -2.0, t = 7.0, n = 0.5, f = 100.0; @@ -1002,10 +925,10 @@ namespace r.m[3][0] = -(rt + l) / (rt - l); r.m[3][1] = -(t + b) / (t - b); r.m[3][2] = -(f + n) / (f - n); - ensure_matrix_close("ortho", LLMatrix4a::ortho(-3.f, 5.f, -2.f, 7.f, 0.5f, 100.f), r, tolerance); + require_matrix_close("ortho", LLMatrix4a::ortho(-3.f, 5.f, -2.f, 7.f, 0.5f, 100.f), r, tolerance); r.m[2][2] = -1.0 / (f - n); r.m[3][2] = -n / (f - n); - ensure_matrix_close("orthoZO", LLMatrix4a::orthoZO(-3.f, 5.f, -2.f, 7.f, 0.5f, 100.f), r, tolerance); + require_matrix_close("orthoZO", LLMatrix4a::orthoZO(-3.f, 5.f, -2.f, 7.f, 0.5f, 100.f), r, tolerance); } // the pick matrix: the region centred and blown up to the viewport @@ -1016,7 +939,7 @@ namespace r.m[1][1] = 720.0 / 7.0; r.m[3][0] = (1280.0 - 2.0 * (300.0 - 10.0)) / 5.0; r.m[3][1] = (720.0 - 2.0 * (400.0 - 20.0)) / 7.0; - ensure_matrix_close("pick", LLMatrix4a::pick(300.f, 400.f, 5.f, 7.f, viewport), r, tolerance); + require_matrix_close("pick", LLMatrix4a::pick(300.f, 400.f, 5.f, 7.f, viewport), r, tolerance); UNSCOPED_INFO("pick of nothing is the identity"); CHECK(LLMatrix4a::pick(300.f, 400.f, 0.f, 7.f, viewport) == LLMatrix4a::identity()); } @@ -1049,20 +972,20 @@ namespace const LLVector4a at4(130.f, 60.f, 29.f, 1.f); const LLVector4a up4(0.f, 0.f, 1.f, 0.f); const LLMatrix4a look = LLMatrix4a::lookAt(eye4, at4, up4); - ensure_matrix_close("lookAt", look, r, tolerance); + require_matrix_close("lookAt", look, r, tolerance); LLVector4a dir; dir.setSub(at4, eye4); dir.mul(2.5f); - ensure_matrix_close("lookDir, any length of direction", LLMatrix4a::lookDir(eye4, dir, up4), r, tolerance); + require_matrix_close("lookDir, any length of direction", LLMatrix4a::lookDir(eye4, dir, up4), r, tolerance); // the eye lands at the origin and the point looked at on -z LLVector4a origin, ahead; look.affineTransform(eye4, origin); look.affineTransform(at4, ahead); const double zero[4] = { 0.0, 0.0, 0.0, 1.0 }; - ensure_vector_close("the eye is the origin of the view", origin, zero, 1e-4f, 3); + require_vector_close("the eye is the origin of the view", origin, zero, 1e-4f, 3); const double down_z[4] = { 0.0, 0.0, -fl, 1.0 }; - ensure_vector_close("the point looked at is straight ahead", ahead, down_z, 1e-4f, 3); + require_vector_close("the point looked at is straight ahead", ahead, down_z, 1e-4f, 3); } } @@ -1079,7 +1002,7 @@ namespace LLQuaternion2 q; q.setAxisAngle(as_direction(axis3), angle); const LLQuaternion2 expected(LLQuaternion(angle, axis3)); - UNSCOPED_INFO("setAxisAngle is LLQuaternion(angle, axis)"); + CAPTURE(angle); REQUIRE(q.equals(expected, 1e-6f)); } LLQuaternion2 none; @@ -1126,16 +1049,14 @@ namespace LLMatrix4a expected; expected.initAll(LLVector3(sc.getF32ptr()), q, LLVector3(tr.getF32ptr())); - ensure_matrix_close("setTRS is initAll", m, expected, tolerance); + require_matrix_close("setTRS is initAll", m, expected, tolerance); LLVector4a scale_out, translation_out; LLQuaternion2 rotation_out; - UNSCOPED_INFO("decompose succeeds"); REQUIRE(m.decompose(scale_out, rotation_out, translation_out)); LLMatrix4a rebuilt; rebuilt.setTRS(translation_out, rotation_out, scale_out); - ensure_matrix_close("decompose then setTRS is the matrix", rebuilt, m, 1e-4f); - UNSCOPED_INFO("translation comes back"); + require_matrix_close("decompose then setTRS is the matrix", rebuilt, m, 1e-4f); REQUIRE(translation_out.equals3(tr, 1e-5f * llmax(1.f, fabsf(tr[1])))); // a right-handed scale comes back as itself; a left-handed @@ -1143,7 +1064,6 @@ namespace const bool right_handed = sc[0] * sc[1] * sc[2] > 0.f; if (right_handed && sc[0] > 0.f && sc[1] > 0.f && sc[2] > 0.f) { - UNSCOPED_INFO("scale comes back"); REQUIRE(scale_out.equals3(sc, 1e-4f * llmax(1.f, fabsf(sc[1])))); LLVector4a negated; negated.setNeg(rotation_out.getVector4a()); @@ -1157,11 +1077,9 @@ namespace // a projective or singular matrix is refused LLVector4a s, t; LLQuaternion2 r; - UNSCOPED_INFO("a projection does not decompose"); CHECK_FALSE(LLMatrix4a::perspective(1.f, 1.f, 1.f, 10.f).decompose(s, r, t)); LLMatrix4a flat; flat.setTRS(translations[1], LLQuaternion2(ROTATIONS[2]), LLVector4a(1.f, 0.f, 1.f, 0.f)); - UNSCOPED_INFO("a flattened matrix does not decompose"); CHECK_FALSE(flat.decompose(s, r, t)); } @@ -1197,22 +1115,21 @@ namespace const double expected_zo[4] = { expected[0], expected[1], ndc[2], 1.0 }; const LLVector4a win = alprojection::project(obj, modelview, projection, viewport); - ensure_vector_close("project", win, expected, tolerance); + require_vector_close("project", win, expected, tolerance); const LLVector4a win_zo = alprojection::project_zo(obj, modelview, projection, viewport); - ensure_vector_close("project_zo", win_zo, expected_zo, tolerance); + require_vector_close("project_zo", win_zo, expected_zo, tolerance); const LLVector4a back = alprojection::unproject(win, modelview, projection, viewport); - ensure_vector_close("unproject of project is the point", back, obj, back_tolerance); + require_vector_close("unproject of project is the point", back, obj, back_tolerance); const LLVector4a back_zo = alprojection::unproject_zo(win_zo, modelview, projection, viewport); - ensure_vector_close("unproject_zo of project_zo is the point", back_zo, obj, back_tolerance); + require_vector_close("unproject_zo of project_zo is the point", back_zo, obj, back_tolerance); // the same through an inverse taken once LLMatrix4a inverse; inverse.setMul(modelview, projection); - UNSCOPED_INFO("the camera inverts"); REQUIRE(inverse.invert()); - ensure_vector_close("unproject through the inverse", alprojection::unproject(win, inverse, viewport), back, 1e-6f); - ensure_vector_close("unproject_zo through the inverse", alprojection::unproject_zo(win_zo, inverse, viewport), back_zo, 1e-6f); + require_vector_close("unproject through the inverse", alprojection::unproject(win, inverse, viewport), back, 1e-6f); + require_vector_close("unproject_zo through the inverse", alprojection::unproject_zo(win_zo, inverse, viewport), back_zo, 1e-6f); } } } diff --git a/indra/llmath/tests/llmodularmath_test.cpp b/indra/llmath/tests/llmodularmath_test.cpp index 4b13077270..d9d7dc6a03 100644 --- a/indra/llmath/tests/llmodularmath_test.cpp +++ b/indra/llmath/tests/llmodularmath_test.cpp @@ -41,7 +41,6 @@ namespace const U32 rhs = 0xFFFFFF; const U32 width = 24; U32 result = LLModularMath::subtract(lhs, rhs); - UNSCOPED_INFO("diff(0x000001, 0xFFFFFF, 24)"); CHECK(result == 2); } @@ -52,7 +51,6 @@ namespace const U32 rhs = 0x000001; const U32 width = 24; U32 result = LLModularMath::subtract(lhs, rhs); - UNSCOPED_INFO("diff(0x000002, 0x000001, 24)"); CHECK(result == 1); } @@ -63,7 +61,6 @@ namespace const U32 rhs = 0xABCDEF; const U32 width = 24; U32 result = LLModularMath::subtract(lhs, rhs); - UNSCOPED_INFO("diff(0xABCDEF, 0xABCDEF, 24)"); CHECK(result == 0); } } diff --git a/indra/llmath/tests/llquaternion2_test.cpp b/indra/llmath/tests/llquaternion2_test.cpp index 07d7108cf5..3d34a8c0e0 100644 --- a/indra/llmath/tests/llquaternion2_test.cpp +++ b/indra/llmath/tests/llquaternion2_test.cpp @@ -70,25 +70,26 @@ namespace return out; } - void ensure_quat_equals(const std::string& msg, const LLQuaternion2& actual, const LLQuaternion& expected) + void require_quat_near(const std::string& msg, const LLQuaternion2& actual, const LLQuaternion& expected) { + INFO(msg); LLQuaternion got; actual.store(got); for (S32 i = 0; i < 4; ++i) { - UNSCOPED_INFO((msg + " component").c_str()); + CAPTURE(i); REQUIRE_THAT(got.mQ[i], Catch::Matchers::WithinAbs(expected.mQ[i], TOLERANCE)); } } // A rotation and its negation are the same rotation. - void ensure_same_rotation(const std::string& msg, const LLQuaternion2& actual, const LLQuaternion& expected) + void require_same_rotation(const std::string& msg, const LLQuaternion2& actual, const LLQuaternion& expected) { LLQuaternion got; actual.store(got); const F32 d = got.mQ[0] * expected.mQ[0] + got.mQ[1] * expected.mQ[1] + got.mQ[2] * expected.mQ[2] + got.mQ[3] * expected.mQ[3]; - UNSCOPED_INFO(msg.c_str()); + INFO(msg); REQUIRE_THAT(fabsf(d), Catch::Matchers::WithinAbs(1.f, TOLERANCE)); } } @@ -106,11 +107,11 @@ namespace for (const LLQuaternion& q : mRotations) { LLQuaternion2 simd(q); - ensure_quat_equals("round trip", simd, q); + require_quat_near("round trip", simd, q); } LLQuaternion ident; - ensure_quat_equals("identity", LLQuaternion2::identity(), ident); + require_quat_near("identity", LLQuaternion2::identity(), ident); } TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: setMul matches LLQuaternion operator* for every pair", "[LLQuaternion2]") @@ -122,7 +123,7 @@ namespace { LLQuaternion2 result; result.setMul(LLQuaternion2(a), LLQuaternion2(b)); - ensure_quat_equals("a * b", result, a * b); + require_quat_near("a * b", result, a * b); } } } @@ -147,7 +148,7 @@ namespace const LLVector3 expected = (v * a) * b; for (S32 i = 0; i < 3; ++i) { - UNSCOPED_INFO("rotate by the product"); + CAPTURE(i); REQUIRE_THAT(out[i], Catch::Matchers::WithinAbs(expected.mV[i], 1e-4f)); } } @@ -182,7 +183,7 @@ namespace const F32 scale = llmax(1.f, expected.magVec()); for (S32 i = 0; i < 3; ++i) { - UNSCOPED_INFO("v * q"); + CAPTURE(i); REQUIRE_THAT(out[i], Catch::Matchers::WithinAbs(expected.mV[i], 1e-5f * scale)); } UNSCOPED_INFO("w is carried through"); @@ -200,7 +201,6 @@ namespace for (const LLQuaternion& b : mRotations) { const LLSimdScalar d = LLQuaternion2(a).dot(LLQuaternion2(b)); - UNSCOPED_INFO("dot"); REQUIRE_THAT(d.getF32(), Catch::Matchers::WithinAbs(dot(a, b), TOLERANCE)); } } @@ -216,17 +216,17 @@ namespace { LLQuaternion2 at_start; at_start.setLerp(LLQuaternion2(a), LLQuaternion2(b), 0.f); - ensure_same_rotation("u = 0 is a", at_start, a); + require_same_rotation("u = 0 is a", at_start, a); LLQuaternion2 at_end; at_end.setLerp(LLQuaternion2(a), LLQuaternion2(b), 1.f); - ensure_same_rotation("u = 1 is b", at_end, b); + require_same_rotation("u = 1 is b", at_end, b); for (F32 u = 0.125f; u < 1.f; u += 0.125f) { LLQuaternion2 mid; mid.setLerp(LLQuaternion2(a), LLQuaternion2(b), u); - UNSCOPED_INFO("interpolated rotation is a rotation"); + CAPTURE(u); REQUIRE(mid.isOkRotation()); } } @@ -259,11 +259,9 @@ namespace // and it really is the eighth turn, not the seven eighths one const LLQuaternion eighth(F_PI * 0.25f, axis); - ensure_same_rotation("half way is an eighth of a turn", from_b, eighth); + require_same_rotation("half way is an eighth of a turn", from_b, eighth); } - - TEST_CASE_METHOD(llquaternion2_data, "LLQuaternion2: a quaternion times its conjugate is the identity", "[LLQuaternion2]") { // Conjugate undoes the rotation, and the product with it is identity. @@ -277,7 +275,7 @@ namespace product.setMul(simd, conj); LLQuaternion ident; - ensure_same_rotation("q times its conjugate is identity", product, ident); + require_same_rotation("q times its conjugate is identity", product, ident); } } @@ -305,7 +303,7 @@ namespace const F32 scale = llmax(1.f, expected.magVec()); for (S32 i = 0; i < 3; ++i) { - UNSCOPED_INFO("off-unit v * q"); + CAPTURE(i); REQUIRE_THAT(out[i], Catch::Matchers::WithinAbs(expected.mV[i], 1e-5f * scale)); } } @@ -340,6 +338,7 @@ namespace product.store(got); for (S32 i = 0; i < 3; ++i) { + CAPTURE(i); UNSCOPED_INFO("q times its inverse has no axis"); REQUIRE_THAT(got.mQ[i], Catch::Matchers::WithinAbs(0.f, 1e-5f)); } @@ -352,8 +351,10 @@ namespace { // Against the free slerp, which is the second implementation this is // compared with rather than the same one written twice. - auto check = [](const char* what, const LLQuaternion& a, const LLQuaternion& b, F32 u) + auto require_follows_slerp = [](const char* what, const LLQuaternion& a, const LLQuaternion& b, F32 u) { + INFO(what); + CAPTURE(u); LLQuaternion2 blended; blended.setSlerp(LLQuaternion2(a), LLQuaternion2(b), u); LLQuaternion got; @@ -361,7 +362,6 @@ namespace LLQuaternion expected = slerp(u, a, b); // q and -q are the same rotation. - UNSCOPED_INFO(what); REQUIRE_THAT(fabsf(dot(got, expected)), Catch::Matchers::WithinAbs(1.f, 1e-4f)); }; @@ -371,14 +371,14 @@ namespace const LLQuaternion half_turn(F_PI * 0.99f, LLVector3::z_axis); for (F32 u = 0.f; u <= 1.f; u += 0.125f) { - check("a half turn follows the arc", at_zero, half_turn, u); + require_follows_slerp("a half turn follows the arc", at_zero, half_turn, u); } // A quarter turn, comfortably inside the range a lerp used to take. const LLQuaternion quarter(F_PI_BY_TWO, LLVector3::y_axis); for (F32 u = 0.f; u <= 1.f; u += 0.125f) { - check("a quarter turn follows the arc", at_zero, quarter, u); + require_follows_slerp("a quarter turn follows the arc", at_zero, quarter, u); } // About an axis that is not a basis vector. @@ -387,7 +387,7 @@ namespace const LLQuaternion oblique(2.f, axis); for (F32 u = 0.f; u <= 1.f; u += 0.125f) { - check("an oblique axis follows the arc", at_zero, oblique, u); + require_follows_slerp("an oblique axis follows the arc", at_zero, oblique, u); } } @@ -445,6 +445,7 @@ namespace for (S32 trial = 0; trial < 200; ++trial) { + CAPTURE(trial); LLVector3 axis_a(spread(rng), spread(rng), spread(rng)); LLVector3 axis_b(spread(rng), spread(rng), spread(rng)); if (axis_a.magVecSquared() < 1e-6f || axis_b.magVecSquared() < 1e-6f) @@ -460,13 +461,11 @@ namespace LLQuaternion2 blended; blended.setSlerp(LLQuaternion2(a), LLQuaternion2(b), u); - UNSCOPED_INFO("a blend of two rotations is a rotation"); REQUIRE(blended.isOkRotation()); LLQuaternion got; blended.store(got); LLQuaternion expected = slerp(u, a, b); - UNSCOPED_INFO("and it is the one slerp gives"); REQUIRE_THAT(fabsf(dot(got, expected)), Catch::Matchers::WithinAbs(1.f, 1e-3f)); } } @@ -479,28 +478,26 @@ namespace { LLQuaternion2 q8(q); q8.quantize8(); - UNSCOPED_INFO("quantize8 is a rotation"); REQUIRE(q8.isOkRotation()); LLQuaternion got8; q8.store(got8); - UNSCOPED_INFO("quantize8 is the rotation it was given, to eight bits"); + UNSCOPED_INFO("quantize8 keeps the rotation it was given"); REQUIRE_THAT(fabsf(dot(got8, q)), Catch::Matchers::WithinAbs(1.f, 1e-4f)); for (S32 i = 0; i < 4; ++i) { - UNSCOPED_INFO("quantize8 component within a step"); + CAPTURE(i); REQUIRE(fabsf(got8.mQ[i] - q.mQ[i]) <= 2.f / 255.f); } LLQuaternion2 q16(q); q16.quantize16(); - UNSCOPED_INFO("quantize16 is a rotation"); REQUIRE(q16.isOkRotation()); - ensure_same_rotation("quantize16 is the rotation it was given", q16, q); + require_same_rotation("quantize16 is the rotation it was given", q16, q); LLQuaternion got16; q16.store(got16); for (S32 i = 0; i < 4; ++i) { - UNSCOPED_INFO("quantize16 component within a step"); + CAPTURE(i); REQUIRE(fabsf(got16.mQ[i] - q.mQ[i]) <= 2.f / 65535.f); } } @@ -515,13 +512,9 @@ namespace conj.setConjugate(LLQuaternion2(q)); LLQuaternion got; conj.store(got); - UNSCOPED_INFO("conjugate x"); CHECK(got.mQ[VX] == -q.mQ[VX]); - UNSCOPED_INFO("conjugate y"); CHECK(got.mQ[VY] == -q.mQ[VY]); - UNSCOPED_INFO("conjugate z"); CHECK(got.mQ[VZ] == -q.mQ[VZ]); - UNSCOPED_INFO("conjugate w"); CHECK(got.mQ[VW] == q.mQ[VW]); LLQuaternion2 ident; diff --git a/indra/llmath/tests/llquaternion_test.cpp b/indra/llmath/tests/llquaternion_test.cpp index 06bc9900fc..5a5bac459c 100644 --- a/indra/llmath/tests/llquaternion_test.cpp +++ b/indra/llmath/tests/llquaternion_test.cpp @@ -43,13 +43,10 @@ namespace TEST_CASE("LLQuaternion: default constructor gives the identity quaternion", "[LLQuaternion]") { LLQuaternion llquat; - { - INFO("LLQuaternion::LLQuaternion() failed"); - CHECK(llquat.mQ[0] == 0.f); - CHECK(llquat.mQ[1] == 0.f); - CHECK(llquat.mQ[2] == 0.f); - CHECK(llquat.mQ[3] == 1.f); - } + CHECK(llquat.mQ[0] == 0.f); + CHECK(llquat.mQ[1] == 0.f); + CHECK(llquat.mQ[2] == 0.f); + CHECK(llquat.mQ[3] == 1.f); } //test case for explicit LLQuaternion(const LLMatrix4 &mat) fn. @@ -62,25 +59,22 @@ namespace LLVector4 vector4(3.0f, 8.0f, 1.0f, 5.0f); llmat.initRows(vector1, vector2, vector3, vector4); - { - INFO("explicit LLQuaternion(const LLMatrix4 &mat) failed"); - CHECK(llmat.mMatrix[0][0] == 2.0f); - CHECK(llmat.mMatrix[0][1] == 1.0f); - CHECK(llmat.mMatrix[0][2] == 3.0f); - CHECK(llmat.mMatrix[0][3] == 6.0f); - CHECK(llmat.mMatrix[1][0] == 5.0f); - CHECK(llmat.mMatrix[1][1] == 6.0f); - CHECK(llmat.mMatrix[1][2] == 0.0f); - CHECK(llmat.mMatrix[1][3] == 1.0f); - CHECK(llmat.mMatrix[2][0] == 2.0f); - CHECK(llmat.mMatrix[2][1] == 1.0f); - CHECK(llmat.mMatrix[2][2] == 2.0f); - CHECK(llmat.mMatrix[2][3] == 9.0f); - CHECK(llmat.mMatrix[3][0] == 3.0f); - CHECK(llmat.mMatrix[3][1] == 8.0f); - CHECK(llmat.mMatrix[3][2] == 1.0f); - CHECK(llmat.mMatrix[3][3] == 5.0f); - } + CHECK(llmat.mMatrix[0][0] == 2.0f); + CHECK(llmat.mMatrix[0][1] == 1.0f); + CHECK(llmat.mMatrix[0][2] == 3.0f); + CHECK(llmat.mMatrix[0][3] == 6.0f); + CHECK(llmat.mMatrix[1][0] == 5.0f); + CHECK(llmat.mMatrix[1][1] == 6.0f); + CHECK(llmat.mMatrix[1][2] == 0.0f); + CHECK(llmat.mMatrix[1][3] == 1.0f); + CHECK(llmat.mMatrix[2][0] == 2.0f); + CHECK(llmat.mMatrix[2][1] == 1.0f); + CHECK(llmat.mMatrix[2][2] == 2.0f); + CHECK(llmat.mMatrix[2][3] == 9.0f); + CHECK(llmat.mMatrix[3][0] == 3.0f); + CHECK(llmat.mMatrix[3][1] == 8.0f); + CHECK(llmat.mMatrix[3][2] == 1.0f); + CHECK(llmat.mMatrix[3][3] == 5.0f); } TEST_CASE("LLQuaternion: LLMatrix3 setRows keeps each float exactly", "[LLQuaternion]") @@ -92,18 +86,15 @@ namespace LLVector3 vect3(566.003034f, 12.98705f, 234.764423f); llmat.setRows(vect1, vect2, vect3); - { - INFO("LLMatrix3::setRows fn failed."); - CHECK(llmat.mMatrix[0][0] == 3.4028234660000000f); - CHECK(llmat.mMatrix[0][1] == 234.56f); - CHECK(llmat.mMatrix[0][2] == 4234.442234f); - CHECK(llmat.mMatrix[1][0] == 741.434f); - CHECK(llmat.mMatrix[1][1] == 23.00034f); - CHECK(llmat.mMatrix[1][2] == 6567.223423f); - CHECK(llmat.mMatrix[2][0] == 566.003034f); - CHECK(llmat.mMatrix[2][1] == 12.98705f); - CHECK(llmat.mMatrix[2][2] == 234.764423f); - } + CHECK(llmat.mMatrix[0][0] == 3.4028234660000000f); + CHECK(llmat.mMatrix[0][1] == 234.56f); + CHECK(llmat.mMatrix[0][2] == 4234.442234f); + CHECK(llmat.mMatrix[1][0] == 741.434f); + CHECK(llmat.mMatrix[1][1] == 23.00034f); + CHECK(llmat.mMatrix[1][2] == 6567.223423f); + CHECK(llmat.mMatrix[2][0] == 566.003034f); + CHECK(llmat.mMatrix[2][1] == 12.98705f); + CHECK(llmat.mMatrix[2][2] == 234.764423f); } //test case for LLQuaternion(F32 x, F32 y, F32 z, F32 w), setQuatInit() and normQuat() fns. @@ -118,13 +109,10 @@ namespace res_quat.setQuatInit(x_val, y_val, z_val, w_val); res_quat.normQuat(); - { - INFO("LLQuaternion::normQuat() fn failed"); - CHECK(is_approx_equal(0.42426407f, res_quat.mQ[0])); - CHECK(is_approx_equal(0.28284273f, res_quat.mQ[1])); - CHECK(is_approx_equal(0.84852815f, res_quat.mQ[2])); - CHECK(is_approx_equal(0.14142136f, res_quat.mQ[3])); - } + CHECK(is_approx_equal(0.42426407f, res_quat.mQ[0])); + CHECK(is_approx_equal(0.28284273f, res_quat.mQ[1])); + CHECK(is_approx_equal(0.84852815f, res_quat.mQ[2])); + CHECK(is_approx_equal(0.14142136f, res_quat.mQ[3])); x_val = 0.0f; y_val = 0.0f; @@ -134,22 +122,16 @@ namespace res_quat.setQuatInit(x_val, y_val, z_val, w_val); res_quat.normQuat(); - { - INFO("LLQuaternion::normQuat() fn. failed."); - CHECK(is_approx_equal(0.0f, res_quat.mQ[0])); - CHECK(is_approx_equal(0.0f, res_quat.mQ[1])); - CHECK(is_approx_equal(0.0f, res_quat.mQ[2])); - CHECK(is_approx_equal(1.0f, res_quat.mQ[3])); - } - - - { - INFO("LLQuaternion::normQuat() fn. failed."); - CHECK(is_approx_equal(0.0f, res_quat.mQ[0])); - CHECK(is_approx_equal(0.0f, res_quat.mQ[1])); - CHECK(is_approx_equal(0.0f, res_quat.mQ[2])); - CHECK(is_approx_equal(1.0f, res_quat.mQ[3])); - } + CHECK(is_approx_equal(0.0f, res_quat.mQ[0])); + CHECK(is_approx_equal(0.0f, res_quat.mQ[1])); + CHECK(is_approx_equal(0.0f, res_quat.mQ[2])); + CHECK(is_approx_equal(1.0f, res_quat.mQ[3])); + + + CHECK(is_approx_equal(0.0f, res_quat.mQ[0])); + CHECK(is_approx_equal(0.0f, res_quat.mQ[1])); + CHECK(is_approx_equal(0.0f, res_quat.mQ[2])); + CHECK(is_approx_equal(1.0f, res_quat.mQ[3])); } //test case for conjQuat() and transQuat() fns. @@ -167,12 +149,9 @@ namespace result.conjQuat(); result1.transQuat(); - { - INFO("LLQuaternion::conjQuat and LLQuaternion::transQuat failed "); - CHECK(is_approx_equal(result1.mQ[0], result.mQ[0])); - CHECK(is_approx_equal(result1.mQ[1], result.mQ[1])); - CHECK(is_approx_equal(result1.mQ[2], result.mQ[2])); - } + CHECK(is_approx_equal(result1.mQ[0], result.mQ[0])); + CHECK(is_approx_equal(result1.mQ[1], result.mQ[1])); + CHECK(is_approx_equal(result1.mQ[2], result.mQ[2])); } @@ -180,11 +159,9 @@ namespace TEST_CASE("LLQuaternion: dot sums the products of the four components", "[LLQuaternion]") { LLQuaternion quat1(3.0f, 2.0f, 6.0f, 0.0f), quat2(1.0f, 1.0f, 1.0f, 1.0f); - UNSCOPED_INFO("1. The two values are different"); CHECK(ll_round(12.000000f, 2) == ll_round(dot(quat1, quat2), 2)); LLQuaternion quat0(3.0f, 9.334f, 34.5f, 23.0f), quat(34.5f, 23.23f, 2.0f, 45.5f); - UNSCOPED_INFO("2. The two values are different"); CHECK(ll_round(1435.828807f, 2) == ll_round(dot(quat0, quat), 2)); } @@ -195,26 +172,20 @@ namespace LLQuaternion quat(3.0f, 2.0f, 6.0f, 0.0f); LLQuaternion quat1; quat1 = quat.constrain(radian); - { - INFO("1. LLQuaternion::constrain(F32 radians) failed"); - CHECK(is_approx_equal_fraction(-0.423442f, quat1.mQ[0], 8)); - CHECK(is_approx_equal_fraction(-0.282295f, quat1.mQ[1], 8)); - CHECK(is_approx_equal_fraction(-0.846884f, quat1.mQ[2], 8)); - CHECK(is_approx_equal_fraction(0.154251f, quat1.mQ[3], 8)); - } + CHECK(is_approx_equal_fraction(-0.423442f, quat1.mQ[0], 8)); + CHECK(is_approx_equal_fraction(-0.282295f, quat1.mQ[1], 8)); + CHECK(is_approx_equal_fraction(-0.846884f, quat1.mQ[2], 8)); + CHECK(is_approx_equal_fraction(0.154251f, quat1.mQ[3], 8)); radian = 30.0f; LLQuaternion quat0(37.50f, 12.0f, 86.023f, 40.32f); quat1 = quat0.constrain(radian); - { - INFO("2. LLQuaternion::constrain(F32 radians) failed"); - CHECK(is_approx_equal_fraction(37.500000f, quat1.mQ[0], 8)); - CHECK(is_approx_equal_fraction(12.0000f, quat1.mQ[1], 8)); - CHECK(is_approx_equal_fraction(86.0230f, quat1.mQ[2], 8)); - CHECK(is_approx_equal_fraction(40.320000f, quat1.mQ[3], 8)); - } + CHECK(is_approx_equal_fraction(37.500000f, quat1.mQ[0], 8)); + CHECK(is_approx_equal_fraction(12.0000f, quat1.mQ[1], 8)); + CHECK(is_approx_equal_fraction(86.0230f, quat1.mQ[2], 8)); + CHECK(is_approx_equal_fraction(40.320000f, quat1.mQ[3], 8)); } TEST_CASE("LLQuaternion: lerp and slerp and nlerp overloads interpolate", "[LLQuaternion]") @@ -232,53 +203,38 @@ namespace // Previous "expected" values were derived from a typo that read // q.mQ[VZ] for W instead of q.mQ[VW]. res_lerp = lerp(value1, quat1); - { - INFO("1. LLQuaternion lerp(F32 t, const LLQuaternion &q) failed"); - CHECK(is_approx_equal_fraction(0.218195f, res_lerp.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.436390f, res_lerp.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.872779f, res_lerp.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.014546f, res_lerp.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.218195f, res_lerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.436390f, res_lerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.872779f, res_lerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.014546f, res_lerp.mQ[3], 16)); //test case for lerp(F32 t, const LLQuaternion &p, const LLQuaternion &q) fn. res_lerp = lerp(value1, quat1, quat2); - { - INFO("2. LLQuaternion lerp(F32 t, const LLQuaternion &p, const LLQuaternion &q) failed"); - CHECK(is_approx_equal_fraction(0.314306f, res_lerp.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.116156f, res_lerp.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.283559f, res_lerp.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.898506f, res_lerp.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.314306f, res_lerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.116156f, res_lerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.283559f, res_lerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.898506f, res_lerp.mQ[3], 16)); //test case for slerp( F32 u, const LLQuaternion &a, const LLQuaternion &b ) fn. res_slerp = slerp(value1, quat1, quat2); - { - INFO("3. LLQuaternion slerp( F32 u, const LLQuaternion &a, const LLQuaternion &b) failed"); - CHECK(is_approx_equal_fraction(46.000f, res_slerp.mQ[0], 16)); - CHECK(is_approx_equal_fraction(17.00f, res_slerp.mQ[1], 16)); - CHECK(is_approx_equal_fraction(41.5f, res_slerp.mQ[2], 16)); - CHECK(is_approx_equal_fraction(131.5f, res_slerp.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(46.000f, res_slerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(17.00f, res_slerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(41.5f, res_slerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(131.5f, res_slerp.mQ[3], 16)); //test case for nlerp(F32 t, const LLQuaternion &a, const LLQuaternion &b) fn. res_nlerp = nlerp(value1, quat1, quat2); - { - INFO("4. LLQuaternion nlerp(F32 t, const LLQuaternion &a, const LLQuaternion &b) failed"); - CHECK(is_approx_equal_fraction(0.314306f, res_nlerp.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.116157f, res_nlerp.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.283559f, res_nlerp.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.898506f, res_nlerp.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.314306f, res_nlerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.116157f, res_nlerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.283559f, res_nlerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.898506f, res_nlerp.mQ[3], 16)); //test case for nlerp(F32 t, const LLQuaternion &q) fn. res_slerp = slerp(value1, quat1); - { - INFO("5. LLQuaternion slerp(F32 t, const LLQuaternion &q) failed"); - CHECK(is_approx_equal_fraction(1.0f, res_slerp.mQ[0], 16)); - CHECK(is_approx_equal_fraction(2.0f, res_slerp.mQ[1], 16)); - CHECK(is_approx_equal_fraction(4.0000f, res_slerp.mQ[2], 16)); - CHECK(is_approx_equal_fraction(1.000f, res_slerp.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(1.0f, res_slerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(2.0f, res_slerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(4.0000f, res_slerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(1.000f, res_slerp.mQ[3], 16)); LLQuaternion quat3(2.0f, 1.0f, 5.5f, 10.5f); LLQuaternion res_nlerp1; @@ -289,13 +245,10 @@ namespace // pre-normalize r = (200, 100, 550, 951) (W = 1 + 100*(10.5 - 1) = 951) // |r| = sqrt(1256901) ~= 1121.116 // r/|r| ~= (0.178394, 0.089197, 0.490583, 0.848262) - { - INFO("6. LLQuaternion nlerp(F32 t, const LLQuaternion &q) failed"); - CHECK(is_approx_equal_fraction(0.178394f, res_nlerp1.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.089197f, res_nlerp1.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.490583f, res_nlerp1.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.848262f, res_nlerp1.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.178394f, res_nlerp1.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.089197f, res_nlerp1.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.490583f, res_nlerp1.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.848262f, res_nlerp1.mQ[3], 16)); //test case for lerp(F32 t, const LLQuaternion &q) fn. // value1 = 100, quat2 = (4, 3, 6.5, 9.7): @@ -303,13 +256,10 @@ namespace // |r| = sqrt(1431141) ~= 1196.303 // r/|r| ~= (0.334363, 0.250773, 0.543341, 0.728076) res_lerp = lerp(value1, quat2); - { - INFO("7. LLQuaternion lerp(F32 t, const LLQuaternion &q) failed"); - CHECK(is_approx_equal_fraction(0.334363f, res_lerp.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.250773f, res_lerp.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.543341f, res_lerp.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.728076f, res_lerp.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.334363f, res_lerp.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.250773f, res_lerp.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.543341f, res_lerp.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.728076f, res_lerp.mQ[3], 16)); } @@ -319,13 +269,10 @@ namespace LLQuaternion quat1(1.0f, 2.5f, 3.5f, 5.5f); LLQuaternion quat2(4.0f, 3.0f, 5.0f, 1.0f); LLQuaternion result = quat1 * quat2; - { - INFO("1. LLQuaternion Operator* failed"); - CHECK(result.mQ[0] == 21.0f); - CHECK(result.mQ[1] == 10.0f); - CHECK(result.mQ[2] == 38.0f); - CHECK(result.mQ[3] == -23.5f); - } + CHECK(result.mQ[0] == 21.0f); + CHECK(result.mQ[1] == 10.0f); + CHECK(result.mQ[2] == 38.0f); + CHECK(result.mQ[3] == -23.5f); LLQuaternion quat3(2341.340f, 2352.345f, 233.25f, 7645.5f); LLQuaternion quat4(674.067f, 893.0897f, 578.0f, 231.0f); @@ -333,51 +280,36 @@ namespace // Products in the millions: the build contracts and reassociates // the arithmetic, so the last bit or two of each is the // machine's, not the one the constants were written on. - { - INFO("2. LLQuaternion Operator* failed"); - CHECK(is_approx_equal_fraction(4543086.5f, result.mQ[0], 2)); - CHECK(is_approx_equal_fraction(8567578.0f, result.mQ[1], 2)); - CHECK(is_approx_equal_fraction(3967591.25f, result.mQ[2], 2)); - CHECK(is_approx_equal_fraction(-2047783.25f, result.mQ[3], 2)); - } + CHECK(is_approx_equal_fraction(4543086.5f, result.mQ[0], 2)); + CHECK(is_approx_equal_fraction(8567578.0f, result.mQ[1], 2)); + CHECK(is_approx_equal_fraction(3967591.25f, result.mQ[2], 2)); + CHECK(is_approx_equal_fraction(-2047783.25f, result.mQ[3], 2)); //inline LLQuaternion operator+(const LLQuaternion &a, const LLQuaternion &b)fn. result = quat1 + quat2; - { - INFO("3. LLQuaternion operator+ failed"); - CHECK(result.mQ[0] == 5.0f); - CHECK(result.mQ[1] == 5.5f); - CHECK(result.mQ[2] == 8.5f); - CHECK(result.mQ[3] == 6.5f); - } + CHECK(result.mQ[0] == 5.0f); + CHECK(result.mQ[1] == 5.5f); + CHECK(result.mQ[2] == 8.5f); + CHECK(result.mQ[3] == 6.5f); result = quat3 + quat4; - { - INFO("4. LLQuaternion operator+ failed"); - CHECK(is_approx_equal(3015.407227f, result.mQ[0])); - CHECK(is_approx_equal(3245.434570f, result.mQ[1])); - CHECK(result.mQ[2] == 811.25f); - CHECK(result.mQ[3] == 7876.5f); - } + CHECK(is_approx_equal(3015.407227f, result.mQ[0])); + CHECK(is_approx_equal(3245.434570f, result.mQ[1])); + CHECK(result.mQ[2] == 811.25f); + CHECK(result.mQ[3] == 7876.5f); //inline LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) fn result = quat1 - quat2; - { - INFO("5. LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) failed"); - CHECK(result.mQ[0] == -3.0f); - CHECK(result.mQ[1] == -0.5f); - CHECK(result.mQ[2] == -1.5f); - CHECK(result.mQ[3] == 4.5f); - } + CHECK(result.mQ[0] == -3.0f); + CHECK(result.mQ[1] == -0.5f); + CHECK(result.mQ[2] == -1.5f); + CHECK(result.mQ[3] == 4.5f); result = quat3 - quat4; - { - INFO("6. LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) failed"); - CHECK(is_approx_equal(1667.273071f, result.mQ[0])); - CHECK(is_approx_equal(1459.255249f, result.mQ[1])); - CHECK(result.mQ[2] == -344.75f); - CHECK(result.mQ[3] == 7414.50f); - } + CHECK(is_approx_equal(1667.273071f, result.mQ[0])); + CHECK(is_approx_equal(1459.255249f, result.mQ[1])); + CHECK(result.mQ[2] == -344.75f); + CHECK(result.mQ[3] == 7414.50f); } //test case for LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) fn. @@ -386,25 +318,19 @@ namespace LLVector4 vect(12.0f, 5.0f, 60.0f, 75.1f); LLQuaternion quat(2323.034f, 23.5f, 673.23f, 57667.5f); LLVector4 result = vect * quat; - { - INFO("1. LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) failed"); - CHECK(is_approx_equal(39928406016.0f, result.mV[0])); - CHECK(1457802240.0f >= result.mV[1]); - CHECK(1457800960.0f <= result.mV[1]); - CHECK(is_approx_equal(200580612096.0f, result.mV[2])); - CHECK(result.mV[3] == 75.099998f); - } + CHECK(is_approx_equal(39928406016.0f, result.mV[0])); + CHECK(1457802240.0f >= result.mV[1]); + CHECK(1457800960.0f <= result.mV[1]); + CHECK(is_approx_equal(200580612096.0f, result.mV[2])); + CHECK(result.mV[3] == 75.099998f); LLVector4 vect1(22.0f, 45.0f, 40.0f, 78.1f); LLQuaternion quat1(2.034f, 45.5f, 37.23f, 7.5f); result = vect1 * quat1; - { - INFO("2. LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) failed"); - CHECK(is_approx_equal(-58153.5390f, result.mV[0])); - CHECK(is_approx_equal(183787.8125f, result.mV[1])); - CHECK(is_approx_equal(116864.164063f, result.mV[2])); - CHECK(is_approx_equal(78.099998f, result.mV[3])); - } + CHECK(is_approx_equal(-58153.5390f, result.mV[0])); + CHECK(is_approx_equal(183787.8125f, result.mV[1])); + CHECK(is_approx_equal(116864.164063f, result.mV[2])); + CHECK(is_approx_equal(78.099998f, result.mV[3])); } //test case for LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) fn. @@ -413,22 +339,16 @@ namespace LLVector3 vect(12.0f, 5.0f, 60.0f); LLQuaternion quat(23.5f, 6.5f, 3.23f, 56.5f); LLVector3 result = vect * quat; - { - INFO("1. LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) failed"); - CHECK(is_approx_equal(97182.953125f,result.mV[0])); - CHECK(is_approx_equal(-135405.640625f, result.mV[1])); - CHECK(is_approx_equal(162986.140f, result.mV[2])); - } + CHECK(is_approx_equal(97182.953125f,result.mV[0])); + CHECK(is_approx_equal(-135405.640625f, result.mV[1])); + CHECK(is_approx_equal(162986.140f, result.mV[2])); LLVector3 vect1(5.0f, 40.0f, 78.1f); LLQuaternion quat1(2.034f, 45.5f, 37.23f, 7.5f); result = vect1 * quat1; - { - INFO("2. LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) failed"); - CHECK(is_approx_equal(33217.703f, result.mV[0])); - CHECK(is_approx_equal(295383.8125f, result.mV[1])); - CHECK(is_approx_equal(84718.140f, result.mV[2])); - } + CHECK(is_approx_equal(33217.703f, result.mV[0])); + CHECK(is_approx_equal(295383.8125f, result.mV[1])); + CHECK(is_approx_equal(84718.140f, result.mV[2])); } //test case for LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) fn. @@ -437,32 +357,23 @@ namespace LLVector3d vect(-2.0f, 5.0f, -6.0f); LLQuaternion quat(-3.5f, 4.5f, 3.5f, 6.5f); LLVector3d result = vect * quat; - { - INFO("1. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed "); - CHECK(result.mdV[0] == -633.0f); - CHECK(result.mdV[1] == -300.0f); - CHECK(result.mdV[2] == -36.0f); - } + CHECK(result.mdV[0] == -633.0f); + CHECK(result.mdV[1] == -300.0f); + CHECK(result.mdV[2] == -36.0f); LLVector3d vect1(5.0f, -4.5f, 8.21f); LLQuaternion quat1(2.0f, 4.5f, -7.2f, 9.5f); result = vect1 * quat1; - { - INFO("2. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed"); - CHECK(is_approx_equal_fraction(-120.29f, (F32) result.mdV[0], 8)); - CHECK(is_approx_equal_fraction(-1683.958f, (F32) result.mdV[1], 8)); - CHECK(is_approx_equal_fraction(516.56f, (F32) result.mdV[2], 8)); - } + CHECK(is_approx_equal_fraction(-120.29f, (F32) result.mdV[0], 8)); + CHECK(is_approx_equal_fraction(-1683.958f, (F32) result.mdV[1], 8)); + CHECK(is_approx_equal_fraction(516.56f, (F32) result.mdV[2], 8)); LLVector3d vect2(2.0f, 3.5f, 1.1f); LLQuaternion quat2(1.0f, 4.0f, 2.0f, 5.0f); result = vect2 * quat2; - { - INFO("3. LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) failed"); - CHECK(is_approx_equal_fraction(18.400001f, (F32) result.mdV[0], 8)); - CHECK(is_approx_equal_fraction(188.6f, (F32) result.mdV[1], 8)); - CHECK(is_approx_equal_fraction(32.20f, (F32) result.mdV[2], 8)); - } + CHECK(is_approx_equal_fraction(18.400001f, (F32) result.mdV[0], 8)); + CHECK(is_approx_equal_fraction(188.6f, (F32) result.mdV[1], 8)); + CHECK(is_approx_equal_fraction(32.20f, (F32) result.mdV[2], 8)); } //test case for inline LLQuaternion operator-(const LLQuaternion &a) fn. @@ -470,23 +381,17 @@ namespace { LLQuaternion quat(23.5f, 34.5f, 16723.4f, 324.7f); LLQuaternion result = -quat; - { - INFO("1. LLQuaternion operator-(const LLQuaternion &a) failed"); - CHECK(result.mQ[0] == -23.5f); - CHECK(result.mQ[1] == -34.5f); - CHECK(result.mQ[2] == -16723.4f); - CHECK(result.mQ[3] == -324.7f); - } + CHECK(result.mQ[0] == -23.5f); + CHECK(result.mQ[1] == -34.5f); + CHECK(result.mQ[2] == -16723.4f); + CHECK(result.mQ[3] == -324.7f); LLQuaternion quat1(-3.5f, -34.5f, -16.4f, -154.7f); result = -quat1; - { - INFO("2. LLQuaternion operator-(const LLQuaternion &a) failed."); - CHECK(result.mQ[0] == 3.5f); - CHECK(result.mQ[1] == 34.5f); - CHECK(result.mQ[2] == 16.4f); - CHECK(result.mQ[3] == 154.7f); - } + CHECK(result.mQ[0] == 3.5f); + CHECK(result.mQ[1] == 34.5f); + CHECK(result.mQ[2] == 16.4f); + CHECK(result.mQ[3] == 154.7f); } //test case for inline LLQuaternion operator*(F32 a, const LLQuaternion &q) and @@ -498,13 +403,10 @@ namespace LLQuaternion result = a * quat_value; LLQuaternion result1 = quat_value * a; - { - INFO("1. LLQuaternion operator* failed"); - CHECK(result.mQ[0] == result1.mQ[0]); - CHECK(result.mQ[1] == result1.mQ[1]); - CHECK(result.mQ[2] == result1.mQ[2]); - CHECK(result.mQ[3] == result1.mQ[3]); - } + CHECK(result.mQ[0] == result1.mQ[0]); + CHECK(result.mQ[1] == result1.mQ[1]); + CHECK(result.mQ[2] == result1.mQ[2]); + CHECK(result.mQ[3] == result1.mQ[3]); LLQuaternion quat_val(9454.0f, 43568.3450f, 456343247.0343f, 2346.03434f); @@ -512,13 +414,10 @@ namespace result = a * quat_val; result1 = quat_val * a; - { - INFO("2. LLQuaternion operator* failed"); - CHECK(result.mQ[0] == result1.mQ[0]); - CHECK(result.mQ[1] == result1.mQ[1]); - CHECK(result.mQ[2] == result1.mQ[2]); - CHECK(result.mQ[3] == result1.mQ[3]); - } + CHECK(result.mQ[0] == result1.mQ[0]); + CHECK(result.mQ[1] == result1.mQ[1]); + CHECK(result.mQ[2] == result1.mQ[2]); + CHECK(result.mQ[3] == result1.mQ[3]); } TEST_CASE("LLQuaternion: operator~ conjugates and operator== matches equal quaternions", "[LLQuaternion]") @@ -526,18 +425,14 @@ namespace // test cases for inline LLQuaternion operator~(const LLQuaternion &a) LLQuaternion quat_val(2323.634f, -43535.4f, 3455.88f, -32232.45f); LLQuaternion result = ~quat_val; - { - INFO("1. LLQuaternion operator~(const LLQuaternion &a) failed "); - CHECK(result.mQ[0] == -2323.634f); - CHECK(result.mQ[1] == 43535.4f); - CHECK(result.mQ[2] == -3455.88f); - CHECK(result.mQ[3] == -32232.45f); - } + CHECK(result.mQ[0] == -2323.634f); + CHECK(result.mQ[1] == 43535.4f); + CHECK(result.mQ[2] == -3455.88f); + CHECK(result.mQ[3] == -32232.45f); //test case for inline bool LLQuaternion::operator==(const LLQuaternion &b) const LLQuaternion quat_val1(2323.634f, -43535.4f, 3455.88f, -32232.45f); LLQuaternion quat_val2(2323.634f, -43535.4f, 3455.88f, -32232.45f); - UNSCOPED_INFO("2. LLQuaternion::operator==(const LLQuaternion &b) failed"); CHECK(quat_val1 == quat_val2); } @@ -546,7 +441,6 @@ namespace //test case for inline bool LLQuaternion::operator!=(const LLQuaternion &b) const LLQuaternion quat_val1(2323.634f, -43535.4f, 3455.88f, -32232.45f); LLQuaternion quat_val2(0, -43535.4f, 3455.88f, -32232.45f); - UNSCOPED_INFO("LLQuaternion::operator!=(const LLQuaternion &b) failed"); CHECK(quat_val1 != quat_val2); } @@ -558,58 +452,40 @@ namespace F32 z = 3.0f; LLQuaternion result = mayaQ(x, y, z, LLQuaternion::XYZ); - { - INFO("1. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XYZ"); - CHECK(is_approx_equal_fraction(0.0172174f, result.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.009179f, result.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.026020f, result.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.999471f, result.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.0172174f, result.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.009179f, result.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026020f, result.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999471f, result.mQ[3], 16)); LLQuaternion result1 = mayaQ(x, y, z, LLQuaternion::YZX); - { - INFO("2. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XYZ"); - CHECK(is_approx_equal_fraction(0.017217f, result1.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.008265f, result1.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.026324f, result1.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.999471f, result1.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.017217f, result1.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.008265f, result1.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026324f, result1.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999471f, result1.mQ[3], 16)); LLQuaternion result2 = mayaQ(x, y, z, LLQuaternion::ZXY); - { - INFO("3. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for ZXY"); - CHECK(is_approx_equal_fraction(0.017674f, result2.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.008265f, result2.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.026020f, result2.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.999471f, result2.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.017674f, result2.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.008265f, result2.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026020f, result2.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999471f, result2.mQ[3], 16)); LLQuaternion result3 = mayaQ(x, y, z, LLQuaternion::XZY); - { - INFO("4. TLLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for XZY"); - CHECK(is_approx_equal_fraction(0.017674f, result3.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.009179f, result3.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.026020f, result3.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.999463f, result3.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.017674f, result3.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.009179f, result3.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026020f, result3.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999463f, result3.mQ[3], 16)); LLQuaternion result4 = mayaQ(x, y, z, LLQuaternion::YXZ); - { - INFO("5. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for YXZ"); - CHECK(is_approx_equal_fraction(0.017217f, result4.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.009179f, result4.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.026324f, result4.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.999463f, result4.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.017217f, result4.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.009179f, result4.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026324f, result4.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999463f, result4.mQ[3], 16)); LLQuaternion result5 = mayaQ(x, y, z, LLQuaternion::ZYX); - { - INFO("6. LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) failed for ZYX"); - CHECK(is_approx_equal_fraction(0.017674f, result5.mQ[0], 16)); - CHECK(is_approx_equal_fraction(0.008265f, result5.mQ[1], 16)); - CHECK(is_approx_equal_fraction(0.026324f, result5.mQ[2], 16)); - CHECK(is_approx_equal_fraction(0.999463f, result5.mQ[3], 16)); - } + CHECK(is_approx_equal_fraction(0.017674f, result5.mQ[0], 16)); + CHECK(is_approx_equal_fraction(0.008265f, result5.mQ[1], 16)); + CHECK(is_approx_equal_fraction(0.026324f, result5.mQ[2], 16)); + CHECK(is_approx_equal_fraction(0.999463f, result5.mQ[3], 16)); } TEST_CASE("LLQuaternion: operator<< prints the components in braces", "[LLQuaternion]") @@ -618,18 +494,15 @@ namespace LLQuaternion a(1.0f, 1.0f, 1.0f, 1.0f); std::ostringstream result_value; result_value << a; - UNSCOPED_INFO("1. Operator << failed"); CHECK(result_value.str() == "{ 1, 1, 1, 1 }"); LLQuaternion b(-31.034f, 231.2340f, 3451.344320f, -341.0f); std::ostringstream result_value1; result_value1 << b; - UNSCOPED_INFO("2. Operator << failed"); CHECK(result_value1.str() == "{ -31.034, 231.234, 3451.34, -341 }"); LLQuaternion c(1.0f, 2.2f, 3.3f, 4.4f); result_value << c; - UNSCOPED_INFO("3. Operator << failed"); CHECK(result_value.str() == "{ 1, 1, 1, 1 }{ 1, 2.2, 3.3, 4.4 }"); } @@ -638,33 +511,25 @@ namespace { //test case for const char *OrderToString( const LLQuaternion::Order order ) fn const char* result = OrderToString(LLQuaternion::XYZ); - UNSCOPED_INFO("1. OrderToString failed for XYZ"); CHECK(std::string_view(result) == "XYZ"); result = OrderToString(LLQuaternion::YZX); - UNSCOPED_INFO("2. OrderToString failed for YZX"); CHECK(std::string_view(result) == "YZX"); result = OrderToString(LLQuaternion::ZXY); - { - INFO("3. OrderToString failed for ZXY"); - CHECK(std::string_view(result) == "ZXY"); - CHECK(std::string_view(result) != "XYZ"); - CHECK(std::string_view(result) != "YXZ"); - CHECK(std::string_view(result) != "ZYX"); - CHECK(std::string_view(result) != "XYZ"); - } + CHECK(std::string_view(result) == "ZXY"); + CHECK(std::string_view(result) != "XYZ"); + CHECK(std::string_view(result) != "YXZ"); + CHECK(std::string_view(result) != "ZYX"); + CHECK(std::string_view(result) != "XYZ"); result = OrderToString(LLQuaternion::XZY); - UNSCOPED_INFO("4. OrderToString failed for XZY"); CHECK(std::string_view(result) == "XZY"); result = OrderToString(LLQuaternion::ZYX); - UNSCOPED_INFO("5. OrderToString failed for ZYX"); CHECK(std::string_view(result) == "ZYX"); result = OrderToString(LLQuaternion::YXZ); - UNSCOPED_INFO("6.OrderToString failed for YXZ"); CHECK(std::string_view(result) == "YXZ"); } @@ -672,27 +537,21 @@ namespace { //test case for LLQuaternion::Order StringToOrder( const char *str ) fn int result = StringToOrder("XYZ"); - UNSCOPED_INFO("1. LLQuaternion::Order StringToOrder(const char *str ) failed for XYZ"); CHECK(result == 0); result = StringToOrder("YZX"); - UNSCOPED_INFO("2. LLQuaternion::Order StringToOrder(const char *str) failed for YZX"); CHECK(result == 1); result = StringToOrder("ZXY"); - UNSCOPED_INFO("3. LLQuaternion::Order StringToOrder(const char *str) failed for ZXY"); CHECK(result == 2); result = StringToOrder("XZY"); - UNSCOPED_INFO("4. LLQuaternion::Order StringToOrder(const char *str) failed for XZY"); CHECK(result == 3); result = StringToOrder("YXZ"); - UNSCOPED_INFO("5. LLQuaternion::Order StringToOrder(const char *str) failed for YXZ"); CHECK(result == 4); result = StringToOrder("ZYX"); - UNSCOPED_INFO("6. LLQuaternion::Order StringToOrder(const char *str) failed for ZYX"); CHECK(result == 5); } @@ -704,13 +563,10 @@ namespace LLVector3 vect(12.0f, 4.0f, 1.0f); LLQuaternion llquat(angle_value, vect); llquat.getAngleAxis(&angle_value, vect); - { - INFO("LLQuaternion::getAngleAxis(F32* angle, LLVector3 &vec) failed"); - CHECK(is_approx_equal_fraction(2.035406f, angle_value, 16)); - CHECK(is_approx_equal_fraction(0.315244f, vect.mV[1], 16)); - CHECK(is_approx_equal_fraction(0.078811f, vect.mV[2], 16)); - CHECK(is_approx_equal_fraction(0.945733f, vect.mV[0], 16)); - } + CHECK(is_approx_equal_fraction(2.035406f, angle_value, 16)); + CHECK(is_approx_equal_fraction(0.315244f, vect.mV[1], 16)); + CHECK(is_approx_equal_fraction(0.078811f, vect.mV[2], 16)); + CHECK(is_approx_equal_fraction(0.945733f, vect.mV[0], 16)); } TEST_CASE("LLQuaternion: getEulerAngles leaves an identity quaternion unchanged", "[LLQuaternion]") @@ -722,16 +578,12 @@ namespace LLQuaternion llquat; llquat.getEulerAngles(&roll, &pitch, &yaw); - { - INFO("LLQuaternion::getEulerAngles(F32 *roll, F32 *pitch, F32 *yaw) failed"); - CHECK(is_approx_equal(0.000f, llquat.mQ[0])); - CHECK(is_approx_equal(0.000f, llquat.mQ[1])); - CHECK(is_approx_equal(0.000f, llquat.mQ[2])); - CHECK(is_approx_equal(1.000f, llquat.mQ[3])); - } + CHECK(is_approx_equal(0.000f, llquat.mQ[0])); + CHECK(is_approx_equal(0.000f, llquat.mQ[1])); + CHECK(is_approx_equal(0.000f, llquat.mQ[2])); + CHECK(is_approx_equal(1.000f, llquat.mQ[3])); } - // The product and the dot evaluate at compile time. TEST_CASE("LLQuaternion: product and dot evaluate at compile time", "[LLQuaternion]") { constexpr LLQuaternion half_turn(0.f, 0.f, 1.f, 0.f); diff --git a/indra/llmath/tests/llrect_test.cpp b/indra/llmath/tests/llrect_test.cpp index d15f96bc6d..72a8e22f5d 100644 --- a/indra/llmath/tests/llrect_test.cpp +++ b/indra/llmath/tests/llrect_test.cpp @@ -48,23 +48,14 @@ namespace // default constructor LLRect rect1; - UNSCOPED_INFO("Empty rect"); CHECK_THAT(rect1.getValue(), ll_test::LLSDEquals(zero)); - UNSCOPED_INFO("Empty rect left"); CHECK(rect1.mLeft == 0); - UNSCOPED_INFO("Empty rect top"); CHECK(rect1.mTop == 0); - UNSCOPED_INFO("Empty rect right"); CHECK(rect1.mRight == 0); - UNSCOPED_INFO("Empty rect bottom"); CHECK(rect1.mBottom == 0); - UNSCOPED_INFO("Empty rect width"); CHECK(rect1.getWidth() == 0); - UNSCOPED_INFO("Empty rect height"); CHECK(rect1.getHeight() == 0); - UNSCOPED_INFO("Empty rect centerx"); CHECK(rect1.getCenterX() == 0); - UNSCOPED_INFO("Empty rect centery"); CHECK(rect1.getCenterY() == 0); } @@ -78,23 +69,14 @@ namespace zerof.append(0.0f); zerof.append(0.0f); zerof.append(0.0f); zerof.append(0.0f); LLRectf rect2; - UNSCOPED_INFO("Empty rectf"); CHECK_THAT(rect2.getValue(), ll_test::LLSDEquals(zerof)); - UNSCOPED_INFO("Empty rectf left"); CHECK(rect2.mLeft == 0.0f); - UNSCOPED_INFO("Empty rectf top"); CHECK(rect2.mTop == 0.0f); - UNSCOPED_INFO("Empty rectf right"); CHECK(rect2.mRight == 0.0f); - UNSCOPED_INFO("Empty rectf bottom"); CHECK(rect2.mBottom == 0.0f); - UNSCOPED_INFO("Empty rectf width"); CHECK(rect2.getWidth() == 0.0f); - UNSCOPED_INFO("Empty rectf height"); CHECK(rect2.getHeight() == 0.0f); - UNSCOPED_INFO("Empty rectf centerx"); CHECK(rect2.getCenterX() == 0.0f); - UNSCOPED_INFO("Empty rectf centery"); CHECK(rect2.getCenterY() == 0.0f); } @@ -105,21 +87,13 @@ namespace // LLRect rect3(LLRect(1, 6, 7, 2)); - UNSCOPED_INFO("Default rect left"); CHECK(rect3.mLeft == 1); - UNSCOPED_INFO("Default rect top"); CHECK(rect3.mTop == 6); - UNSCOPED_INFO("Default rect right"); CHECK(rect3.mRight == 7); - UNSCOPED_INFO("Default rect bottom"); CHECK(rect3.mBottom == 2); - UNSCOPED_INFO("Default rect width"); CHECK(rect3.getWidth() == 6); - UNSCOPED_INFO("Default rect height"); CHECK(rect3.getHeight() == 4); - UNSCOPED_INFO("Default rect centerx"); CHECK(rect3.getCenterX() == 4); - UNSCOPED_INFO("Default rect centery"); CHECK(rect3.getCenterY() == 4); } @@ -130,21 +104,13 @@ namespace // LLRectf rect4(1.0f, 5.0f, 6.0f, 2.0f); - UNSCOPED_INFO("Default rectf left"); CHECK(rect4.mLeft == 1.0f); - UNSCOPED_INFO("Default rectf top"); CHECK(rect4.mTop == 5.0f); - UNSCOPED_INFO("Default rectf right"); CHECK(rect4.mRight == 6.0f); - UNSCOPED_INFO("Default rectf bottom"); CHECK(rect4.mBottom == 2.0f); - UNSCOPED_INFO("Default rectf width"); CHECK(rect4.getWidth() == 5.0f); - UNSCOPED_INFO("Default rectf height"); CHECK(rect4.getHeight() == 3.0f); - UNSCOPED_INFO("Default rectf centerx"); CHECK(rect4.getCenterX() == 3.5f); - UNSCOPED_INFO("Default rectf centery"); CHECK(rect4.getCenterY() == 3.5f); } @@ -157,21 +123,13 @@ namespace LLSD array; array.append(-1.0f); array.append(0.0f); array.append(0.0f); array.append(-1.0f); LLRectf rect5(array); - UNSCOPED_INFO("LLSD rectf left"); CHECK(rect5.mLeft == -1.0f); - UNSCOPED_INFO("LLSD rectf top"); CHECK(rect5.mTop == 0.0f); - UNSCOPED_INFO("LLSD rectf right"); CHECK(rect5.mRight == 0.0f); - UNSCOPED_INFO("LLSD rectf bottom"); CHECK(rect5.mBottom == -1.0f); - UNSCOPED_INFO("LLSD rectf width"); CHECK(rect5.getWidth() == 1.0f); - UNSCOPED_INFO("LLSD rectf height"); CHECK(rect5.getHeight() == 1.0f); - UNSCOPED_INFO("LLSD rectf centerx"); CHECK(rect5.getCenterX() == -0.5f); - UNSCOPED_INFO("LLSD rectf centery"); CHECK(rect5.getCenterY() == -0.5f); } @@ -187,21 +145,13 @@ namespace rectf.mTop = 1.0f; rectf.mRight = 1.0f; rectf.mBottom = -1.0f; - UNSCOPED_INFO("Member-set rectf left"); CHECK(rectf.mLeft == -1.0f); - UNSCOPED_INFO("Member-set rectf top"); CHECK(rectf.mTop == 1.0f); - UNSCOPED_INFO("Member-set rectf right"); CHECK(rectf.mRight == 1.0f); - UNSCOPED_INFO("Member-set rectf bottom"); CHECK(rectf.mBottom == -1.0f); - UNSCOPED_INFO("Member-set rectf width"); CHECK(rectf.getWidth() == 2.0f); - UNSCOPED_INFO("Member-set rectf height"); CHECK(rectf.getHeight() == 2.0f); - UNSCOPED_INFO("Member-set rectf centerx"); CHECK(rectf.getCenterX() == 0.0f); - UNSCOPED_INFO("Member-set rectf centery"); CHECK(rectf.getCenterY() == 0.0f); } @@ -216,21 +166,13 @@ namespace LLSD array; array.append(-1.0f); array.append(0.0f); array.append(0.0f); array.append(-1.0f); rectf.setValue(array); - UNSCOPED_INFO("setValue() rectf left"); CHECK(rectf.mLeft == -1.0f); - UNSCOPED_INFO("setValue() rectf top"); CHECK(rectf.mTop == 0.0f); - UNSCOPED_INFO("setValue() rectf right"); CHECK(rectf.mRight == 0.0f); - UNSCOPED_INFO("setValue() rectf bottom"); CHECK(rectf.mBottom == -1.0f); - UNSCOPED_INFO("setValue() rectf width"); CHECK(rectf.getWidth() == 1.0f); - UNSCOPED_INFO("setValue() rectf height"); CHECK(rectf.getHeight() == 1.0f); - UNSCOPED_INFO("setValue() rectf centerx"); CHECK(rectf.getCenterX() == -0.5f); - UNSCOPED_INFO("setValue() rectf centery"); CHECK(rectf.getCenterY() == -0.5f); } @@ -243,21 +185,13 @@ namespace LLRect rect; rect.set(10, 90, 70, 10); - UNSCOPED_INFO("set() rectf left"); CHECK(rect.mLeft == 10); - UNSCOPED_INFO("set() rectf top"); CHECK(rect.mTop == 90); - UNSCOPED_INFO("set() rectf right"); CHECK(rect.mRight == 70); - UNSCOPED_INFO("set() rectf bottom"); CHECK(rect.mBottom == 10); - UNSCOPED_INFO("set() rectf width"); CHECK(rect.getWidth() == 60); - UNSCOPED_INFO("set() rectf height"); CHECK(rect.getHeight() == 80); - UNSCOPED_INFO("set() rectf centerx"); CHECK(rect.getCenterX() == 40); - UNSCOPED_INFO("set() rectf centery"); CHECK(rect.getCenterY() == 50); } @@ -270,21 +204,13 @@ namespace LLRectf rectf; rectf.setOriginAndSize(0.0f, 0.0f, 2.0f, 1.0f); - UNSCOPED_INFO("setOriginAndSize() rectf left"); CHECK(rectf.mLeft == 0.0f); - UNSCOPED_INFO("setOriginAndSize() rectf top"); CHECK(rectf.mTop == 1.0f); - UNSCOPED_INFO("setOriginAndSize() rectf right"); CHECK(rectf.mRight == 2.0f); - UNSCOPED_INFO("setOriginAndSize() rectf bottom"); CHECK(rectf.mBottom == 0.0f); - UNSCOPED_INFO("setOriginAndSize() rectf width"); CHECK(rectf.getWidth() == 2.0f); - UNSCOPED_INFO("setOriginAndSize() rectf height"); CHECK(rectf.getHeight() == 1.0f); - UNSCOPED_INFO("setOriginAndSize() rectf centerx"); CHECK(rectf.getCenterX() == 1.0f); - UNSCOPED_INFO("setOriginAndSize() rectf centery"); CHECK(rectf.getCenterY() == 0.5f); } @@ -297,21 +223,13 @@ namespace LLRectf rectf; rectf.setLeftTopAndSize(0.0f, 0.0f, 2.0f, 1.0f); - UNSCOPED_INFO("setLeftTopAndSize() rectf left"); CHECK(rectf.mLeft == 0.0f); - UNSCOPED_INFO("setLeftTopAndSize() rectf top"); CHECK(rectf.mTop == 0.0f); - UNSCOPED_INFO("setLeftTopAndSize() rectf right"); CHECK(rectf.mRight == 2.0f); - UNSCOPED_INFO("setLeftTopAndSize() rectf bottom"); CHECK(rectf.mBottom == -1.0f); - UNSCOPED_INFO("setLeftTopAndSize() rectf width"); CHECK(rectf.getWidth() == 2.0f); - UNSCOPED_INFO("setLeftTopAndSize() rectf height"); CHECK(rectf.getHeight() == 1.0f); - UNSCOPED_INFO("setLeftTopAndSize() rectf centerx"); CHECK(rectf.getCenterX() == 1.0f); - UNSCOPED_INFO("setLeftTopAndSize() rectf centery"); CHECK(rectf.getCenterY() == -0.5f); } @@ -324,21 +242,13 @@ namespace LLRectf rectf; rectf.setCenterAndSize(0.0f, 0.0f, 2.0f, 1.0f); - UNSCOPED_INFO("setCenterAndSize() rectf left"); CHECK(rectf.mLeft == -1.0f); - UNSCOPED_INFO("setCenterAndSize() rectf top"); CHECK(rectf.mTop == 0.5f); - UNSCOPED_INFO("setCenterAndSize() rectf right"); CHECK(rectf.mRight == 1.0f); - UNSCOPED_INFO("setCenterAndSize() rectf bottom"); CHECK(rectf.mBottom == -0.5f); - UNSCOPED_INFO("setCenterAndSize() rectf width"); CHECK(rectf.getWidth() == 2.0f); - UNSCOPED_INFO("setCenterAndSize() rectf height"); CHECK(rectf.getHeight() == 1.0f); - UNSCOPED_INFO("setCenterAndSize() rectf centerx"); CHECK(rectf.getCenterX() == 0.0f); - UNSCOPED_INFO("setCenterAndSize() rectf centery"); CHECK(rectf.getCenterY() == 0.0f); } @@ -351,19 +261,17 @@ namespace LLRectf rectf; rectf.set(-1.0f, 1.0f, 1.0f, -1.0f); - UNSCOPED_INFO("BBox is valid"); CHECK(rectf.isValid()); rectf.mLeft = 2.0f; - UNSCOPED_INFO("BBox is not valid"); CHECK_FALSE(rectf.isValid()); rectf.makeValid(); - UNSCOPED_INFO("BBox forced valid"); + UNSCOPED_INFO("after makeValid()"); CHECK(rectf.isValid()); rectf.set(-1.0f, -1.0f, -1.0f, -1.0f); - UNSCOPED_INFO("BBox(0,0,0,0) is valid"); + UNSCOPED_INFO("zero-size rect"); CHECK(rectf.isValid()); } @@ -376,18 +284,16 @@ namespace LLRectf rectf; rectf.set(-1.0f, 1.0f, 1.0f, -1.0f); - UNSCOPED_INFO("BBox is not Null"); CHECK_FALSE(rectf.isEmpty()); - UNSCOPED_INFO("BBox notNull"); CHECK(rectf.notEmpty()); rectf.mLeft = 2.0f; rectf.makeValid(); - UNSCOPED_INFO("BBox is now Null"); + UNSCOPED_INFO("inverted rect after makeValid()"); CHECK(rectf.isEmpty()); rectf.set(-1.0f, -1.0f, -1.0f, -1.0f); - UNSCOPED_INFO("BBox(0,0,0,0) is Null"); + UNSCOPED_INFO("zero-size rect"); CHECK(rectf.isEmpty()); } @@ -402,14 +308,10 @@ namespace rect1.set(-1.0f, 1.0f, 1.0f, -1.0f); rect2.set(-1.0f, 0.9f, 1.0f, -1.0f); - UNSCOPED_INFO("rect1 == rect2 (false)"); CHECK_FALSE(rect1 == rect2); - UNSCOPED_INFO("rect1 != rect2 (true)"); CHECK(rect1 != rect2); - UNSCOPED_INFO("rect1 == rect1 (true)"); CHECK(rect1 == rect1); - UNSCOPED_INFO("rect1 != rect1 (false)"); CHECK_FALSE(rect1 != rect1); } @@ -421,9 +323,7 @@ namespace LLRectf rect1, rect2(rect1); - UNSCOPED_INFO("rect1 == rect2 (true)"); CHECK(rect1 == rect2); - UNSCOPED_INFO("rect1 != rect2 (false)"); CHECK_FALSE(rect1 != rect2); } @@ -511,14 +411,14 @@ namespace rect2.set(-2.0f, 2.0f, 0.0f, 0.0f); rect3 = rect1; rect3.unionWith(rect2); - UNSCOPED_INFO("union - overlap"); + UNSCOPED_INFO("overlapping rects"); CHECK(rect3 == LLRectf(-2.0f, 2.0f, 1.0f, -1.0f)); rect1.set(-1.0f, 1.0f, 1.0f, -1.0f); rect2.set(5.0f, 10.0f, 10.0f, 5.0f); rect3 = rect1; rect3.unionWith(rect2); - UNSCOPED_INFO("union - no overlap"); + UNSCOPED_INFO("disjoint rects"); CHECK(rect3 == LLRectf(-1.0f, 10.0f, 10.0f, -1.0f)); } @@ -541,14 +441,14 @@ namespace rect2.set(-2.0f, 2.0f, 0.0f, 0.0f); rect3 = rect1; rect3.intersectWith(rect2); - UNSCOPED_INFO("intersect - overlap"); + UNSCOPED_INFO("overlapping rects"); CHECK(rect3 == LLRectf(-1.0f, 1.0f, 0.0f, 0.0f)); rect1.set(-1.0f, 1.0f, 1.0f, -1.0f); rect2.set(5.0f, 10.0f, 10.0f, 5.0f); rect3 = rect1; rect3.intersectWith(rect2); - UNSCOPED_INFO("intersect - no overlap"); + UNSCOPED_INFO("disjoint rects"); CHECK(rect3.isEmpty()); } @@ -560,19 +460,12 @@ namespace LLRectf rect(1.0f, 3.0f, 3.0f, 1.0f); - UNSCOPED_INFO("(0,0) not in rect"); CHECK_FALSE(rect.pointInRect(0.0f, 0.0f)); - UNSCOPED_INFO("(2,2) in rect"); CHECK(rect.pointInRect(2.0f, 2.0f)); - UNSCOPED_INFO("(1,1) in rect"); CHECK(rect.pointInRect(1.0f, 1.0f)); - UNSCOPED_INFO("(3,3) not in rect"); CHECK_FALSE(rect.pointInRect(3.0f, 3.0f)); - UNSCOPED_INFO("(2.999,2.999) in rect"); CHECK(rect.pointInRect(2.999f, 2.999f)); - UNSCOPED_INFO("(2.999,3.0) not in rect"); CHECK_FALSE(rect.pointInRect(2.999f, 3.0f)); - UNSCOPED_INFO("(3.0,2.999) not in rect"); CHECK_FALSE(rect.pointInRect(3.0f, 2.999f)); } @@ -584,19 +477,12 @@ namespace LLRectf rect(1.0f, 3.0f, 3.0f, 1.0f); - UNSCOPED_INFO("(0,0) in local rect"); CHECK(rect.localPointInRect(0.0f, 0.0f)); - UNSCOPED_INFO("(-0.0001,-0.0001) not in local rect"); CHECK_FALSE(rect.localPointInRect(-0.0001f, -0.001f)); - UNSCOPED_INFO("(1,1) in local rect"); CHECK(rect.localPointInRect(1.0f, 1.0f)); - UNSCOPED_INFO("(2,2) not in local rect"); CHECK_FALSE(rect.localPointInRect(2.0f, 2.0f)); - UNSCOPED_INFO("(1.999,1.999) in local rect"); CHECK(rect.localPointInRect(1.999f, 1.999f)); - UNSCOPED_INFO("(1.999,2.0) not in local rect"); CHECK_FALSE(rect.localPointInRect(1.999f, 2.0f)); - UNSCOPED_INFO("(2.0,1.999) not in local rect"); CHECK_FALSE(rect.localPointInRect(2.0f, 1.999f)); } @@ -611,23 +497,23 @@ namespace x = 2.0f; y = 2.0f; rect.clampPointToRect(x, y); - UNSCOPED_INFO("clamp x-coord within rect"); + UNSCOPED_INFO("point inside rect"); CHECK(x == 2.0f); - UNSCOPED_INFO("clamp y-coord within rect"); + UNSCOPED_INFO("point inside rect"); CHECK(y == 2.0f); x = -100.0f; y = 100.0f; rect.clampPointToRect(x, y); - UNSCOPED_INFO("clamp x-coord outside rect"); + UNSCOPED_INFO("point outside rect"); CHECK(x == 1.0f); - UNSCOPED_INFO("clamp y-coord outside rect"); + UNSCOPED_INFO("point outside rect"); CHECK(y == 3.0f); x = 3.0f; y = 1.0f; rect.clampPointToRect(x, y); - UNSCOPED_INFO("clamp x-coord edge rect"); + UNSCOPED_INFO("point on the bottom-right corner"); CHECK(x == 3.0f); - UNSCOPED_INFO("clamp y-coord edge rect"); + UNSCOPED_INFO("point on the bottom-right corner"); CHECK(y == 1.0f); } } diff --git a/indra/llmath/tests/llsdutil_math_test.cpp b/indra/llmath/tests/llsdutil_math_test.cpp index 89154a480b..0314c32592 100644 --- a/indra/llmath/tests/llsdutil_math_test.cpp +++ b/indra/llmath/tests/llsdutil_math_test.cpp @@ -63,7 +63,6 @@ namespace // This test is supposed to succeed. Comparing to the // empty string ensures that if the test fails, it will // display the string received so we can tell what failed. - UNSCOPED_INFO("match"); REQUIRE(match == ""); } else @@ -72,7 +71,7 @@ namespace // the string 'match' will be empty, which doesn't tell us // much about which case went awry. So construct a more // detailed description string. - UNSCOPED_INFO(proto_key + " shouldn't match " + pi->first); + INFO(proto_key << " shouldn't match " << pi->first); REQUIRE_FALSE(match.empty()); } } @@ -85,18 +84,15 @@ namespace LLVector3 vec1(-1.0, 2.0, -3.0); sd = ll_sd_from_vector3(vec1); LLVector3 vec2 = ll_vector3_from_sd(sd); - UNSCOPED_INFO("vector3 -> sd -> vector3: 1"); CHECK(vec1 == vec2); LLVector3 vec3(sd); - UNSCOPED_INFO("vector3 -> sd -> vector3: 2"); CHECK(vec1 == vec3); sd.clear(); vec1.setVec(0., 0., 0.); sd = ll_sd_from_vector3(vec1); vec2 = ll_vector3_from_sd(sd); - UNSCOPED_INFO("vector3 -> sd -> vector3: 3"); CHECK(vec1 == vec2); sd.clear(); } @@ -107,11 +103,9 @@ namespace LLVector3d vec1((F64)(U64L(0xFEDCBA9876543210) << 2), -1., 0); sd = ll_sd_from_vector3d(vec1); LLVector3d vec2 = ll_vector3d_from_sd(sd); - UNSCOPED_INFO("vector3d -> sd -> vector3d: 1"); CHECK(vec1 == vec2); LLVector3d vec3(sd); - UNSCOPED_INFO("vector3d -> sd -> vector3d : 2"); CHECK(vec1 == vec3); } @@ -121,11 +115,9 @@ namespace LLVector2 vec((F32) -3., (F32) 4.2); sd = ll_sd_from_vector2(vec); LLVector2 vec1 = ll_vector2_from_sd(sd); - UNSCOPED_INFO("vector2 -> sd -> vector2"); CHECK(vec == vec1); LLSD sd2 = ll_sd_from_vector2(vec1); - UNSCOPED_INFO("sd -> vector2 -> sd: 2"); CHECK_THAT(sd, ll_test::LLSDEquals(sd2)); } @@ -135,11 +127,9 @@ namespace LLQuaternion quat((F32) 1., (F32) -0.98, (F32) 2.3, (F32) 0xffff); sd = ll_sd_from_quaternion(quat); LLQuaternion quat1 = ll_quaternion_from_sd(sd); - UNSCOPED_INFO("LLQuaternion -> sd -> LLQuaternion"); CHECK(quat == quat1); LLSD sd2 = ll_sd_from_quaternion(quat1); - UNSCOPED_INFO("sd -> LLQuaternion -> sd "); CHECK_THAT(sd, ll_test::LLSDEquals(sd2)); } @@ -149,11 +139,9 @@ namespace LLColor4 c(1.0f, 2.2f, 4.0f, 7.f); sd = ll_sd_from_color4(c); LLColor4 c1 =ll_color4_from_sd(sd); - UNSCOPED_INFO("LLColor4 -> sd -> LLColor4"); CHECK(c == c1); LLSD sd1 = ll_sd_from_color4(c1); - UNSCOPED_INFO("sd -> LLColor4 -> sd"); CHECK_THAT(sd, ll_test::LLSDEquals(sd1)); } } diff --git a/indra/llmath/tests/llvector4a_test.cpp b/indra/llmath/tests/llvector4a_test.cpp index c46fffb594..3479dc3f92 100644 --- a/indra/llmath/tests/llvector4a_test.cpp +++ b/indra/llmath/tests/llvector4a_test.cpp @@ -80,58 +80,59 @@ namespace return (U32)(d < 0 ? -d : d); } - void ensure_bits(const std::string& what, const LLVector4a& got, U32 x, U32 y, U32 z, U32 w) + void require_bits(const std::string& what, const LLVector4a& got, U32 x, U32 y, U32 z, U32 w) { + INFO(what); const U32 expected[4] = {x, y, z, w}; - for (int i = 0; i < 4; ++i) + for (int lane = 0; lane < 4; ++lane) { - UNSCOPED_INFO(what + " lane " + std::to_string(i)); - REQUIRE(bits_of(got[i]) == expected[i]); + CAPTURE(lane); + REQUIRE(bits_of(got[lane]) == expected[lane]); } } - void ensure_exact(const std::string& what, const LLVector4a& got, F32 x, F32 y, F32 z, F32 w) + void require_exact(const std::string& what, const LLVector4a& got, F32 x, F32 y, F32 z, F32 w) { - ensure_bits(what, got, bits_of(x), bits_of(y), bits_of(z), bits_of(w)); + require_bits(what, got, bits_of(x), bits_of(y), bits_of(z), bits_of(w)); } - void ensure_ulps(const std::string& what, F32 got, F32 expected, U32 ulps) + void require_ulps(const std::string& what, F32 got, F32 expected, U32 ulps) { const U32 d = ulps_between(got, expected); - UNSCOPED_INFO(what + ": " + precise(got) + " is " + std::to_string(d) + " ulps from " + precise(expected)); + INFO(what << ": " << precise(got) << " is " << d << " ulps from " << precise(expected)); REQUIRE(d <= ulps); } - void ensure_ulps(const std::string& what, const LLVector4a& got, const F32* expected, U32 ulps, int lanes = 4) + void require_ulps(const std::string& what, const LLVector4a& got, const F32* expected, U32 ulps, int lanes = 4) { - for (int i = 0; i < lanes; ++i) + for (int lane = 0; lane < lanes; ++lane) { - ensure_ulps(what + " lane " + std::to_string(i), got[i], expected[i], ulps); + CAPTURE(lane); + require_ulps(what, got[lane], expected[lane], ulps); } } - void ensure_relative(const std::string& what, F32 got, double expected, double relative) + void require_relative(const std::string& what, F32 got, double expected, double relative) { const double err = std::fabs((double)got - expected) / std::fabs(expected); - UNSCOPED_INFO(what + ": " + precise(got) + " is off " + precise(err) + " relative from " + precise(expected)); + INFO(what << ": " << precise(got) << " is off " << precise(err) << " relative from " << precise(expected)); REQUIRE(err <= relative); } - void ensure_relative(const std::string& what, const LLVector4a& got, const double* expected, double relative, int lanes = 4) + void require_relative(const std::string& what, const LLVector4a& got, const double* expected, double relative, int lanes = 4) { - for (int i = 0; i < lanes; ++i) + for (int lane = 0; lane < lanes; ++lane) { - ensure_relative(what + " lane " + std::to_string(i), got[i], expected[i], relative); + CAPTURE(lane); + require_relative(what, got[lane], expected[lane], relative); } } - void ensure_same_lanes(const std::string& what, const LLVector4a& v) + void require_same_lanes(const std::string& what, const LLVector4a& v) { - UNSCOPED_INFO(what + " lane 1"); + INFO(what); REQUIRE(bits_of(v[1]) == bits_of(v[0])); - UNSCOPED_INFO(what + " lane 2"); REQUIRE(bits_of(v[2]) == bits_of(v[0])); - UNSCOPED_INFO(what + " lane 3"); REQUIRE(bits_of(v[3]) == bits_of(v[0])); } @@ -185,21 +186,15 @@ namespace // Plain data of one register, and the two constants. TEST_CASE_METHOD(llvector4a_data, "LLVector4a: plain data of one register and the two constants", "[LLVector4a]") { - UNSCOPED_INFO("sizeof LLVector4a"); CHECK(sizeof(LLVector4a) == (size_t)16); - UNSCOPED_INFO("alignof LLVector4a"); CHECK(alignof(LLVector4a) == (size_t)16); - UNSCOPED_INFO("sizeof LLSimdScalar"); CHECK(sizeof(LLSimdScalar) == (size_t)16); - UNSCOPED_INFO("sizeof LLVector4Logical"); CHECK(sizeof(LLVector4Logical) == (size_t)16); - UNSCOPED_INFO("trivially copyable"); CHECK(std::is_trivially_copyable::value); - UNSCOPED_INFO("standard layout"); CHECK(std::is_standard_layout::value); - ensure_exact("zero", LLVector4a::getZero(), 0.f, 0.f, 0.f, 0.f); - ensure_exact("epsilon", LLVector4a::getEpsilon(), F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO); + require_exact("zero", LLVector4a::getZero(), 0.f, 0.f, 0.f, 0.f); + require_exact("epsilon", LLVector4a::getEpsilon(), F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO); } // Loads, stores, set, splat and the lane accessors move bits. @@ -209,71 +204,62 @@ namespace LLVector4a v; v.load4a(src); - ensure_bits("load4a", v, pa[0], pa[1], pa[2], pa[3]); + require_bits("load4a", v, pa[0], pa[1], pa[2], pa[3]); v.loadua(src + 1); - ensure_bits("loadua", v, pa[1], pa[2], pa[3], bits_of(99.f)); + require_bits("loadua", v, pa[1], pa[2], pa[3], bits_of(99.f)); v.load3(src + 1); - ensure_bits("load3", v, pa[1], pa[2], pa[3], 0u); + require_bits("load3", v, pa[1], pa[2], pa[3], 0u); alignas(16) F32 dst[4] = {1.f, 1.f, 1.f, 1.f}; a().store4a(dst); - UNSCOPED_INFO("store4a x"); CHECK(bits_of(dst[0]) == pa[0]); - UNSCOPED_INFO("store4a y"); CHECK(bits_of(dst[1]) == pa[1]); - UNSCOPED_INFO("store4a z"); CHECK(bits_of(dst[2]) == pa[2]); - UNSCOPED_INFO("store4a w"); CHECK(bits_of(dst[3]) == pa[3]); alignas(16) F32 copied[4]; LLVector4a::copy4a(copied, src); - UNSCOPED_INFO("copy4a"); CHECK(bits_of(copied[2]) == pa[2]); v.set(1.f, 2.f, 3.f); - ensure_exact("set defaults w to zero", v, 1.f, 2.f, 3.f, 0.f); + require_exact("set defaults w to zero", v, 1.f, 2.f, 3.f, 0.f); v.clear(); - ensure_exact("clear", v, 0.f, 0.f, 0.f, 0.f); + require_exact("clear", v, 0.f, 0.f, 0.f, 0.f); v.splat(float_of(pa[2])); - ensure_bits("splat(F32)", v, pa[2], pa[2], pa[2], pa[2]); + require_bits("splat(F32)", v, pa[2], pa[2], pa[2], pa[2]); v.splat<3>(a()); - ensure_bits("splat<3>", v, pa[3], pa[3], pa[3], pa[3]); + require_bits("splat<3>", v, pa[3], pa[3], pa[3], pa[3]); v.splat(a(), 1); - ensure_bits("splat(v, 1)", v, pa[1], pa[1], pa[1], pa[1]); + require_bits("splat(v, 1)", v, pa[1], pa[1], pa[1], pa[1]); const LLVector4a splatted(a().getScalarAt<2>()); - ensure_bits("LLVector4a(LLSimdScalar)", splatted, pa[2], pa[2], pa[2], pa[2]); + require_bits("LLVector4a(LLSimdScalar)", splatted, pa[2], pa[2], pa[2], pa[2]); v.splat(a().getScalarAt(3)); - ensure_bits("splat(LLSimdScalar) from getScalarAt(3)", v, pa[3], pa[3], pa[3], pa[3]); + require_bits("splat(LLSimdScalar) from getScalarAt(3)", v, pa[3], pa[3], pa[3], pa[3]); const LLVector4a one(1.f); - ensure_exact("explicit LLVector4a(F32)", one, 1.f, 1.f, 1.f, 1.f); + require_exact("explicit LLVector4a(F32)", one, 1.f, 1.f, 1.f, 1.f); const LLVector4a from_a = a(); - UNSCOPED_INFO("operator[] 2"); CHECK(bits_of(from_a[2]) == pa[2]); - UNSCOPED_INFO("getScalarAt<1>"); CHECK(bits_of(from_a.getScalarAt<1>().getF32()) == pa[1]); - UNSCOPED_INFO("getScalarAt(0)"); CHECK(bits_of(from_a.getScalarAt(0).getF32()) == pa[0]); LLVector4a written = a(); written.getF32ptr()[1] = 7.f; - ensure_bits("getF32ptr writes through", written, pa[0], bits_of(7.f), pa[2], pa[3]); - UNSCOPED_INFO("getF32ptr reads"); + require_bits("getF32ptr writes through", written, pa[0], bits_of(7.f), pa[2], pa[3]); CHECK(bits_of(written.getF32ptr()[3]) == pa[3]); LLVector4a copy; copy = (LLQuad)a(); - ensure_bits("operator=(LLQuad)", copy, pa[0], pa[1], pa[2], pa[3]); + require_bits("operator=(LLQuad)", copy, pa[0], pa[1], pa[2], pa[3]); } // The mask type: compares, gathered bits, any/all, invert, the elements. @@ -286,56 +272,37 @@ namespace const LLVector4a y(2.f, 2.f, 2.f, nan_lane); const LLVector4a y_again(2.f, 2.f, 2.f, nan_lane); - UNSCOPED_INFO("greaterThan"); CHECK(x.greaterThan(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_Z)); - UNSCOPED_INFO("lessThan"); CHECK(x.lessThan(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_X)); - UNSCOPED_INFO("greaterEqual"); CHECK(x.greaterEqual(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_Y | LLVector4Logical::MASK_Z)); - UNSCOPED_INFO("lessEqual"); CHECK(x.lessEqual(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_Y)); - UNSCOPED_INFO("equal"); CHECK(x.equal(y).getGatheredBits() == (U32)(LLVector4Logical::MASK_Y)); UNSCOPED_INFO("NaN compares equal to nothing"); CHECK(y.equal(y_again).getGatheredBits() == (U32)(LLVector4Logical::MASK_XYZ)); LLVector4Logical m = x.greaterEqual(y); - UNSCOPED_INFO("areAnySet"); CHECK(m.areAnySet()); - UNSCOPED_INFO("areAnySet(MASK_XYZ)"); CHECK(m.areAnySet(LLVector4Logical::MASK_XYZ)); - UNSCOPED_INFO("areAnySet(MASK_X) false"); CHECK_FALSE(m.areAnySet(LLVector4Logical::MASK_X)); - UNSCOPED_INFO("areAnySet(MASK_W) false"); CHECK_FALSE(m.areAnySet(LLVector4Logical::MASK_W)); - UNSCOPED_INFO("areAllSet false"); CHECK_FALSE(m.areAllSet()); - UNSCOPED_INFO("areAllSet(MASK_XYZ) false"); CHECK_FALSE(m.areAllSet(LLVector4Logical::MASK_XYZ)); - UNSCOPED_INFO("areAllSet(Y|Z)"); CHECK(m.areAllSet(LLVector4Logical::MASK_Y | LLVector4Logical::MASK_Z)); m.invert(); - UNSCOPED_INFO("invert"); CHECK(m.getGatheredBits() == (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_W)); LLVector4Logical built; built.clear(); - UNSCOPED_INFO("clear"); CHECK(built.getGatheredBits() == 0u); - UNSCOPED_INFO("clear areAnySet"); CHECK_FALSE(built.areAnySet()); built.setElement<0>(); built.setElement<2>(); - UNSCOPED_INFO("setElement"); CHECK(built.getGatheredBits() == (U32)(LLVector4Logical::MASK_X | LLVector4Logical::MASK_Z)); built.setElement<1>(); - UNSCOPED_INFO("areAllSet(MASK_XYZ)"); CHECK(built.areAllSet(LLVector4Logical::MASK_XYZ)); - UNSCOPED_INFO("areAllSet still false"); CHECK_FALSE(built.areAllSet()); built.setElement<3>(); - UNSCOPED_INFO("areAllSet"); CHECK(built.areAllSet()); // select takes whole lanes, bits and all @@ -345,7 +312,7 @@ namespace pick.setElement<3>(); LLVector4a chosen; chosen.setSelectWithMask(pick, a(), b()); - ensure_bits("setSelectWithMask", chosen, pb[0], pa[1], pb[2], pa[3]); + require_bits("setSelectWithMask", chosen, pb[0], pa[1], pb[2], pa[3]); } // Lane arithmetic is one rounding per lane, so it matches scalar float @@ -360,49 +327,49 @@ namespace r.setAdd(p, q); for (int i = 0; i < 4; ++i) { volatile F32 e = na[i] + nb[i]; expected[i] = e; } - ensure_exact("setAdd", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("setAdd", r, expected[0], expected[1], expected[2], expected[3]); r = p; r.add(q); - ensure_exact("add", r, expected[0], expected[1], expected[2], expected[3]); - ensure_exact("operator+", p + q, expected[0], expected[1], expected[2], expected[3]); + require_exact("add", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("operator+", p + q, expected[0], expected[1], expected[2], expected[3]); r = p; r += q; - ensure_exact("operator+=", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("operator+=", r, expected[0], expected[1], expected[2], expected[3]); r.setSub(p, q); for (int i = 0; i < 4; ++i) { volatile F32 e = na[i] - nb[i]; expected[i] = e; } - ensure_exact("setSub", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("setSub", r, expected[0], expected[1], expected[2], expected[3]); r = p; r.sub(q); - ensure_exact("sub", r, expected[0], expected[1], expected[2], expected[3]); - ensure_exact("operator-", p - q, expected[0], expected[1], expected[2], expected[3]); + require_exact("sub", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("operator-", p - q, expected[0], expected[1], expected[2], expected[3]); r = p; r -= q; - ensure_exact("operator-=", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("operator-=", r, expected[0], expected[1], expected[2], expected[3]); r.setMul(p, q); for (int i = 0; i < 4; ++i) { volatile F32 e = na[i] * nb[i]; expected[i] = e; } - ensure_exact("setMul", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("setMul", r, expected[0], expected[1], expected[2], expected[3]); r = p; r.mul(q); - ensure_exact("mul", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("mul", r, expected[0], expected[1], expected[2], expected[3]); r = p; r.mul(-3.f); for (int i = 0; i < 4; ++i) { volatile F32 e = na[i] * -3.f; expected[i] = e; } - ensure_exact("mul(F32)", r, expected[0], expected[1], expected[2], expected[3]); + require_exact("mul(F32)", r, expected[0], expected[1], expected[2], expected[3]); r.setDiv(p, q); for (int i = 0; i < 4; ++i) { volatile F32 e = na[i] / nb[i]; expected[i] = e; } - ensure_ulps("setDiv", r, expected, 1); + require_ulps("setDiv", r, expected, 1); r = p; r.div(q); - ensure_ulps("div", r, expected, 1); + require_ulps("div", r, expected, 1); // The negative zero as its bits, in and out: /fp:fast lets the // compiler take -0.f for 0.f, and MSVC for arm64 does. const LLVector4a signs(alsimd::or_(alsimd::set(-1.5f, 0.f, 0.f, -INF), alsimd::and_(alsimd::set1_bits(0x80000000u), alsimd::mask_lane<2>()))); r.setAbs(signs); - ensure_bits("setAbs", r, bits_of(1.5f), 0u, 0u, bits_of(INF)); + require_bits("setAbs", r, bits_of(1.5f), 0u, 0u, bits_of(INF)); r.setNeg(signs); - ensure_bits("setNeg", r, bits_of(1.5f), 0x80000000u, 0u, bits_of(INF)); + require_bits("setNeg", r, bits_of(1.5f), 0x80000000u, 0u, bits_of(INF)); r = signs; r.negate(); - ensure_bits("negate", r, bits_of(1.5f), 0x80000000u, 0u, bits_of(INF)); + require_bits("negate", r, bits_of(1.5f), 0x80000000u, 0u, bits_of(INF)); // a NaN payload survives the sign ops unchanged r.setAbs(a()); @@ -416,7 +383,6 @@ namespace // sets its own threads to const LLVector4a tiny(DENORMAL, DENORMAL, 0.f, 0.f); r.setAdd(tiny, tiny); - UNSCOPED_INFO("denormal add"); CHECK(bits_of(r[0]) == bits_of(DENORMAL) * 2); } @@ -429,27 +395,27 @@ namespace LLVector4a r; r.setAllDot3(p, q); - ensure_same_lanes("setAllDot3", r); - ensure_relative("setAllDot3", r[0], dot3_exact(na, nb), 4e-7); - ensure_ulps("setAllDot3 vs scalar", r[0], dot3_ref(na, nb), 2); + require_same_lanes("setAllDot3", r); + require_relative("setAllDot3", r[0], dot3_exact(na, nb), 4e-7); + require_ulps("setAllDot3 vs scalar", r[0], dot3_ref(na, nb), 2); r.setAllDot4(p, q); - ensure_same_lanes("setAllDot4", r); - ensure_relative("setAllDot4", r[0], dot4_exact(na, nb), 4e-7); + require_same_lanes("setAllDot4", r); + require_relative("setAllDot4", r[0], dot4_exact(na, nb), 4e-7); - ensure_relative("dot3", p.dot3(q).getF32(), dot3_exact(na, nb), 4e-7); - ensure_relative("dot4", p.dot4(q).getF32(), dot4_exact(na, nb), 4e-7); + require_relative("dot3", p.dot3(q).getF32(), dot3_exact(na, nb), 4e-7); + require_relative("dot4", p.dot4(q).getF32(), dot4_exact(na, nb), 4e-7); // w does not reach the three-lane forms const LLVector4a p_w(na[0], na[1], na[2], 1e30f); - ensure_relative("dot3 ignores w", p_w.dot3(q).getF32(), dot3_exact(na, nb), 4e-7); + require_relative("dot3 ignores w", p_w.dot3(q).getF32(), dot3_exact(na, nb), 4e-7); // a length, both ways const LLVector4a v(3.f, 4.f, 12.f, 100.f); - ensure_ulps("getLength3", v.getLength3().getF32(), 13.f, 1); + require_ulps("getLength3", v.getLength3().getF32(), 13.f, 1); r.setAllLength3(v); - ensure_same_lanes("setAllLength3", r); - ensure_ulps("setAllLength3", r[0], 13.f, 1); + require_same_lanes("setAllLength3", r); + require_ulps("setAllLength3", r[0], 13.f, 1); } // The cross product against double, within the rounding the fused form @@ -468,10 +434,9 @@ namespace LLVector4a r; r.setCross3(p, q); - ensure_relative("setCross3", r, expected, 4e-7, 3); - UNSCOPED_INFO("setCross3 w is zero"); + require_relative("setCross3", r, expected, 4e-7, 3); CHECK(r[3] == 0.f); - ensure_relative("cross3", p.cross3(q), expected, 4e-7, 3); + require_relative("cross3", p.cross3(q), expected, 4e-7, 3); // perpendicular to both, and antisymmetric UNSCOPED_INFO("cross is perpendicular to a"); @@ -486,7 +451,7 @@ namespace // the basis r.setCross3(LLVector4a(1.f, 0.f, 0.f), LLVector4a(0.f, 1.f, 0.f)); - ensure_exact("x cross y is z", r, 0.f, 0.f, 1.f, 0.f); + require_exact("x cross y is z", r, 0.f, 0.f, 1.f, 0.f); } // The normalize family: the full-precision forms within an ulp or two of @@ -502,42 +467,37 @@ namespace LLVector4a v(raw[0], raw[1], raw[2], raw[3]); v.normalize3(); - ensure_relative("normalize3", v, n3, 4e-7, 3); - UNSCOPED_INFO("normalize3 result isNormalized3"); + require_relative("normalize3", v, n3, 4e-7, 3); CHECK(v.isNormalized3()); - UNSCOPED_INFO("normalize3 result isNormalized3 tight"); CHECK(v.isNormalized3(1e-6f)); v.set(raw[0], raw[1], raw[2], raw[3]); v.normalize4(); - ensure_relative("normalize4", v, n4, 4e-7); - UNSCOPED_INFO("normalize4 result isNormalized4"); + require_relative("normalize4", v, n4, 4e-7); CHECK(v.isNormalized4(1e-6f)); v.set(raw[0], raw[1], raw[2], raw[3]); const F32 length = v.normalize3withLength().getF32(); - ensure_relative("normalize3withLength direction", v, n3, 4e-7, 3); - ensure_ulps("normalize3withLength length", length, 13.f, 1); + require_relative("normalize3withLength direction", v, n3, 4e-7, 3); + require_ulps("normalize3withLength length", length, 13.f, 1); v.set(raw[0], raw[1], raw[2], raw[3]); v.normalize3fast(); - ensure_relative("normalize3fast", v, n3, 1.0 / 2048.0, 3); + require_relative("normalize3fast", v, n3, 1.0 / 2048.0, 3); // large and small lengths, where an estimate's range matters const double third = 1.0 / std::sqrt(3.0); const double unit3[3] = {third, third, third}; v.set(1e18f, 1e18f, 1e18f); v.normalize3(); - ensure_relative("normalize3 of a large vector", v, unit3, 4e-7, 3); + require_relative("normalize3 of a large vector", v, unit3, 4e-7, 3); v.set(1e-18f, 1e-18f, 1e-18f); v.normalize3(); - ensure_relative("normalize3 of a small vector", v, unit3, 4e-7, 3); + require_relative("normalize3 of a small vector", v, unit3, 4e-7, 3); // isNormalized measures length, not squared length const LLVector4a off(1.0005f, 0.f, 0.f, 0.f); - UNSCOPED_INFO("isNormalized3 at the default tolerance"); CHECK(off.isNormalized3()); - UNSCOPED_INFO("isNormalized3 at a tight tolerance"); CHECK_FALSE(off.isNormalized3(1e-4f)); const LLVector4a off4(0.f, 0.f, 0.f, 1.0005f); UNSCOPED_INFO("isNormalized4 sees w"); @@ -548,16 +508,16 @@ namespace // the checked form LLVector4a checked(3.f, -4.f, 12.f, 0.f); checked.normalize3fast_checked(); - ensure_relative("normalize3fast_checked", checked, n3, 1.0 / 2048.0, 3); + require_relative("normalize3fast_checked", checked, n3, 1.0 / 2048.0, 3); checked.set(NAN_QUIET, 1.f, 1.f); checked.normalize3fast_checked(); - ensure_exact("normalize3fast_checked of a NaN is the default", checked, 0.f, 1.f, 0.f, 1.f); + require_exact("normalize3fast_checked of a NaN is the default", checked, 0.f, 1.f, 0.f, 1.f); checked.set(0.f, 0.f, 0.f); LLVector4a fallback(0.f, 0.f, 1.f, 0.f); checked.normalize3fast_checked(&fallback); - ensure_exact("normalize3fast_checked of zero is the fallback", checked, 0.f, 0.f, 1.f, 0.f); + require_exact("normalize3fast_checked of zero is the fallback", checked, 0.f, 0.f, 1.f, 0.f); } // min, max, clamp and lerp: the first two on numbers alone, clamp with @@ -569,54 +529,46 @@ namespace LLVector4a r; r.setMin(p, q); - ensure_exact("setMin", r, -0.75f, -2.25f, 1.0625f, -8.f); + require_exact("setMin", r, -0.75f, -2.25f, 1.0625f, -8.f); r.setMax(p, q); - ensure_exact("setMax", r, 1.5f, 4.f, 3.125f, 0.5f); + require_exact("setMax", r, 1.5f, 4.f, 3.125f, 0.5f); LLVector4a lo(-1.f, -1.f, -1.f, -1.f), hi(1.f, 1.f, 1.f, 1.f); update_min_max(lo, hi, p); - ensure_exact("update_min_max min", lo, -1.f, -2.25f, -1.f, -1.f); - ensure_exact("update_min_max max", hi, 1.5f, 1.f, 3.125f, 1.f); + require_exact("update_min_max min", lo, -1.f, -2.25f, -1.f, -1.f); + require_exact("update_min_max max", hi, 1.5f, 1.f, 3.125f, 1.f); const LLVector4a low(0.f, -1.f, -1.f, 0.f); const LLVector4a high(1.f, 1.f, 2.f, 0.f); r.set(-0.5f, 0.5f, 3.f, NAN_QUIET); r.clamp(low, high); - ensure_exact("clamp x y z", LLVector4a(r[0], r[1], r[2], 0.f), 0.f, 0.5f, 2.f, 0.f); + require_exact("clamp x y z", LLVector4a(r[0], r[1], r[2], 0.f), 0.f, 0.5f, 2.f, 0.f); UNSCOPED_INFO("clamp leaves a NaN lane NaN"); CHECK(std::isnan(r[3])); r.set(0.f, 1.f, -1.f, 0.f); r.clamp(low, high); - ensure_exact("clamp at the bounds", r, 0.f, 1.f, -1.f, 0.f); + require_exact("clamp at the bounds", r, 0.f, 1.f, -1.f, 0.f); r.setLerp(p, q, 0.f); - ensure_exact("setLerp at 0 is lhs", r, na[0], na[1], na[2], na[3]); + require_exact("setLerp at 0 is lhs", r, na[0], na[1], na[2], na[3]); r.setLerp(p, q, 1.f); - ensure_ulps("setLerp at 1 is rhs", r, nb, 1); + require_ulps("setLerp at 1 is rhs", r, nb, 1); r.setLerp(p, q, 0.25f); F32 expected[4]; for (int i = 0; i < 4; ++i) { expected[i] = (F32)((double)na[i] + ((double)nb[i] - na[i]) * 0.25); } - ensure_ulps("setLerp at a quarter", r, expected, 2); + require_ulps("setLerp at a quarter", r, expected, 2); } // Finite means every exponent short of all ones, in the lanes asked. TEST_CASE_METHOD(llvector4a_data, "LLVector4a: isFinite3 and isFinite4 reject infinity and NaN in their lanes", "[LLVector4a]") { - UNSCOPED_INFO("numbers are finite"); CHECK(n_a().isFinite4()); - UNSCOPED_INFO("a denormal is finite"); CHECK(LLVector4a(DENORMAL, 0.f, 0.f, 0.f).isFinite4()); - UNSCOPED_INFO("the largest float is finite"); CHECK(LLVector4a(std::numeric_limits::max(), 0.f, 0.f, 0.f).isFinite4()); - UNSCOPED_INFO("an infinity in x"); CHECK_FALSE(LLVector4a(INF, 0.f, 0.f, 0.f).isFinite3()); - UNSCOPED_INFO("a negative infinity in z"); CHECK_FALSE(LLVector4a(0.f, 0.f, -INF, 0.f).isFinite3()); - UNSCOPED_INFO("a NaN in y"); CHECK_FALSE(LLVector4a(0.f, NAN_QUIET, 0.f, 0.f).isFinite3()); - UNSCOPED_INFO("a NaN in w passes isFinite3"); CHECK(LLVector4a(0.f, 0.f, 0.f, NAN_QUIET).isFinite3()); - UNSCOPED_INFO("a NaN in w fails isFinite4"); CHECK_FALSE(LLVector4a(0.f, 0.f, 0.f, NAN_QUIET).isFinite4()); UNSCOPED_INFO("a NaN payload fails"); CHECK_FALSE(a().isFinite3()); @@ -628,11 +580,8 @@ namespace const LLVector4a p = n_a(); LLVector4a nearby = p; nearby.add(LLVector4a(1e-7f, -1e-7f, 1e-7f, 1e-7f)); - UNSCOPED_INFO("equals4 within the default tolerance"); CHECK(p.equals4(nearby)); - UNSCOPED_INFO("operator== within the default tolerance"); CHECK(p == nearby); - UNSCOPED_INFO("equals4 outside a tight tolerance"); CHECK_FALSE(p.equals4(nearby, 1e-8f)); LLVector4a w_off = p; @@ -641,15 +590,12 @@ namespace CHECK(p.equals3(w_off)); UNSCOPED_INFO("equals4 sees w"); CHECK_FALSE(p.equals4(w_off)); - UNSCOPED_INFO("operator!="); CHECK(p != w_off); // Held against another value rather than itself: /fp:fast lets the // compiler fold x - x to 0, so a vector equals itself whatever it holds. const LLVector4a nan(NAN_QUIET, 0.f, 0.f, 0.f); - UNSCOPED_INFO("a NaN equals nothing"); CHECK_FALSE(nan.equals4(LLVector4a::getZero())); - UNSCOPED_INFO("and nothing equals a NaN"); CHECK_FALSE(LLVector4a::getZero().equals4(nan)); } @@ -680,24 +626,16 @@ namespace for (int i = 0; i < 3; ++i) { const F32 clamped = llclamp(in[i], low[i], high[i]); - const std::string lane = " lane " + std::to_string(i) + " of " + precise(in[i]); - UNSCOPED_INFO("quantize8 within half a step" + lane); + INFO("lane " << i << " of " << precise(in[i])); REQUIRE(std::fabs(v8[i] - clamped) <= delta[i] / 255.f * 0.5f + 1e-6f); - UNSCOPED_INFO("quantize16 within half a step" + lane); REQUIRE(std::fabs(v16[i] - clamped) <= delta[i] / 65535.f * 0.5f + 1e-6f); // the way back multiplies by the step and by the range, so a // bound can come back an ulp past itself const F32 slack = 1e-6f * llmax(1.f, std::fabs(high[i])); - { - INFO("quantize8 within the range" + lane); - REQUIRE(v8[i] >= low[i] - slack); - REQUIRE(v8[i] <= high[i] + slack); - } - { - INFO("quantize16 within the range" + lane); - REQUIRE(v16[i] >= low[i] - slack); - REQUIRE(v16[i] <= high[i] + slack); - } + REQUIRE(v8[i] >= low[i] - slack); + REQUIRE(v8[i] <= high[i] + slack); + REQUIRE(v16[i] >= low[i] - slack); + REQUIRE(v16[i] <= high[i] + slack); } UNSCOPED_INFO("quantize8 flat channel is low"); REQUIRE(v8[3] == 5.f); @@ -717,11 +655,8 @@ namespace // a value within a step of zero becomes zero LLVector4a tiny(0.001f, -0.001f, 0.01f, 5.f); tiny.quantize8(low, high); - UNSCOPED_INFO("quantize8 snaps x to zero"); CHECK(bits_of(tiny[0]) == 0u); - UNSCOPED_INFO("quantize8 snaps y to zero"); CHECK(bits_of(tiny[1]) == 0u); - UNSCOPED_INFO("quantize8 snaps z to zero"); CHECK(bits_of(tiny[2]) == 0u); } @@ -765,20 +700,14 @@ namespace LLVector4a by_matrix; by_matrix.setRotated(rot, v); - UNSCOPED_INFO("setRotated(LLRotation) x"); REQUIRE(std::fabs(by_matrix[0] - expected.mV[0]) <= tolerance); - UNSCOPED_INFO("setRotated(LLRotation) y"); REQUIRE(std::fabs(by_matrix[1] - expected.mV[1]) <= tolerance); - UNSCOPED_INFO("setRotated(LLRotation) z"); REQUIRE(std::fabs(by_matrix[2] - expected.mV[2]) <= tolerance); LLVector4a by_quat; by_quat.setRotated(q2, v); - UNSCOPED_INFO("setRotated(LLQuaternion2) x"); REQUIRE(std::fabs(by_quat[0] - expected_q.mV[0]) <= tolerance); - UNSCOPED_INFO("setRotated(LLQuaternion2) y"); REQUIRE(std::fabs(by_quat[1] - expected_q.mV[1]) <= tolerance); - UNSCOPED_INFO("setRotated(LLQuaternion2) z"); REQUIRE(std::fabs(by_quat[2] - expected_q.mV[2]) <= tolerance); LLVector4a back; @@ -799,53 +728,36 @@ namespace { const LLSimdScalar three(3.f); const LLSimdScalar minus_two(-2.f); - UNSCOPED_INFO("getF32"); CHECK(three.getF32() == 3.f); - UNSCOPED_INFO("operator+"); CHECK((three + minus_two).getF32() == 1.f); - UNSCOPED_INFO("operator-"); CHECK((three - minus_two).getF32() == 5.f); - UNSCOPED_INFO("operator*"); CHECK((three * minus_two).getF32() == -6.f); - UNSCOPED_INFO("operator/"); CHECK((three / minus_two).getF32() == -1.5f); - UNSCOPED_INFO("unary minus"); CHECK((-three).getF32() == -3.f); - UNSCOPED_INFO("getAbs"); CHECK(minus_two.getAbs().getF32() == 2.f); - UNSCOPED_INFO("getZero"); CHECK(LLSimdScalar::getZero().getF32() == 0.f); LLSimdScalar s = three; - s += minus_two; UNSCOPED_INFO("+="); CHECK(s.getF32() == 1.f); - s -= minus_two; UNSCOPED_INFO("-="); CHECK(s.getF32() == 3.f); - s *= minus_two; UNSCOPED_INFO("*="); CHECK(s.getF32() == -6.f); - s /= three; UNSCOPED_INFO("/="); CHECK(s.getF32() == -2.f); - s = 7.f; UNSCOPED_INFO("="); CHECK(s.getF32() == 7.f); + s += minus_two; CHECK(s.getF32() == 1.f); + s -= minus_two; CHECK(s.getF32() == 3.f); + s *= minus_two; CHECK(s.getF32() == -6.f); + s /= three; CHECK(s.getF32() == -2.f); + s = 7.f; CHECK(s.getF32() == 7.f); LLSimdScalar mm; - mm.setMax(three, minus_two); UNSCOPED_INFO("setMax"); CHECK(mm.getF32() == 3.f); - mm.setMin(three, minus_two); UNSCOPED_INFO("setMin"); CHECK(mm.getF32() == -2.f); + mm.setMax(three, minus_two); CHECK(mm.getF32() == 3.f); + mm.setMin(three, minus_two); CHECK(mm.getF32() == -2.f); - UNSCOPED_INFO("<"); CHECK(minus_two < three); - UNSCOPED_INFO("<="); CHECK(three <= three); - UNSCOPED_INFO(">"); CHECK(three > minus_two); - UNSCOPED_INFO(">="); CHECK(minus_two >= minus_two); - UNSCOPED_INFO("=="); CHECK(three == LLSimdScalar(3.f)); - UNSCOPED_INFO("!="); CHECK(three != minus_two); - UNSCOPED_INFO("isApproximatelyEqual"); CHECK(three.isApproximatelyEqual(LLSimdScalar(3.f + 1e-6f))); - UNSCOPED_INFO("isApproximatelyEqual outside"); CHECK_FALSE(three.isApproximatelyEqual(minus_two)); // a dot product comes back in the lane that is read - UNSCOPED_INFO("a dot as scalar"); CHECK(n_a().dot3(LLVector4a(1.f, 0.f, 0.f)).getF32() == na[0]); } } diff --git a/indra/llmath/tests/m3math_test.cpp b/indra/llmath/tests/m3math_test.cpp index a105420f09..35b6ba611c 100644 --- a/indra/llmath/tests/m3math_test.cpp +++ b/indra/llmath/tests/m3math_test.cpp @@ -54,18 +54,15 @@ namespace { LLMatrix3 llmat3_obj; llmat3_obj.setIdentity(); - { - INFO("LLMatrix3::setIdentity failed"); - CHECK(llmat3_obj.mMatrix[0][0] == 1.f); - CHECK(llmat3_obj.mMatrix[0][1] == 0.f); - CHECK(llmat3_obj.mMatrix[0][2] == 0.f); - CHECK(llmat3_obj.mMatrix[1][0] == 0.f); - CHECK(llmat3_obj.mMatrix[1][1] == 1.f); - CHECK(llmat3_obj.mMatrix[1][2] == 0.f); - CHECK(llmat3_obj.mMatrix[2][0] == 0.f); - CHECK(llmat3_obj.mMatrix[2][1] == 0.f); - CHECK(llmat3_obj.mMatrix[2][2] == 1.f); - } + CHECK(llmat3_obj.mMatrix[0][0] == 1.f); + CHECK(llmat3_obj.mMatrix[0][1] == 0.f); + CHECK(llmat3_obj.mMatrix[0][2] == 0.f); + CHECK(llmat3_obj.mMatrix[1][0] == 0.f); + CHECK(llmat3_obj.mMatrix[1][1] == 1.f); + CHECK(llmat3_obj.mMatrix[1][2] == 0.f); + CHECK(llmat3_obj.mMatrix[2][0] == 0.f); + CHECK(llmat3_obj.mMatrix[2][1] == 0.f); + CHECK(llmat3_obj.mMatrix[2][2] == 1.f); } //test case for LLMatrix3& setZero() fn. @@ -74,18 +71,15 @@ namespace LLMatrix3 llmat3_obj; llmat3_obj.setZero(); - { - INFO("LLMatrix3::setZero failed"); - CHECK(llmat3_obj.setZero().mMatrix[0][0] == 0.f); - CHECK(llmat3_obj.setZero().mMatrix[0][1] == 0.f); - CHECK(llmat3_obj.setZero().mMatrix[0][2] == 0.f); - CHECK(llmat3_obj.setZero().mMatrix[1][0] == 0.f); - CHECK(llmat3_obj.setZero().mMatrix[1][1] == 0.f); - CHECK(llmat3_obj.setZero().mMatrix[1][2] == 0.f); - CHECK(llmat3_obj.setZero().mMatrix[2][0] == 0.f); - CHECK(llmat3_obj.setZero().mMatrix[2][1] == 0.f); - CHECK(llmat3_obj.setZero().mMatrix[2][2] == 0.f); - } + CHECK(llmat3_obj.setZero().mMatrix[0][0] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[0][1] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[0][2] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[1][0] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[1][1] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[1][2] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[2][0] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[2][1] == 0.f); + CHECK(llmat3_obj.setZero().mMatrix[2][2] == 0.f); } //test case for setRows(const LLVector3 &x_axis, const LLVector3 &y_axis, const LLVector3 &z_axis) fns. @@ -96,18 +90,15 @@ namespace LLVector3 vect2(3, 5, 7); LLVector3 vect3(6, 9, 7); llmat3_obj.setRows(vect1, vect2, vect3); - { - INFO("LLVector3::setRows failed "); - CHECK(llmat3_obj.mMatrix[0][0] == 2); - CHECK(llmat3_obj.mMatrix[0][1] == 1); - CHECK(llmat3_obj.mMatrix[0][2] == 4); - CHECK(llmat3_obj.mMatrix[1][0] == 3); - CHECK(llmat3_obj.mMatrix[1][1] == 5); - CHECK(llmat3_obj.mMatrix[1][2] == 7); - CHECK(llmat3_obj.mMatrix[2][0] == 6); - CHECK(llmat3_obj.mMatrix[2][1] == 9); - CHECK(llmat3_obj.mMatrix[2][2] == 7); - } + CHECK(llmat3_obj.mMatrix[0][0] == 2); + CHECK(llmat3_obj.mMatrix[0][1] == 1); + CHECK(llmat3_obj.mMatrix[0][2] == 4); + CHECK(llmat3_obj.mMatrix[1][0] == 3); + CHECK(llmat3_obj.mMatrix[1][1] == 5); + CHECK(llmat3_obj.mMatrix[1][2] == 7); + CHECK(llmat3_obj.mMatrix[2][0] == 6); + CHECK(llmat3_obj.mMatrix[2][1] == 9); + CHECK(llmat3_obj.mMatrix[2][2] == 7); } //test case for getFwdRow(), getLeftRow(), getUpRow() fns. @@ -119,11 +110,8 @@ namespace LLVector3 vect3(6, 9, 7); llmat3_obj.setRows(vect1, vect2, vect3); - UNSCOPED_INFO("LLVector3::getFwdRow failed "); CHECK(vect1 == llmat3_obj.getFwdRow()); - UNSCOPED_INFO("LLVector3::getLeftRow failed "); CHECK(vect2 == llmat3_obj.getLeftRow()); - UNSCOPED_INFO("LLVector3::getUpRow failed "); CHECK(vect3 == llmat3_obj.getUpRow()); } @@ -150,18 +138,15 @@ namespace llmat_obj2.setRows(llvec4, llvec5, llvec6); llmat_obj3.setRows(llvec7, llvec8, llvec9); llmat_obj3 = llmat_obj1 * llmat_obj2; - { - INFO("LLMatrix3::operator*(const LLMatrix3 &a, const LLMatrix3 &b) failed"); - CHECK(llmat_obj3.mMatrix[0][0] == 50); - CHECK(llmat_obj3.mMatrix[0][1] == 49); - CHECK(llmat_obj3.mMatrix[0][2] == 39); - CHECK(llmat_obj3.mMatrix[1][0] == 29); - CHECK(llmat_obj3.mMatrix[1][1] == 45); - CHECK(llmat_obj3.mMatrix[1][2] == 65); - CHECK(llmat_obj3.mMatrix[2][0] == 94); - CHECK(llmat_obj3.mMatrix[2][1] == 94); - CHECK(llmat_obj3.mMatrix[2][2] == 86); - } + CHECK(llmat_obj3.mMatrix[0][0] == 50); + CHECK(llmat_obj3.mMatrix[0][1] == 49); + CHECK(llmat_obj3.mMatrix[0][2] == 39); + CHECK(llmat_obj3.mMatrix[1][0] == 29); + CHECK(llmat_obj3.mMatrix[1][1] == 45); + CHECK(llmat_obj3.mMatrix[1][2] == 65); + CHECK(llmat_obj3.mMatrix[2][0] == 94); + CHECK(llmat_obj3.mMatrix[2][1] == 94); + CHECK(llmat_obj3.mMatrix[2][2] == 86); } @@ -182,7 +167,6 @@ namespace LLVector3 expected_result(30, 51, 53); - UNSCOPED_INFO("LLMatrix3::operator*(const LLVector3 &a, const LLMatrix3 &b) failed"); CHECK(res_vec == expected_result); } @@ -202,7 +186,6 @@ namespace LLVector3d expected_result(25, 30, 3); - UNSCOPED_INFO("LLMatrix3::operator*(const LLVector3 &a, const LLMatrix3 &b) failed"); CHECK(llvec3d1 == expected_result); } @@ -218,11 +201,9 @@ namespace llmat_obj1.setRows(llvec1, llvec2, llvec3); llmat_obj2.setRows(llvec1, llvec2, llvec3); - UNSCOPED_INFO("LLMatrix3::operator==(const LLMatrix3 &a, const LLMatrix3 &b) failed"); CHECK(llmat_obj1 == llmat_obj2); llmat_obj2.setRows(llvec2, llvec2, llvec3); - UNSCOPED_INFO("LLMatrix3::operator!=(const LLMatrix3 &a, const LLMatrix3 &b) failed"); CHECK(llmat_obj1 != llmat_obj2); } @@ -238,13 +219,10 @@ namespace llmat_obj1.setRows(llmat1, llmat2, llmat3); llmat_quat = llmat_obj1.quaternion(); - { - INFO("LLMatrix3::quaternion failed "); - CHECK(is_approx_equal(-0.66666669f, llmat_quat.mQ[0])); - CHECK(is_approx_equal(-0.83333337f, llmat_quat.mQ[1])); - CHECK(is_approx_equal(0.0f, llmat_quat.mQ[2])); - CHECK(is_approx_equal(1.5f, llmat_quat.mQ[3])); - } + CHECK(is_approx_equal(-0.66666669f, llmat_quat.mQ[0])); + CHECK(is_approx_equal(-0.83333337f, llmat_quat.mQ[1])); + CHECK(is_approx_equal(0.0f, llmat_quat.mQ[2])); + CHECK(is_approx_equal(1.5f, llmat_quat.mQ[3])); } //test case for transpose() fn. @@ -265,7 +243,6 @@ namespace LLMatrix3 expectedllmat_obj; expectedllmat_obj.setRows(resllvec1, resllvec2, resllvec3); - UNSCOPED_INFO("LLMatrix3::transpose failed "); CHECK(llmat_obj == expectedllmat_obj); } @@ -278,7 +255,6 @@ namespace LLVector3 llvec2(3, 2, 1); LLVector3 llvec3(2, 2, 2); llmat_obj1.setRows(llvec1, llvec2, llvec3); - UNSCOPED_INFO("LLMatrix3::determinant failed "); CHECK(llmat_obj1.determinant() == 0.0f); } @@ -296,18 +272,15 @@ namespace llmat_obj.setRows(llvec1, llvec2, llvec3); llmat_obj.orthogonalize(); - { - INFO("LLMatrix3::orthogonalize failed "); - CHECK(is_approx_equal(0.19611614f, llmat_obj.mMatrix[0][0])); - CHECK(is_approx_equal(0.78446454f, llmat_obj.mMatrix[0][1])); - CHECK(is_approx_equal(0.58834841f, llmat_obj.mMatrix[0][2])); - CHECK(is_approx_equal(0.47628204f, llmat_obj.mMatrix[1][0])); - CHECK(is_approx_equal(0.44826545f, llmat_obj.mMatrix[1][1])); - CHECK(is_approx_equal(-0.75644795f, llmat_obj.mMatrix[1][2])); - CHECK(is_approx_equal(-0.85714286f, llmat_obj.mMatrix[2][0])); - CHECK(is_approx_equal(0.42857143f, llmat_obj.mMatrix[2][1])); - CHECK(is_approx_equal(-0.28571429f, llmat_obj.mMatrix[2][2])); - } + CHECK(is_approx_equal(0.19611614f, llmat_obj.mMatrix[0][0])); + CHECK(is_approx_equal(0.78446454f, llmat_obj.mMatrix[0][1])); + CHECK(is_approx_equal(0.58834841f, llmat_obj.mMatrix[0][2])); + CHECK(is_approx_equal(0.47628204f, llmat_obj.mMatrix[1][0])); + CHECK(is_approx_equal(0.44826545f, llmat_obj.mMatrix[1][1])); + CHECK(is_approx_equal(-0.75644795f, llmat_obj.mMatrix[1][2])); + CHECK(is_approx_equal(-0.85714286f, llmat_obj.mMatrix[2][0])); + CHECK(is_approx_equal(0.42857143f, llmat_obj.mMatrix[2][1])); + CHECK(is_approx_equal(-0.28571429f, llmat_obj.mMatrix[2][2])); } //test case for adjointTranspose() fn. @@ -322,23 +295,19 @@ namespace llmat_obj.setRows(llvec1, llvec2, llvec3); llmat_obj.adjointTranspose(); - { - INFO("LLMatrix3::adjointTranspose failed "); - CHECK(llmat_obj.mMatrix[0][0] == 10); - CHECK(llmat_obj.mMatrix[1][0] == -45); - CHECK(llmat_obj.mMatrix[2][0] == 30); - CHECK(llmat_obj.mMatrix[0][1] == -10); - CHECK(llmat_obj.mMatrix[1][1] == 21); - CHECK(llmat_obj.mMatrix[2][1] == -12); - CHECK(llmat_obj.mMatrix[0][2] == 0); - CHECK(llmat_obj.mMatrix[1][2] == 3); - CHECK(llmat_obj.mMatrix[2][2] == -6); - } + CHECK(llmat_obj.mMatrix[0][0] == 10); + CHECK(llmat_obj.mMatrix[1][0] == -45); + CHECK(llmat_obj.mMatrix[2][0] == 30); + CHECK(llmat_obj.mMatrix[0][1] == -10); + CHECK(llmat_obj.mMatrix[1][1] == 21); + CHECK(llmat_obj.mMatrix[2][1] == -12); + CHECK(llmat_obj.mMatrix[0][2] == 0); + CHECK(llmat_obj.mMatrix[1][2] == 3); + CHECK(llmat_obj.mMatrix[2][2] == -6); } /* TBD: Need to add test cases for getEulerAngles() and setRot() functions */ - // The field-only operations of both matrices evaluate at compile time. TEST_CASE("m3math_h: field-only operations of both matrices evaluate at compile time", "[m3math_h]") { constexpr LLMatrix3 product = []() constexpr diff --git a/indra/llmath/tests/mathmisc_test.cpp b/indra/llmath/tests/mathmisc_test.cpp index 61559dbd4a..e3a2d3e558 100644 --- a/indra/llmath/tests/mathmisc_test.cpp +++ b/indra/llmath/tests/mathmisc_test.cpp @@ -46,11 +46,9 @@ namespace { S32 val = 89543; val = llabs(val); - UNSCOPED_INFO("integer absolute value 1"); CHECK(val == 89543); val = -500; val = llabs(val); - UNSCOPED_INFO("integer absolute value 2"); CHECK(val == 500); } @@ -58,11 +56,9 @@ namespace { F32 val = -2583.4f; val = llabs(val); - UNSCOPED_INFO("float absolute value 1"); CHECK(val == 2583.4f); val = 430903.f; val = llabs(val); - UNSCOPED_INFO("float absolute value 2"); CHECK(val == 430903.f); } @@ -70,11 +66,9 @@ namespace { F64 val = 387439393.987329839; val = llabs(val); - UNSCOPED_INFO("double absolute value 1"); CHECK(val == 387439393.987329839); val = -8937843.9394878; val = llabs(val); - UNSCOPED_INFO("double absolute value 2"); CHECK(val == 8937843.9394878); } @@ -82,11 +76,9 @@ namespace { F32 val = 430903.9f; S32 val1 = lltrunc(val); - UNSCOPED_INFO("float truncate value 1"); CHECK(val1 == 430903); val = -2303.9f; val1 = lltrunc(val); - UNSCOPED_INFO("float truncate value 2"); CHECK(val1 == -2303); } @@ -94,11 +86,9 @@ namespace { F64 val = 387439393.987329839 ; S32 val1 = lltrunc(val); - UNSCOPED_INFO("float truncate value 1"); CHECK(val1 == 387439393); val = -387439393.987329839; val1 = lltrunc(val); - UNSCOPED_INFO("float truncate value 2"); CHECK(val1 == -387439393); } @@ -106,11 +96,9 @@ namespace { F32 val = 430903.2f; S32 val1 = llfloor(val); - UNSCOPED_INFO("float llfloor value 1"); CHECK(val1 == 430903); val = -430903.9f; val1 = llfloor(val); - UNSCOPED_INFO("float llfloor value 2"); CHECK(val1 == -430904); } @@ -118,11 +106,9 @@ namespace { F32 val = 430903.2f; S32 val1 = llceil(val); - UNSCOPED_INFO("float llceil value 1"); CHECK(val1 == 430904); val = -430903.9f; val1 = llceil(val); - UNSCOPED_INFO("float llceil value 2"); CHECK(val1 == -430903); } @@ -130,11 +116,9 @@ namespace { F32 val = 430903.2f; S32 val1 = ll_round(val); - UNSCOPED_INFO("float ll_round value 1"); CHECK(val1 == 430903); val = -430903.9f; val1 = ll_round(val); - UNSCOPED_INFO("float ll_round value 2"); CHECK(val1 == -430904); } @@ -142,11 +126,9 @@ namespace { F32 val = 430905.2654f, nearest = 100.f; val = ll_round(val, nearest); - UNSCOPED_INFO("float ll_round value 1"); CHECK(val == 430900); val = -430905.2654f, nearest = 10.f; val = ll_round(val, nearest); - UNSCOPED_INFO("float ll_round value 1"); CHECK(val == -430910); } @@ -154,11 +136,9 @@ namespace { F64 val = 430905.2654, nearest = 100.0; val = ll_round(val, nearest); - UNSCOPED_INFO("double ll_round value 1"); CHECK(val == 430900); val = -430905.2654, nearest = 10.0; val = ll_round(val, nearest); - UNSCOPED_INFO("double ll_round value 1"); CHECK(val == -430910.00000); } @@ -167,18 +147,12 @@ namespace const F32 F_PI = 3.1415926535897932384626433832795f; F32 angle = 3506.f; angle = llsimple_angle(angle); - { - INFO("llsimple_angle value 1"); - CHECK(angle <=F_PI); - CHECK(angle >= -F_PI); - } + CHECK(angle <=F_PI); + CHECK(angle >= -F_PI); angle = -431.f; angle = llsimple_angle(angle); - { - INFO("llsimple_angle value 1"); - CHECK(angle <=F_PI); - CHECK(angle >= -F_PI); - } + CHECK(angle <=F_PI); + CHECK(angle >= -F_PI); } } @@ -191,13 +165,10 @@ namespace TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: isNull by default and after setNull but not after generate", "[mathmisc_LLUUID]") { - UNSCOPED_INFO("uuid null"); CHECK(id.isNull()); id.generate(); - UNSCOPED_INFO("generate not null"); CHECK(id.notNull()); id.setNull(); - UNSCOPED_INFO("set null"); CHECK(id.isNull()); } @@ -205,13 +176,10 @@ namespace { id.generate(); LLUUID a(id); - UNSCOPED_INFO("copy equal"); CHECK(id == a); a.generate(); - UNSCOPED_INFO("generate not equal"); CHECK(id != a); a = id; - UNSCOPED_INFO("assignment equal"); CHECK(id == a); } @@ -222,10 +190,8 @@ namespace LLUUID mask; mask.generate(); copy ^= mask; - UNSCOPED_INFO("mask not equal"); CHECK(id != copy); copy ^= mask; - UNSCOPED_INFO("mask back"); CHECK(id == copy); } @@ -234,7 +200,6 @@ namespace id.generate(); std::string id_str = id.asString(); LLUUID copy(id_str.c_str()); - UNSCOPED_INFO("string serialization"); CHECK(id == copy); } @@ -254,7 +219,6 @@ namespace c2.update(*rh); ++rh; } - UNSCOPED_INFO("crc update 1"); CHECK(c1.getCRC() == c2.getCRC()); } @@ -280,7 +244,6 @@ namespace } c2.update((U8*)TEST_BUFFER2, sizeof(TEST_BUFFER2) - 1); - UNSCOPED_INFO("crc update 2"); CHECK(c1.getCRC() == c2.getCRC()); } } @@ -293,7 +256,7 @@ namespace struct line_data { - // Tests 1 and 3 seed the main thread's generator. + // The intersects and two-planes cases seed the main thread's generator. ~line_data() { ll_test::reseedRandomFromEntropy(); } }; @@ -612,9 +575,7 @@ namespace volatile float zero = 0.0f; float nan = zero / zero; float inf = 1.0f / zero; - UNSCOPED_INFO("isnan sees a NaN"); CHECK(std::isnan(nan)); - UNSCOPED_INFO("isinf sees an infinity"); CHECK(std::isinf(inf)); } } diff --git a/indra/llmath/tests/v2math_test.cpp b/indra/llmath/tests/v2math_test.cpp index e45675c465..7f71583579 100644 --- a/indra/llmath/tests/v2math_test.cpp +++ b/indra/llmath/tests/v2math_test.cpp @@ -37,41 +37,26 @@ namespace TEST_CASE("v2math_h: constructors and clearVec/zeroVec", "[v2math_h]") { LLVector2 vec2; - { - INFO("LLVector2:Fail to initialize "); - CHECK(vec2.mV[VX] == 0.f); - CHECK(vec2.mV[VY] == 0.f); - } + CHECK(vec2.mV[VX] == 0.f); + CHECK(vec2.mV[VY] == 0.f); F32 x =2.0f, y = 3.2f ; LLVector2 vec3(x,y); - { - INFO("LLVector2(F32 x, F32 y):Fail to initialize "); - CHECK(x == vec3.mV[VX]); - CHECK(y == vec3.mV[VY]); - } + CHECK(x == vec3.mV[VX]); + CHECK(y == vec3.mV[VY]); const F32 vec[2] = {3.2f, 4.5f}; LLVector2 vec4(vec); - { - INFO("LLVector2(const F32 *vec):Fail to initialize "); - CHECK(vec[0] == vec4.mV[VX]); - CHECK(vec[1] == vec4.mV[VY]); - } + CHECK(vec[0] == vec4.mV[VX]); + CHECK(vec[1] == vec4.mV[VY]); vec4.clearVec(); - { - INFO("clearVec():Fail to clean the values "); - CHECK(vec4.mV[VX] == 0.f); - CHECK(vec4.mV[VY] == 0.f); - } + CHECK(vec4.mV[VX] == 0.f); + CHECK(vec4.mV[VY] == 0.f); vec3.zeroVec(); - { - INFO("zeroVec():Fail to fill the zero "); - CHECK(vec3.mV[VX] == 0.f); - CHECK(vec3.mV[VY] == 0.f); - } + CHECK(vec3.mV[VX] == 0.f); + CHECK(vec3.mV[VY] == 0.f); } TEST_CASE("v2math_h: the setVec overloads", "[v2math_h]") @@ -79,33 +64,24 @@ namespace F32 x = 123.356f, y = 2387.453f; LLVector2 vec2,vec3; vec2.setVec(x, y); - { - INFO("1:setVec: Fail "); - CHECK(x == vec2.mV[VX]); - CHECK(y == vec2.mV[VY]); - } + CHECK(x == vec2.mV[VX]); + CHECK(y == vec2.mV[VY]); vec3.setVec(vec2); - UNSCOPED_INFO("2:setVec: Fail "); CHECK(vec2 == vec3); vec3.zeroVec(); const F32 vec[2] = {3.24653f, 457653.4f}; vec3.setVec(vec); - { - INFO("3:setVec: Fail "); - CHECK(vec[0] == vec3.mV[VX]); - CHECK(vec[1] == vec3.mV[VY]); - } + CHECK(vec[0] == vec3.mV[VX]); + CHECK(vec[1] == vec3.mV[VY]); } TEST_CASE("v2math_h: magVecSquared and magVec", "[v2math_h]") { F32 x = 2.2345f, y = 3.5678f ; LLVector2 vec2(x,y); - UNSCOPED_INFO("magVecSquared:Fail "); CHECK(is_approx_equal(vec2.magVecSquared(), (x*x + y*y))); - UNSCOPED_INFO("magVec:Fail "); CHECK(is_approx_equal(vec2.magVec(), (F32) sqrt(x*x + y*y))); } @@ -113,19 +89,14 @@ namespace { F32 x =-2.0f, y = -3.0f ; LLVector2 vec2(x,y); - UNSCOPED_INFO("abs():Fail"); - CHECK(vec2.abs() == true); - UNSCOPED_INFO("abs() x"); + CHECK(vec2.abs()); CHECK(is_approx_equal(vec2.mV[VX], 2.f)); - UNSCOPED_INFO("abs() y"); CHECK(is_approx_equal(vec2.mV[VY], 3.f)); - UNSCOPED_INFO("isNull():Fail "); CHECK_FALSE(vec2.isNull()); //Returns true if vector has a _very_small_ length x =.00000001f, y = .000001001f; vec2.setVec(x, y); - UNSCOPED_INFO("isNull(): Fail "); CHECK(vec2.isNull()); } @@ -134,22 +105,16 @@ namespace F32 x =1.f, y = 2.f; LLVector2 vec2(x, y), vec3; vec3 = vec3.scaleVec(vec2); - { - INFO("scaleVec: Fail "); - CHECK(vec3.mV[VX] == 0.); - CHECK(vec3.mV[VY] == 0.); - } - UNSCOPED_INFO("isExactlyZero(): Fail"); + CHECK(vec3.mV[VX] == 0.); + CHECK(vec3.mV[VY] == 0.); + CHECK(vec3.isExactlyZero()); vec3.setVec(2.f, 1.f); vec3 = vec3.scaleVec(vec2); - { - INFO("scaleVec: Fail "); - CHECK(vec3.mV[VX] == 2.f); - CHECK(vec3.mV[VY] == 2.f); - } - UNSCOPED_INFO("isExactlyZero():Fail"); + CHECK(vec3.mV[VX] == 2.f); + CHECK(vec3.mV[VY] == 2.f); + CHECK_FALSE(vec3.isExactlyZero()); } @@ -161,11 +126,8 @@ namespace vec4 = vec2 + vec3 ; val1 = x1+x2; val2 = y1+y2; - { - INFO("1:operator+ failed"); - CHECK(val1 == vec4.mV[VX]); - CHECK(val2 == vec4.mV[VY]); - } + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); vec2.clearVec(); vec3.clearVec(); @@ -175,11 +137,8 @@ namespace vec4 = vec2 + vec3; val1 = x1+x2; val2 = y1+y2; - { - INFO("2:operator+ failed"); - CHECK(val1 == vec4.mV[VX]); - CHECK(val2 == vec4.mV[VY]); - } + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); } TEST_CASE("v2math_h: operator- subtracts componentwise", "[v2math_h]") @@ -190,11 +149,8 @@ namespace vec4 = vec2 - vec3 ; val1 = x1-x2; val2 = y1-y2; - { - INFO("1:operator- failed"); - CHECK(val1 == vec4.mV[VX]); - CHECK(val2 == vec4.mV[VY]); - } + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); vec2.clearVec(); vec3.clearVec(); @@ -205,11 +161,8 @@ namespace vec4 = vec2 - vec3; val1 = x1-x2; val2 = y1-y2; - { - INFO("2:operator- failed"); - CHECK(val1 == vec4.mV[VX]); - CHECK(val2 == vec4.mV[VY]); - } + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); } TEST_CASE("v2math_h: operator* as dot product and scalar product", "[v2math_h]") @@ -219,7 +172,6 @@ namespace LLVector2 vec2(x1, y1), vec3(x2, y2); val1 = vec2 * vec3; val2 = x1*x2 + y1*y2; - UNSCOPED_INFO("1:operator* failed"); CHECK(val1 == val2); vec3.clearVec(); @@ -227,19 +179,13 @@ namespace vec3 = vec2 * mulVal; val1 = x1*mulVal; val2 = y1*mulVal; - { - INFO("2:operator* failed"); - CHECK(val1 == vec3.mV[VX]); - CHECK(val2 == vec3.mV[VY]); - } + CHECK(val1 == vec3.mV[VX]); + CHECK(val2 == vec3.mV[VY]); vec3.clearVec(); vec3 = mulVal * vec2; - { - INFO("3:operator* failed"); - CHECK(val1 == vec3.mV[VX]); - CHECK(val2 == vec3.mV[VY]); - } + CHECK(val1 == vec3.mV[VX]); + CHECK(val2 == vec3.mV[VY]); } TEST_CASE("v2math_h: operator/ divides by a scalar", "[v2math_h]") @@ -250,11 +196,8 @@ namespace vec3 = vec2 / div; val1 = x1 / div; val2 = y1 / div; - { - INFO("1:operator/ failed"); - CHECK(is_approx_equal(val1, vec3.mV[VX])); - CHECK(is_approx_equal(val2, vec3.mV[VY])); - } + CHECK(is_approx_equal(val1, vec3.mV[VX])); + CHECK(is_approx_equal(val2, vec3.mV[VY])); vec3.clearVec(); x1 = -.235f, y1 = -24.32f, div = -2.2f; @@ -262,11 +205,8 @@ namespace vec3 = vec2 / div; val1 = x1 / div; val2 = y1 / div; - { - INFO("2:operator/ failed"); - CHECK(is_approx_equal(val1, vec3.mV[VX])); - CHECK(is_approx_equal(val2, vec3.mV[VY])); - } + CHECK(is_approx_equal(val1, vec3.mV[VX])); + CHECK(is_approx_equal(val2, vec3.mV[VY])); } TEST_CASE("v2math_h: operator% is the cross product", "[v2math_h]") @@ -277,11 +217,8 @@ namespace vec4 = vec2 % vec3; val1 = x1*y2 - x2*y1; val2 = y1*x2 - y2*x1; - { - INFO("1:operator% failed"); - CHECK(val1 == vec4.mV[VX]); - CHECK(val2 == vec4.mV[VY]); - } + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); vec2.clearVec(); vec3.clearVec(); @@ -292,17 +229,14 @@ namespace vec4 = vec2 % vec3; val1 = x1*y2 - x2*y1; val2 = y1*x2 - y2*x1; - { - INFO("2:operator% failed"); - CHECK(val1 == vec4.mV[VX]); - CHECK(val2 == vec4.mV[VY]); - } + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); } + TEST_CASE("v2math_h: operator== for equal vectors", "[v2math_h]") { F32 x1 =1.f, y1 = 2.f; LLVector2 vec2(x1, y1), vec3(x1, y1); - UNSCOPED_INFO("1:operator== failed"); CHECK(vec2 == vec3); vec2.clearVec(); @@ -310,7 +244,6 @@ namespace x1 = -.235f, y1 = -24.32f; vec2.setVec(x1, y1); vec3.setVec(vec2); - UNSCOPED_INFO("2:operator== failed"); CHECK(vec2 == vec3); } @@ -318,16 +251,15 @@ namespace { F32 x1 = 1.f, y1 = 2.f,x2 = 2.332f, y2 = -1.23f; LLVector2 vec2(x1, y1), vec3(x2, y2); - UNSCOPED_INFO("1:operator!= failed"); CHECK(vec2 != vec3); vec2.clearVec(); vec3.clearVec(); vec2.setVec(x1, y1); vec3.setVec(vec2); - UNSCOPED_INFO("2:operator!= failed"); CHECK_FALSE(vec2 != vec3); } + TEST_CASE("v2math_h: operator+= and operator-=", "[v2math_h]") { F32 x1 = 1.f, y1 = 2.f,x2 = 2.332f, y2 = -1.23f; @@ -336,21 +268,15 @@ namespace vec2 +=vec3; val1 = x1+x2; val2 = y1+y2; - { - INFO("1:operator+= failed"); - CHECK(val1 == vec2.mV[VX]); - CHECK(val2 == vec2.mV[VY]); - } + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); vec2.setVec(x1, y1); vec2 -=vec3; val1 = x1-x2; val2 = y1-y2; - { - INFO("2:operator-= failed"); - CHECK(val1 == vec2.mV[VX]); - CHECK(val2 == vec2.mV[VY]); - } + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); vec2.clearVec(); vec3.clearVec(); @@ -360,21 +286,15 @@ namespace vec2 +=vec3; val1 = x1+x2; val2 = y1+y2; - { - INFO("3:operator+= failed"); - CHECK(val1 == vec2.mV[VX]); - CHECK(val2 == vec2.mV[VY]); - } + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); vec2.setVec(x1, y1); vec2 -=vec3; val1 = x1-x2; val2 = y1-y2; - { - INFO("4:operator-= failed"); - CHECK(is_approx_equal(val1, vec2.mV[VX])); - CHECK(is_approx_equal(val2, vec2.mV[VY])); - } + CHECK(is_approx_equal(val1, vec2.mV[VX])); + CHECK(is_approx_equal(val2, vec2.mV[VY])); } TEST_CASE("v2math_h: operator/= divides by a scalar", "[v2math_h]") @@ -385,11 +305,8 @@ namespace vec2 /=mulVal; val1 = x1 / mulVal; val2 = y1 / mulVal; - { - INFO("1:operator/= failed"); - CHECK(is_approx_equal(val1, vec2.mV[VX])); - CHECK(is_approx_equal(val2, vec2.mV[VY])); - } + CHECK(is_approx_equal(val1, vec2.mV[VX])); + CHECK(is_approx_equal(val2, vec2.mV[VY])); vec2.clearVec(); x1 = .213f, y1 = -2.34f, mulVal = -.23f; @@ -397,11 +314,8 @@ namespace vec2 /=mulVal; val1 = x1 / mulVal; val2 = y1 / mulVal; - { - INFO("2:operator/= failed"); - CHECK(is_approx_equal(val1, vec2.mV[VX])); - CHECK(is_approx_equal(val2, vec2.mV[VY])); - } + CHECK(is_approx_equal(val1, vec2.mV[VX])); + CHECK(is_approx_equal(val2, vec2.mV[VY])); } TEST_CASE("v2math_h: operator*= multiplies by a scalar", "[v2math_h]") @@ -412,11 +326,8 @@ namespace vec2 *=mulVal; val1 = x1*mulVal; val2 = y1*mulVal; - { - INFO("1:operator*= failed"); - CHECK(val1 == vec2.mV[VX]); - CHECK(val2 == vec2.mV[VY]); - } + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); vec2.clearVec(); x1 = .213f, y1 = -2.34f, mulVal = -.23f; @@ -424,11 +335,8 @@ namespace vec2 *=mulVal; val1 = x1*mulVal; val2 = y1*mulVal; - { - INFO("2:operator*= failed"); - CHECK(val1 == vec2.mV[VX]); - CHECK(val2 == vec2.mV[VY]); - } + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); } TEST_CASE("v2math_h: operator%= matches the cross product", "[v2math_h]") @@ -439,11 +347,8 @@ namespace vec2 %= vec3; val1 = x1*y2 - x2*y1; val2 = y1*x2 - y2*x1; - { - INFO("1:operator%= failed"); - CHECK(val1 == vec2.mV[VX]); - CHECK(val2 == vec2.mV[VY]); - } + CHECK(val1 == vec2.mV[VX]); + CHECK(val2 == vec2.mV[VY]); } TEST_CASE("v2math_h: unary operator- negates", "[v2math_h]") @@ -451,7 +356,6 @@ namespace F32 x1 =1.f, y1 = 2.f; LLVector2 vec2(x1, y1),vec3; vec3 = -vec2; - UNSCOPED_INFO("1:operator- failed"); CHECK(-vec3 == vec2); } @@ -463,7 +367,6 @@ namespace stream1 << vec2; vec3.setVec(x1, y1); stream2 << vec3; - UNSCOPED_INFO("1:operator << failed"); CHECK(stream1.str() == stream2.str()); } @@ -471,13 +374,11 @@ namespace { F32 x1 =1.0f, y1 = 2.0f, x2 = -.32f, y2 = .2234f; LLVector2 vec2(x1, y1),vec3(x2, y2); - UNSCOPED_INFO("1:operator < failed"); CHECK(vec3 < vec2); x1 = 1.0f, y1 = 2.0f, x2 = 1.0f, y2 = 3.2234f; vec2.setVec(x1, y1); vec3.setVec(x2, y2); - UNSCOPED_INFO("2:operator < failed"); CHECK_FALSE(vec3 < vec2); } @@ -485,19 +386,15 @@ namespace { F32 x1 =1.0f, y1 = 2.0f; LLVector2 vec2(x1, y1); - UNSCOPED_INFO("1:operator [] failed"); CHECK(x1 == vec2[0]); - UNSCOPED_INFO("2:operator [] failed"); CHECK(y1 == vec2[1]); vec2.clearVec(); x1 = 23.0f, y1 = -.2361f; vec2.setVec(x1, y1); F32 ref1 = vec2[0]; - UNSCOPED_INFO("3:operator [] failed"); CHECK(ref1 == x1); F32 ref2 = vec2[1]; - UNSCOPED_INFO("4:operator [] failed"); CHECK(ref2 == y1); } @@ -508,16 +405,13 @@ namespace LLVector2 vec2(x1, y1),vec3(x2, y2); val1 = dist_vec_squared2D(vec2, vec3); val2 = (x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2); - UNSCOPED_INFO("dist_vec_squared2D values are not equal"); CHECK(val2 == val1); val1 = dist_vec_squared(vec2, vec3); - UNSCOPED_INFO("dist_vec_squared values are not equal"); CHECK(val2 == val1); val1 = dist_vec(vec2, vec3); val2 = (F32) sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2)); - UNSCOPED_INFO("dist_vec values are not equal"); CHECK(val2 == val1); } @@ -529,11 +423,8 @@ namespace LLVector2 vec4 = lerp(vec2, vec3, fVal); val1 = x1 + (x2 - x1) * fVal; val2 = y1 + (y2 - y1) * fVal; - { - INFO("lerp values are not equal"); - CHECK(val1 == vec4.mV[VX]); - CHECK(val2 == vec4.mV[VY]); - } + CHECK(val1 == vec4.mV[VX]); + CHECK(val2 == vec4.mV[VY]); } TEST_CASE("v2math_h: normVec normalises and zeroes a tiny vector", "[v2math_h]") @@ -549,22 +440,16 @@ namespace val1 = x1 * oomag; val2 = y1 * oomag; - { - INFO("normVec failed"); - CHECK(is_approx_equal(val1, vec2.mV[VX])); - CHECK(is_approx_equal(val2, vec2.mV[VY])); - CHECK(is_approx_equal(vecMag, mag)); - } + CHECK(is_approx_equal(val1, vec2.mV[VX])); + CHECK(is_approx_equal(val2, vec2.mV[VY])); + CHECK(is_approx_equal(vecMag, mag)); x1 =.00000001f, y1 = 0.f; vec2.setVec(x1, y1); vecMag = vec2.normVec(); - { - INFO("normVec failed should be 0."); - CHECK(vec2.mV[VX] == 0.); - CHECK(vec2.mV[VY] == 0.); - CHECK(vecMag == 0.); - } + CHECK(vec2.mV[VX] == 0.); + CHECK(vec2.mV[VY] == 0.); + CHECK(vecMag == 0.); } } diff --git a/indra/llmath/tests/v3color_test.cpp b/indra/llmath/tests/v3color_test.cpp index 6be134ff8e..7ffa6cb49a 100644 --- a/indra/llmath/tests/v3color_test.cpp +++ b/indra/llmath/tests/v3color_test.cpp @@ -37,60 +37,42 @@ namespace TEST_CASE("v3color_h: constructors take nothing or components or an array or a hex string", "[v3color_h]") { LLColor3 llcolor3; - { - INFO("1:LLColor3:Fail to default-initialize "); - CHECK(llcolor3.mV[0] == 0.0f); - CHECK(llcolor3.mV[1] == 0.0f); - CHECK(llcolor3.mV[2] == 0.0f); - } + CHECK(llcolor3.mV[0] == 0.0f); + CHECK(llcolor3.mV[1] == 0.0f); + CHECK(llcolor3.mV[2] == 0.0f); F32 r = 2.0f, g = 3.2f, b = 1.f; F32 v1,v2,v3; LLColor3 llcolor3a(r,g,b); - { - INFO("2:LLColor3:Fail to initialize "); - CHECK(llcolor3a.mV[0] == 2.0f); - CHECK(llcolor3a.mV[1] == 3.2f); - CHECK(llcolor3a.mV[2] == 1.f); - } + CHECK(llcolor3a.mV[0] == 2.0f); + CHECK(llcolor3a.mV[1] == 3.2f); + CHECK(llcolor3a.mV[2] == 1.f); const F32 vec[3] = {2.0f, 3.2f,1.f}; LLColor3 llcolor3b(vec); - { - INFO("3:LLColor3:Fail to initialize "); - CHECK(llcolor3b.mV[0] == 2.0f); - CHECK(llcolor3b.mV[1] == 3.2f); - CHECK(llcolor3b.mV[2] == 1.f); - } + CHECK(llcolor3b.mV[0] == 2.0f); + CHECK(llcolor3b.mV[1] == 3.2f); + CHECK(llcolor3b.mV[2] == 1.f); const char* str = "561122"; LLColor3 llcolor3c(str); v1 = (F32)86.0f/255.0f; // 0x56 = 86 v2 = (F32)17.0f/255.0f; // 0x11 = 17 v3 = (F32)34.0f/255.f; // 0x22 = 34 - { - INFO("4:LLColor3:Fail to initialize "); - CHECK(is_approx_equal(v1, llcolor3c.mV[0])); - CHECK(is_approx_equal(v2, llcolor3c.mV[1])); - CHECK(is_approx_equal(v3, llcolor3c.mV[2])); - } + CHECK(is_approx_equal(v1, llcolor3c.mV[0])); + CHECK(is_approx_equal(v2, llcolor3c.mV[1])); + CHECK(is_approx_equal(v3, llcolor3c.mV[2])); } TEST_CASE("v3color_h: setToBlack and setToWhite", "[v3color_h]") { LLColor3 llcolor3; llcolor3.setToBlack(); - { - INFO("setToBlack:Fail to set black "); - CHECK(llcolor3.mV[0] == 0.f); - CHECK(llcolor3.mV[1] == 0.f); - CHECK(llcolor3.mV[2] == 0.f); - } + CHECK(llcolor3.mV[0] == 0.f); + CHECK(llcolor3.mV[1] == 0.f); + CHECK(llcolor3.mV[2] == 0.f); llcolor3.setToWhite(); - { - INFO("setToWhite:Fail to set white "); - CHECK(llcolor3.mV[0] == 1.f); - CHECK(llcolor3.mV[1] == 1.f); - CHECK(llcolor3.mV[2] == 1.f); - } + CHECK(llcolor3.mV[0] == 1.f); + CHECK(llcolor3.mV[1] == 1.f); + CHECK(llcolor3.mV[2] == 1.f); } TEST_CASE("v3color_h: setVec from components or another color or an array", "[v3color_h]") @@ -98,33 +80,24 @@ namespace F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; LLColor3 llcolor3, llcolor3a; llcolor3.setVec(r,g,b); - { - INFO("1:setVec(r,g,b) Fail "); - CHECK(r == llcolor3.mV[0]); - CHECK(g == llcolor3.mV[1]); - CHECK(b == llcolor3.mV[2]); - } + CHECK(r == llcolor3.mV[0]); + CHECK(g == llcolor3.mV[1]); + CHECK(b == llcolor3.mV[2]); llcolor3a.setVec(llcolor3); - UNSCOPED_INFO("2:setVec(LLColor3) Fail "); CHECK(llcolor3 == llcolor3a); F32 vec[3] = {1.2324f, 2.45634f, .234563f}; llcolor3.setToBlack(); llcolor3.setVec(vec); - { - INFO("3:setVec(F32*) Fail "); - CHECK(vec[0] == llcolor3.mV[0]); - CHECK(vec[1] == llcolor3.mV[1]); - CHECK(vec[2] == llcolor3.mV[2]); - } + CHECK(vec[0] == llcolor3.mV[0]); + CHECK(vec[1] == llcolor3.mV[1]); + CHECK(vec[2] == llcolor3.mV[2]); } TEST_CASE("v3color_h: magVecSquared and magVec", "[v3color_h]") { F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; LLColor3 llcolor3(r,g,b); - UNSCOPED_INFO("magVecSquared:Fail "); CHECK(is_approx_equal(llcolor3.magVecSquared(), (r*r + g*g + b*b))); - UNSCOPED_INFO("magVec:Fail "); CHECK(is_approx_equal(llcolor3.magVec(), (F32) sqrt(r*r + g*g + b*b))); } @@ -139,23 +112,17 @@ namespace val1 = r * oomag; val2 = g * oomag; val3 = b * oomag; - { - INFO("1:normVec failed "); - CHECK(is_approx_equal(val1, llcolor3.mV[0])); - CHECK(is_approx_equal(val2, llcolor3.mV[1])); - CHECK(is_approx_equal(val3, llcolor3.mV[2])); - CHECK(is_approx_equal(vecMag, mag)); - } + CHECK(is_approx_equal(val1, llcolor3.mV[0])); + CHECK(is_approx_equal(val2, llcolor3.mV[1])); + CHECK(is_approx_equal(val3, llcolor3.mV[2])); + CHECK(is_approx_equal(vecMag, mag)); r = .000000000f, g = 0.f, b = 0.0f; llcolor3.setVec(r,g,b); vecMag = llcolor3.normVec(); - { - INFO("2:normVec failed should be 0. "); - CHECK(llcolor3.mV[0] == 0.); - CHECK(llcolor3.mV[1] == 0.); - CHECK(llcolor3.mV[2] == 0.); - CHECK(vecMag == 0.); - } + CHECK(llcolor3.mV[0] == 0.); + CHECK(llcolor3.mV[1] == 0.); + CHECK(llcolor3.mV[2] == 0.); + CHECK(vecMag == 0.); } TEST_CASE("v3color_h: operator<< prints equal colors the same", "[v3color_h]") @@ -166,7 +133,6 @@ namespace stream1 << llcolor3; llcolor3a.setVec(r,g,b); stream2 << llcolor3a; - UNSCOPED_INFO("operator << failed "); CHECK(stream1.str() == stream2.str()); } @@ -175,7 +141,6 @@ namespace F32 r = 2.3436212f, g = -1231.f, b = .7849321232f; LLColor3 llcolor3(r,g,b),llcolor3a; llcolor3a = llcolor3; - UNSCOPED_INFO("operator == failed "); CHECK(llcolor3a == llcolor3); } @@ -184,22 +149,16 @@ namespace F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2),llcolor3b; llcolor3b = llcolor3 + llcolor3a ; - { - INFO("1:operator+ failed"); - CHECK(is_approx_equal(r1+r2 ,llcolor3b.mV[0])); - CHECK(is_approx_equal(g1+g2,llcolor3b.mV[1])); - CHECK(is_approx_equal(b1+b2,llcolor3b.mV[2])); - } + CHECK(is_approx_equal(r1+r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1+g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1+b2,llcolor3b.mV[2])); r1 = -.235f, g1 = -24.32f, b1 = 2.13f, r2 = -2.3f, g2 = 1.f, b2 = 34.21f; llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); llcolor3b = llcolor3 + llcolor3a; - { - INFO("2:operator+ failed"); - CHECK(is_approx_equal(r1+r2 ,llcolor3b.mV[0])); - CHECK(is_approx_equal(g1+g2,llcolor3b.mV[1])); - CHECK(is_approx_equal(b1+b2,llcolor3b.mV[2])); - } + CHECK(is_approx_equal(r1+r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1+g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1+b2,llcolor3b.mV[2])); } TEST_CASE("v3color_h: operator- subtracts component-wise", "[v3color_h]") @@ -207,22 +166,16 @@ namespace F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2),llcolor3b; llcolor3b = llcolor3 - llcolor3a ; - { - INFO("1:operator- failed"); - CHECK(is_approx_equal(r1-r2 ,llcolor3b.mV[0])); - CHECK(is_approx_equal(g1-g2,llcolor3b.mV[1])); - CHECK(is_approx_equal(b1-b2,llcolor3b.mV[2])); - } + CHECK(is_approx_equal(r1-r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1-g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1-b2,llcolor3b.mV[2])); r1 = -.235f, g1 = -24.32f, b1 = 2.13f, r2 = -2.3f, g2 = 1.f, b2 = 34.21f; llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); llcolor3b = llcolor3 - llcolor3a; - { - INFO("2:operator- failed"); - CHECK(is_approx_equal(r1-r2 ,llcolor3b.mV[0])); - CHECK(is_approx_equal(g1-g2,llcolor3b.mV[1])); - CHECK(is_approx_equal(b1-b2,llcolor3b.mV[2])); - } + CHECK(is_approx_equal(r1-r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1-g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1-b2,llcolor3b.mV[2])); } TEST_CASE("v3color_h: operator* by a color and by a scalar on either side", "[v3color_h]") @@ -230,29 +183,20 @@ namespace F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2),llcolor3b; llcolor3b = llcolor3 * llcolor3a; - { - INFO("1:operator* failed"); - CHECK(is_approx_equal(r1*r2 ,llcolor3b.mV[0])); - CHECK(is_approx_equal(g1*g2,llcolor3b.mV[1])); - CHECK(is_approx_equal(b1*b2,llcolor3b.mV[2])); - } + CHECK(is_approx_equal(r1*r2 ,llcolor3b.mV[0])); + CHECK(is_approx_equal(g1*g2,llcolor3b.mV[1])); + CHECK(is_approx_equal(b1*b2,llcolor3b.mV[2])); llcolor3a.setToBlack(); F32 mulVal = 4.332f; llcolor3a = llcolor3 * mulVal; - { - INFO("2:operator* failed"); - CHECK(is_approx_equal(r1*mulVal ,llcolor3a.mV[0])); - CHECK(is_approx_equal(g1*mulVal,llcolor3a.mV[1])); - CHECK(is_approx_equal(b1*mulVal,llcolor3a.mV[2])); - } + CHECK(is_approx_equal(r1*mulVal ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1*mulVal,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1*mulVal,llcolor3a.mV[2])); llcolor3a.setToBlack(); llcolor3a = mulVal * llcolor3; - { - INFO("3:operator* failed"); - CHECK(is_approx_equal(r1*mulVal ,llcolor3a.mV[0])); - CHECK(is_approx_equal(g1*mulVal,llcolor3a.mV[1])); - CHECK(is_approx_equal(b1*mulVal,llcolor3a.mV[2])); - } + CHECK(is_approx_equal(r1*mulVal ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1*mulVal,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1*mulVal,llcolor3a.mV[2])); } TEST_CASE("v3color_h: unary minus negates", "[v3color_h]") @@ -260,7 +204,6 @@ namespace F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; LLColor3 llcolor3(r,g,b),llcolor3a; llcolor3a = -llcolor3; - UNSCOPED_INFO("operator- failed "); CHECK(-llcolor3a == llcolor3); } @@ -268,12 +211,10 @@ namespace { F32 r = 2.3436212f, g = 1231.f, b = 4.7849321232f; LLColor3 llcolor3(r,g,b),llcolor3a(r,g,b); - UNSCOPED_INFO("1:operator== failed"); CHECK(llcolor3a == llcolor3); r = 13.3436212f, g = -11.f, b = .7849321232f; llcolor3.setVec(r,g,b); llcolor3a.setVec(r,g,b); - UNSCOPED_INFO("2:operator== failed"); CHECK(llcolor3a == llcolor3); } @@ -281,11 +222,9 @@ namespace { F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); - UNSCOPED_INFO("1:operator!= failed"); CHECK(llcolor3 != llcolor3a); llcolor3.setToBlack(); llcolor3a.setVec(llcolor3); - UNSCOPED_INFO("2:operator!= failed"); CHECK_FALSE(llcolor3a != llcolor3); } @@ -294,21 +233,15 @@ namespace F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); llcolor3a += llcolor3; - { - INFO("1:operator+= failed"); - CHECK(is_approx_equal(r1+r2 ,llcolor3a.mV[0])); - CHECK(is_approx_equal(g1+g2,llcolor3a.mV[1])); - CHECK(is_approx_equal(b1+b2,llcolor3a.mV[2])); - } + CHECK(is_approx_equal(r1+r2 ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1+g2,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1+b2,llcolor3a.mV[2])); llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); llcolor3a += llcolor3; - { - INFO("2:operator+= failed"); - CHECK(is_approx_equal(r1+r2 ,llcolor3a.mV[0])); - CHECK(is_approx_equal(g1+g2,llcolor3a.mV[1])); - CHECK(is_approx_equal(b1+b2,llcolor3a.mV[2])); - } + CHECK(is_approx_equal(r1+r2 ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1+g2,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1+b2,llcolor3a.mV[2])); } TEST_CASE("v3color_h: operator-= subtracts component-wise", "[v3color_h]") @@ -316,20 +249,14 @@ namespace F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); llcolor3a -= llcolor3; - UNSCOPED_INFO("1:operator-= failed"); CHECK(is_approx_equal(r2-r1, llcolor3a.mV[0])); - UNSCOPED_INFO("2:operator-= failed"); CHECK(is_approx_equal(g2-g1, llcolor3a.mV[1])); - UNSCOPED_INFO("3:operator-= failed"); CHECK(is_approx_equal(b2-b1, llcolor3a.mV[2])); llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); llcolor3a -= llcolor3; - UNSCOPED_INFO("4:operator-= failed"); CHECK(is_approx_equal(r2-r1, llcolor3a.mV[0])); - UNSCOPED_INFO("5:operator-= failed"); CHECK(is_approx_equal(g2-g1, llcolor3a.mV[1])); - UNSCOPED_INFO("6:operator-= failed"); CHECK(is_approx_equal(b2-b1, llcolor3a.mV[2])); } @@ -338,20 +265,14 @@ namespace F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); llcolor3a *= llcolor3; - { - INFO("1:operator*= failed"); - CHECK(is_approx_equal(r1*r2 ,llcolor3a.mV[0])); - CHECK(is_approx_equal(g1*g2,llcolor3a.mV[1])); - CHECK(is_approx_equal(b1*b2,llcolor3a.mV[2])); - } + CHECK(is_approx_equal(r1*r2 ,llcolor3a.mV[0])); + CHECK(is_approx_equal(g1*g2,llcolor3a.mV[1])); + CHECK(is_approx_equal(b1*b2,llcolor3a.mV[2])); F32 mulVal = 4.332f; llcolor3 *=mulVal; - { - INFO("2:operator*= failed"); - CHECK(is_approx_equal(r1*mulVal ,llcolor3.mV[0])); - CHECK(is_approx_equal(g1*mulVal,llcolor3.mV[1])); - CHECK(is_approx_equal(b1*mulVal,llcolor3.mV[2])); - } + CHECK(is_approx_equal(r1*mulVal ,llcolor3.mV[0])); + CHECK(is_approx_equal(g1*mulVal,llcolor3.mV[1])); + CHECK(is_approx_equal(b1*mulVal,llcolor3.mV[2])); } TEST_CASE("v3color_h: clamp limits components to the 0 to 1 range", "[v3color_h]") @@ -359,21 +280,15 @@ namespace F32 r = 2.3436212f, g = -1231.f, b = .7849321232f; LLColor3 llcolor3(r,g,b); llcolor3.clamp(); - { - INFO("1:clamp:Fail to clamp "); - CHECK(llcolor3.mV[0] == 1.0f); - CHECK(llcolor3.mV[1] == 0.f); - CHECK(b == llcolor3.mV[2]); - } + CHECK(llcolor3.mV[0] == 1.0f); + CHECK(llcolor3.mV[1] == 0.f); + CHECK(b == llcolor3.mV[2]); r = -2.3436212f, g = -1231.f, b = 67.7849321232f; llcolor3.setVec(r,g,b); llcolor3.clamp(); - { - INFO("2:clamp:Fail to clamp "); - CHECK(llcolor3.mV[0] == 0.f); - CHECK(llcolor3.mV[1] == 0.f); - CHECK(llcolor3.mV[2] == 1.f); - } + CHECK(llcolor3.mV[0] == 0.f); + CHECK(llcolor3.mV[1] == 0.f); + CHECK(llcolor3.mV[2] == 1.f); } TEST_CASE("v3color_h: lerp interpolates every component", "[v3color_h]") @@ -385,12 +300,9 @@ namespace val2 = g1 + (g2 - g1)* val; val3 = b1 + (b2 - b1)* val; LLColor3 llcolor3b = lerp(llcolor3,llcolor3a,val); - { - INFO("lerp failed "); - CHECK(val1 ==llcolor3b.mV[0]); - CHECK(val2 ==llcolor3b.mV[1]); - CHECK(val3 ==llcolor3b.mV[2]); - } + CHECK(val1 ==llcolor3b.mV[0]); + CHECK(val2 ==llcolor3b.mV[1]); + CHECK(val3 ==llcolor3b.mV[2]); } TEST_CASE("v3color_h: distVec and distVec_squared", "[v3color_h]") @@ -398,11 +310,9 @@ namespace F32 r1 =1.f, g1 = 2.f,b1 = 1.2f, r2 = -2.3f, g2 = 1.11f, b2 = 1234.234f; LLColor3 llcolor3(r1,g1,b1),llcolor3a(r2,g2,b2); F32 val = distVec(llcolor3,llcolor3a); - UNSCOPED_INFO("distVec failed "); CHECK(is_approx_equal((F32) sqrt((r1-r2)*(r1-r2) + (g1-g2)*(g1-g2) + (b1-b2)*(b1-b2)) ,val)); F32 val1 = distVec_squared(llcolor3,llcolor3a); - UNSCOPED_INFO("distVec_squared failed "); CHECK(is_approx_equal(((r1-r2)*(r1-r2) + (g1-g2)*(g1-g2) + (b1-b2)*(b1-b2)) ,val1)); } @@ -414,11 +324,9 @@ namespace LLSD sd = llcolor31.getValue(); LLColor3 llcolor32; llcolor32.setValue(sd); - UNSCOPED_INFO("LLColor3::setValue/getValue failed"); CHECK(llcolor31 == llcolor32); LLColor3 llcolor33(sd); - UNSCOPED_INFO("LLColor3(LLSD) failed"); CHECK(llcolor31 == llcolor33); } } diff --git a/indra/llmath/tests/v3dmath_test.cpp b/indra/llmath/tests/v3dmath_test.cpp index 215e505b50..3f660402ec 100644 --- a/indra/llmath/tests/v3dmath_test.cpp +++ b/indra/llmath/tests/v3dmath_test.cpp @@ -42,31 +42,24 @@ namespace TEST_CASE("v3dmath_h: constructors set every component", "[v3dmath_h]") { LLVector3d vec3D; - { - INFO("1:LLVector3d:Fail to initialize "); - CHECK(vec3D.mdV[VX] == 0); - CHECK(vec3D.mdV[VY] == 0); - CHECK(vec3D.mdV[VZ] == 0); - } + CHECK(vec3D.mdV[VX] == 0); + CHECK(vec3D.mdV[VY] == 0); + CHECK(vec3D.mdV[VZ] == 0); + F64 x = 2.32f, y = 1.212f, z = -.12f; LLVector3d vec3Da(x,y,z); - { - INFO("2:LLVector3d:Fail to initialize "); - CHECK(vec3Da.mdV[VX] == 2.32f); - CHECK(vec3Da.mdV[VY] == 1.212f); - CHECK(vec3Da.mdV[VZ] == -.12f); - } + CHECK(vec3Da.mdV[VX] == 2.32f); + CHECK(vec3Da.mdV[VY] == 1.212f); + CHECK(vec3Da.mdV[VZ] == -.12f); + const F64 vec[3] = {1.2f ,3.2f, -4.2f}; LLVector3d vec3Db(vec); - { - INFO("3:LLVector3d:Fail to initialize "); - CHECK(vec3Db.mdV[VX] == 1.2f); - CHECK(vec3Db.mdV[VY] == 3.2f); - CHECK(vec3Db.mdV[VZ] == -4.2f); - } + CHECK(vec3Db.mdV[VX] == 1.2f); + CHECK(vec3Db.mdV[VY] == 3.2f); + CHECK(vec3Db.mdV[VZ] == -4.2f); + LLVector3 vec3((F32)x,(F32)y,(F32)z); LLVector3d vec3Dc(vec3); - UNSCOPED_INFO("4:LLVector3d Fail to initialize"); CHECK(vec3Da == vec3Dc); } @@ -77,7 +70,6 @@ namespace LLVector3d vec3d(llsd); LLSD sd = vec3d.getValue(); LLVector3d vec3da(sd); - UNSCOPED_INFO("1:getValue:Fail "); CHECK(vec3d == vec3da); } @@ -89,7 +81,6 @@ namespace vec3d.setValue(llsd); LLSD sd = vec3d.getValue(); LLVector3d vec3da(sd); - UNSCOPED_INFO("1:setValue:Fail to initialize "); CHECK(vec3d == vec3da); } @@ -102,12 +93,9 @@ namespace llsd[2] = a[2]; LLVector3d vec3D; vec3D = (LLVector3d)llsd; - { - INFO("1:operator=:Fail to initialize "); - CHECK(llsd[0].asReal()== vec3D.mdV[VX]); - CHECK(llsd[1].asReal() == vec3D.mdV[VY]); - CHECK(llsd[2].asReal() == vec3D.mdV[VZ]); - } + CHECK(llsd[0].asReal()== vec3D.mdV[VX]); + CHECK(llsd[1].asReal() == vec3D.mdV[VY]); + CHECK(llsd[2].asReal() == vec3D.mdV[VZ]); } TEST_CASE("v3dmath_h: clearVec/zeroVec and the setVec overloads", "[v3dmath_h]") @@ -115,47 +103,36 @@ namespace F64 x = 2.32f, y = 1.212f, z = -.12f; LLVector3d vec3D(x,y,z); vec3D.clearVec(); - { - INFO("1:clearVec:Fail to initialize "); - CHECK(vec3D.mdV[VX] == 0); - CHECK(vec3D.mdV[VY] == 0); - CHECK(vec3D.mdV[VZ] == 0); - } + CHECK(vec3D.mdV[VX] == 0); + CHECK(vec3D.mdV[VY] == 0); + CHECK(vec3D.mdV[VZ] == 0); + vec3D.setVec(x,y,z); - { - INFO("2:setVec:Fail to initialize "); - CHECK(x == vec3D.mdV[VX]); - CHECK(y == vec3D.mdV[VY]); - CHECK(z == vec3D.mdV[VZ]); - } + CHECK(x == vec3D.mdV[VX]); + CHECK(y == vec3D.mdV[VY]); + CHECK(z == vec3D.mdV[VZ]); + vec3D.zeroVec(); - { - INFO("3:zeroVec:Fail to initialize "); - CHECK(vec3D.mdV[VX] == 0); - CHECK(vec3D.mdV[VY] == 0); - CHECK(vec3D.mdV[VZ] == 0); - } + CHECK(vec3D.mdV[VX] == 0); + CHECK(vec3D.mdV[VY] == 0); + CHECK(vec3D.mdV[VZ] == 0); + vec3D.clearVec(); LLVector3 vec3((F32)x,(F32)y,(F32)z); vec3D.setVec(vec3); - { - INFO("4:setVec:Fail to initialize "); - CHECK(x == vec3D.mdV[VX]); - CHECK(y == vec3D.mdV[VY]); - CHECK(z == vec3D.mdV[VZ]); - } + CHECK(x == vec3D.mdV[VX]); + CHECK(y == vec3D.mdV[VY]); + CHECK(z == vec3D.mdV[VZ]); + vec3D.clearVec(); const F64 vec[3] = {x,y,z}; vec3D.setVec(vec); - { - INFO("5:setVec:Fail to initialize "); - CHECK(x == vec3D.mdV[VX]); - CHECK(y == vec3D.mdV[VY]); - CHECK(z == vec3D.mdV[VZ]); - } + CHECK(x == vec3D.mdV[VX]); + CHECK(y == vec3D.mdV[VY]); + CHECK(z == vec3D.mdV[VZ]); + LLVector3d vec3Da; vec3Da.setVec(vec3D); - UNSCOPED_INFO("6:setVec: Fail to initialize"); CHECK(vec3D == vec3Da); } @@ -164,24 +141,18 @@ namespace F64 x = -2.32, y = 1.212, z = -.12; LLVector3d vec3D(x,y,z); vec3D.abs(); - { - INFO("1:abs:Fail "); - CHECK(-x == vec3D.mdV[VX]); - CHECK(y == vec3D.mdV[VY]); - CHECK(-z == vec3D.mdV[VZ]); - } - UNSCOPED_INFO("2:isNull():Fail "); + CHECK(-x == vec3D.mdV[VX]); + CHECK(y == vec3D.mdV[VY]); + CHECK(-z == vec3D.mdV[VZ]); + CHECK_FALSE(vec3D.isNull()); vec3D.clearVec(); x =.00000001, y = .000001001, z = .000001001; vec3D.setVec(x,y,z); - UNSCOPED_INFO("3:isNull():Fail "); CHECK(vec3D.isNull()); - UNSCOPED_INFO("4:isExactlyZero():Fail "); CHECK_FALSE(vec3D.isExactlyZero()); x =.0000000, y = .00000000, z = .00000000; vec3D.setVec(x,y,z); - UNSCOPED_INFO("5:isExactlyZero():Fail "); CHECK(vec3D.isExactlyZero()); } @@ -190,23 +161,17 @@ namespace F64 x = -2.32, y = 1.212, z = -.12; LLVector3d vec3D(x,y,z); - UNSCOPED_INFO("1:operator [] failed"); CHECK(x == vec3D[0]); - UNSCOPED_INFO("2:operator [] failed"); CHECK(y == vec3D[1]); - UNSCOPED_INFO("3:operator [] failed"); CHECK(z == vec3D[2]); vec3D.clearVec(); x = 23.23, y = -.2361, z = 3.25; vec3D.setVec(x,y,z); F64 &ref1 = vec3D[0]; - UNSCOPED_INFO("4:operator [] failed"); CHECK(ref1 == vec3D[0]); F64 &ref2 = vec3D[1]; - UNSCOPED_INFO("5:operator [] failed"); CHECK(ref2 == vec3D[1]); F64 &ref3 = vec3D[2]; - UNSCOPED_INFO("6:operator [] failed"); CHECK(ref3 == vec3D[2]); } @@ -216,12 +181,9 @@ namespace LLVector4 vec4(x,y,z); LLVector3d vec3D; vec3D = vec4; - { - INFO("1:operator=:Fail to initialize "); - CHECK(vec4.mV[VX] == vec3D.mdV[VX]); - CHECK(vec4.mV[VY] == vec3D.mdV[VY]); - CHECK(vec4.mV[VZ] == vec3D.mdV[VZ]); - } + CHECK(vec4.mV[VX] == vec3D.mdV[VX]); + CHECK(vec4.mV[VY] == vec3D.mdV[VY]); + CHECK(vec4.mV[VZ] == vec3D.mdV[VZ]); } TEST_CASE("v3dmath_h: operator+ and operator+=", "[v3dmath_h]") @@ -230,26 +192,20 @@ namespace F64 x2 = 1.2, y2 = 2.5, z2 = 1.; LLVector3d vec3D(x1,y1,z1),vec3Da(x2,y2,z2),vec3Db; vec3Db = vec3Da+ vec3D; - { - INFO("1:operator+:Fail to initialize "); - CHECK(x1+x2 == vec3Db.mdV[VX]); - CHECK(y1+y2 == vec3Db.mdV[VY]); - CHECK(z1+z2 == vec3Db.mdV[VZ]); - } + CHECK(x1+x2 == vec3Db.mdV[VX]); + CHECK(y1+y2 == vec3Db.mdV[VY]); + CHECK(z1+z2 == vec3Db.mdV[VZ]); + x1 = -2.45, y1 = 2.1, z1 = 3.0; vec3D.clearVec(); vec3Da.clearVec(); vec3D.setVec(x1,y1,z1); vec3Da += vec3D; - UNSCOPED_INFO("2:operator+=: Fail to initialize"); CHECK(vec3Da == vec3D); vec3Da += vec3D; - { - INFO("3:operator+=:Fail to initialize "); - CHECK(2*x1 == vec3Da.mdV[VX]); - CHECK(2*y1 == vec3Da.mdV[VY]); - CHECK(2*z1 == vec3Da.mdV[VZ]); - } + CHECK(2*x1 == vec3Da.mdV[VX]); + CHECK(2*y1 == vec3Da.mdV[VY]); + CHECK(2*z1 == vec3Da.mdV[VZ]); } TEST_CASE("v3dmath_h: operator- and operator-=", "[v3dmath_h]") @@ -258,83 +214,47 @@ namespace F64 x2 = 1.2, y2 = 2.5, z2 = 1.; LLVector3d vec3D(x1,y1,z1),vec3Da(x2,y2,z2),vec3Db; vec3Db = vec3Da - vec3D; - { - INFO("1:operator-:Fail to initialize "); - CHECK(x2-x1 == vec3Db.mdV[VX]); - CHECK(y2-y1 == vec3Db.mdV[VY]); - CHECK(z2-z1 == vec3Db.mdV[VZ]); - } + CHECK(x2-x1 == vec3Db.mdV[VX]); + CHECK(y2-y1 == vec3Db.mdV[VY]); + CHECK(z2-z1 == vec3Db.mdV[VZ]); + x1 = -2.45, y1 = 2.1, z1 = 3.0; vec3D.clearVec(); vec3Da.clearVec(); vec3D.setVec(x1,y1,z1); vec3Da -=vec3D; - { - INFO("2:operator-=:Fail to initialize "); - CHECK(vec3Da.mdV[VX] == 2.45); - CHECK(vec3Da.mdV[VY] == -2.1); - CHECK(vec3Da.mdV[VZ] == -3.0); - } + CHECK(vec3Da.mdV[VX] == 2.45); + CHECK(vec3Da.mdV[VY] == -2.1); + CHECK(vec3Da.mdV[VZ] == -3.0); + vec3Da -= vec3D; - { - INFO("3:operator-=:Fail to initialize "); - CHECK(-2*x1 == vec3Da.mdV[VX]); - CHECK(-2*y1 == vec3Da.mdV[VY]); - CHECK(-2*z1 == vec3Da.mdV[VZ]); - } + CHECK(-2*x1 == vec3Da.mdV[VX]); + CHECK(-2*y1 == vec3Da.mdV[VY]); + CHECK(-2*z1 == vec3Da.mdV[VZ]); } + TEST_CASE("v3dmath_h: operator* dot and scalar products and operator*=", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; F64 x2 = 1.2, y2 = 2.5, z2 = 1.; LLVector3d vec3D(x1,y1,z1),vec3Da(x2,y2,z2); F64 res = vec3D * vec3Da; - UNSCOPED_INFO("1:operator* failed"); - CHECK_THAT(res, - ll_test::WithinBits((x1*x2 + y1*y2 + z1*z2), - 8)); + CHECK_THAT(res, ll_test::WithinBits(x1*x2 + y1*y2 + z1*z2, 8)); vec3Da.clearVec(); F64 mulVal = 4.2; vec3Da = vec3D * mulVal; - UNSCOPED_INFO("2a:operator* failed"); - CHECK_THAT(vec3Da.mdV[VX], - ll_test::WithinBits(x1*mulVal, - 8)); - UNSCOPED_INFO("2b:operator* failed"); - CHECK_THAT(vec3Da.mdV[VY], - ll_test::WithinBits(y1*mulVal, - 8)); - UNSCOPED_INFO("2c:operator* failed"); - CHECK_THAT(vec3Da.mdV[VZ], - ll_test::WithinBits(z1*mulVal, - 8)); + CHECK_THAT(vec3Da.mdV[VX], ll_test::WithinBits(x1*mulVal, 8)); + CHECK_THAT(vec3Da.mdV[VY], ll_test::WithinBits(y1*mulVal, 8)); + CHECK_THAT(vec3Da.mdV[VZ], ll_test::WithinBits(z1*mulVal, 8)); vec3Da.clearVec(); vec3Da = mulVal * vec3D; - UNSCOPED_INFO("3a:operator* failed"); - CHECK_THAT(vec3Da.mdV[VX], - ll_test::WithinBits(x1*mulVal, - 8)); - UNSCOPED_INFO("3b:operator* failed"); - CHECK_THAT(vec3Da.mdV[VY], - ll_test::WithinBits(y1*mulVal, - 8)); - UNSCOPED_INFO("3c:operator* failed"); - CHECK_THAT(vec3Da.mdV[VZ], - ll_test::WithinBits(z1*mulVal, - 8)); + CHECK_THAT(vec3Da.mdV[VX], ll_test::WithinBits(x1*mulVal, 8)); + CHECK_THAT(vec3Da.mdV[VY], ll_test::WithinBits(y1*mulVal, 8)); + CHECK_THAT(vec3Da.mdV[VZ], ll_test::WithinBits(z1*mulVal, 8)); vec3D *= mulVal; - UNSCOPED_INFO("4a:operator*= failed"); - CHECK_THAT(vec3D.mdV[VX], - ll_test::WithinBits(x1*mulVal, - 8)); - UNSCOPED_INFO("4b:operator*= failed"); - CHECK_THAT(vec3D.mdV[VY], - ll_test::WithinBits(y1*mulVal, - 8)); - UNSCOPED_INFO("4c:operator*= failed"); - CHECK_THAT(vec3D.mdV[VZ], - ll_test::WithinBits(z1*mulVal, - 8)); + CHECK_THAT(vec3D.mdV[VX], ll_test::WithinBits(x1*mulVal, 8)); + CHECK_THAT(vec3D.mdV[VY], ll_test::WithinBits(y1*mulVal, 8)); + CHECK_THAT(vec3D.mdV[VZ], ll_test::WithinBits(z1*mulVal, 8)); } TEST_CASE("v3dmath_h: operator% and operator%= cross products", "[v3dmath_h]") @@ -347,19 +267,14 @@ namespace val1 = y1*z2 - y2*z1; val2 = z1*x2 -z2*x1; val3 = x1*y2-x2*y1; - { - INFO("1:operator% failed"); - CHECK(val1 == vec3Db.mdV[VX]); - CHECK(val2 == vec3Db.mdV[VY]); - CHECK(val3 == vec3Db.mdV[VZ]); - } + CHECK(val1 == vec3Db.mdV[VX]); + CHECK(val2 == vec3Db.mdV[VY]); + CHECK(val3 == vec3Db.mdV[VZ]); + vec3D %= vec3Da; - { - INFO("2:operator%= failed"); - CHECK(is_approx_equal(vec3D.mdV[VX],vec3Db.mdV[VX])); - CHECK(is_approx_equal(vec3D.mdV[VY],vec3Db.mdV[VY])); - CHECK(is_approx_equal(vec3D.mdV[VZ],vec3Db.mdV[VZ])); - } + CHECK(is_approx_equal(vec3D.mdV[VX],vec3Db.mdV[VX])); + CHECK(is_approx_equal(vec3D.mdV[VY],vec3Db.mdV[VY])); + CHECK(is_approx_equal(vec3D.mdV[VZ],vec3Db.mdV[VZ])); } TEST_CASE("v3dmath_h: operator/ and operator/= divide by a scalar", "[v3dmath_h]") @@ -368,67 +283,36 @@ namespace F64 t = 1.f / div; LLVector3d vec3D(x1,y1,z1), vec3Da; vec3Da = vec3D/div; - UNSCOPED_INFO("1a:operator/ failed"); - CHECK_THAT(vec3Da.mdV[VX], - ll_test::WithinBits(x1*t, - 8)); - UNSCOPED_INFO("1b:operator/ failed"); - CHECK_THAT(vec3Da.mdV[VY], - ll_test::WithinBits(y1*t, - 8)); - UNSCOPED_INFO("1c:operator/ failed"); - CHECK_THAT(vec3Da.mdV[VZ], - ll_test::WithinBits(z1*t, - 8)); + CHECK_THAT(vec3Da.mdV[VX], ll_test::WithinBits(x1*t, 8)); + CHECK_THAT(vec3Da.mdV[VY], ll_test::WithinBits(y1*t, 8)); + CHECK_THAT(vec3Da.mdV[VZ], ll_test::WithinBits(z1*t, 8)); x1 = 1.23, y1 = 4., z1 = -2.32; vec3D.clearVec(); vec3Da.clearVec(); vec3D.setVec(x1,y1,z1); vec3Da = vec3D/div; - UNSCOPED_INFO("2a:operator/ failed"); - CHECK_THAT(vec3Da.mdV[VX], - ll_test::WithinBits(x1*t, - 8)); - UNSCOPED_INFO("2b:operator/ failed"); - CHECK_THAT(vec3Da.mdV[VY], - ll_test::WithinBits(y1*t, - 8)); - UNSCOPED_INFO("2c:operator/ failed"); - CHECK_THAT(vec3Da.mdV[VZ], - ll_test::WithinBits(z1*t, - 8)); + CHECK_THAT(vec3Da.mdV[VX], ll_test::WithinBits(x1*t, 8)); + CHECK_THAT(vec3Da.mdV[VY], ll_test::WithinBits(y1*t, 8)); + CHECK_THAT(vec3Da.mdV[VZ], ll_test::WithinBits(z1*t, 8)); vec3D /= div; - UNSCOPED_INFO("3a:operator/= failed"); - CHECK_THAT(vec3D.mdV[VX], - ll_test::WithinBits(x1*t, - 8)); - UNSCOPED_INFO("3b:operator/= failed"); - CHECK_THAT(vec3D.mdV[VY], - ll_test::WithinBits(y1*t, - 8)); - UNSCOPED_INFO("3c:operator/= failed"); - CHECK_THAT(vec3D.mdV[VZ], - ll_test::WithinBits(z1*t, - 8)); + CHECK_THAT(vec3D.mdV[VX], ll_test::WithinBits(x1*t, 8)); + CHECK_THAT(vec3D.mdV[VY], ll_test::WithinBits(y1*t, 8)); + CHECK_THAT(vec3D.mdV[VZ], ll_test::WithinBits(z1*t, 8)); } TEST_CASE("v3dmath_h: operator== and operator!=", "[v3dmath_h]") { F64 x1 = 1., y1 = 2., z1 = -1.1; LLVector3d vec3D(x1,y1,z1), vec3Da; - UNSCOPED_INFO("1:operator!= failed"); CHECK(vec3D !=vec3Da); vec3Da = vec3D; - UNSCOPED_INFO("2:operator== failed"); CHECK(vec3D ==vec3Da); vec3D.clearVec(); vec3Da.clearVec(); x1 = .211, y1 = 21.111, z1 = 23.22; vec3D.setVec(x1,y1,z1); vec3Da.setVec(x1,y1,z1); - UNSCOPED_INFO("3:operator== failed"); CHECK(vec3D ==vec3Da); - UNSCOPED_INFO("4:operator!= failed"); CHECK_FALSE(vec3D !=vec3Da); } @@ -440,7 +324,6 @@ namespace stream1 << vec3D; vec3Da.setVec(x1,y1,z1); stream2 << vec3Da; - UNSCOPED_INFO("1:operator << failed"); CHECK(stream1.str() == stream2.str()); } @@ -450,7 +333,6 @@ namespace std::string buf("1.23 2. 4"); LLVector3d vec3D, vec3Da(x1,y1,z1); LLVector3d::parseVector3d(buf, &vec3D); - UNSCOPED_INFO("1:parseVector3d: failed "); CHECK(vec3D == vec3Da); } @@ -459,7 +341,6 @@ namespace F64 x1 = 1., y1 = 2., z1 = -1.1; LLVector3d vec3D(x1,y1,z1), vec3Da; vec3Da = -vec3D; - UNSCOPED_INFO("1:operator- failed"); CHECK(vec3D == - vec3Da); } @@ -468,17 +349,12 @@ namespace F64 x = 1., y = 2., z = -1.1; LLVector3d vec3D(x,y,z); F64 res = (x*x + y*y + z*z) - vec3D.magVecSquared(); - { - INFO("1:magVecSquared:Fail "); - CHECK(-F_APPROXIMATELY_ZERO <= res); - CHECK(res <=F_APPROXIMATELY_ZERO); - } + CHECK(-F_APPROXIMATELY_ZERO <= res); + CHECK(res <=F_APPROXIMATELY_ZERO); + res = (F32) sqrt(x*x + y*y + z*z) - vec3D.magVec(); - { - INFO("2:magVec: Fail "); - CHECK(-F_APPROXIMATELY_ZERO <= res); - CHECK(res <=F_APPROXIMATELY_ZERO); - } + CHECK(-F_APPROXIMATELY_ZERO <= res); + CHECK(res <=F_APPROXIMATELY_ZERO); } TEST_CASE("v3dmath_h: normVec normalises long and tiny vectors", "[v3dmath_h]") @@ -487,34 +363,16 @@ namespace LLVector3d vec3D(x,y,z); F64 mag = vec3D.normVec(); mag = 1.f/ mag; - UNSCOPED_INFO("1a:normVec: Fail "); - CHECK_THAT(vec3D.mdV[VX], - ll_test::WithinBits(x * mag, - 8)); - UNSCOPED_INFO("1b:normVec: Fail "); - CHECK_THAT(vec3D.mdV[VY], - ll_test::WithinBits(y * mag, - 8)); - UNSCOPED_INFO("1c:normVec: Fail "); - CHECK_THAT(vec3D.mdV[VZ], - ll_test::WithinBits(z * mag, - 8)); + CHECK_THAT(vec3D.mdV[VX], ll_test::WithinBits(x * mag, 8)); + CHECK_THAT(vec3D.mdV[VY], ll_test::WithinBits(y * mag, 8)); + CHECK_THAT(vec3D.mdV[VZ], ll_test::WithinBits(z * mag, 8)); x = 0.000000001, y = 0.000000001, z = 0.000000001; vec3D.clearVec(); vec3D.setVec(x,y,z); mag = vec3D.normVec(); - UNSCOPED_INFO("2a:normVec: Fail "); - CHECK_THAT(vec3D.mdV[VX], - ll_test::WithinBits(x * mag, - 8)); - UNSCOPED_INFO("2b:normVec: Fail "); - CHECK_THAT(vec3D.mdV[VY], - ll_test::WithinBits(y * mag, - 8)); - UNSCOPED_INFO("2c:normVec: Fail "); - CHECK_THAT(vec3D.mdV[VZ], - ll_test::WithinBits(z * mag, - 8)); + CHECK_THAT(vec3D.mdV[VX], ll_test::WithinBits(x * mag, 8)); + CHECK_THAT(vec3D.mdV[VY], ll_test::WithinBits(y * mag, 8)); + CHECK_THAT(vec3D.mdV[VZ], ll_test::WithinBits(z * mag, 8)); } TEST_CASE("v3dmath_h: parseVector3d reads large values exactly", "[v3dmath_h]") @@ -525,7 +383,6 @@ namespace std::string buf("1111.232222 2222222222.22 422222222222"); LLVector3d vec3Da, vec3Db(x1,y1,z1); LLVector3d::parseVector3d(buf, &vec3Da); - UNSCOPED_INFO("1:parseVector3 failed"); CHECK(vec3Da == vec3Db); } @@ -539,12 +396,9 @@ namespace val3 = z1 + (z2 - z1)* val; LLVector3d vec3Da(x1,y1,z1),vec3Db(x2,y2,z2); LLVector3d vec3d = lerp(vec3Da,vec3Db,val); - { - INFO("1:lerp failed"); - CHECK(val1 ==vec3d.mdV[VX]); - CHECK(val2 ==vec3d.mdV[VY]); - CHECK(val3 ==vec3d.mdV[VZ]); - } + CHECK(val1 ==vec3d.mdV[VX]); + CHECK(val2 ==vec3d.mdV[VY]); + CHECK(val3 ==vec3d.mdV[VZ]); } TEST_CASE("v3dmath_h: clamp reports a component out of range", "[v3dmath_h]") @@ -552,11 +406,9 @@ namespace F64 x = 2.32, y = 1.212, z = -.12; F64 min = 0.0001, max = 3.0; LLVector3d vec3d(x,y,z); - UNSCOPED_INFO("1:clamp:Fail "); CHECK(vec3d.clamp(min, max)); x = 0.000001f, z = 5.3f; vec3d.setVec(x,y,z); - UNSCOPED_INFO("2:clamp:Fail "); CHECK(vec3d.clamp(min, max)); } @@ -565,12 +417,10 @@ namespace F64 x = 10., y = 20., z = -15.; F64 epsilon = .23425; LLVector3d vec3Da(x,y,z), vec3Db(x,y,z); - UNSCOPED_INFO("1:are_parallel: Fail "); CHECK(are_parallel(vec3Da,vec3Db,epsilon)); F64 x1 = -12., y1 = -20., z1 = -100.; vec3Db.clearVec(); vec3Db.setVec(x1,y1,z1); - UNSCOPED_INFO("2:are_parallel: Fail "); CHECK_FALSE(are_parallel(vec3Da,vec3Db,epsilon)); } @@ -580,7 +430,6 @@ namespace F64 angle1, angle2; LLVector3d vec3Da(x,y,z), vec3Db(x,y,z); angle1 = angle_between(vec3Da, vec3Db); - UNSCOPED_INFO("1:angle_between: Fail "); CHECK(angle1 == 0); F64 x1 = -1., y1 = -20., z1 = -1.; vec3Da.clearVec(); @@ -590,7 +439,6 @@ namespace vec3Da.normVec(); F64 angle = vec3Db*vec3Da; angle = acos(angle); - UNSCOPED_INFO("2:angle_between: Fail "); CHECK(angle == angle2); } } diff --git a/indra/llmath/tests/v3math_test.cpp b/indra/llmath/tests/v3math_test.cpp index b7810a622f..18ded63c88 100644 --- a/indra/llmath/tests/v3math_test.cpp +++ b/indra/llmath/tests/v3math_test.cpp @@ -43,28 +43,21 @@ namespace TEST_CASE("v3math_h: constructors set every component", "[v3math_h]") { LLVector3 vec3; - { - INFO("1:LLVector3:Fail to initialize "); - CHECK(vec3.mV[VX] == 0.f); - CHECK(vec3.mV[VY] == 0.f); - CHECK(vec3.mV[VZ] == 0.f); - } + CHECK(vec3.mV[VX] == 0.f); + CHECK(vec3.mV[VY] == 0.f); + CHECK(vec3.mV[VZ] == 0.f); + F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3a(x,y,z); - { - INFO("2:LLVector3:Fail to initialize "); - CHECK(vec3a.mV[VX] == 2.32f); - CHECK(vec3a.mV[VY] == 1.212f); - CHECK(vec3a.mV[VZ] == -.12f); - } + CHECK(vec3a.mV[VX] == 2.32f); + CHECK(vec3a.mV[VY] == 1.212f); + CHECK(vec3a.mV[VZ] == -.12f); + const F32 vec[3] = {1.2f ,3.2f, -4.2f}; LLVector3 vec3b(vec); - { - INFO("3:LLVector3:Fail to initialize "); - CHECK(vec3b.mV[VX] == 1.2f); - CHECK(vec3b.mV[VY] == 3.2f); - CHECK(vec3b.mV[VZ] == -4.2f); - } + CHECK(vec3b.mV[VX] == 1.2f); + CHECK(vec3b.mV[VY] == 3.2f); + CHECK(vec3b.mV[VZ] == -4.2f); } TEST_CASE("v3math_h: conversion from LLVector3d and LLVector4", "[v3math_h]") @@ -73,11 +66,9 @@ namespace LLVector3 vec3(x,y,z); LLVector3d vector3d(vec3); LLVector3 vec3a(vector3d); - UNSCOPED_INFO("1:LLVector3:Fail to initialize "); CHECK(vec3 == vec3a); LLVector4 vector4(vec3); LLVector3 vec3b(vector4); - UNSCOPED_INFO("2:LLVector3:Fail to initialize "); CHECK(vec3 == vec3b); } @@ -88,7 +79,6 @@ namespace LLVector3 vec3(llsd); LLSD sd = vec3.getValue(); LLVector3 vec3a(sd); - UNSCOPED_INFO("1:LLVector3:Fail to initialize "); CHECK(vec3 == vec3a); } @@ -98,7 +88,6 @@ namespace LLSD llsd(a); LLVector3 vec3(llsd),vec3a; vec3a = vec3; - UNSCOPED_INFO("1:Operator= Fail to initialize "); CHECK(vec3 == vec3a); } @@ -106,29 +95,21 @@ namespace { F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3(x,y,z); - UNSCOPED_INFO("1:isFinite= Fail to initialize "); CHECK(vec3.isFinite());//need more test cases: vec3.clearVec(); - { - INFO("2:clearVec:Fail to set values "); - CHECK(vec3.mV[VX] == 0.f); - CHECK(vec3.mV[VY] == 0.f); - CHECK(vec3.mV[VZ] == 0.f); - } + CHECK(vec3.mV[VX] == 0.f); + CHECK(vec3.mV[VY] == 0.f); + CHECK(vec3.mV[VZ] == 0.f); + vec3.setVec(x,y,z); - { - INFO("3:setVec:Fail to set values "); - CHECK(vec3.mV[VX] == 2.32f); - CHECK(vec3.mV[VY] == 1.212f); - CHECK(vec3.mV[VZ] == -.12f); - } + CHECK(vec3.mV[VX] == 2.32f); + CHECK(vec3.mV[VY] == 1.212f); + CHECK(vec3.mV[VZ] == -.12f); + vec3.zeroVec(); - { - INFO("4:zeroVec:Fail to set values "); - CHECK(vec3.mV[VX] == 0.f); - CHECK(vec3.mV[VY] == 0.f); - CHECK(vec3.mV[VZ] == 0.f); - } + CHECK(vec3.mV[VX] == 0.f); + CHECK(vec3.mV[VY] == 0.f); + CHECK(vec3.mV[VZ] == 0.f); } TEST_CASE("v3math_h: abs and the setVec overloads", "[v3math_h]") @@ -136,33 +117,26 @@ namespace F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3(x,y,z),vec3a; vec3.abs(); - { - INFO("1:abs:Fail "); - CHECK(x == vec3.mV[VX]); - CHECK(y == vec3.mV[VY]); - CHECK(-z == vec3.mV[VZ]); - } + CHECK(x == vec3.mV[VX]); + CHECK(y == vec3.mV[VY]); + CHECK(-z == vec3.mV[VZ]); + vec3a.setVec(vec3); - UNSCOPED_INFO("2:setVec:Fail to initialize "); CHECK(vec3a == vec3); const F32 vec[3] = {1.2f ,3.2f, -4.2f}; vec3.clearVec(); vec3.setVec(vec); - { - INFO("3:setVec:Fail to initialize "); - CHECK(vec3.mV[VX] == 1.2f); - CHECK(vec3.mV[VY] == 3.2f); - CHECK(vec3.mV[VZ] == -4.2f); - } + CHECK(vec3.mV[VX] == 1.2f); + CHECK(vec3.mV[VY] == 3.2f); + CHECK(vec3.mV[VZ] == -4.2f); + vec3a.clearVec(); LLVector3d vector3d(vec3); vec3a.setVec(vector3d); - UNSCOPED_INFO("4:setVec:Fail to initialize "); CHECK(vec3 == vec3a); LLVector4 vector4(vec3); vec3a.clearVec(); vec3a.setVec(vector4); - UNSCOPED_INFO("5:setVec:Fail to initialize "); CHECK(vec3 == vec3a); } @@ -171,16 +145,13 @@ namespace F32 x = 2.32f, y = 3.212f, z = -.12f; F32 min = 0.0001f, max = 3.0f; LLVector3 vec3(x,y,z); - { - INFO("1:clamp:Fail "); - CHECK(vec3.clamp(min, max)); - CHECK(x == vec3.mV[VX]); - CHECK(max == vec3.mV[VY]); - CHECK(min == vec3.mV[VZ]); - } + CHECK(vec3.clamp(min, max)); + CHECK(x == vec3.mV[VX]); + CHECK(max == vec3.mV[VY]); + CHECK(min == vec3.mV[VZ]); + x = 1.f, y = 2.2f, z = 2.8f; vec3.setVec(x,y,z); - UNSCOPED_INFO("2:clamp:Fail "); CHECK_FALSE(vec3.clamp(min, max)); } @@ -188,9 +159,7 @@ namespace { F32 x = 2.32f, y = 1.212f, z = -.12f; LLVector3 vec3(x,y,z); - UNSCOPED_INFO("1:magVecSquared:Fail "); CHECK(is_approx_equal(vec3.magVecSquared(), (x*x + y*y + z*z))); - UNSCOPED_INFO("2:magVec:Fail "); CHECK(is_approx_equal(vec3.magVec(), (F32) sqrt(x*x + y*y + z*z))); } @@ -198,13 +167,10 @@ namespace { F32 x =-2.0f, y = -3.0f, z = 1.23f ; LLVector3 vec3(x,y,z); - UNSCOPED_INFO("1:abs():Fail "); CHECK(vec3.abs()); - UNSCOPED_INFO("2:isNull():Fail"); CHECK_FALSE(vec3.isNull()); //Returns true if vector has a _very_small_ length x =.00000001f, y = .000001001f, z = .000001001f; vec3.setVec(x,y,z); - UNSCOPED_INFO("3:isNull(): Fail "); CHECK(vec3.isNull()); } @@ -212,24 +178,18 @@ namespace { F32 x =-2.0f, y = -3.0f, z = 1.f ; LLVector3 vec3(x,y,z),vec3a; - UNSCOPED_INFO("1:isExactlyZero():Fail "); CHECK(vec3a.isExactlyZero()); vec3a = vec3a.scaleVec(vec3); - { - INFO("2:scaleVec: Fail "); - CHECK(vec3a.mV[VX] == 0.f); - CHECK(vec3a.mV[VY] == 0.f); - CHECK(vec3a.mV[VZ] == 0.f); - } + CHECK(vec3a.mV[VX] == 0.f); + CHECK(vec3a.mV[VY] == 0.f); + CHECK(vec3a.mV[VZ] == 0.f); + vec3a.setVec(x,y,z); vec3a = vec3a.scaleVec(vec3); - { - INFO("3:scaleVec: Fail "); - CHECK(vec3a.mV[VX] == 4); - CHECK(vec3a.mV[VY] == 9); - CHECK(vec3a.mV[VZ] == 1); - } - UNSCOPED_INFO("4:isExactlyZero():Fail "); + CHECK(vec3a.mV[VX] == 4); + CHECK(vec3a.mV[VY] == 9); + CHECK(vec3a.mV[VZ] == 1); + CHECK_FALSE(vec3.isExactlyZero()); } @@ -241,7 +201,6 @@ namespace vec3a = vec3a.rotVec(angle, vec3); LLVector3 vec3b(1.f,2.f,3.f); vec3b = vec3b.rotVec(angle, vec3); - UNSCOPED_INFO("rotVec():Fail"); CHECK(vec3b == vec3a); } @@ -249,24 +208,18 @@ namespace { F32 x =-2.0f, y = -3.0f, z = 1.f ; LLVector3 vec3(x,y,z); - UNSCOPED_INFO("1:operator [] failed"); CHECK(x == vec3[0]); - UNSCOPED_INFO("2:operator [] failed"); CHECK(y == vec3[1]); - UNSCOPED_INFO("3:operator [] failed"); CHECK(z == vec3[2]); vec3.clearVec(); x = 23.f, y = -.2361f, z = 3.25; vec3.setVec(x,y,z); F32 &ref1 = vec3[0]; - UNSCOPED_INFO("4:operator [] failed"); CHECK(ref1 == vec3[0]); F32 &ref2 = vec3[1]; - UNSCOPED_INFO("5:operator [] failed"); CHECK(ref2 == vec3[1]); F32 &ref3 = vec3[2]; - UNSCOPED_INFO("6:operator [] failed"); CHECK(ref3 == vec3[2]); } @@ -279,12 +232,9 @@ namespace val1 = x1+x2; val2 = y1+y2; val3 = z1+z2; - { - INFO("1:operator+ failed"); - CHECK(val1 == vec3b.mV[VX]); - CHECK(val2 == vec3b.mV[VY]); - CHECK(val3 == vec3b.mV[VZ]); - } + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); vec3.clearVec(); vec3a.clearVec(); @@ -296,12 +246,9 @@ namespace val1 = x1+x2; val2 = y1+y2; val3 = z1+z2; - { - INFO("2:operator+ failed"); - CHECK(val1 == vec3b.mV[VX]); - CHECK(val2 == vec3b.mV[VY]); - CHECK(val3 == vec3b.mV[VZ]); - } + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); } TEST_CASE("v3math_h: operator- subtracts componentwise", "[v3math_h]") @@ -313,12 +260,9 @@ namespace val1 = x1-x2; val2 = y1-y2; val3 = z1-z2; - { - INFO("1:operator- failed"); - CHECK(val1 == vec3b.mV[VX]); - CHECK(val2 == vec3b.mV[VY]); - CHECK(val3 == vec3b.mV[VZ]); - } + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); vec3.clearVec(); vec3a.clearVec(); @@ -330,12 +274,9 @@ namespace val1 = x1-x2; val2 = y1-y2; val3 = z1-z2; - { - INFO("2:operator- failed"); - CHECK(val1 == vec3b.mV[VX]); - CHECK(val2 == vec3b.mV[VY]); - CHECK(val3 == vec3b.mV[VZ]); - } + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); } TEST_CASE("v3math_h: operator* as dot product and scalar product", "[v3math_h]") @@ -345,7 +286,6 @@ namespace LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); val1 = vec3 * vec3a; val2 = x1*x2 + y1*y2 + z1*z2; - UNSCOPED_INFO("1:operator* failed"); CHECK(val1 == val2); vec3a.clearVec(); @@ -354,20 +294,15 @@ namespace val1 = x1*mulVal; val2 = y1*mulVal; val3 = z1*mulVal; - { - INFO("2:operator* failed"); - CHECK(val1 == vec3a.mV[VX]); - CHECK(val2 == vec3a.mV[VY]); - CHECK(val3 == vec3a.mV[VZ]); - } + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); + vec3a.clearVec(); vec3a = mulVal * vec3; - { - INFO("3:operator* failed "); - CHECK(val1 == vec3a.mV[VX]); - CHECK(val2 == vec3a.mV[VY]); - CHECK(val3 == vec3a.mV[VZ]); - } + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); } TEST_CASE("v3math_h: operator% is the cross product", "[v3math_h]") @@ -379,12 +314,9 @@ namespace val1 = y1*z2 - y2*z1; val2 = z1*x2 -z2*x1; val3 = x1*y2-x2*y1; - { - INFO("1:operator% failed"); - CHECK(val1 == vec3b.mV[VX]); - CHECK(val2 == vec3b.mV[VY]); - CHECK(val3 == vec3b.mV[VZ]); - } + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); vec3.clearVec(); vec3a.clearVec(); @@ -396,12 +328,9 @@ namespace val1 = y1*z2 - y2*z1; val2 = z1*x2 -z2*x1; val3 = x1*y2-x2*y1; - { - INFO("2:operator% failed "); - CHECK(val1 == vec3b.mV[VX]); - CHECK(val2 == vec3b.mV[VY]); - CHECK(val3 == vec3b.mV[VZ]); - } + CHECK(val1 == vec3b.mV[VX]); + CHECK(val2 == vec3b.mV[VY]); + CHECK(val3 == vec3b.mV[VZ]); } TEST_CASE("v3math_h: operator/ divides by a scalar", "[v3math_h]") @@ -413,12 +342,9 @@ namespace val1 = x1 * t; val2 = y1 * t; val3 = z1 *t; - { - INFO("1:operator/ failed"); - CHECK(val1 == vec3a.mV[VX]); - CHECK(val2 == vec3a.mV[VY]); - CHECK(val3 == vec3a.mV[VZ]); - } + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); vec3a.clearVec(); x1 = -.235f, y1 = -24.32f, z1 = .342f, div = -2.2f; @@ -428,19 +354,15 @@ namespace val1 = x1 * t; val2 = y1 * t; val3 = z1 *t; - { - INFO("2:operator/ failed"); - CHECK(val1 == vec3a.mV[VX]); - CHECK(val2 == vec3a.mV[VY]); - CHECK(val3 == vec3a.mV[VZ]); - } + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); } TEST_CASE("v3math_h: operator== for equal vectors", "[v3math_h]") { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f; LLVector3 vec3(x1,y1,z1), vec3a(x1,y1,z1); - UNSCOPED_INFO("1:operator== failed"); CHECK(vec3 == vec3a); vec3a.clearVec(); @@ -449,7 +371,6 @@ namespace vec3a.clearVec(); vec3.setVec(x1,y1,z1); vec3a.setVec(x1,y1,z1); - UNSCOPED_INFO("2:operator== failed "); CHECK(vec3 == vec3a); } @@ -457,13 +378,11 @@ namespace { F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.234f,z2 = 11.2f;; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); - UNSCOPED_INFO("1:operator!= failed"); CHECK(vec3a != vec3); vec3.clearVec(); vec3.clearVec(); vec3a.setVec(vec3); - UNSCOPED_INFO("2:operator!= failed"); CHECK_FALSE(vec3a != vec3); } @@ -476,12 +395,9 @@ namespace val1 = x1+x2; val2 = y1+y2; val3 = z1+z2; - { - INFO("1:operator+= failed"); - CHECK(val1 == vec3a.mV[VX]); - CHECK(val2 == vec3a.mV[VY]); - CHECK(val3 == vec3a.mV[VZ]); - } + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); } TEST_CASE("v3math_h: operator-= subtracts componentwise", "[v3math_h]") @@ -493,12 +409,9 @@ namespace val1 = x2-x1; val2 = y2-y1; val3 = z2-z1; - { - INFO("1:operator-= failed"); - CHECK(val1 == vec3a.mV[VX]); - CHECK(val2 == vec3a.mV[VY]); - CHECK(val3 == vec3a.mV[VZ]); - } + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); } TEST_CASE("v3math_h: operator*= by a vector and by a scalar", "[v3math_h]") @@ -510,24 +423,18 @@ namespace val1 = x1*x2; val2 = y1*y2; val3 = z1*z2; - { - INFO("1:operator*= failed"); - CHECK(val1 == vec3a.mV[VX]); - CHECK(val2 == vec3a.mV[VY]); - CHECK(val3 == vec3a.mV[VZ]); - } + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); F32 mulVal = 4.332f; vec3 *=mulVal; val1 = x1*mulVal; val2 = y1*mulVal; val3 = z1*mulVal; - { - INFO("2:operator*= failed "); - CHECK(is_approx_equal(val1, vec3.mV[VX])); - CHECK(is_approx_equal(val2, vec3.mV[VY])); - CHECK(is_approx_equal(val3, vec3.mV[VZ])); - } + CHECK(is_approx_equal(val1, vec3.mV[VX])); + CHECK(is_approx_equal(val2, vec3.mV[VY])); + CHECK(is_approx_equal(val3, vec3.mV[VZ])); } TEST_CASE("v3math_h: operator%= matches operator%", "[v3math_h]") @@ -536,7 +443,6 @@ namespace LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2),vec3b; vec3b = vec3a % vec3; vec3a %= vec3; - UNSCOPED_INFO("1:operator%= failed"); CHECK(vec3a == vec3b); } @@ -549,12 +455,9 @@ namespace val1 = x1 * t; val2 = y1 * t; val3 = z1 *t; - { - INFO("1:operator/= failed"); - CHECK(val1 == vec3a.mV[VX]); - CHECK(val2 == vec3a.mV[VY]); - CHECK(val3 == vec3a.mV[VZ]); - } + CHECK(val1 == vec3a.mV[VX]); + CHECK(val2 == vec3a.mV[VY]); + CHECK(val3 == vec3a.mV[VZ]); } TEST_CASE("v3math_h: unary operator- negates", "[v3math_h]") @@ -562,7 +465,6 @@ namespace F32 x1 =1.f, y1 = 2.f,z1 = 1.2f; LLVector3 vec3(x1,y1,z1), vec3a; vec3a = -vec3; - UNSCOPED_INFO("1:operator- failed"); CHECK(-vec3a == vec3); } @@ -574,7 +476,6 @@ namespace stream1 << vec3; vec3a.setVec(x1,y1,z1); stream2 << vec3a; - UNSCOPED_INFO("1:operator << failed"); CHECK(stream1.str() == stream2.str()); } @@ -582,17 +483,14 @@ namespace { F32 x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 1.11f, z2 = 1234.234f; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); - UNSCOPED_INFO("1:operator< failed"); CHECK(vec3 < vec3a); x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 2.f, z2 = 1234.234f; vec3.setVec(x1,y1,z1); vec3a.setVec(x2,y2,z2); - UNSCOPED_INFO("2:operator< failed "); CHECK(vec3 < vec3a); x1 =2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, vec3.setVec(x1,y1,z1); vec3a.setVec(x2,y2,z2); - UNSCOPED_INFO("3:operator< failed "); CHECK_FALSE(vec3 < vec3a); } @@ -602,7 +500,6 @@ namespace std::string buf("1.23 2. 4"); LLVector3 vec3, vec3a(x1,y1,z1); LLVector3::parseVector3(buf, &vec3); - UNSCOPED_INFO("1:parseVector3 failed"); CHECK(vec3 == vec3a); } @@ -612,17 +509,13 @@ namespace LLVector3 vec3(x1,y1,z1),vec3a,vec3b; vec3a.setVec(1,1,1); vec3a.scaleVec(vec3); - UNSCOPED_INFO("1:scaleVec failed"); CHECK(vec3 == vec3a); vec3a.clearVec(); vec3a.setVec(x1,y1,z1); vec3a.scaleVec(vec3); - { - INFO("2:scaleVec failed"); - CHECK(vec3a.mV[VX] == 1.f); - CHECK(vec3a.mV[VY] == 4.f); - CHECK(vec3a.mV[VZ] == 16.f); - } + CHECK(vec3a.mV[VX] == 1.f); + CHECK(vec3a.mV[VY] == 4.f); + CHECK(vec3a.mV[VZ] == 16.f); } TEST_CASE("v3math_h: lerp between two vectors", "[v3math_h]") @@ -634,12 +527,9 @@ namespace val3 = z1 + (z2 - z1)* val; LLVector3 vec3(x1,y1,z1),vec3a(x2,y2,z2); LLVector3 vec3b = lerp(vec3,vec3a,val); - { - INFO("1:lerp failed"); - CHECK(val1 ==vec3b.mV[VX]); - CHECK(val2 ==vec3b.mV[VY]); - CHECK(val3 ==vec3b.mV[VZ]); - } + CHECK(val1 ==vec3b.mV[VX]); + CHECK(val2 ==vec3b.mV[VY]); + CHECK(val3 ==vec3b.mV[VZ]); } TEST_CASE("v3math_h: dist_vec and its squared and 2D forms", "[v3math_h]") @@ -649,15 +539,12 @@ namespace LLVector3 vec3(x1,y1,z1),vec3a(x2,y2,z2); val1 = dist_vec(vec3,vec3a); val2 = (F32) sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2) + (z1 - z2)* (z1 -z2)); - UNSCOPED_INFO("1:dist_vec: Fail "); CHECK(val2 == val1); val1 = dist_vec_squared(vec3,vec3a); val2 =((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2) + (z1 - z2)* (z1 -z2)); - UNSCOPED_INFO("2:dist_vec_squared: Fail "); CHECK(val2 == val1); val1 = dist_vec_squared2D(vec3, vec3a); val2 =(x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2); - UNSCOPED_INFO("3:dist_vec_squared2D: Fail "); CHECK(val2 == val1); } @@ -668,24 +555,19 @@ namespace F32 mag = vec3.normVec(); mag = 1.f/ mag; F32 val1 = x* mag, val2 = y* mag, val3 = z* mag; - { - INFO("1:normVec: Fail "); - CHECK(is_approx_equal(val1, vec3.mV[VX])); - CHECK(is_approx_equal(val2, vec3.mV[VY])); - CHECK(is_approx_equal(val3, vec3.mV[VZ])); - } + CHECK(is_approx_equal(val1, vec3.mV[VX])); + CHECK(is_approx_equal(val2, vec3.mV[VY])); + CHECK(is_approx_equal(val3, vec3.mV[VZ])); + x = 0.000000001f, y = 0.f, z = 0.f; vec3.clearVec(); vec3.setVec(x,y,z); mag = vec3.normVec(); val1 = x* mag, val2 = y* mag, val3 = z* mag; - { - INFO("2:normVec: Fail "); - CHECK(mag == 0.); - CHECK(vec3.mV[VX] == 0.); - CHECK(vec3.mV[VY] == 0.); - CHECK(vec3.mV[VZ] == 0.); - } + CHECK(mag == 0.); + CHECK(vec3.mV[VX] == 0.); + CHECK(vec3.mV[VY] == 0.); + CHECK(vec3.mV[VZ] == 0.); } TEST_CASE("v3math_h: snap rounds to the given decimal places", "[v3math_h]") @@ -693,12 +575,9 @@ namespace F32 x = -202.23412f, y = 123.2312f, z = -89.f; LLVector3 vec(x,y,z); vec.snap(2); - { - INFO("1:snap: Fail "); - CHECK(is_approx_equal(-202.23f, vec.mV[VX])); - CHECK(is_approx_equal(123.23f, vec.mV[VY])); - CHECK(is_approx_equal(-89.f, vec.mV[VZ])); - } + CHECK(is_approx_equal(-202.23f, vec.mV[VX])); + CHECK(is_approx_equal(123.23f, vec.mV[VY])); + CHECK(is_approx_equal(-89.f, vec.mV[VZ])); } TEST_CASE("v3math_h: quantize16 and quantize8 match the U16 and U8 round trips", "[v3math_h]") @@ -711,23 +590,18 @@ namespace x1 = U16_to_F32(F32_to_U16(x, lowerxy, upperxy), lowerxy, upperxy); y1 = U16_to_F32(F32_to_U16(y, lowerxy, upperxy), lowerxy, upperxy); z1 = U16_to_F32(F32_to_U16(z, lowerz, upperz), lowerz, upperz); - { - INFO("1:quantize16: Fail "); - CHECK(is_approx_equal(x1, vec3.mV[VX])); - CHECK(is_approx_equal(y1, vec3.mV[VY])); - CHECK(is_approx_equal(z1, vec3.mV[VZ])); - } + CHECK(is_approx_equal(x1, vec3.mV[VX])); + CHECK(is_approx_equal(y1, vec3.mV[VY])); + CHECK(is_approx_equal(z1, vec3.mV[VZ])); + LLVector3 vec3a(x,y,z); vec3a.quantize8(lowerxy,upperxy,lowerz,upperz); x1 = U8_to_F32(F32_to_U8(x, lowerxy, upperxy), lowerxy, upperxy); y1 = U8_to_F32(F32_to_U8(y, lowerxy, upperxy), lowerxy, upperxy); z1 = U8_to_F32(F32_to_U8(z, lowerz, upperz), lowerz, upperz); - { - INFO("2:quantize8: Fail "); - CHECK(is_approx_equal(x1, vec3a.mV[VX])); - CHECK(is_approx_equal(y1, vec3a.mV[VY])); - CHECK(is_approx_equal(z1, vec3a.mV[VZ])); - } + CHECK(is_approx_equal(x1, vec3a.mV[VX])); + CHECK(is_approx_equal(y1, vec3a.mV[VY])); + CHECK(is_approx_equal(z1, vec3a.mV[VZ])); } TEST_CASE("v3math_h: LLSD arrays shorter than three leave the rest zero", "[v3math_h]") @@ -736,29 +610,20 @@ namespace sd[0] = 1.f; LLVector3 parsed_1(sd); - { - INFO("1:LLSD parse: Fail "); - CHECK(is_approx_equal(parsed_1.mV[VX], 1.f)); - CHECK(is_approx_equal(parsed_1.mV[VY], 0.f)); - CHECK(is_approx_equal(parsed_1.mV[VZ], 0.f)); - } + CHECK(is_approx_equal(parsed_1.mV[VX], 1.f)); + CHECK(is_approx_equal(parsed_1.mV[VY], 0.f)); + CHECK(is_approx_equal(parsed_1.mV[VZ], 0.f)); sd[1] = 2.f; LLVector3 parsed_2(sd); - { - INFO("2:LLSD parse: Fail "); - CHECK(is_approx_equal(parsed_2.mV[VX], 1.f)); - CHECK(is_approx_equal(parsed_2.mV[VY], 2.f)); - CHECK(is_approx_equal(parsed_2.mV[VZ], 0.f)); - } + CHECK(is_approx_equal(parsed_2.mV[VX], 1.f)); + CHECK(is_approx_equal(parsed_2.mV[VY], 2.f)); + CHECK(is_approx_equal(parsed_2.mV[VZ], 0.f)); sd[2] = 3.f; LLVector3 parsed_3(sd); - { - INFO("3:LLSD parse: Fail "); - CHECK(is_approx_equal(parsed_3.mV[VX], 1.f)); - CHECK(is_approx_equal(parsed_3.mV[VY], 2.f)); - CHECK(is_approx_equal(parsed_3.mV[VZ], 3.f)); - } + CHECK(is_approx_equal(parsed_3.mV[VX], 1.f)); + CHECK(is_approx_equal(parsed_3.mV[VY], 2.f)); + CHECK(is_approx_equal(parsed_3.mV[VZ], 3.f)); } } diff --git a/indra/llmath/tests/v4color_test.cpp b/indra/llmath/tests/v4color_test.cpp index 5789100b67..3f72e62c75 100644 --- a/indra/llmath/tests/v4color_test.cpp +++ b/indra/llmath/tests/v4color_test.cpp @@ -41,57 +41,41 @@ namespace TEST_CASE("v4color_h: constructors from values or an array or LLColor3 or LLSD or LLColor4U", "[v4color_h]") { LLColor4 llcolor4; - { - INFO("1:LLColor4:Fail to initialize "); - CHECK(llcolor4.mV[VRED] == 0); - CHECK(llcolor4.mV[VGREEN] == 0); - CHECK(llcolor4.mV[VBLUE] == 0); - CHECK(llcolor4.mV[VALPHA] == 1.0f); - } + CHECK(llcolor4.mV[VRED] == 0); + CHECK(llcolor4.mV[VGREEN] == 0); + CHECK(llcolor4.mV[VBLUE] == 0); + CHECK(llcolor4.mV[VALPHA] == 1.0f); F32 r = 0x20, g = 0xFFFF, b = 0xFF, a = 0xAF; LLColor4 llcolor4a(r,g,b); - { - INFO("2:LLColor4:Fail to initialize "); - CHECK(r == llcolor4a.mV[VRED]); - CHECK(g == llcolor4a.mV[VGREEN]); - CHECK(b == llcolor4a.mV[VBLUE]); - CHECK(llcolor4a.mV[VALPHA] == 1.0f); - } + CHECK(r == llcolor4a.mV[VRED]); + CHECK(g == llcolor4a.mV[VGREEN]); + CHECK(b == llcolor4a.mV[VBLUE]); + CHECK(llcolor4a.mV[VALPHA] == 1.0f); LLColor4 llcolor4b(r,g,b,a); - { - INFO("3:LLColor4:Fail to initialize "); - CHECK(r == llcolor4b.mV[VRED]); - CHECK(g == llcolor4b.mV[VGREEN]); - CHECK(b == llcolor4b.mV[VBLUE]); - CHECK(a == llcolor4b.mV[VALPHA]); - } + CHECK(r == llcolor4b.mV[VRED]); + CHECK(g == llcolor4b.mV[VGREEN]); + CHECK(b == llcolor4b.mV[VBLUE]); + CHECK(a == llcolor4b.mV[VALPHA]); const F32 vec[4] = {.112f ,23.2f, -4.2f, -.0001f}; LLColor4 llcolor4c(vec); - { - INFO("4:LLColor4:Fail to initialize "); - CHECK(vec[0] == llcolor4c.mV[VRED]); - CHECK(vec[1] == llcolor4c.mV[VGREEN]); - CHECK(vec[2] == llcolor4c.mV[VBLUE]); - CHECK(vec[3] == llcolor4c.mV[VALPHA]); - } + CHECK(vec[0] == llcolor4c.mV[VRED]); + CHECK(vec[1] == llcolor4c.mV[VGREEN]); + CHECK(vec[2] == llcolor4c.mV[VBLUE]); + CHECK(vec[3] == llcolor4c.mV[VALPHA]); LLColor3 llcolor3(-2.23f,1.01f,42.3f); F32 val = -.1f; LLColor4 llcolor4d(llcolor3,val); - { - INFO("5:LLColor4:Fail to initialize "); - CHECK(llcolor3.mV[VRED] == llcolor4d.mV[VRED]); - CHECK(llcolor3.mV[VGREEN] == llcolor4d.mV[VGREEN]); - CHECK(llcolor3.mV[VBLUE] == llcolor4d.mV[VBLUE]); - CHECK(val == llcolor4d.mV[VALPHA]); - } + CHECK(llcolor3.mV[VRED] == llcolor4d.mV[VRED]); + CHECK(llcolor3.mV[VGREEN] == llcolor4d.mV[VGREEN]); + CHECK(llcolor3.mV[VBLUE] == llcolor4d.mV[VBLUE]); + CHECK(val == llcolor4d.mV[VALPHA]); LLSD sd = llcolor4d.getValue(); LLColor4 llcolor4e(sd); - UNSCOPED_INFO("6:LLColor4:(LLSD) failed "); CHECK(llcolor4d == llcolor4e); U8 r1 = 0xF2, g1 = 0xFA, b1 = 0xBF; @@ -99,12 +83,9 @@ namespace LLColor4 llcolor4g(color4u); const F32 SCALE = 1.f/255.f; F32 r2 = r1*SCALE, g2 = g1* SCALE, b2 = b1* SCALE; - { - INFO("7:LLColor4:Fail to initialize "); - CHECK(r2 == llcolor4g.mV[VRED]); - CHECK(g2 == llcolor4g.mV[VGREEN]); - CHECK(b2 == llcolor4g.mV[VBLUE]); - } + CHECK(r2 == llcolor4g.mV[VRED]); + CHECK(g2 == llcolor4g.mV[VGREEN]); + CHECK(b2 == llcolor4g.mV[VBLUE]); } TEST_CASE("v4color_h: setValue and getValue round-trip through LLSD", "[v4color_h]") @@ -113,11 +94,9 @@ namespace LLSD llsd = llcolor.getValue(); LLColor4 llcolor4(llsd), llcolor4a; llcolor4a.setValue(llsd); - UNSCOPED_INFO("setValue: failed"); CHECK(llcolor4 == llcolor4a); LLSD sd = llcolor4a.getValue(); LLColor4 llcolor4b(sd); - UNSCOPED_INFO("getValue: Failed "); CHECK(llcolor4b == llcolor4a); } @@ -126,22 +105,16 @@ namespace F32 r = 0x20, g = 0xFFFF, b = 0xFF,a = 0xAF; LLColor4 llcolor4(r,g,b,a); llcolor4.setToBlack(); - { - INFO("setToBlack:Fail to set the black "); - CHECK(llcolor4.mV[VRED] == 0); - CHECK(llcolor4.mV[VGREEN] == 0); - CHECK(llcolor4.mV[VBLUE] == 0); - CHECK(llcolor4.mV[VALPHA] == 1.0f); - } + CHECK(llcolor4.mV[VRED] == 0); + CHECK(llcolor4.mV[VGREEN] == 0); + CHECK(llcolor4.mV[VBLUE] == 0); + CHECK(llcolor4.mV[VALPHA] == 1.0f); llcolor4.setToWhite(); - { - INFO("setToWhite:Fail to set the white "); - CHECK(llcolor4.mV[VRED] == 1.f); - CHECK(llcolor4.mV[VGREEN] == 1.f); - CHECK(llcolor4.mV[VBLUE] == 1.f); - CHECK(llcolor4.mV[VALPHA] == 1.0f); - } + CHECK(llcolor4.mV[VRED] == 1.f); + CHECK(llcolor4.mV[VGREEN] == 1.f); + CHECK(llcolor4.mV[VBLUE] == 1.f); + CHECK(llcolor4.mV[VALPHA] == 1.0f); } TEST_CASE("v4color_h: setVec from values or colors or an array or LLColor4U", "[v4color_h]") @@ -149,69 +122,50 @@ namespace F32 r = 0x20, g = 0xFFFF, b = 0xFF, a = 0xAF; LLColor4 llcolor4; llcolor4.setVec(r,g,b); - { - INFO("1:setVec:Fail to set the values "); - CHECK(r == llcolor4.mV[VRED]); - CHECK(g == llcolor4.mV[VGREEN]); - CHECK(b == llcolor4.mV[VBLUE]); - CHECK(llcolor4.mV[VALPHA] == 1.f); - } + CHECK(r == llcolor4.mV[VRED]); + CHECK(g == llcolor4.mV[VGREEN]); + CHECK(b == llcolor4.mV[VBLUE]); + CHECK(llcolor4.mV[VALPHA] == 1.f); llcolor4.setVec(r,g,b,a); - { - INFO("2:setVec:Fail to set the values "); - CHECK(r == llcolor4.mV[VRED]); - CHECK(g == llcolor4.mV[VGREEN]); - CHECK(b == llcolor4.mV[VBLUE]); - CHECK(a == llcolor4.mV[VALPHA]); - } + CHECK(r == llcolor4.mV[VRED]); + CHECK(g == llcolor4.mV[VGREEN]); + CHECK(b == llcolor4.mV[VBLUE]); + CHECK(a == llcolor4.mV[VALPHA]); LLColor4 llcolor4a; llcolor4a.setVec(llcolor4); - UNSCOPED_INFO("3:setVec:Fail to set the values "); CHECK(llcolor4a == llcolor4); LLColor3 llcolor3(-2.23f,1.01f,42.3f); llcolor4a.setVec(llcolor3); - { - INFO("4:setVec:Fail to set the values "); - CHECK(llcolor3.mV[VRED] == llcolor4a.mV[VRED]); - CHECK(llcolor3.mV[VGREEN] == llcolor4a.mV[VGREEN]); - CHECK(llcolor3.mV[VBLUE] == llcolor4a.mV[VBLUE]); - } + CHECK(llcolor3.mV[VRED] == llcolor4a.mV[VRED]); + CHECK(llcolor3.mV[VGREEN] == llcolor4a.mV[VGREEN]); + CHECK(llcolor3.mV[VBLUE] == llcolor4a.mV[VBLUE]); F32 val = -.33f; llcolor4a.setVec(llcolor3,val); - { - INFO("4:setVec:Fail to set the values "); - CHECK(llcolor3.mV[VRED] == llcolor4a.mV[VRED]); - CHECK(llcolor3.mV[VGREEN] == llcolor4a.mV[VGREEN]); - CHECK(llcolor3.mV[VBLUE] == llcolor4a.mV[VBLUE]); - CHECK(val == llcolor4a.mV[VALPHA]); - } + CHECK(llcolor3.mV[VRED] == llcolor4a.mV[VRED]); + CHECK(llcolor3.mV[VGREEN] == llcolor4a.mV[VGREEN]); + CHECK(llcolor3.mV[VBLUE] == llcolor4a.mV[VBLUE]); + CHECK(val == llcolor4a.mV[VALPHA]); const F32 vec[4] = {.112f ,23.2f, -4.2f, -.0001f}; LLColor4 llcolor4c; llcolor4c.setVec(vec); - { - INFO("5:setVec:Fail to initialize "); - CHECK(vec[0] == llcolor4c.mV[VRED]); - CHECK(vec[1] == llcolor4c.mV[VGREEN]); - CHECK(vec[2] == llcolor4c.mV[VBLUE]); - CHECK(vec[3] == llcolor4c.mV[VALPHA]); - } + CHECK(vec[0] == llcolor4c.mV[VRED]); + CHECK(vec[1] == llcolor4c.mV[VGREEN]); + CHECK(vec[2] == llcolor4c.mV[VBLUE]); + CHECK(vec[3] == llcolor4c.mV[VALPHA]); U8 r1 = 0xF2, g1 = 0xFA, b1= 0xBF; LLColor4U color4u(r1,g1,b1); llcolor4.setVec(color4u); const F32 SCALE = 1.f/255.f; F32 r2 = r1*SCALE, g2 = g1* SCALE, b2 = b1* SCALE; - { - INFO("6:setVec:Fail to initialize "); - CHECK(r2 == llcolor4.mV[VRED]); - CHECK(g2 == llcolor4.mV[VGREEN]); - CHECK(b2 == llcolor4.mV[VBLUE]); - } + CHECK(r2 == llcolor4.mV[VRED]); + CHECK(g2 == llcolor4.mV[VGREEN]); + CHECK(b2 == llcolor4.mV[VBLUE]); } TEST_CASE("v4color_h: setAlpha sets the alpha", "[v4color_h]") @@ -219,7 +173,6 @@ namespace F32 alpha = 0xAF; LLColor4 llcolor4; llcolor4.setAlpha(alpha); - UNSCOPED_INFO("setAlpha:Fail to initialize "); CHECK(alpha == llcolor4.mV[VALPHA]); } @@ -227,9 +180,7 @@ namespace { F32 r = 0x20, g = 0xFFFF, b = 0xFF; LLColor4 llcolor4(r,g,b); - UNSCOPED_INFO("magVecSquared:Fail "); CHECK(is_approx_equal(llcolor4.magVecSquared(), (r*r + g*g + b*b))); - UNSCOPED_INFO("magVec:Fail "); CHECK(is_approx_equal(llcolor4.magVec(), (F32) sqrt(r*r + g*g + b*b))); } @@ -241,27 +192,21 @@ namespace F32 mag = (F32) sqrt(r*r + g*g + b*b); F32 oomag = 1.f / mag; F32 val1 = r * oomag, val2 = g * oomag, val3 = b * oomag; - { - INFO("1:normVec failed "); - CHECK(is_approx_equal(val1, llcolor4.mV[0])); - CHECK(is_approx_equal(val2, llcolor4.mV[1])); - CHECK(is_approx_equal(val3, llcolor4.mV[2])); - CHECK(is_approx_equal(vecMag, mag)); - } + CHECK(is_approx_equal(val1, llcolor4.mV[0])); + CHECK(is_approx_equal(val2, llcolor4.mV[1])); + CHECK(is_approx_equal(val3, llcolor4.mV[2])); + CHECK(is_approx_equal(vecMag, mag)); } TEST_CASE("v4color_h: isOpaque only when alpha is exactly 1", "[v4color_h]") { LLColor4 llcolor4; - UNSCOPED_INFO("1:isOpaque failed "); CHECK(llcolor4.isOpaque() == 1); F32 r = 0x20, g = 0xFFFF, b = 0xFF,a = 1.f; llcolor4.setVec(r,g,b,a); - UNSCOPED_INFO("2:isOpaque failed "); CHECK(llcolor4.isOpaque() == 1); a = 2.f; llcolor4.setVec(r,g,b,a); - UNSCOPED_INFO("3:isOpaque failed "); CHECK(llcolor4.isOpaque() == 0); } @@ -269,23 +214,17 @@ namespace { F32 r = 0x20, g = 0xFFFF, b = 0xFF; LLColor4 llcolor4(r,g,b); - UNSCOPED_INFO("1:operator [] failed"); CHECK(r == llcolor4[0]); - UNSCOPED_INFO("2:operator [] failed"); CHECK(g == llcolor4[1]); - UNSCOPED_INFO("3:operator [] failed"); CHECK(b == llcolor4[2]); r = 0xA20, g = 0xFBFF, b = 0xFFF; llcolor4.setVec(r,g,b); F32 &ref1 = llcolor4[0]; - UNSCOPED_INFO("4:operator [] failed"); CHECK(ref1 == llcolor4[0]); F32 &ref2 = llcolor4[1]; - UNSCOPED_INFO("5:operator [] failed"); CHECK(ref2 == llcolor4[1]); F32 &ref3 = llcolor4[2]; - UNSCOPED_INFO("6:operator [] failed"); CHECK(ref3 == llcolor4[2]); } @@ -295,15 +234,11 @@ namespace LLColor3 llcolor3(r,g,b); LLColor4 llcolor4a,llcolor4b; llcolor4a = llcolor3; - { - INFO("Operator=:Fail to initialize "); - CHECK(llcolor3.mV[0] == llcolor4a.mV[VRED]); - CHECK(llcolor3.mV[1] == llcolor4a.mV[VGREEN]); - CHECK(llcolor3.mV[2] == llcolor4a.mV[VBLUE]); - } + CHECK(llcolor3.mV[0] == llcolor4a.mV[VRED]); + CHECK(llcolor3.mV[1] == llcolor4a.mV[VGREEN]); + CHECK(llcolor3.mV[2] == llcolor4a.mV[VBLUE]); LLSD sd = llcolor4a.getValue(); llcolor4b = LLColor4(sd); - UNSCOPED_INFO("Operator= LLSD:Fail "); CHECK(llcolor4a == llcolor4b); } @@ -315,7 +250,6 @@ namespace stream1 << llcolor4a; llcolor4b.setVec(r,g,b); stream2 << llcolor4b; - UNSCOPED_INFO("operator << failed "); CHECK(stream1.str() == stream2.str()); } @@ -325,20 +259,14 @@ namespace F32 r2 = 0xABF, g2 = 0xFB, b2 = 0xFFF; LLColor4 llcolor4a(r1,g1,b1),llcolor4b(r2,g2,b2),llcolor4c; llcolor4c = llcolor4b + llcolor4a; - { - INFO("operator+:Fail to Add the values "); - CHECK(is_approx_equal(r1+r2,llcolor4c.mV[VRED])); - CHECK(is_approx_equal(g1+g2,llcolor4c.mV[VGREEN])); - CHECK(is_approx_equal(b1+b2,llcolor4c.mV[VBLUE])); - } + CHECK(is_approx_equal(r1+r2,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1+g2,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1+b2,llcolor4c.mV[VBLUE])); llcolor4b += llcolor4a; - { - INFO("operator+=:Fail to Add the values "); - CHECK(is_approx_equal(r1+r2,llcolor4b.mV[VRED])); - CHECK(is_approx_equal(g1+g2,llcolor4b.mV[VGREEN])); - CHECK(is_approx_equal(b1+b2,llcolor4b.mV[VBLUE])); - } + CHECK(is_approx_equal(r1+r2,llcolor4b.mV[VRED])); + CHECK(is_approx_equal(g1+g2,llcolor4b.mV[VGREEN])); + CHECK(is_approx_equal(b1+b2,llcolor4b.mV[VBLUE])); } TEST_CASE("v4color_h: operator- and operator-=", "[v4color_h]") @@ -347,20 +275,14 @@ namespace F32 r2 = 0xABF, g2 = 0xFB, b2 = 0xFFF; LLColor4 llcolor4a(r1,g1,b1),llcolor4b(r2,g2,b2),llcolor4c; llcolor4c = llcolor4a - llcolor4b; - { - INFO("operator-:Fail to subtract the values "); - CHECK(is_approx_equal(r1-r2,llcolor4c.mV[VRED])); - CHECK(is_approx_equal(g1-g2,llcolor4c.mV[VGREEN])); - CHECK(is_approx_equal(b1-b2,llcolor4c.mV[VBLUE])); - } + CHECK(is_approx_equal(r1-r2,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1-g2,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1-b2,llcolor4c.mV[VBLUE])); llcolor4a -= llcolor4b; - { - INFO("operator-=:Fail to subtract the values "); - CHECK(is_approx_equal(r1-r2,llcolor4a.mV[VRED])); - CHECK(is_approx_equal(g1-g2,llcolor4a.mV[VGREEN])); - CHECK(is_approx_equal(b1-b2,llcolor4a.mV[VBLUE])); - } + CHECK(is_approx_equal(r1-r2,llcolor4a.mV[VRED])); + CHECK(is_approx_equal(g1-g2,llcolor4a.mV[VGREEN])); + CHECK(is_approx_equal(b1-b2,llcolor4a.mV[VBLUE])); } TEST_CASE("v4color_h: operator* and operator*= by a color and by a scalar", "[v4color_h]") @@ -369,45 +291,30 @@ namespace F32 r2 = 0xABF, g2 = 0xFB, b2 = 0xFFF; LLColor4 llcolor4a(r1,g1,b1),llcolor4b(r2,g2,b2),llcolor4c; llcolor4c = llcolor4a * llcolor4b; - { - INFO("1:operator*:Fail to multiply the values"); - CHECK(is_approx_equal(r1*r2,llcolor4c.mV[VRED])); - CHECK(is_approx_equal(g1*g2,llcolor4c.mV[VGREEN])); - CHECK(is_approx_equal(b1*b2,llcolor4c.mV[VBLUE])); - } + CHECK(is_approx_equal(r1*r2,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1*g2,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1*b2,llcolor4c.mV[VBLUE])); F32 mulVal = 3.33f; llcolor4c = llcolor4a * mulVal; - { - INFO("2:operator*:Fail "); - CHECK(is_approx_equal(r1*mulVal,llcolor4c.mV[VRED])); - CHECK(is_approx_equal(g1*mulVal,llcolor4c.mV[VGREEN])); - CHECK(is_approx_equal(b1*mulVal,llcolor4c.mV[VBLUE])); - } + CHECK(is_approx_equal(r1*mulVal,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1*mulVal,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1*mulVal,llcolor4c.mV[VBLUE])); llcolor4c = mulVal * llcolor4a; - { - INFO("3:operator*:Fail to multiply the values"); - CHECK(is_approx_equal(r1*mulVal,llcolor4c.mV[VRED])); - CHECK(is_approx_equal(g1*mulVal,llcolor4c.mV[VGREEN])); - CHECK(is_approx_equal(b1*mulVal,llcolor4c.mV[VBLUE])); - } + CHECK(is_approx_equal(r1*mulVal,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1*mulVal,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1*mulVal,llcolor4c.mV[VBLUE])); llcolor4a *= mulVal; - { - INFO("4:operator*=:Fail to multiply the values "); - CHECK(is_approx_equal(r1*mulVal,llcolor4a.mV[VRED])); - CHECK(is_approx_equal(g1*mulVal,llcolor4a.mV[VGREEN])); - CHECK(is_approx_equal(b1*mulVal,llcolor4a.mV[VBLUE])); - } + CHECK(is_approx_equal(r1*mulVal,llcolor4a.mV[VRED])); + CHECK(is_approx_equal(g1*mulVal,llcolor4a.mV[VGREEN])); + CHECK(is_approx_equal(b1*mulVal,llcolor4a.mV[VBLUE])); LLColor4 llcolor4d(r1,g1,b1),llcolor4e(r2,g2,b2); llcolor4e *= llcolor4d; - { - INFO("5:operator*=:Fail to multiply the values "); - CHECK(is_approx_equal(r1*r2,llcolor4e.mV[VRED])); - CHECK(is_approx_equal(g1*g2,llcolor4e.mV[VGREEN])); - CHECK(is_approx_equal(b1*b2,llcolor4e.mV[VBLUE])); - } + CHECK(is_approx_equal(r1*r2,llcolor4e.mV[VRED])); + CHECK(is_approx_equal(g1*g2,llcolor4e.mV[VGREEN])); + CHECK(is_approx_equal(b1*b2,llcolor4e.mV[VBLUE])); } TEST_CASE("v4color_h: operator% and operator%= scale only the alpha", "[v4color_h]") @@ -416,25 +323,18 @@ namespace F32 div = 12.345f; LLColor4 llcolor4a(r,g,b,a),llcolor4b; llcolor4b = llcolor4a % div;//chnage only alpha value nor r,g,b; - { - INFO("1operator%:Fail "); - CHECK(is_approx_equal(r,llcolor4b.mV[VRED])); - CHECK(is_approx_equal(g,llcolor4b.mV[VGREEN])); - CHECK(is_approx_equal(b,llcolor4b.mV[VBLUE])); - CHECK(is_approx_equal(div*a,llcolor4b.mV[VALPHA])); - } + CHECK(is_approx_equal(r,llcolor4b.mV[VRED])); + CHECK(is_approx_equal(g,llcolor4b.mV[VGREEN])); + CHECK(is_approx_equal(b,llcolor4b.mV[VBLUE])); + CHECK(is_approx_equal(div*a,llcolor4b.mV[VALPHA])); llcolor4b = div % llcolor4a; - { - INFO("2operator%:Fail "); - CHECK(is_approx_equal(r,llcolor4b.mV[VRED])); - CHECK(is_approx_equal(g,llcolor4b.mV[VGREEN])); - CHECK(is_approx_equal(b,llcolor4b.mV[VBLUE])); - CHECK(is_approx_equal(div*a,llcolor4b.mV[VALPHA])); - } + CHECK(is_approx_equal(r,llcolor4b.mV[VRED])); + CHECK(is_approx_equal(g,llcolor4b.mV[VGREEN])); + CHECK(is_approx_equal(b,llcolor4b.mV[VBLUE])); + CHECK(is_approx_equal(div*a,llcolor4b.mV[VALPHA])); llcolor4a %= div; - UNSCOPED_INFO("operator%=:Fail "); CHECK(is_approx_equal(a*div,llcolor4a.mV[VALPHA])); } @@ -443,14 +343,11 @@ namespace F32 r = 0x20, g = 0xFFFF, b = 0xFF,a = 0x30; LLColor4 llcolor4a(r,g,b,a),llcolor4b; llcolor4b = llcolor4a; - UNSCOPED_INFO("1:operator== failed to ensure the equality "); CHECK(llcolor4b == llcolor4a); F32 r1 = 0x2, g1 = 0xFF, b1 = 0xFA; LLColor3 llcolor3(r1,g1,b1); llcolor4b = llcolor3; - UNSCOPED_INFO("2:operator== failed to ensure the equality "); CHECK(llcolor4b == llcolor3); - UNSCOPED_INFO("2:operator!= failed to ensure the equality "); CHECK(llcolor4a != llcolor3); } @@ -459,14 +356,10 @@ namespace F32 r = 0x20, g = 0xFFFF, b = 0xFF; LLColor4 llcolor4a(r,g,b),llcolor4b; LLColor3 llcolor3 = vec4to3(llcolor4a); - { - INFO("vec4to3:Fail to convert vec4 to vec3 "); - CHECK(is_approx_equal(llcolor3.mV[VRED],llcolor4a.mV[VRED])); - CHECK(is_approx_equal(llcolor3.mV[VGREEN],llcolor4a.mV[VGREEN])); - CHECK(is_approx_equal(llcolor3.mV[VBLUE],llcolor4a.mV[VBLUE])); - } + CHECK(is_approx_equal(llcolor3.mV[VRED],llcolor4a.mV[VRED])); + CHECK(is_approx_equal(llcolor3.mV[VGREEN],llcolor4a.mV[VGREEN])); + CHECK(is_approx_equal(llcolor3.mV[VBLUE],llcolor4a.mV[VBLUE])); llcolor4b = vec3to4(llcolor3); - UNSCOPED_INFO("vec3to4:Fail to convert vec3 to vec4 "); CHECK(llcolor4b == llcolor4a); } @@ -476,12 +369,9 @@ namespace F32 r2 = 0xABF, g2 = 0xFB, b2 = 0xFFF; LLColor4 llcolor4a(r1,g1,b1),llcolor4b(r2,g2,b2),llcolor4c; llcolor4c = lerp(llcolor4a,llcolor4b,val); - { - INFO("lerp:Fail "); - CHECK(is_approx_equal(r1 + (r2 - r1)* val,llcolor4c.mV[VRED])); - CHECK(is_approx_equal(g1 + (g2 - g1)* val,llcolor4c.mV[VGREEN])); - CHECK(is_approx_equal(b1 + (b2 - b1)* val,llcolor4c.mV[VBLUE])); - } + CHECK(is_approx_equal(r1 + (r2 - r1)* val,llcolor4c.mV[VRED])); + CHECK(is_approx_equal(g1 + (g2 - g1)* val,llcolor4c.mV[VGREEN])); + CHECK(is_approx_equal(b1 + (b2 - b1)* val,llcolor4c.mV[VBLUE])); } TEST_CASE("v4color_h: parseColor reads color names and 0 to 255 components", "[v4color_h]") @@ -490,19 +380,16 @@ namespace LLColor4 llcolor4a(r,g,b,a),llcolor4b; std::string color("red"); LLColor4::parseColor(color, &llcolor4b); - UNSCOPED_INFO("1:parseColor() failed to parse the color value "); CHECK(llcolor4b == LLColor4::red); color = "12.0, -2.3, 1.32, 5.0"; LLColor4::parseColor(color, &llcolor4b); llcolor4a = llcolor4a * (1.f / 255.f); - UNSCOPED_INFO("2:parseColor() failed to parse the color value "); CHECK(llcolor4a == llcolor4b); color = "yellow5"; llcolor4a.setVec(r,g,b); LLColor4::parseColor(color, &llcolor4a); - UNSCOPED_INFO("3:parseColor() failed to parse the color value "); CHECK(llcolor4a == LLColor4::yellow5); } @@ -512,7 +399,6 @@ namespace LLColor4 llcolor4a(r,g,b,a),llcolor4b; std::string color("12.0, -2.3, 1.32, 5.0"); LLColor4::parseColor4(color, &llcolor4b); - UNSCOPED_INFO("parseColor4() failed to parse the color value "); CHECK(llcolor4a == llcolor4b); } } diff --git a/indra/llmath/tests/v4coloru_test.cpp b/indra/llmath/tests/v4coloru_test.cpp index 61d94b8207..3f2a7a984d 100644 --- a/indra/llmath/tests/v4coloru_test.cpp +++ b/indra/llmath/tests/v4coloru_test.cpp @@ -40,46 +40,33 @@ namespace TEST_CASE("v4coloru_h: constructors set components and default alpha to 255", "[v4coloru_h]") { LLColor4U llcolor4u; - { - INFO("1:LLColor4u:Fail to initialize "); - CHECK(llcolor4u.mV[VRED] == 0); - CHECK(llcolor4u.mV[VGREEN] == 0); - CHECK(llcolor4u.mV[VBLUE] == 0); - CHECK(llcolor4u.mV[VALPHA] == 255); - } + CHECK(llcolor4u.mV[VRED] == 0); + CHECK(llcolor4u.mV[VGREEN] == 0); + CHECK(llcolor4u.mV[VBLUE] == 0); + CHECK(llcolor4u.mV[VALPHA] == 255); U8 r = 0x12, g = 0xFF, b = 0xAF, a = 0x23; LLColor4U llcolor4u1(r,g,b); - { - INFO("2:LLColor4u:Fail to initialize "); - CHECK(r == llcolor4u1.mV[VRED]); - CHECK(g == llcolor4u1.mV[VGREEN]); - CHECK(b == llcolor4u1.mV[VBLUE]); - CHECK(llcolor4u1.mV[VALPHA] == 255); - } + CHECK(r == llcolor4u1.mV[VRED]); + CHECK(g == llcolor4u1.mV[VGREEN]); + CHECK(b == llcolor4u1.mV[VBLUE]); + CHECK(llcolor4u1.mV[VALPHA] == 255); LLColor4U llcolor4u2(r,g,b,a); - { - INFO("3:LLColor4u:Fail to initialize "); - CHECK(r == llcolor4u2.mV[VRED]); - CHECK(g == llcolor4u2.mV[VGREEN]); - CHECK(b == llcolor4u2.mV[VBLUE]); - CHECK(a == llcolor4u2.mV[VALPHA]); - } + CHECK(r == llcolor4u2.mV[VRED]); + CHECK(g == llcolor4u2.mV[VGREEN]); + CHECK(b == llcolor4u2.mV[VBLUE]); + CHECK(a == llcolor4u2.mV[VALPHA]); const U8 vec[4] = {0x12,0xFF,0xAF,0x23}; LLColor4U llcolor4u3(vec); - { - INFO("4:LLColor4u:Fail to initialize "); - CHECK(vec[0] == llcolor4u3.mV[VRED]); - CHECK(vec[1] == llcolor4u3.mV[VGREEN]); - CHECK(vec[2] == llcolor4u3.mV[VBLUE]); - CHECK(vec[3] == llcolor4u3.mV[VALPHA]); - } + CHECK(vec[0] == llcolor4u3.mV[VRED]); + CHECK(vec[1] == llcolor4u3.mV[VGREEN]); + CHECK(vec[2] == llcolor4u3.mV[VBLUE]); + CHECK(vec[3] == llcolor4u3.mV[VALPHA]); LLSD sd = llcolor4u3.getValue(); LLColor4U llcolor4u4(sd); - UNSCOPED_INFO("5:LLColor4u (LLSD) Failed "); CHECK(llcolor4u4 == llcolor4u3); } @@ -89,7 +76,6 @@ namespace LLSD sd = llcolor4ua.getValue(); LLColor4U llcolor4u; llcolor4u.setValue(sd); - UNSCOPED_INFO("setValue(LLSD)/getValue Failed "); CHECK(llcolor4u == llcolor4ua); } @@ -98,22 +84,16 @@ namespace U8 r = 0x12, g = 0xFF, b = 0xAF, a = 0x23; LLColor4U llcolor4u(r,g,b,a); llcolor4u.setToBlack(); - { - INFO("setToBlack:Fail to set black "); - CHECK(llcolor4u.mV[VRED] == 0); - CHECK(llcolor4u.mV[VGREEN] == 0); - CHECK(llcolor4u.mV[VBLUE] == 0); - CHECK(llcolor4u.mV[VALPHA] == 255); - } + CHECK(llcolor4u.mV[VRED] == 0); + CHECK(llcolor4u.mV[VGREEN] == 0); + CHECK(llcolor4u.mV[VBLUE] == 0); + CHECK(llcolor4u.mV[VALPHA] == 255); llcolor4u.setToWhite(); - { - INFO("setToWhite:Fail to white "); - CHECK(llcolor4u.mV[VRED] == 255); - CHECK(llcolor4u.mV[VGREEN] == 255); - CHECK(llcolor4u.mV[VBLUE] == 255); - CHECK(llcolor4u.mV[VALPHA] == 255); - } + CHECK(llcolor4u.mV[VRED] == 255); + CHECK(llcolor4u.mV[VGREEN] == 255); + CHECK(llcolor4u.mV[VBLUE] == 255); + CHECK(llcolor4u.mV[VALPHA] == 255); } TEST_CASE("v4coloru_h: cast from LLSD reads back what getValue wrote", "[v4coloru_h]") @@ -122,7 +102,6 @@ namespace LLColor4U llcolor4ua(r,g,b,a); LLSD sd = llcolor4ua.getValue(); LLColor4U llcolor4u = (LLColor4U)sd; - UNSCOPED_INFO("Operator=(LLSD) Failed "); CHECK(llcolor4u == llcolor4ua); } @@ -131,39 +110,29 @@ namespace U8 r = 0x12, g = 0xFF, b = 0xAF, a = 0x23; LLColor4U llcolor4u; llcolor4u.setVec(r,g,b,a); - { - INFO("1:setVec:Fail to set the values "); - CHECK(r == llcolor4u.mV[VRED]); - CHECK(g == llcolor4u.mV[VGREEN]); - CHECK(b == llcolor4u.mV[VBLUE]); - CHECK(a == llcolor4u.mV[VALPHA]); - } + CHECK(r == llcolor4u.mV[VRED]); + CHECK(g == llcolor4u.mV[VGREEN]); + CHECK(b == llcolor4u.mV[VBLUE]); + CHECK(a == llcolor4u.mV[VALPHA]); llcolor4u.setToBlack(); llcolor4u.setVec(r,g,b); - { - INFO("2:setVec:Fail to set the values "); - CHECK(r == llcolor4u.mV[VRED]); - CHECK(g == llcolor4u.mV[VGREEN]); - CHECK(b == llcolor4u.mV[VBLUE]); - CHECK(llcolor4u.mV[VALPHA] == 255); - } + CHECK(r == llcolor4u.mV[VRED]); + CHECK(g == llcolor4u.mV[VGREEN]); + CHECK(b == llcolor4u.mV[VBLUE]); + CHECK(llcolor4u.mV[VALPHA] == 255); LLColor4U llcolor4u1; llcolor4u1.setVec(llcolor4u); - UNSCOPED_INFO("3:setVec:Fail to set the values "); CHECK(llcolor4u1 == llcolor4u); const U8 vec[4] = {0x12,0xFF,0xAF,0x23}; LLColor4U llcolor4u2; llcolor4u2.setVec(vec); - { - INFO("4:setVec:Fail to set the values "); - CHECK(vec[0] == llcolor4u2.mV[VRED]); - CHECK(vec[1] == llcolor4u2.mV[VGREEN]); - CHECK(vec[2] == llcolor4u2.mV[VBLUE]); - CHECK(vec[3] == llcolor4u2.mV[VALPHA]); - } + CHECK(vec[0] == llcolor4u2.mV[VRED]); + CHECK(vec[1] == llcolor4u2.mV[VGREEN]); + CHECK(vec[2] == llcolor4u2.mV[VBLUE]); + CHECK(vec[3] == llcolor4u2.mV[VALPHA]); } TEST_CASE("v4coloru_h: setAlpha sets the alpha", "[v4coloru_h]") @@ -171,7 +140,6 @@ namespace U8 alpha = 0x12; LLColor4U llcolor4u; llcolor4u.setAlpha(alpha); - UNSCOPED_INFO("setAlpha:Fail to set alpha value "); CHECK(alpha == llcolor4u.mV[VALPHA]); } @@ -179,9 +147,7 @@ namespace { U8 r = 0x12, g = 0xFF, b = 0xAF; LLColor4U llcolor4u(r,g,b); - UNSCOPED_INFO("magVecSquared:Fail "); CHECK(is_approx_equal(llcolor4u.magVecSquared(), (F32)(r*r + g*g + b*b))); - UNSCOPED_INFO("magVec:Fail "); CHECK(is_approx_equal(llcolor4u.magVec(), (F32) sqrt((F32) (r*r + g*g + b*b)))); } @@ -193,7 +159,6 @@ namespace stream1 << llcolor4u1; llcolor4u2.setVec(r,g,b); stream2 << llcolor4u2; - UNSCOPED_INFO("operator << failed "); CHECK(stream1.str() == stream2.str()); } @@ -203,26 +168,14 @@ namespace U8 r2 = 0x1C, g2 = 0x9A, b2 = 0x1B; LLColor4U llcolor4u1(r1,g1,b1), llcolor4u2(r2,g2,b2),llcolor4u3; llcolor4u3 = llcolor4u1 + llcolor4u2; - UNSCOPED_INFO("1a.operator+:Fail to Add the values "); - CHECK(llcolor4u3.mV[VRED] == - (U8)(r1+r2)); - UNSCOPED_INFO("1b.operator+:Fail to Add the values "); - CHECK(llcolor4u3.mV[VGREEN] == - (U8)(g1+g2)); - UNSCOPED_INFO("1c.operator+:Fail to Add the values "); - CHECK(llcolor4u3.mV[VBLUE] == - (U8)(b1+b2)); + CHECK(llcolor4u3.mV[VRED] == (U8)(r1+r2)); + CHECK(llcolor4u3.mV[VGREEN] == (U8)(g1+g2)); + CHECK(llcolor4u3.mV[VBLUE] == (U8)(b1+b2)); llcolor4u2 += llcolor4u1; - UNSCOPED_INFO("2a.operator+=:Fail to Add the values "); - CHECK(llcolor4u2.mV[VRED] == - (U8)(r1+r2)); - UNSCOPED_INFO("2b.operator+=:Fail to Add the values "); - CHECK(llcolor4u2.mV[VGREEN] == - (U8)(g1+g2)); - UNSCOPED_INFO("2c.operator+=:Fail to Add the values "); - CHECK(llcolor4u2.mV[VBLUE] == - (U8)(b1+b2)); + CHECK(llcolor4u2.mV[VRED] == (U8)(r1+r2)); + CHECK(llcolor4u2.mV[VGREEN] == (U8)(g1+g2)); + CHECK(llcolor4u2.mV[VBLUE] == (U8)(b1+b2)); } TEST_CASE("v4coloru_h: operator- and operator-= wrap modulo 256", "[v4coloru_h]") @@ -231,26 +184,14 @@ namespace U8 r2 = 0x1C, g2 = 0x9A, b2 = 0x1B; LLColor4U llcolor4u1(r1,g1,b1), llcolor4u2(r2,g2,b2),llcolor4u3; llcolor4u3 = llcolor4u1 - llcolor4u2; - UNSCOPED_INFO("1a. operator-:Fail to Add the values "); - CHECK(llcolor4u3.mV[VRED] == - (U8)(r1-r2)); - UNSCOPED_INFO("1b. operator-:Fail to Add the values "); - CHECK(llcolor4u3.mV[VGREEN] == - (U8)(g1-g2)); - UNSCOPED_INFO("1c. operator-:Fail to Add the values "); - CHECK(llcolor4u3.mV[VBLUE] == - (U8)(b1-b2)); + CHECK(llcolor4u3.mV[VRED] == (U8)(r1-r2)); + CHECK(llcolor4u3.mV[VGREEN] == (U8)(g1-g2)); + CHECK(llcolor4u3.mV[VBLUE] == (U8)(b1-b2)); llcolor4u1 -= llcolor4u2; - UNSCOPED_INFO("2a. operator-=:Fail to Add the values "); - CHECK(llcolor4u1.mV[VRED] == - (U8)(r1-r2)); - UNSCOPED_INFO("2b. operator-=:Fail to Add the values "); - CHECK(llcolor4u1.mV[VGREEN] == - (U8)(g1-g2)); - UNSCOPED_INFO("2c. operator-=:Fail to Add the values "); - CHECK(llcolor4u1.mV[VBLUE] == - (U8)(b1-b2)); + CHECK(llcolor4u1.mV[VRED] == (U8)(r1-r2)); + CHECK(llcolor4u1.mV[VGREEN] == (U8)(g1-g2)); + CHECK(llcolor4u1.mV[VBLUE] == (U8)(b1-b2)); } TEST_CASE("v4coloru_h: operator* by a color and operator*= by a scalar wrap modulo 256", "[v4coloru_h]") @@ -259,27 +200,15 @@ namespace U8 r2 = 0x1C, g2 = 0x9A, b2 = 0x1B; LLColor4U llcolor4u1(r1,g1,b1), llcolor4u2(r2,g2,b2),llcolor4u3; llcolor4u3 = llcolor4u1 * llcolor4u2; - UNSCOPED_INFO("1a. operator*:Fail to multiply the values"); - CHECK(llcolor4u3.mV[VRED] == - (U8)(r1*r2)); - UNSCOPED_INFO("1b. operator*:Fail to multiply the values"); - CHECK(llcolor4u3.mV[VGREEN] == - (U8)(g1*g2)); - UNSCOPED_INFO("1c. operator*:Fail to multiply the values"); - CHECK(llcolor4u3.mV[VBLUE] == - (U8)(b1*b2)); + CHECK(llcolor4u3.mV[VRED] == (U8)(r1*r2)); + CHECK(llcolor4u3.mV[VGREEN] == (U8)(g1*g2)); + CHECK(llcolor4u3.mV[VBLUE] == (U8)(b1*b2)); U8 mulVal = 123; llcolor4u1 *= mulVal; - UNSCOPED_INFO("2a. operator*=:Fail to multiply the values"); - CHECK(llcolor4u1.mV[VRED] == - (U8)(r1*mulVal)); - UNSCOPED_INFO("2b. operator*=:Fail to multiply the values"); - CHECK(llcolor4u1.mV[VGREEN] == - (U8)(g1*mulVal)); - UNSCOPED_INFO("2c. operator*=:Fail to multiply the values"); - CHECK(llcolor4u1.mV[VBLUE] == - (U8)(b1*mulVal)); + CHECK(llcolor4u1.mV[VRED] == (U8)(r1*mulVal)); + CHECK(llcolor4u1.mV[VGREEN] == (U8)(g1*mulVal)); + CHECK(llcolor4u1.mV[VBLUE] == (U8)(b1*mulVal)); } TEST_CASE("v4coloru_h: operator== and operator!=", "[v4coloru_h]") @@ -287,10 +216,8 @@ namespace U8 r = 0x12, g = 0xFF, b = 0xAF; LLColor4U llcolor4u(r,g,b),llcolor4u1; llcolor4u1 = llcolor4u; - UNSCOPED_INFO("operator== failed to ensure the equality "); CHECK(llcolor4u1 == llcolor4u); llcolor4u1.setToBlack(); - UNSCOPED_INFO("operator!= failed to ensure the equality "); CHECK(llcolor4u1 != llcolor4u); } @@ -300,7 +227,6 @@ namespace LLColor4U llcolor4u(r,g,b,a); U8 modVal = 45; llcolor4u %= modVal; - UNSCOPED_INFO("operator%=:Fail "); CHECK(llcolor4u.mV[VALPHA] == (U8)(a * modVal)); } @@ -310,20 +236,15 @@ namespace LLColor4U llcolor4u1(r,g,b,a); std::string color("12, 23, 132, 50"); LLColor4U::parseColor4U(color, &llcolor4u1); - { - INFO("parseColor4U() failed to parse the color value "); - CHECK(llcolor4u1.mV[VRED] == 12); - CHECK(llcolor4u1.mV[VGREEN] == 23); - CHECK(llcolor4u1.mV[VBLUE] == 132); - CHECK(llcolor4u1.mV[VALPHA] == 50); - } + CHECK(llcolor4u1.mV[VRED] == 12); + CHECK(llcolor4u1.mV[VGREEN] == 23); + CHECK(llcolor4u1.mV[VBLUE] == 132); + CHECK(llcolor4u1.mV[VALPHA] == 50); color = "12, 23, 132"; - UNSCOPED_INFO("2:parseColor4U() failed to parse the color value "); CHECK_FALSE(LLColor4U::parseColor4U(color, &llcolor4u1)); color = "12"; - UNSCOPED_INFO("2:parseColor4U() failed to parse the color value "); CHECK_FALSE(LLColor4U::parseColor4U(color, &llcolor4u1)); } @@ -337,13 +258,10 @@ namespace // Previously the test used (U8)ll_round(...) directly, which wraps // modulo 256 for products > 255 (e.g. 123 * 3 = 369 -> 113), locking // in the wrap bug. multAll now clamps, so green saturates at 255. - { - INFO("multAll:Fail to multiply "); - CHECK((U8)llclampb(ll_round(r * fVal)) == llcolor4u1.mV[VRED]); - CHECK((U8)llclampb(ll_round(g * fVal)) == llcolor4u1.mV[VGREEN]); - CHECK((U8)llclampb(ll_round(b * fVal)) == llcolor4u1.mV[VBLUE]); - CHECK((U8)llclampb(ll_round(a * fVal)) == llcolor4u1.mV[VALPHA]); - } + CHECK((U8)llclampb(ll_round(r * fVal)) == llcolor4u1.mV[VRED]); + CHECK((U8)llclampb(ll_round(g * fVal)) == llcolor4u1.mV[VGREEN]); + CHECK((U8)llclampb(ll_round(b * fVal)) == llcolor4u1.mV[VBLUE]); + CHECK((U8)llclampb(ll_round(a * fVal)) == llcolor4u1.mV[VALPHA]); } TEST_CASE("v4coloru_h: addClampMax adds and clamps each channel at 255", "[v4coloru_h]") @@ -352,25 +270,19 @@ namespace U8 r2 = 23, g2 = 230, b2 = 124, a2 = 255; LLColor4U llcolor4u(r1,g1,b1,a1),llcolor4u1(r2,g2,b2,a2); llcolor4u1 = llcolor4u1.addClampMax(llcolor4u); - { - INFO("1:addClampMax():Fail to add the value "); - CHECK(r1+r2 == llcolor4u1.mV[VRED]); - CHECK(llcolor4u1.mV[VGREEN] == 255); - CHECK(b1+b2 == llcolor4u1.mV[VBLUE]); - CHECK(llcolor4u1.mV[VALPHA] == 255); - } + CHECK(r1+r2 == llcolor4u1.mV[VRED]); + CHECK(llcolor4u1.mV[VGREEN] == 255); + CHECK(b1+b2 == llcolor4u1.mV[VBLUE]); + CHECK(llcolor4u1.mV[VALPHA] == 255); r1 = 132, g1 = 3, b1 = 3, a1 = 2; r2 = 123, g2 = 230, b2 = 154, a2 = 25; LLColor4U llcolor4u2(r1,g1,b1,a1),llcolor4u3(r2,g2,b2,a2); llcolor4u3 = llcolor4u3.addClampMax(llcolor4u2); - { - INFO("2:addClampMax():Fail to add the value "); - CHECK(llcolor4u3.mV[VRED] == 255); - CHECK(g1+g2 == llcolor4u3.mV[VGREEN]); - CHECK(b1+b2 == llcolor4u3.mV[VBLUE]); - CHECK(a1+a2 == llcolor4u3.mV[VALPHA]); - } + CHECK(llcolor4u3.mV[VRED] == 255); + CHECK(g1+g2 == llcolor4u3.mV[VGREEN]); + CHECK(b1+b2 == llcolor4u3.mV[VBLUE]); + CHECK(a1+a2 == llcolor4u3.mV[VALPHA]); } TEST_CASE("v4coloru_h: setVecScaleClamp scales the brightest channel to 255 and clamps at 0", "[v4coloru_h]") @@ -383,12 +295,9 @@ namespace F32 color_scale_factor = MAX_COLOR/r; S32 r2 = ll_round(r * color_scale_factor); S32 g2 = ll_round(g * color_scale_factor); - { - INFO("setVecScaleClamp():Fail to add the value "); - CHECK(r2 == llcolor4u.mV[VRED]); - CHECK(g2 == llcolor4u.mV[VGREEN]); - CHECK(llcolor4u.mV[VBLUE] == 0); - CHECK(llcolor4u.mV[VALPHA] == 255); - } + CHECK(r2 == llcolor4u.mV[VRED]); + CHECK(g2 == llcolor4u.mV[VGREEN]); + CHECK(llcolor4u.mV[VBLUE] == 0); + CHECK(llcolor4u.mV[VALPHA] == 255); } } diff --git a/indra/llmath/tests/v4math_test.cpp b/indra/llmath/tests/v4math_test.cpp index d49589bd3e..ac96ff8515 100644 --- a/indra/llmath/tests/v4math_test.cpp +++ b/indra/llmath/tests/v4math_test.cpp @@ -40,57 +40,39 @@ namespace TEST_CASE("v4math_h: constructors set every component and default w to 1", "[v4math_h]") { LLVector4 vec4; - { - INFO("1:LLVector4:Fail to initialize "); - CHECK(vec4.mV[VX] == 0); - CHECK(vec4.mV[VY] == 0); - CHECK(vec4.mV[VZ] == 0); - CHECK(vec4.mV[VW] == 1.0f); - } + CHECK(vec4.mV[VX] == 0); + CHECK(vec4.mV[VY] == 0); + CHECK(vec4.mV[VZ] == 0); + CHECK(vec4.mV[VW] == 1.0f); F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4a(x,y,z); - { - INFO("2:LLVector4:Fail to initialize "); - CHECK(x == vec4a.mV[VX]); - CHECK(y == vec4a.mV[VY]); - CHECK(z == vec4a.mV[VZ]); - CHECK(vec4a.mV[VW] == 1.0f); - } + CHECK(x == vec4a.mV[VX]); + CHECK(y == vec4a.mV[VY]); + CHECK(z == vec4a.mV[VZ]); + CHECK(vec4a.mV[VW] == 1.0f); LLVector4 vec4b(x,y,z,w); - { - INFO("3:LLVector4:Fail to initialize "); - CHECK(x == vec4b.mV[VX]); - CHECK(y == vec4b.mV[VY]); - CHECK(z == vec4b.mV[VZ]); - CHECK(w == vec4b.mV[VW]); - } + CHECK(x == vec4b.mV[VX]); + CHECK(y == vec4b.mV[VY]); + CHECK(z == vec4b.mV[VZ]); + CHECK(w == vec4b.mV[VW]); const F32 vec[4] = {.112f ,23.2f, -4.2f, -.0001f}; LLVector4 vec4c(vec); - { - INFO("4:LLVector4:Fail to initialize "); - CHECK(vec[0] == vec4c.mV[VX]); - CHECK(vec[1] == vec4c.mV[VY]); - CHECK(vec[2] == vec4c.mV[VZ]); - CHECK(vec[3] == vec4c.mV[VW]); - } + CHECK(vec[0] == vec4c.mV[VX]); + CHECK(vec[1] == vec4c.mV[VY]); + CHECK(vec[2] == vec4c.mV[VZ]); + CHECK(vec[3] == vec4c.mV[VW]); LLVector3 vec3(-2.23f,1.01f,42.3f); LLVector4 vec4d(vec3); - { - INFO("5:LLVector4:Fail to initialize "); - CHECK(vec3.mV[VX] == vec4d.mV[VX]); - CHECK(vec3.mV[VY] == vec4d.mV[VY]); - CHECK(vec3.mV[VZ] == vec4d.mV[VZ]); - CHECK(vec4d.mV[VW] == 1.f); - } + CHECK(vec3.mV[VX] == vec4d.mV[VX]); + CHECK(vec3.mV[VY] == vec4d.mV[VY]); + CHECK(vec3.mV[VZ] == vec4d.mV[VZ]); + CHECK(vec4d.mV[VW] == 1.f); F32 w1 = -.234f; LLVector4 vec4e(vec3,w1); - { - INFO("6:LLVector4:Fail to initialize "); - CHECK(vec3.mV[VX] == vec4e.mV[VX]); - CHECK(vec3.mV[VY] == vec4e.mV[VY]); - CHECK(vec3.mV[VZ] == vec4e.mV[VZ]); - CHECK(w1 == vec4e.mV[VW]); - } + CHECK(vec3.mV[VX] == vec4e.mV[VX]); + CHECK(vec3.mV[VY] == vec4e.mV[VY]); + CHECK(vec3.mV[VZ] == vec4e.mV[VZ]); + CHECK(w1 == vec4e.mV[VW]); } TEST_CASE("v4math_h: setVec sets components and clearVec/zeroVec reset them", "[v4math_h]") @@ -98,76 +80,53 @@ namespace F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4; vec4.setVec(x,y,z); - { - INFO("1:setVec:Fail to initialize "); - CHECK(x == vec4.mV[VX]); - CHECK(y == vec4.mV[VY]); - CHECK(z == vec4.mV[VZ]); - CHECK(vec4.mV[VW] == 1.0f); - } + CHECK(x == vec4.mV[VX]); + CHECK(y == vec4.mV[VY]); + CHECK(z == vec4.mV[VZ]); + CHECK(vec4.mV[VW] == 1.0f); vec4.clearVec(); - { - INFO("2:clearVec:Fail "); - CHECK(vec4.mV[VX] == 0); - CHECK(vec4.mV[VY] == 0); - CHECK(vec4.mV[VZ] == 0); - CHECK(vec4.mV[VW] == 1.0f); - } + CHECK(vec4.mV[VX] == 0); + CHECK(vec4.mV[VY] == 0); + CHECK(vec4.mV[VZ] == 0); + CHECK(vec4.mV[VW] == 1.0f); vec4.setVec(x,y,z,w); - { - INFO("3:setVec:Fail to initialize "); - CHECK(x == vec4.mV[VX]); - CHECK(y == vec4.mV[VY]); - CHECK(z == vec4.mV[VZ]); - CHECK(w == vec4.mV[VW]); - } + CHECK(x == vec4.mV[VX]); + CHECK(y == vec4.mV[VY]); + CHECK(z == vec4.mV[VZ]); + CHECK(w == vec4.mV[VW]); vec4.zeroVec(); - { - INFO("4:zeroVec:Fail "); - CHECK(vec4.mV[VX] == 0); - CHECK(vec4.mV[VY] == 0); - CHECK(vec4.mV[VZ] == 0); - CHECK(vec4.mV[VW] == 0); - } + CHECK(vec4.mV[VX] == 0); + CHECK(vec4.mV[VY] == 0); + CHECK(vec4.mV[VZ] == 0); + CHECK(vec4.mV[VW] == 0); LLVector3 vec3(-2.23f,1.01f,42.3f); vec4.clearVec(); vec4.setVec(vec3); - { - INFO("5:setVec:Fail to initialize "); - CHECK(vec3.mV[VX] == vec4.mV[VX]); - CHECK(vec3.mV[VY] == vec4.mV[VY]); - CHECK(vec3.mV[VZ] == vec4.mV[VZ]); - CHECK(vec4.mV[VW] == 1.f); - } + CHECK(vec3.mV[VX] == vec4.mV[VX]); + CHECK(vec3.mV[VY] == vec4.mV[VY]); + CHECK(vec3.mV[VZ] == vec4.mV[VZ]); + CHECK(vec4.mV[VW] == 1.f); F32 w1 = -.234f; vec4.zeroVec(); vec4.setVec(vec3,w1); - { - INFO("6:setVec:Fail to initialize "); - CHECK(vec3.mV[VX] == vec4.mV[VX]); - CHECK(vec3.mV[VY] == vec4.mV[VY]); - CHECK(vec3.mV[VZ] == vec4.mV[VZ]); - CHECK(w1 == vec4.mV[VW]); - } + CHECK(vec3.mV[VX] == vec4.mV[VX]); + CHECK(vec3.mV[VY] == vec4.mV[VY]); + CHECK(vec3.mV[VZ] == vec4.mV[VZ]); + CHECK(w1 == vec4.mV[VW]); const F32 vec[4] = {.112f ,23.2f, -4.2f, -.0001f}; LLVector4 vec4a; vec4a.setVec(vec); - { - INFO("7:setVec:Fail to initialize "); - CHECK(vec[0] == vec4a.mV[VX]); - CHECK(vec[1] == vec4a.mV[VY]); - CHECK(vec[2] == vec4a.mV[VZ]); - CHECK(vec[3] == vec4a.mV[VW]); - } + CHECK(vec[0] == vec4a.mV[VX]); + CHECK(vec[1] == vec4a.mV[VY]); + CHECK(vec[2] == vec4a.mV[VZ]); + CHECK(vec[3] == vec4a.mV[VW]); } TEST_CASE("v4math_h: magVec and magVecSquared", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f; LLVector4 vec4(x,y,z); - UNSCOPED_INFO("magVec:Fail "); CHECK(is_approx_equal(vec4.magVec(), (F32) sqrt(x*x + y*y + z*z))); - UNSCOPED_INFO("magVecSquared:Fail "); CHECK(is_approx_equal(vec4.magVecSquared(), (x*x + y*y + z*z))); } @@ -177,22 +136,16 @@ namespace LLVector4 vec4(x,y,z); F32 mag = vec4.normVec(); mag = 1.f/ mag; - { - INFO("1:normVec: Fail "); - CHECK(is_approx_equal(mag*x,vec4.mV[VX])); - CHECK(is_approx_equal(mag*y, vec4.mV[VY])); - CHECK(is_approx_equal(mag*z, vec4.mV[VZ])); - } + CHECK(is_approx_equal(mag*x,vec4.mV[VX])); + CHECK(is_approx_equal(mag*y, vec4.mV[VY])); + CHECK(is_approx_equal(mag*z, vec4.mV[VZ])); x = 0.000000001f, y = 0.000000001f, z = 0.000000001f; vec4.clearVec(); vec4.setVec(x,y,z); mag = vec4.normVec(); - { - INFO("2:normVec: Fail "); - CHECK(is_approx_equal(mag*x,vec4.mV[VX])); - CHECK(is_approx_equal(mag*y, vec4.mV[VY])); - CHECK(is_approx_equal(mag*z, vec4.mV[VZ])); - } + CHECK(is_approx_equal(mag*x,vec4.mV[VX])); + CHECK(is_approx_equal(mag*y, vec4.mV[VY])); + CHECK(is_approx_equal(mag*z, vec4.mV[VZ])); } TEST_CASE("v4math_h: abs drops signs; isExactlyClear and isExactlyZero", "[v4math_h]") @@ -200,18 +153,13 @@ namespace F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4(x,y,z,w); vec4.abs(); - { - INFO("abs:Fail "); - CHECK(x == vec4.mV[VX]); - CHECK(-y == vec4.mV[VY]); - CHECK(-z == vec4.mV[VZ]); - CHECK(-w == vec4.mV[VW]); - } + CHECK(x == vec4.mV[VX]); + CHECK(-y == vec4.mV[VY]); + CHECK(-z == vec4.mV[VZ]); + CHECK(-w == vec4.mV[VW]); vec4.clearVec(); - UNSCOPED_INFO("isExactlyClear:Fail "); CHECK(vec4.isExactlyClear()); vec4.zeroVec(); - UNSCOPED_INFO("isExactlyZero:Fail "); CHECK(vec4.isExactlyZero()); } @@ -220,40 +168,29 @@ namespace F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4(x,y,z,w),vec4a; vec4a = vec4.scaleVec(vec4); - { - INFO("scaleVec:Fail "); - CHECK(is_approx_equal(x*x, vec4a.mV[VX])); - CHECK(is_approx_equal(y*y, vec4a.mV[VY])); - CHECK(is_approx_equal(z*z, vec4a.mV[VZ])); - CHECK(is_approx_equal(w*w, vec4a.mV[VW])); - } + CHECK(is_approx_equal(x*x, vec4a.mV[VX])); + CHECK(is_approx_equal(y*y, vec4a.mV[VY])); + CHECK(is_approx_equal(z*z, vec4a.mV[VZ])); + CHECK(is_approx_equal(w*w, vec4a.mV[VW])); } TEST_CASE("v4math_h: subscript operator reads components and gives references", "[v4math_h]") { F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; LLVector4 vec4(x,y,z,w); - UNSCOPED_INFO("1:operator [] failed "); CHECK(x == vec4[0]); - UNSCOPED_INFO("2:operator [] failed "); CHECK(y == vec4[1]); - UNSCOPED_INFO("3:operator [] failed "); CHECK(z == vec4[2]); - UNSCOPED_INFO("4:operator [] failed "); CHECK(w == vec4[3]); x = 23.f, y = -.2361f, z = 3.25; vec4.setVec(x,y,z); F32 &ref1 = vec4[0]; - UNSCOPED_INFO("5:operator [] failed "); CHECK(ref1 == vec4[0]); F32 &ref2 = vec4[1]; - UNSCOPED_INFO("6:operator [] failed "); CHECK(ref2 == vec4[1]); F32 &ref3 = vec4[2]; - UNSCOPED_INFO("7:operator [] failed "); CHECK(ref3 == vec4[2]); F32 &ref4 = vec4[3]; - UNSCOPED_INFO("8:operator [] failed "); CHECK(ref4 == vec4[3]); } @@ -271,7 +208,6 @@ namespace vec4.rotVec(mat); vec4a.setVec(x,y,z,w); vec4a.rotVec(mat); - UNSCOPED_INFO("1:rotVec: Fail "); CHECK(vec4a == vec4); F32 a = 2.32f, b = -23.2f, c = -34.1112f, d = 1.010112f; LLQuaternion q(a,b,c,d); @@ -279,7 +215,6 @@ namespace vec4b.rotVec(q); vec4c.setVec(a, b, c, d); vec4c.rotVec(q); - UNSCOPED_INFO("2:rotVec: Fail "); CHECK(vec4b == vec4c); } @@ -291,7 +226,6 @@ namespace stream1 << vec4; vec4a.setVec(x,y,z,w); stream2 << vec4a; - UNSCOPED_INFO("operator << failed"); CHECK(stream1.str() == stream2.str()); } @@ -301,26 +235,19 @@ namespace F32 x2 = 1.2f, y2 = 2.5f, z2 = 1.f, w2 = 1.3f; LLVector4 vec4(x1,y1,z1,w1),vec4a(x2,y2,z2,w2),vec4b; vec4b = vec4a + vec4; - { - INFO("1:operator+:Fail to initialize "); - CHECK(is_approx_equal(x1+x2,vec4b.mV[VX])); - CHECK(is_approx_equal(y1+y2,vec4b.mV[VY])); - CHECK(is_approx_equal(z1+z2,vec4b.mV[VZ])); - } + CHECK(is_approx_equal(x1+x2,vec4b.mV[VX])); + CHECK(is_approx_equal(y1+y2,vec4b.mV[VY])); + CHECK(is_approx_equal(z1+z2,vec4b.mV[VZ])); x1 = -2.45f, y1 = 2.1f, z1 = 3.0f; vec4.clearVec(); vec4a.clearVec(); vec4.setVec(x1,y1,z1); vec4a +=vec4; - UNSCOPED_INFO("2:operator+=: Fail to initialize"); CHECK(vec4a == vec4); vec4a += vec4; - { - INFO("3:operator+=:Fail to initialize "); - CHECK(is_approx_equal(2*x1,vec4a.mV[VX])); - CHECK(is_approx_equal(2*y1,vec4a.mV[VY])); - CHECK(is_approx_equal(2*z1,vec4a.mV[VZ])); - } + CHECK(is_approx_equal(2*x1,vec4a.mV[VX])); + CHECK(is_approx_equal(2*y1,vec4a.mV[VY])); + CHECK(is_approx_equal(2*z1,vec4a.mV[VZ])); } TEST_CASE("v4math_h: operator- and operator-=", "[v4math_h]") { @@ -328,26 +255,19 @@ namespace F32 x2 = 1.2f, y2 = 2.5f, z2 = 1.f, w2 = 1.3f; LLVector4 vec4(x1,y1,z1,w1),vec4a(x2,y2,z2,w2),vec4b; vec4b = vec4a - vec4; - { - INFO("1:operator-:Fail to initialize "); - CHECK(is_approx_equal(x2-x1,vec4b.mV[VX])); - CHECK(is_approx_equal(y2-y1,vec4b.mV[VY])); - CHECK(is_approx_equal(z2-z1,vec4b.mV[VZ])); - } + CHECK(is_approx_equal(x2-x1,vec4b.mV[VX])); + CHECK(is_approx_equal(y2-y1,vec4b.mV[VY])); + CHECK(is_approx_equal(z2-z1,vec4b.mV[VZ])); x1 = -2.45f, y1 = 2.1f, z1 = 3.0f; vec4.clearVec(); vec4a.clearVec(); vec4.setVec(x1,y1,z1); vec4a -=vec4; - UNSCOPED_INFO("2:operator-=: Fail to initialize"); CHECK(vec4a == -vec4); vec4a -=vec4; - { - INFO("3:operator-=:Fail to initialize "); - CHECK(is_approx_equal(-2*x1,vec4a.mV[VX])); - CHECK(is_approx_equal(-2*y1,vec4a.mV[VY])); - CHECK(is_approx_equal(-2*z1,vec4a.mV[VZ])); - } + CHECK(is_approx_equal(-2*x1,vec4a.mV[VX])); + CHECK(is_approx_equal(-2*y1,vec4a.mV[VY])); + CHECK(is_approx_equal(-2*z1,vec4a.mV[VZ])); } TEST_CASE("v4math_h: dot product and scalar multiplication", "[v4math_h]") @@ -356,32 +276,22 @@ namespace F32 x2 = 1.2f, y2 = 2.5f, z2 = 1.f; LLVector4 vec4(x1,y1,z1),vec4a(x2,y2,z2); F32 res = vec4 * vec4a; - UNSCOPED_INFO("1:operator* failed "); CHECK(is_approx_equal(res, x1*x2 + y1*y2 + z1*z2)); vec4a.clearVec(); F32 mulVal = 4.2f; vec4a = vec4 * mulVal; - { - INFO("2:operator* failed "); - CHECK(is_approx_equal(x1*mulVal,vec4a.mV[VX])); - CHECK(is_approx_equal(y1*mulVal, vec4a.mV[VY])); - CHECK(is_approx_equal(z1*mulVal, vec4a.mV[VZ])); - } + CHECK(is_approx_equal(x1*mulVal,vec4a.mV[VX])); + CHECK(is_approx_equal(y1*mulVal, vec4a.mV[VY])); + CHECK(is_approx_equal(z1*mulVal, vec4a.mV[VZ])); vec4a.clearVec(); vec4a = mulVal * vec4 ; - { - INFO("3:operator* failed "); - CHECK(is_approx_equal(x1*mulVal, vec4a.mV[VX])); - CHECK(is_approx_equal(y1*mulVal, vec4a.mV[VY])); - CHECK(is_approx_equal(z1*mulVal, vec4a.mV[VZ])); - } + CHECK(is_approx_equal(x1*mulVal, vec4a.mV[VX])); + CHECK(is_approx_equal(y1*mulVal, vec4a.mV[VY])); + CHECK(is_approx_equal(z1*mulVal, vec4a.mV[VZ])); vec4 *= mulVal; - { - INFO("4:operator*= failed "); - CHECK(is_approx_equal(x1*mulVal, vec4.mV[VX])); - CHECK(is_approx_equal(y1*mulVal, vec4.mV[VY])); - CHECK(is_approx_equal(z1*mulVal, vec4.mV[VZ])); - } + CHECK(is_approx_equal(x1*mulVal, vec4.mV[VX])); + CHECK(is_approx_equal(y1*mulVal, vec4.mV[VY])); + CHECK(is_approx_equal(z1*mulVal, vec4.mV[VZ])); } TEST_CASE("v4math_h: cross product operator% and operator%=", "[v4math_h]") @@ -390,14 +300,10 @@ namespace F32 x2 = 1.2f, y2 = 2.5f, z2 = 1.f; LLVector4 vec4(x1,y1,z1),vec4a(x2,y2,z2),vec4b; vec4b = vec4 % vec4a; - { - INFO("1:operator% failed "); - CHECK(is_approx_equal(y1*z2 - y2*z1, vec4b.mV[VX])); - CHECK(is_approx_equal(z1*x2 -z2*x1, vec4b.mV[VY])); - CHECK(is_approx_equal(x1*y2-x2*y1, vec4b.mV[VZ])); - } + CHECK(is_approx_equal(y1*z2 - y2*z1, vec4b.mV[VX])); + CHECK(is_approx_equal(z1*x2 -z2*x1, vec4b.mV[VY])); + CHECK(is_approx_equal(x1*y2-x2*y1, vec4b.mV[VZ])); vec4 %= vec4a; - UNSCOPED_INFO("operator%= failed "); CHECK(vec4 == vec4b); } @@ -407,40 +313,29 @@ namespace F32 t = 1.f / div; LLVector4 vec4(x,y,z), vec4a; vec4a = vec4/div; - { - INFO("1:operator/ failed "); - CHECK(is_approx_equal(x*t, vec4a.mV[VX])); - CHECK(is_approx_equal(y*t, vec4a.mV[VY])); - CHECK(is_approx_equal(z*t, vec4a.mV[VZ])); - } + CHECK(is_approx_equal(x*t, vec4a.mV[VX])); + CHECK(is_approx_equal(y*t, vec4a.mV[VY])); + CHECK(is_approx_equal(z*t, vec4a.mV[VZ])); x = 1.23f, y = 4.f, z = -2.32f; vec4.clearVec(); vec4a.clearVec(); vec4.setVec(x,y,z); vec4a = vec4/div; - { - INFO("2:operator/ failed "); - CHECK(is_approx_equal(x*t, vec4a.mV[VX])); - CHECK(is_approx_equal(y*t, vec4a.mV[VY])); - CHECK(is_approx_equal(z*t, vec4a.mV[VZ])); - } + CHECK(is_approx_equal(x*t, vec4a.mV[VX])); + CHECK(is_approx_equal(y*t, vec4a.mV[VY])); + CHECK(is_approx_equal(z*t, vec4a.mV[VZ])); vec4 /= div; - { - INFO("3:operator/ failed "); - CHECK(is_approx_equal(x*t, vec4.mV[VX])); - CHECK(is_approx_equal(y*t, vec4.mV[VY])); - CHECK(is_approx_equal(z*t, vec4.mV[VZ])); - } + CHECK(is_approx_equal(x*t, vec4.mV[VX])); + CHECK(is_approx_equal(y*t, vec4.mV[VY])); + CHECK(is_approx_equal(z*t, vec4.mV[VZ])); } TEST_CASE("v4math_h: operator== and operator!=", "[v4math_h]") { F32 x = 1.f, y = 2.f, z = -1.1f; LLVector4 vec4(x,y,z), vec4a; - UNSCOPED_INFO("operator!= failed "); CHECK(vec4 != vec4a); vec4a = vec4; - UNSCOPED_INFO("operator== failed "); CHECK(vec4 ==vec4a); } @@ -449,7 +344,6 @@ namespace F32 x = 1.f, y = 2.f, z = -1.1f; LLVector4 vec4(x,y,z), vec4a; vec4a = - vec4; - UNSCOPED_INFO("operator- failed "); CHECK(vec4 == - vec4a); } @@ -457,12 +351,10 @@ namespace { F32 x = 1.f, y = 2.f, z = -1.1f,epsilon = .23425f; LLVector4 vec4(x,y,z), vec4a(x,y,z); - UNSCOPED_INFO("1:are_parallel: Fail "); CHECK(are_parallel(vec4a,vec4,epsilon)); x = 21.f, y = 12.f, z = -123.1f; vec4a.clearVec(); vec4a.setVec(x,y,z); - UNSCOPED_INFO("2:are_parallel: Fail "); CHECK_FALSE(are_parallel(vec4a,vec4,epsilon)); } @@ -475,7 +367,6 @@ namespace vec4.normVec(); vec4a.normVec(); angle2 = acos(vec4 * vec4a); - UNSCOPED_INFO("1:angle_between: Fail "); CHECK_THAT(angle1, ll_test::WithinBits(angle2, 8)); F32 x1 = 21.f, y1 = 2.23f, z1 = -1.1f; LLVector4 vec4b(x,y,z), vec4c(x1,y1,z1); @@ -483,7 +374,6 @@ namespace vec4b.normVec(); vec4c.normVec(); angle2 = acos(vec4b * vec4c); - UNSCOPED_INFO("2:angle_between: Fail "); CHECK_THAT(angle1, ll_test::WithinBits(angle2, 8)); } @@ -494,11 +384,9 @@ namespace LLVector4 vec4(x1,y1,z1),vec4a(x2,y2,z2); val1 = dist_vec(vec4,vec4a); val2 = (F32) sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2) + (z1 - z2)* (z1 -z2)); - UNSCOPED_INFO("dist_vec: Fail "); CHECK(val2 == val1); val1 = dist_vec_squared(vec4,vec4a); val2 =((x1 - x2)*(x1 - x2) + (y1 - y2)* (y1 - y2) + (z1 - z2)* (z1 -z2)); - UNSCOPED_INFO("dist_vec_squared: Fail "); CHECK(val2 == val1); } @@ -513,7 +401,6 @@ namespace val4 = w1 + (w2 - w1)* val; LLVector4 vec4b = lerp(vec4,vec4a,val); LLVector4 check(val1, val2, val3, val4); - UNSCOPED_INFO("lerp failed"); CHECK(check == vec4b); } @@ -522,14 +409,10 @@ namespace F32 x = 1.f, y = 2.f, z = -1.1f; LLVector4 vec4(x,y,z); LLVector3 vec3 = vec4to3(vec4); - { - INFO("vec4to3 failed"); - CHECK(x == vec3.mV[VX]); - CHECK(y == vec3.mV[VY]); - CHECK(z == vec3.mV[VZ]); - } + CHECK(x == vec3.mV[VX]); + CHECK(y == vec3.mV[VY]); + CHECK(z == vec3.mV[VZ]); LLVector4 vec4a = vec3to4(vec3); - UNSCOPED_INFO("vec3to4 failed"); CHECK(vec4a == vec4); } @@ -540,7 +423,6 @@ namespace LLSD llsd = vec4.getValue(); LLVector3 vec3(llsd); LLVector4 vec4a = vec3to4(vec3); - UNSCOPED_INFO("getValue failed"); CHECK(vec4a == vec4); } } diff --git a/indra/llmath/tests/xform_test.cpp b/indra/llmath/tests/xform_test.cpp index a9959d29e2..bfda263550 100644 --- a/indra/llmath/tests/xform_test.cpp +++ b/indra/llmath/tests/xform_test.cpp @@ -40,17 +40,14 @@ namespace LLVector3 emptyVec(0.f,0.f,0.f); LLVector3 initialScaleVec(1.f,1.f,1.f); - { - INFO("LLXform empty constructor failed: "); - REQUIRE_FALSE(xform_obj.getParent()); - CHECK_FALSE(xform_obj.isChanged()); - CHECK(xform_obj.getPosition() == emptyVec); - CHECK((xform_obj.getRotation()).isIdentity()); - CHECK(xform_obj.getScale() == initialScaleVec); - CHECK(xform_obj.getPositionW() == emptyVec); - CHECK((xform_obj.getWorldRotation()).isIdentity()); - CHECK_FALSE(xform_obj.getScaleChildOffset()); - } + CHECK_FALSE(xform_obj.getParent()); + CHECK_FALSE(xform_obj.isChanged()); + CHECK(xform_obj.getPosition() == emptyVec); + CHECK((xform_obj.getRotation()).isIdentity()); + CHECK(xform_obj.getScale() == initialScaleVec); + CHECK(xform_obj.getPositionW() == emptyVec); + CHECK((xform_obj.getWorldRotation()).isIdentity()); + CHECK_FALSE(xform_obj.getScaleChildOffset()); } // test cases for @@ -72,11 +69,9 @@ namespace LLVector3 vec(x, y, z); xform_obj.setScale(x, y, z); xform_obj.setPosition(x, y, posz); - UNSCOPED_INFO("setScale failed: "); CHECK(xform_obj.getScale() == vec); vec.setVec(x, y, posz); - UNSCOPED_INFO("getPosition failed: "); CHECK(xform_obj.getPosition() == vec); x = x * 2.f; @@ -86,36 +81,29 @@ namespace xform_obj.setPositionX(x); xform_obj.setPositionY(y); xform_obj.setPositionZ(z); - UNSCOPED_INFO("setPositionX/Y/Z failed: "); CHECK(xform_obj.getPosition() == vec); xform_obj.setScaleChildOffset(true); - UNSCOPED_INFO("setScaleChildOffset failed: "); CHECK(xform_obj.getScaleChildOffset()); vec.setVec(x, y, z); xform_obj.addPosition(vec); vec += vec; - UNSCOPED_INFO("addPosition failed: "); CHECK(xform_obj.getPosition() == vec); xform_obj.setScale(vec); - UNSCOPED_INFO("setScale vector failed: "); CHECK(xform_obj.getScale() == vec); LLQuaternion quat(x, y, z, w); xform_obj.setRotation(quat); - UNSCOPED_INFO("setRotation quat failed: "); CHECK(xform_obj.getRotation() == quat); xform_obj.setRotation(x, y, z, w); - UNSCOPED_INFO("getRotation 2 failed: "); CHECK(xform_obj.getRotation() == quat); xform_obj.setRotation(x, y, z); quat.setQuat(x,y,z); - UNSCOPED_INFO("setRotation xyz failed: "); CHECK(xform_obj.getRotation() == quat); // LLXform::setRotation(const F32 x, const F32 y, const F32 z) @@ -133,52 +121,38 @@ namespace LLXform grandpar; xform_obj.setParent(&par); par.setParent(&grandpar); - UNSCOPED_INFO("setParent/getParent failed: "); CHECK(&par == xform_obj.getParent()); - UNSCOPED_INFO("getRoot failed: "); CHECK(&grandpar == xform_obj.getRoot()); - { - INFO("isRoot failed: "); - CHECK(grandpar.isRoot()); - CHECK_FALSE(par.isRoot()); - CHECK_FALSE(xform_obj.isRoot()); - } - { - INFO("isRootEdit failed: "); - CHECK(grandpar.isRootEdit()); - CHECK_FALSE(par.isRootEdit()); - CHECK_FALSE(xform_obj.isRootEdit()); - } + CHECK(grandpar.isRoot()); + CHECK_FALSE(par.isRoot()); + CHECK_FALSE(xform_obj.isRoot()); + CHECK(grandpar.isRootEdit()); + CHECK_FALSE(par.isRootEdit()); + CHECK_FALSE(xform_obj.isRootEdit()); } TEST_CASE("LLXForm: setters raise their changed flags and clearChanged clears them", "[LLXForm]") { LLXform xform_obj; xform_obj.setChanged(LLXform::TRANSLATED | LLXform::ROTATED | LLXform::SCALED); - UNSCOPED_INFO("setChanged/isChanged failed: "); CHECK(xform_obj.isChanged()); xform_obj.clearChanged(LLXform::TRANSLATED | LLXform::ROTATED | LLXform::SCALED); - UNSCOPED_INFO("clearChanged failed: "); CHECK_FALSE(xform_obj.isChanged()); LLVector3 llvect3(12.4f, -5.6f, 0.34f); xform_obj.setScale(llvect3); - UNSCOPED_INFO("setScale did not set SCALED flag: "); CHECK(xform_obj.isChanged(LLXform::SCALED)); xform_obj.setPosition(1.2f, 2.3f, 3.4f); - UNSCOPED_INFO("setScale did not set TRANSLATED flag: "); CHECK(xform_obj.isChanged(LLXform::TRANSLATED)); - UNSCOPED_INFO("TRANSLATED reset SCALED flag: "); + UNSCOPED_INFO("setPosition must not clear SCALED"); CHECK(xform_obj.isChanged(LLXform::TRANSLATED | LLXform::SCALED)); xform_obj.clearChanged(LLXform::SCALED); - UNSCOPED_INFO("reset SCALED failed: "); CHECK_FALSE(xform_obj.isChanged(LLXform::SCALED)); xform_obj.setRotation(1, 2, 3, 4); - UNSCOPED_INFO("ROTATION flag not set "); + UNSCOPED_INFO("setRotation must set ROTATED"); CHECK(xform_obj.isChanged(LLXform::TRANSLATED | LLXform::ROTATED)); xform_obj.setScale(llvect3); - UNSCOPED_INFO("ROTATION flag not set "); CHECK(xform_obj.isChanged(LLXform::MOVED)); } @@ -189,37 +163,21 @@ namespace formMatrix_obj.init(); LLMatrix4 mat4_obj; - UNSCOPED_INFO("1. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[0][0] == 1.f); - UNSCOPED_INFO("2. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[0][1] == 0.f); - UNSCOPED_INFO("3. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[0][2] == 0.f); - UNSCOPED_INFO("4. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[0][3] == 0.f); - UNSCOPED_INFO("5. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[1][0] == 0.f); - UNSCOPED_INFO("6. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[1][1] == 1.f); - UNSCOPED_INFO("7. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[1][2] == 0.f); - UNSCOPED_INFO("8. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[1][3] == 0.f); - UNSCOPED_INFO("9. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[2][0] == 0.f); - UNSCOPED_INFO("10. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[2][1] == 0.f); - UNSCOPED_INFO("11. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[2][2] == 1.f); - UNSCOPED_INFO("12. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[2][3] == 0.f); - UNSCOPED_INFO("13. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[3][0] == 0.f); - UNSCOPED_INFO("14. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[3][1] == 0.f); - UNSCOPED_INFO("15. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[3][2] == 0.f); - UNSCOPED_INFO("16. The value is not NULL"); CHECK(formMatrix_obj.getWorldMatrix().mMatrix[3][3] == 1.f); } @@ -231,17 +189,11 @@ namespace LLVector3 llmin_vec3; LLVector3 llmax_vec3; formMatrix_obj.getMinMax(llmin_vec3, llmax_vec3); - UNSCOPED_INFO("1. The value is not NULL"); CHECK(llmin_vec3.mV[0] == 0.f); - UNSCOPED_INFO("2. The value is not NULL"); CHECK(llmin_vec3.mV[1] == 0.f); - UNSCOPED_INFO("3. The value is not NULL"); CHECK(llmin_vec3.mV[2] == 0.f); - UNSCOPED_INFO("4. The value is not NULL"); CHECK(llmin_vec3.mV[0] == 0.f); - UNSCOPED_INFO("5. The value is not NULL"); CHECK(llmin_vec3.mV[1] == 0.f); - UNSCOPED_INFO("6. The value is not NULL"); CHECK(llmin_vec3.mV[2] == 0.f); } @@ -276,14 +228,10 @@ namespace LLQuaternion worldRot = quat * quatparent; - UNSCOPED_INFO("getWorldPosition failed: "); CHECK(formMatrix_obj.getWorldPosition() == worldPos); - UNSCOPED_INFO("getWorldRotation failed: "); CHECK(formMatrix_obj.getWorldRotation() == worldRot); - UNSCOPED_INFO("getWorldPosition for parent failed: "); CHECK(parent.getWorldPosition() == llvecpospar); - UNSCOPED_INFO("getWorldRotation for parent failed: "); CHECK(parent.getWorldRotation() == quatparent); } } From 61c6507a0d48a0f9f2c4d1a6febbe0e9600c1c13 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 18:19:23 -0400 Subject: [PATCH 16/55] The harness's own test names no variable near, which windows.h defines away minwindef.h defines near and far as nothing, for 16-bit pointers, so on Windows the WithinBits case's const F32 near = ... declared a nameless float and nothing after it compiled. The value is just_off. Co-Authored-By: Claude Opus 5.5 --- indra/test/tests/altestsupport_test.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/indra/test/tests/altestsupport_test.cpp b/indra/test/tests/altestsupport_test.cpp index 9245ae6067..3c6546dd7d 100644 --- a/indra/test/tests/altestsupport_test.cpp +++ b/indra/test/tests/altestsupport_test.cpp @@ -55,13 +55,14 @@ namespace TEST_CASE("WithinBits holds to the bits asked for, and no further", "[altestsupport]") { - // 2^-20 apart: equal to 16 bits of the fraction, not to 24. + // 2^-20 apart: equal to 16 bits of the fraction, not to 24. (Not "near": + // windows.h defines that, and far, as nothing.) const F32 one = 1.f; - const F32 near = 1.f + 1.f / (1 << 20); - CHECK_THAT(near, ll_test::WithinBits(one, 16)); - CHECK_FALSE(ll_test::WithinBits(one, 24).match(near)); + const F32 just_off = 1.f + 1.f / (1 << 20); + CHECK_THAT(just_off, ll_test::WithinBits(one, 16)); + CHECK_FALSE(ll_test::WithinBits(one, 24).match(just_off)); CHECK_FALSE(ll_test::WithinBits(one, 16).match(1.01f)); - CHECK_THAT(F64(near), ll_test::WithinBits(F64(one), 16)); + CHECK_THAT(F64(just_off), ll_test::WithinBits(F64(one), 16)); } TEST_CASE("LLSDEquals compares all the way down and says where two differ", "[altestsupport]") From c44e8890c5a67fafda1e66eb496c4cb93036b9d1 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 18:19:24 -0400 Subject: [PATCH 17/55] The default test run leaves out one label, manual, which benchmarks and pending combined runs carry The run CI starts, BUILD_AND_RUN_TESTS, left out two labels with -LE "benchmark|combined_pending". CMake hands those arguments to Ninja's shell unquoted, which took the | for a pipe and ran combined_pending: "command not found", exit 127, on macOS and Linux. Two -LE leave out only what matches both. Benchmarks and a binary's pending combined run now also carry manual, and the default run leaves out -LE manual; -L benchmark and -L combined_pending still choose each. Co-Authored-By: Claude Opus 5.5 --- indra/CMakeLists.txt | 13 +++++++------ indra/cmake/ALAddTestBinary.cmake | 10 +++++----- indra/cmake/LLAddBuildTest.cmake | 6 +++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 7ac29f6c06..483e0b6a48 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -417,18 +417,19 @@ if(AL_BUILD_TESTS) include(ProcessorCount) ProcessorCount(NUM_CORES) - # Set parallel level to decrease test runtime. Benchmarks are registered - # as tests under their own label and left out of the default run, since - # their numbers are their output and a shared parallel run would ruin them. - # A Catch2 binary's run of all its tests in one process stays out too until - # they are known to share one (combined_pending; see ALAddTestBinary.cmake). + # Set parallel level to decrease test runtime. What is labelled manual runs + # only when asked for by its own label: benchmarks, since their numbers are + # their output and a shared parallel run would ruin them, and a Catch2 + # binary's run of all its tests in one process until they are known to + # share one (combined_pending; see ALAddTestBinary.cmake). One label, since + # these arguments reach a shell, which would take a | in a regex for a pipe. # Each run also writes its results as JUnit, which CI keeps. set( CMAKE_CTEST_ARGUMENTS -j${NUM_CORES} --output-on-failure -LE - "benchmark|combined_pending" + manual --output-junit "${CMAKE_BINARY_DIR}/Testing/test-results.xml" ) diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index b6e144e1ca..4069647614 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -32,10 +32,10 @@ endif() # file belongs to one binary, or to one at each ISA tier. # # /combined runs all of them in one process. It is labelled -# combined_pending, 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. +# 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. # # 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 @@ -207,7 +207,7 @@ function(al_add_test_binary name) list(LENGTH stems file_count) math(EXPR combined_timeout "${timeout} * ${file_count}") - set(combined_label combined_pending) + set(combined_label "combined_pending;manual") if(arg_COMBINED) set(combined_label combined) endif() diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index 6cbc67ffb9..289e2d7fd6 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -194,8 +194,8 @@ endfunction() # [INCLUDES ...] [DEFINES ...]) # # Builds tests/_bench.cpp, which brings its own main, into -# BENCH_ and registers it as a test labelled benchmark. The default -# test run leaves that label out; BUILD_AND_RUN_BENCHMARKS runs it, verbose, +# BENCH_ and registers it as a test labelled benchmark and manual. The +# default test run leaves manual out; BUILD_AND_RUN_BENCHMARKS runs it, verbose, # since the numbers are the output. A benchmark exits 125, which CTest reads # as skipped, from a build whose numbers would say nothing. function(al_add_bench name) @@ -232,7 +232,7 @@ function(al_add_bench name) ${target} PROPERTIES ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" - LABELS benchmark + LABELS "benchmark;manual" SKIP_RETURN_CODE 125 ) From 4c06fdfb4c7a3e0ef1662c8a3fd767ff4b228685 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 18:19:33 -0400 Subject: [PATCH 18/55] AL_TEST_PER_CASE configures, and its cases carry what a file's entry does Turned on, the option had never configured: include(Catch) looked on the module path for Catch.cmake, which vcpkg installs beside Catch2's package config. And had it, its cases would have run without what every other entry of the binary has: - WORKING_DIRECTORY, first among the PROPERTIES, is also a keyword of catch_discover_tests, so it ended them there: TIMEOUT, SKIP_RETURN_CODE and DISABLED went unset, and a skipped case failed. It is passed as the keyword now. - A binary's ENVIRONMENT (a PYTHON binary's interpreter among it), its AL_TEST_ENVIRONMENT, and its labels (the project, gl, isa, python) are lists, which catch_discover_tests flattens with the rest of its PROPERTIES, and a CTest script cannot append to a property. A script run after the discovery sets them on each case whole, so -L and -LE choose the same tests in either mode. The labels are a file entry's, not the cases' Catch2 tags, which a test spec still chooses by. Configured on, llmath_tests is 355 entries, each labelled llmath, with its timeout and skip code, and m3math's one skipped case is skipped. Co-Authored-By: Claude Opus 5.5 --- indra/cmake/ALAddTestBinary.cmake | 42 +++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index 4069647614..e943623284 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -35,7 +35,8 @@ endif() # 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. +# 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 @@ -156,11 +157,13 @@ function(al_add_test_binary name) endif() if(AL_TEST_PER_CASE) - include(Catch) + # Beside Catch2's package config, where vcpkg puts it, and not on the + # module path. + include("${Catch2_DIR}/Catch.cmake") + # WORKING_DIRECTORY is catch_discover_tests' own: among the PROPERTIES it + # would end them, and what came after would go unset. set( per_case_properties - WORKING_DIRECTORY - ${CMAKE_CURRENT_BINARY_DIR} TIMEOUT ${timeout} SKIP_RETURN_CODE @@ -168,21 +171,44 @@ function(al_add_test_binary name) DISABLED ${disabled} ) - if(arg_RESOURCE_LOCK) - list(APPEND per_case_properties RESOURCE_LOCK "${arg_RESOURCE_LOCK}") - endif() # The main makes Catch2's "all skipped" CTest's 125, so a skip is not # left to the discovery's own code for it. catch_discover_tests( ${target} TEST_PREFIX "${target}/" + TEST_LIST ${target}_cases + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} EXTRA_ARGS ${arguments} DISCOVERY_MODE PRE_TEST SKIP_IS_FAILURE - ADD_TAGS_AS_LABELS DL_PATHS ${SHARED_LIB_STAGING_DIR} PROPERTIES ${per_case_properties} ) + # The environment, labels and lock a file's entry has are lists, which + # catch_discover_tests flattens with the rest of its PROPERTIES, and a + # CTest script cannot append to a property: a script run after the + # discovery sets them on each case whole. The labels are a file entry's, + # not the cases' Catch2 tags, so that -L and -LE choose alike either way. + # The library path is DL_PATHS', where AL_TEST_ENVIRONMENT has nothing + # more to say. + set(case_script "${CMAKE_CURRENT_BINARY_DIR}/${target}_cases.cmake") + set(case_properties "LABELS [==[${labels}]==]") + if(environment) + string(APPEND case_properties " ENVIRONMENT [==[${environment}]==]") + endif() + if(AL_TEST_ENVIRONMENT) + string(APPEND case_properties " ENVIRONMENT_MODIFICATION [==[${AL_TEST_ENVIRONMENT}]==]") + endif() + if(arg_RESOURCE_LOCK) + string(APPEND case_properties " RESOURCE_LOCK [==[${arg_RESOURCE_LOCK}]==]") + endif() + file( + WRITE "${case_script}" + "foreach(case IN LISTS ${target}_cases)\n" + " set_tests_properties(\"\${case}\" PROPERTIES ${case_properties})\n" + "endforeach()\n" + ) + set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${case_script}") else() foreach(stem IN LISTS stems) add_test( From a88d5882bd890a626df2dd86030c4d6a84b8a84a Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 18:31:59 -0400 Subject: [PATCH 19/55] A per-case entry whose name has brackets gets its properties too catch_discover_tests' list of the cases it registered holds each name as it escaped it for a list, [ and ] as the bytes 2 and 3, while the test is registered under the name itself: set_tests_properties on the list's name would have found no such test, and ctest stopped there. The script turns them back first. Tried on a case named "t/a [b] c": it is labelled. Co-Authored-By: Claude Opus 5.5 --- indra/cmake/ALAddTestBinary.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index e943623284..6550537505 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -202,9 +202,15 @@ function(al_add_test_binary name) if(arg_RESOURCE_LOCK) string(APPEND case_properties " RESOURCE_LOCK [==[${arg_RESOURCE_LOCK}]==]") endif() + # The list holds each name as Catch2 escaped it for a list: [ and ] as + # the bytes 2 and 3. The test is registered under the name itself. file( WRITE "${case_script}" + "string(ASCII 2 open)\n" + "string(ASCII 3 close)\n" "foreach(case IN LISTS ${target}_cases)\n" + " string(REPLACE \"\${open}\" \"[\" case \"\${case}\")\n" + " string(REPLACE \"\${close}\" \"]\" case \"\${case}\")\n" " set_tests_properties(\"\${case}\" PROPERTIES ${case_properties})\n" "endforeach()\n" ) From 03e47a7eeb859d50f01064906e61c2dafe16d45f Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:37:29 -0400 Subject: [PATCH 20/55] The Catch2 harness has ensure_distance's strict matcher, WithinDistance TUT's ensure_distance failed a value on the border, expected plus or minus the distance, and worked the bounds in the test's own type. Catch2's WithinAbs passes the border and works in double, so a conversion through it passed what TUT failed. WithinDistance keeps TUT's open interval and its arithmetic; tut2catch.py will emit it. Co-Authored-By: Claude Opus 5.5 --- indra/test/altestmatchers.h | 37 +++++++++++++++++++++++++ indra/test/tests/altestsupport_test.cpp | 9 ++++++ 2 files changed, 46 insertions(+) diff --git a/indra/test/altestmatchers.h b/indra/test/altestmatchers.h index 18a385148e..74a7970f41 100644 --- a/indra/test/altestmatchers.h +++ b/indra/test/altestmatchers.h @@ -29,6 +29,7 @@ #include "llmath.h" #include "llsd.h" +#include #include #include @@ -76,6 +77,42 @@ namespace ll_test return WithinBitsMatcher(expected, frac_bits); } + // ensure_distance: strictly less than distance from expected, worked in + // T as TUT worked it, so a value on the border fails, where Catch2's + // WithinAbs, worked in double, would pass it. + template + class WithinDistanceMatcher : public Catch::Matchers::MatcherBase + { + public: + WithinDistanceMatcher(T expected, T distance) + : mExpected(expected), mDistance(distance) + { + } + + bool match(const T& actual) const override + { + return mExpected - mDistance < actual && actual < mExpected + mDistance; + } + + std::string describe() const override + { + std::ostringstream out; + out << "is strictly between " << Catch::Detail::stringify(T(mExpected - mDistance)) << " and " + << Catch::Detail::stringify(T(mExpected + mDistance)); + return out.str(); + } + + private: + T mExpected; + T mDistance; + }; + + template + WithinDistanceMatcher WithinDistance(T expected, T distance) + { + return WithinDistanceMatcher(expected, distance); + } + // ensure_equals on LLSD: the same type, and the same value all the way // down, maps in key order and arrays in element order. A mismatch is // described by where it is, as in ["key"][3] type, so a deep difference diff --git a/indra/test/tests/altestsupport_test.cpp b/indra/test/tests/altestsupport_test.cpp index 3c6546dd7d..d20970254c 100644 --- a/indra/test/tests/altestsupport_test.cpp +++ b/indra/test/tests/altestsupport_test.cpp @@ -65,6 +65,15 @@ TEST_CASE("WithinBits holds to the bits asked for, and no further", "[altestsupp CHECK_THAT(F64(just_off), ll_test::WithinBits(F64(one), 16)); } +TEST_CASE("WithinDistance holds strictly inside the distance, as ensure_distance did", "[altestsupport]") +{ + CHECK_THAT(11, ll_test::WithinDistance(12, 2)); + CHECK_FALSE(ll_test::WithinDistance(12, 2).match(10)); + CHECK_FALSE(ll_test::WithinDistance(12, 2).match(14)); + CHECK_THAT(0.5, ll_test::WithinDistance(1.0, 0.75)); + CHECK_FALSE(ll_test::WithinDistance(1.0, 0.5).match(0.5)); +} + TEST_CASE("LLSDEquals compares all the way down and says where two differ", "[altestsupport]") { CHECK_THAT(nested(1.5), ll_test::LLSDEquals(nested(1.5))); From f5a9d12c74cb8373d325fe2bffb0e7b108a8146f Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:43:08 -0400 Subject: [PATCH 21/55] The Catch2 harness fails every run that ends badly, and says what ended it A review of the harness found runs that passed when they should not have, and failures that said too little: - A teardown step that threw, an LL_ERRS or a failed llassert included, was printed and forgotten, and the run exited 0. Teardown::runAll now counts them and the main fails the run, as such a check at static destruction ended a TUT process. - The state guard was asked only of a run that exited 0, after "all skipped" had already become 125, so a case that leaked and then skipped left its entry Skipped. A run of skips is held to the guard too. - A process-wide fixture that changes process state on purpose as it is made had no way to say so, and the case that first asked for it was blamed. StateGuard::accept takes the state as it now is. - A crash ended a case without the event LOGFAIL replays on, so the log was lost exactly when it was wanted. The listener replays it as Catch2 reports the signal, unless the crashed thread held the log. - An exception off the main thread ends the process through std::terminate, which on Windows nothing named. A terminate handler names the case and what was thrown. altestfatal.h's comment said Catch2's signal handler names it everywhere; it now says where, and that an LLThread posts what its run() throws to the "mainloop" queue instead. - LLSDEquals compared dates with LLDate's ==, made of <, so a NaN date equalled every date. It compares the seconds, as TUT did. - 92f8c42a4d set Catch2's float and double precision to max_digits10, which Catch2 3.16.1 already uses; the comment's 5 and 10 were an older Catch2's. The no-op and its claim go. - --al-log's help said DEBUG; the file gets LOGTEST's or LOGFAIL's level when either names one. - listing_needs_no_setup could not fail for the reason in its name: a listing that set up first would still have listed. It now lists at an ISA level no host runs, which would skip. Each has a hidden case and an entry that checks what the run did; run_and_expect.cmake takes EXIT nonzero for a crash, whose code differs by platform. Co-Authored-By: Claude Opus 5.5 --- indra/test/CMakeLists.txt | 40 +++++++++++++++-- indra/test/altestfatal.cpp | 35 +++++++++++++++ indra/test/altestfatal.h | 16 +++++-- indra/test/altestlogreplay.cpp | 26 ++++++++++- indra/test/altestlogreplay.h | 5 ++- indra/test/altestmain.cpp | 38 +++++++++------- indra/test/altestmatchers.cpp | 4 +- indra/test/alteststateguard.cpp | 11 +++++ indra/test/alteststateguard.h | 9 ++++ indra/test/altestteardown.cpp | 6 ++- indra/test/altestteardown.h | 9 ++-- indra/test/run_and_expect.cmake | 8 +++- indra/test/tests/altestsupport_test.cpp | 59 ++++++++++++++++++++++--- 13 files changed, 227 insertions(+), 39 deletions(-) diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 3077b659f8..3d596a9862 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -67,8 +67,10 @@ set_target_properties(al_test_support PROPERTIES FOLDER "Tests") # The harness's own tests, the first Catch2 binary. Its hidden test cases are # run one at a time by the entries below, which check the exit code and the -# output CTest sees from the harness: the state guard, LOGFAIL's replay, the -# skip and no-match codes, the ISA gate, and a listing needing no setup. +# output CTest sees from the harness: the state guard and what a fixture may +# tell it, LOGFAIL's replay, a crash and an uncaught LL_ERRS, a fixture's end +# failing, the skip and no-match codes, the ISA gate, and a listing needing no +# setup. al_add_test_binary( test_support_tests PROJECT test @@ -126,7 +128,37 @@ al_expect_from_test_support( EXIT 0 REJECT "the line a passing case logged" ) +al_expect_from_test_support( + guard_accepts_a_fixture + ARGS "[.guard-accept]" + EXIT 0 + REJECT "State guard" +) +al_expect_from_test_support( + replay_on_crash + ARGS "[.crash-replay]" + EXIT nonzero + OUTPUT "log of .a test case that logged, then crashes.*the line a crashing case logged" +) +al_expect_from_test_support( + terminate_names_the_case + ARGS "[.thread-errs]" + EXIT nonzero + OUTPUT "std::terminate in test case .a test case whose thread hits an LL_ERRS.: LL_ERRS: .*an error off the main thread" +) +al_expect_from_test_support( + teardown_failure_fails + ARGS "[.teardown-fails]" + EXIT 1 + OUTPUT "Teardown step threw: LL_ERRS: .*a teardown step that fails" +) al_expect_from_test_support(all_skipped_is_125 ARGS "[.skip]" EXIT 125) +al_expect_from_test_support( + guard_fails_a_skipped_run + ARGS "[.skip-leak]" + EXIT 1 + OUTPUT "left the environment changed.*AL_TEST_SUPPORT_SKIP_LEAK=1" +) al_expect_from_test_support(nothing_matched_fails ARGS "no such test case" EXIT 2) al_expect_from_test_support( isa_gate_skips @@ -134,9 +166,11 @@ al_expect_from_test_support( EXIT 125 OUTPUT "does not run x86-64 level 9" ) +# The ISA level is one no host runs: a listing that set up first, or held to +# the gate, would skip rather than list. al_expect_from_test_support( listing_needs_no_setup - ARGS "--list-tests" + ARGS "--list-tests|--isa-level|9" EXIT 0 OUTPUT "LLSDEquals compares all the way down" ) diff --git a/indra/test/altestfatal.cpp b/indra/test/altestfatal.cpp index a3a477a334..19f02a3f5d 100644 --- a/indra/test/altestfatal.cpp +++ b/indra/test/altestfatal.cpp @@ -25,9 +25,14 @@ #include "linden_common.h" #include "altestfatal.h" +#include "altestname.h" #include "llerrorcontrol.h" +#include +#include +#include + namespace ll_test { FatalError::FatalError(const std::string& message) @@ -47,4 +52,34 @@ namespace ll_test { LLError::setFatalFunction(throwFatalError); } + + void installTerminateHandler() + { + std::set_terminate( + [] + { + std::cerr << "std::terminate in test case \"" << currentTestName() << "\""; + if (std::exception_ptr in_flight = std::current_exception()) + { + try + { + std::rethrow_exception(in_flight); + } + catch (const std::exception& e) + { + std::cerr << ": " << e.what(); + } + catch (...) + { + std::cerr << ": a non-standard exception"; + } + } + std::cerr << std::endl; +#if LL_WINDOWS + // No abort dialog for CTest to wait out. + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); +#endif + std::abort(); + }); + } } diff --git a/indra/test/altestfatal.h b/indra/test/altestfatal.h index f078540602..11b567f576 100644 --- a/indra/test/altestfatal.h +++ b/indra/test/altestfatal.h @@ -43,10 +43,20 @@ namespace ll_test }; // Makes every LL_ERRS from here on throw FatalError, on whichever thread - // it happens. Off the main thread nothing catches it, so the process - // ends there, and Catch2's signal handler names the test case that was - // running. + // it happens. Off the main thread, on a thread of the test's own, nothing + // catches it and the process ends through std::terminate; see + // installTerminateHandler. An LLThread is different: outside Windows it + // catches what its run() throws and posts it to the "mainloop" WorkQueue, + // to be thrown again there, so a test of one sees its LL_ERRS only by + // servicing that queue. void installFatalFunction(); + + // Makes std::terminate, before it aborts, name the test case that was + // running and what the exception in flight said. Catch2's own report of + // the abort that follows names the case outside Windows, but not what was + // thrown; on Windows, where it handles no SIGABRT, nothing else would say + // either. + void installTerminateHandler(); } #endif // AL_ALTESTFATAL_H diff --git a/indra/test/altestlogreplay.cpp b/indra/test/altestlogreplay.cpp index 6d2ab43869..168ce4bcbd 100644 --- a/indra/test/altestlogreplay.cpp +++ b/indra/test/altestlogreplay.cpp @@ -25,6 +25,7 @@ #include "linden_common.h" #include "altestlogreplay.h" +#include "altestname.h" #include #include @@ -60,6 +61,14 @@ namespace ll_test LogReplay::replay(std::cerr, stats.testInfo->name); } } + + // A crash -- a signal, or on Windows a structured exception -- ends + // the case without a testCasePartialEnded, at the moment its log + // is wanted most. + void fatalErrorEncountered(Catch::StringRef) override + { + LogReplay::replay(std::cerr, currentTestName(), true); + } }; } @@ -82,13 +91,26 @@ namespace ll_test } } - void LogReplay::replay(std::ostream& out, const std::string& test_case) + void LogReplay::replay(std::ostream& out, const std::string& test_case, bool after_crash) { if (!sReplay) { return; } - std::lock_guard lock(sReplay->mMutex); + std::unique_lock lock(sReplay->mMutex, std::defer_lock); + if (after_crash) + { + if (!lock.try_lock()) + { + out << "---- log of \"" << test_case << "\" not replayed: it was being written ----" + << std::endl; + return; + } + } + else + { + lock.lock(); + } out << "---- log of \"" << test_case << "\" ----\n"; for (const std::string& line : sReplay->mLines) { diff --git a/indra/test/altestlogreplay.h b/indra/test/altestlogreplay.h index 5a2e6ef7d8..67a788f2f0 100644 --- a/indra/test/altestlogreplay.h +++ b/indra/test/altestlogreplay.h @@ -47,9 +47,10 @@ namespace ll_test static void install(LLError::ELevel level); // The listener's, around each test case. Both do nothing when LOGFAIL - // was not set. + // was not set. After a crash, the thread that crashed may hold the + // log as it writes to it: then a replay says so rather than wait. static void clear(); - static void replay(std::ostream& out, const std::string& test_case); + static void replay(std::ostream& out, const std::string& test_case, bool after_crash = false); void recordMessage(LLError::ELevel level, const std::string& message) override; diff --git a/indra/test/altestmain.cpp b/indra/test/altestmain.cpp index 5a9a64ec49..a6358e06d3 100644 --- a/indra/test/altestmain.cpp +++ b/indra/test/altestmain.cpp @@ -39,11 +39,9 @@ #include "lltracethreadrecorder.h" #include -#include #include #include -#include namespace { @@ -114,18 +112,13 @@ int main(int argc, char* argv[]) "exit 125, which CTest reads as skipped, unless the host runs this x86-64 " "level: 2 for SSE4.2, 3 for AVX2, 4 for AVX-512") | Opt(log_to_file)["--al-log"]( - "also log everything, at DEBUG, to ..log")); + "also log to ..log, at the level LOGTEST or LOGFAIL " + "names, else DEBUG")); // Declared order, as TUT ran a group's tests, until no test leans on the // ones before it. --order on the command line still wins. session.configData().runOrder = Catch::TestRunOrder::Declared; - // Every digit a float or double holds, where Catch2 prints 5 and 10: two - // values a failure prints alike are then alike, which matters to tests - // that compare to the last bit. - Catch::StringMaker::precision = std::numeric_limits::max_digits10; - Catch::StringMaker::precision = std::numeric_limits::max_digits10; - if (const int result = session.applyCommandLine(argc, argv); result != 0) { return result; @@ -149,6 +142,7 @@ int main(int argc, char* argv[]) ll_init_apr(); setUpLogging(argv[0], log_to_file); ll_test::installFatalFunction(); + ll_test::installTerminateHandler(); // LLTrace needs a master recorder before any thread records. Never freed, // as the TUT runner never freed it: static destructors that record would @@ -159,20 +153,32 @@ int main(int argc, char* argv[]) int result = session.run(); - ll_test::Teardown::runAll(); + const int teardown_failures = ll_test::Teardown::runAll(); ll_cleanup_apr(); + // A run that failed no case can still fail: a case left process state + // changed, or a fixture's end found something wrong. Skipped cases ran + // too, as far as they got, so a run of nothing but skips is held to the + // same. + if (result == 0 || result == Catch::AllTestsSkippedExitCode) + { + if (ll_test::StateGuard::changes() > 0) + { + std::cerr << "The state guard found " << ll_test::StateGuard::changes() + << " change(s) a test case left behind; see above." << std::endl; + result = 1; + } + if (teardown_failures > 0) + { + std::cerr << teardown_failures << " teardown step(s) threw; see above." << std::endl; + result = 1; + } + } // Every test file is a CTest entry of its own, all with 125 as their skip // code; Catch2 says "all skipped" with 4. if (result == Catch::AllTestsSkippedExitCode) { result = 125; } - if (result == 0 && ll_test::StateGuard::changes() > 0) - { - std::cerr << "The state guard found " << ll_test::StateGuard::changes() - << " change(s) a test case left behind; see above." << std::endl; - result = 1; - } return result; } diff --git a/indra/test/altestmatchers.cpp b/indra/test/altestmatchers.cpp index 2627cd7da0..633a7ce47a 100644 --- a/indra/test/altestmatchers.cpp +++ b/indra/test/altestmatchers.cpp @@ -71,7 +71,9 @@ namespace ll_test same = actual.asUUID() == expected.asUUID(); break; case LLSD::TypeDate: - same = actual.asDate() == expected.asDate(); + // Not LLDate's ==, which is made of < and so holds a NaN + // date equal to every date; TUT compared the seconds. + same = actual.asDate().secondsSinceEpoch() == expected.asDate().secondsSinceEpoch(); break; case LLSD::TypeURI: same = actual.asURI().asString() == expected.asURI().asString(); diff --git a/indra/test/alteststateguard.cpp b/indra/test/alteststateguard.cpp index 5d2149d300..d2cc19e392 100644 --- a/indra/test/alteststateguard.cpp +++ b/indra/test/alteststateguard.cpp @@ -243,6 +243,17 @@ namespace ll_test } } + void StateGuard::accept(const std::string& check_name) + { + for (Check& check : checks()) + { + if (check_name.empty() || check.name == check_name) + { + check.last = check.describe(); + } + } + } + void StateGuard::check(const std::string& test_case) { for (Check& check : checks()) diff --git a/indra/test/alteststateguard.h b/indra/test/alteststateguard.h index bbfea4c2a3..59dc3a8fdb 100644 --- a/indra/test/alteststateguard.h +++ b/indra/test/alteststateguard.h @@ -58,6 +58,15 @@ namespace ll_test static void snapshot(); static void check(const std::string& test_case); + // A process-wide fixture's, once it is made (see Teardown): what it + // changed on purpose -- a variable it set, the LLApp it made, the + // floating-point mode a GL driver chose -- becomes the state to + // compare with, so the test case that first asked for the fixture is + // not blamed for it. Names one check, as add() named it ("the + // environment"), or every check when empty. Whatever that case had + // itself changed of the same state is accepted with it. + static void accept(const std::string& check_name = {}); + // How many times a case left something changed. The main fails the run // if any did, since a listener cannot fail a test case of its own. static int changes(); diff --git a/indra/test/altestteardown.cpp b/indra/test/altestteardown.cpp index 3c00f43049..02f2a30e4e 100644 --- a/indra/test/altestteardown.cpp +++ b/indra/test/altestteardown.cpp @@ -48,8 +48,9 @@ namespace ll_test steps().push_back(std::move(step)); } - void Teardown::runAll() + int Teardown::runAll() { + int failed = 0; std::vector>& pending = steps(); while (!pending.empty()) { @@ -61,12 +62,15 @@ namespace ll_test } catch (const std::exception& e) { + ++failed; std::cerr << "Teardown step threw: " << e.what() << std::endl; } catch (...) { + ++failed; std::cerr << "Teardown step threw a non-standard exception" << std::endl; } } + return failed; } } diff --git a/indra/test/altestteardown.h b/indra/test/altestteardown.h index 06ffd1478b..00c999e7a8 100644 --- a/indra/test/altestteardown.h +++ b/indra/test/altestteardown.h @@ -35,15 +35,18 @@ namespace ll_test // and registers its end here. The main runs the ends after the last test // case, newest first, while APR and LLError are still up. Left to static // destruction, they would run in whatever order the linker chose, after - // the main had already let APR go. + // the main had already let APR go. A fixture that changes process state + // on purpose as it is made tells the state guard (StateGuard::accept). class Teardown { public: static void add(std::function step); // The main's: runs each step once, newest first. A step that throws - // is reported and the rest still run. - static void runAll(); + // is reported and the rest still run; the count of those that threw + // is returned, and the main fails the run on any, as a check at + // static destruction ended a TUT process. + static int runAll(); }; } diff --git a/indra/test/run_and_expect.cmake b/indra/test/run_and_expect.cmake index 52f5f53bad..4b77915c08 100644 --- a/indra/test/run_and_expect.cmake +++ b/indra/test/run_and_expect.cmake @@ -9,6 +9,8 @@ # [-DEXPECT_OUTPUT=] [-DREJECT_OUTPUT=] -P run_and_expect.cmake # # ARGS separates its arguments with |, since CTest would split them on ;. +# EXPECT_EXIT nonzero takes any end but a clean exit, a crash included, whose +# code a signal decides on one platform and the system on another. string(REPLACE "|" ";" arguments "${ARGS}") execute_process( @@ -19,7 +21,11 @@ execute_process( ) message("${output}") -if(NOT exit_code STREQUAL EXPECT_EXIT) +if(EXPECT_EXIT STREQUAL "nonzero") + if(exit_code STREQUAL "0") + message(FATAL_ERROR "Expected the run to fail, and it exited 0") + endif() +elseif(NOT exit_code STREQUAL EXPECT_EXIT) message(FATAL_ERROR "Expected exit code ${EXPECT_EXIT}, got ${exit_code}") endif() if(DEFINED EXPECT_OUTPUT AND NOT output MATCHES "${EXPECT_OUTPUT}") diff --git a/indra/test/tests/altestsupport_test.cpp b/indra/test/tests/altestsupport_test.cpp index d20970254c..d8c1283ad7 100644 --- a/indra/test/tests/altestsupport_test.cpp +++ b/indra/test/tests/altestsupport_test.cpp @@ -30,6 +30,7 @@ #include "altestfatal.h" #include "altestmatchers.h" #include "altestname.h" +#include "alteststateguard.h" #include "altestteardown.h" #include "llerror.h" @@ -40,6 +41,8 @@ #include #include +#include +#include #include namespace @@ -51,6 +54,15 @@ namespace sd["b"] = "text"; return sd; } + + void setLeak(const char* name) + { +#if LL_WINDOWS + _putenv_s(name, "1"); +#else + setenv(name, "1", 1); +#endif + } } TEST_CASE("WithinBits holds to the bits asked for, and no further", "[altestsupport]") @@ -85,6 +97,11 @@ TEST_CASE("LLSDEquals compares all the way down and says where two differ", "[al const ll_test::LLSDEqualsMatcher top = ll_test::LLSDEquals(LLSD("1")); CHECK_FALSE(top.match(LLSD(1))); CHECK_THAT(top.describe(), Catch::Matchers::ContainsSubstring("the top: type")); + + // LLDate's own == holds a NaN date equal to any. + const LLSD nan_date = LLDate(std::numeric_limits::quiet_NaN()); + CHECK_FALSE(ll_test::LLSDEquals(LLDate(0.0)).match(nan_date)); + CHECK_THAT(LLSD(LLDate(1.0)), ll_test::LLSDEquals(LLDate(1.0))); } TEST_CASE("MemoryMatches compares byte for byte and says where two part", "[altestsupport]") @@ -119,10 +136,10 @@ TEST_CASE("Teardown runs each step once, newest first", "[altestsupport]") std::vector order; ll_test::Teardown::add([&order] { order.push_back(1); }); ll_test::Teardown::add([&order] { order.push_back(2); }); - ll_test::Teardown::runAll(); + CHECK(ll_test::Teardown::runAll() == 0); const std::vector newest_first{ 2, 1 }; CHECK(order == newest_first); - ll_test::Teardown::runAll(); + CHECK(ll_test::Teardown::runAll() == 0); CHECK(order.size() == 2); } @@ -131,11 +148,7 @@ TEST_CASE("Teardown runs each step once, newest first", "[altestsupport]") TEST_CASE("a test case that leaves the environment changed", "[.guard-leak]") { -#if LL_WINDOWS - _putenv_s("AL_TEST_SUPPORT_LEAK", "1"); -#else - setenv("AL_TEST_SUPPORT_LEAK", "1", 1); -#endif + setLeak("AL_TEST_SUPPORT_LEAK"); SUCCEED(); } @@ -155,3 +168,35 @@ TEST_CASE("a test case that skips", "[.skip]") { SKIP("deliberately"); } + +TEST_CASE("a test case that leaves the environment changed, then skips", "[.skip-leak]") +{ + setLeak("AL_TEST_SUPPORT_SKIP_LEAK"); + SKIP("deliberately"); +} + +TEST_CASE("a test case whose fixture sets a variable and says so", "[.guard-accept]") +{ + setLeak("AL_TEST_SUPPORT_ACCEPTED"); + ll_test::StateGuard::accept("the environment"); + SUCCEED(); +} + +TEST_CASE("a test case whose fixture's end fails", "[.teardown-fails]") +{ + ll_test::Teardown::add([] { LL_ERRS("altestsupport") << "a teardown step that fails" << LL_ENDL; }); + SUCCEED(); +} + +TEST_CASE("a test case that logged, then crashes", "[.crash-replay]") +{ + LL_INFOS("altestsupport") << "the line a crashing case logged" << LL_ENDL; + // Through a volatile pointer, so no compiler can know it is null. + volatile int* volatile nowhere = nullptr; + *nowhere = 1; +} + +TEST_CASE("a test case whose thread hits an LL_ERRS", "[.thread-errs]") +{ + std::thread([] { LL_ERRS("altestsupport") << "an error off the main thread" << LL_ENDL; }).join(); +} From 77cb2df5174fb2fd45588c7be99507886f262e6c Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:45:28 -0400 Subject: [PATCH 22/55] al_add_test_binary passes the ISA level as one argument, and fails a case no file's entry runs - catch_discover_tests hands EXTRA_ARGS to each case as a single argument, so with AL_TEST_PER_CASE every case of an ISA_TIER binary got "--isa-level;3" and failed on an unrecognised token. The level is now passed as --isa-level=N, one argument either way. - A file's tag is its name less its last extension, as Catch2 makes it; NAME_WE cut at the first dot, so foo.bar_test.cpp's entry filtered on a tag no case had. - A case written in a header or a SUPPORT library is tagged with that file's name, and no file's entry ran it: it ran only in the manual combined entry, without a word. /stray_cases runs whatever no file's tag names and expects nothing to run. - The per-case script wrote AL_TEST_ENVIRONMENT into a CTest script, where its generator expressions stayed unevaluated and put a bogus entry on PATH; DL_PATHS already gives each case the library path. - The combined run's timeout grew with the file count, past GitHub's six hours for llui; it stops at an hour. Co-Authored-By: Claude Opus 5.5 --- indra/cmake/ALAddTestBinary.cmake | 44 +++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index 6550537505..655b43b4eb 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -29,7 +29,11 @@ endif() # benchmarks, so the filter leaves those out, as a run with no filter does. A # filter that matches nothing exits 2, so a file whose test cases went missing # fails rather than passing empty. A stem names one file in a binary, and a -# file belongs to one binary, or to one at each ISA tier. +# file belongs to one binary, or to one at each ISA tier. The tag is the +# file's name less its last extension, the stem here too. A case written in a +# header, or in a SUPPORT library, is tagged with that file's name and no +# file's entry would run it, so /stray_cases fails when the binary has +# one: it runs whatever no file's tag names, and expects nothing to run. # # /combined runs all of them in one process. It is labelled # combined_pending and manual, which the default run leaves out, until the @@ -79,7 +83,7 @@ function(al_add_test_binary name) set(stems) foreach(source IN LISTS arg_SOURCES) - get_filename_component(stem "${source}" NAME_WE) + get_filename_component(stem "${source}" NAME_WLE) if(stem IN_LIST stems) message( FATAL_ERROR @@ -137,7 +141,9 @@ function(al_add_test_binary name) if(tree_level GREATER level) set(level ${tree_level}) endif() - list(APPEND arguments --isa-level ${level}) + # One argument, not two: catch_discover_tests hands EXTRA_ARGS to each + # case as one, joined with ;. + list(APPEND arguments --isa-level=${level}) list(APPEND labels isa) endif() if(arg_PYTHON) @@ -189,16 +195,13 @@ function(al_add_test_binary name) # CTest script cannot append to a property: a script run after the # discovery sets them on each case whole. The labels are a file entry's, # not the cases' Catch2 tags, so that -L and -LE choose alike either way. - # The library path is DL_PATHS', where AL_TEST_ENVIRONMENT has nothing - # more to say. + # The library path is DL_PATHS', and not AL_TEST_ENVIRONMENT's, whose + # generator expressions a script written here would leave unevaluated. set(case_script "${CMAKE_CURRENT_BINARY_DIR}/${target}_cases.cmake") set(case_properties "LABELS [==[${labels}]==]") if(environment) string(APPEND case_properties " ENVIRONMENT [==[${environment}]==]") endif() - if(AL_TEST_ENVIRONMENT) - string(APPEND case_properties " ENVIRONMENT_MODIFICATION [==[${AL_TEST_ENVIRONMENT}]==]") - endif() if(arg_RESOURCE_LOCK) string(APPEND case_properties " RESOURCE_LOCK [==[${arg_RESOURCE_LOCK}]==]") endif() @@ -237,8 +240,33 @@ function(al_add_test_binary name) endif() endforeach() + # Every case no file's tag names: a filter of nothing but exclusions + # leaves the hidden cases out, as the files' entries do, and one that + # matches nothing exits 2. The ISA gate is not asked: nothing runs, and a + # listing already needs the binary to start on any host. + set(strays) + foreach(stem IN LISTS stems) + string(APPEND strays "~[#${stem}]") + endforeach() + add_test( + NAME ${target}/stray_cases + COMMAND + ${CMAKE_COMMAND} "-DEXE=$" "-DARGS=--filenames-as-tags|${strays}~[.]" + -DEXPECT_EXIT=2 -P ${INDRA_SOURCE_DIR}/test/run_and_expect.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties( + ${target}/stray_cases + 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) From 9fe76d9c41579991353c729c309b9aa8c8261366 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:45:28 -0400 Subject: [PATCH 23/55] CI keeps the build's test log and the combined run's JUnit, and the docs run what the build runs The pending combined step passed --output-junit a path under the build directory while CTest reads a relative one from the test directory, so the file landed in build-X/build-X/Testing and the upload never found it. Its ctest run also wrote LastTest.log over the build's, so the log CI kept was only the combined run's. The build's log is copied aside first, under a name the upload takes, the JUnit path is relative to the test directory, and the step is bounded at an hour. BUILD.md and CLAUDE.md gave a plain ctest run, which now runs the pending combined entries the build's own test target leaves out; they pass -LE manual, and say where the Catch2 binaries come from. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yaml | 13 +++++++++++-- CLAUDE.md | 6 ++++-- doc/BUILD.md | 6 ++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5fc27f25fd..91b56e6b4f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -517,10 +517,12 @@ jobs: # Each Catch2 binary's run of all its tests in one process, while it is # still combined_pending: the default run leaves those out until they # are known to share a process, and this is where that is learned, on - # every row. It reports, and never fails the job. + # every row. It reports, and never fails the job; an hour bounds it, as + # CTest bounds each run, should one hang. - name: Run pending combined tests if: ${{ !cancelled() && matrix.build_variant == 'Tests' }} continue-on-error: true + timeout-minutes: 60 shell: bash env: BUILD_DIRECTORY: ${{ steps.configure.outputs.build_directory }} @@ -531,9 +533,16 @@ jobs: optdebug) cmake_config=OptDebug ;; *) cmake_config=Debug ;; esac + # This run writes LastTest.log over the build's; the build's is kept + # under a name the upload below still takes. + log="$BUILD_DIRECTORY/Testing/Temporary/LastTest.log" + if [[ -f "$log" ]]; then + cp "$log" "$BUILD_DIRECTORY/Testing/Temporary/LastTest-build.log" + fi + # CTest reads a relative --output-junit from the test directory. ctest --test-dir "$BUILD_DIRECTORY" -C "$cmake_config" -L combined_pending \ --output-on-failure --no-tests=ignore \ - --output-junit "$BUILD_DIRECTORY/Testing/combined-pending-results.xml" + --output-junit Testing/combined-pending-results.xml # What each test did, on a row whose tests failed as much as on one # whose tests passed: the JUnit file CTest writes and its own log. The diff --git a/CLAUDE.md b/CLAUDE.md index 6a682455d8..eb07719ace 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,10 +57,12 @@ The viewer executable lands at `build--/newview//` (e.g., `S Tests are off by default. To enable: add `-DAL_BUILD_TESTS=ON` to the configure command. Then run via CTest: ``` -ctest --test-dir build-- --output-on-failure +ctest --test-dir build-- --output-on-failure -LE manual ``` -Unit tests live alongside the library they test in `indra//tests/` directories. The test framework is TUT (Template Unit Test). Test targets are defined by the `LL_ADD_PROJECT_UNIT_TESTS` macro in `indra/cmake/LLAddBuildTest.cmake`. Integration tests are in `indra/integration_tests/`. +`-LE manual` leaves out the benchmarks and the pending one-process runs of Catch2 binaries (label `combined_pending`), as the build's own test target does. + +Unit tests live alongside the library they test in `indra//tests/` directories. They are moving from TUT (Template Unit Test) to Catch2 a library at a time. TUT test targets are defined by the `LL_ADD_PROJECT_UNIT_TESTS` macro in `indra/cmake/LLAddBuildTest.cmake`; a converted library builds its files into one Catch2 binary with `al_add_test_binary` (`indra/cmake/ALAddTestBinary.cmake`), on the harness in `indra/test/`. Integration tests are in `indra/integration_tests/`. ## Architecture diff --git a/doc/BUILD.md b/doc/BUILD.md index 2145c434d2..6e0d0547ad 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -430,10 +430,12 @@ Build, then run with CTest: ``` cmake --build --config RelWithDebInfo -ctest --test-dir --output-on-failure +ctest --test-dir --output-on-failure -LE manual ``` -Unit tests live alongside the library they cover in `indra//tests/`, written against the TUT (Template Unit Test) framework. Integration tests are in `indra/integration_tests/`. +`-LE manual` leaves out what the build's own test target leaves out: the benchmarks, and each Catch2 binary's run of all its files in one process while that run is still labelled `combined_pending` (`ctest -L combined_pending` runs those). + +Unit tests live alongside the library they cover in `indra//tests/`. They are moving from the TUT (Template Unit Test) framework to Catch2 a library at a time: a converted library builds its test files into one binary (`al_add_test_binary`, `indra/cmake/ALAddTestBinary.cmake`), each file still a CTest entry of its own, on the harness in `indra/test/`. Integration tests are in `indra/integration_tests/`. ## Editing XUI From 68d7ff64bb7bf6d2c96888d4cc2059b24d11742a Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:47:18 -0400 Subject: [PATCH 24/55] mathmisc's cases run in TUT's order, and llmatrix4a stops where TUT stopped TUT keeps a file's groups in a std::map and so runs them by name, each group's tests by number; Catch2 runs cases as they are declared. mathmisc declared BasicLindenMath's last two tests after LLLine and the LLUUID group before LLCrc and LLLine, so the converted file ran them in another order than TUT did. BasicLindenMath's two join the rest of their group and the LLUUID group goes last, which is TUT's order: BasicLindenMath, LLCrc, LLLine, mathmisc LLUUID. Nothing in the file leaned on the old order (it passes in random orders either way); parity now holds the order to TUT's. llmatrix4a's far-matrix inverse was a CHECK, though the products after it read the inverse it fills in: had setInverse failed, they would have read an unset matrix where TUT's ensure stopped the test. It is a REQUIRE. The case named "setTranslation and setRow change only what they name" checks less than that, row 0 and the translation, then the row setRow wrote; its name says so. Co-Authored-By: Claude Opus 5.5 --- indra/llmath/tests/llmatrix4a_test.cpp | 4 +- indra/llmath/tests/mathmisc_test.cpp | 130 ++++++++++++------------- 2 files changed, 66 insertions(+), 68 deletions(-) diff --git a/indra/llmath/tests/llmatrix4a_test.cpp b/indra/llmath/tests/llmatrix4a_test.cpp index d667ab8b16..c85aec8462 100644 --- a/indra/llmath/tests/llmatrix4a_test.cpp +++ b/indra/llmath/tests/llmatrix4a_test.cpp @@ -489,7 +489,7 @@ namespace CHECK(turned[2] == 3.f); } - TEST_CASE("LLMatrix4a: setTranslation and setRow change only what they name", "[LLMatrix4a]") + TEST_CASE("LLMatrix4a: setTranslation writes the translation and keeps row 0, and setRow writes its row", "[LLMatrix4a]") { LLMatrix4a m; m.initAll(LLVector3(2.f, 2.f, 2.f), @@ -799,7 +799,7 @@ namespace LLMatrix4a far_away; far_away.initAll(LLVector3(1.f, 1.f, 1.f), ROTATIONS[5], LLVector3(-256.f, 4096.f, 0.125f)); LLMatrix4a far_inv; - CHECK(far_inv.setInverse(far_away)); + REQUIRE(far_inv.setInverse(far_away)); require_product_is_identity("far M * inverse(M)", far_away, far_inv, 1e-6f); require_product_is_identity("far inverse(M) * M", far_inv, far_away, 1e-6f); diff --git a/indra/llmath/tests/mathmisc_test.cpp b/indra/llmath/tests/mathmisc_test.cpp index e3a2d3e558..70dd912dee 100644 --- a/indra/llmath/tests/mathmisc_test.cpp +++ b/indra/llmath/tests/mathmisc_test.cpp @@ -154,55 +154,34 @@ namespace CHECK(angle <=F_PI); CHECK(angle >= -F_PI); } -} - -namespace -{ - struct uuid_data - { - LLUUID id; - }; - - TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: isNull by default and after setNull but not after generate", "[mathmisc_LLUUID]") - { - CHECK(id.isNull()); - id.generate(); - CHECK(id.notNull()); - id.setNull(); - CHECK(id.isNull()); - } - TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: copies and assignments compare equal", "[mathmisc_LLUUID]") - { - id.generate(); - LLUUID a(id); - CHECK(id == a); - a.generate(); - CHECK(id != a); - a = id; - CHECK(id == a); - } - - TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: xor with a mask twice restores the id", "[mathmisc_LLUUID]") + // The floating-point environment this executable was linked with. The + // build sets flush-to-zero and denormals-are-zero per thread on purpose, + // in code; nothing may impose them on the whole process from the link + // line. A denormal that survives a multiply here means no crtfastmath.o + // was linked. + TEST_CASE("BasicLindenMath: the process default keeps denormals", "[BasicLindenMath]") { - id.generate(); - LLUUID copy(id); - LLUUID mask; - mask.generate(); - copy ^= mask; - CHECK(id != copy); - copy ^= mask; - CHECK(id == copy); + volatile float denormal = 1.0e-40f; + volatile float one = 1.0f; + float product = denormal * one; + UNSCOPED_INFO("denormals are not flushed by the process default"); + CHECK(product != 0.0f); + UNSCOPED_INFO("a denormal is still a denormal"); + CHECK(product == 1.0e-40f); } - TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: asString round-trips through the string constructor", "[mathmisc_LLUUID]") + // Optimised builds relax floating-point semantics to MSVC's /fp:fast + // contract, which keeps NaN and infinity. -ffinite-math-only would fold + // both of these guards to a constant. + TEST_CASE("BasicLindenMath: optimised builds keep NaN and infinity", "[BasicLindenMath]") { - id.generate(); - std::string id_str = id.asString(); - LLUUID copy(id_str.c_str()); - CHECK(id == copy); + volatile float zero = 0.0f; + float nan = zero / zero; + float inf = 1.0f / zero; + CHECK(std::isnan(nan)); + CHECK(std::isinf(inf)); } - } namespace @@ -550,33 +529,52 @@ namespace REQUIRE(measured_intersection.intersects(some_point, LARGE_RADIUS * allowable_relative_error)); } } +} - // The floating-point environment this executable was linked with. The - // build sets flush-to-zero and denormals-are-zero per thread on purpose, - // in code; nothing may impose them on the whole process from the link - // line. A denormal that survives a multiply here means no crtfastmath.o - // was linked. - TEST_CASE("BasicLindenMath: the process default keeps denormals", "[BasicLindenMath]") +namespace +{ + struct uuid_data { - volatile float denormal = 1.0e-40f; - volatile float one = 1.0f; - float product = denormal * one; - UNSCOPED_INFO("denormals are not flushed by the process default"); - CHECK(product != 0.0f); - UNSCOPED_INFO("a denormal is still a denormal"); - CHECK(product == 1.0e-40f); + LLUUID id; + }; + + TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: isNull by default and after setNull but not after generate", "[mathmisc_LLUUID]") + { + CHECK(id.isNull()); + id.generate(); + CHECK(id.notNull()); + id.setNull(); + CHECK(id.isNull()); } - // Optimised builds relax floating-point semantics to MSVC's /fp:fast - // contract, which keeps NaN and infinity. -ffinite-math-only would fold - // both of these guards to a constant. - TEST_CASE("BasicLindenMath: optimised builds keep NaN and infinity", "[BasicLindenMath]") + TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: copies and assignments compare equal", "[mathmisc_LLUUID]") { - volatile float zero = 0.0f; - float nan = zero / zero; - float inf = 1.0f / zero; - CHECK(std::isnan(nan)); - CHECK(std::isinf(inf)); + id.generate(); + LLUUID a(id); + CHECK(id == a); + a.generate(); + CHECK(id != a); + a = id; + CHECK(id == a); + } + + TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: xor with a mask twice restores the id", "[mathmisc_LLUUID]") + { + id.generate(); + LLUUID copy(id); + LLUUID mask; + mask.generate(); + copy ^= mask; + CHECK(id != copy); + copy ^= mask; + CHECK(id == copy); } -} + TEST_CASE_METHOD(uuid_data, "mathmisc LLUUID: asString round-trips through the string constructor", "[mathmisc_LLUUID]") + { + id.generate(); + std::string id_str = id.asString(); + LLUUID copy(id_str.c_str()); + CHECK(id == copy); + } +} From 3f32f5d036edde68979bdf96d72040d7eb42d4fe Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:45:24 -0400 Subject: [PATCH 25/55] tut_parity.py takes TUT's result for a test only from the entry that ran it A test defined in both branches of an #if is two mapping entries with one group and number, and TUT ran only the active one. The loop took TUT's result for the key first and only then skipped the inactive twin, so where the inactive entry came first it carried the result away: the active twin then "did not run under TUT", and its case was "no TUT test of the mapping" -- two errors for a file whose conversion was right. An entry not compiled here, or past its group's limit with no result, is now listed before anything is taken, and the result stays for the twin that ran it. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 665f7adb8f..ae2a824f46 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -309,14 +309,16 @@ def check(args): seen_cases = set() for e in entries: key = (e["group"], e["n"]) - t = tut_results.pop(key, None) name = e["catch2_name"] - if e.get("not_compiled_here") or (e.get("past_group_limit") and t is None): + # Before taking TUT's result: a test defined in both branches + # of an #if is two entries, and the result is the active twin's. + if e.get("not_compiled_here") or (e.get("past_group_limit") and key not in tut_results): listed.append(f"{e['group']} {e['n']}: " + ("not compiled here" if e.get("not_compiled_here") else "past its group's limit")) if name in cases: seen_cases.add(name) continue + t = tut_results.pop(key, None) if t is None: problems.append(f"{path}: {e['group']} test<{e['n']}> did not run under TUT") continue From 2634d9d98a8e8edf42be9c763335f369c03e0137 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:48:30 -0400 Subject: [PATCH 26/55] tut_parity.py holds a run's cases one by one, and its exit code to them It read the XML into a dict by case name, and Catch2 3.16.1 takes two cases of one name that differ in their tags: a converted file with a failing "demo: sub" and a passing one beside it kept whichever came last, so parity could pass a failing conversion. The run's exit code was printed and never read: a run whose cases all passed but whose process then died (exit -6) held. And a case tagged [!mayfail] or [!shouldfail], whose failures Catch2 forgives, matched a TUT pass. The cases are now a list in the order they ran. A second case of a name is a problem of its own, and the first is the one matched; a case under either forgiving tag is a problem; and the exit code must fit the cases: 0 when some passed and none failed (1 when the state guard says it found a change), 125 when every case skipped, and neither of those when any failed or none ran. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 69 ++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index ae2a824f46..23b3ed85a6 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -18,6 +18,11 @@ - each pair agrees: ok is a pass, a TUT skip is a Catch2 skip, and every TUT failure (fail, exception, abnormal end) is a Catch2 failure; and the name TUT printed is the one the mapping folded into the case's; + - no two cases share a name (Catch2 takes two that differ in their tags), + none is tagged [!mayfail] or [!shouldfail], under which Catch2 passes a + case whatever it does, and the run's exit code fits its cases: 0 when + some passed and none failed, 125 when all skipped, neither when any + failed; - each case's assertions, counted in the converted file, number its TUT test's ensure, fail and skip calls, as the mapping counted them in the TUT file, where a && chain the converter split counts one a term @@ -50,6 +55,11 @@ ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) DEFAULT_BUILD = "build-Darwin-ninja-os" DEFAULT_CONFIG = "RelWithDebInfo" +# What every test entry exits with when it skips: the harness turns Catch2's +# "all skipped" into it, and CTest reads it as skipped. +SKIP_CODE = 125 +# Tags under which Catch2 passes a case whatever it does. +FORGIVING_TAGS = ("[!mayfail]", "[!shouldfail]") RESULT_LINE = re.compile( r"^\[(?P.+?), (?P\d+)(?:: (?P.*?))?\] " r"(?Pok|fail|exception|test destructor throw|abnormal termination|skipped known failure|unknown)\b") @@ -157,29 +167,42 @@ def record(args): # check # -------------------------------------------------------------------------- def catch2_results(test, timeout): - """{case name: pass | skip | fail} from running a Catch2 entry as XML.""" + """The cases a Catch2 entry ran, as XML, in the order it ran them: each + {name, tags, result: ok | skip | fail}. A list, not a dict by name: Catch2 + takes two cases of one name that differ in their tags.""" with tempfile.TemporaryDirectory() as tmp: report = os.path.join(tmp, "report.xml") code, text = run(test, ["-r", f"xml::out={report}"], timeout) - cases = {} + cases = [] try: root = ElementTree.parse(report).getroot() except (ElementTree.ParseError, FileNotFoundError): return code, None, text for case in root.iter("TestCase"): result = case.find("OverallResult") - if result is None: - cases[case.get("name")] = "fail" - continue - if result.get("success") != "true": - cases[case.get("name")] = "fail" + if result is None or result.get("success") != "true": + outcome = "fail" elif int(result.get("skips", "0")) > 0: - cases[case.get("name")] = "skip" + outcome = "skip" else: - cases[case.get("name")] = "ok" + outcome = "ok" + cases.append({"name": case.get("name"), "tags": case.get("tags", ""), "result": outcome}) return code, cases, text +def exit_fits(code, cases, output): + """Whether a run's exit code says what its cases do: 0 when some passed + and none failed (1 when the state guard found a change, which it says), + the skip code when every case skipped, and neither when any failed or + none ran -- a filter that matches nothing exits 2.""" + results = {c["result"] for c in cases} + if "fail" in results or not results: + return code not in (0, SKIP_CODE, "timeout") + if results == {"skip"}: + return code == SKIP_CODE + return code == 0 or (code == 1 and "The state guard found" in output) + + def recount(path, cases): """The Catch2 assertions in each named case's body, from the file itself.""" with open(path, "rb") as f: @@ -302,6 +325,24 @@ def check(args): problems.append(f"{path}: {entry_names[0]} gave no XML (exit {code}):\n{output[-2000:]}") continue guard = [line for line in output.splitlines() if line.startswith("State guard:")] + # The first case of a name is the one matched; a second is an error + # of its own. + by_name = {} + for case in cases: + if case["name"] in by_name: + problems.append(f"{path}: Catch2 has more than one case named {case['name']!r}") + else: + by_name[case["name"]] = case + for tag in FORGIVING_TAGS: + if tag in case["tags"]: + problems.append(f"{path}: {case['name']!r} is tagged {tag}, under which Catch2 passes it " + f"whatever it does") + if not exit_fits(code, cases, output): + counts = {} + for case in cases: + counts[case["result"]] = counts.get(case["result"], 0) + 1 + problems.append(f"{path}: {entry_names[0]} exited {code}, which does not fit its cases (" + + (", ".join(f"{v} {k}" for k, v in sorted(counts.items())) or "none") + ")") tut_results = {(r["group"], r["n"]): r for r in recorded["results"]} counted = recount(os.path.join(ROOT, path), [e["catch2_name"] for e in entries if e["catch2_name"]]) matched = agree = counts_ok = 0 @@ -315,14 +356,14 @@ def check(args): if e.get("not_compiled_here") or (e.get("past_group_limit") and key not in tut_results): listed.append(f"{e['group']} {e['n']}: " + ("not compiled here" if e.get("not_compiled_here") else "past its group's limit")) - if name in cases: + if name in by_name: seen_cases.add(name) continue t = tut_results.pop(key, None) if t is None: problems.append(f"{path}: {e['group']} test<{e['n']}> did not run under TUT") continue - if not name or name not in cases: + if not name or name not in by_name: problems.append(f"{path}: {e['group']} test<{e['n']}> has no Catch2 case" + (f" ({name!r})" if name else "")) continue @@ -331,10 +372,10 @@ def check(args): if e.get("tut_name") is not None and e["tut_name"] != t["name"]: problems.append(f"{path}: {e['group']} test<{e['n']}>: TUT named it {t['name']!r}, " f"the mapping {e['tut_name']!r}") - if cases[name] == t["result"]: + if by_name[name]["result"] == t["result"]: agree += 1 else: - problems.append(f"{path}: {name!r}: TUT {t['said']!r}, Catch2 {cases[name]}") + problems.append(f"{path}: {name!r}: TUT {t['said']!r}, Catch2 {by_name[name]['result']}") static = counted.get(name) # One TUT call is one Catch2 assertion, but for a && chain split # into one assertion a term, which the mapping counted. @@ -350,7 +391,7 @@ def check(args): f"{static} in the file") for key, t in sorted(tut_results.items()): problems.append(f"{path}: TUT ran [{key[0]}, {key[1]}], which the mapping does not have") - for name in sorted(set(cases) - seen_cases): + for name in sorted(set(by_name) - seen_cases): problems.append(f"{path}: Catch2 case {name!r} is no TUT test of the mapping") clean = not any(p.startswith(path + ":") for p in problems) rows.append((path, len(recorded["results"]), len(cases), matched, agree, counts_ok, len(listed), code, From 3f448df87622202b12961efee149df962066c356 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:49:00 -0400 Subject: [PATCH 27/55] tut_parity.py holds the cases to the order TUT ran their tests in The harness runs a file's cases in declared order because a TUT test may lean on what the tests before it left, and the converter moves a test declared out of order to keep that order, but parity never looked: a conversion that ran test 6 before test 5, so that 5 no longer saw the fresh state it checks, held. The run's cases, the first of each name, are now mapped back to their TUT group and number and must come in the order of the recorded results, which is the order TUT ran them in. Listed entries, and cases that matched nothing (problems of their own), are left out of both. On HEAD's llmath this finds mathmisc_test: TUT ran its groups by name (BasicLindenMath, LLCrc, LLLine, mathmisc LLUUID), and the file runs mathmisc LLUUID's tests after BasicLindenMath's eleventh and that group's last two at the end. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 23b3ed85a6..ed16a9f2a9 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -18,6 +18,9 @@ - each pair agrees: ok is a pass, a TUT skip is a Catch2 skip, and every TUT failure (fail, exception, abnormal end) is a Catch2 failure; and the name TUT printed is the one the mapping folded into the case's; + - the cases run in the order TUT ran their tests, groups by name and + each group's tests by number, since a test may lean on what the ones + before it left; - no two cases share a name (Catch2 takes two that differ in their tags), none is tagged [!mayfail] or [!shouldfail], under which Catch2 passes a case whatever it does, and the run's exit code fits its cases: 0 when @@ -348,6 +351,7 @@ def check(args): matched = agree = counts_ok = 0 listed = [] seen_cases = set() + paired = {} for e in entries: key = (e["group"], e["n"]) name = e["catch2_name"] @@ -368,6 +372,7 @@ def check(args): + (f" ({name!r})" if name else "")) continue seen_cases.add(name) + paired[name] = key matched += 1 if e.get("tut_name") is not None and e["tut_name"] != t["name"]: problems.append(f"{path}: {e['group']} test<{e['n']}>: TUT named it {t['name']!r}, " @@ -393,6 +398,17 @@ def check(args): problems.append(f"{path}: TUT ran [{key[0]}, {key[1]}], which the mapping does not have") for name in sorted(set(by_name) - seen_cases): problems.append(f"{path}: Catch2 case {name!r} is no TUT test of the mapping") + # TUT ran a file's groups by name and each group's tests by number, + # and a test may lean on what the ones before it left. + keys = set(paired.values()) + tut_order = [(r["group"], r["n"]) for r in recorded["results"] if (r["group"], r["n"]) in keys] + catch2_order = [(paired[c["name"]], c["name"]) for c in cases + if c["name"] in paired and by_name[c["name"]] is c] + for at, (tut_key, (key, name)) in enumerate(zip(tut_order, catch2_order)): + if tut_key != key: + problems.append(f"{path}: Catch2 runs {name!r}, [{key[0]}, {key[1]}], as test {at + 1} of " + f"{len(tut_order)}, where TUT ran [{tut_key[0]}, {tut_key[1]}]: not TUT's order") + break clean = not any(p.startswith(path + ":") for p in problems) rows.append((path, len(recorded["results"]), len(cases), matched, agree, counts_ok, len(listed), code, "ok" if clean else "MISMATCH")) From c1a6e97a90e4e5aa01e4ca09990958c49921b129 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:52:11 -0400 Subject: [PATCH 28/55] tut_parity.py holds every assertion in a passing case to having run Its assertion count was macro text: a CHECK in a loop that never turns counted, and so did one under #if 0, since its own masking knew nothing of dead branches. A conversion whose assertions never ran passed as long as their number was right. The run now keeps its passing assertions (-s), streamed, since a case that loops over its checks writes megabytes, and records the file and line of each that reported. Every CHECK, REQUIRE and _THAT site in the body of a case that passed must have reported from a line it spans -- AppleClang reports a call spread over lines from its last, and others may not. A FAIL or SKIP that ran would have failed or skipped the case, and a case that failed or skipped may have stopped early, so neither is held to it. A site outside every case, in a helper, that no case ran is listed: llsdutil_math_test's test_matches is dead in TUT too. The converted file is read as tut2catch.py reads it, through its own mask_source, dead_regions and CATCH_ASSERTION, loaded by path from beside this script, so the count is made the way the mapping's was and a case or assertion under #if 0 is not counted at all. Over HEAD's 23 llmath files, 1,772 sites in the bodies of passing cases are held, and every one ran; nothing new is found there. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 218 +++++++++++++++++++++++----------- 1 file changed, 147 insertions(+), 71 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index ed16a9f2a9..6c2a031d47 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -10,7 +10,8 @@ A test TUT never reported (a dummy test slot) is not a test. check after converting and building: runs each converted file's CTest entry, /, as XML, and matches the cases to the - TUT results through tut2catch.py's mapping. + TUT results through tut2catch.py's mapping. tut2catch.py must be + beside this script: the converted file is read as it reads one. Parity holds for a file when: - every TUT test it reported has exactly one Catch2 case, and every case @@ -26,15 +27,23 @@ case whatever it does, and the run's exit code fits its cases: 0 when some passed and none failed, 125 when all skipped, neither when any failed; - - each case's assertions, counted in the converted file, number its - TUT test's ensure, fail and skip calls, as the mapping counted them in - the TUT file, where a && chain the converter split counts one a term - (catch2_expected). After a file has been edited by hand past what the - converter wrote, --recounted lists the counts instead of holding them - to the mapping; names and results must still agree. + - each case's assertions, counted in the converted file (read as + tut2catch.py reads it: comments, literals and #if 0 code left out), + number its TUT test's ensure, fail and skip calls, as the mapping + counted them in the TUT file, where a && chain the converter split + counts one a term (catch2_expected). After a file has been edited by + hand past what the converter wrote, --recounted lists the counts + instead of holding them to the mapping; names and results must still + agree; + - every assertion counted in a passing case's body ran: the run keeps its + passing assertions (-s), and each CHECK, REQUIRE and _THAT site must + report from at least one line it spans. A FAIL or SKIP that ran would + have failed or skipped the case, and a case that failed or skipped may + have stopped early, so neither is held to it. A test the mapping says TUT never ran (past its group's limit) or this host -does not compile is listed, not counted against parity. So is what the +does not compile is listed, not counted against parity, as is an assertion +outside every case (a helper's) that no case ran. So is what the Catch2 harness's state guard says a case left changed: TUT never looked, so it is a finding about the test, not a difference the conversion made; the table's exit column shows the run's exit code. It prints a table, one row a @@ -47,6 +56,8 @@ python3 -I scripts/testing/tut_parity.py check --tut /tmp/tut.json --mapping /tmp/map.json """ import argparse +import bisect +import importlib.util import json import os import re @@ -66,8 +77,11 @@ RESULT_LINE = re.compile( r"^\[(?P.+?), (?P\d+)(?:: (?P.*?))?\] " r"(?Pok|fail|exception|test destructor throw|abnormal termination|skipped known failure|unknown)\b") -CATCH_ASSERTION = re.compile( - r"(?:])(?:REQUIRE|REQUIRE_FALSE|REQUIRE_THAT|CHECK|CHECK_FALSE|CHECK_THAT|FAIL|FAIL_CHECK|SKIP)\s*\(") +# Assertions that report only when they run, and then fail or skip the case: +# in a case that passed, one of these not running is the pass. +REPORTING_ONLY = {"FAIL", "FAIL_CHECK", "SKIP"} +# What a run's XML says an assertion ran with, and where. +RAN_ELEMENTS = {"Expression", "Failure", "Skip"} # -------------------------------------------------------------------------- @@ -170,26 +184,42 @@ def record(args): # check # -------------------------------------------------------------------------- def catch2_results(test, timeout): - """The cases a Catch2 entry ran, as XML, in the order it ran them: each - {name, tags, result: ok | skip | fail}. A list, not a dict by name: Catch2 - takes two cases of one name that differ in their tags.""" + """The cases a Catch2 entry ran, as XML with its passing assertions too + (-s), in the order it ran them: each {name, tags, line, result: ok | skip + | fail, ran: {(file, line) of each assertion that ran}}. A list, not a + dict by name: Catch2 takes two cases of one name that differ in their + tags. Read as it streams: a case that loops over its checks writes + megabytes.""" with tempfile.TemporaryDirectory() as tmp: report = os.path.join(tmp, "report.xml") - code, text = run(test, ["-r", f"xml::out={report}"], timeout) + code, text = run(test, ["-s", "-r", f"xml::out={report}"], timeout) cases = [] + case = None try: - root = ElementTree.parse(report).getroot() + for event, element in ElementTree.iterparse(report, events=("start", "end")): + if event == "start": + if element.tag == "TestCase": + case = {"name": element.get("name"), "tags": element.get("tags", ""), + "line": int(element.get("line", "0")), "result": "fail", "ran": set()} + continue + if case is None: + continue + if element.tag in RAN_ELEMENTS: + case["ran"].add((element.get("filename", ""), int(element.get("line", "0")))) + element.clear() + elif element.tag == "OverallResult": + if element.get("success") != "true": + case["result"] = "fail" + elif int(element.get("skips", "0")) > 0: + case["result"] = "skip" + else: + case["result"] = "ok" + elif element.tag == "TestCase": + cases.append(case) + case = None + element.clear() except (ElementTree.ParseError, FileNotFoundError): return code, None, text - for case in root.iter("TestCase"): - result = case.find("OverallResult") - if result is None or result.get("success") != "true": - outcome = "fail" - elif int(result.get("skips", "0")) > 0: - outcome = "skip" - else: - outcome = "ok" - cases.append({"name": case.get("name"), "tags": case.get("tags", ""), "result": outcome}) return code, cases, text @@ -206,16 +236,54 @@ def exit_fits(code, cases, output): return code == 0 or (code == 1 and "The state guard found" in output) -def recount(path, cases): - """The Catch2 assertions in each named case's body, from the file itself.""" +_CONVERTER = None + + +def converter(): + """tut2catch.py, beside this script: the file is read with the masking + and the #if 0 regions the mapping's counts were made with. By path, as + python -I puts no script's directory on sys.path.""" + global _CONVERTER + if _CONVERTER is None: + path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tut2catch.py") + spec = importlib.util.spec_from_file_location("tut2catch", path) + _CONVERTER = importlib.util.module_from_spec(spec) + spec.loader.exec_module(_CONVERTER) + return _CONVERTER + + +def read_cases(path): + """The converted file's test cases and assertion sites, read from the file + as tut2catch.py reads it: comments and literals masked, #if 0 branches + left out. A site is (first line, last line, macro), the lines its call + spans; returns ([{name, lines, sites}], the sites outside every case).""" + tc = converter() with open(path, "rb") as f: - text = f.read().decode("utf-8", "surrogateescape") - masked = mask(text) - counts = {} - for m in re.finditer(r'\bTEST_CASE(_METHOD)?\s*\(', masked): + data = f.read() + masked = tc.mask_source(data) + dead = tc.dead_regions(masked) + newlines = [m.start() for m in re.finditer(rb"\n", masked)] + + def live(at): + return not any(start <= at < end for start, end in dead) + + def line(at): + return bisect.bisect_left(newlines, at) + 1 + + sites = [] + for m in tc.CATCH_ASSERTION.finditer(masked): + if live(m.start()): + close = matching(masked, m.end() - 1) + macro = m.group(0).rstrip(b"( \t\r\n").decode() + sites.append((m.start(), (line(m.start()), line(close), macro))) + cases = [] + inside = set() + for m in re.finditer(rb"\bTEST_CASE(_METHOD)?\s*\(", masked): + if not live(m.start()): + continue open_paren = m.end() - 1 close = matching(masked, open_paren) - header = text[open_paren + 1:close] + header = data[open_paren + 1:close].decode("utf-8", "surrogateescape") # The name is the first argument of a TEST_CASE, the second of a # TEST_CASE_METHOD. literal = re.search(r',\s*((?:"(?:[^"\\]|\\.)*"\s*)+),' if m.group(1) else @@ -223,52 +291,41 @@ def recount(path, cases): if not literal: continue name = "".join(unescape(p) for p in re.findall(r'"((?:[^"\\]|\\.)*)"', literal.group(1))) - brace = masked.find("{", close) + brace = masked.find(b"{", close) end = matching(masked, brace) - counts[name] = len(CATCH_ASSERTION.findall(masked, brace, end)) - return {name: counts.get(name) for name in cases} - - -def mask(text): - """Comments blanked and literals' insides replaced, offsets kept.""" - out = list(text) - i, n = 0, len(text) - while i < n: - if text.startswith("//", i): - j = text.find("\n", i) - j = n if j < 0 else j - out[i:j] = " " * (j - i) - i = j - elif text.startswith("/*", i): - j = text.find("*/", i + 2) - j = n if j < 0 else j + 2 - out[i:j] = [c if c == "\n" else " " for c in text[i:j]] - i = j - elif text[i] in "\"'": - if text[i] == "'" and i > 0 and text[i - 1].isalnum() and re.match(r"\d", text[i - 1]): - i += 1 - continue - q = text[i] - j = i + 1 - while j < n and text[j] != q and text[j] != "\n": - j += 2 if text[j] == "\\" else 1 - out[i + 1:j] = "_" * (j - i - 1) - i = j + 1 - else: - i += 1 - return "".join(out) + body = [(at, site) for at, site in sites if brace < at < end] + inside.update(at for at, _ in body) + cases.append({"name": name, "lines": (line(m.start()), line(brace)), "sites": [site for _, site in body]}) + return cases, [site for at, site in sites if at not in inside] -def matching(text, at): +def matching(masked, at): + """Where the bracket at at closes, in masked text.""" depth = 0 - for i in range(at, len(text)): - if text[i] in "({[": + for i in range(at, len(masked)): + c = masked[i] + if c in b"({[": depth += 1 - elif text[i] in ")}]": + elif c in b")}]": depth -= 1 if depth == 0: return i - return len(text) + return len(masked) + + +def ran_here(case, path): + """The lines of path a case's run reported an assertion from.""" + suffix = "/" + path.replace("\\", "/") + return {line for file, line in case["ran"] if ("/" + file.replace("\\", "/")).endswith(suffix)} + + +def body_of(case, file_cases): + """The file's case a run's case is: by name, and by the line Catch2 says + it starts on where two have one name.""" + named = [c for c in file_cases if c["name"] == case["name"]] + if len(named) > 1: + named = [c for c in named if c["lines"][0] <= case["line"] <= c["lines"][1]] + return named[0] if len(named) == 1 else None def unescape(body): @@ -347,7 +404,26 @@ def check(args): problems.append(f"{path}: {entry_names[0]} exited {code}, which does not fit its cases (" + (", ".join(f"{v} {k}" for k, v in sorted(counts.items())) or "none") + ")") tut_results = {(r["group"], r["n"]): r for r in recorded["results"]} - counted = recount(os.path.join(ROOT, path), [e["catch2_name"] for e in entries if e["catch2_name"]]) + file_cases, outside = read_cases(os.path.join(ROOT, path)) + counted = {} + for c in file_cases: + counted.setdefault(c["name"], len(c["sites"])) + # An assertion counted is not one that ran: a case passes as well + # when a loop around its CHECK never turns. Each site in a passing + # case's body must have reported at least once. + ran_anywhere = set() + for case in cases: + ran = ran_here(case, path) + ran_anywhere |= ran + if case["result"] != "ok": + continue + body = body_of(case, file_cases) + for first, last, macro in body["sites"] if body else []: + if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran): + problems.append(f"{path}: {case['name']!r} passed without running the {macro} on line {first}") + never = [f"the {macro} on line {first}, outside every case, never ran" + for first, last, macro in outside + if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran_anywhere)] matched = agree = counts_ok = 0 listed = [] seen_cases = set() @@ -412,7 +488,7 @@ def check(args): clean = not any(p.startswith(path + ":") for p in problems) rows.append((path, len(recorded["results"]), len(cases), matched, agree, counts_ok, len(listed), code, "ok" if clean else "MISMATCH")) - for line in listed + guard: + for line in listed + never + guard: print(f"{path}: listed: {line}") header = ("file", "TUT", "Catch2", "matched", "agree", "counts", "listed", "exit", "parity") From 7405bf9ec7fc654ec49dd66c803afb3fbb16f79a Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:52:49 -0400 Subject: [PATCH 29/55] tut_parity.py --recounted still fails a case left with no assertion --recounted lets a file edited by hand after converting have other assertion counts than the mapping's, and took any count at all: a case whose every assertion was deleted, or compiled out, listed "1 assertions converted, 0 in the file now" and passed, an empty test standing in for one that checked something. A case whose TUT test made any assertion call and whose body now has none is a problem under --recounted too; other counts are still only listed. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 6c2a031d47..3b50f95ac0 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -34,7 +34,7 @@ counts one a term (catch2_expected). After a file has been edited by hand past what the converter wrote, --recounted lists the counts instead of holding them to the mapping; names and results must still - agree; + agree, and a case whose TUT test asserted anything must still assert; - every assertion counted in a passing case's body ran: the run keeps its passing assertions (-s), and each CHECK, REQUIRE and _THAT site must report from at least one line it spans. A FAIL or SKIP that ran would @@ -463,6 +463,9 @@ def check(args): expected = e.get("catch2_expected", e["tut_assertions"]) if expected == e["catch2_assertions"] == static: counts_ok += 1 + elif args.recounted and static == 0 and e["tut_assertions"] > 0: + problems.append(f"{path}: {name!r}: {e['tut_assertions']} TUT assertion calls, " + f"none in the file") elif args.recounted and static is not None: counts_ok += 1 print(f"{path}: listed: {name!r}: {expected} assertions converted, {static} in the file now") From 945ef287f793a79c85182d40443c2421803dc6df Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:54:17 -0400 Subject: [PATCH 30/55] tut_parity.py holds the assertions outside every case to the mapping's file totals Each case's count is held to its TUT test's, but an assertion outside every test body -- a helper's, a fixture's -- was in no test's count, so a conversion that dropped one, or emptied a helper, held. tut2catch.py's mapping becomes an object, {"tests": [...], "files": [...]}, with a record a file: tut_outside_calls, TUT's assertion calls outside every test body, and catch2_outside_expected, the Catch2 assertions those became. The assertions outside every case in the converted file, read as for the cases, must number the latter; under --recounted they are listed, but none where TUT had some is still a problem. The bare list the mapping was at first is still read: it has no file records, which is listed for each file, not failed. What is listed for a file is now kept apart from the tests listed, which the table's column still counts. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 38 +++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 3b50f95ac0..7aac858c6b 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -35,6 +35,11 @@ hand past what the converter wrote, --recounted lists the counts instead of holding them to the mapping; names and results must still agree, and a case whose TUT test asserted anything must still assert; + - the assertions outside every case (helpers, fixtures) number what the + mapping's record of the file says those outside every TUT test became + (catch2_outside_expected); under --recounted, listed, but none where + TUT had some is still a problem. A mapping in its first form, a bare + list of tests, has no file records: that is listed; - every assertion counted in a passing case's body ran: the run keeps its passing assertions (-s), and each CHECK, REQUIRE and _THAT site must report from at least one line it spans. A FAIL or SKIP that ran would @@ -356,9 +361,18 @@ def unescape(body): return "".join(out) -def check(args): - with open(args.mapping) as f: +def read_mapping(path): + """tut2catch.py's mapping: its tests, and its records of the files by path + -- None for a mapping in the first form it took, a bare list of tests.""" + with open(path) as f: mapping = json.load(f) + if isinstance(mapping, list): + return mapping, None + return mapping["tests"], {record["file"]: record for record in mapping.get("files", [])} + + +def check(args): + mapping, file_records = read_mapping(args.mapping) with open(args.tut) as f: tut = json.load(f) tests = ctest_tests(args.build_dir, args.config) @@ -421,11 +435,27 @@ def check(args): for first, last, macro in body["sites"] if body else []: if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran): problems.append(f"{path}: {case['name']!r} passed without running the {macro} on line {first}") - never = [f"the {macro} on line {first}, outside every case, never ran" + # Listed for the file, apart from the tests listed. + notes = [f"the {macro} on line {first}, outside every case, never ran" for first, last, macro in outside if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran_anywhere)] matched = agree = counts_ok = 0 listed = [] + # What no test's count holds: the assertions outside every test body + # (helpers, fixtures), which the mapping totals for the file. + record = (file_records or {}).get(path) + if file_records is None: + notes.append("no file totals in the mapping") + elif record is None: + problems.append(f"{path}: the mapping has no record of the file") + elif len(outside) != record["catch2_outside_expected"]: + if args.recounted and (outside or not record["tut_outside_calls"]): + notes.append(f"{record['catch2_outside_expected']} assertions outside every case converted, " + f"{len(outside)} in the file now") + else: + problems.append(f"{path}: {record['tut_outside_calls']} TUT assertion calls outside every test, " + f"{record['catch2_outside_expected']} Catch2 assertions they became, " + f"{len(outside)} outside every case in the file") seen_cases = set() paired = {} for e in entries: @@ -491,7 +521,7 @@ def check(args): clean = not any(p.startswith(path + ":") for p in problems) rows.append((path, len(recorded["results"]), len(cases), matched, agree, counts_ok, len(listed), code, "ok" if clean else "MISMATCH")) - for line in listed + never + guard: + for line in listed + notes + guard: print(f"{path}: listed: {line}") header = ("file", "TUT", "Catch2", "matched", "agree", "counts", "listed", "exit", "parity") From faf945190183b93b20e5b42bec8c35474e497472 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:56:24 -0400 Subject: [PATCH 31/55] tut_parity.py refuses inputs that are not of the run it checks check read the converted file from the tree and ran whatever binary was built, and never asked whether one was built from the other: a file edited after its build (a CHECK changed to expect 7) was counted from the edit and run as built, and held. Nor could it tell that the TUT results it was given were recorded from the source the mapping was made from. A converted file newer than the executable its CTest entry runs is now a problem, and the file is not run: build it again. record keeps, with each TUT entry's results, the sha256 of its source, the one indra/**/tests/.cpp git tracks under the tree (none, with a note, where there is not exactly one), and check holds it to the sha256 the mapping's record of the file now carries; where either is missing that is listed. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 51 +++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 7aac858c6b..f617fdf452 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -8,12 +8,18 @@ entries give, and keeps every line a test's end prints: [group, N: name] ok | fail | exception: ... | skipped known failure | ... A test TUT never reported (a dummy test slot) is not a test. + With them it keeps the sha256 of the TUT source, the one + indra/**/tests/.cpp git tracks. check after converting and building: runs each converted file's CTest entry, /, as XML, and matches the cases to the TUT results through tut2catch.py's mapping. tut2catch.py must be beside this script: the converted file is read as it reads one. Parity holds for a file when: + - its inputs are the ones the run is of: the converted file is no newer + than the executable its entry runs, and where the recorded results + and the mapping both have the TUT source's sha256, the two agree (where + either has none, that is listed); - every TUT test it reported has exactly one Catch2 case, and every case of the file is one of its TUT tests; - each pair agrees: ok is a pass, a TUT skip is a Catch2 skip, and every @@ -62,6 +68,7 @@ """ import argparse import bisect +import hashlib import importlib.util import json import os @@ -150,6 +157,22 @@ def stem_of_tut(name): return re.sub(r"_(?:baseline|v2|v3|v4)$", "", m.group(1)) + "_test" +def tut_source(stem): + """The TUT source a stem names, as git tracks it under ROOT, and the + sha256 of its bytes: (None, None) unless exactly one + indra/**/tests/.cpp is tracked.""" + try: + listing = subprocess.run(["git", "-C", ROOT, "ls-files", "-z", "--", f":(glob)indra/**/tests/{stem}.cpp"], + capture_output=True, check=True).stdout + except (OSError, subprocess.CalledProcessError): + return None, None + paths = [p.decode("utf-8", "surrogateescape") for p in listing.split(b"\0") if p] + if len(paths) != 1: + return None, None + with open(os.path.join(ROOT, paths[0]), "rb") as f: + return paths[0], hashlib.sha256(f.read()).hexdigest() + + # -------------------------------------------------------------------------- # record # -------------------------------------------------------------------------- @@ -174,7 +197,14 @@ def record(args): results.append({"group": m.group("group"), "n": int(m.group("n")), "name": m.group("name") or "", "result": {"ok": "ok", "skipped known failure": "skip"}.get(result, "fail"), "said": line[m.start("result"):].strip()}) - out[stem_of_tut(name)] = {"ctest": name, "exit": code, "results": results} + stem = stem_of_tut(name) + # What the results are of, so that check can tell they are of the + # source the mapping was made from. + source, digest = tut_source(stem) + if source is None: + print(f"{name}: not exactly one tracked indra/**/tests/{stem}.cpp, so no source hash kept", + file=sys.stderr) + out[stem] = {"ctest": name, "exit": code, "tut_file": source, "tut_sha256": digest, "results": results} counts = {} for r in results: counts[r["result"]] = counts.get(r["result"], 0) + 1 @@ -394,6 +424,13 @@ def check(args): if len(entry_names) != 1: problems.append(f"{path}: {len(entry_names)} Catch2 CTest entries end in /{stem}") continue + # A converted file changed since its binary was built is not what + # the binary runs: its cases, lines and counts would not be the run's. + command = tests[entry_names[0]]["command"] + if command and os.path.isfile(command[0]) and \ + os.path.getmtime(os.path.join(ROOT, path)) > os.path.getmtime(command[0]): + problems.append(f"{path}: changed since {command[0]} was built; build it again to check it") + continue code, cases, output = catch2_results(tests[entry_names[0]], args.timeout) if cases is None: problems.append(f"{path}: {entry_names[0]} gave no XML (exit {code}):\n{output[-2000:]}") @@ -441,9 +478,19 @@ def check(args): if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran_anywhere)] matched = agree = counts_ok = 0 listed = [] + record = (file_records or {}).get(path) + # The TUT results must be of the source the mapping was made from. + hashes = {"the recorded results": recorded.get("tut_sha256"), "the mapping": (record or {}).get("tut_sha256")} + if all(hashes.values()): + if len(set(hashes.values())) > 1: + problems.append(f"{path}: TUT's results were recorded from another {stem}.cpp than the one " + f"converted (sha256 {hashes['the recorded results'][:12]}, the mapping's " + f"{hashes['the mapping'][:12]})") + else: + notes.append(f"no TUT source hash in {' or '.join(k for k, v in hashes.items() if not v)}, " + f"so the two are not known to be of one source") # What no test's count holds: the assertions outside every test body # (helpers, fixtures), which the mapping totals for the file. - record = (file_records or {}).get(path) if file_records is None: notes.append("no file totals in the mapping") elif record is None: From cabb20b03a9b791e33b2d88d76275c4ff43efff3 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:02:05 -0400 Subject: [PATCH 32/55] tut_parity.py checks a file built at several ISA tiers a tier at a time alsimdkernels_test runs at the tree's tier and, on x86-64 Windows and Linux, again at each other: TUT as INTEGRATION_TEST_RUNNER_alsimdkernels and _baseline, _v2, _v3, _v4; Catch2 as llmath_tests/alsimdkernels_test and simd_tests_/alsimdkernels_test. record folded the tier entries into one key by stem, the last one winning, and a tier the host cannot run exits 125 with no results, so the v4 run wiped out the real ones; check wanted exactly one */ Catch2 entry and found four, so the file could never be checked on those hosts. record now keeps a tier's run under @, with its tier, and check reads the tier of every record from its CTest name, so results recorded the old way say which tier they are of rather than standing for the untiered run. Each Catch2 entry is checked against TUT's run at its binary's tier, and the tree's own binary against TUT's untiered run, or its run at the tree's tier (AL_ISA_TIER) where there is no untiered one. A tier both sides skipped (125, the host below it) is listed, one side only skipping is a problem, and so is a TUT run at a tier no Catch2 entry runs -- but for the tree's own tier, which the untiered pair covers. The table has a row a tier. Shown with a fake x86-64 build directory, since this host runs no x86 tier: four Catch2 entries over the arm64 llmath_tests, the v4 one with --isa-level 4, which skips here as on a host without AVX-512. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 399 +++++++++++++++++++++------------- 1 file changed, 244 insertions(+), 155 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index f617fdf452..846defa886 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -9,11 +9,18 @@ [group, N: name] ok | fail | exception: ... | skipped known failure | ... A test TUT never reported (a dummy test slot) is not a test. With them it keeps the sha256 of the TUT source, the one - indra/**/tests/.cpp git tracks. + indra/**/tests/.cpp git tracks. A test built at other + x86-64 tiers too (an entry ending _baseline, _v2, _v3, _v4) is + kept a tier apart, under @. check after converting and building: runs each converted file's CTest entry, /, as XML, and matches the cases to the TUT results through tut2catch.py's mapping. tut2catch.py must be beside this script: the converted file is read as it reads one. + A file built at other tiers too has an entry + _/ at each, held to TUT's run at that + tier; the tree's own binary is held to TUT's untiered run (or, + where there is none, its run at the tree's tier, AL_ISA_TIER). A + tier the host does not run skips on both sides, which is listed. Parity holds for a file when: - its inputs are the ones the run is of: the converted file is no newer @@ -54,11 +61,12 @@ A test the mapping says TUT never ran (past its group's limit) or this host does not compile is listed, not counted against parity, as is an assertion -outside every case (a helper's) that no case ran. So is what the -Catch2 harness's state guard says a case left changed: TUT never looked, so -it is a finding about the test, not a difference the conversion made; the +outside every case (a helper's) that no case ran. So is what the Catch2 +harness's state guard says a case left changed: TUT never looked, so it is +a finding about the test, not a difference the conversion made; the table's exit column shows the run's exit code. It prints a table, one row a -file, and exits 1 on any mismatch, listing each. +file (and one a tier it is built at besides), and exits 1 on any mismatch, +listing each. python3 -I scripts/testing/tut_parity.py record --out /tmp/tut.json \\ INTEGRATION_TEST_RUNNER_v3math PROJECT_llmath_TEST_v3color @@ -84,6 +92,8 @@ # What every test entry exits with when it skips: the harness turns Catch2's # "all skipped" into it, and CTest reads it as skipped. SKIP_CODE = 125 +# The x86-64 tiers a test is built for beside the tree's own, lowest first. +TIERS = ("baseline", "v2", "v3", "v4") # Tags under which Catch2 passes a case whatever it does. FORGIVING_TAGS = ("[!mayfail]", "[!shouldfail]") RESULT_LINE = re.compile( @@ -149,12 +159,21 @@ def run(test, extra, timeout): return "timeout", (e.stdout or b"").decode("utf-8", "replace") -def stem_of_tut(name): - """The test file a TUT CTest entry runs, by stem.""" +def isa_tier(name): + """The x86-64 tier a TUT entry's or a Catch2 binary's name ends in + (_baseline, _v2, _v3, _v4), it being built for that tier; None for one + built at the tree's own.""" + m = re.search(r"_(" + "|".join(TIERS) + r")$", name) + return m.group(1) if m else None + + +def tut_identity(name): + """The test file a TUT CTest entry runs, by stem, and its ISA tier.""" m = re.search(r"TEST_RUNNER_(\w+)$", name) or re.search(r"_TEST_(\w+)$", name) if not m: - return name - return re.sub(r"_(?:baseline|v2|v3|v4)$", "", m.group(1)) + "_test" + return name, None + tier = isa_tier(m.group(1)) + return (m.group(1)[:-len(tier) - 1] if tier else m.group(1)) + "_test", tier def tut_source(stem): @@ -197,18 +216,21 @@ def record(args): results.append({"group": m.group("group"), "n": int(m.group("n")), "name": m.group("name") or "", "result": {"ok": "ok", "skipped known failure": "skip"}.get(result, "fail"), "said": line[m.start("result"):].strip()}) - stem = stem_of_tut(name) + stem, tier = tut_identity(name) # What the results are of, so that check can tell they are of the # source the mapping was made from. source, digest = tut_source(stem) if source is None: print(f"{name}: not exactly one tracked indra/**/tests/{stem}.cpp, so no source hash kept", file=sys.stderr) - out[stem] = {"ctest": name, "exit": code, "tut_file": source, "tut_sha256": digest, "results": results} + # Each tier's run is kept apart: one the host cannot run exits 125 + # with no results, which must not stand for the others. + out[f"{stem}@{tier}" if tier else stem] = {"ctest": name, "tier": tier, "exit": code, "tut_file": source, + "tut_sha256": digest, "results": results} counts = {} for r in results: counts[r["result"]] = counts.get(r["result"], 0) + 1 - print(f"{name}: exit {code}, " + ", ".join(f"{v} {k}" for k, v in sorted(counts.items()))) + print(f"{name}: exit {code}, " + (", ".join(f"{v} {k}" for k, v in sorted(counts.items())) or "no results")) if args.out: with open(args.out, "w") as f: json.dump(out, f, indent=1) @@ -401,10 +423,172 @@ def read_mapping(path): return mapping["tests"], {record["file"]: record for record in mapping.get("files", [])} +def tree_tier(build_dir): + """The ISA tier the build tree is built for: AL_ISA_TIER in its cache.""" + try: + with open(os.path.join(build_dir, "CMakeCache.txt"), errors="replace") as f: + m = re.search(r"^AL_ISA_TIER:\w+=(\S+)$", f.read(), re.M) + except OSError: + return None + return m.group(1) if m else None + + +def check_run(args, label, path, entry_name, test, recorded, entries, file_cases, outside, record, problems): + """One Catch2 entry's run of a converted file against TUT's run of its + original: appends what does not hold to problems, prints what is listed, + and returns the table's row, or None where there is no run to show.""" + first_problem = len(problems) + # A converted file changed since its binary was built is not what the + # binary runs: its cases, lines and counts would not be the run's. + command = test["command"] + if command and os.path.isfile(command[0]) and \ + os.path.getmtime(os.path.join(ROOT, path)) > os.path.getmtime(command[0]): + problems.append(f"{label}: changed since {command[0]} was built; build it again to check it") + return None + code, cases, output = catch2_results(test, args.timeout) + # Where the host cannot run a tier, either side exits with the skip code + # before running anything: TUT with no results, Catch2 with no XML. + tut_skipped = recorded["exit"] == SKIP_CODE and not recorded["results"] + catch2_skipped = code == SKIP_CODE and cases is None + if tut_skipped and catch2_skipped: + print(f"{label}: listed: TUT and Catch2 both skipped it (exit {SKIP_CODE}): the host does not run its tier") + return label, 0, 0, 0, 0, 0, 0, code, "skipped" + if tut_skipped or catch2_skipped: + problems.append(f"{label}: TUT {'skipped' if tut_skipped else 'ran'} it (exit {recorded['exit']}), " + f"{entry_name} {'skipped' if catch2_skipped else 'ran'} it (exit {code})") + return None + if cases is None: + problems.append(f"{label}: {entry_name} gave no XML (exit {code}):\n{output[-2000:]}") + return None + guard = [line for line in output.splitlines() if line.startswith("State guard:")] + # The first case of a name is the one matched; a second is an error of + # its own. + by_name = {} + for case in cases: + if case["name"] in by_name: + problems.append(f"{label}: Catch2 has more than one case named {case['name']!r}") + else: + by_name[case["name"]] = case + for tag in FORGIVING_TAGS: + if tag in case["tags"]: + problems.append(f"{label}: {case['name']!r} is tagged {tag}, under which Catch2 passes it " + f"whatever it does") + if not exit_fits(code, cases, output): + counts = {} + for case in cases: + counts[case["result"]] = counts.get(case["result"], 0) + 1 + problems.append(f"{label}: {entry_name} exited {code}, which does not fit its cases (" + + (", ".join(f"{v} {k}" for k, v in sorted(counts.items())) or "none") + ")") + tut_results = {(r["group"], r["n"]): r for r in recorded["results"]} + counted = {} + for c in file_cases: + counted.setdefault(c["name"], len(c["sites"])) + # An assertion counted is not one that ran: a case passes as well when a + # loop around its CHECK never turns. Each site in a passing case's body + # must have reported at least once. + ran_anywhere = set() + for case in cases: + ran = ran_here(case, path) + ran_anywhere |= ran + if case["result"] != "ok": + continue + body = body_of(case, file_cases) + for first, last, macro in body["sites"] if body else []: + if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran): + problems.append(f"{label}: {case['name']!r} passed without running the {macro} on line {first}") + # Listed for the run, apart from the tests listed. + notes = [f"the {macro} on line {first}, outside every case, never ran" + for first, last, macro in outside + if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran_anywhere)] + # The TUT results must be of the source the mapping was made from. + hashes = {"the recorded results": recorded.get("tut_sha256"), "the mapping": (record or {}).get("tut_sha256")} + if all(hashes.values()): + if len(set(hashes.values())) > 1: + problems.append(f"{label}: TUT's results were recorded from another {os.path.basename(path)} than the " + f"one converted (sha256 {hashes['the recorded results'][:12]}, the mapping's " + f"{hashes['the mapping'][:12]})") + else: + notes.append(f"no TUT source hash in {' or '.join(k for k, v in hashes.items() if not v)}, " + f"so the two are not known to be of one source") + matched = agree = counts_ok = 0 + listed = [] + seen_cases = set() + paired = {} + for e in entries: + key = (e["group"], e["n"]) + name = e["catch2_name"] + # Before taking TUT's result: a test defined in both branches of + # an #if is two entries, and the result is the active twin's. + if e.get("not_compiled_here") or (e.get("past_group_limit") and key not in tut_results): + listed.append(f"{e['group']} {e['n']}: " + + ("not compiled here" if e.get("not_compiled_here") else "past its group's limit")) + if name in by_name: + seen_cases.add(name) + continue + t = tut_results.pop(key, None) + if t is None: + problems.append(f"{label}: {e['group']} test<{e['n']}> did not run under TUT") + continue + if not name or name not in by_name: + problems.append(f"{label}: {e['group']} test<{e['n']}> has no Catch2 case" + + (f" ({name!r})" if name else "")) + continue + seen_cases.add(name) + paired[name] = key + matched += 1 + if e.get("tut_name") is not None and e["tut_name"] != t["name"]: + problems.append(f"{label}: {e['group']} test<{e['n']}>: TUT named it {t['name']!r}, " + f"the mapping {e['tut_name']!r}") + if by_name[name]["result"] == t["result"]: + agree += 1 + else: + problems.append(f"{label}: {name!r}: TUT {t['said']!r}, Catch2 {by_name[name]['result']}") + static = counted.get(name) + # One TUT call is one Catch2 assertion, but for a && chain split into + # one assertion a term, which the mapping counted. + expected = e.get("catch2_expected", e["tut_assertions"]) + if expected == e["catch2_assertions"] == static: + counts_ok += 1 + elif args.recounted and static == 0 and e["tut_assertions"] > 0: + problems.append(f"{label}: {name!r}: {e['tut_assertions']} TUT assertion calls, none in the file") + elif args.recounted and static is not None: + counts_ok += 1 + print(f"{label}: listed: {name!r}: {expected} assertions converted, {static} in the file now") + else: + problems.append(f"{label}: {name!r}: {e['tut_assertions']} TUT assertion calls, " + f"{expected} Catch2 assertions they became, {e['catch2_assertions']} mapped, " + f"{static} in the file") + for key, t in sorted(tut_results.items()): + problems.append(f"{label}: TUT ran [{key[0]}, {key[1]}], which the mapping does not have") + for name in sorted(set(by_name) - seen_cases): + problems.append(f"{label}: Catch2 case {name!r} is no TUT test of the mapping") + # TUT ran a file's groups by name and each group's tests by number, and a + # test may lean on what the ones before it left. + keys = set(paired.values()) + tut_order = [(r["group"], r["n"]) for r in recorded["results"] if (r["group"], r["n"]) in keys] + catch2_order = [(paired[c["name"]], c["name"]) for c in cases if c["name"] in paired and by_name[c["name"]] is c] + for at, (tut_key, (key, name)) in enumerate(zip(tut_order, catch2_order)): + if tut_key != key: + problems.append(f"{label}: Catch2 runs {name!r}, [{key[0]}, {key[1]}], as test {at + 1} of " + f"{len(tut_order)}, where TUT ran [{tut_key[0]}, {tut_key[1]}]: not TUT's order") + break + for line in listed + notes + guard: + print(f"{label}: listed: {line}") + return (label, len(recorded["results"]), len(cases), matched, agree, counts_ok, len(listed), code, + "ok" if len(problems) == first_problem else "MISMATCH") + + def check(args): mapping, file_records = read_mapping(args.mapping) with open(args.tut) as f: tut = json.load(f) + # By stem and tier, the tier read from each record's CTest name: results + # recorded before they were kept by tier, the last tier's under the stem, + # still say which tier they are of. + records = {} + for key, recorded in tut.items(): + records[tut_identity(recorded["ctest"]) if recorded.get("ctest") else (key, None)] = recorded + own_tier = tree_tier(args.build_dir) tests = ctest_tests(args.build_dir, args.config) by_file = {} for entry in mapping: @@ -416,160 +600,65 @@ def check(args): for path in files: entries = by_file.get(path, []) stem = os.path.splitext(os.path.basename(path))[0] - recorded = tut.get(stem) - if recorded is None: - problems.append(f"{path}: no TUT results recorded for {stem}") - continue - entry_names = [n for n, t in tests.items() if n.endswith("/" + stem)] - if len(entry_names) != 1: - problems.append(f"{path}: {len(entry_names)} Catch2 CTest entries end in /{stem}") - continue - # A converted file changed since its binary was built is not what - # the binary runs: its cases, lines and counts would not be the run's. - command = tests[entry_names[0]]["command"] - if command and os.path.isfile(command[0]) and \ - os.path.getmtime(os.path.join(ROOT, path)) > os.path.getmtime(command[0]): - problems.append(f"{path}: changed since {command[0]} was built; build it again to check it") - continue - code, cases, output = catch2_results(tests[entry_names[0]], args.timeout) - if cases is None: - problems.append(f"{path}: {entry_names[0]} gave no XML (exit {code}):\n{output[-2000:]}") - continue - guard = [line for line in output.splitlines() if line.startswith("State guard:")] - # The first case of a name is the one matched; a second is an error - # of its own. - by_name = {} - for case in cases: - if case["name"] in by_name: - problems.append(f"{path}: Catch2 has more than one case named {case['name']!r}") - else: - by_name[case["name"]] = case - for tag in FORGIVING_TAGS: - if tag in case["tags"]: - problems.append(f"{path}: {case['name']!r} is tagged {tag}, under which Catch2 passes it " - f"whatever it does") - if not exit_fits(code, cases, output): - counts = {} - for case in cases: - counts[case["result"]] = counts.get(case["result"], 0) + 1 - problems.append(f"{path}: {entry_names[0]} exited {code}, which does not fit its cases (" - + (", ".join(f"{v} {k}" for k, v in sorted(counts.items())) or "none") + ")") - tut_results = {(r["group"], r["n"]): r for r in recorded["results"]} + file_problems = [] file_cases, outside = read_cases(os.path.join(ROOT, path)) - counted = {} - for c in file_cases: - counted.setdefault(c["name"], len(c["sites"])) - # An assertion counted is not one that ran: a case passes as well - # when a loop around its CHECK never turns. Each site in a passing - # case's body must have reported at least once. - ran_anywhere = set() - for case in cases: - ran = ran_here(case, path) - ran_anywhere |= ran - if case["result"] != "ok": - continue - body = body_of(case, file_cases) - for first, last, macro in body["sites"] if body else []: - if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran): - problems.append(f"{path}: {case['name']!r} passed without running the {macro} on line {first}") - # Listed for the file, apart from the tests listed. - notes = [f"the {macro} on line {first}, outside every case, never ran" - for first, last, macro in outside - if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran_anywhere)] - matched = agree = counts_ok = 0 - listed = [] record = (file_records or {}).get(path) - # The TUT results must be of the source the mapping was made from. - hashes = {"the recorded results": recorded.get("tut_sha256"), "the mapping": (record or {}).get("tut_sha256")} - if all(hashes.values()): - if len(set(hashes.values())) > 1: - problems.append(f"{path}: TUT's results were recorded from another {stem}.cpp than the one " - f"converted (sha256 {hashes['the recorded results'][:12]}, the mapping's " - f"{hashes['the mapping'][:12]})") - else: - notes.append(f"no TUT source hash in {' or '.join(k for k, v in hashes.items() if not v)}, " - f"so the two are not known to be of one source") # What no test's count holds: the assertions outside every test body # (helpers, fixtures), which the mapping totals for the file. if file_records is None: - notes.append("no file totals in the mapping") + print(f"{path}: listed: no file totals in the mapping") elif record is None: - problems.append(f"{path}: the mapping has no record of the file") + file_problems.append(f"{path}: the mapping has no record of the file") elif len(outside) != record["catch2_outside_expected"]: if args.recounted and (outside or not record["tut_outside_calls"]): - notes.append(f"{record['catch2_outside_expected']} assertions outside every case converted, " - f"{len(outside)} in the file now") + print(f"{path}: listed: {record['catch2_outside_expected']} assertions outside every case " + f"converted, {len(outside)} in the file now") else: - problems.append(f"{path}: {record['tut_outside_calls']} TUT assertion calls outside every test, " - f"{record['catch2_outside_expected']} Catch2 assertions they became, " - f"{len(outside)} outside every case in the file") - seen_cases = set() - paired = {} - for e in entries: - key = (e["group"], e["n"]) - name = e["catch2_name"] - # Before taking TUT's result: a test defined in both branches - # of an #if is two entries, and the result is the active twin's. - if e.get("not_compiled_here") or (e.get("past_group_limit") and key not in tut_results): - listed.append(f"{e['group']} {e['n']}: " + - ("not compiled here" if e.get("not_compiled_here") else "past its group's limit")) - if name in by_name: - seen_cases.add(name) - continue - t = tut_results.pop(key, None) - if t is None: - problems.append(f"{path}: {e['group']} test<{e['n']}> did not run under TUT") + file_problems.append(f"{path}: {record['tut_outside_calls']} TUT assertion calls outside every " + f"test, {record['catch2_outside_expected']} Catch2 assertions they became, " + f"{len(outside)} outside every case in the file") + # The file's Catch2 entries, /, by the tier the binary + # is built for: one at the tree's own, checked against TUT's + # untiered entry (or, where there is none, its entry at the tree's + # tier), and at most one, _, at each other tier, + # checked against TUT's entry at that tier. + runs = {} + for name in tests: + if name.endswith("/" + stem): + runs.setdefault(isa_tier(name[:-len(stem) - 1]), []).append(name) + if len(runs.get(None, [])) != 1: + file_problems.append(f"{path}: {len(runs.get(None, []))} Catch2 CTest entries end in /{stem}") + used = set() + file_rows = [] + for tier in sorted(runs, key=lambda t: TIERS.index(t) + 1 if t else 0): + label = f"{path} ({tier})" if tier else path + if len(runs[tier]) != 1: + if tier: + file_problems.append(f"{label}: {len(runs[tier])} Catch2 CTest entries end in _{tier}/{stem}") continue - if not name or name not in by_name: - problems.append(f"{path}: {e['group']} test<{e['n']}> has no Catch2 case" - + (f" ({name!r})" if name else "")) + key = (stem, tier) + if tier is None and key not in records and (stem, own_tier) in records: + key = (stem, own_tier) + if key not in records: + file_problems.append(f"{label}: no TUT results recorded for {stem}" + (f" at {tier}" if tier else "")) continue - seen_cases.add(name) - paired[name] = key - matched += 1 - if e.get("tut_name") is not None and e["tut_name"] != t["name"]: - problems.append(f"{path}: {e['group']} test<{e['n']}>: TUT named it {t['name']!r}, " - f"the mapping {e['tut_name']!r}") - if by_name[name]["result"] == t["result"]: - agree += 1 - else: - problems.append(f"{path}: {name!r}: TUT {t['said']!r}, Catch2 {by_name[name]['result']}") - static = counted.get(name) - # One TUT call is one Catch2 assertion, but for a && chain split - # into one assertion a term, which the mapping counted. - expected = e.get("catch2_expected", e["tut_assertions"]) - if expected == e["catch2_assertions"] == static: - counts_ok += 1 - elif args.recounted and static == 0 and e["tut_assertions"] > 0: - problems.append(f"{path}: {name!r}: {e['tut_assertions']} TUT assertion calls, " - f"none in the file") - elif args.recounted and static is not None: - counts_ok += 1 - print(f"{path}: listed: {name!r}: {expected} assertions converted, {static} in the file now") + used.add(key) + row = check_run(args, label, path, runs[tier][0], tests[runs[tier][0]], records[key], entries, + file_cases, outside, record, problems) + if row: + file_rows.append(row) + # TUT's runs no Catch2 entry was checked against: its run at the + # tree's own tier is the untiered one again, where that was checked. + for key in sorted((k for k in records if k[0] == stem and k not in used), key=lambda k: k[1] or ""): + if key[1] and key[1] == own_tier and (stem, None) in used: + print(f"{path}: listed: TUT's run at {own_tier}, the tree's own tier: {runs[None][0]} runs that " + f"tier, and was checked against TUT's untiered run") else: - problems.append(f"{path}: {name!r}: {e['tut_assertions']} TUT assertion calls, " - f"{expected} Catch2 assertions they became, {e['catch2_assertions']} mapped, " - f"{static} in the file") - for key, t in sorted(tut_results.items()): - problems.append(f"{path}: TUT ran [{key[0]}, {key[1]}], which the mapping does not have") - for name in sorted(set(by_name) - seen_cases): - problems.append(f"{path}: Catch2 case {name!r} is no TUT test of the mapping") - # TUT ran a file's groups by name and each group's tests by number, - # and a test may lean on what the ones before it left. - keys = set(paired.values()) - tut_order = [(r["group"], r["n"]) for r in recorded["results"] if (r["group"], r["n"]) in keys] - catch2_order = [(paired[c["name"]], c["name"]) for c in cases - if c["name"] in paired and by_name[c["name"]] is c] - for at, (tut_key, (key, name)) in enumerate(zip(tut_order, catch2_order)): - if tut_key != key: - problems.append(f"{path}: Catch2 runs {name!r}, [{key[0]}, {key[1]}], as test {at + 1} of " - f"{len(tut_order)}, where TUT ran [{tut_key[0]}, {tut_key[1]}]: not TUT's order") - break - clean = not any(p.startswith(path + ":") for p in problems) - rows.append((path, len(recorded["results"]), len(cases), matched, agree, counts_ok, len(listed), code, - "ok" if clean else "MISMATCH")) - for line in listed + notes + guard: - print(f"{path}: listed: {line}") + file_problems.append(f"{path}: TUT ran {stem} " + (f"at {key[1]}" if key[1] else "untiered") + + f" ({records[key]['ctest']}), and no Catch2 entry does") + problems.extend(file_problems) + for row in file_rows: + rows.append(row[:-1] + ("MISMATCH",) if file_problems else row) header = ("file", "TUT", "Catch2", "matched", "agree", "counts", "listed", "exit", "parity") widths = [max(len(str(r[i])) for r in rows + [header]) for i in range(len(header))] @@ -577,7 +666,7 @@ def check(args): print(" ".join(str(v).ljust(w) if i == 0 else str(v).rjust(w) for i, (v, w) in enumerate(zip(r, widths)))) for p in problems: print(f"mismatch: {p}") - return 1 if problems or any(r[-1] != "ok" for r in rows) else 0 + return 1 if problems else 0 def main(): From 5fb4ffa80056f810e9af4160bfd2e1db58fc1038 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:04:34 -0400 Subject: [PATCH 33/55] tut_parity.py counts a case's assertions in the body that ran tut2catch.py now gives two cases one name where an #if's branches part them, a test defined in both. The count was taken from the first case of the name in the file, so on a host compiling the second branch the active twin was held to the inactive one's count: a right conversion of a test with one assertion on Windows and two elsewhere failed on macOS with "2 mapped, 1 in the file". The count is now of the file's case the run's case is: by name, and where two have one name by the line Catch2 says the case starts on, as the check that every assertion ran already found it. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 846defa886..876403b065 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -480,9 +480,12 @@ def check_run(args, label, path, entry_name, test, recorded, entries, file_cases problems.append(f"{label}: {entry_name} exited {code}, which does not fit its cases (" + (", ".join(f"{v} {k}" for k, v in sorted(counts.items())) or "none") + ")") tut_results = {(r["group"], r["n"]): r for r in recorded["results"]} + # Counted in the body that ran: a test in both branches of an #if is + # two cases of one name in the file, and the run names the line of its. counted = {} - for c in file_cases: - counted.setdefault(c["name"], len(c["sites"])) + for name, case in by_name.items(): + body = body_of(case, file_cases) + counted[name] = len(body["sites"]) if body else None # An assertion counted is not one that ran: a case passes as well when a # loop around its CHECK never turns. Each site in a passing case's body # must have reported at least once. From 4cbc3351ad0064584ad8d302604007852fe172f4 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:08:07 -0400 Subject: [PATCH 34/55] tut_parity.py holds to having run only the assertions this build compiles The check that every assertion in a passing case ran took every site outside #if 0 to be compiled, so an assertion under #if LL_WINDOWS (as llprocess_test has inside its tests) would fail a right conversion on macOS and Linux for never having run. Which lines the build compiles is now asked of the file's own compile command, from the build's compile_commands.json through tut2catch.py's CompileDatabase, run as clang++ -E, as the converter decides a test is not compiled here: a site whose lines the preprocessor leaves out is not held, and one it keeps is, under an #if or not. Where there is no command to ask (no database, the file not in it, the preprocessor failing), a site under an #if that the case itself is not under -- a case that ran was compiled -- is listed rather than held. Paths are compared through links, so a tree reached by a link still finds its commands. Helper sites the build leaves out are not listed as never run. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 122 +++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 15 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 876403b065..23a889259b 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -57,7 +57,10 @@ passing assertions (-s), and each CHECK, REQUIRE and _THAT site must report from at least one line it spans. A FAIL or SKIP that ran would have failed or skipped the case, and a case that failed or skipped may - have stopped early, so neither is held to it. + have stopped early, so neither is held to it; nor is one the build + leaves out, which the file's compile command in the build's + compile_commands.json, run as clang++ -E, says. Where there is no such + command, one under an #if that never ran is listed. A test the mapping says TUT never ran (past its group's limit) or this host does not compile is listed, not counted against parity, as is an assertion @@ -312,18 +315,24 @@ def converter(): def read_cases(path): """The converted file's test cases and assertion sites, read from the file as tut2catch.py reads it: comments and literals masked, #if 0 branches - left out. A site is (first line, last line, macro), the lines its call - spans; returns ([{name, lines, sites}], the sites outside every case).""" + left out. A site is (first line, last line, macro, the #if blocks that + hold it), the lines its call spans; a case is {name, lines, sites, the + #if blocks that hold it}. Returns (the cases, the sites outside every + case).""" tc = converter() with open(path, "rb") as f: data = f.read() masked = tc.mask_source(data) dead = tc.dead_regions(masked) + conditional = conditional_regions(masked) newlines = [m.start() for m in re.finditer(rb"\n", masked)] def live(at): return not any(start <= at < end for start, end in dead) + def held(at): + return frozenset(i for i, (start, end) in enumerate(conditional) if start <= at < end) + def line(at): return bisect.bisect_left(newlines, at) + 1 @@ -332,7 +341,7 @@ def line(at): if live(m.start()): close = matching(masked, m.end() - 1) macro = m.group(0).rstrip(b"( \t\r\n").decode() - sites.append((m.start(), (line(m.start()), line(close), macro))) + sites.append((m.start(), (line(m.start()), line(close), macro, held(m.start())))) cases = [] inside = set() for m in re.finditer(rb"\bTEST_CASE(_METHOD)?\s*\(", masked): @@ -352,10 +361,75 @@ def line(at): end = matching(masked, brace) body = [(at, site) for at, site in sites if brace < at < end] inside.update(at for at, _ in body) - cases.append({"name": name, "lines": (line(m.start()), line(brace)), "sites": [site for _, site in body]}) + cases.append({"name": name, "lines": (line(m.start()), line(brace)), "sites": [site for _, site in body], + "held": held(m.start())}) return cases, [site for at, site in sites if at not in inside] +def conditional_regions(masked): + """[start, end) of each #if block, but an include guard's: code a build + may leave out.""" + regions = [] + stack = [] + for m in re.finditer(rb"^[ \t]*#[ \t]*(if|ifdef|ifndef|endif)\b([^\n]*)", masked, re.M): + if m.group(1) != b"endif": + name = m.group(2).strip() + guard = m.group(1) == b"ifndef" and re.match(rb"\s*#[ \t]*define[ \t]+" + re.escape(name) + rb"\b", + masked[m.end():]) is not None + stack.append((m.end(), guard)) + elif stack: + start, guard = stack.pop() + if not guard: + regions.append((start, m.start())) + return regions + + +class CompiledLines: + """The lines of a file this build compiles: its compile command, from + compile_commands.json as tut2catch.py reads it, run as clang++ -E. None + for a file with no command to be had, or one that fails.""" + + def __init__(self, build_dir, config): + self.build_dir = build_dir + self.config = config + self.database = None + self.loaded = False + + def __call__(self, path): + tc = converter() + # As the build names it, through any link on the way. + path = os.path.realpath(path) + if not self.loaded: + self.loaded = True + try: + self.database = tc.CompileDatabase(self.build_dir, self.config) + except tc.ConvertError: + pass + if self.database is None: + return None + try: + directory, args, compiled = self.database.command(path) + proc = subprocess.run(args + ["-E", compiled], cwd=directory, capture_output=True) + except (tc.ConvertError, OSError): + return None + if proc.returncode != 0: + return None + kept = set() + line = 0 + in_main = False + for raw in proc.stdout.split(b"\n"): + m = re.match(rb'^# (\d+) "((?:[^"\\]|\\.)*)"', raw) + if m: + line = int(m.group(1)) + named = os.path.join(directory, m.group(2).decode("utf-8", "replace")) + in_main = os.path.basename(named) == os.path.basename(path) and os.path.realpath(named) == path + continue + if in_main and raw.strip(): + kept.add(line) + line += 1 + return kept + + def matching(masked, at): """Where the bracket at at closes, in masked text.""" depth = 0 @@ -433,10 +507,12 @@ def tree_tier(build_dir): return m.group(1) if m else None -def check_run(args, label, path, entry_name, test, recorded, entries, file_cases, outside, record, problems): +def check_run(args, label, path, entry_name, test, recorded, entries, file_cases, outside, kept, record, problems): """One Catch2 entry's run of a converted file against TUT's run of its original: appends what does not hold to problems, prints what is listed, - and returns the table's row, or None where there is no run to show.""" + and returns the table's row, or None where there is no run to show. kept + is the file's lines this build compiles, or None where that is not + known.""" first_problem = len(problems) # A converted file changed since its binary was built is not what the # binary runs: its cases, lines and counts would not be the run's. @@ -488,7 +564,15 @@ def check_run(args, label, path, entry_name, test, recorded, entries, file_cases counted[name] = len(body["sites"]) if body else None # An assertion counted is not one that ran: a case passes as well when a # loop around its CHECK never turns. Each site in a passing case's body - # must have reported at least once. + # this build compiles must have reported at least once. Where that is + # not known, one under an #if the case is not under (a case that ran was + # compiled) is listed. + def compiled(first, last, conditional): + return any(n in kept for n in range(first, last + 1)) if kept is not None else \ + (None if conditional else True) + + # Listed for the run, apart from the tests listed. + notes = [] ran_anywhere = set() for case in cases: ran = ran_here(case, path) @@ -496,13 +580,19 @@ def check_run(args, label, path, entry_name, test, recorded, entries, file_cases if case["result"] != "ok": continue body = body_of(case, file_cases) - for first, last, macro in body["sites"] if body else []: - if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran): + for first, last, macro, blocks in body["sites"] if body else []: + if macro in REPORTING_ONLY or any(first <= n <= last for n in ran): + continue + here = compiled(first, last, bool(blocks - body["held"])) + if here: problems.append(f"{label}: {case['name']!r} passed without running the {macro} on line {first}") - # Listed for the run, apart from the tests listed. - notes = [f"the {macro} on line {first}, outside every case, never ran" - for first, last, macro in outside - if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran_anywhere)] + elif here is None: + notes.append(f"{case['name']!r}: the {macro} on line {first} never ran, and is under an #if no " + f"compile command here says this build compiles") + notes += [f"the {macro} on line {first}, outside every case, never ran" + for first, last, macro, blocks in outside + if macro not in REPORTING_ONLY and not any(first <= n <= last for n in ran_anywhere) + and compiled(first, last, bool(blocks)) is not False] # The TUT results must be of the source the mapping was made from. hashes = {"the recorded results": recorded.get("tut_sha256"), "the mapping": (record or {}).get("tut_sha256")} if all(hashes.values()): @@ -593,6 +683,7 @@ def check(args): records[tut_identity(recorded["ctest"]) if recorded.get("ctest") else (key, None)] = recorded own_tier = tree_tier(args.build_dir) tests = ctest_tests(args.build_dir, args.config) + compiled_lines = CompiledLines(args.build_dir, args.config) by_file = {} for entry in mapping: by_file.setdefault(entry["file"], []).append(entry) @@ -605,6 +696,7 @@ def check(args): stem = os.path.splitext(os.path.basename(path))[0] file_problems = [] file_cases, outside = read_cases(os.path.join(ROOT, path)) + kept = compiled_lines(os.path.join(ROOT, path)) record = (file_records or {}).get(path) # What no test's count holds: the assertions outside every test body # (helpers, fixtures), which the mapping totals for the file. @@ -647,7 +739,7 @@ def check(args): continue used.add(key) row = check_run(args, label, path, runs[tier][0], tests[runs[tier][0]], records[key], entries, - file_cases, outside, record, problems) + file_cases, outside, kept, record, problems) if row: file_rows.append(row) # TUT's runs no Catch2 entry was checked against: its run at the From 347a3b328a1d505da64687c412d2719509b03e7b Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:11:27 -0400 Subject: [PATCH 35/55] tut_parity.py reads the harness's exit 1 as it now means The harness on rye/tests-stage2 now fails a run that failed no case for the state guard's finding a change whether the cases passed or all skipped, and fails one where a teardown step threw, saying which. The exit check took 1 as fitting only a run with a pass and the state guard's line, so a run of skips the guard failed was a mismatch for its exit code, and a run whose teardown threw passed wherever the guard had also spoken. Where no case failed, 1 now fits the cases when the harness says the state guard found a change -- what the guard finds is listed, as TUT never looked -- and never when it says a teardown step threw, which is the harness's counterpart of a check at static destruction ending a TUT process. A mismatched exit code is reported with what the harness said. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index 23a889259b..e8617f31e8 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -39,7 +39,8 @@ none is tagged [!mayfail] or [!shouldfail], under which Catch2 passes a case whatever it does, and the run's exit code fits its cases: 0 when some passed and none failed, 125 when all skipped, neither when any - failed; + failed -- or, where none failed, 1 when the harness says its state + guard found a change, but not when it says a teardown step threw; - each case's assertions, counted in the converted file (read as tut2catch.py reads it: comments, literals and #if 0 code left out), number its TUT test's ensure, fail and skip calls, as the mapping @@ -285,15 +286,17 @@ def catch2_results(test, timeout): def exit_fits(code, cases, output): """Whether a run's exit code says what its cases do: 0 when some passed - and none failed (1 when the state guard found a change, which it says), - the skip code when every case skipped, and neither when any failed or - none ran -- a filter that matches nothing exits 2.""" + and none failed, the skip code when every case skipped, and neither when + any failed or none ran -- a filter that matches nothing exits 2. Where + none failed, the harness exits 1 for the state guard's finding a change, + which is listed, and for a teardown step's throwing, which is not: a + check at static destruction ended a TUT process.""" results = {c["result"] for c in cases} if "fail" in results or not results: return code not in (0, SKIP_CODE, "timeout") - if results == {"skip"}: - return code == SKIP_CODE - return code == 0 or (code == 1 and "The state guard found" in output) + if code == 1 and "The state guard found" in output and "teardown step(s) threw" not in output: + return True + return code == (SKIP_CODE if results == {"skip"} else 0) _CONVERTER = None @@ -553,8 +556,11 @@ def check_run(args, label, path, entry_name, test, recorded, entries, file_cases counts = {} for case in cases: counts[case["result"]] = counts.get(case["result"], 0) + 1 + said = [line.strip() for line in output.splitlines() + if "teardown step(s) threw" in line or "The state guard found" in line] problems.append(f"{label}: {entry_name} exited {code}, which does not fit its cases (" - + (", ".join(f"{v} {k}" for k, v in sorted(counts.items())) or "none") + ")") + + (", ".join(f"{v} {k}" for k, v in sorted(counts.items())) or "none") + ")" + + "".join(f"; it said: {line}" for line in said)) tut_results = {(r["group"], r["n"]): r for r in recorded["results"]} # Counted in the body that ran: a test in both branches of an #if is # two cases of one name in the file, and the run names the line of its. From 8aa8a732ade9d634619a30724556a208ae6724d0 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:48:15 -0400 Subject: [PATCH 36/55] tut2catch.py records a file its own bug stopped, traceback and all, and goes on convert_one caught only ConvertError. Any other exception, a KeyError from an AST shape the converter had not met, ended the worker thread with nothing in its result, so the run died on KeyError: 'value' after every other file had been converted, and wrote no mapping. It is now that file's error, its traceback the message: the run goes on, counts it among the errors, maps the other files and exits 2. Tried by making one of two probe files raise: the other converted and was mapped. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 3585a5c875..d7accd8f4e 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -65,7 +65,8 @@ What it cannot decide it leaves unconverted, marked // TM-CHECK: (or /* TM-CHECK: */ inside a macro), and it exits 1 listing every mark; 2 if a -file could not be read at all. It never +file could not be read at all, or the converter itself failed on one (its +traceback is that file's error, and the other files go on). It never drops a test or an assertion: --mapping writes one entry per test, with the TUT-side assertion calls in its body and the Catch2 assertions now in it, which scripts/testing/tut_parity.py checks. @@ -93,6 +94,7 @@ import subprocess import sys import threading +import traceback ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) DEFAULT_BUILD = "build-Darwin-ninja-os" @@ -2684,6 +2686,11 @@ def work(): except ConvertError as e: result["value"] = {"file": os.path.relpath(path, ROOT), "error": str(e), "tests": [], "checks": [], "notes": []} + except Exception: + # A bug of the converter's own, met in one file: it is that + # file's error, traceback and all, and the others go on. + result["value"] = {"file": os.path.relpath(path, ROOT), "error": traceback.format_exc().rstrip(), + "tests": [], "checks": [], "notes": []} # Deep ASTs recurse deeply. threading.stack_size(512 * 1024 * 1024) t = threading.Thread(target=work) From 7ec8a26dabb2acce8e0c78ea8bd1713aa6f15521 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:49:41 -0400 Subject: [PATCH 37/55] tut2catch.py finds the tut:: types production code befriends with a leading :: Every such friend in the tree is written friend struct ::tut::X, which the search for them, wanting tut right after struct or class, never matched: it found none, so llfontregistry_test.cpp and alincludesearch_test.cpp had namespace tut made anonymous, their fixtures with it, and lost the friendship that reaches LLFontRegistry's and ALIncludeSearch's private members. Converted now, both keep namespace tut, and clang++ -fsyntax-only with their own flags passes both, where it failed both before on the private members. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index d7accd8f4e..09537b51d1 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -2595,7 +2595,7 @@ def tut_friends(): for f in files: if f.endswith((".h", ".hpp", ".inl")): with open(os.path.join(top, f), "rb") as fh: - for m in re.finditer(rb"friend\s+(?:struct|class)\s+tut\s*::\s*(\w+)", fh.read()): + for m in re.finditer(rb"friend\s+(?:struct|class)\s+(?:::\s*)?tut\s*::\s*(\w+)", fh.read()): _FRIENDS.add(m.group(1).decode()) return _FRIENDS From 4144cf9bf095a2906fc4df912b01667264696c36 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:50:44 -0400 Subject: [PATCH 38/55] tut2catch.py gives two cases one name only where one #if's branches part them The names were kept apart by the set of uncompiled branches around each test: a test in an #if this host does not compile and a test outside any #if counted as never compiled together, so both kept a name where the #if was true and Catch2 met two cases of one name. Only tests in different branches of one #if chain are never compiled together; any other two may be, whatever their conditions, so their names must differ. A probe with a test in #if LL_WINDOWS named as an unconditional one now names it #2; three tests in #if/#elif/#else keep one name; one in a nested #if takes its number. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 57 +++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 09537b51d1..2502488102 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -14,14 +14,15 @@ set_test_name literal; then the first sentence of the comment written above the test, where that is prose; else #N. " #N" is added to a name another case of the file has - (ignoring case). Tests declared out of TUT's numeric order - are moved later in the file, never earlier, so that - declaration order, Catch2's, is TUT's. The group's typedefs, - its test_group object and a class derived from test_group - only to name it go. namespace tut, once nothing names tut:: - and no type production code befriends as tut:: is in it, - becomes an anonymous namespace, taking in one directly inside - it. + (ignoring case), unless the two are in different branches + of one #if, never compiled together. Tests declared out of + TUT's numeric order are moved later in the file, never + earlier, so that declaration order, Catch2's, is TUT's. The + group's typedefs, its test_group object and a class derived + from test_group only to name it go. namespace tut, once + nothing names tut:: and no type production code befriends as + tut:: is in it, becomes an anonymous namespace, taking in one + directly inside it. assertions CHECK, which lets a case go on and so reports every failure of a run; REQUIRE where going on could only crash or bury the first failure: a condition that guards what follows (a @@ -325,6 +326,21 @@ def matching(self, off): i += 1 return -1 + def conditionals_at(self, off): + """The #if chains around off, outermost first: each as (where its #if + is, which of its branches holds off, counting from 0).""" + stack = [] + for start, end, kw, _ in self.directives: + if start > off: + break + if kw in ("if", "ifdef", "ifndef"): + stack.append((start, 0)) + elif kw in ("elif", "else", "elifdef", "elifndef") and stack: + stack[-1] = (stack[-1][0], stack[-1][1] + 1) + elif kw == "endif" and stack: + stack.pop() + return tuple(stack) + def state(self, off): """dead (under #if 0), inactive (a branch this host does not compile) or active.""" result = "active" @@ -1962,7 +1978,7 @@ def test_containing(self, offset): def name_tests(self): for data, group in self.groups.items(): - seen = set() + seen = [] # (the #if branches around a case, its name) for test in sorted(group.tests, key=lambda t: t.n): if test.macro: test.tut_name = None # left as it is: no case, and parity says so @@ -1995,13 +2011,14 @@ def name_tests(self): text_name = given test.name_literal_inner = c_escape(given) base = f"{group.name}: {text_name}" if text_name is not None else f"{group.name}: #{test.n}" - # Tests in different branches of an #if are never compiled - # together, so their names cannot clash. - branch = tuple((a, b) for a, b, what in self.src.regions if a <= test.start < b) + # Only tests in different branches of one #if are never + # compiled together; any other two may be, so their names + # must differ. + where = self.src.conditionals_at(test.start) name = base - if (branch, name.lower()) in seen: + if any(other == name.lower() and not exclusive(where, at) for at, other in seen): name = f"{base} #{test.n}" - seen.add((branch, name.lower())) + seen.append((where, name.lower())) test.catch2_name = name if text_name is not None and name == base: test.catch2_literal = '"' + c_escape(group.name + ": ") + test.name_literal_inner + '"' @@ -2431,6 +2448,18 @@ def dead_regions(masked): return regions +def exclusive(a, b): + """Whether code at two places, given by Source.conditionals_at, is never + compiled together: only where they lie in different branches of one + #if. Different #ifs may both hold, whatever their conditions say.""" + for (chain_a, branch_a), (chain_b, branch_b) in zip(a, b): + if chain_a != chain_b: + return False + if branch_a != branch_b: + return True + return False + + def test_body(test): return next((c for c in test.node.get("inner", []) if c.get("kind") == "CompoundStmt"), None) \ if test.node else None From e14ace130abba03c787ae91ace8439bde499e38b Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 19:59:37 -0400 Subject: [PATCH 39/55] tut2catch.py keeps a class derived from test_group that holds more than a name Any class derived from test_group was deleted whole, with its object, on the strength of llvocache's, which only names the group. One with a constructor that does something, a destructor or members of its own lost them silently: TUT ran them around the group's tests, Catch2 never would. Only a class holding nothing but a constructor that takes nothing, does nothing and passes the group its name goes now; any other stays, its object with it, under a TM-CHECK to convert it by hand. On probes, a factory whose constructor sets up state and one with a member are kept and marked; one like llvocache's still goes, as llvocache's own does. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 45 ++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 2502488102..a4d7870005 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -19,7 +19,9 @@ TUT's numeric order are moved later in the file, never earlier, so that declaration order, Catch2's, is TUT's. The group's typedefs, its test_group object and a class derived - from test_group only to name it go. namespace tut, once + from test_group only to name it go; a class derived from it + that holds more (a constructor's body, a destructor, + members) stays, with its object, marked. namespace tut, once nothing names tut:: and no type production code befriends as tut:: is in it, becomes an anonymous namespace, taking in one directly inside it. @@ -1051,13 +1053,14 @@ def on_var(self, node, parents): derived = self.derived_groups.get(full) or self.derived_groups.get(full.rpartition("::")[2]) if not m and derived is None: return + plain = True if m: args = split_template_args(m.group(1)) data, limit = args[0], int(args[1]) if len(args) > 1 else 50 literal = find_kind(node, "StringLiteral") name = literal.get("value") if literal else None else: - data, limit, name = derived + data, limit, name, plain = derived where = self.node_range(node)[0] if self.node_range(node) else 0 if not name or not name.startswith('"'): self.check(where, "a test_group whose name is not a string literal") @@ -1066,11 +1069,15 @@ def on_var(self, node, parents): if data in self.groups: self.check(where, f"two groups run the tests of {data}") self.groups[data] = Group(name, data, limit) - self.remove_decl(node) + if plain: + self.remove_decl(node) def on_record(self, node, parents): - """A class derived from test_group only to name the group in its - constructor, as llvocache's is.""" + """A class derived from test_group: one that only names the group in + its constructor, as llvocache's does, goes with the group; one that + holds more (a constructor's body, a destructor, members), which TUT + ran around the group's tests, stays, and its object with it, marked + for converting by hand.""" for base in node.get("bases", []): t = base.get("type", {}) full = t.get("desugaredQualType") or t.get("qualType") or "" @@ -1081,10 +1088,17 @@ def on_record(self, node, parents): data = args[0] if "::" in args[0] else "tut::" + args[0] literal = find_kind(node, "StringLiteral") name = literal.get("value") if literal else None + plain = names_only(node) qualified = "::".join([n for n in self.namespaces_of(parents) if n] + [node.get("name", "")]) for key in (qualified, node.get("name", "")): - self.derived_groups[key] = (data, int(args[1]) if len(args) > 1 else 50, name) - self.remove_decl(node) + self.derived_groups[key] = (data, int(args[1]) if len(args) > 1 else 50, name, plain) + if plain: + self.remove_decl(node) + else: + rng = self.node_range(node) + self.check(rng[0] if rng else 0, + f"{node.get('name')} derives from test_group and holds more than the group's name, " + f"which TUT ran around the group's tests: convert it by hand") def remove_decl(self, node): rng = self.node_range(node) @@ -2448,6 +2462,23 @@ def dead_regions(masked): return regions +def names_only(record): + """Whether a class derived from test_group holds nothing but a + constructor, taking nothing and doing nothing, that passes the group its + name: nothing TUT ran around the group's tests goes with it.""" + own = [c for c in record.get("inner", []) if "kind" in c and not c.get("isImplicit") + and c.get("kind") != "AccessSpecDecl"] + if len(own) != 1 or own[0].get("kind") != "CXXConstructorDecl": + return False + parts = [c for c in own[0].get("inner", []) if "kind" in c] + inits = [c for c in parts if c.get("kind") == "CXXCtorInitializer"] + rest = [c for c in parts if c.get("kind") != "CXXCtorInitializer"] + if len(inits) != 1 or "baseInit" not in inits[0]: + return False + return len(rest) == 1 and rest[0].get("kind") == "CompoundStmt" and \ + not [c for c in rest[0].get("inner", []) if "kind" in c] + + def exclusive(a, b): """Whether code at two places, given by Source.conditionals_at, is never compiled together: only where they lie in different branches of one From a93039a9db862317ca7b6e6c6f423818c8693f3e Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:07:48 -0400 Subject: [PATCH 40/55] tut2catch.py splits and rewrites a condition only where the test wrote it, not a macro A && chain was split wherever clang's tree had a &&, so ensure(WRAP(a == 1 && b == 2)) became CHECK(a == 1); CHECK(b == 2) and the macro was gone; so was the parenthesis-dropping of ensure(WRAP(a == 1)), and with ensure(a == 1 && b == ID(2)) the second term's text ended inside ID(, a line that could not compile. A node is now rewritten only where both its ends are in the call's own text (in the file, or in the one macro argument the call itself is written in): a && is split only where its operands are and the text between them is the &&, parentheses dropped only where they are the test's own, and anything else goes whole, macro and all, parenthesised where Catch2 cannot decompose it. An argument a macro begins, as ID(x) does, now starts where the macro was used rather than at the x inside it, which had cut ensure_equals(ID(a), 1) and the whole-condition text short. Probed with WRAP, an identity macro, a macro whose body is the &&, a macro constant at a chain's end, and a chain inside a macro's argument (still split, as before). Over the stage-3 files one line changes: llbuffer_test's (0 == ... && NULL == segment.data()) && 0 == segment.size() splits at the && it wrote around the last term, where before the NULL in the middle kept the whole chain from splitting. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 129 +++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 36 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index a4d7870005..5e79fcfa5d 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -57,6 +57,10 @@ std::string_views. Operands are parenthesised where a macro or an operator would split or rebind them; a condition Catch2 cannot decompose (||, ?:) is parenthesised whole. + None of this reaches into a macro the test used: a && or + parentheses a macro made (WRAP(a && b), a && in a macro's + body) are not split or dropped, and the condition goes as + the test wrote it, macro and all. includes lltut.h and tut.hpp give way to the Catch2 headers the file now uses, and altestmatchers.h, altestname.h or altestfailure.h where it uses them. @@ -719,24 +723,6 @@ def unparenthesised(node): return node -def and_terms(node): - """The terms of a && chain, each as the test wrote it, or [node].""" - node = skip_implicit(node) - while node.get("kind") == "ParenExpr": - inner = [c for c in node.get("inner", []) if "kind" in c] - if not inner: - break - inside = skip_implicit(inner[0]) - if inside.get("kind") != "BinaryOperator" or inside.get("opcode") != "&&": - break - node = inside - if node.get("kind") == "BinaryOperator" and node.get("opcode") == "&&": - inner = [c for c in node.get("inner", []) if "kind" in c] - if len(inner) == 2: - return and_terms(inner[0]) + and_terms(inner[1]) - return [node] - - def operands(node): """A comparison's two operands, implicit nodes and all, or None.""" node = skip_implicit(node) @@ -1385,7 +1371,71 @@ def plain_span(self, node): return None return rng[0], rng[1] - def assertion(self, node, span, negate): + @staticmethod + def expansion(loc): + """Where the macro whose expansion holds loc was used, or None for a + place in the file's own text.""" + if loc and "expansionLoc" in loc: + return loc["expansionLoc"].get("_f"), loc["expansionLoc"].get("offset") + return None + + def call_span(self, node, record): + """[start, end) of a node written in its call's own text, or None: + where the call is in the file, no end of the node is a macro's; + where the call is in a macro's argument, the node is in it too. A + node one end of which a macro made, as the ( of WRAP(a && b) or + the 2 of ID(2), has no text of its own to rewrite.""" + span = self.plain_span(node) + rng = node.get("range") or {} + if span is None: + return None + own = self.expansion(record["node"].get("range", {}).get("begin")) + if self.expansion(rng.get("begin")) != own or self.expansion(rng.get("end")) != own: + return None + return span + + def written_parens(self, node, record): + """Whether a ParenExpr's parentheses are the call's own text, not a + macro's.""" + span = self.call_span(node, record) + return span is not None and self.src.masked[span[0]:span[0] + 1] == b"(" and \ + self.src.masked[span[1] - 1:span[1]] == b")" + + def bare(self, node, record): + """node, without the parentheses the test wrote around a whole Catch2 + can decompose (a macro's stay), and without the implicit nodes + around that.""" + node = skip_implicit(node) + while node.get("kind") == "ParenExpr" and self.written_parens(node, record): + inner = [c for c in node.get("inner", []) if "kind" in c] + if not inner or not decomposable(inner[0]): + break + node = skip_implicit(inner[0]) + return node + + def and_terms(self, node, record): + """The terms of a && chain, each as the test wrote it, or [node]. + Only a && and parentheses written in the call's own text are split + or seen through: one a macro made (WRAP(a && b), or the && of a + macro's body) would take the macro with it.""" + node = skip_implicit(node) + while node.get("kind") == "ParenExpr" and self.written_parens(node, record): + inner = [c for c in node.get("inner", []) if "kind" in c] + if not inner: + break + inside = skip_implicit(inner[0]) + if inside.get("kind") != "BinaryOperator" or inside.get("opcode") != "&&": + break + node = inside + if node.get("kind") == "BinaryOperator" and node.get("opcode") == "&&": + inner = [c for c in node.get("inner", []) if "kind" in c] + spans = [self.call_span(c, record) for c in inner] + if len(inner) == 2 and None not in spans and \ + self.src.masked[spans[0][1]:spans[1][0]].strip() in (b"&&", b"and"): + return self.and_terms(inner[0], record) + self.and_terms(inner[1], record) + return [node] + + def assertion(self, node, span, negate, record): """(suffix, text) of the one assertion a condition is: "" or "_FALSE" after CHECK or REQUIRE, and what goes between its parentheses. Parentheses the test wrote around a whole Catch2 decomposes go, so @@ -1394,28 +1444,28 @@ def assertion(self, node, span, negate): itself; and strcmp() is compared as std::string_view, which prints.""" suffix = "_FALSE" if negate else "" whole = self.render(*span) - node = unparenthesised(node) - nspan = self.plain_span(node) + node = self.bare(node, record) + nspan = self.call_span(node, record) if nspan is None: return suffix, whole if decomposable(node) else "(" + whole + ")" pair = operands(node) op = node.get("opcode") if pair else None if op in ("==", "!="): left, right = (skip_implicit(p) for p in pair) - spans = [self.plain_span(p) for p in pair] + spans = [self.call_span(p, record) for p in pair] if None not in spans: for lit, other, other_span in ((left, right, spans[1]), (right, left, spans[0])): if lit.get("kind") == "CXXBoolLiteralExpr" and value_type(other) == "bool": truth = (lit.get("value") is True) == (op == "==") - other_node = unparenthesised(other) - inner = self.render(*(self.plain_span(other_node) or other_span)) + other_node = self.bare(other, record) + inner = self.render(*(self.call_span(other_node, record) or other_span)) if not decomposable(other_node) and not wrapped(inner): inner = "(" + inner + ")" return ("" if truth != negate else "_FALSE"), inner for zero, call in ((left, right), (right, left)): args = strcmp_args(call) if args and zero.get("kind") == "IntegerLiteral" and zero.get("value") == "0": - text = self.string_views(args, op) + text = self.string_views(args, op, record) if text: return suffix, text if is_literal(left) and not is_literal(right): @@ -1423,24 +1473,24 @@ def assertion(self, node, span, negate): if node.get("kind") == "UnaryOperator" and node.get("opcode") == "!": inner = [c for c in node.get("inner", []) if "kind" in c] args = strcmp_args(inner[0]) if inner else None - text = self.string_views(args, "==") if args else None + text = self.string_views(args, "==", record) if args else None if text: return suffix, text # !x is x, asserted false, so that x prints. - operand = unparenthesised(inner[0]) if inner else None - ospan = self.plain_span(operand) if operand else None + operand = self.bare(inner[0], record) if inner else None + ospan = self.call_span(operand, record) if operand else None if ospan: text = self.render(*ospan) return ("" if negate else "_FALSE"), text if decomposable(operand) else "(" + text + ")" args = strcmp_args(node) - text = self.string_views(args, "!=") if args else None + text = self.string_views(args, "!=", record) if args else None if text: return suffix, text text = self.render(*nspan) return suffix, text if decomposable(node) else "(" + text + ")" - def string_views(self, args, op): - spans = [self.plain_span(a) for a in args] + def string_views(self, args, op, record): + spans = [self.call_span(a, record) for a in args] if None in spans: return None texts = [] @@ -1461,8 +1511,15 @@ def arg_ranges(self, record): open_paren = self.src.masked.find(b"(", start, end) return [(self.src.skip_space(open_paren + 1), self.src.skip_space_back(end - 1))] begins = [] + own = self.expansion(record["node"].get("range", {}).get("begin")) for arg in record["args"]: - begin = self.loc(arg.get("range", {}).get("begin")).in_text() + loc = arg.get("range", {}).get("begin") + where = self.loc(loc) + begin = where.in_text() + # An argument a macro begins, as ID(x) does with x, begins where + # the macro was used, not at the x inside it. + if where.how == "macro-arg" and where.expansion and self.expansion(loc) != own: + begin = where.expansion[0] if begin is None or not (start < begin < end): return None begins.append(begin) @@ -1600,15 +1657,15 @@ def streamed(i): condition = args[n - 1] # A && chain is as many assertions, each printing its operands, # each as strong as its own term needs. - terms = and_terms(condition) if name == "ensure" else [condition] - if len(terms) > 1 and any(self.plain_span(t) is None for t in terms): + terms = self.and_terms(condition, record) if name == "ensure" else [condition] + if len(terms) > 1 and any(self.call_span(t, record) is None for t in terms): terms = [condition] def build(): out = [] for term in terms: - span = ranges[n - 1] if len(terms) == 1 else self.plain_span(term) - suffix, text = self.assertion(term, span, name == "ensure_not") + span = ranges[n - 1] if len(terms) == 1 else self.call_span(term, record) + suffix, text = self.assertion(term, span, name == "ensure_not", record) strength = req if len(terms) == 1 else self.strength(record, [term]) out.append(f"{strength}{suffix}({text})") msg = message(0) if n == 2 else [] From 81dc09c15c069ae8cce451f38f4763ade8a6b45f Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:22:10 -0400 Subject: [PATCH 41/55] tut2catch.py makes REQUIRE every guard a regression would crash past, and a chain's term a later one reads Only raw pointers and a few members (size, empty, has...) were seen to guard what follows, so the rest became CHECK, which goes on past a failure: hip != aliases.end() went on to hip->second, found.has_value() to found->text, again && ... to again->whole, a crash where TUT had failed the test. Seen as guards now, beside those: - a position compared, == or !=, built in or overloaded (rewritten C++20 comparisons included), with a container's end(), cend(), rend() or crend(), or with npos; - an object tested through its operator bool (an optional, a smart pointer, a stream, an error_code; not an atomic, whose bool is its value) or compared with nullptr through an operator of its own; - has_value() and found(). Two more rules make REQUIRE what TUT's single ensure stopped at: - a condition that calls what makes the state the test reads after it: a call returning bool to a non-const member function of an object (clang converts the object to const for a const one, so one left non-const is the other), or with an argument it may fill (a non-const lvalue bound as it is, or its address), where the test's body names that object or argument after the assertion, as ensure(far_inv.setInverse(far_away)) before far_inv is used; - a term of a split && chain that a later term of it reads a variable or field of (a constant number is no state), since TUT stopped the test at the first term that failed. A plain compare of values stays CHECK, as do a const call, a call whose object or filled argument is never read again, and a destructor's assertions. Over the stage-3 files, 520 CHECKs become REQUIRE in 47 files and nothing else changes: 87 by the new guards, 325 by the calls that make state (timer.isExpired(..., started, ...), convertToS32(str, value), store.write(...) and the like) and 108 by the chain rule. Every instance the review named, in alrecoverystore, llbvhloader, allsloptimizer, alscriptkeymap and alpreprocessor, is REQUIRE. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 220 ++++++++++++++++++++++++++++++++--- 1 file changed, 201 insertions(+), 19 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 5e79fcfa5d..7ef4e14730 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -28,10 +28,20 @@ assertions CHECK, which lets a case go on and so reports every failure of a run; REQUIRE where going on could only crash or bury the first failure: a condition that guards what follows (a - pointer, a size, an emptiness, a presence), in a loop or a - lambda, and in a helper outside a test's body. In a - destructor, where a throw would end the process, CHECK - always. With that macro (X below): + pointer, null or not; an optional or a smart pointer, tested + through its operator bool or compared with nullptr; a + position compared with end() or npos; a size, an emptiness, + a presence: has(), has_value(), found() and the like); a + condition that calls what makes it (a non-const member + function returning bool, ensure(m.setInverse(n)), or a call + returning bool that fills an argument passed by reference or + address), where the test reads that object or argument + after it; a term of a split && chain that a later term of it + reads a variable of (TUT's ensure stopped the test at the + first term that failed); in a loop or a lambda; and in a + helper outside a test's body. A plain compare of values + stays CHECK. In a destructor, where a throw would end the + process, CHECK always. With that macro (X below): ensure(c) -> X(c); ensure(a && b && ...) -> X(a); X(b); ... one assertion a term, so that each prints its operands; ensure(msg, c) -> UNSCOPED_INFO(msg) then X(c), or, where c @@ -139,7 +149,13 @@ # What a condition asks of an object when what follows relies on the answer: # that it holds something, or how much. GUARD_MEMBERS = {"size", "empty", "length", "count", "isDefined", "isUndefined", "has", "isMap", "isArray", - "notNull", "isNull"} + "notNull", "isNull", "has_value", "found"} +# A container's ends, which a position found in it is compared with: at one, +# there is nothing to read. +END_MEMBERS = {"end", "cend", "rend", "crend"} +# The arithmetic types: a constant of one is no state a later term relies on. +ARITHMETIC = re.compile(r"(?:(?:unsigned|signed|short|long|int|char|float|double|bool|wchar_t|char8_t|char16_t|" + r"char32_t|size_t|F32|F64|S8|U8|S16|U16|S32|U32|S64|U64)\s*)+") LITERAL_KINDS = {"IntegerLiteral", "FloatingLiteral", "CXXBoolLiteralExpr", "CharacterLiteral", "StringLiteral", "CXXNullPtrLiteralExpr", "GNUNullExpr", "FixedPointLiteral"} # A comment that names nothing: a separator, or a note to whoever reads on. @@ -937,6 +953,7 @@ def __init__(self, source, items, tut_ids, active_lines, demangled): self.name_overrides = {} # (group, n) -> a case's name, from --names self.friend_names = set() # tut:: types production code befriends self.tut_namespaces = [] # NamespaceDecl nodes named tut, with their parents + self.body_reads = {} # id of a test's body -> (offset, declaration) of what it reads source.find_conditionals(active_lines) # ---- helpers ------------------------------------------------------- @@ -1313,9 +1330,9 @@ def strength(self, record, nodes): - outside a test's own body (a helper, which any caller may call in a loop) and in a loop or lambda of one, REQUIRE, so that the first element that fails is the one reported; - - where the condition guards what follows (a pointer not null, a - size, an emptiness, a presence), REQUIRE; - - otherwise CHECK.""" + - where the condition guards what follows (see guards()), or + calls what makes it (see prepares()), REQUIRE; + - otherwise CHECK, a plain compare of values among it.""" if self.in_destructor(record): return "CHECK" test = self.test_containing(record["range"][0]) @@ -1327,17 +1344,22 @@ def strength(self, record, nodes): break if parent.get("kind") in LOOP_KINDS or parent.get("kind") == "LambdaExpr": return "REQUIRE" - if any(self.guards(n) for n in nodes if n): + if any(self.guards(n) or self.prepares(n, record) for n in nodes if n): return "REQUIRE" return "CHECK" @staticmethod def guards(node): """Whether a condition or value tells what the code after it relies - on: a pointer, compared with null or tested as a bool, or a member - that says how much an object holds or whether it holds anything. - Default arguments are not looked in: a NULL a callee defaults to is - nothing the test compared.""" + on: a pointer, compared with null or tested as a bool; an object + tested through its operator bool (an optional, a smart pointer, a + stream; not an atomic, whose bool is its value) or compared with + nullptr through an operator of its own; a position + compared with its container's end() or with npos; or a member that + says how much an object holds or whether it holds anything + (has_value() and found() among them). Default arguments are not + looked in: a NULL a callee defaults to is nothing the test + compared.""" if value_type(unparenthesised(node)).endswith("*"): return True stack = [node] @@ -1348,11 +1370,13 @@ def guards(node): continue if kind == "ImplicitCastExpr" and sub.get("castKind") == "PointerToBoolean": return True - if kind == "BinaryOperator" and sub.get("opcode") in ("==", "!="): - if any(skip_implicit(c).get("kind") in ("CXXNullPtrLiteralExpr", "GNUNullExpr") or - (c.get("kind") == "ImplicitCastExpr" and c.get("castKind") == "NullToPointer") - for c in sub.get("inner", []) if "kind" in c): + if kind == "MemberExpr" and sub.get("name") == "operator bool": + # An atomic's bool is its value, not whether it holds one. + base = [c for c in sub.get("inner", []) if "kind" in c] + if not (base and re.search(r"atomic", base[0].get("type", {}).get("qualType", ""), re.I)): return True + if any(is_end(c) for c in compared(sub)): + return True if kind == "MemberExpr" and sub.get("name") in GUARD_MEMBERS: # isNull() and notNull() guard only on what points: on a # vector or a UUID they are values like any other. @@ -1364,6 +1388,57 @@ def guards(node): stack.extend(c for c in sub.get("inner", []) if isinstance(c, dict) and "kind" in c) return False + def prepares(self, node, record): + """Whether a condition calls what makes the state the test reads + after it: a call returning bool, to a non-const member function of + an object, or with an argument it may fill (one passed by non-const + reference, or by address), where the test's body reads that object + or argument again after the assertion. A false answer is that what + it goes on to read was never made, as with + ensure(inverse.setInverse(m)) before inverse is used.""" + test = self.test_containing(record["range"][0]) + body = test_body(test) if test else None + if body is None: + return False + made = set() + stack = [node] + while stack: + sub = stack.pop() + kind = sub.get("kind") + if kind in ("CXXDefaultArgExpr", "LambdaExpr"): + continue + inner = [c for c in sub.get("inner", []) if isinstance(c, dict) and "kind" in c] + if kind in ("CallExpr", "CXXMemberCallExpr") and inner and value_type(sub) == "bool": + callee = inner[0] + if kind == "CXXMemberCallExpr" and callee.get("kind") == "MemberExpr" and \ + callee.get("name") != "operator bool": + base = [c for c in callee.get("inner", []) if "kind" in c] + if base and not const_object(base[0], callee.get("isArrow")): + made |= variables(base[0]) + for arg in inner[1:]: + if fills(arg): + made |= variables(arg) + stack.extend(inner) + if not made: + return False + after = record["range"][1] + return any(at >= after and decl in made for at, decl in self.reads(body)) + + def reads(self, body): + """(offset, declaration) of every variable and field a test's body + names, once per body.""" + cache = self.body_reads + if id(body) not in cache: + found = [] + for sub in iter_nodes(body): + decl = variable_of(sub) + if decl: + at = self.loc(sub.get("range", {}).get("begin")).in_text() + if at is not None: + found.append((at, decl)) + cache[id(body)] = found + return cache[id(body)] + def plain_span(self, node): """[start, end) of a node written in this file's text, or None.""" rng = self.node_range(node) @@ -1661,12 +1736,25 @@ def streamed(i): if len(terms) > 1 and any(self.call_span(t, record) is None for t in terms): terms = [condition] + # TUT's ensure stopped the test at the first term that failed: + # one a later term reads the same variable as ends the case. + chained = [False] * len(terms) + if len(terms) > 1 and not self.in_destructor(record): + later = set() + for i in range(len(terms) - 1, -1, -1): + reads = variables(terms[i], constants=False) + chained[i] = bool(reads & later) + later |= reads + def build(): out = [] - for term in terms: + for i, term in enumerate(terms): span = ranges[n - 1] if len(terms) == 1 else self.call_span(term, record) suffix, text = self.assertion(term, span, name == "ensure_not", record) - strength = req if len(terms) == 1 else self.strength(record, [term]) + if len(terms) == 1: + strength = req + else: + strength = "REQUIRE" if chained[i] else self.strength(record, [term]) out.append(f"{strength}{suffix}({text})") msg = message(0) if n == 2 else [] if msg and len(out) > 1: @@ -2519,6 +2607,100 @@ def dead_regions(masked): return regions +def compared(node): + """The two operands of an == or != comparison, built in or overloaded, + or ().""" + kind = node.get("kind") + inner = [c for c in node.get("inner", []) if isinstance(c, dict) and "kind" in c] + if kind == "BinaryOperator" and node.get("opcode") in ("==", "!=") and len(inner) == 2: + return inner + if kind == "CXXOperatorCallExpr" and len(inner) == 3: + name = skip_implicit(inner[0]).get("referencedDecl", {}).get("name") + if name in ("operator==", "operator!="): + return inner[1:] + return () + + +def is_end(node): + """Whether a compared operand is where nothing is: null, a container's + end() and the like, or npos.""" + if node.get("kind") == "ImplicitCastExpr" and node.get("castKind") == "NullToPointer": + return True + node = skip_implicit(node) + kind = node.get("kind") + if kind in ("CXXNullPtrLiteralExpr", "GNUNullExpr"): + return True + if kind == "DeclRefExpr" and node.get("referencedDecl", {}).get("name") == "npos": + return True + if kind == "CXXMemberCallExpr": + callee = next((c for c in node.get("inner", []) if "kind" in c), {}) + return callee.get("kind") == "MemberExpr" and callee.get("name") in END_MEMBERS + return False + + +def variable_of(node): + """The declaration a node names, where it is a variable or a field.""" + kind = node.get("kind") + if kind == "DeclRefExpr": + ref = node.get("referencedDecl", {}) + if ref.get("kind") in ("VarDecl", "ParmVarDecl", "BindingDecl"): + return ref.get("id") + elif kind == "MemberExpr" and node.get("type", {}).get("qualType") != "": + return node.get("referencedMemberDecl") + return None + + +def variables(node, constants=True): + """The variables and fields a node reads, by declaration, without those + of default arguments; without, too, unless constants, a constant of + arithmetic type, which is no state.""" + found = set() + stack = [node] + while stack: + sub = stack.pop() + if sub.get("kind") == "CXXDefaultArgExpr": + continue + decl = variable_of(sub) + if decl: + t = sub.get("referencedDecl", {}).get("type", {}) if sub.get("kind") == "DeclRefExpr" \ + else sub.get("type", {}) + text = t.get("desugaredQualType") or t.get("qualType") or "" + if constants or not re.fullmatch(r"const\s+" + ARITHMETIC.pattern, text.strip()): + found.add(decl) + stack.extend(c for c in sub.get("inner", []) if isinstance(c, dict) and "kind" in c) + return found + + +def const_object(base, arrow): + """Whether the object a member function is called on is const there, as + clang typed it: a const member function called on an object that is not + const has it converted to const, so a call on an object that stays + non-const is to a non-const member function.""" + text = base.get("type", {}).get("qualType", "") + if arrow and "*" in text: + text = text[:text.rfind("*")] + while re.search(r"<[^<>]*>", text): + text = re.sub(r"<[^<>]*>", "", text) + return re.search(r"\bconst\b", text) is not None + + +def fills(arg): + """Whether a call may fill an argument: an lvalue passed as it is, bound + to a non-const reference, or the address of one, to a pointer to + non-const. One bound to a const reference, or to a const pointer, is + converted to const first.""" + kind = arg.get("kind") + text = arg.get("type", {}).get("qualType", "") + if kind == "UnaryOperator" and arg.get("opcode") == "&": + return not const_object(arg, True) + if kind in ("ImplicitCastExpr", "CXXConstructExpr", "MaterializeTemporaryExpr", "CXXBindTemporaryExpr") or \ + arg.get("valueCategory") != "lvalue": + return False + while re.search(r"<[^<>]*>", text): + text = re.sub(r"<[^<>]*>", "", text) + return re.search(r"\bconst\b", text) is None + + def names_only(record): """Whether a class derived from test_group holds nothing but a constructor, taking nothing and doing nothing, that passes the group its From 12923586bf5753c59d564bf3ae41077aacaeb82e Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:27:15 -0400 Subject: [PATCH 42/55] tut2catch.py declares a file's cases in the order TUT ran them, groups by name TUT's runner keeps a file's groups in a std::map keyed by name, so it ran them in the byte order of their names, each group's tests by number; Catch2 runs the cases as declared. Only each group's numeric order was restored, so a file whose groups were declared in another order than their names' ran them in another order than TUT did, and the docstring's claim that declaration order is TUT's held only within a group. The order is now the file's: walking TUT's run order, a test declared before the one TUT ran ahead of it moves later, never earlier, to just after the latest-declared test placed so far. A move stays in its own namespace block (the brace around it) and #if branch; where one would have to leave them, the order is left as declared, but for each group's numeric order where that can be restored, and a TM-CHECK and a note say the cases run in another order than TUT's. Over the stage-3 files: llpose_test and llsdserialize_test now declare their cases in TUT's order exactly, and still pass clang++ -fsyntax-only; commonmisc_test and io_test, each of whose groups sits in a namespace tut block of its own, are marked (and so keep namespace tut). No other file changes. Probes: groups declared Zulu, Mike (2, 1), Alpha, apple come out Alpha, Mike 1, Mike 2, Zulu, apple; groups in separate blocks are marked and only put in numeric order. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 132 +++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 43 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 7ef4e14730..9fe5827432 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -15,16 +15,20 @@ comment written above the test, where that is prose; else #N. " #N" is added to a name another case of the file has (ignoring case), unless the two are in different branches - of one #if, never compiled together. Tests declared out of - TUT's numeric order are moved later in the file, never - earlier, so that declaration order, Catch2's, is TUT's. The - group's typedefs, its test_group object and a class derived - from test_group only to name it go; a class derived from it - that holds more (a constructor's body, a destructor, - members) stays, with its object, marked. namespace tut, once - nothing names tut:: and no type production code befriends as - tut:: is in it, becomes an anonymous namespace, taking in one - directly inside it. + of one #if, never compiled together. TUT ran a file's groups + in the byte order of their names, each group's tests by + number; Catch2 runs cases as declared, so a test declared + before one TUT ran ahead of it moves later in the file, + never earlier, to just after it. A move stays in its + namespace block and #if branch; where one cannot, the order + is left (each group's tests still put in numeric order where + they can be) and the file marked. The group's typedefs, its + test_group object and a class derived from test_group only + to name it go; a class derived from it that holds more (a + constructor's body, a destructor, members) stays, with its + object, marked. namespace tut, once nothing names tut:: and + no type production code befriends as tut:: is in it, becomes + an anonymous namespace, taking in one directly inside it. assertions CHECK, which lets a case go on and so reports every failure of a run; REQUIRE where going on could only crash or bury the first failure: a condition that guards what follows (a @@ -2408,47 +2412,89 @@ def remove_empty_namespaces(self): self.removed_spans.append((start, end)) def plan_order(self): - """Tests declared out of TUT's numeric order move later in the file, - never earlier, to where the tests numbered below them are: TUT ran a - group's tests by number, Catch2 runs them as declared.""" + """TUT ran a file's groups in the byte order of their names (its + runner keeps them in a std::map keyed by name) and each group's + tests by number; Catch2 runs the cases as declared. So, walking + TUT's run order, a test declared before the one TUT ran just ahead + of it moves later in the file, never earlier, to just after the + latest-declared of those placed so far. A move stays in its own + namespace block and #if branch: where one would have to leave them, + the cases' order is left as declared, but for each group's tests, + put in their numeric order where they can be, and the file is + marked, since its cases run in another order than TUT ran them.""" + # A test this host does not compile is in another #if branch: never + # declared beside these, it has no place in their order. + tests = [t for t in self.tests if not t.macro and t.body_end is not None and t.catch2_name + and not t.inactive and t.group is not None] + run = sorted(tests, key=lambda t: (t.group.name.encode("utf-8"), t.n)) + if run == sorted(tests, key=lambda t: t.start): + return + slot, stuck = self.slots(run) + if not stuck: + self.move(run, slot) + self.notes.append("tests moved into TUT's run order: groups by name, each group's tests by number") + return + first = min(stuck, key=lambda t: t.start) + groups = ", ".join(dict.fromkeys(t.group.name for t in run)) + self.check(first.start, f"TUT ran this file's groups by name ({groups}), each group's tests by number, and " + f"{first.group.name} test<{first.n}> cannot move to its place in that order across " + f"a namespace block or an #if: these cases run in another order than TUT's") + self.notes.append("the cases run in another order than TUT ran them: a move across a namespace block or " + "an #if was needed") for group in self.groups.values(): - # A test this host does not compile is in another #if branch: - # never declared beside these, it has no place in their order. - tests = [t for t in group.tests if not t.macro and t.body_end is not None and t.catch2_name - and not t.inactive] - by_position = sorted(tests, key=lambda t: t.start) - if [t.n for t in by_position] == sorted(t.n for t in by_position): + own = [t for t in run if t.group is group] + if [t.n for t in sorted(own, key=lambda t: t.start)] == [t.n for t in own]: continue - slot = {} - latest = None - for test in sorted(tests, key=lambda t: t.n): - if latest is None or test.start > latest.start: - latest = test - slot[id(test)] = latest - stuck = [t for t in tests if slot[id(t)] is not t and - (t.namespaces != slot[id(t)].namespaces or - self.branch_of(t.start) != self.branch_of(slot[id(t)].start))] + slot, stuck = self.slots(own) if stuck: for t in stuck: self.check(t.start, f"test<{t.n}> is declared out of TUT's numeric order, and cannot move to " - f"its place across an #if or a namespace") + f"its place across an #if or a namespace block") continue - assigned = {} - for test in sorted(tests, key=lambda t: t.n): - assigned.setdefault(id(slot[id(test)]), []).append(test) - chunks = {id(t): self.chunk(t) for t in tests} - for test in by_position: - members = assigned.get(id(test), []) - if members == [test]: - continue - parts = [chunks[id(m)] for m in members] - plan = Plan(chunks[id(test)][0], chunks[id(test)][1], None) - plan.render = lambda conv, plan, parts=parts: "\n".join( - conv.render(s, e, skip=conv.slot_at.get(s)) for s, e in parts) - self.slot_at[plan.start] = plan - self.plans.append(plan) + self.move(own, slot) self.notes.append(f"{group.name}: tests moved into TUT's numeric order") + def slots(self, run): + """For tests in the order TUT ran them, the test each goes just after + (itself, where it stays), and those that would leave their + namespace block or #if branch to get there.""" + slot = {} + latest = None + for test in run: + if latest is None or test.start > latest.start: + latest = test + slot[id(test)] = latest + stuck = [t for t in run if slot[id(t)] is not t and self.nesting(t.start) != self.nesting(slot[id(t)].start)] + return slot, stuck + + def move(self, run, slot): + """Plans that put each test just after its slot, in run order.""" + assigned = {} + for test in run: + assigned.setdefault(id(slot[id(test)]), []).append(test) + chunks = {id(t): self.chunk(t) for t in run} + for test in sorted(run, key=lambda t: t.start): + members = assigned.get(id(test), []) + if members == [test]: + continue + parts = [chunks[id(m)] for m in members] + plan = Plan(chunks[id(test)][0], chunks[id(test)][1], None) + plan.render = lambda conv, plan, parts=parts: "\n".join( + conv.render(s, e, skip=conv.slot_at.get(s)) for s, e in parts) + self.slot_at[plan.start] = plan + self.plans.append(plan) + + def nesting(self, offset): + """Where offset is: the brace that opens the block around it (a + namespace's, for a test), and its #if branches.""" + stack = [] + for m in re.finditer(rb"[{}]", self.src.masked[:offset]): + if m.group(0) == b"{": + stack.append(m.start()) + elif stack: + stack.pop() + return (stack[-1] if stack else None), self.branch_of(offset) + def chunk(self, test): """A test's text with the comment lines right above it, through the end of its last line.""" From 18d71edfa4a8aff09dd263faebfdce0f26ad22af Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:32:00 -0400 Subject: [PATCH 43/55] tut2catch.py writes ensure_distance as WithinDistance, in TUT's type and open interval ensure_distance became Catch2's WithinAbs, which passes a value on the border, expected plus or minus the distance, and works in double, where TUT's failed the border and worked in T, the type its template took: a conversion passed what TUT failed. It is now CHECK_THAT(a, ll_test::WithinDistance(e, d)), T the type clang resolved for the call (its last parameter's, const T&), so an explicit ensure_distance on doubles still works in float; a T the converter cannot tell, or cannot write in a macro's argument, is marked. In a macro of the file, every use must agree on T, as on any kind. ensure_approximately_equals_range keeps WithinAbs, but its actual is narrowed to F32, wrapped F32(...) where its type is not F32, as TUT's F32 parameter narrowed it. One tightening is accepted and documented: a NaN, which TUT's fabs(actual - expected) > delta let pass, fails WithinAbs. Over the stage-3 files only llframetimer_test changes: its three ensure_distance calls on F64 become WithinDistance, with altestmatchers.h for the floating-point matchers' header; it passes clang++ -fsyntax-only. Every _range call there already passes an F32. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 52 ++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 9fe5827432..0a0e765134 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -55,8 +55,12 @@ b), except where both values are LLSD (X_THAT with ll_test::LLSDEquals, the deep compare TUT's overload made) or LLURI (their asString()s, as TUT compared them); - ensure_not_equals -> X(a != b); ensure_distance and - ensure_approximately_equals_range -> WithinAbs; + ensure_not_equals -> X(a != b); ensure_distance -> + ll_test::WithinDistance, which, as TUT's did, fails a + value on the border and works in T, the type TUT's + template took; ensure_approximately_equals_range -> + WithinAbs, the actual narrowed to F32 as TUT's parameter + narrowed it (a NaN, which TUT's let pass, fails it); ensure_approximately_equals -> ll_test::WithinBits, at the precision of the overload TUT chose; ensure_contains, _does_not_contain, _starts_with, _ends_with -> the string @@ -706,9 +710,9 @@ def precedence(node): def headers_for(name, kind): """What a converted call needs included, beyond the test macros.""" - if kind == "equals-llsd" or name in ("ensure_approximately_equals", "ensure_memory_matches"): + if kind == "equals-llsd" or name in ("ensure_approximately_equals", "ensure_memory_matches", "ensure_distance"): return {'"altestmatchers.h"'} - if name in ("ensure_distance", "ensure_approximately_equals_range"): + if name == "ensure_approximately_equals_range": return {""} if name in ("ensure_contains", "ensure_does_not_contain", "ensure_starts_with", "ensure_ends_with"): return {""} @@ -1284,8 +1288,24 @@ def conversion_kind(self, record): return "equals" if name == "ensure_approximately_equals": return "bits-" + (self.parameter_float(record) or "?") + if name == "ensure_distance": + return "distance-" + (self.distance_type(record) or "?") return name + def distance_type(self, record): + """The type TUT's ensure_distance worked in, as clang resolved it: + that of its last parameter, const T&.""" + callee = record["callee"] or {} + qual = callee.get("referencedDecl", {}).get("type", {}).get("qualType", "") + m = re.match(r"^void \((.*)\)$", qual) + if not m: + return None + last = split_template_args(m.group(1))[-1] + t = re.sub(r"\s*&$", "", last.strip()) + t = re.sub(r"^const\s+|\s+const$", "", t).replace("(anonymous namespace)::", "").strip() + # A comma would split the macro's arguments. + return t if t and "," not in t else None + def parameter_float(self, record): callee = record["callee"] or {} qual = callee.get("referencedDecl", {}).get("type", {}).get("qualType", "") @@ -1679,6 +1699,9 @@ def convert_call(self, record, kind): if kind == "bits-?": self.check(start, "cannot tell which ensure_approximately_equals overload was called") return + if kind == "distance-?": + self.check(start, "cannot tell, or cannot write in a macro's argument, the type ensure_distance worked in") + return build = self.builder(name, kind, record, ranges, self.strength(record, value_args(name, args))) if build is not None: self.headers.update(headers_for(name, kind)) @@ -1803,7 +1826,15 @@ def build(): actual = self.operand(ranges, a, args[a], 1, guard=True) expected = self.operand(ranges, a + 1, args[a + 1], 1) margin = self.operand(ranges, a + 2, args[a + 2], 1) - return msg + [f"{req}_THAT({actual},{self.separator(ranges, a)}Catch::Matchers::WithinAbs(" + if name == "ensure_distance": + # TUT's: strictly inside, worked in its own type. + matcher = f"ll_test::WithinDistance<{kind.split('-', 1)[1]}>(" + else: + # TUT's parameters were F32: the actual was narrowed. + if value_type(skip_implicit_casts(args[a])) not in ("float", "F32"): + actual = f"F32({actual})" + matcher = "Catch::Matchers::WithinAbs(" + return msg + [f"{req}_THAT({actual},{self.separator(ranges, a)}{matcher}" f"{expected},{self.separator(ranges, a + 1)}{margin}))"] return build if name == "ensure_approximately_equals": @@ -1862,7 +1893,7 @@ def convert_in_macro(self, offset, records): self.check(offset, f"{name}() in a macro whose uses differ, or are not statements in a block") return kind = kinds.pop() - if kind in ("dependent", "bits-?") or name in TUT_MEMBERS or name == "ensure_errno": + if kind in ("dependent", "bits-?", "distance-?") or name in TUT_MEMBERS or name == "ensure_errno": self.check(offset, f"{name}() in a macro, as {kind}") return parts = self.text_call(offset) @@ -2888,12 +2919,17 @@ def message(t): "ensure_starts_with": "Catch::Matchers::StartsWith", "ensure_ends_with": "Catch::Matchers::EndsWith"}[name] return message(texts[0]) + [f"REQUIRE_THAT({operand(texts[1])}, {matcher}({texts[2]}))"] - if name in ("ensure_distance", "ensure_approximately_equals_range"): + if name == "ensure_distance" and kind.startswith("distance-"): if n not in (3, 4): return None msg = message(texts[0]) if n == 4 else [] a, e, d = texts[-3:] - return msg + [f"REQUIRE_THAT({operand(a)}, Catch::Matchers::WithinAbs({e}, {d}))"] + return msg + [f"REQUIRE_THAT({operand(a)}, ll_test::WithinDistance<{kind[9:]}>({e}, {d}))"] + if name == "ensure_approximately_equals_range": + if n != 4: + return None + a, e, d = texts[-3:] + return message(texts[0]) + [f"REQUIRE_THAT(F32({a}), Catch::Matchers::WithinAbs({e}, {d}))"] if name == "ensure_approximately_equals" and kind in ("bits-F32", "bits-F64"): if n not in (3, 4): return None From 5cf16c3761c9cb9b10cfe479435686f674ae989b Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:32:31 -0400 Subject: [PATCH 44/55] tut2catch.py compares two string literals strcmp compared as a string_view strcmp(a, b) == 0 becomes a == b with each side that is not a literal made a std::string_view, so that the compare is of the text; where both were literals neither was, and the result compared two arrays' addresses, which clang rejects (-Wstring-compare is an error here) and which would be wrong besides. The first is a string_view now. Met by a probe while checking the macro fixes; no test in the tree compares two literals so. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 0a0e765134..ba838aebcc 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -1596,6 +1596,9 @@ def string_views(self, args, op, record): for arg, span in zip(args, spans): text = self.render(*span) texts.append(text if skip_implicit(arg).get("kind") == "StringLiteral" else f"std::string_view({text})") + # Two literals compared as they are would be two arrays' addresses. + if not any(t.startswith("std::string_view(") for t in texts): + texts[0] = f"std::string_view({texts[0]})" if not texts[0].startswith("std::string_view("): texts.reverse() self.headers.add("") From 3ba8f47d36c2351f904333c4cdbb0fe4515f6259 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:41:44 -0400 Subject: [PATCH 45/55] tut2catch.py's mapping holds each file's TUT hash and the assertions outside its tests The mapping was a bare list of tests, each with the TUT assertion calls in its body and the Catch2 assertions now in it: what a file's helpers, fixtures and namespace-scope lambdas asserted was in no count, so a conversion could drop one there unseen, and nothing tied a mapping to the TUT source it was made from. It is now {"tests": [...], "files": [...]}: the tests as before, and a record a file with its tut_sha256 (of the bytes the converter read), tut_outside_calls (the TUT assertion calls outside every test's body, counted as in the bodies: in a branch this host compiles or not, never under #if 0) and catch2_outside_expected (the Catch2 assertions those became, a split && chain a term each). A file the converter could not read, or skipped, has none. Over the stage-3 files the converted output and the tests' entries are unchanged; 37 files assert outside their tests, and a recount of each converted file outside its TEST_CASE bodies finds the expected number in all but three, each short by exactly the calls a TM-CHECK left unconverted there (llprocess_test 2, llsd_test 2, llsdserialize_test 1). Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index ba838aebcc..c734559546 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -92,9 +92,13 @@ /* TM-CHECK: */ inside a macro), and it exits 1 listing every mark; 2 if a file could not be read at all, or the converter itself failed on one (its traceback is that file's error, and the other files go on). It never -drops a test or an assertion: --mapping writes one entry per test, with the -TUT-side assertion calls in its body and the Catch2 assertions now in it, -which scripts/testing/tut_parity.py checks. +drops a test or an assertion: --mapping writes, as {"tests": [...], +"files": [...]}, one entry per test, with the TUT-side assertion calls in +its body and the Catch2 assertions now in it, and one per file, with the +sha256 of the TUT source it read and the TUT assertion calls outside every +test's body, in code not under #if 0 (tut_outside_calls), with the Catch2 +assertions they became (catch2_outside_expected), all of which +scripts/testing/tut_parity.py checks. Needs the build tree's compile_commands.json (CMake writes it; the presets turn it on) and the compiler it names, which must be clang: the AST comes @@ -112,6 +116,7 @@ import argparse import bisect import concurrent.futures +import hashlib import json import os import re @@ -2623,14 +2628,21 @@ def finish(self): return self.render(0, len(self.src.data)) def counts(self, converted): - """Per test: the TUT assertion calls in its body before, and the Catch2 - assertions in it after.""" + """Per test: the TUT assertion calls in its body before, the Catch2 + assertions in it after, and the Catch2 assertions those calls became; + and for the file, the TUT assertion calls outside every test's body + (a helper's, a fixture's, a lambda's at namespace scope) and the + Catch2 assertions those became. Calls are counted as in the bodies: + in a branch this host compiles or not, never under #if 0 or in a + comment, so a recount of the converted file finds as many.""" tut = {} expected = {} + inside = set() for test in self.tests: if test.body_start is None or test.body_end is None: continue calls = [o for o in self.counted if test.body_start <= o < test.body_end] + inside.update(calls) tut[id(test)] = len(calls) # A split && chain is more than one assertion for one TUT call. expected[id(test)] = sum(self.emitted.get(o, 1) for o in calls) @@ -2638,7 +2650,8 @@ def counts(self, converted): catch = {id(t): n for t, n in count_catch_assertions(data, [ (t, getattr(t, "header_text", f"TEST_CASE_METHOD({t.fixture}, {t.catch2_literal},")) for t in self.tests if t.catch2_name and not t.macro])} - return tut, catch, expected + outside = [o for o in self.counted if o not in inside] + return tut, catch, expected, len(outside), sum(self.emitted.get(o, 1) for o in outside) def count_catch_assertions(data, cases): @@ -3030,7 +3043,7 @@ def convert_file(path, build_dir, config, dry_run, output_dir=None, names=None): if conv.can_unwrap(converted): conv.plan_unwrap() converted = conv.finish() - tut_counts, catch_counts, expected_counts = conv.counts(converted) + tut_counts, catch_counts, expected_counts, tut_outside, catch_outside = conv.counts(converted) if output_dir: out = os.path.join(output_dir, os.path.relpath(path, ROOT)) os.makedirs(os.path.dirname(out), exist_ok=True) @@ -3056,7 +3069,10 @@ def convert_file(path, build_dir, config, dry_run, output_dir=None, names=None): }) checks = [{"file": os.path.relpath(path, ROOT), "line": source.line_of(o), "why": w} for o, w in sorted(conv.checks)] - return {"file": os.path.relpath(path, ROOT), "tests": tests, "checks": checks, "notes": conv.notes, + totals = {"file": os.path.relpath(path, ROOT), "tut_sha256": hashlib.sha256(source.data).hexdigest(), + "tut_outside_calls": tut_outside, "catch2_outside_expected": catch_outside} + return {"file": os.path.relpath(path, ROOT), "tests": tests, "totals": totals, "checks": checks, + "notes": conv.notes, "groups": sorted(g.name for g in conv.groups.values())} @@ -3110,6 +3126,7 @@ def main(): results = [convert_one(w) for w in work] mapping = [] + totals = [] total_checks = 0 errors = 0 for r in results: @@ -3122,6 +3139,7 @@ def main(): print(f"{r['file']}: {r['skipped']}") continue mapping.extend(r["tests"]) + totals.append(r["totals"]) total_checks += len(r["checks"]) if not args.quiet: converted = sum(1 for t in r["tests"] if t["catch2_name"]) @@ -3133,7 +3151,7 @@ def main(): print(f"{c['file']}:{c['line']}: TM-CHECK: {c['why']}") if args.mapping: with open(args.mapping, "w") as f: - json.dump(mapping, f, indent=1) + json.dump({"tests": mapping, "files": totals}, f, indent=1) print(f"{len(results)} file(s), {len(mapping)} test(s), {total_checks} TM-CHECK(s), {errors} error(s)") return 2 if errors else (1 if total_checks else 0) From 34b9eac4b7f1e20532fea014b9e00df18332458e Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 20:58:55 -0400 Subject: [PATCH 46/55] tut2catch.py ends a case at a chain's term only where a later term leans on it for safety The chain rule made a term of a split && chain REQUIRE wherever a later term named a variable it named, so v.mV[VX] == 0.f && v.mV[VY] == 0.f stopped at the first component, as TUT did, where reporting each failure is what CHECK is for: 108 assertions in stage 3 and 585 in llmath became REQUIRE for no crash or garbage they could prevent. An earlier term ends the case now only where a later one leans on what it read for its own safety: dereferences it (unary *, ->, a class's operator* or operator->), uses it as a subscript or a pointer's offset, or subscripts through it where it is a pointer, an iterator, an optional or a smart pointer. A value's member read with a dot, a constant subscript of a value's array and a call do not; constants of arithmetic type still count for nothing. Probed: idx >= 0 && idx < 3 && arr[idx] == 2 keeps REQUIRE, REQUIRE, CHECK, as do p[0] && p[1] through a pointer, it->first && it->second, o.value() && *o, sp->x && sp->y; v.mV[VX] && v.mV[VY] && v.mV[VZ], vec.front() && vec[1] and two const calls are CHECKs. Over the stage-3 files 107 of the rule's 108 REQUIREs go back to CHECK and nothing else changes. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 72 ++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index c734559546..56b8bc5945 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -40,12 +40,14 @@ function returning bool, ensure(m.setInverse(n)), or a call returning bool that fills an argument passed by reference or address), where the test reads that object or argument - after it; a term of a split && chain that a later term of it - reads a variable of (TUT's ensure stopped the test at the - first term that failed); in a loop or a lambda; and in a - helper outside a test's body. A plain compare of values - stays CHECK. In a destructor, where a throw would end the - process, CHECK always. With that macro (X below): + after it; a term of a split && chain whose variable a later + term of it leans on for its safety, dereferencing it or + indexing by it (TUT's ensure stopped the test at the first + term that failed; v.mV[VX] == 0 && v.mV[VY] == 0 stays two + CHECKs); in a loop or a lambda; and in a helper outside a + test's body. A plain compare of values stays CHECK. In a + destructor, where a throw would end the process, CHECK + always. With that macro (X below): ensure(c) -> X(c); ensure(a && b && ...) -> X(a); X(b); ... one assertion a term, so that each prints its operands; ensure(msg, c) -> UNSCOPED_INFO(msg) then X(c), or, where c @@ -1771,15 +1773,16 @@ def streamed(i): if len(terms) > 1 and any(self.call_span(t, record) is None for t in terms): terms = [condition] - # TUT's ensure stopped the test at the first term that failed: - # one a later term reads the same variable as ends the case. + # TUT's ensure stopped the test at the first term that failed; + # a term ends the case where a later one leans on what it read + # for its own safety (dereferences it, indexes by it), so that + # going on past its failure could crash or read garbage. chained = [False] * len(terms) if len(terms) > 1 and not self.in_destructor(record): later = set() for i in range(len(terms) - 1, -1, -1): - reads = variables(terms[i], constants=False) - chained[i] = bool(reads & later) - later |= reads + chained[i] = bool(variables(terms[i], constants=False) & later) + later |= leaned_on(terms[i]) def build(): out = [] @@ -2764,6 +2767,53 @@ def variables(node, constants=True): return found +# What a subscript through a class reads as a pointer would: a handle to +# something else, not a value of its own. +HANDLES = re.compile(r"iterator|__wrap_iter|optional|unique_ptr|shared_ptr|weak_ptr|LLPointer|_ptr\b|Ptr\b") + + +def leaned_on(node): + """The variables and fields a condition leans on for its own safety: + those it dereferences (unary *, ->, a class's operator* or + operator->), uses as a subscript or a pointer's offset, or subscripts + through where they are a pointer, an iterator, an optional or a smart + pointer. A value's member read with a dot, a constant subscript of a + value's array (v.mV[VX]) and a call are no such reliance. Constants of + arithmetic type are left out, as no state.""" + found = set() + stack = [node] + while stack: + sub = stack.pop() + kind = sub.get("kind") + if kind == "CXXDefaultArgExpr": + continue + inner = [c for c in sub.get("inner", []) if isinstance(c, dict) and "kind" in c] + if kind == "UnaryOperator" and sub.get("opcode") == "*" and inner: + found |= variables(inner[0], constants=False) + elif kind == "MemberExpr" and sub.get("isArrow") and inner: + found |= variables(inner[0], constants=False) + elif kind == "CXXOperatorCallExpr" and inner: + name = skip_implicit(inner[0]).get("referencedDecl", {}).get("name") + if name in ("operator*", "operator->") and len(inner) == 2: + found |= variables(inner[1], constants=False) + elif name == "operator[]" and len(inner) == 3: + found |= variables(inner[2], constants=False) + if HANDLES.search(value_type(skip_implicit(inner[1]))): + found |= variables(inner[1], constants=False) + elif kind == "ArraySubscriptExpr" and len(inner) == 2: + found |= variables(inner[1], constants=False) + # An array decays to the pointer subscripted; a pointer is one. + if "[" not in value_type(skip_implicit_casts(inner[0])): + found |= variables(inner[0], constants=False) + elif kind in ("BinaryOperator", "CompoundAssignOperator") and sub.get("opcode") in ("+", "-", "+=", "-=") \ + and len(inner) == 2: + for offset, other in ((inner[0], inner[1]), (inner[1], inner[0])): + if value_type(other).endswith("*") and not value_type(offset).endswith("*"): + found |= variables(offset, constants=False) + stack.extend(inner) + return found + + def const_object(base, arrow): """Whether the object a member function is called on is const there, as clang typed it: a const member function called on an object that is not From d434b890868109fe7304bc7c20880f5be34df31e Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 21:03:10 -0400 Subject: [PATCH 47/55] tut2catch.py ends a case at a call that makes state only where that state is a bare local A call returning bool to a non-const member function, or filling an argument, made its assertion REQUIRE wherever the test read the object or argument again: timer.isExpired(...), store.write(...), mMutex.isLocked() and the like, 325 in stage 3, though nothing they touch is garbage when they fail. What REQUIRE is for is state left indeterminate: the call's object, or the argument it fills, must now be a local of the test declared without an initializer, and written by nothing between its declaration and the call (an assignment, an increment, a non-const member call, a call that fills it), that the test reads again after the assertion: LLMatrix4a far_inv; ensure(far_inv.setInverse(m)), or S32 value; ensure(convertToS32(str, value)), the first time. Its root is taken through x.m, x[i] of an array and &x; a local initialised or written since (LLMatrix4a untouched; untouched.setIdentity()), a member, a parameter, what a pointer points at and a container's element do not count. Without an initializer means no init in clang's AST, or a class's default constructor called with no argument written. Whether that constructor sets the members cannot be told: the JSON of the call names no triviality, the class is a header's the converter does not parse, and an empty user-provided one, as LLVector4a's, is non-trivial yet leaves them indeterminate, so any class-typed local so declared counts, LLSD, LLPermissions and std::string among them. Probed: a bare S32, a bare array's element, a bare S32 by address and a bare LLVector3 keep REQUIRE, as does the first fill of a bare S32; an initialised S32, an initialised LLVector3, a bare one set since, a second fill, an assigned one and a fixture's member are CHECKs. Over the stage-3 files 261 of the rule's 325 REQUIREs go back to CHECK and nothing else changes. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 126 +++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 13 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 56b8bc5945..f3a6a248a7 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -37,10 +37,12 @@ position compared with end() or npos; a size, an emptiness, a presence: has(), has_value(), found() and the like); a condition that calls what makes it (a non-const member - function returning bool, ensure(m.setInverse(n)), or a call - returning bool that fills an argument passed by reference or - address), where the test reads that object or argument - after it; a term of a split && chain whose variable a later + function returning bool, or a call returning bool that fills + an argument passed by reference or address), where that + object or argument is a local declared without an + initializer, written by nothing since, that the test reads + after it (LLMatrix4a m; ensure(m.setInverse(n))), so + indeterminate had the call failed; a term of a split && chain whose variable a later term of it leans on for its safety, dereferencing it or indexing by it (TUT's ensure stopped the test at the first term that failed; v.mV[VX] == 0 && v.mV[VY] == 0 stays two @@ -969,6 +971,8 @@ def __init__(self, source, items, tut_ids, active_lines, demangled): self.friend_names = set() # tut:: types production code befriends self.tut_namespaces = [] # NamespaceDecl nodes named tut, with their parents self.body_reads = {} # id of a test's body -> (offset, declaration) of what it reads + self.body_bare = {} # id of a test's body -> the variables it declares without an initializer + self.body_writes = {} # id of a test's body -> (offset, variable) of what it writes source.find_conditionals(active_lines) # ---- helpers ------------------------------------------------------- @@ -1420,17 +1424,22 @@ def guards(node): return False def prepares(self, node, record): - """Whether a condition calls what makes the state the test reads - after it: a call returning bool, to a non-const member function of - an object, or with an argument it may fill (one passed by non-const - reference, or by address), where the test's body reads that object - or argument again after the assertion. A false answer is that what - it goes on to read was never made, as with - ensure(inverse.setInverse(m)) before inverse is used.""" + """Whether a condition calls what makes state the test reads after + it, state that is indeterminate where the call failed: a call + returning bool, to a non-const member function of an object, or + with an argument it may fill (one passed by non-const reference, or + by address), where that object or argument is a local of the test + declared without an initializer and written by nothing since, and + its body reads it again after the assertion, as with LLMatrix4a + inverse; ensure(inverse.setInverse(m)), or S32 value; + ensure(convertToS32(s, value)). A local initialised or written + before (LLMatrix4a m; m.setIdentity()), a member or a parameter + holds a value whatever the call did.""" test = self.test_containing(record["range"][0]) body = test_body(test) if test else None if body is None: return False + bare = self.bare_locals(body) made = set() stack = [node] while stack: @@ -1445,16 +1454,69 @@ def prepares(self, node, record): callee.get("name") != "operator bool": base = [c for c in callee.get("inner", []) if "kind" in c] if base and not const_object(base[0], callee.get("isArrow")): - made |= variables(base[0]) + made.add(written_root(base[0])) for arg in inner[1:]: if fills(arg): - made |= variables(arg) + made.add(written_root(arg)) stack.extend(inner) + # Bare, and not written between its declaration and this call. + at = record["range"][0] + made = {d for d in made if d in bare and + not any(bare[d] < where < at and decl == d for where, decl in self.writes(body))} if not made: return False after = record["range"][1] return any(at >= after and decl in made for at, decl in self.reads(body)) + def bare_locals(self, body): + """The variables a test's body declares without an initializer, each + with where its declaration ends, once per body.""" + cache = self.body_bare + if id(body) not in cache: + found = {} + for n in iter_nodes(body): + if n.get("kind") == "VarDecl" and declared_bare(n): + rng = self.node_range(n) + if rng: + found[n.get("id")] = rng[1] + cache[id(body)] = found + return cache[id(body)] + + def writes(self, body): + """(offset, variable) of every write a test's body makes to a + variable of its own, as written_root() finds it: an assignment, an + increment, a non-const member call on it, a call it is filled by. + Once per body.""" + cache = self.body_writes + if id(body) not in cache: + found = [] + for n in iter_nodes(body): + kind = n.get("kind") + inner = [c for c in n.get("inner", []) if isinstance(c, dict) and "kind" in c] + targets = [] + if kind in ("BinaryOperator", "CompoundAssignOperator") and n.get("opcode", "").endswith("=") \ + and n.get("opcode") not in ("==", "!=", "<=", ">=") and inner: + targets.append(inner[0]) + elif kind == "UnaryOperator" and n.get("opcode") in ("++", "--") and inner: + targets.append(inner[0]) + elif kind == "CXXOperatorCallExpr" and len(inner) >= 2: + name = skip_implicit(inner[0]).get("referencedDecl", {}).get("name", "") + if re.fullmatch(r"operator(?:[-+*/%^&|]|<<|>>)?=|operator(?:\+\+|--)", name): + targets.append(inner[1]) + elif kind == "CXXMemberCallExpr" and inner and inner[0].get("kind") == "MemberExpr": + base = [c for c in inner[0].get("inner", []) if "kind" in c] + if base and not const_object(base[0], inner[0].get("isArrow")): + targets.append(base[0]) + if kind in ("CallExpr", "CXXMemberCallExpr", "CXXConstructExpr"): + targets += [a for a in (inner if kind == "CXXConstructExpr" else inner[1:]) if fills(a)] + for target in targets: + decl = written_root(target) + where = self.loc(n.get("range", {}).get("begin")).in_text() + if decl and where is not None: + found.append((where, decl)) + cache[id(body)] = found + return cache[id(body)] + def reads(self, body): """(offset, declaration) of every variable and field a test's body names, once per body.""" @@ -2814,6 +2876,44 @@ def leaned_on(node): return found +def declared_bare(decl): + """Whether a variable is declared without an initializer: none at all, + or, for a class, its default constructor called with nothing written. + Whether that constructor sets the members is not in the AST read here + (the class is a header's; its JSON says nothing of it), and an empty + one, as LLVector4a's, leaves them indeterminate: a class counts, set + or not.""" + if "init" not in decl: + return True + if decl.get("init") != "call": + return False + inner = [c for c in decl.get("inner", []) if isinstance(c, dict) and "kind" in c] + if len(inner) != 1 or inner[0].get("kind") != "CXXConstructExpr" or inner[0].get("list"): + return False + return all(a.get("kind") == "CXXDefaultArgExpr" for a in inner[0].get("inner", []) if "kind" in a) + + +def written_root(node): + """The variable an expression a call writes through is part of: x of + x, x.m, x[i] where x is an array, &x; None where it is no variable's + own (what a pointer points at, a container's element).""" + while True: + kind = node.get("kind") + inner = [c for c in node.get("inner", []) if isinstance(c, dict) and "kind" in c] + if kind == "DeclRefExpr": + ref = node.get("referencedDecl", {}) + return ref.get("id") if ref.get("kind") == "VarDecl" else None + if not inner: + return None + if kind in ("ImplicitCastExpr", "ParenExpr", "MaterializeTemporaryExpr", "CXXBindTemporaryExpr") or \ + (kind == "MemberExpr" and not node.get("isArrow")) or \ + (kind == "UnaryOperator" and node.get("opcode") == "&") or \ + (kind == "ArraySubscriptExpr" and "[" in value_type(skip_implicit_casts(inner[0]))): + node = skip_implicit_casts(inner[0]) if kind == "ArraySubscriptExpr" else inner[0] + continue + return None + + def const_object(base, arrow): """Whether the object a member function is called on is const there, as clang typed it: a const member function called on an object that is not From 3c3ea5f7ff1e3274e7b25463f05570696dfaf6c5 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 21:13:45 -0400 Subject: [PATCH 48/55] tut2catch.py moves a test into a later block of its namespace, where TUT's order needs it A move in TUT's run order had to stay in its own namespace block, so a file whose groups each sit in a namespace tut { } of their own could not be reordered at all, and was marked: commonmisc_test, io_test and llmath's mathmisc_test among them. Every block of a namespace is one scope, though, and a later block has declared all an earlier one did, so a test moved later into it still finds everything it names. A move now stays in its namespace, by the names of the namespaces around it, whatever block of it it lands in; never into another namespace, into or out of a nested scope, or across an #if branch. Braces under #if 0, whose code need not be C++ (commonmisc's holds a quoted record full of them), no longer count. A test moved away takes the blank line after it along, so a block it leaves is not left a run of blank lines. Over the stage-3 files, commonmisc_test and io_test now declare their cases in TUT's order exactly, without the mark, as llpose_test and llsdserialize_test do, and all four pass clang++ -fsyntax-only. In the ten other files where tests moved, llpose_test and llsdserialize_test among them, blank lines go and nothing else. A probe with groups in three tut blocks, an #if 0 and another namespace between them, comes out in TUT's order and compiles. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 70 +++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index f3a6a248a7..f34d14b96b 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -20,9 +20,10 @@ number; Catch2 runs cases as declared, so a test declared before one TUT ran ahead of it moves later in the file, never earlier, to just after it. A move stays in its - namespace block and #if branch; where one cannot, the order - is left (each group's tests still put in numeric order where - they can be) and the file marked. The group's typedefs, its + namespace (a later block of it is the same scope) and #if + branch; where one cannot, the order is left (each group's + tests still put in numeric order where they can be) and the + file marked. The group's typedefs, its test_group object and a class derived from test_group only to name it go; a class derived from it that holds more (a constructor's body, a destructor, members) stays, with its @@ -973,6 +974,7 @@ def __init__(self, source, items, tut_ids, active_lines, demangled): self.body_reads = {} # id of a test's body -> (offset, declaration) of what it reads self.body_bare = {} # id of a test's body -> the variables it declares without an initializer self.body_writes = {} # id of a test's body -> (offset, variable) of what it writes + self.live = None # the masked text with code under #if 0 blanked too source.find_conditionals(active_lines) # ---- helpers ------------------------------------------------------- @@ -2522,10 +2524,11 @@ def plan_order(self): TUT's run order, a test declared before the one TUT ran just ahead of it moves later in the file, never earlier, to just after the latest-declared of those placed so far. A move stays in its own - namespace block and #if branch: where one would have to leave them, - the cases' order is left as declared, but for each group's tests, - put in their numeric order where they can be, and the file is - marked, since its cases run in another order than TUT ran them.""" + namespace, though it may pass into a later block of it, and in its + #if branch: where one would have to leave them, the cases' order is + left as declared, but for each group's tests, put in their numeric + order where they can be, and the file is marked, since its cases + run in another order than TUT ran them.""" # A test this host does not compile is in another #if branch: never # declared beside these, it has no place in their order. tests = [t for t in self.tests if not t.macro and t.body_end is not None and t.catch2_name @@ -2541,10 +2544,10 @@ def plan_order(self): first = min(stuck, key=lambda t: t.start) groups = ", ".join(dict.fromkeys(t.group.name for t in run)) self.check(first.start, f"TUT ran this file's groups by name ({groups}), each group's tests by number, and " - f"{first.group.name} test<{first.n}> cannot move to its place in that order across " - f"a namespace block or an #if: these cases run in another order than TUT's") - self.notes.append("the cases run in another order than TUT ran them: a move across a namespace block or " - "an #if was needed") + f"{first.group.name} test<{first.n}> cannot move to its place in that order out " + f"of its namespace or #if: these cases run in another order than TUT's") + self.notes.append("the cases run in another order than TUT ran them: a move out of a namespace or an #if " + "was needed") for group in self.groups.values(): own = [t for t in run if t.group is group] if [t.n for t in sorted(own, key=lambda t: t.start)] == [t.n for t in own]: @@ -2553,7 +2556,7 @@ def plan_order(self): if stuck: for t in stuck: self.check(t.start, f"test<{t.n}> is declared out of TUT's numeric order, and cannot move to " - f"its place across an #if or a namespace block") + f"its place out of its namespace or #if") continue self.move(own, slot) self.notes.append(f"{group.name}: tests moved into TUT's numeric order") @@ -2561,7 +2564,7 @@ def plan_order(self): def slots(self, run): """For tests in the order TUT ran them, the test each goes just after (itself, where it stays), and those that would leave their - namespace block or #if branch to get there.""" + namespace or #if branch to get there.""" slot = {} latest = None for test in run: @@ -2582,22 +2585,47 @@ def move(self, run, slot): if members == [test]: continue parts = [chunks[id(m)] for m in members] - plan = Plan(chunks[id(test)][0], chunks[id(test)][1], None) + start, end = chunks[id(test)] + if not members: + # A test moved away takes the blank line after it along, + # where nothing else is planned there. + after = self.src.line_end(end) + if end < len(self.src.data) and self.src.data[end:after].strip() == b"" and \ + not any(end <= p.start <= after or (p.start < end < p.end) for p in self.plans): + end = min(after + 1, len(self.src.data)) + plan = Plan(start, end, None) plan.render = lambda conv, plan, parts=parts: "\n".join( conv.render(s, e, skip=conv.slot_at.get(s)) for s, e in parts) self.slot_at[plan.start] = plan self.plans.append(plan) def nesting(self, offset): - """Where offset is: the brace that opens the block around it (a - namespace's, for a test), and its #if branches.""" - stack = [] - for m in re.finditer(rb"[{}]", self.src.masked[:offset]): + """Where offset is, for a move: the namespaces around it, by name, + where it is directly in one, since every block of a namespace is + one scope and a later block of it has declared all an earlier one + did; else the brace of the block it is in; and its #if branches.""" + if self.live is None: + # Code under #if 0 need not even be C++: none of its braces count. + live = bytearray(self.src.masked) + for start, end, what in self.src.regions: + if what == "dead": + live[start:end] = re.sub(rb"[^\n]", b" ", bytes(live[start:end])) + self.live = bytes(live) + stack = [] # (where a { is, the namespace it opens or None) + last = 0 + for m in re.finditer(rb"[{};]", self.live[:offset]): if m.group(0) == b"{": - stack.append(m.start()) - elif stack: + head = b" ".join(line for line in self.live[last:m.start()].split(b"\n") + if not line.strip().startswith(b"#")) + name = re.fullmatch(rb"\s*(?:inline\s+)?namespace\s*([\w:]*)\s*", head) + stack.append((m.start(), name.group(1).decode() if name else None)) + elif m.group(0) == b"}" and stack: stack.pop() - return (stack[-1] if stack else None), self.branch_of(offset) + last = m.end() + if any(name is None for _, name in stack): + return ("block", stack[-1][0]), self.branch_of(offset) + path = tuple(part for _, name in stack for part in (name.split("::") if name else [""])) + return ("namespace", path), self.branch_of(offset) def chunk(self, test): """A test's text with the comment lines right above it, through the end From b5b244c8316f8a6e8807df6f79da69b3e9b9362d Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 21:18:19 -0400 Subject: [PATCH 49/55] tut2catch.py narrows ensure_approximately_equals_range's expected and delta to F32 too TUT's ensure_approximately_equals_range takes F32 actual, expected and delta; the conversion narrowed only the actual, so a double expected or delta was compared in double where TUT had rounded it to float first, and a value on the edge could pass one and fail the other. Each of the three is wrapped F32(...) now where its type is not F32 already, and in a macro's text all three are. Probed with a double expected, a double delta and int ones: each is wrapped, an all-F32 call is left as it was, and both probe files pass clang++ -fsyntax-only. No call in the stage-3 files passes anything but F32s, and none changes. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index f34d14b96b..f7356ddde3 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -64,8 +64,9 @@ ll_test::WithinDistance, which, as TUT's did, fails a value on the border and works in T, the type TUT's template took; ensure_approximately_equals_range -> - WithinAbs, the actual narrowed to F32 as TUT's parameter - narrowed it (a NaN, which TUT's let pass, fails it); + WithinAbs, the actual, the expected and the delta narrowed + to F32 where they are not, as TUT's parameters narrowed them + (a NaN, which TUT's let pass, fails it); ensure_approximately_equals -> ll_test::WithinBits, at the precision of the overload TUT chose; ensure_contains, _does_not_contain, _starts_with, _ends_with -> the string @@ -1905,9 +1906,10 @@ def build(): # TUT's: strictly inside, worked in its own type. matcher = f"ll_test::WithinDistance<{kind.split('-', 1)[1]}>(" else: - # TUT's parameters were F32: the actual was narrowed. - if value_type(skip_implicit_casts(args[a])) not in ("float", "F32"): - actual = f"F32({actual})" + # TUT's parameters were F32: all three were narrowed. + narrow = [value_type(skip_implicit_casts(args[a + i])) not in ("float", "F32") for i in range(3)] + actual, expected, margin = (f"F32({t})" if cast else t + for t, cast in zip((actual, expected, margin), narrow)) matcher = "Catch::Matchers::WithinAbs(" return msg + [f"{req}_THAT({actual},{self.separator(ranges, a)}{matcher}" f"{expected},{self.separator(ranges, a + 1)}{margin}))"] @@ -3123,7 +3125,7 @@ def message(t): if n != 4: return None a, e, d = texts[-3:] - return message(texts[0]) + [f"REQUIRE_THAT(F32({a}), Catch::Matchers::WithinAbs({e}, {d}))"] + return message(texts[0]) + [f"REQUIRE_THAT(F32({a}), Catch::Matchers::WithinAbs(F32({e}), F32({d})))"] if name == "ensure_approximately_equals" and kind in ("bits-F32", "bits-F64"): if n not in (3, 4): return None From 054acabe1d48c47d25311fbcaefb2dd3bceb55ae Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 21:22:23 -0400 Subject: [PATCH 50/55] tut2catch.py's mapping holds the sha256 of each Catch2 file it wrote A file's record held the hash of the TUT source the converter read but nothing of what it wrote, so a converted file edited since, by hand or by another run, could not be told from the conversion the mapping describes. Each record now has catch2_sha256 too: the hash of the Catch2 text the converter wrote, in place or under --output-dir, or would have written under --dry-run. Over the stage-3 files, written under --output-dir, all 103 hashes are those of the files written, and nothing else in the mapping or the output changes. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index f7356ddde3..8a4a2f0609 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -101,10 +101,12 @@ drops a test or an assertion: --mapping writes, as {"tests": [...], "files": [...]}, one entry per test, with the TUT-side assertion calls in its body and the Catch2 assertions now in it, and one per file, with the -sha256 of the TUT source it read and the TUT assertion calls outside every -test's body, in code not under #if 0 (tut_outside_calls), with the Catch2 -assertions they became (catch2_outside_expected), all of which -scripts/testing/tut_parity.py checks. +sha256 of the TUT source it read (tut_sha256) and of the Catch2 text it +wrote, or would have under --dry-run (catch2_sha256), so that a file edited +since shows, and the TUT assertion calls outside every test's body, in +code not under #if 0 (tut_outside_calls), with the Catch2 assertions they +became (catch2_outside_expected), all of which scripts/testing/tut_parity.py +checks. Needs the build tree's compile_commands.json (CMake writes it; the presets turn it on) and the compiler it names, which must be clang: the AST comes @@ -3250,6 +3252,7 @@ def convert_file(path, build_dir, config, dry_run, output_dir=None, names=None): checks = [{"file": os.path.relpath(path, ROOT), "line": source.line_of(o), "why": w} for o, w in sorted(conv.checks)] totals = {"file": os.path.relpath(path, ROOT), "tut_sha256": hashlib.sha256(source.data).hexdigest(), + "catch2_sha256": hashlib.sha256(converted.encode("utf-8", "surrogateescape")).hexdigest(), "tut_outside_calls": tut_outside, "catch2_outside_expected": catch_outside} return {"file": os.path.relpath(path, ROOT), "tests": tests, "totals": totals, "checks": checks, "notes": conv.notes, From 860deb9da2d808c0b95ba3ed50c449416f22319d Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 21:46:32 -0400 Subject: [PATCH 51/55] tut2catch.py leaves a && chain whole where a later term changes something TUT's ensure(i != 0 && ++i == 1) incremented i only where i != 0 held; split into CHECK(i != 0); CHECK(++i == 1), the increment ran whatever the first term said, and every assertion after read another i than TUT's did. A chain a term after the first of which has a side effect of its own, ++ or --, an assignment or a compound assignment, built in or an overloaded operator=, +=, ++ and the like, is not split now: the condition goes whole, parenthesised, as one Catch2 cannot decompose. Calls are not looked in: they are in most conditions, and the ones whose failure leaves garbage are the REQUIRE rules' business. A side effect in the first term, which ran in TUT too, splits as before. Probed with ++, a postfix ++, =, += on an int and = and += on a std::string after the first term: each stays whole and compiles; ++ in the first term still splits. Nothing in the stage-3 files or in llmath's converted from develop changes. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index 8a4a2f0609..ccd4822ef5 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -52,7 +52,10 @@ destructor, where a throw would end the process, CHECK always. With that macro (X below): ensure(c) -> X(c); ensure(a && b && ...) -> X(a); X(b); ... - one assertion a term, so that each prints its operands; + one assertion a term, so that each prints its operands + (whole, where a term after the first changes something, ++, + -- or an assignment, which TUT's && ran only past the terms + before it); ensure(msg, c) -> UNSCOPED_INFO(msg) then X(c), or, where c split, { INFO(msg); X(a); X(b); } so the message goes with each; braced where the call was a lone statement of an if, @@ -1837,7 +1840,10 @@ def streamed(i): # A && chain is as many assertions, each printing its operands, # each as strong as its own term needs. terms = self.and_terms(condition, record) if name == "ensure" else [condition] - if len(terms) > 1 and any(self.call_span(t, record) is None for t in terms): + # A later term that changes something ran only where the ones + # before it held: split, it would run regardless. + if len(terms) > 1 and (any(self.call_span(t, record) is None for t in terms) or + any(side_effect(t) for t in terms[1:])): terms = [condition] # TUT's ensure stopped the test at the first term that failed; @@ -2866,6 +2872,30 @@ def variables(node, constants=True): HANDLES = re.compile(r"iterator|__wrap_iter|optional|unique_ptr|shared_ptr|weak_ptr|LLPointer|_ptr\b|Ptr\b") +def side_effect(node): + """Whether evaluating node changes something of itself: ++ or --, an + assignment or compound assignment, built in or overloaded. A call may + too, but is not looked in: calls are too common in a condition, and + those whose failure leaves garbage are REQUIRE's business.""" + stack = [node] + while stack: + sub = stack.pop() + kind = sub.get("kind") + if kind in ("LambdaExpr", "CXXDefaultArgExpr"): + continue + inner = [c for c in sub.get("inner", []) if isinstance(c, dict) and "kind" in c] + if kind == "UnaryOperator" and sub.get("opcode") in ("++", "--"): + return True + if kind == "CompoundAssignOperator" or (kind == "BinaryOperator" and sub.get("opcode") == "="): + return True + if kind == "CXXOperatorCallExpr" and inner: + name = skip_implicit(inner[0]).get("referencedDecl", {}).get("name", "") + if re.fullmatch(r"operator(?:[-+*/%^&|]|<<|>>)?=|operator(?:\+\+|--)", name): + return True + stack.extend(inner) + return False + + def leaned_on(node): """The variables and fields a condition leans on for its own safety: those it dereferences (unary *, ->, a class's operator* or From bf34f38870a36cd2ca7b3efa4e44eb9f27b04846 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 21:51:17 -0400 Subject: [PATCH 52/55] tut2catch.py takes a member for a size only where it answers with a bool or an integer A member named size, length, count, empty and the like guarded what followed whatever it answered with, so LLVector3's length(), a float magnitude, made ensure(v.length() > 4.f) REQUIRE, and so did a field named count that is an LLInitParam block's parameter, not a count. Such a member, called or read, guards now only where its answer, the call's return or the field's type, is a bool or an integer. Probed: v.length() and a float field named length are CHECKs; a string's length(), a vector's size() and empty(), LLSD's has() and size(), and bool and int fields named found and count stay REQUIRE. Over the stage-3 files 12 REQUIREs go back to CHECK, all in llinitparam_test, all on its block's count parameter (block.count(), block.count.isProvided(), destination/read/copy/original.count()); nothing else changes, and llmath's converted from develop is the same. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut2catch.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/scripts/testing/tut2catch.py b/scripts/testing/tut2catch.py index ccd4822ef5..49e78fff33 100644 --- a/scripts/testing/tut2catch.py +++ b/scripts/testing/tut2catch.py @@ -36,7 +36,9 @@ pointer, null or not; an optional or a smart pointer, tested through its operator bool or compared with nullptr; a position compared with end() or npos; a size, an emptiness, - a presence: has(), has_value(), found() and the like); a + a presence: has(), has_value(), found() and the like, + answered with a bool or an integer, not a vector's float + length()); a condition that calls what makes it (a non-const member function returning bool, or a call returning bool that fills an argument passed by reference or address), where that @@ -174,6 +176,9 @@ # that it holds something, or how much. GUARD_MEMBERS = {"size", "empty", "length", "count", "isDefined", "isUndefined", "has", "isMap", "isArray", "notNull", "isNull", "has_value", "found"} +# What a member answering how much or whether answers with. +INTEGRAL = re.compile(r"(?:(?:unsigned|signed|short|long|int|char|bool|wchar_t|char8_t|char16_t|char32_t|" + r"__int128)\s*)+") # A container's ends, which a position found in it is compared with: at one, # there is nothing to read. END_MEMBERS = {"end", "cend", "rend", "crend"} @@ -1400,7 +1405,8 @@ def guards(node): nullptr through an operator of its own; a position compared with its container's end() or with npos; or a member that says how much an object holds or whether it holds anything - (has_value() and found() among them). Default arguments are not + (has_value() and found() among them), answering with a bool or an + integer: a float length() is a magnitude, not a size. Default arguments are not looked in: a NULL a callee defaults to is nothing the test compared.""" if value_type(unparenthesised(node)).endswith("*"): @@ -1420,12 +1426,21 @@ def guards(node): return True if any(is_end(c) for c in compared(sub)): return True - if kind == "MemberExpr" and sub.get("name") in GUARD_MEMBERS: + # A member, called or read, answering how much or whether: with + # a bool or an integer, as a size or a count does; a float + # length() is a magnitude. + member = None + if kind == "CXXMemberCallExpr": + member = next((c for c in sub.get("inner", []) if "kind" in c), {}) + elif kind == "MemberExpr" and sub.get("type", {}).get("qualType") != "": + member = sub + if member and member.get("kind") == "MemberExpr" and member.get("name") in GUARD_MEMBERS and \ + INTEGRAL.fullmatch(value_type(sub)): # isNull() and notNull() guard only on what points: on a # vector or a UUID they are values like any other. - if sub.get("name") not in ("isNull", "notNull"): + if member.get("name") not in ("isNull", "notNull"): return True - base = [c for c in sub.get("inner", []) if "kind" in c] + base = [c for c in member.get("inner", []) if "kind" in c] if base and re.search(r"Pointer|ptr\b|Handle|\*", value_type(skip_implicit(base[0]))): return True stack.extend(c for c in sub.get("inner", []) if isinstance(c, dict) and "kind" in c) From 17a46c7631ae5071c9de4d5d98e82996229c9710 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 21:52:34 -0400 Subject: [PATCH 53/55] tut_parity.py refuses a file edited since conversion unless --recounted, and a TUT binary older than its source An edit that weakens an assertion -- CHECK(a == b) become CHECK(a == a) -- keeps every count, result and order parity holds a file to, so nothing saw it. The converter now records the sha256 of each file it wrote; a file that differs is refused until --recounted says the edits were reviewed, so a weakened assertion can no longer pass as the converter's. record took a TUT executable's results whatever its age. One built before its source last changed is refused, as check refuses a stale Catch2 one. Co-Authored-By: Claude Opus 5.5 --- scripts/testing/tut_parity.py | 44 ++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/scripts/testing/tut_parity.py b/scripts/testing/tut_parity.py index e8617f31e8..41825ad42e 100644 --- a/scripts/testing/tut_parity.py +++ b/scripts/testing/tut_parity.py @@ -9,7 +9,8 @@ [group, N: name] ok | fail | exception: ... | skipped known failure | ... A test TUT never reported (a dummy test slot) is not a test. With them it keeps the sha256 of the TUT source, the one - indra/**/tests/.cpp git tracks. A test built at other + indra/**/tests/.cpp git tracks, and it refuses an + executable built before that source last changed. A test built at other x86-64 tiers too (an entry ending _baseline, _v2, _v3, _v4) is kept a tier apart, under @. check after converting and building: runs each converted file's CTest @@ -26,7 +27,10 @@ - its inputs are the ones the run is of: the converted file is no newer than the executable its entry runs, and where the recorded results and the mapping both have the TUT source's sha256, the two agree (where - either has none, that is listed); + either has none, that is listed); and the file is the one the converter + wrote, by the mapping's catch2_sha256, unless --recounted says it was + edited by hand and the edits were reviewed -- an edit that weakens an + assertion keeps every count, so nothing else here would see it; - every TUT test it reported has exactly one Catch2 case, and every case of the file is one of its TUT tests; - each pair agrees: ok is a pass, a TUT skip is a Catch2 skip, and every @@ -210,6 +214,21 @@ def record(args): if test is None: print(f"{name}: no such CTest entry", file=sys.stderr) return 2 + stem, tier = tut_identity(name) + # What the results are of, so that check can tell they are of the + # source the mapping was made from. + source, digest = tut_source(stem) + if source is None: + print(f"{name}: not exactly one tracked indra/**/tests/{stem}.cpp, so no source hash kept", + file=sys.stderr) + # An executable older than its source is not of it: its results + # would stand for tests the file no longer has. + command = test["command"] + if source is not None and command and os.path.isfile(command[0]) and \ + os.path.getmtime(os.path.join(ROOT, source)) > os.path.getmtime(command[0]): + print(f"{name}: {source} changed since {command[0]} was built; build it again to record it", + file=sys.stderr) + return 2 code, text = run(test, ["-v"], args.timeout) results = [] for line in text.splitlines(): @@ -220,13 +239,6 @@ def record(args): results.append({"group": m.group("group"), "n": int(m.group("n")), "name": m.group("name") or "", "result": {"ok": "ok", "skipped known failure": "skip"}.get(result, "fail"), "said": line[m.start("result"):].strip()}) - stem, tier = tut_identity(name) - # What the results are of, so that check can tell they are of the - # source the mapping was made from. - source, digest = tut_source(stem) - if source is None: - print(f"{name}: not exactly one tracked indra/**/tests/{stem}.cpp, so no source hash kept", - file=sys.stderr) # Each tier's run is kept apart: one the host cannot run exits 125 # with no results, which must not stand for the others. out[f"{stem}@{tier}" if tier else stem] = {"ctest": name, "tier": tier, "exit": code, "tut_file": source, @@ -704,6 +716,20 @@ def check(args): file_cases, outside = read_cases(os.path.join(ROOT, path)) kept = compiled_lines(os.path.join(ROOT, path)) record = (file_records or {}).get(path) + # A file edited past what the converter wrote is held to the + # converter's counts only once --recounted says it was looked at: an + # edit that kept every count, a weakened assertion, shows nowhere + # else. + if record is not None: + with open(os.path.join(ROOT, path), "rb") as f: + written = hashlib.sha256(f.read()).hexdigest() == record.get("catch2_sha256") + if not record.get("catch2_sha256"): + print(f"{path}: listed: no hash in the mapping of the file the converter wrote") + elif not written and args.recounted: + print(f"{path}: listed: edited since the converter wrote it") + elif not written: + file_problems.append(f"{path}: edited since the converter wrote it; review the edits, then " + f"check it with --recounted") # What no test's count holds: the assertions outside every test body # (helpers, fixtures), which the mapping totals for the file. if file_records is None: From df2000be924f375a849069139f162841b1d6ade0 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Oct 2026 22:55:59 -0400 Subject: [PATCH 54/55] CI runs the pending combined tests side by side, inside a step limit past CTest's The step's 60 minutes matched CTest's 3600 s cap on one combined run, and the runs went one after another: a hang, which the step is there to find, could have the runner kill the step before CTest recorded its timeout and wrote the JUnit report. The runs now go side by side, a core each, so the step takes about as long as its longest run, and the step allows 75 minutes. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 91b56e6b4f..87b2aeb1c6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -517,12 +517,14 @@ jobs: # Each Catch2 binary's run of all its tests in one process, while it is # still combined_pending: the default run leaves those out until they # are known to share a process, and this is where that is learned, on - # every row. It reports, and never fails the job; an hour bounds it, as - # CTest bounds each run, should one hang. + # every row. It reports, and never fails the job. The runs go side by + # side, so the step takes about as long as the longest, which CTest + # stops at an hour; the step's own limit is past that, so a hang is + # recorded as CTest's timeout and reaches the JUnit report. - name: Run pending combined tests if: ${{ !cancelled() && matrix.build_variant == 'Tests' }} continue-on-error: true - timeout-minutes: 60 + timeout-minutes: 75 shell: bash env: BUILD_DIRECTORY: ${{ steps.configure.outputs.build_directory }} @@ -539,9 +541,10 @@ jobs: if [[ -f "$log" ]]; then cp "$log" "$BUILD_DIRECTORY/Testing/Temporary/LastTest-build.log" fi + jobs="$(nproc 2>/dev/null || sysctl -n hw.logicalcpu)" # CTest reads a relative --output-junit from the test directory. ctest --test-dir "$BUILD_DIRECTORY" -C "$cmake_config" -L combined_pending \ - --output-on-failure --no-tests=ignore \ + --parallel "$jobs" --output-on-failure --no-tests=ignore \ --output-junit Testing/combined-pending-results.xml # What each test did, on a row whose tests failed as much as on one From b1e6aaee81f48a79a8ad2b4cbb79a0a588d702e6 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 12 Oct 2026 00:22:14 -0400 Subject: [PATCH 55/55] On Windows the Catch2 harness names the case an abort from any thread ended terminate_names_the_case failed on the Windows arm64 Tests row: the process ended without the line naming the case. The MSVC runtime keeps a terminate handler a thread, so the harness's served the main thread only, and an exception off a test's own thread ended in the default handler's abort() without a word. The CRT's SIGABRT handler is the process's, and Catch2, which handles structured exceptions on Windows, leaves it alone: there the harness names the case and replays LOGFAIL's log, where the LL_ERRS that threw wrote what went wrong. The abort-dialog setting moves with it, so it holds for every thread. The self-test asks for the case's name and the error's text, as either platform gives them. Co-Authored-By: Claude Opus 5.5 --- indra/test/CMakeLists.txt | 2 +- indra/test/altestfatal.cpp | 25 +++++++++++++++++++++---- indra/test/altestfatal.h | 5 +++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 3d596a9862..ab1dce528a 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -144,7 +144,7 @@ al_expect_from_test_support( terminate_names_the_case ARGS "[.thread-errs]" EXIT nonzero - OUTPUT "std::terminate in test case .a test case whose thread hits an LL_ERRS.: LL_ERRS: .*an error off the main thread" + OUTPUT "in test case .a test case whose thread hits an LL_ERRS..*an error off the main thread" ) al_expect_from_test_support( teardown_failure_fails diff --git a/indra/test/altestfatal.cpp b/indra/test/altestfatal.cpp index 19f02a3f5d..c2aaf11481 100644 --- a/indra/test/altestfatal.cpp +++ b/indra/test/altestfatal.cpp @@ -25,10 +25,12 @@ #include "linden_common.h" #include "altestfatal.h" +#include "altestlogreplay.h" #include "altestname.h" #include "llerrorcontrol.h" +#include #include #include #include @@ -55,6 +57,10 @@ namespace ll_test void installTerminateHandler() { +#if LL_WINDOWS + // No abort dialog for CTest to wait out, from any thread. + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); +#endif std::set_terminate( [] { @@ -75,11 +81,22 @@ namespace ll_test } } std::cerr << std::endl; -#if LL_WINDOWS - // No abort dialog for CTest to wait out. - _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); -#endif std::abort(); }); +#if LL_WINDOWS + // The CRT keeps a terminate handler a thread, so the one above serves + // the main thread only, and a thread of a test's own ends in the + // default one's abort(). The CRT's SIGABRT handler is the process's, + // and Catch2, which handles structured exceptions on Windows, leaves + // it alone: there the case is named, and LOGFAIL's log replayed, as + // Catch2 does for a crash. What was thrown is no longer in reach, but + // an LL_ERRS logged it. + std::signal(SIGABRT, + [](int) + { + std::cerr << "abort in test case \"" << currentTestName() << "\"" << std::endl; + LogReplay::replay(std::cerr, currentTestName(), true); + }); +#endif } } diff --git a/indra/test/altestfatal.h b/indra/test/altestfatal.h index 11b567f576..ca45a5fb84 100644 --- a/indra/test/altestfatal.h +++ b/indra/test/altestfatal.h @@ -54,8 +54,9 @@ namespace ll_test // Makes std::terminate, before it aborts, name the test case that was // running and what the exception in flight said. Catch2's own report of // the abort that follows names the case outside Windows, but not what was - // thrown; on Windows, where it handles no SIGABRT, nothing else would say - // either. + // thrown. On Windows, where Catch2 handles no SIGABRT and the CRT keeps a + // terminate handler a thread, an abort from any thread names the case and + // replays LOGFAIL's log, where an LL_ERRS wrote what went wrong. void installTerminateHandler(); }