Unit testing with MPI, googletest, and cmake
Introduction
In this article we take a short detour into the problem of continuous unit testing
of code that contains MPI calls and use either mpich
or Open MPI. I have recently moved from
CTest-based testing to
a combination of CMake and
googletest. The reason for the shift
is the convenience googletest
provides. However, the googletest
primer
and advanced manual
do not contain many examples and can be cryptic at times. I hope this
article will provide pointers to those who run into a roadblock when testing MPI
applications with googletest
.
Installing googletest
I typically install googletest
as a submodule
in my git repository. For example,
There are several caveats when using git submodules. For our purposes, we only have to remember that when we clone the repository we have to use
Other tips can be found in a nicely condensed form in Sentheon’s blog.
Making sure cmake finds and compiles googletest
To make sure that googletest
can be found and is built during the compile process,
I add the following to by root CMakeLists.txt
file:
The first line is needed because I have not been successful in passing the clang
compiler
name to googletest
without specifying the full path. I’m sure one can use a more general
approach, but I haven’t felt the need to spend the time trying to figure out a better way.
The add_subdirectory
command is all that is needed for cmake
to compile googletest
and
produce static libraries.
Adding local unit tests
Next I add a UnitTests
directory in my directory of interest and modify the local
CMakeLists.txt
to be able to find the UnitTests
directory. For example,
In this case I am going to test my Smoothed Particle Hydrodynamics code.
The CMakeLists.txt file in UnitTests
Now we are finally really to add our unit tests to the build chain. The CMakeLists.txt
file in the UnitTests
directory has two sections (which can be simplified if you
are so inclined).
In the first section, we find the location of the googletest
headers and libraries:
Note that the two googletest
libraries that we use are gtest_main
and gtest
.
In the second section, we add the actual test that requires MPI
. Once again, these details
can be abstracted into a cmake
function if you so desire.
The add_executable
line identifies the unit test program testSPHParticleScatter.cpp
which tests the scatter operation between two MPI processes.
The target_link_libraries
lists the libraries that are needed: the googletest
libraries (GTEST_LIB
) and our coupled DEM-SPH code library (ellip3D_lib
)
Then we set up a command to run (MPI_COMMAND
) and make it use mpirun
. You
can generalize this if you want.
Finally we, add the add_custom_command
line that tells cmake
to run the MPI_COMMAND
after the build is complete (POST_BUILD
).
The actual test C++ code
Now that the build system has been configured, we just write our unit test
testSPHParticleScatter.cpp
.
First, we include the required headers:
Note that we are using the Boost MPI wrappers.
The MPI test environment class
But we cannot use the boost::mpi::environment
call to set up MPI
(because the environment
object is deleted before googletest
s are run). Instead, we have to set up a custom environment
to run our MPI
tests by creating a MPIEnvironment
class that extends the ::testing::Environment
class provided by googletest
.
Here, the SetUp
function calls MPI_Init
and sets up the environment while the TearDown
function
calls ‘MPI_Finalize`. All tests are performed when the environment is active.
The main
test function
The main
function in typically not needed in standard googletest
tests and is generate
by some internal magic. However, we do need a main
function in testSPHParticleScatter.cpp
because we are using mpirun
to run the test.
The MPI specific environment is created by the AddGlobalTestEnvironment
function to which we
pass a MPIEnvironment
object. The object is deleted internally by googletest
.
The tests in testSPHParticleScatter.cpp
are then run using RUN_ALL_TESTS()
. Note that this
function returns from main
and, therefore, and non-googletest environment objects created in
main (e.g., with boost::mpi::environment) are deleted before the tests are run.
The actual test
Finally, we add an actual test to testSPHParticleScatter.cpp
as follows:
The test can be written in the standard way and numerous examples can be found on the web. Of course, we have to be careful about keeping in mind that the test will be run on two processes in this particular case.
Caveat
The Boost
MPI environment created by
boost::mpi::environment
allows MPI calls to
fail without throwing an exception. For example, in my Patch
code discussed in
an earlier article, I have
I deliberately send invalid neighborCoords
to this function to find out if a patch
is a boundary patch. This does not create a problem when I use the boost::mpi::environment
set up. But when setting up the environment explicitly for the unit tests, I have
to add
before I call MPI_Cart_rank
to make sure I don’t get errors when I use invalid
values of neighborCoords
.
The output from make
Now, if we run make
(after setting up the makefiles with cmake, of course),
we not only compile the code but also run the unit test! In this particular case,
here’s what the output looks like:
Remarks
I hope this article has been of use to you. Our series on communication between patches will continue when I get some free time.