Search code examples
c++c++14catch-unit-testcatch2

Detect specific tag match in Catch2 at runtime


I have integration tests in my Catch2 project that depend on some expensive global state being set up. I'd like to only initialize that global state when the test runner will actually be testing the systems that depend on it.

What I have appears to work, but it's a little horrifying... it depends on rather a lot of implementation details in the Catch config.

Here's my main:

#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
...

int main(int argc, const char* argv[])
{
    // Construct a fake TestCaseInfo to match against the [integration] tag
    const char * expensive_tag = "integration";
    Catch::SourceLineInfo fake_source_line("?", 0)
    Catch::TestCaseInfo fake_test_case("?", "?", "?", {expensive_tag}, fake_source_line);

    Catch::Session session;
    session.applyCommandLine(argc, argv);
    auto test_spec = session.config().testSpec();
    const bool want_integration_tests = test_spec.matches(fake_test_spec);

    if(want_integration_tests)
    {
        do_expensive_setup();
    }
    
    return session.run();
}

And then my test file is just:

#include "catch.hpp"
...

TEST_CASE("expensive-to-initialize system", "[.integration]")
{
    REQUIRE(expensive_setup_is_done());

    SECTION("has property 1") { ... }
    SECTION("has property 2") { ... }
    ...
}

Note that because there are multiple sections (and, in my actual project, multiple test cases) that depend on the global setup, I can't just move the initialization into the top of the TEST_CASE.

Is there a better way?


Solution

  • Just do the initialization on demand, using something like std::call_once:

    TEST_CASE("expensive-to-initialize system", "[.integration]")
    {
        static std::once_flag init_flag;
        std::call_once(init_flag, do_expensive_setup);
        
        // ...
    }
    

    This will ensure that do_expensive_setup is called one time, but only if needed. If there are multiple places that need this setup, just wrap this in a function.

    Note that if do_expensive_setup throws, it will may be called a second time. But once the function successfully exits, that's it.