summaryrefslogtreecommitdiff
path: root/indra/test
diff options
context:
space:
mode:
Diffstat (limited to 'indra/test')
-rw-r--r--indra/test/debug.h69
-rw-r--r--indra/test/hexdump.h97
-rw-r--r--indra/test/io.cpp40
-rw-r--r--indra/test/llevents_tut.cpp58
-rw-r--r--indra/test/lltut.h49
-rw-r--r--indra/test/namedtempfile.h24
-rw-r--r--indra/test/print.h4
-rw-r--r--indra/test/test.cpp102
-rwxr-xr-xindra/test/writestr.h39
9 files changed, 228 insertions, 254 deletions
diff --git a/indra/test/debug.h b/indra/test/debug.h
index 1579bb9c86..ea9c634cc7 100644
--- a/indra/test/debug.h
+++ b/indra/test/debug.h
@@ -30,43 +30,56 @@
#define LL_DEBUG_H
#include "print.h"
+#include "stringize.h"
+#include <exception> // std::uncaught_exceptions()
/*****************************************************************************
* Debugging stuff
*****************************************************************************/
/**
- * This class is intended to illuminate entry to a given block, exit from the
- * same block and checkpoints along the way. It also provides a convenient
- * place to turn std::cerr output on and off.
- *
- * If the environment variable LOGTEST is non-empty, each Debug instance will
- * announce its construction and destruction, presumably at entry and exit to
- * the block in which it's declared. Moreover, any arguments passed to its
- * operator()() will be streamed to std::cerr, prefixed by the block
- * description.
+ * Return true if the environment variable LOGTEST is non-empty.
*
* The variable LOGTEST is used because that's the environment variable
* checked by test.cpp, our TUT main() program, to turn on LLError logging. It
* is expected that Debug is solely for use in test programs.
*/
+inline
+bool LOGTEST_enabled()
+{
+ auto LOGTEST{ getenv("LOGTEST") };
+ // debug output enabled when LOGTEST is set AND non-empty
+ return LOGTEST && *LOGTEST;
+}
+
+/**
+ * This class is intended to illuminate entry to a given block, exit from the
+ * same block and checkpoints along the way. It also provides a convenient
+ * place to turn std::cerr output on and off.
+ *
+ * If enabled, each Debug instance will announce its construction and
+ * destruction, presumably at entry and exit to the block in which it's
+ * declared. Moreover, any arguments passed to its operator()() will be
+ * streamed to std::cerr, prefixed by the block description.
+ */
class Debug
{
public:
- Debug(const std::string& block):
- mBlock(block),
- mLOGTEST(getenv("LOGTEST")),
- // debug output enabled when LOGTEST is set AND non-empty
- mEnabled(mLOGTEST && *mLOGTEST)
+ template <typename... ARGS>
+ Debug(ARGS&&... args):
+ mBlock(stringize(std::forward<ARGS>(args)...)),
+ mEnabled(LOGTEST_enabled())
{
(*this)("entry");
}
// non-copyable
Debug(const Debug&) = delete;
+ Debug& operator=(const Debug&) = delete;
~Debug()
{
- (*this)("exit");
+ auto exceptional{ std::uncaught_exceptions()? "exceptional " : "" };
+ (*this)(exceptional, "exit");
}
template <typename... ARGS>
@@ -80,7 +93,6 @@ public:
private:
const std::string mBlock;
- const char* mLOGTEST;
bool mEnabled;
};
@@ -88,20 +100,19 @@ private:
// of the Debug block.
#define DEBUG Debug debug(LL_PRETTY_FUNCTION)
-// These BEGIN/END macros are specifically for debugging output -- please
-// don't assume you must use such for coroutines in general! They only help to
-// make control flow (as well as exception exits) explicit.
-#define BEGIN \
-{ \
- DEBUG; \
- try
+/// If enabled, debug_expr(expression) gives you output concerning an inline
+/// expression such as a class member initializer.
+#define debug_expr(expr) debug_expr_(#expr, [&](){ return expr; })
-#define END \
- catch (...) \
- { \
- debug("*** exceptional "); \
- throw; \
- } \
+template <typename EXPR>
+inline auto debug_expr_(const char* strexpr, EXPR&& lambda)
+{
+ if (! LOGTEST_enabled())
+ return std::forward<EXPR>(lambda)();
+ print("Before: ", strexpr);
+ auto result{ std::forward<EXPR>(lambda)() };
+ print(strexpr, " -> ", result);
+ return result;
}
#endif /* ! defined(LL_DEBUG_H) */
diff --git a/indra/test/hexdump.h b/indra/test/hexdump.h
deleted file mode 100644
index 95f1e297c3..0000000000
--- a/indra/test/hexdump.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * @file hexdump.h
- * @author Nat Goodspeed
- * @date 2023-09-08
- * @brief Provide hexdump() and hexmix() ostream formatters
- *
- * $LicenseInfo:firstyear=2023&license=viewerlgpl$
- * Copyright (c) 2023, Linden Research, Inc.
- * $/LicenseInfo$
- */
-
-#if ! defined(LL_HEXDUMP_H)
-#define LL_HEXDUMP_H
-
-#include <cctype>
-#include <iomanip>
-#include <iostream>
-#include <string_view>
-
-// Format a given byte string as 2-digit hex values, no separators
-// Usage: std::cout << hexdump(somestring) << ...
-class hexdump
-{
-public:
- hexdump(const std::string_view& data):
- hexdump(data.data(), data.length())
- {}
-
- hexdump(const char* data, size_t len):
- hexdump(reinterpret_cast<const unsigned char*>(data), len)
- {}
-
- hexdump(const unsigned char* data, size_t len):
- mData(data, data + len)
- {}
-
- friend std::ostream& operator<<(std::ostream& out, const hexdump& self)
- {
- auto oldfmt{ out.flags() };
- auto oldfill{ out.fill() };
- out.setf(std::ios_base::hex, std::ios_base::basefield);
- out.fill('0');
- for (auto c : self.mData)
- {
- out << std::setw(2) << unsigned(c);
- }
- out.setf(oldfmt, std::ios_base::basefield);
- out.fill(oldfill);
- return out;
- }
-
-private:
- std::vector<unsigned char> mData;
-};
-
-// Format a given byte string as a mix of printable characters and, for each
-// non-printable character, "\xnn"
-// Usage: std::cout << hexmix(somestring) << ...
-class hexmix
-{
-public:
- hexmix(const std::string_view& data):
- mData(data)
- {}
-
- hexmix(const char* data, size_t len):
- mData(data, len)
- {}
-
- friend std::ostream& operator<<(std::ostream& out, const hexmix& self)
- {
- auto oldfmt{ out.flags() };
- auto oldfill{ out.fill() };
- out.setf(std::ios_base::hex, std::ios_base::basefield);
- out.fill('0');
- for (auto c : self.mData)
- {
- // std::isprint() must be passed an unsigned char!
- if (std::isprint(static_cast<unsigned char>(c)))
- {
- out << c;
- }
- else
- {
- out << "\\x" << std::setw(2) << unsigned(c);
- }
- }
- out.setf(oldfmt, std::ios_base::basefield);
- out.fill(oldfill);
- return out;
- }
-
-private:
- std::string mData;
-};
-
-#endif /* ! defined(LL_HEXDUMP_H) */
diff --git a/indra/test/io.cpp b/indra/test/io.cpp
index f77402065a..24e1a782d3 100644
--- a/indra/test/io.cpp
+++ b/indra/test/io.cpp
@@ -45,6 +45,7 @@
#include "llcommon.h"
#include "lluuid.h"
#include "llinstantmessage.h"
+#include "stringize.h"
namespace tut
{
@@ -1116,6 +1117,9 @@ namespace tut
template<> template<>
void fitness_test_object::test<5>()
{
+ skip("Test is strongly timing dependent, "
+ "and on slow CI machines it fails way too often.");
+ const int retries = 100;
// Set up the server
LLPumpIO::chain_t chain;
typedef LLCloneIOFactory<LLIOSleeper> sleeper_t;
@@ -1129,9 +1133,12 @@ namespace tut
chain.push_back(LLIOPipe::ptr_t(server));
mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS);
// We need to tickle the pump a little to set up the listen()
- pump_loop(mPump, 0.1f);
+ for (int retry = 0; mPump->runningChains() < 1 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
auto count = mPump->runningChains();
- ensure_equals("server chain onboard", count, 1);
+ ensure_equals("server chain 1 onboard", count, 1);
LL_DEBUGS() << "** Server is up." << LL_ENDL;
// Set up the client
@@ -1140,9 +1147,12 @@ namespace tut
bool connected = client->blockingConnect(server_host);
ensure("Connected to server", connected);
LL_DEBUGS() << "connected" << LL_ENDL;
- pump_loop(mPump,0.1f);
+ for (int retry = 0; mPump->runningChains() < 2 && retry < retries; ++retry)
+ {
+ pump_loop(mPump,0.1f);
+ }
count = mPump->runningChains();
- ensure_equals("server chain onboard", count, 2);
+ ensure_equals("server chain 2 onboard", count, 2);
LL_DEBUGS() << "** Client is connected." << LL_ENDL;
// We have connected, since the socket reader does not block,
@@ -1156,20 +1166,32 @@ namespace tut
chain.clear();
// pump for a bit and make sure all 3 chains are running
- pump_loop(mPump,0.1f);
+ for (int retry = 0; mPump->runningChains() < 3 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
count = mPump->runningChains();
- // ensure_equals("client chain onboard", count, 3); commented out because it fails frequently - appears to be timing sensitive
+ ensure_equals("client chain onboard", count, 3);
LL_DEBUGS() << "** request should have been sent." << LL_ENDL;
// pump for long enough the the client socket closes, and the
// server socket should not be closed yet.
- pump_loop(mPump,0.2f);
+ for (int retry = 0; mPump->runningChains() == 3 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
+ // We used to test for count == 2 here, but on a slow test machine it
+ // can happen that not just one but two chains close before we reach
+ // this point.
count = mPump->runningChains();
- ensure_equals("client chain timed out ", count, 2);
+ ensure(stringize("client chain timed out: count ", count), count < 3);
LL_DEBUGS() << "** client chain should be closed." << LL_ENDL;
// At this point, the socket should be closed by the timeout
- pump_loop(mPump,1.0f);
+ for (int retry = 0; mPump->runningChains() > 1 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
count = mPump->runningChains();
ensure_equals("accepted socked close", count, 1);
LL_DEBUGS() << "** Sleeper should have timed out.." << LL_ENDL;
diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp
index bf5cd3f853..1f723c84b6 100644
--- a/indra/test/llevents_tut.cpp
+++ b/indra/test/llevents_tut.cpp
@@ -428,7 +428,7 @@ void events_object::test<9>()
{
set_test_name("listen(boost::bind(...TempListener...))");
// listen() can't do anything about a plain TempListener instance:
- // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass
+ // it's not managed with shared_ptr
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
@@ -452,60 +452,4 @@ void events_object::test<9>()
heaptest.stopListening("temp");
}
-class TempTrackableListener: public TempListener, public LLEventTrackable
-{
-public:
- TempTrackableListener(const std::string& name, bool& liveFlag):
- TempListener(name, liveFlag)
- {}
-};
-
-template<> template<>
-void events_object::test<10>()
-{
- set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
- bool live = false;
- LLEventPump& heaptest(pumps.obtain("heaptest"));
- LLBoundListener connection;
- {
- TempTrackableListener tempListener("temp", live);
- ensure("TempTrackableListener constructed", live);
- connection = heaptest.listen(tempListener.getName(),
- boost::bind(&TempTrackableListener::call,
- boost::ref(tempListener), _1));
- heaptest.post(1);
- check_listener("received", tempListener, 1);
- } // presumably this will make tempListener go away?
- // verify that
- ensure("TempTrackableListener destroyed", ! live);
- ensure("implicit disconnect", ! connection.connected());
- // now just make sure we don't blow up trying to access a freed object!
- heaptest.post(2);
-}
-
-template<> template<>
-void events_object::test<11>()
-{
- set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
- bool live = false;
- LLEventPump& heaptest(pumps.obtain("heaptest"));
- LLBoundListener connection;
- {
- TempTrackableListener* newListener(new TempTrackableListener("temp", live));
- ensure("TempTrackableListener constructed", live);
- connection = heaptest.listen(newListener->getName(),
- boost::bind(&TempTrackableListener::call,
- newListener, _1));
- heaptest.post(1);
- check_listener("received", *newListener, 1);
- // explicitly destroy newListener
- delete newListener;
- }
- // verify that
- ensure("TempTrackableListener destroyed", ! live);
- ensure("implicit disconnect", ! connection.connected());
- // now just make sure we don't blow up trying to access a freed object!
- heaptest.post(2);
-}
-
} // namespace tut
diff --git a/indra/test/lltut.h b/indra/test/lltut.h
index e56b4e8d1c..986bdd0619 100644
--- a/indra/test/lltut.h
+++ b/indra/test/lltut.h
@@ -30,7 +30,11 @@
#define LL_LLTUT_H
#include "is_approx_equal_fraction.h" // instead of llmath.h
+#include "stringize.h"
#include <cstring>
+#include <string>
+#include <string_view>
+#include <vector>
class LLDate;
class LLSD;
@@ -87,49 +91,38 @@ namespace tut
// The functions BELOW this point actually consume tut.hpp functionality.
namespace tut
{
- inline void ensure_approximately_equals(const char* msg, F64 actual, F64 expected, U32 frac_bits)
+ template <typename F> // replace with C++20 floating-point concept
+ inline void ensure_approximately_equals(std::string_view msg, F actual, F expected, U32 frac_bits)
{
if(!is_approx_equal_fraction(actual, expected, frac_bits))
{
- std::stringstream ss;
- ss << (msg?msg:"") << (msg?": ":"") << "not equal actual: " << actual << " expected: " << expected;
- throw tut::failure(ss.str().c_str());
+ throw tut::failure(stringize(msg, (msg.empty()?"":": "), "not equal actual: ",
+ actual, " expected: ", expected));
}
}
- inline void ensure_approximately_equals(const char* msg, F32 actual, F32 expected, U32 frac_bits)
+ template <typename F> // replace with C++20 floating-point concept
+ inline void ensure_approximately_equals(F actual, F expected, U32 frac_bits)
{
- if(!is_approx_equal_fraction(actual, expected, frac_bits))
- {
- std::stringstream ss;
- ss << (msg?msg:"") << (msg?": ":"") << "not equal actual: " << actual << " expected: " << expected;
- throw tut::failure(ss.str().c_str());
- }
- }
-
- inline void ensure_approximately_equals(F32 actual, F32 expected, U32 frac_bits)
- {
- ensure_approximately_equals(NULL, actual, expected, frac_bits);
+ ensure_approximately_equals("", actual, expected, frac_bits);
}
- inline void ensure_approximately_equals_range(const char *msg, F32 actual, F32 expected, F32 delta)
+ template <typename F> // replace with C++20 floating-point concept
+ inline void ensure_approximately_equals_range(std::string_view msg, F actual, F expected, F delta)
{
if (fabs(actual-expected)>delta)
{
- std::stringstream ss;
- ss << (msg?msg:"") << (msg?": ":"") << "not equal actual: " << actual << " expected: " << expected << " tolerance: " << delta;
- throw tut::failure(ss.str().c_str());
+ throw tut::failure(stringize(msg, (msg.empty()?"":": "), "not equal actual: ",
+ actual, " expected: ", expected, " tolerance: ", delta));
}
}
- inline void ensure_memory_matches(const char* msg,const void* actual, U32 actual_len, const void* expected,U32 expected_len)
+ inline void ensure_memory_matches(std::string_view msg,const void* actual, U32 actual_len, const void* expected,U32 expected_len)
{
if((expected_len != actual_len) ||
(std::memcmp(actual, expected, actual_len) != 0))
{
- std::stringstream ss;
- ss << (msg?msg:"") << (msg?": ":"") << "not equal";
- throw tut::failure(ss.str().c_str());
+ throw tut::failure(stringize(msg, (msg.empty()?"":": "), "not equal"));
}
}
@@ -139,20 +132,18 @@ namespace tut
}
template <class T,class Q>
- void ensure_not_equals(const char* msg,const Q& actual,const T& expected)
+ void ensure_not_equals(std::string_view msg,const Q& actual,const T& expected)
{
if( expected == actual )
{
- std::stringstream ss;
- ss << (msg?msg:"") << (msg?": ":"") << "both equal " << expected;
- throw tut::failure(ss.str().c_str());
+ throw tut::failure(stringize(msg, (msg.empty()?"":": "), "both equal ", expected));
}
}
template <class T,class Q>
void ensure_not_equals(const Q& actual,const T& expected)
{
- ensure_not_equals(NULL, actual, expected);
+ ensure_not_equals("", actual, expected);
}
}
diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h
index 8027f95728..96b19523ab 100644
--- a/indra/test/namedtempfile.h
+++ b/indra/test/namedtempfile.h
@@ -32,18 +32,18 @@ class NamedTempFile: public boost::noncopyable
{
LOG_CLASS(NamedTempFile);
public:
- NamedTempFile(const std::string_view& pfx,
- const std::string_view& content,
- const std::string_view& sfx=std::string_view(""))
+ NamedTempFile(std::string_view pfx,
+ std::string_view content,
+ std::string_view sfx=std::string_view(""))
{
createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx);
}
// Disambiguate when passing string literal -- unclear why a string
// literal should be ambiguous wrt std::string_view and Streamer
- NamedTempFile(const std::string_view& pfx,
+ NamedTempFile(std::string_view pfx,
const char* content,
- const std::string_view& sfx=std::string_view(""))
+ std::string_view sfx=std::string_view(""))
{
createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx);
}
@@ -53,9 +53,9 @@ public:
// (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n')
typedef std::function<void(std::ostream&)> Streamer;
- NamedTempFile(const std::string_view& pfx,
+ NamedTempFile(std::string_view pfx,
const Streamer& func,
- const std::string_view& sfx=std::string_view(""))
+ std::string_view sfx=std::string_view(""))
{
createFile(pfx, func, sfx);
}
@@ -94,8 +94,8 @@ public:
return out;
}
- static boost::filesystem::path temp_path(const std::string_view& pfx="",
- const std::string_view& sfx="")
+ static boost::filesystem::path temp_path(std::string_view pfx="",
+ std::string_view sfx="")
{
// This variable is set by GitHub actions and is the recommended place
// to put temp files belonging to an actions job.
@@ -114,9 +114,9 @@ public:
}
protected:
- void createFile(const std::string_view& pfx,
+ void createFile(std::string_view pfx,
const Streamer& func,
- const std::string_view& sfx)
+ std::string_view sfx)
{
// Create file in a temporary place.
mPath = temp_path(pfx, sfx);
@@ -137,7 +137,7 @@ class NamedExtTempFile: public NamedTempFile
{
LOG_CLASS(NamedExtTempFile);
public:
- NamedExtTempFile(const std::string& ext, const std::string_view& content):
+ NamedExtTempFile(const std::string& ext, std::string_view content):
NamedTempFile(remove_dot(ext), content, ensure_dot(ext))
{}
diff --git a/indra/test/print.h b/indra/test/print.h
index 7577698cc8..6906eae581 100644
--- a/indra/test/print.h
+++ b/indra/test/print.h
@@ -23,7 +23,9 @@ struct NONL_t {};
inline
void print()
{
+#ifdef LL_TEST
std::cerr << std::endl;
+#endif
}
// print(NONL) is a no-op
@@ -35,8 +37,10 @@ void print(NONL_t)
template <typename T, typename... ARGS>
void print(T&& first, ARGS&&... rest)
{
+#ifdef LL_TEST
std::cerr << first;
print(std::forward<ARGS>(rest)...);
+#endif
}
#endif /* ! defined(LL_PRINT_H) */
diff --git a/indra/test/test.cpp b/indra/test/test.cpp
index 172b6e3542..c002edd94c 100644
--- a/indra/test/test.cpp
+++ b/indra/test/test.cpp
@@ -35,13 +35,15 @@
*/
#include "linden_common.h"
-#include "llerrorcontrol.h"
-#include "lltut.h"
+#include "llexception.h"
#include "chained_callback.h"
-#include "stringize.h"
-#include "namedtempfile.h"
+#include "fsyspath.h"
+#include "llerrorcontrol.h"
#include "lltrace.h"
#include "lltracethreadrecorder.h"
+#include "lltut.h"
+#include "namedtempfile.h"
+#include "stringize.h"
#include "apr_pools.h"
#include "apr_getopt.h"
@@ -164,10 +166,6 @@ public:
LLTestCallback(bool verbose_mode, std::ostream *stream,
std::shared_ptr<LLReplayLog> replayer) :
mVerboseMode(verbose_mode),
- mTotalTests(0),
- mPassedTests(0),
- mFailedTests(0),
- mSkippedTests(0),
// By default, capture a shared_ptr to std::cout, with a no-op "deleter"
// so that destroying the shared_ptr makes no attempt to delete std::cout.
mStream(std::shared_ptr<std::ostream>(&std::cout, [](std::ostream*){})),
@@ -203,6 +201,8 @@ public:
virtual void group_started(const std::string& name) {
LL_INFOS("TestRunner")<<"Unit test group_started name=" << name << LL_ENDL;
*mStream << "Unit test group_started name=" << name << std::endl;
+ mGroup = name;
+ mGroupTests = 0;
super::group_started(name);
}
@@ -215,6 +215,7 @@ public:
virtual void test_completed(const tut::test_result& tr)
{
++mTotalTests;
+ ++mGroupTests;
// If this test failed, dump requested log messages BEFORE stating the
// test result.
@@ -302,12 +303,15 @@ public:
super::run_completed();
}
+ std::string mGroup;
+ int mGroupTests{ 0 };
+
protected:
- bool mVerboseMode;
- int mTotalTests;
- int mPassedTests;
- int mFailedTests;
- int mSkippedTests;
+ bool mVerboseMode{ false };
+ int mTotalTests{ 0 };
+ int mPassedTests{ 0 };
+ int mFailedTests{ 0 };
+ int mSkippedTests{ 0 };
std::shared_ptr<std::ostream> mStream;
std::shared_ptr<LLReplayLog> mReplayer;
};
@@ -522,6 +526,29 @@ int main(int argc, char **argv)
// LOGTEST overrides default, but can be overridden by --debug.
const char* LOGTEST = getenv("LOGTEST");
+ // Sometimes we must rebuild much of the viewer before we get to the
+ // specific test we want to monitor, and some viewer integration tests are
+ // quite verbose. In addition to noticing plain LOGTEST= (for all tests),
+ // also notice LOGTEST_progname= (for a specific test).
+ // (Why doesn't MSVC notice fsyspath::operator std::string()?
+ // Why must we explicitly call fsyspath::string()?)
+ std::string basename(fsyspath(argv[0]).stem().string());
+ // don't make user set LOGTEST_INTEGRATION_TEST_progname or (worse)
+ // LOGTEST_PROJECT_foo_TEST_bar -- only LOGTEST_progname or LOGTEST_bar
+ auto _TEST_ = basename.find("_TEST_");
+ if (_TEST_ != std::string::npos)
+ {
+ basename.erase(0, _TEST_+6);
+ }
+ std::string LOGTEST_prog_key("LOGTEST_" + basename);
+ const char* LOGTEST_prog = getenv(LOGTEST_prog_key.c_str());
+// std::cout << LOGTEST_prog_key << "='" << (LOGTEST_prog? LOGTEST_prog : "") << "'" << std::endl;
+ if (LOGTEST_prog && *LOGTEST_prog)
+ {
+ LOGTEST = LOGTEST_prog;
+ std::cout << "LOGTEST='" << LOGTEST << "' from " << LOGTEST_prog_key << std::endl;
+ }
+
// values used for options parsing
apr_status_t apr_err;
const char* opt_arg = NULL;
@@ -635,14 +662,47 @@ int main(int argc, char **argv)
// a chained_callback subclass must be linked with previous
mycallback->link();
- if(test_group.empty())
- {
- tut::runner.get().run_tests();
- }
- else
- {
- tut::runner.get().run_tests(test_group);
- }
+ LL::seh::catcher(
+ // __try
+ [test_group]
+ {
+ if(test_group.empty())
+ {
+ tut::runner.get().run_tests();
+ }
+ else
+ {
+ tut::runner.get().run_tests(test_group);
+ }
+ },
+ // __except
+ [mycallback](U32 code, const std::string& /*stacktrace*/)
+ {
+ static std::map<U32, const char*> codes = {
+ { 0xC0000005, "Access Violation" },
+ { 0xC00000FD, "Stack Overflow" },
+ // ... continue filling in as desired
+ };
+
+ auto found{ codes.find(code) };
+ const char* name = ((found == codes.end())? "unknown" : found->second);
+ auto msg{ stringize("test threw ", std::hex, code, " (", name, ")") };
+
+ // Instead of bombing the whole test run, report this as a test
+ // failure. Arguably, catching structured exceptions should be
+ // hacked into TUT itself.
+ mycallback->test_completed(tut::test_result(
+ mycallback->mGroup,
+ mycallback->mGroupTests+1, // test within group
+ "unknown", // test name
+ tut::test_result::ex, // result: exception
+ // we don't have to throw this exception subclass to use it to
+ // populate the test_result struct
+ Windows_SEH_exception(msg)));
+ // we've left the TUT framework -- finish up by hand
+ mycallback->group_completed(mycallback->mGroup);
+ mycallback->run_completed();
+ });
bool success = (mycallback->getFailedTests() == 0);
diff --git a/indra/test/writestr.h b/indra/test/writestr.h
new file mode 100755
index 0000000000..af8be5a3aa
--- /dev/null
+++ b/indra/test/writestr.h
@@ -0,0 +1,39 @@
+/**
+ * @file writestr.h
+ * @author Nat Goodspeed
+ * @date 2024-05-21
+ * @brief writestr() function for when iostream isn't set up
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_WRITESTR_H)
+#define LL_WRITESTR_H
+
+#include "stringize.h"
+
+#ifndef LL_WINDOWS
+
+#include <unistd.h>
+
+#else // LL_WINDOWS
+
+#include <io.h>
+inline
+int write(int fd, const void* buffer, unsigned int count)
+{
+ return _write(fd, buffer, count);
+}
+
+#endif // LL_WINDOWS
+
+template <typename... ARGS>
+auto writestr(int fd, ARGS&&... args)
+{
+ std::string str{ stringize(std::forward<ARGS>(args)..., '\n') };
+ return write(fd, str.data(), str.length());
+}
+
+#endif /* ! defined(LL_WRITESTR_H) */