Boost unit test dynamic link on Ubuntu
I am trying to create a unit test using Boost unit test. I would like to dynamically link test suite libraries with an auto-generated test unit that Boost provides. Here's the basic construction I used:
test_main.cpp:
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>
lib_case.cpp:
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_SUITE( test_lib )
BOOST_AUTO_TEST_CASE( test_lib_case ) {
BOOST_ASSERT(true);
}
BOOST_AUTO_TEST_SUITE_END()
Makefile:
all: unittest unittest2 unittest3
lib_case.o: lib_case.cpp
g++ -g -c -Wall -fPIC lib_case.cpp -o lib_case.o
libcase.so: lib_case.o
g++ -shared -Wl,-soname,libcase.so -o libcase.so lib_case.o
unittest: libcase.so
g++ -o unittest test_main.cpp -L. -lcase -lboost_unit_test_framework
unittest2: test_main.cpp lib_case.cpp
g++ -o unittest2 test_main.cpp lib_case.cpp -lboost_unit_test_framework
unittest3: lib_case.o
g++ -o unittest3 test_main.cpp lib_case.o -lboost_unit_test_framework
Testing on Ubuntu 14.04, all binaries compile and link without error.
'unittest' fails to execute package 'test_lib', claiming that the installation failed, but 'unittest2' and 'unittest3' will succeed:
$./unittest
Test setup error: test tree is empty
$./unittest2
Running 1 test case...
*** No errors detected
$./unittest3
Running 1 test case...
*** No errors detected
Now for the headache: all unittest * are running the test suite on Fedora 20.
When looking through the dependency lists for "unittest" I see that "libcase.so" is not listed in Ubuntu version but is in Fedora 20 version. I have been playing around with re-ordering dependencies using absolute paths for SO and modifying Boost versions (1.54 and 1.55 ). Nothing succeeded.
Any ideas on what might be preventing libbra.so from linking from Ubuntu 14.04 but not Fedora 20? Am I missing some magic compiler / linker flag?
Update:
The comment and answer helped narrow down the problem a little more. If I understand correctly the dynamic linked UTF implementation of Boost (since at least 1.54 / 55) then the framework provides a singleton test case dispatcher. Each test will be automatically registered with the construction manager.
I think the problem is that for some reason the Ubuntu linking "optimizes" the static global used for the singleton manager instance when linking the library to the binary. Basically, it does not bind two singleton instances, despite using the same global static variable. He views them as two separate instances.
I followed the steps outlined in Multiple Singleton Instances in Shared Libraries on Linux to check out the library and binaries. Unlike my case, the -rdynamic parameter does not solve my problem.
I did some more tests and found it interesting. If you preload the libcase.so object, unittest works on Ubuntu. Although libcase.so doesn't show up in ldd. I feel like this was expected because the singleton for the manager was "preloaded", when unittest runs it will bind to it.
$ LD_PRELOAD=/absolute/path/to/libcase.so ./unittest
Running 1 test case ...
Still not sure why Ubuntu doesn't want to bundle as expected / intended where Fedora is doing. Reading this tutorial (specifically the Comparison to Microsoft DLL section) makes me think Ubuntu follows the Windows binding pattern.
source to share
Got it!
Ubuntu seems to use the --as-needed
default linker option where Fedora might not work. If you disable it, add the libcase.so library to the desired list for unittest. After deploying the library (or using LD_LIBRARY_PATH) the unittest works now.
unittest: libcase.so
g++ -o unittest test_main.cpp -Wl,--no-as-needed -L. -lcase -lboost_unit_test_framework
The numbers were something simple ...
source to share
The problem is that it is lib_case.o
optimized because there are no references to anything in there.
If all references refer to the test case definition in the unittest framework (for self-registration), but do not return, compile and link the main test axes to the "unused" library.
I could reproduce this on my system (Ubuntu 14). Here's a simple hack to show how you could fix this by forcing a reference (in this case, a global variable called force_reference_this_object_file
.
Notes
- Of course, you usually declare that global in your header file
- you find that you either need to expand
libcase.so
or use LD_LIBRARY_PATH to include./
in the library path
lib_case.cpp
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
int force_reference_this_object_file = 42;
BOOST_AUTO_TEST_SUITE( test_lib )
BOOST_AUTO_TEST_CASE( test_lib_case ) {
BOOST_ASSERT(true);
}
BOOST_AUTO_TEST_SUITE_END()
test_main.cpp
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>
extern int force_reference_this_object_file;
namespace {
struct Local
{
int& ref_;
Local() : ref_(force_reference_this_object_file) {}
};
static Local hack_;
}
Makefile
all: unittest unittest3
CPPFLAGS=-Wall -fPIC
LDFLAGS+=-L ~/WORK/pocpp/3rdparty/boost_1_58_0/stage/lib/
%.o: %.cpp
g++ -c $(CPPFLAGS) $^ -o $@
libcase.so: lib_case.o
g++ $(CPPFLAGS) -shared -Wl,-soname,$@ -o $@ $^
unittest: test_main.o | libcase.so
#g++ $(CPPFLAGS) -o $@ $< $(LDFLAGS) -L. -lcase -lboost_unit_test_framework
g++ $(CPPFLAGS) -o $@ $< $(LDFLAGS) ./libcase.so -lboost_unit_test_framework
unittest3: test_main.o lib_case.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -lboost_unit_test_framework
source to share