GTest not finding tests in separate compilation units

googletest

Solution

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`

Problem

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

Original source