diff options
Diffstat (limited to 'indra/test')
| -rw-r--r-- | indra/test/test.cpp | 134 | 
1 files changed, 117 insertions, 17 deletions
diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 172b6e3542..09147a65a3 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -56,6 +56,13 @@  #include <boost/iostreams/tee.hpp>  #include <boost/iostreams/stream.hpp> +// On Mac, got: +// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define +// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if +// _Unwind_Backtrace is available without `_GNU_SOURCE`." +#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED +#include <boost/stacktrace.hpp> +  #include <fstream>  void wouldHaveCrashed(const std::string& message); @@ -164,10 +171,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 +206,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 +220,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 +308,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;  }; @@ -503,6 +512,64 @@ void wouldHaveCrashed(const std::string& message)  static LLTrace::ThreadRecorder* sMasterThreadRecorder = NULL; +// this is used in platform-generic code -- define outside #if LL_WINDOWS +struct Windows_SEH_exception: public std::runtime_error +{ +    Windows_SEH_exception(const std::string& what): std::runtime_error(what) {} +}; + +#if LL_WINDOWS + +static constexpr U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific +static constexpr U32 STATUS_STACK_FULL    = 0xC00000FD; + +U32 seh_filter(U32 code, struct _EXCEPTION_POINTERS*) +{ +    if (code == STATUS_MSC_EXCEPTION) +    { +        // C++ exception, go on -- but TUT is supposed to have caught those already?! +        return EXCEPTION_CONTINUE_SEARCH; +    } +    else +    { +        // This is a non-C++ exception, e.g. hardware check. +        // By the time the handler gets control, the stack has been unwound, +        // so report the stack trace now at filter() time. +        // Sadly, even though, at the time of this writing, stack overflow is +        // the problem we would most like to diagnose, calling another +        // function when the stack is already blown only terminates us faster. +        if (code != STATUS_STACK_FULL) +        { +            std::cerr << boost::stacktrace::stacktrace() << std::endl; +        } +        // pass control into the handler block +        return EXCEPTION_EXECUTE_HANDLER; +    } +} + +template <typename CALLABLE0, typename CALLABLE1> +void seh_catcher(CALLABLE0&& trycode, CALLABLE1&& handler) +{ +    __try +    { +        trycode(); +    } +    __except (seh_filter(GetExceptionCode(), GetExceptionInformation())) +    { +        handler(GetExceptionCode()); +    } +} + +#else  // not LL_WINDOWS + +template <typename CALLABLE0, typename CALLABLE1> +void seh_catcher(CALLABLE0&& trycode, CALLABLE1&&) +{ +    trycode(); +} + +#endif // not LL_WINDOWS +  int main(int argc, char **argv)  {      ll_init_apr(); @@ -635,14 +702,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); -    } +    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) +        { +            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);  | 
