diff options
-rw-r--r-- | .github/workflows/build.yaml | 35 | ||||
-rw-r--r-- | indra/llcommon/llcoros.cpp | 4 | ||||
-rw-r--r-- | indra/llcommon/tests/llleap_test.cpp | 2 | ||||
-rw-r--r-- | indra/test/test.cpp | 130 |
4 files changed, 133 insertions, 38 deletions
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8d1c6b63e6..f77d84be4f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,13 +21,19 @@ jobs: runs-on: ubuntu-latest outputs: release_run: ${{ steps.setvar.outputs.release_run }} + branch: ${{ steps.which-branch.outputs.branch }} + relnotes: ${{ steps.which-branch.outputs.relnotes }} env: # Build with a tag like "Second_Life#abcdef0" to generate a release page # (used for builds we are planning to deploy). + # Even though inputs.release_run is specified with type boolean, which + # correctly presents a checkbox, its *value* is a GH workflow string + # 'true' or 'false'. If you simply test github.event.inputs.release_run, + # it always evaluates as true because it's a non-empty string either way. # When you want to use a string variable as a workflow YAML boolean, it's # important to ensure it's the empty string when false. If you omit || '', - # its value when false is "false", which is interpreted as true. - RELEASE_RUN: ${{ (github.event.inputs.release_run || github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life')) && 'Y' || '' }} + # its value when false is "false", which (again) is interpreted as true. + RELEASE_RUN: ${{ (github.event.inputs.release_run != 'false' || (github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life'))) && 'Y' || '' }} steps: - name: Set Variable id: setvar @@ -35,6 +41,12 @@ jobs: run: | echo "release_run=$RELEASE_RUN" >> "$GITHUB_OUTPUT" + - name: Determine source branch + id: which-branch + uses: secondlife/viewer-build-util/which-branch@v2 + with: + token: ${{ github.token }} + build: needs: setvar strategy: @@ -52,8 +64,6 @@ jobs: outputs: viewer_channel: ${{ steps.build.outputs.viewer_channel }} viewer_version: ${{ steps.build.outputs.viewer_version }} - viewer_branch: ${{ steps.which-branch.outputs.branch }} - relnotes: ${{ steps.which-branch.outputs.relnotes }} imagename: ${{ steps.build.outputs.imagename }} env: AUTOBUILD_ADDRSIZE: 64 @@ -136,19 +146,12 @@ jobs: if: env.BUILD && runner.os == 'Windows' run: choco install nsis-unicode - - name: Determine source branch - id: which-branch - if: env.BUILD - uses: secondlife/viewer-build-util/which-branch@v2 - with: - token: ${{ github.token }} - - name: Build id: build if: env.BUILD shell: bash env: - AUTOBUILD_VCS_BRANCH: ${{ steps.which-branch.outputs.branch }} + AUTOBUILD_VCS_BRANCH: ${{ needs.setvar.outputs.branch }} RUNNER_OS: ${{ runner.os }} run: | # set up things the viewer's build.sh script expects @@ -403,7 +406,11 @@ jobs: release: needs: [setvar, build, sign-and-package-windows, sign-and-package-mac] runs-on: ubuntu-latest - if: needs.setvar.outputs.release_run + # action-gh-release requires a tag (presumably for automatic generation of + # release notes). Possible TODO: if we arrive here but do not have a + # suitable tag for github.sha, create one? If we do that, of course remove + # this == 'tag' condition. + if: needs.setvar.outputs.release_run && github.ref_type == 'tag' steps: - uses: actions/download-artifact@v4 with: @@ -433,7 +440,7 @@ jobs: Build ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} ${{ needs.build.outputs.viewer_channel }} ${{ needs.build.outputs.viewer_version }} - ${{ needs.build.outputs.relnotes }} + ${{ needs.setvar.outputs.relnotes }} prerelease: true generate_release_notes: true target_commitish: ${{ github.sha }} diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index aa8eca7d90..a70e3d9ae7 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -123,7 +123,7 @@ LLCoros::LLCoros(): // Previously we used // boost::context::guarded_stack_allocator::default_stacksize(); // empirically this is insufficient. - mStackSize(1024*1024), + mStackSize(512*1024), // mCurrent does NOT own the current CoroData instance -- it simply // points to it. So initialize it with a no-op deleter. mCurrent{ [](CoroData*){} } @@ -155,7 +155,7 @@ void LLCoros::cleanupSingleton() // don't use llcoro::suspend() because that module depends // on this one // This will yield current(main) thread and will let active - // corutines run once + // coroutines run once boost::this_fiber::yield(); } printActiveCoroutines("after pumping"); diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index fa48bcdefd..3fb25b4cef 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -35,7 +35,7 @@ // causes Windows abdominal pain such that it later fails code-signing in some // mysterious way. Entirely suppressing these LLLeap tests pushes the failure // rate MUCH lower. Can we re-enable them with a smaller data size on Windows? -const size_t BUFFERED_LENGTH = 100*1024; +const size_t BUFFERED_LENGTH = 1023*1024; #else // not Windows const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 61a4eb07c5..c4cf4b2d28 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -68,10 +68,12 @@ #pragma warning (pop) #endif -#include <boost/scoped_ptr.hpp> -#include <boost/shared_ptr.hpp> -#include <boost/make_shared.hpp> -#include <boost/foreach.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> @@ -181,10 +183,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*){})), @@ -220,6 +218,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); } @@ -232,6 +232,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. @@ -319,12 +320,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; }; @@ -520,6 +524,57 @@ 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 const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific + +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. + 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) { // The following line must be executed to initialize Google Mock @@ -658,14 +713,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); |