I've got a program written in C++, with some subfolders containing libraries linked in. There's a top level SConscript, which calls SConscript files in the subfolders/libraries.
Inside a library cpp, there is a GTest test function:
TEST(X, just_a_passing_test) {
EXPECT_EQ(true, true);
}
There is main()
in the top level program source, which just calls GTests main, and has another GTest test within it:
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
TEST(Dummy, should_pass){
EXPECT_EQ(true, true);
}
Now the issue is that when I run the program, GTest only runs the test in the main.cpp
source. Ignoring the test in the library. Now it gets bizarre when I reference an unrelated class in the same library cpp in main.cpp
, in a no side-effect kind of way (eg. SomeClass foo;
), the test magically appears. I've tried using -O0 and other tricks to force gcc to not optimize out code that isn't called. I've even tried Clang.
I suspect it's something to do with how GTest does test discovery during compilation, but I can't find any info on this issue. I believe it uses static initialization, so maybe there's some weird ordering going on there.
Any help/info is greatly appreciated!
Update: Found a section in the FAQ that sounds like this problem, despite it referring specifically to Visual C++. Which includes a trick/hack to force the compiler to not discard the library if not referenced. It recommends not putting tests in libraries, but that leaves me wondering how else would you test libraries, without having an executable for every one, making quickly running them a pain and with bloated output. https://code.google.com/p/googletest/wiki/Primer#Important_note_for_Visual_C++_users
gtest-parallel is a script that executes Google Test binaries in parallel, providing good speedup for single-threaded tests (on multi-core machines) and tests that do not run at 100% CPU (on single- or multi-core machines).
As all Google's C++ code, Google Test does not use exceptions, so exception safety flow won't be an issue. As long as your headers are C++-compatible (not using C++ keywords, export symbols with correct linkage), it should be fine.
From the scene-setting one gathers that the library whose gtest
test case
goes missing is statically linked in the application build. Also that the
GNU toolchain is in use.
The cause of the problem behaviour is straightforward. The test
program contains no references to anything in the library that contains
TEST(X, just_a_passing_test)
. So the linker doesn't need to link any
object file from that library to link the program. So it doesn't. So the
gtest
runtime doesn't find that test in the executable, because it's not there.
It helps to understand that a static library in GNU format is an archive of object files, adorned with a house-keeping header block and a global symbol table.
The OP discovered that by coding in the program an ad hoc reference to any public symbol in the problem library, he could "magically" compel its test case into the program.
No magic. To satisfy the reference to that public symbol, the linker is
now obliged to link an object file from the library - the one that contains
the definition of the symbol. And the OP imparts that the library is made
from a .cpp
. So there is only one object file in the library, and it
contains the definition of the test case, too. With that object file in the
linkage, the test case is in program.
The OP twiddled in vain with the compiler options, switching from GCC to clang,
in search of a more respectable way to achieve the same end. The compiler is
irrelevant. GCC or clang, it gets its linking done by the system linker, ld
(unless unusual measures have been taken to replace it).
Is there a more respectable way to get ld
to link an object file from a
static library even when the program refers to no symbols in that object file?
There is. Say the problem program is app
and the problem static library is
libcool.a
Then the usual GCC commandline that links app
resembles this, in the relevant
points:
g++ -o app -L/path/to/the/libcool/archive -lcool
This delegates a commandline to ld
, with additional linker options and
libraries that g++
deems to be defaults for the system where it finds itself.
When the linker comes to consider -lcool
, it will figure out this is a request
for the archive /path/to/the/libcool/archive/libcool.a
. Then it will figure
out whether at this point it has still got any unresolved symbol references in hand
whose definitions are compiled in object files in libcool.a
. If there are
any, then it will link those object files into app
. If not, then it links
nothing from libcool.a
and passes on.
But we know there are symbol definitions in libcool.a
that we want to
link, even though app
does not refer to them. In that case, we can tell
the linker to link the object files from libcool.a
even though they are
not referenced. More precisely, we can tell g++
to tell the linker to do that,
like so:
g++ -o app -L/path/to/the/libcool/archive -Wl,--whole-archive -lcool -Wl,-no-whole-archive
Those -Wl,...
options tell g++
to pass the options ...
to ld
. The --whole-archive
option tells ld
to link all object files from subsequent archives, whether they
are referenced or not, until further notice. The -no-whole-archive
tells the
ld
to stop doing that and resume business as usual.
It may look as if -Wl,-no-whole-archive
is redundant, as it's the last thing on the
g++
commandline. But it's not. Remember that g++
appends system default libraries
to the commandline, behind the scenes, before passing it to the ld
. You definitely
do not want --whole-archive
to be in force when those default libraries are linked.
(The linkage will fail with multiple definition errors).
Apply this solution to the problem case and TEST(X, just_a_passing_test)
will be executed, without the hack of forcing the program to make some no-op
reference into the object file that defines that test.
There's an obvious downside to this solution in the general case. If it happens that the library from
which we want to force linkage of some unreferenced object file contains a
bunch of other unreferenced object files that we really don't need.
--whole-archive
links them all of them too, and they're just bloat in the program.
The --whole-archive
solution may be more respectable that the no-op reference
hack, but it's not respectable. It doesn't even look respectable.
The real solution here is just to do the reasonable thing. If you want the linker to link the definition of something in your program, then don't keep that a secret from the linker. At least declare the thing in each compilation unit where you expect its definition to be used.
Doing the reasonable thing with gtest
test-cases involves understanding that
a gtest
macro like TEST(X, just_a_passing_test)
expands to a class definition,
in this case:
class X_just_a_passing_test_Test : public ::testing::Test {
public:
X_just_a_passing_test_Test() {}
private:
virtual void TestBody();
static ::testing::TestInfo* const test_info_ __attribute__ ((unused));
X_just_a_passing_test_Test(X_just_a_passing_test_Test const &);
void operator=(X_just_a_passing_test_Test const &);
};
(plus a static initializer for test_info_
and a definition for TestBody()
).
Likewise for the TEST_F
, TEST_P
variants. Consequently, you can deploy these
macros in your code with just the same constraints and expectations that would
apply to class definitions.
In this light, if you have a library libcool
defined in cool.h
, implemented in cool.cpp
and want gtest
unit tests for it, to be executed by a test program tests
that is implemented in tests.cpp
, the reasonable thing is:-
cool_test.h
#include "cool.h"
in it#include <gtest/gtest.h>
in it. libcool
test cases in it#include "cool_test.h"
in tests.cpp
,tests.cpp
with libcool
and libgtest
And it's obvious why you wouldn't do what the OP has done. You would not define
classes that are needed by tests.cpp
, and not needed by cool.cpp
, within cool.cpp
and not in tests.cpp
.
The OP was averse to the advice against defining the test cases in the library because:
how else would you test libraries, without having an executable for every one, making quickly running them a pain.
As a rule of thumb I would recommend the practice of maintaining a gtest
executable
per library to be unit-tested: running them quickly is painless with commonplace automation tools
such a make
, and it's far better to get a pass/fail verdict per library than
just a verdict for a bunch of libraries. But if you don't want to do that there's still nothing to the
objection:
// tests.cpp
#include "cool_test.h"
#include "cooler_test.h"
#include "coolest_test.h"
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Compile and link with libcool
, libcooler
, libcoolest
and libgtest
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With