Boost unit test dynamic linking on Ubuntu

2.8k views Asked by At

I am trying to build a unit test using Boost's unit test framework. I would like to dynamically link test suite libraries with the auto generated test module that Boost provides. Here is the basic construction I've been using:

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 executables compile and link without error.

'unittest' fails to execute the 'test_lib' suite claiming that the setup failed, but 'unittest2' and 'unittest3' 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* run the test suite on Fedora 20.

In looking at the dependency lists for 'unittest', I do see that 'libcase.so' is not listed in the Ubuntu version, but is in the Fedora 20 version. I have played around with re-ordering the dependencies, using absolute paths for the SO, and changing versions of Boost (1.54 and 1.55). Nothing worked.

Any ideas on what may preventing the 'libcase.so' from be linked on Ubuntu 14.04 but not on Fedora 20? Am I missing some magic compiler/linker flag?

Update:

Sehe's comment and answer have helped to narrow down the problem a bit more. If I'm understanding Boost's dynamically linked UTF implementation (at least as of 1.54/55) correctly, then the framework provides a test case manager singleton. Every test case will automagically register with the manager upon construction.

I think the problem is that, for whatever reason, linking on Ubuntu 'optimizes out' the static global variable used for the singleton instance of the manager during the linking of the library to the binary. In effect, it does not link the two singleton instances despite sharing the same global static variable. It treats them as two separate instances.

I followed the steps described in Multiple instances of singleton across shared libraries on Linux to inspect the library and binary files. Unlike in their case, the -rdynamic option does not solve my problem.

I did some more testing and found this interesting. If you preload libcase.so object, unittest works on Ubuntu. Even though the libcase.so does not appear in its ldd listing. I feel like this is expected because the singleton for the manager was 'preloaded' when unittest runs it will link with it.

$ LD_PRELOAD=/absolute/path/to/libcase.so ./unittest
Running 1 test case ...

Still have no idea why Ubuntu does not want to link as expected/intended, where Fedora does. Reading this tutorial (specifically the 'Comparison to the Microsoft DLL' section) makes me think Ubuntu is following a Windows linking pattern.

2

There are 2 answers

1
putnampp On BEST ANSWER

Got it!

Ubuntu seems to use the --as-needed linker option by default, where as Fedora may not. Turning it off will add libcase.so library to the needed 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

Figures it was something simple ...

2
sehe On

The problem is that the lib_case.o is optimized out, because there are no references to anything contained there.

If all the references refer from the test case definition to the unittest framework (for self-registration) but none back, compiling and linking the test main axes the "unused" library.

I could reproduce this on my system (Ubuntu 14). Here's a simple hack to show how you could fix it by forcing a reference (in this case to a global variable called force_reference_this_object_file.

Notes

  • of course you would normally declare that global in a header file
  • you'll find that you need to either deploy the 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