From d26eafbe3f5f57e50d28feecd3817035b9651b87 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Sat, 10 Oct 2020 17:22:18 -0400 Subject: [PATCH] Refactor MPI test driver --- unittest/c-library/test_library_mpi.cpp | 264 +----------------------- unittest/testing/mpitesting.h | 228 ++++++++++++++++++++ unittest/testing/test_mpi_main.h | 67 ++++++ 3 files changed, 297 insertions(+), 262 deletions(-) create mode 100644 unittest/testing/mpitesting.h create mode 100644 unittest/testing/test_mpi_main.h diff --git a/unittest/c-library/test_library_mpi.cpp b/unittest/c-library/test_library_mpi.cpp index 5effb31b4d..c4a50b54ce 100644 --- a/unittest/c-library/test_library_mpi.cpp +++ b/unittest/c-library/test_library_mpi.cpp @@ -4,230 +4,17 @@ #include "library.h" #include "timer.h" #include -#include - -#include #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "../testing/test_mpi_main.h" + using ::testing::ExitedWithCode; using ::testing::HasSubstr; using ::testing::StartsWith; using ::testing::StrEq; -using ::testing::TestEventListener; -using ::testing::TestCase; -using ::testing::TestSuite; -using ::testing::UnitTest; -using ::testing::TestPartResult; -using ::testing::TestInfo; - -class MPIPrinter : public TestEventListener { - MPI_Comm comm; - TestEventListener * default_listener; - int me; - int nprocs; - char * buffer; - size_t buffer_size; - std::deque results; - bool finalize_test; -public: - MPIPrinter(TestEventListener * default_listener) : default_listener(default_listener) { - comm = MPI_COMM_WORLD; - MPI_Comm_rank(comm, &me); - MPI_Comm_size(comm, &nprocs); - buffer_size = 1024; - buffer = new char[buffer_size]; - finalize_test = false; - } - - ~MPIPrinter() override { - delete default_listener; - default_listener = nullptr; - - delete [] buffer; - buffer = nullptr; - buffer_size = 0; - } - - virtual void OnTestProgramStart(const UnitTest& unit_test) override { - if(me == 0) default_listener->OnTestProgramStart(unit_test); - } - - virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration) override { - if(me == 0) default_listener->OnTestIterationStart(unit_test, iteration); - } - - virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override { - if(me == 0) default_listener->OnEnvironmentsSetUpStart(unit_test); - } - - virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) override { - if(me == 0) default_listener->OnEnvironmentsSetUpEnd(unit_test); - } - - virtual void OnTestSuiteStart(const TestSuite& test_suite) override { - if(me == 0) default_listener->OnTestSuiteStart(test_suite); - } - - // Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - virtual void OnTestCaseStart(const TestCase& test_case) override { - if(me == 0) default_listener->OnTestSuiteStart(test_case); - } -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - - virtual void OnTestStart(const TestInfo& test_info) override { - // Called before a test starts. - if(me == 0) default_listener->OnTestStart(test_info); - results.clear(); - finalize_test = false; - } - - - virtual void OnTestPartResult(const TestPartResult& test_part_result) override { - // Called after a failed assertion or a SUCCESS(). - // test_part_result() - - if (me == 0 && finalize_test) { - default_listener->OnTestPartResult(test_part_result); - } else { - std::stringstream proc_message; - std::istringstream msg(test_part_result.message()); - std::string line; - - while(std::getline(msg, line)) { - proc_message << "[Rank " << me << "] " << line << std::endl; - } - - results.push_back(TestPartResult(test_part_result.type(), test_part_result.file_name(), test_part_result.line_number(), proc_message.str().c_str())); - } - } - - virtual void OnTestEnd(const TestInfo& test_info) override { - // Called after a test ends. - MPI_Barrier(comm); - - // other procs send their test part results - if(me != 0) { - int nresults = results.size(); - MPI_Send(&nresults, 1, MPI_INT, 0, 0, comm); - - for(auto& test_part_result : results) { - - int type = test_part_result.type(); - MPI_Send(&type, 1, MPI_INT, 0, 0, comm); - - const char * str = test_part_result.file_name(); - int length = 0; - if(str) length = strlen(str)+1; - MPI_Send(&length, 1, MPI_INT, 0, 0, comm); - if(str) MPI_Send(str, length, MPI_CHAR, 0, 0, comm); - - int lineno = test_part_result.line_number(); - MPI_Send(&lineno, 1, MPI_INT, 0, 0, comm); - - str = test_part_result.message(); - length = 0; - if(str) length = strlen(str)+1; - MPI_Send(&length, 1, MPI_INT, 0, 0, comm); - if(str) MPI_Send(str, length, MPI_CHAR, 0, 0, comm); - } - } - - if(me == 0) { - // collect results from other procs - for(int p = 1; p < nprocs; p++) { - int nresults = 0; - MPI_Recv(&nresults, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); - - for(int r = 0; r < nresults; r++) { - - int type; - MPI_Recv(&type, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); - - int length = 0; - MPI_Recv(&length, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); - std::string file_name; - - if (length > 0) { - if (length > buffer_size) { - delete [] buffer; - buffer = new char[length]; - buffer_size = length; - } - MPI_Recv(buffer, length, MPI_CHAR, p, 0, comm, MPI_STATUS_IGNORE); - file_name = buffer; - } - - int lineno; - MPI_Recv(&lineno, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); - - MPI_Recv(&length, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); - std::string message; - - if (length > 0) { - if (length > buffer_size) { - delete [] buffer; - buffer = new char[length]; - buffer_size = length; - } - MPI_Recv(buffer, length, MPI_CHAR, p, 0, comm, MPI_STATUS_IGNORE); - message = std::string(buffer); - } - - results.push_back(TestPartResult((TestPartResult::Type)type, file_name.c_str(), lineno, message.c_str())); - } - } - - // ensure failures are reported - finalize_test = true; - - // add all failures - while(!results.empty()) { - auto result = results.front(); - if(result.failed()) { - ADD_FAILURE_AT(result.file_name(), result.line_number()) << result.message(); - } else { - default_listener->OnTestPartResult(result); - } - results.pop_front(); - } - - default_listener->OnTestEnd(test_info); - } - } - - virtual void OnTestSuiteEnd(const TestSuite& test_suite) override { - if(me == 0) default_listener->OnTestSuiteEnd(test_suite); - } - -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - virtual void OnTestCaseEnd(const TestCase& test_case) override { - if(me == 0) default_listener->OnTestCaseEnd(test_case); - } -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override { - if(me == 0) default_listener->OnEnvironmentsTearDownStart(unit_test); - } - - virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) override { - if(me == 0) default_listener->OnEnvironmentsTearDownEnd(unit_test); - } - - virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override { - if(me == 0) default_listener->OnTestIterationEnd(unit_test, iteration); - } - - virtual void OnTestProgramEnd(const UnitTest& unit_test) override { - if(me == 0) default_listener->OnTestProgramEnd(unit_test); - } -}; - - TEST(MPI, global_box) { int nprocs, me; @@ -328,50 +115,3 @@ TEST(MPI, sub_box) lammps_close(lmp); ::testing::internal::GetCapturedStdout(); }; - - -bool verbose = false; - -int main(int argc, char **argv) -{ - MPI_Init(&argc, &argv); - ::testing::InitGoogleMock(&argc, argv); - - if (argc < 1) { - return 1; - } - - // handle arguments passed via environment variable - if (const char *var = getenv("TEST_ARGS")) { - std::vector env = LAMMPS_NS::utils::split_words(var); - for (auto arg : env) { - if (arg == "-v") { - verbose = true; - } - } - } - - int iarg = 1; - while (iarg < argc) { - if (strcmp(argv[iarg], "-v") == 0) { - verbose = true; - ++iarg; - } else { - std::cerr << "unknown option: " << argv[iarg] << "\n\n"; - MPI_Finalize(); - return 1; - } - } - - auto & listeners = UnitTest::GetInstance()->listeners(); - - // Remove default listener - auto default_listener = listeners.Release(listeners.default_result_printer()); - - // Adds a listener to the end. googletest takes the ownership. - listeners.Append(new MPIPrinter(default_listener)); - - int rv = RUN_ALL_TESTS(); - MPI_Finalize(); - return rv; -} diff --git a/unittest/testing/mpitesting.h b/unittest/testing/mpitesting.h new file mode 100644 index 0000000000..d73cf11d44 --- /dev/null +++ b/unittest/testing/mpitesting.h @@ -0,0 +1,228 @@ +/* ---------------------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + http://lammps.sandia.gov, Sandia National Laboratories + Steve Plimpton, sjplimp@sandia.gov + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include + +using ::testing::TestEventListener; +using ::testing::TestCase; +using ::testing::TestSuite; +using ::testing::UnitTest; +using ::testing::TestPartResult; +using ::testing::TestInfo; + +class MPIPrinter : public TestEventListener { + MPI_Comm comm; + TestEventListener * default_listener; + int me; + int nprocs; + char * buffer; + size_t buffer_size; + std::deque results; + bool finalize_test; +public: + MPIPrinter(TestEventListener * default_listener) : default_listener(default_listener) { + comm = MPI_COMM_WORLD; + MPI_Comm_rank(comm, &me); + MPI_Comm_size(comm, &nprocs); + buffer_size = 1024; + buffer = new char[buffer_size]; + finalize_test = false; + } + + ~MPIPrinter() override { + delete default_listener; + default_listener = nullptr; + + delete [] buffer; + buffer = nullptr; + buffer_size = 0; + } + + virtual void OnTestProgramStart(const UnitTest& unit_test) override { + if(me == 0) default_listener->OnTestProgramStart(unit_test); + } + + virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration) override { + if(me == 0) default_listener->OnTestIterationStart(unit_test, iteration); + } + + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override { + if(me == 0) default_listener->OnEnvironmentsSetUpStart(unit_test); + } + + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) override { + if(me == 0) default_listener->OnEnvironmentsSetUpEnd(unit_test); + } + + virtual void OnTestSuiteStart(const TestSuite& test_suite) override { + if(me == 0) default_listener->OnTestSuiteStart(test_suite); + } + + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + virtual void OnTestCaseStart(const TestCase& test_case) override { + if(me == 0) default_listener->OnTestSuiteStart(test_case); + } +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + + virtual void OnTestStart(const TestInfo& test_info) override { + // Called before a test starts. + if(me == 0) default_listener->OnTestStart(test_info); + results.clear(); + finalize_test = false; + } + + + virtual void OnTestPartResult(const TestPartResult& test_part_result) override { + // Called after a failed assertion or a SUCCESS(). + // test_part_result() + + if (me == 0 && finalize_test) { + default_listener->OnTestPartResult(test_part_result); + } else { + std::stringstream proc_message; + std::istringstream msg(test_part_result.message()); + std::string line; + + while(std::getline(msg, line)) { + proc_message << "[Rank " << me << "] " << line << std::endl; + } + + results.push_back(TestPartResult(test_part_result.type(), test_part_result.file_name(), test_part_result.line_number(), proc_message.str().c_str())); + } + } + + virtual void OnTestEnd(const TestInfo& test_info) override { + // Called after a test ends. + MPI_Barrier(comm); + + // other procs send their test part results + if(me != 0) { + int nresults = results.size(); + MPI_Send(&nresults, 1, MPI_INT, 0, 0, comm); + + for(auto& test_part_result : results) { + + int type = test_part_result.type(); + MPI_Send(&type, 1, MPI_INT, 0, 0, comm); + + const char * str = test_part_result.file_name(); + int length = 0; + if(str) length = strlen(str)+1; + MPI_Send(&length, 1, MPI_INT, 0, 0, comm); + if(str) MPI_Send(str, length, MPI_CHAR, 0, 0, comm); + + int lineno = test_part_result.line_number(); + MPI_Send(&lineno, 1, MPI_INT, 0, 0, comm); + + str = test_part_result.message(); + length = 0; + if(str) length = strlen(str)+1; + MPI_Send(&length, 1, MPI_INT, 0, 0, comm); + if(str) MPI_Send(str, length, MPI_CHAR, 0, 0, comm); + } + } + + if(me == 0) { + // collect results from other procs + for(int p = 1; p < nprocs; p++) { + int nresults = 0; + MPI_Recv(&nresults, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); + + for(int r = 0; r < nresults; r++) { + + int type; + MPI_Recv(&type, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); + + int length = 0; + MPI_Recv(&length, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); + std::string file_name; + + if (length > 0) { + if (length > buffer_size) { + delete [] buffer; + buffer = new char[length]; + buffer_size = length; + } + MPI_Recv(buffer, length, MPI_CHAR, p, 0, comm, MPI_STATUS_IGNORE); + file_name = buffer; + } + + int lineno; + MPI_Recv(&lineno, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); + + MPI_Recv(&length, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE); + std::string message; + + if (length > 0) { + if (length > buffer_size) { + delete [] buffer; + buffer = new char[length]; + buffer_size = length; + } + MPI_Recv(buffer, length, MPI_CHAR, p, 0, comm, MPI_STATUS_IGNORE); + message = std::string(buffer); + } + + results.push_back(TestPartResult((TestPartResult::Type)type, file_name.c_str(), lineno, message.c_str())); + } + } + + // ensure failures are reported + finalize_test = true; + + // add all failures + while(!results.empty()) { + auto result = results.front(); + if(result.failed()) { + ADD_FAILURE_AT(result.file_name(), result.line_number()) << result.message(); + } else { + default_listener->OnTestPartResult(result); + } + results.pop_front(); + } + + default_listener->OnTestEnd(test_info); + } + } + + virtual void OnTestSuiteEnd(const TestSuite& test_suite) override { + if(me == 0) default_listener->OnTestSuiteEnd(test_suite); + } + +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + virtual void OnTestCaseEnd(const TestCase& test_case) override { + if(me == 0) default_listener->OnTestCaseEnd(test_case); + } +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override { + if(me == 0) default_listener->OnEnvironmentsTearDownStart(unit_test); + } + + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) override { + if(me == 0) default_listener->OnEnvironmentsTearDownEnd(unit_test); + } + + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override { + if(me == 0) default_listener->OnTestIterationEnd(unit_test, iteration); + } + + virtual void OnTestProgramEnd(const UnitTest& unit_test) override { + if(me == 0) default_listener->OnTestProgramEnd(unit_test); + } +}; diff --git a/unittest/testing/test_mpi_main.h b/unittest/testing/test_mpi_main.h new file mode 100644 index 0000000000..1e89ba3c8c --- /dev/null +++ b/unittest/testing/test_mpi_main.h @@ -0,0 +1,67 @@ +/* -*- c++ -*- ---------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + http://lammps.sandia.gov, Sandia National Laboratories + Steve Plimpton, sjplimp@sandia.gov + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#include "utils.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "mpitesting.h" + +#include +#include + +// whether to print verbose output (i.e. not capturing LAMMPS screen output). +bool verbose = false; + +int main(int argc, char **argv) +{ + MPI_Init(&argc, &argv); + ::testing::InitGoogleMock(&argc, argv); + + if (argc < 1) { + return 1; + } + + // handle arguments passed via environment variable + if (const char *var = getenv("TEST_ARGS")) { + std::vector env = LAMMPS_NS::utils::split_words(var); + for (auto arg : env) { + if (arg == "-v") { + verbose = true; + } + } + } + + int iarg = 1; + while (iarg < argc) { + if (strcmp(argv[iarg], "-v") == 0) { + verbose = true; + ++iarg; + } else { + std::cerr << "unknown option: " << argv[iarg] << "\n\n"; + MPI_Finalize(); + return 1; + } + } + + auto & listeners = UnitTest::GetInstance()->listeners(); + + // Remove default listener + auto default_listener = listeners.Release(listeners.default_result_printer()); + + // Adds a listener to the end. googletest takes the ownership. + listeners.Append(new MPIPrinter(default_listener)); + + int rv = RUN_ALL_TESTS(); + MPI_Finalize(); + return rv; +}