对于与使用 MPI 的代码/库兼容的单元测试框架有什么建议吗?

计算科学 并行计算 宠物 mpi 测试 再现性
2021-12-08 00:32:15

通常,我编写串行代码,当我这样做时,我会使用一些 xUnit 风格的测试框架(MATLAB xUnit、PyUnit/nose 或 Google 的 C++ 测试框架)编写单元测试。

根据粗略的谷歌搜索,我没有看到很多关于从业者如何使用 MPI 进行单元测试的代码。有什么最佳实践吗?

Strategies for unit testing 和 test-driven development相比,我正在寻找与我应该为测试框架使用什么软件相关的答案(如果有的话——答案很可能是“滚动你自己的代码”,其中自定义测试代码的案例示例会有所帮助)。

我要测试的大部分内容是用于集成半离散 PDE 的时间步进器的右侧函数评估和雅可比矩阵组装例程。我将使用 PETSc,所以如果有任何特定于 PETSc 的内容,除了更通用的测试框架之外,这将很有帮助。

澄清编辑:

一个例子是在${PETSC_DIR}/src/ts/examples/tutorials/ex2.c,我想测试类似RHSFunction(右侧函数评估)和RHSJacobian(雅可比矩阵评估)。我将针对组装的右侧和组装的雅可比矩阵的已知值进行测试;对于一些简单的问题实例,我可以通过分析获得这些值。这些函数是特定于应用程序的函数,不会执行任何其他应用程序级函数,但如果在函数内完成向量或矩阵组装(如上面链接的 PETSc 示例),它们可以调用 MPI。如果我编写的函数只计算处理器本地的向量或矩阵的一部分,我想尽可能地测试全局的组装版本,因为作为并行编程的新手,考虑全局向量和全局对我来说更直观矩阵。这些测试将在小问题和少量处理器上运行。

我可以想到一些策略来做到这一点:

  • 根据我在该主题上所做的 Google 搜索,一个可能效果不佳的策略是构建一个已知输出,并行查找相对/绝对误差,然后进行简单的比较。输出可能会出现乱码——任何用 MPI 编写过“Hello, world”程序的人都知道原因——这限制了进行单元测试的效用。这是提出这个问题的动力。调用单元测试框架似乎也有一些潜在的技巧。
  • 将输出写入文件(例如,在 PETSc 中,使用VecViewand ),并使用类似orMatView的东西与已知输出进行比较从以前使用文件比较进行单元测试的经验中,我对这种方法的直觉是它会很挑剔,并且需要一些过滤。不过,这种方法似乎非常适合回归测试,因为我可以用 plain 替换上面的实用程序,而不必担心匹配文本格式。我收集到这个策略或多或少是 WolfgangBangerth 和 andybauer 所建议的。PETSc 似乎也对它所做的一些测试使用了类似的方法。ndiffnumdiffdiff
  • 使用单元测试框架,将所有内容收集到 MPI 等级为 0 的处理器上,并要求它仅在处理器等级为 0 时执行单元测试。我可以用规范做类似的事情(这样可能更容易),尽管权衡是返回的任何错误都会告诉我我的计算有问题,但不会告诉我哪些元素有问题。那我就不用担心任何单元测试输出会乱码了;我只需要担心正确调用单元测试框架。当精确的解决方案可用时,PETSc 似乎在其示例程序中使用规范比较,但在进行这些比较时它不使用单元测试框架(也不一定)。
4个回答

我是 GoogleTest 的快乐用户,在 CMake/CTest 构建环境中使用 C++ MPI 代码:

  • CMake 自动从 svn 安装/链接 googletest!
  • 添加测试是单行的!
  • 编写测试很容易!(而且 google mock 非常强大!)
  • CTest 可以将命令行参数传递给您的测试,并将数据导出到 Cdash!

这就是它的工作原理。一批需要 mpi 的单元测试被写入某个my_mpi_test.cpp文件,如下所示:

#include <gtest/gtest.h>
#include <boost/mpi.h>

/// Most testing libraries allow to define main yourself to override initialization.
int main(int argc, char* argv[]) {
    ::testing::InitGoogleTest(&argc, argv);  /// Set gtest environment
    mpi::environment env(argc, argv);  /// Set mpi environment
    return RUN_ALL_TESTS();  /// Execute all gtest tests
}

TEST(test_batch_name, test_name) {  /// Then you can create tests as usual,
  using namespace mpi;
  communicator world;  /// and use MPI inside your tests.
  /* ... test stuff here ... */
}

添加此测试的 CMakeLists.txt 是:

add_mpi_test(my_mpi 2)  # Uses 2 MPI processes

在我的根 CMakeLists.txtadd_mpi_test包装 CMake 的位置:add_test

function(add_mpi_test name no_mpi_proc)
  include_directories(${MY_TESTING_INCLUDES})
      # My test are all called name_test.cpp
      add_executable(${name} ${name}_test.cpp)
      add_dependencies(${name} googletest)
  # Make sure to link MPI here too:
  target_link_libraries(${name} ${MY_TESTING_LIBS})
  set(test_parameters ${MPIEXEC_NUMPROC_FLAG} ${no_mpi_proc} "./${name}")
      add_test(NAME ${name} COMMAND ${MPIEXEC} ${test_parameters})
endfunction(add_mpi_test)

最后一部分不是必需的,但允许您在一行中轻松添加 mpi 测试。然后,您可以决定是要硬编码每个测试的 MPI 进程数,还是通过命令行参数将其读取到 ctest。

我们只是在 deal.II 中滚动我们自己的代码——本质上,我们告诉框架使用mpirun -np .... 我们之前刚刚使用了基于 Makefile 的测试方案(编译、链接、执行测试,然后将输出与之前保存的输出进行比较),您可以在这里找到:

对于上下文,非 MPI 目标在这里:

我们正在使用 CMake/CTest 重写东西,目前的开发在这里:

有几个启用 MPI 的软件包使用CMake工具集进行测试。我能想到的就是 Trilinos、VTK 和 ParaView。我认为您不想假设可执行文件需要使用 mpirun 和/或 mpiexec 启动。CMake 支持指定如何正确启动可执行文件以及不同的选项,例如要使用的最大进程数以及前后标志(如果需要)。

您可能想查看ParaView 仪表板的 HPC 站点部分,其中测试在各种 NERSC 和 Argonne 超级计算机上运行。您还需要指定大多数设置才能使其在这些机器上运行。

作为参考,Trilinos 仪表板列出了各种各样的包,对我来说,它的组织令人印象深刻。

全面披露:我是 Kitware 的员工,CMake 是 Kitware 参与的开源项目之一。

Trilinos 中的 Teuchos 单元测试工具原生支持使用 MPI 的单元测试。诸如控制多个进程的输出和汇总所有进程的通过/失败之类的事情是自动的。看一看:

http://trilinos.org/docs/dev/packages/teuchos/doc/html/group__Teuchos__UnitTest__grp.html