Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to compile multiple test sources with Catch2?

Tags:

c++

catch2

I have the following project structure:

test_main.cc

#define CATCH_CONFIG_MAIN

#include "catch2.hpp"

test1.cc

#include "catch2.hpp"
#include "test_utils.hpp"

TEST_CASE("test1", "[test1]") {
  REQUIRE(1 == 1);
}

test2.cc

#include "catch2.hpp"
#include "test_utils.hpp"

TEST_CASE("test2", "[test2]") {
  REQUIRE(2 == 2);
}

test_utils.hpp

#pragma once
#include <iostream>

void something_great() {
  std::cout << ":)\n";
}

If I compile using something like clang++ -std=c++17 test_main.cc test1.cc test2.cc, the function something_great is defined in both test1.o and test2.o. This leads to an error like

duplicate symbol __Z15something_greatv in:
    test1.cc.o
    test2.cc.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

In the Scaling Up section of the Catch2 documentation, they mention that in order to split up your tests you may want to

Use as many additional cpp files (or whatever you call your implementation files) as you need for your tests, partitioned however makes most sense for your way of working. Each additional file need only #include "catch.hpp"

but in the examples section of the documentation I don't see a use case like mine. I read this blog post which describes three solutions which don't appeal to me: defining functions as macros, or making functions static or inline.

Is there another way to compile these files which yield a single executable with the main function defined by test_main.cc?

like image 713
Collin Avatar asked Mar 14 '19 22:03

Collin


People also ask

What is catch2 in C++ testing?

In my book, Modern C++ Programming Cookbook, I discussed several testing frameworks for C++, more precisely, Boost.Test, Google Test, and Catch (which stands for C++ Automated Test Cases in a Header ). Since the publishing of the book, a new version of Catch, called Catch2 has been released.

What are assertions in catch2?

As with all testing frameworks, the two most fundamental parts of Catch2 are test cases that contain assertions. Assertions exist in the REQUIRE [1] macro and must be contained within a test case [2], which in turn is created using the TEST_CASE macro. The following simple example defines a single test case with 3 assertions.

How do I parametrise test cases in catch2?

Test cases in Catch2 can be also parametrised by type, via the TEMPLATE_TEST_CASE and TEMPLATE_PRODUCT_TEST_CASE macros, which behave in the same way the TEST_CASE macro, but are run for every type or type combination. For more details, see our documentation on test cases and sections.

Where to put catch2?

Where to put it? The simplest way to get Catch2 is to download the latest single header version. The single header is generated by merging a set of individual headers but it is still just normal source code in a header file. Alternative ways of getting Catch2 include using your system package manager, or installing it using its CMake package.


1 Answers

This actually has nothing to do with Catch or testing. When you #include a file in C++, it gets copy-pasted at the #include line verbatim. If you put free function definitions in headers, you would see this problem building your actual program, etc.

The underlying problem is that #include is not the same kind of import-a-module directive as is the equivalent directive (import, require, etc.) in most languages, which do the sane thing in a situation like this (confirm that the header is the same one we've already seen and ignore the repeated method definition).

The commenter that suggested you write inline is technically correct, in the sense that this will "solve your problem" because your compiler won't generate object code for the method multiple times. However, it doesn't really explain what's going on or address the underlying issue.


The clean solution is:

  • In test_utils.hpp, replace the method definition with a method declaration: void something_great();.
  • Create test_utils.cc with the definition of the method (which you currently have in the .hpp).
  • clang++ -std=c++17 test1.cc -c
  • clang++ -std=c++17 test2.cc -c
  • clang++ -std=c++17 test_main.cc -c
  • clang++ -std=c++17 test_utils.cc -c
  • clang++ -std=c++17 test1.o test2.o test_utils.o test_main.o

I also recommend you read this: What is the difference between a definition and a declaration?

Explicitly:

// test_utils.hpp
#pragma once

// This tells the compiler that when the final executable is linked,
// there will be a method named something_great which takes no arguments
// and returns void defined; the definition lives in test_utils.o in our
// case, although in practice the definition could live in any .o file
// in the final linking clang++ call.
void something_great();

And:

// test_utils.cpp
#include "test_utils.hpp"
#include <iostream>

// Generates a DEFINITION for something_great, which
// will get put in test_utils.o.
void something_great() { std::cout << "Hi\n"; }

It seems you are worried about "recompiling Catch" every time you make a change to a test. I hate to break it to you, but you are in C++ land now: you are going to be recompiling stuff pointlessly a lot. Header-only libraries like Catch MUST be "recompiled" to some extent when a source file including them changes, because for better or worse, if the source file or a header file included transitively from the source file includes catch2.hpp, then the source code of catch2.hpp will get parsed by the compiler when that source file is read.

like image 154
Andrey Mishchenko Avatar answered Oct 20 '22 11:10

Andrey Mishchenko