Use Zig to test c/cpp files with gtest

Hey,
I’m trying to build a template file for building all my c++ projects easily, and I want to also include tests via gtest. Currently, I have it easily compiling my cpp code, but for zig run test I can’t figure out how I would link what is needed. Typically the flags -lgtest -ltest_main -pthread but I can’t figure out how to add the -pthread to the script. So far, the test part of my script is as following:

const unit_tests = b.addExecutable(.{ .name = "tests", .target = target });
unit_tests.addCSourceFiles(.{
    .files = &[_][]const u8{
        "tests/main.cpp",
    },
    .flags = &[_][]const u8{"-pthread"},
});
unit_tests.linkLibCpp();
unit_tests.linkSystemLibrary("gtest");
unit_tests.linkSystemLibrary("gtest_main");

b.installArtifact(unit_tests);

const run_unit_tests = b.addRunArtifact(unit_tests);
run_unit_tests.step.dependOn(b.getInstallStep());

const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);

tests/main.cpp simply has this:

#include <gtest/gtest.h>

#include <stdio.h>
TEST(SimpleTest, BasicAssertion) {
    EXPECT_EQ(1 + 1, 2);
}
int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

FYI we have packaged googletest so you could turn that system dependency into a normal Zig dependency that the package manager can fulfill directly :^)

That said, going back to your project, if you also link to libc (linkLibC()) (and remove the manual -pthread flag), what errors do you get from the build?

1 Like

I added linkLibC() above linkLibCpp() and removed that pthread flag, I’ve noticed specifically the

TEST(SimpleTest, BasicAssertion) {
    EXPECT_EQ(1 + 1, 2);
}

part of the code causes the issues, and it gives me this output when it’s uncommented:

run
└─ run main
   └─ install
      └─ install tests
         └─ zig build-exe tests Debug native 2 errors
error: ld.lld: undefined symbol: testing::internal::MakeAndRegisterTestInfo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, char const*, char const*, char const*, testing::internal::CodeLocation, void const*, void (*)(), void (*)(), testing::internal::TestFactoryBase*)
    note: referenced by main.cpp:4 (tests/main.cpp:4)
    note:               /home/ben/Code/Zig/build_system/.zig-cache/o/9d32c30b6c665ad90dacdb8b58639a54/main.o:(__cxx_global_var_init)
error: ld.lld: undefined symbol: testing::internal::EqFailure(char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, bool)
    note: referenced by gtest.h:1377 (/usr/include/gtest/gtest.h:1377)
    note:               /home/ben/Code/Zig/build_system/.zig-cache/o/9d32c30b6c665ad90dacdb8b58639a54/main.o:(testing::AssertionResult testing::internal::CmpHelperEQFailure<int, int>(char const*, char const*, int const&, int const&))
error: the following command failed with 2 compilation errors:
/usr/bin/zig build-exe /home/ben/Code/Zig/build_system/tests/main.cpp -DGTEST_HAS_PTHREAD=1 -lgtest -DGTEST_HAS_PTHREAD=1 -lgtest_main -lgtest -ODebug -Mroot -lc++ -lc --cache-dir /home/ben/Code/Zig/build_system/.zig-cache --global-cache-dir /home/ben/.cache/zig --name tests --listen=- 
Build Summary: 2/7 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
└─ run main transitive failure
   └─ install transitive failure
      └─ install tests transitive failure
         └─ zig build-exe tests Debug native 2 errors
error: the following build command failed with exit code 1:
/home/ben/Code/Zig/build_system/.zig-cache/o/508c21c553b9dddfe4b08e0fd95c7fc1/build /usr/bin/zig /home/ben/Code/Zig/build_system /home/ben/Code/Zig/build_system/.zig-cache /home/ben/.cache/zig --seed 0x7b0e1f3a -Z59feabae1ff3f6c4 run

These errors are about gtest, so that’s what you need to fix next. Maybe the version installed in your system is mismatched with what you expect in your project? That might explain the first error, not sure about the second.

Do you have a way of knowing precisely which version of gtest you expect in your codebase and if your system is providing it directly (or a compatible version at least)?

Maybe the suggestion to use the one packaged in allyourcodebase isn’t that bad afterall :^)

I’ll have to admit I’m a bit ignorant to gtest and about Zig, generally. I’m trying to do a basic ASSERT_EQ as seem above, and I can confirm it does with with g++. Is there a way I can use the allyourcodebase one, to see if that works? I certain would like to use Zig as a package manager if I am able to, ideally to help with building on Windows as well since I’m not a fan of manually linking libraries.

Unfortunately troubleshooting C/C++ build pipelines tends to require you to get familiar with the process. In this case, if another system compiler builds everything correctly, it’s probably a missing flag, but exactly which and where does require knowing how the build works.

I can give you some instructions to use Zig to build googletest but let me warn you right away that whatever issue is causing your build to fail might not go away simply by sourcing the dependency through Zig.

Keeping that in mind, in allyourcodebase there’s another repo that depends on googletest, you can copy the relevant lines from there:

First add googletest as a dependency in your build.zig.zon:

Then, in your build.zig, obtain a reference to the dependency:

Finally, link the library:

Note that in your case you also seem to want gtest_main, so make sure to link that too.

1 Like

This worked perfectly! Thank you so much for your help!

As a followup, do you know of any resources you can link me about the Zig build system and Zon package manager? The more I know about what the build system can do, the better, and libraries will be much better to work with if I can package them simply with Zon, such as the classic SDL2 library (Which I have figured out how to build with addLibraryPath() and installBinFile)

1 Like