Page MenuHome

Link C/C++ unit tests into single executable
ClosedPublic

Authored by Sybren A. Stüvel (sybren) on May 7 2020, 3:23 PM.

Details

Summary

This is a first patch for T73268: Blender Linking Time & GTests. It includes:

  • A demo of what things would look like if we were to move the tests into the main source/ tree. I have moved the two blenkernel test cases there for now.
  • A test runner binary bin/tests/bf_gtest_runner_test that contains the above two tests, and is intended to contain all GTest tests (at a minimum, those tests that would link more than a tiny fraction of Blender's modules).

The test binary can be run with ctest. Not yet ported tests still build & run as before.

Please ignore the exact namespace in the test files for now. What I do want to have feedback on, is whether the test should be in:

  • the same namespace as the code under test,
  • in a tests sub-namespace, or
  • in an anonymous sub-namespace.

Diff Detail

Repository
rB Blender

Event Timeline

Sybren A. Stüvel (sybren) requested review of this revision.May 7 2020, 3:23 PM
Sybren A. Stüvel (sybren) created this revision.

So the tests would ship with blender? or are we going to build 2 blenders on the bot, one for testing and one for distro?

@Ray Molenkamp (LazyDodo) Good question. I don't see much harm in shipping with the tests enabled, although many of them won't run without the supporting files in SVN.

anyone aware of other software products that ship unit tests in their release binaries?

  • Moved test runner outside the main Blender binary.
  • Moved blenkernel test files next to the files under test.

This already improves the situation compared to the master branch, as it merges the BKE_armature and BKE_fcurve test into one binary bin/tests/bf_gtest_runner_test. I'm not a fan of the _test suffix there, but that's automatically added by the BLENDER_SRC_GTEST() macro, and can be addressed later if desired.

If the approach in this patch is approved, I'll continue moving more tests from tests/gtests to blender/ directory so they sit next to the files under test, and end up in the same binary.

Sybren A. Stüvel (sybren) retitled this revision from Link C/C++ unit tests with Blender executable to Link C/C++ unit tests into single executable.Jun 5 2020, 3:51 PM

For the namespace question: I think it should be tests sub-namespace of what's being tested. For example, blender::depsgraph::tests.
When some helpers are needed use anonymous namespace inside of the tests.

For the rest, seems quite clear implementation actually. Only lacking some explanation which will help adding new tests.

CMakeLists.txt
1631–1634

Do we care that some libraries (i.e. libmv) adds definitions for GFLAGS and GLOG?
I think this is harmless since compiler seems to be smart enough to not warn as long as definition is the same, but could be something to be cleaned up later.

build_files/cmake/macros.cmake
357

I think some explanation of what an intended usecase of this function is, and how it affects the build process.

tests/gtests/CMakeLists.txt
2–3

# This runner takes care of ...

tests/gtests/gtests_runner.cc
1 ↗(On Diff #25526)

Copyright.

14 ↗(On Diff #25526)

I would use vector<const char*> gtest_argv; and pass gtest_argv.data() to ParseCommandLineFlags.

Eliminates manual memory allocation/free.

24 ↗(On Diff #25526)

const int

tests/gtests/gtests_runner.h
1 ↗(On Diff #25526)

Copytight.

6 ↗(On Diff #25526)

Comment.

Brecht Van Lommel (brecht) requested changes to this revision.Jun 5 2020, 4:34 PM

Please ignore the exact namespace in the test files for now. What I do want to have feedback on, is whether the test should be in:

the same namespace as the code under test,
in a tests sub-namespace, or
in an anonymous sub-namespace.

Do the tests need to be in any namespace? The tests should already have unique names if they are going to be included in a single GTest executable, using a C++ namespace will not actually avoid naming conflicts. Any utility functions they use can be static or in an anonymous namespace.

CMakeLists.txt
1632–1634

Do we need to do all this globally? These are already being set in other places, which leads to duplicated defines and unclear code.

Is it possible to:

  • Move include(GTestTesting) into source/blender/blenkernel/CMakeLists.txt
  • Remove add_definitions(-DWITH_GTESTS) (not sure why it's needed?)
  • Move the other add_definitions into blender_add_test_lib
source/blender/blenkernel/CMakeLists.txt
711–713

Maybe this indicates the test should actually be located in editors/animation, or the tested code should be moved into blenkernel at some point

714

Can this be moved into blender_add_test_lib, or is it only needed for BKE tests?

tests/gtests/CMakeLists.txt
18

Maybe a good name for this executable is just blender_test rather than bf_gtest_runner_test?

I guess running it will work like any other test executable, the difference would be that for ctest it's called with specific arguments to run a subset of the tests.

tests/gtests/gtests_runner.cc
12–19 ↗(On Diff #25526)

I don't understand what this does, how is Blender test runner an indication of the command to run? This seems like a description rather than a command.

21 ↗(On Diff #25526)

This needs to be updated now that it's no longer part of the Blender executable (or removed?).

This revision now requires changes to proceed.Jun 5 2020, 4:34 PM
Sybren A. Stüvel (sybren) planned changes to this revision.Jun 5 2020, 5:26 PM
Sybren A. Stüvel (sybren) marked 7 inline comments as done.
Sybren A. Stüvel (sybren) edited the summary of this revision. (Show Details)
  • Moved BLENDER_GFLAGS_NAMESPACE/GFLAGS/GLOG defines away from global scope. This does add to the duplication between blender_add_test_lib and BLENDER_SRC_GTEST_EX, so I can look into that later.
  • Removed gtests_runner.{cc,h} as these were actuall needed for running the tests from Blender itself.
  • Moved remove_strict_flags() into GTestTesting.cmake
  • Added some clarifying comments
source/blender/blenkernel/CMakeLists.txt
711–713

It is for calling insert_vert_fcurve(), to get some keyframes in the fcurve to make it possible to do interpolation/extrapolation/evaluation tests. And yes, I agree it would be good to have such functions in blenkernel instead. I have added a note about this in my personal Code Quality Day ideas wiki page.

  • Updated namespaces

I had to add this to make it build, but I guess this is still work in progress (and not integrated with ctest?).

diff --git a/tests/gtests/CMakeLists.txt b/tests/gtests/CMakeLists.txt
index 68a44b28817..90f610899b1 100644
--- a/tests/gtests/CMakeLists.txt
+++ b/tests/gtests/CMakeLists.txt
@@ -21,9 +21,13 @@ if(WITH_GTESTS)
   endif()
   unset(_test_libs)

+  setup_libdirs()
+
   # This builds `bin/tests/blender_test`.
   BLENDER_SRC_GTEST(blender "blender_test.cc" "${TEST_LIBS}")

+  setup_liblinks(blender_test)
+
   # Build the not-yet-ported tests
   include(GTestTesting)

Other than that looks good so far.

I had to disable WITH_LIBMV to even get it through cmake

This patch does not pass the CMake step for me either, afaict it's missing the BLENDER_SRC_GTEST entirely?

  • Setup libdirs and liblinks as per Brecht's diff.
  • Add include(GTestTesting) to intern/libmv/CMakeLists.txt to get libmv building again.
  • Add buildinfo to the blender_test executable.

This makes things work from scratch.

Do the tests need to be in any namespace? The tests should already have unique names if they are going to be included in a single GTest executable, using a C++ namespace will not actually avoid naming conflicts. Any utility functions they use can be static or in an anonymous namespace.

I think it's nice to have the tests in the same namespace as the code under test. It could be in the global namespace too and have a using blender::module; statement at the top. I don't have strong feelings about this one.

I guess this is still work in progress (and not integrated with ctest?).

It's integrated with ctest on my machine. Running ctest -R blender -V shows that the mat3_vec_to_roll and evaluate_fcurve tests are run from blender_test. Is there any other way of running CTest that I should test with?

I think it's nice to have the tests in the same namespace as the code under test. It could be in the global namespace too and have a using blender::module; statement at the top. I don't have strong feelings about this one.

Personally that's what I would do, put it in the global namespace just like the C code it is testing. When testing C++ code in some namespace the tests can also be in the corresponding namespace. I don't feel strongly about this either though.

It's integrated with ctest on my machine. Running ctest -R blender -V shows that the mat3_vec_to_roll and evaluate_fcurve tests are run from blender_test. Is there any other way of running CTest that I should test with?

I was expecting the tests to remain individual ctests instead of all being part of one Blender ctest, invoking blender_tests multiple times with different --gtest_filter arguments.

I was expecting the tests to remain individual ctests instead of all being part of one Blender ctest, invoking blender_tests multiple times with different --gtest_filter arguments.

Ah, that clarifies things. Of course this can be done, but it has some downsides:

  • Each test invocation then has to load the same binary. I expect this to be potentially quite slow, as it can be 1.5 GB in debug mode. Loading that binary once and then running all tests will probably be faster, although it does increase the chance of one test influencing the test (as they'd then all run in the same process) and would prevent running the tests in parallel.
  • So far, the CMake commands that construct the tests only handle source & library filenames. The names of the tests are unrelated to those, so in order to have CMake generate individual add_test() calls, test names would either have to be repeated in the CMakeLists.txt files, or we'd need some way to obtain them from the input source or the generated test library.

Since the Python tests also start Blender many times over, I don't think the first point is that big. The second, however, is more of an issue.

  • Each test invocation then has to load the same binary. I expect this to be potentially quite slow, as it can be 1.5 GB in debug mode. Loading that binary once and then running all tests will probably be faster, although it does increase the chance of one test influencing the test (as they'd then all run in the same process) and would prevent running the tests in parallel.

I'm pretty sure the OS will mmap and load just the required symbols, so it's shouldn't be that bad.

  • So far, the CMake commands that construct the tests only handle source & library filenames. The names of the tests are unrelated to those, so in order to have CMake generate individual add_test() calls, test names would either have to be repeated in the CMakeLists.txt files, or we'd need some way to obtain them from the input source or the generated test library.

Maybe we can use gtest_discover_tests? Or copy the code for that from GoogleTest.cmake and tweak it if we need it to work a little different.
https://cmake.org/cmake/help/git-stage/module/GoogleTest.html

That or use a naming convention so every test in a file starts with the same prefix, but that's a bit fragile.

That or use a naming convention so every test in a file starts with the same prefix, but that's a bit fragile.

I would strongly suggest against using name.
You want to test any complex module with all sort of tests, and it would be wrong to stick to a single test case name, or to a single test fixture (and you can not name test case and test fixture the same name). Having an arbitrary prefix for all tests seems annoying super-imposed requirement which can be avoided.

  • CTest integration via the gtest_discover_tests() command.

As it turns out, my worries that it would be slow to expose individual tests were unfounded:

% ctest -R 'fcurve|mat3'
Test project /home/sybren/workspace/blender-git/build_linux
      Start 62: mat3_vec_to_roll.UnitMatrix
 1/10 Test #62: mat3_vec_to_roll.UnitMatrix ...............   Passed    0.05 sec
      Start 63: mat3_vec_to_roll.Rotationmatrix
 2/10 Test #63: mat3_vec_to_roll.Rotationmatrix ...........   Passed    0.03 sec
      Start 64: evaluate_fcurve.EmptyFCurve
 3/10 Test #64: evaluate_fcurve.EmptyFCurve ...............   Passed    0.03 sec
      Start 65: evaluate_fcurve.OnKeys
 4/10 Test #65: evaluate_fcurve.OnKeys ....................   Passed    0.02 sec
      Start 66: evaluate_fcurve.InterpolationConstant
 5/10 Test #66: evaluate_fcurve.InterpolationConstant .....   Passed    0.02 sec
      Start 67: evaluate_fcurve.InterpolationLinear
 6/10 Test #67: evaluate_fcurve.InterpolationLinear .......   Passed    0.03 sec
      Start 68: evaluate_fcurve.InterpolationBezier
 7/10 Test #68: evaluate_fcurve.InterpolationBezier .......   Passed    0.03 sec
      Start 69: evaluate_fcurve.InterpolationBounce
 8/10 Test #69: evaluate_fcurve.InterpolationBounce .......   Passed    0.03 sec
      Start 70: evaluate_fcurve.ExtrapolationLinearKeys
 9/10 Test #70: evaluate_fcurve.ExtrapolationLinearKeys ...   Passed    0.03 sec
      Start 71: evaluate_fcurve.ExtrapolationBezierKeys
10/10 Test #71: evaluate_fcurve.ExtrapolationBezierKeys ...   Passed    0.02 sec

100% tests passed, 0 tests failed out of 10

Total Test time (real) =   0.31 sec
tests/gtests/CMakeLists.txt
7–15

The logic here should be changed like we did in: rB59b523c3c98e: Fix build with USD, Clang and Linux.

12

Doing all this setup for blender_test and then going into all the subdirectories is causing problems here with duplicate build flags, that then give build errors.

We can split building the test runner into another folder, at least as long as we need subdirectories here for non-ported tests.

Address own comments (since you probably would not have been able to easily repro).

This revision is now accepted and ready to land.Jul 7 2020, 6:17 PM