How to set up Catch2 in Bazel project

1.5k views Asked by At

I have started a simple C++ project that uses Bazel as build system and would like to add Catch2 to it, as test framework.

This is what my project looks like so far:

WORKSPACE -> empty file
src/
  Money.hpp
  Money.cpp
BUILD

where BUILD is just

cc_library(
  name = "Money",
  srcs = ["Money.cpp"],
  hdrs = ["Money.hpp"]
)

I would like to be able to create tests for each cc_library, in this case for Money. I tried setting it up but got confused with Catch2 main. Any advice on how to do this best is appreciated!

4

There are 4 answers

0
Martinsos On BEST ANSWER

After some back and forth I managed to get this working, for Bazel 0.16.1 and Catch2 2.4.0.

First let's create directory test/ next to src/, to keep our tests there.

In order to use Catch2, we need to download catch.hpp. Catch2 is header only library, meaning that one file is all we need. I put it in test/vendor/catch2/.
Then, we need to define to bazel how to use it. In test/vendor/catch2 we create following BUILD file:

cc_library(
  name = "catch2",
  hdrs = ["catch.hpp"],
  visibility = ["//test:__pkg__"]
)

Now Bazel recognizes Catch2 as a library. We added visibility attribute, so that it can be used from the //test package (which is defined by BUILD in /test directory).

Next, Catch2 requires us to define one translation unit with correctly defined main method. Following their instructions, we create test/main.cpp file:

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

Now, we write our test, in test/Money.test.cpp:

#include "catch.hpp"
#include "Money.hpp"

TEST_CASE("Money works.") {
  ...
}

Finally, we need to explain to Bazel how to build all this. Notice that we directly included Money.hpp and catch.hpp in our files, with no relative path, so that is also smth we need to keep in mind. We create following test/BUILD file:

# We describe to Bazel how to build main.cpp.
# It includes "catch.hpp" directly, so we need to add
# "-Itest/vendor/catch2" compiler option.
cc_library(
    name = "catch-main",
    srcs = ["main.cpp"],
    copts = ["-Itest/vendor/catch2"],
    deps = [
        "//test/vendor/catch2"
    ]
)

# Here we define our test. It needs to build together with the catch2
# main that we defined above, so we add it to deps. We directly
# include src/Money.hpp and test/vendor/catch2/catch.hpp in
# Money.test.cpp, so we need to add their parent directories as copts.
# We also add Money and catch2 as dependencies.
cc_test(
    name = "Money",
    srcs = ["Money.test.cpp"],
    copts = ["-Itest/vendor/catch2/", "-Isrc/"],
    deps = [
        # Or "//test/vendor/catch2:catch2", it is the same.
        "//test/vendor/catch2",
        "catch-main",
        "//src:Money"
    ]
)

# Test suite that runs all the tests.
test_suite(
    name = "all-tests",
    tests = [
        "Money"
    ]
)

Finally, we just need to add visibility attribute to src/BUILD so that it can be accessed from tests. We modify src/BUILD to look like this:

cc_library(
    name = "Money",
    srcs = ["Money.cpp"],
    hdrs = ["Money.hpp"],
    visibility = ["//test:__pkg__"]
)

Final file structure looks like this:

WORKSPACE
src/
  Money.hpp
  Money.cpp
  BUILD
test/
  BUILD
  main.cpp
  Money.test.cpp
  vendor/
    catch2/
      catch.hpp
      BUILD

Now you can run your tests with bazel test //test:all-tests!

I created Github repo with this example, you can check it out here. I also turned it into a blog post.

1
V Tolmer On

Bazel support was upstreamed to catch2: https://github.com/catchorg/Catch2/pull/1923

Using that, you just need to add to your WORKSPACE:

http_archive(
  name = "com_github_catchorg_catch2",
  urls = ["https://github.com/catchorg/Catch2/archive/v2.12.1.tar.gz"],
  strip_prefix = "Catch2-2.12.1",
  sha256 = "e5635c082282ea518a8dd7ee89796c8026af8ea9068cd7402fb1615deacd91c3",
)

(replace the version and sha256 with the right values if you want a newer version).

Then your build file can simply do:

cc_library(
  name = "test_main",
  srcs = ["test_main.cpp"],
  deps = ["@com_github_catchorg_catch2//:catch2"],
)

with a test_main.cpp that contains:

#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"

Finally, you can define tests like this:

cc_library(
  name = "my_test",
  srcs = ["my_test.cpp"],
  deps = [
      ":test_main",
      "@com_github_catchorg_catch2//:catch2",
  ],
)
0
Vertexwahn On

Catch2 (v2.13.0) is supported by Bazel out of the box.

Example

WORKSPACE.bazel:

workspace(name = "Catch2Demo")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "catch2",
    strip_prefix = "Catch2-2.13.0",
    urls = ["https://github.com/catchorg/Catch2/archive/v2.13.0.tar.gz"],
)

BUILD.bazel:

cc_test(
    name = "my_test",
    srcs = ["my_test.cpp"],
    defines = ["CATCH_CONFIG_MAIN"],
    deps = [
        "@catch2",
    ],
)

my_test.cpp:

#include <catch2/catch.hpp>

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

TEST_CASE( "Factorials are computed", "[factorial]" ) {
    REQUIRE( Factorial(1) == 1 );
    REQUIRE( Factorial(2) == 2 );
    REQUIRE( Factorial(3) == 6 );
    REQUIRE( Factorial(10) == 3628800 );
}
0
user7610 On

Catch2 v3 (3.1.1)

Catch2 v3 is split into multiple headers, requires C++14 or greater, and is no longer header-only, which slightly changes things

WORKSPACE.bazel:
workspace(name = "Catch2Demo")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "catch2",
    strip_prefix = "Catch2-3.1.1",
    urls = ["https://github.com/catchorg/Catch2/archive/v3.1.1.tar.gz"],
)
BUILD.bazel:
cc_test(
    name = "my_test",
    srcs = ["my_test.cpp"],
    copts = ["-std=c++14"],
    deps = [
        "@catch2//:catch2_main",
    ],
)
my_test.cpp:
#include <catch2/catch_test_macros.hpp>

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

TEST_CASE( "Factorials are computed", "[factorial]" ) {
REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
REQUIRE( Factorial(10) == 3628800 );
}

Now, run tests with

bazelisk test //:my_test --cxxopt='-std=c++14'