test: Add thread-safe fast-failing test macros #35139

pull maflcko wants to merge 3 commits into bitcoin:master from maflcko:2604-test-fail-fast-thread-safe-macros changing 2 files +333 −163
  1. maflcko commented at 2:04 PM on April 22, 2026: member

    Currently, the unit tests use different boost check macros. However, this is problematic:

    • The check macros are tied to the boost test framework and can not be used in other tests, such as the fuzz tests.
    • The check macros are not overflow-safe. For example BOOST_CHECK_GT(-10, 10U); compiles and passes fine, even though it is obviously wrong.
    • The check macros are not thread-safe. Using them in a thread will compile fine, but then fail tsan later on. Usually it is trivial to replace them by a plain assert, but it would be better if the macros just worked under tsan.
    • The check macros are not fast-failing. This is confusing, as the test continues to run and will print more logs, making it harder to spot the first failure. Also, it is contrary to the internal check macros assert and Assert, and even CHECK_NONFATAL under debug mode, which all lead to an immediate abort.

    Fix all issues by adding new check macros: ASSERT, ASSERT_EQ (and friends), ASSERT_EQ_COLLECTIONS (C++20 range-based), as well as ASSERT_EXCEPTION. They can be used as a drop-in replacement for the corresponding boost macro.

    The implementation for all macros is only ~150 LOC, so it should be preferable to any external dependency addressing the above issues (which I couldn't find anyway).

    Note on naming (why ASSERT?)

    The test macros follow the naming and semantics of the internal Bitcoin Core macros (assert, and Assert). While it may confusing to have three unary assert macros that do roughly the same, it should be harmless and any macro that compiles is fine to use. (There is a special edge case to Assert, which allows to be turned into an exception as part of g_detail_test_only_CheckFailuresAreExceptionsNotAborts, but this shouldn't matter much).

    In theory, if devs didn't care about nice diagnostic messages on a failure (printing the left and right side), the macros could even be a direct alias of assert or Assert, reducing the LOC even more. Though, I think it is fine and preferable to have slightly better failure messages in tests.

    Also, the naming and semantics follow the Bitcoin Core functional test macros, which also start with assert_.

    Later optimizations

    The macros are currently sitting in the header, but once they are applied to the whole codebase, the pretty-printing implementation can move to a cpp file, which reduces the weight of the header and makes it possible to change or add pretty-printing features without a whole re-compilation of all tests.

  2. DrahtBot renamed this:
    test: Add thread-safe fast-failing test macros
    test: Add thread-safe fast-failing test macros
    on Apr 22, 2026
  3. DrahtBot added the label Tests on Apr 22, 2026
  4. DrahtBot commented at 2:04 PM on April 22, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process. A summary of reviews will appear here.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35003 (validation: improve block data I/O error handling in P2P paths by furszy)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • BufferedFile bfbad{file, 25, 25} in src/test/streams_tests.cpp
    • BufferedFile bf{file, 25, 10} in src/test/streams_tests.cpp

    <sup>2026-04-23 06:12:19</sup>

  5. maflcko force-pushed on Apr 22, 2026
  6. DrahtBot added the label CI failed on Apr 22, 2026
  7. maflcko commented at 3:42 PM on April 22, 2026: member

    To test the Tsan error, one can use this diff:

    diff --git a/src/test/btcsignals_tests.cpp b/src/test/btcsignals_tests.cpp
    index b3203ebeb2..43a68607a3 100644
    --- a/src/test/btcsignals_tests.cpp
    +++ b/src/test/btcsignals_tests.cpp
    @@ -5,2 +5,3 @@
     #include <btcsignals.h>
    +#include <test/util/common.h>
     #include <test/util/setup_common.h>
    @@ -162,3 +163,3 @@ BOOST_AUTO_TEST_CASE(thread_safety)
             // a race inside of BOOST_CHECK itself (writing to the log).
    -        assert(!sig0.empty());
    +        ASSERT(!sig0.empty());
             assert(conn0.connected());
    

    Tsan passes with ASSSERT or assert, or Assert, but fails with BOOST_CHECK when running this test.

    <!-- # Insert the required header sed --in-place '5i#include <test/util/common.h>' src/test/streams_tests.cpp # - Use range-based collection equality check sed --in-place --regexp-extended 's/BOOST_CHECK_EQUAL_COLLECTIONS\(([^.]+)\.begin\(\), \1\.end\(\), ([^.]+)\.begin\(\), \2\.end\(\)\)/ASSERT_EQ_COLLECTIONS(\1, \2)/g' src/test/streams_tests.cpp

  8. maflcko force-pushed on Apr 22, 2026
  9. maflcko force-pushed on Apr 22, 2026
  10. maflcko commented at 8:11 PM on April 22, 2026: member

    To test the overall behavior, and compare it with the boost behavior, one can use this diff (it includes recommended instructions to run)

    <details><summary>a diff to trigger different failures</summary>

    diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp
    index 9bf7f6f6fd..e6a4049328 100644
    --- a/src/test/streams_tests.cpp
    +++ b/src/test/streams_tests.cpp
    @@ -17,6 +17,154 @@
     using namespace std::string_literals;
     using namespace util::hex_literals;
     
    +// All suites below will fail
    +// For testing, run them via Bash:
    +// clang-format off
    +// cmake --build ./bld-cmake --parallel && for s in Suite_Bool Suite_Equality Suite_Null Suite_Null2 Suite_Inequality Suite_LessThan Suite_GreaterThan Suite_LessEqual Suite_GreaterEqual Suite_Collections Suite_CollectionsEnd Suite_Exceptions Suite_ExceptionsWrongCatch Suite_ExceptionsWrongThrow; do valgrind ./bld-cmake/bin/test_bitcoin --catch_system_error=no -t $s; echo "Exit code: $?"; done
    +// clang-format on
    +BOOST_AUTO_TEST_SUITE(Suite_Bool)
    +BOOST_AUTO_TEST_CASE(Test_Assert)
    +{
    +    short* condition{};
    +    BOOST_CHECK(condition);
    +    ASSERT(condition);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_Equality)
    +BOOST_AUTO_TEST_CASE(Test_AssertEq)
    +{
    +    const char* a{"hi"};
    +    std::cout << "Passes on both:\n";
    +    BOOST_CHECK_EQUAL(a, "hi");
    +    ASSERT_EQ(a, "hi");
    +
    +    std::cout << "Fails on the new macro (This check is stricter and will do pointer-compare)\n";
    +    char b[3] = {'h', 'i', '\0'};
    +    BOOST_CHECK_EQUAL(b, "hi");
    +    using namespace std::literals;
    +    ASSERT_EQ(b, "hi"); // Use "hi"sv or "hi"s instead
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_Null)
    +BOOST_AUTO_TEST_CASE(Test_AssertNull)
    +{
    +    const char* a{"hi"};
    +    BOOST_CHECK_EQUAL(a, nullptr);
    +    ASSERT_EQ(a, nullptr);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_Null2)
    +BOOST_AUTO_TEST_CASE(Test_AssertNull2)
    +{
    +    const char* a{};
    +    BOOST_CHECK_EQUAL(a, "hi");
    +    ASSERT_EQ(a, "hi");
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_Inequality)
    +BOOST_AUTO_TEST_CASE(Test_AssertNe)
    +{
    +    bool a{true};
    +    BOOST_CHECK_NE(a, true);
    +    ASSERT_NE(a, true);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_LessThan)
    +BOOST_AUTO_TEST_CASE(Test_AssertLt)
    +{
    +    BOOST_CHECK_LT(10, 5U);
    +    ASSERT_LT(10U, 5U); // Checks at compile time that the same signdness is provided!
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_GreaterThan)
    +BOOST_AUTO_TEST_CASE(Test_AssertGt)
    +{
    +    std::cout << "This should obviously fail, but boost passes!\n";
    +    BOOST_CHECK_GT(-10, 10U);
    +    // ASSERT_GT(-10, 10U); // does not compile
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_LessEqual)
    +BOOST_AUTO_TEST_CASE(Test_AssertLe)
    +{
    +    BOOST_CHECK_LE(10.1, 5.1);
    +    ASSERT_LE(10.1, 5.1);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_GreaterEqual)
    +BOOST_AUTO_TEST_CASE(Test_AssertGe)
    +{
    +    BOOST_CHECK_GE(char{12}, 'a');
    +    ASSERT_GE(char{12}, 'a');
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_Collections)
    +BOOST_AUTO_TEST_CASE(Test_AssertCollections)
    +{
    +    std::vector<int> v1{1, 2, 3}, v2{1, 99, 3};
    +    BOOST_CHECK_EQUAL_COLLECTIONS(v1.begin(), v1.end(), v2.begin(), v2.end());
    +    ASSERT_EQ_COLLECTIONS(v1, v2);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_CollectionsEnd)
    +BOOST_AUTO_TEST_CASE(Test_AssertCollectionsEnd)
    +{
    +    std::vector<NodeSeconds> v1{
    +        NodeSeconds{1s},
    +    },
    +        v2{};
    +    BOOST_CHECK_EQUAL_COLLECTIONS(v1.begin(), v1.end(), v2.begin(), v2.end());
    +    ASSERT_EQ_COLLECTIONS(v1, v2);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_Exceptions)
    +BOOST_AUTO_TEST_CASE(Test_AssertException)
    +{
    +    auto thrower = []() { throw std::runtime_error("test error"); };
    +    auto predicate = [](const std::runtime_error&) { return false; };
    +
    +    BOOST_CHECK_EXCEPTION(thrower(), std::runtime_error, predicate);
    +    ASSERT_EXCEPTION(thrower(), std::runtime_error, predicate);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_ExceptionsWrongCatch)
    +BOOST_AUTO_TEST_CASE(Test_AssertExceptionWrongCatch)
    +{
    +    auto thrower = []() { throw std::runtime_error("test error"); };
    +    auto predicate = [](const int&) { return false; };
    +
    +    try {
    +        BOOST_CHECK_EXCEPTION(thrower(), int, predicate);
    +    } catch (...) {
    +        std::cout << "boost lets the exception through\n";
    +    }
    +    ASSERT_EXCEPTION(thrower(), int, predicate);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
    +BOOST_AUTO_TEST_SUITE(Suite_ExceptionsWrongThrow)
    +BOOST_AUTO_TEST_CASE(Test_AssertExceptionWrongThrow)
    +{
    +    auto thrower = []() {};
    +    auto predicate = [](const int&) { return false; };
    +
    +    BOOST_CHECK_EXCEPTION(thrower(), int, predicate);
    +    ASSERT_EXCEPTION(thrower(), int, predicate);
    +}
    +BOOST_AUTO_TEST_SUITE_END()
    +
     BOOST_FIXTURE_TEST_SUITE(streams_tests, BasicTestingSetup)
     
     // Check optimized obfuscation with random offsets and sizes to ensure proper
    
    

    </details>

  11. DrahtBot removed the label CI failed on Apr 22, 2026
  12. test: Add thread-safe fast-failing test macros fa3b92d4be
  13. scripted-diff: Use new test macros in streams_tests.cpp
    -BEGIN VERIFY SCRIPT-
    
     ren() { sed --in-place --regexp-extended "s|\<$1\>|$2|g" src/test/streams_tests.cpp ; }
    
     # "Boring" replacements:
     ren BOOST_CHECK           ASSERT
     ren BOOST_CHECK_EQUAL     ASSERT_EQ
     ren BOOST_REQUIRE_EQUAL   ASSERT_EQ
     ren BOOST_CHECK_EXCEPTION ASSERT_EXCEPTION
    
     # "Advanced" replacements:
     # Append empty HasReason operator
     sed --in-place --regexp-extended 's/BOOST_CHECK_THROW\((.+)\)/ASSERT_EXCEPTION(\1, HasReason{""})/g' src/test/streams_tests.cpp
    
    -END VERIFY SCRIPT-
    fa021a57cf
  14. test: Manually replace multi-line BOOST_CHECK_EQUAL_COLLECTIONS macro fac40f847e
  15. maflcko force-pushed on Apr 23, 2026
Contributors
Labels

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-24 09:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me