I have many testing source files in which I use Boost.Test and I'm trying to run them, but get multiple definition of boost::unit_test::runtime_config::argument_store()
error and the same for many modules of this library.
For example, there are two files: test_size.cpp
#define BOOST_TEST_MODULE Test_Repeats
#include "../include/test_config.h"
BOOST_AUTO_TEST_CASE(test_repeats) {
int n = 30;
Repeats r = Repeats(n);
BOOST_REQUIRE(r.rep == n);
}
And test_repeats.cpp
:
#define BOOST_TEST_MODULE Test_Size
#include "../include/test_config.h"
BOOST_AUTO_TEST_CASE(test_size) {
int n = 20;
int m = 30;
Size sz = Size(n, m);
BOOST_REQUIRE(sz.n == n && sz.m == m);
}
And test_config.h
:
#include <boost/test/included/unit_test.hpp>
#include "../include/size.h"
#include "../include/repeats.h"
Here is my Makefile
to run tests:
FAST_HEADERS := $(wildcard Fast/**/*.h)
FAST_TEST_SOURCES := $(filter-out Fast/src/main.cpp, $(wildcard Fast/src/*.cpp Fast/tests/test_size.cpp Fast/tests/test_repeats.cpp))
FAST_TEST_OBJECTS := ${FAST_TEST_SOURCES:.cpp=.o}
fast_test: $(FAST_TEST_OBJECTS)
g $(FAST_TEST_OBJECTS) -lboost_unit_test_framework -o fast_test
./fast_test
%.o: Fast/**/%.cpp $(FAST_HEADERS)
g $@ -o $<
I know that it's possible to have all tests in one source file and it will work well, but I'm curious whether it's possible to distribute all test units in different source files to make it more structured.
What is the problem with my Makefile
or with source files?
CodePudding user response:
%.o: Fast/**/%.cpp $(FAST_HEADERS)
g $@ -o $<
This never applies due to **
. You also reversed $@
and $<
. You could use
Fast/tests/%.o: Fast/tests/%.cpp | $(FAST_HEADERS)
Fast/src/%.o: Fast/src/%.cpp | $(FAST_HEADERS)
%.o: %.cpp
g -o $@ $<
The Real Problem
That said, let's find out what the problem is with the linker messages. To be honest, I spent way more time than I'm comfortable admitting here to "accidentally" find it. First I, obviously per-used the documentation, mainly:
- https://www.boost.org/doc/libs/1_53_0/libs/test/doc/html/utf/user-guide/test-runners.html
- notably the section on "Dynamic library variant of the UTF" which helpfully mentions
For a multi-file test module flags can't be defined in makefile and have to be defined in only one of the test files to avoid duplicate copies of the function
main()
.
And also several mailing list/SO posts:
- boost test library: Multiple definition error
- Boost Test Error: main already defined
- and some others
Honestly the only tangible thing I learned was that
However, the other documented flags BOOST_TEST_DYN_LINK
and BOOST_TEST_NO_MAIN
had zero effect. After some trial and error and preprocessor #error
debugging I found out that those symbols are all just undef
-ined after inclusion of
#include <boost/test/included/unit_test.hpp>
I didn't immediately recognize the /included/
part. On a whim I thought to change to:
#include <boost/test/unit_test.hpp>
That worked, but now main
was undefined. So, I came up with a the following refactored makefile and an extra Fast/tests/module.cpp
to define the main. No doubt this is all still not completely optimal. E.g. I think, since you're linking the dynamic library, BOOST_TEST_DYN_LINK ought to be defined, but it appears to work fine without that ¯\(ツ)/¯.
So part of the Makefile shown here is for inspiration on how to achieve particular advanced tasks in case you need them.
test: # make default target
FAST_HEADERS := $(wildcard Fast/**/*.h)
FAST_TEST_SOURCES := $(filter-out Fast/src/main.cpp, $(wildcard Fast/src/*.cpp Fast/tests/*.cpp))
FAST_TEST_OBJECTS := ${FAST_TEST_SOURCES:.cpp=.o}
CPPFLAGS = $(INCLUDES)
CPPFLAGS = -D BOOST_TEST_DYN_LINK
CXXFLAGS = -std=c 11 $(CPPFLAGS)
LDFLAGS = -lboost_unit_test_framework
Fast/tests/test_%.o: CPPFLAGS =-DBOOST_TEST_NO_MAIN
Fast/tests/%.o: Fast/tests/%.cpp | $(FAST_HEADERS)
Fast/src/%.o: Fast/src/%.cpp | $(FAST_HEADERS)
%.o: %.cpp
g $(CXXFLAGS) -o $@ -c $<
fast_test: $(FAST_TEST_OBJECTS)
g $(CXXFLAGS) $^ $(LDFLAGS) -o $@
test: fast_test
./$<
.PHONY: test
Testing with make -Bsn
shows how the flags combine:
g -std=c 11 -D BOOST_TEST_DYN_LINK -o Fast/src/size.o -c Fast/src/size.cpp
g -std=c 11 -D BOOST_TEST_DYN_LINK -o Fast/src/repeats.o -c Fast/src/repeats.cpp
g -std=c 11 -D BOOST_TEST_DYN_LINK -DBOOST_TEST_NO_MAIN -o Fast/tests/test_size.o -c Fast/tests/test_size.cpp
g -std=c 11 -D BOOST_TEST_DYN_LINK -DBOOST_TEST_NO_MAIN -o Fast/tests/test_repeats.o -c Fast/tests/test_repeats.cpp
g -std=c 11 -D BOOST_TEST_DYN_LINK -o Fast/tests/module.o -c Fast/tests/module.cpp
g -std=c 11 -D BOOST_TEST_DYN_LINK Fast/src/size.o Fast/src/repeats.o Fast/tests/test_size.o Fast/tests/test_repeats.o Fast/tests/module.o
-lboost_unit_test_framework -o fast_test
./fast_test
The output of e.g. ./fast_test -l all
:
Running 2 test cases...
Entering test module "Fast_Tests"
Fast/tests/test_size.cpp(3): Entering test case "test_size"
Fast/tests/test_size.cpp(8): info: check sz.n == n && sz.m == m has passed
Fast/tests/test_size.cpp(3): Leaving test case "test_size"; testing time: 136us
Fast/tests/test_repeats.cpp(3): Entering test case "test_repeats"
Fast/tests/test_repeats.cpp(7): info: check r.rep == n has passed
Fast/tests/test_repeats.cpp(3): Leaving test case "test_repeats"; testing time: 125us
Leaving test module "Fast_Tests"; testing time: 311us
*** No errors detected
Loose Ends
I kept the "test-driven" implementations for Size
/Repeats
header-only. You need to link the dependent objects if you change that for your real code.
For the GNU Make features I didn't explain, see https://www.gnu.org/software/make/manual/
Code Dump:
File
Makefile
test: # make default target FAST_HEADERS := $(wildcard Fast/**/*.h) FAST_TEST_SOURCES := $(filter-out Fast/src/main.cpp, $(wildcard Fast/src/*.cpp Fast/tests/*.cpp)) FAST_TEST_OBJECTS := ${FAST_TEST_SOURCES:.cpp=.o} CPPFLAGS = $(INCLUDES) CPPFLAGS = -D BOOST_TEST_DYN_LINK CXXFLAGS = -std=c 11 $(CPPFLAGS) LDFLAGS = -lboost_unit_test_framework Fast/tests/test_%.o: CPPFLAGS =-DBOOST_TEST_NO_MAIN Fast/tests/%.o: Fast/tests/%.cpp | $(FAST_HEADERS) Fast/src/%.o: Fast/src/%.cpp | $(FAST_HEADERS) %.o: %.cpp g $(CXXFLAGS) -o $@ -c $< fast_test: $(FAST_TEST_OBJECTS) g $(CXXFLAGS) $^ $(LDFLAGS) -o $@ test: fast_test ./$< .PHONY: test
File
Fast/include/repeats.h
#pragma once struct Repeats { explicit Repeats(int n) : rep(n){} int const rep; };
File
Fast/include/size.h
#pragma once struct Size { explicit Size(int n, int m) : n(n) , m(m) { } int const n, m; };
File
Fast/include/test_config.h
#include <boost/test/unit_test.hpp> #include "../include/size.h" #include "../include/repeats.h"
File
Fast/src/main.cpp
File
Fast/src/repeats.cpp
File
Fast/src/size.cpp
File
Fast/tests/module.cpp
#define BOOST_TEST_MODULE Fast_Tests #include <boost/test/included/unit_test.hpp>
File
Fast/tests/test_repeats.cpp
#include "../include/test_config.h" BOOST_AUTO_TEST_CASE(test_repeats) { int n = 30; Repeats r = Repeats(n); BOOST_REQUIRE(r.rep == n); }
File
Fast/tests/test_size.cpp
#include "../include/test_config.h" BOOST_AUTO_TEST_CASE(test_size) { int n = 20; int m = 30; Size sz = Size(n, m); BOOST_REQUIRE(sz.n == n && sz.m == m); }