Skip to content

Catch2 C++20 module example. #2983

Open
@gwankyun

Description

@gwankyun
// src/catch2.session.ixx
module;
#include <catch2/catch_session.hpp>

export module catch2.session;

export namespace Catch
{
    using Catch::Session;
} // namespace Catch
// src/catch.ixx
module;
#include <catch2/catch_test_macros.hpp>

export module catch2;
export import catch2.session;
import std;

::Catch::SourceLineInfo lineInfo(std::source_location _location)
{
    return ::Catch::SourceLineInfo(_location.file_name(), static_cast<std::size_t>(_location.line()));
}

void test(
    bool _expression, std::string _message, Catch::StringRef _macroName,
    Catch::ResultDisposition::Flags _resultDisposition, std::source_location _location = std::source_location::current()
)
{
    do
    {
        Catch::AssertionHandler catchAssertionHandler(
            _macroName, lineInfo(_location), Catch::StringRef(_message), _resultDisposition
        );
        try
        {
            catchAssertionHandler.handleExpr(Catch::Decomposer() <= _expression);
        }
        catch (...)
        {
            (catchAssertionHandler).handleUnexpectedInflightException();
        }
        catchAssertionHandler.complete();
    } while ((void)0, (false) && static_cast<const bool&>(!!(_expression)));
}

export namespace Catch
{
    void require(
        bool _expression, std::string _message = "", std::source_location _location = std::source_location::current()
    )
    {
        test(_expression, _message, "REQUIRE"_catch_sr, Catch::ResultDisposition::Normal, _location);
    }

    void check(
        bool _expression, std::string _message = "", std::source_location _location = std::source_location::current()
    )
    {
        test(_expression, _message, "CHECK"_catch_sr, Catch::ResultDisposition::ContinueOnFailure, _location);
    }

    void test_case(
        std::string _name, std::string _tags, void (*_fn)(),
        std::source_location _location = std::source_location::current()
    )
    {
        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
        const Catch::AutoReg autoRegistrar(
            Catch::makeTestInvoker(_fn), lineInfo(_location), Catch::StringRef(), Catch::NameAndTags{_name, _tags}
        );
        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
    }

    void session(
        std::string _message, std::function<void()> _fn,
        std::source_location _location = std::source_location::current()
    )
    {
        if (Catch::Section const& catch_internal_Section = Catch::Section(lineInfo(_location), _message))
        {
            _fn();
        }
    }
} // namespace Catch

example:

// src/main.cpp
import std;
import catch2;

#ifndef CATCH_STRING
#  define CATCH_STRING(_str) #_str
#endif // !CATCH_STRING

#ifndef CATCH_EXPRESSION
#  define CATCH_EXPRESSION(_expression) _expression, CATCH_STRING(_expression)
#endif // !CATCH_EXPRESSION

#ifndef REQUIRE
#  define REQUIRE(_expression) Catch::require(CATCH_EXPRESSION(_expression))
#endif // !REQUIRE

#ifndef CHECK
#  define CHECK(_expression) Catch::check(CATCH_EXPRESSION(_expression))
#endif // !CHECK

unsigned int Factorial(unsigned int number)
{
    return number <= 1 ? number : Factorial(number - 1) * number;
}

void factorials_are_computed()
{
    REQUIRE(Factorial(1) == 1);
    REQUIRE(Factorial(2) == 2);
    REQUIRE(Factorial(3) == 6);
    REQUIRE(Factorial(10) == 3628800);
}

void vectors_can_be_sized_and_resized()
{
    using Catch::session;
    // This setup will be done 4 times in total, once for each section
    std::vector<int> v(5);

    REQUIRE(v.size() == 5);
    REQUIRE(v.capacity() >= 5);

    session(
        "resizing bigger changes size and capacity",
        [&]
        {
            v.resize(10);

            REQUIRE(v.size() == 10);
            REQUIRE(v.capacity() >= 10);
        }
    );
    session(
        "resizing smaller changes size but not capacity",
        [&]
        {
            v.resize(0);

            REQUIRE(v.size() == 0);
            REQUIRE(v.capacity() >= 5);
        }
    );
    session(
        "reserving bigger changes capacity but not size",
        [&]
        {
            v.reserve(10);

            REQUIRE(v.size() == 5);
            REQUIRE(v.capacity() >= 10);
        }
    );
    session(
        "reserving smaller does not change size or capacity",
        [&]
        {
            v.reserve(0);

            REQUIRE(v.size() == 5);
            REQUIRE(v.capacity() >= 5);
        }
    );
}

int main(int _argc, char* _argv[])
{
    using Catch::test_case;
    test_case("Factorials are computed", "[factorial]", &factorials_are_computed);
    test_case("vectors can be sized and resized", "[vector]", &vectors_can_be_sized_and_resized);

    {
        test_case(
            "base", "[lambda]",
            []
            {
                using namespace std::literals::string_literals;
                REQUIRE(1 + 2 == 3);
                REQUIRE("123"s + "456"s == "123456"s);
            }
        );
    }

    test_case(
        "without macro", "[lambda]",
        []
        {
            Catch::require(1 + 2 == 3, "1 + 2 == 3");
            Catch::check(false, "false");
        }
    );

    auto result = Catch::Session().run(_argc, _argv);
    return result;
}

CMake

# CMakeLists.txt
cmake_minimum_required(VERSION 3.28...4.0)

project(catch2-module-example)

find_package(Catch2 CONFIG REQUIRED)

set(msvc_options)
list(APPEND msvc_options "/W4" "/MP")

list(APPEND msvc_options "/experimental:module")

list(APPEND msvc_options "/utf-8")

add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:${msvc_options}>")

add_library(catch2_module)
target_sources(catch2_module
  PRIVATE FILE_SET CXX_MODULES FILES
    src/catch2.ixx
    src/catch2.session.ixx)
target_include_directories(catch2_module PUBLIC include)
target_link_libraries(catch2_module PUBLIC Catch2::Catch2)
target_compile_features(catch2_module PRIVATE cxx_std_23)

add_executable(main)
target_sources(main
  PRIVATE
    src/main.cpp
)
target_link_libraries(main PRIVATE catch2_module)
target_compile_features(main PRIVATE cxx_std_23)

Full repo

I think Catch2 could consider adding support for modules.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions