summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yaml35
-rw-r--r--indra/llcommon/llcoros.cpp4
-rw-r--r--indra/llcommon/tests/llleap_test.cpp2
-rw-r--r--indra/test/test.cpp130
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);