Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GTest not finding tests in separate compilation units

Tags:

googletest

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

like image 626
Coran Avatar asked Jul 28 '13 21:07

Coran


People also ask

Does Gtest run tests in parallel?

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).

Does Gtest work with C?

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.


1 Answers

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:-

  • Write a header file, cool_test.h
  • #include "cool.h" in it
  • #include <gtest/gtest.h> in it.
  • Then define your libcool test cases in it
  • #include "cool_test.h" in tests.cpp,
  • Compile and link 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

like image 195
Mike Kinghan Avatar answered Nov 06 '22 00:11

Mike Kinghan