Generalize constraints on MPI rank counts for tests
[alexxy/gromacs.git] / src / testutils / testinit.cpp
index 83e877d7be942f26d0397b5fe72b7b8aae6173f2..e04b49bec5da4fbc7e58cb724933ef3c1c998f02 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2012,2013,2014,2015,2016, by the GROMACS development team, led by
+ * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
+ * Copyright (c) 2017,2018,2019,2020,2021, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -41,7 +42,7 @@
  */
 #include "gmxpre.h"
 
-#include "testinit.h"
+#include "testutils/testinit.h"
 
 #include <cstdio>
 #include <cstdlib>
 #include "gromacs/utility/programcontext.h"
 #include "gromacs/utility/textwriter.h"
 
-#include "testutils/mpi-printer.h"
+#include "testutils/mpitest.h"
 #include "testutils/refdata.h"
+#include "testutils/test_hardware_environment.h"
 #include "testutils/testfilemanager.h"
 #include "testutils/testoptions.h"
 
+#include "mpi_printer.h"
+
 namespace gmx
 {
 namespace test
@@ -85,67 +89,52 @@ namespace
  *
  * This context overrides the installationPrefix() implementation to always
  * load data files from the source directory, as the test binaries are never
- * installed.  It also support overriding the directory through a command-line
+ * installed.  It also supports overriding the directory through a command-line
  * option to the test binary.
  *
  * \ingroup module_testutils
  */
 class TestProgramContext : public IProgramContext
 {
-    public:
-        /*! \brief
-         * Initializes a test program context.
-         *
-         * \param[in] context  Current \Gromacs program context.
-         */
-        explicit TestProgramContext(const IProgramContext &context)
-            : context_(context), dataPath_(CMAKE_SOURCE_DIR)
-        {
-        }
+public:
+    /*! \brief
+     * Initializes a test program context.
+     *
+     * \param[in] context  Current \Gromacs program context.
+     */
+    explicit TestProgramContext(const IProgramContext& context) :
+        context_(context), dataPath_(CMAKE_SOURCE_DIR)
+    {
+    }
 
-        /*! \brief
-         * Sets the source directory root from which to look for data files.
-         */
-        void overrideSourceRoot(const std::string &sourceRoot)
-        {
-            dataPath_ = sourceRoot;
-        }
+    /*! \brief
+     * Sets the source directory root from which to look for data files.
+     */
+    void overrideSourceRoot(const std::string& sourceRoot) { dataPath_ = sourceRoot; }
 
-        virtual const char *programName() const
-        {
-            return context_.programName();
-        }
-        virtual const char *displayName() const
-        {
-            return context_.displayName();
-        }
-        virtual const char *fullBinaryPath() const
-        {
-            return context_.fullBinaryPath();
-        }
-        virtual InstallationPrefixInfo installationPrefix() const
-        {
-            return InstallationPrefixInfo(dataPath_.c_str(), true);
-        }
-        virtual const char *commandLine() const
-        {
-            return context_.commandLine();
-        }
+    const char*            programName() const override { return context_.programName(); }
+    const char*            displayName() const override { return context_.displayName(); }
+    const char*            fullBinaryPath() const override { return context_.fullBinaryPath(); }
+    InstallationPrefixInfo installationPrefix() const override
+    {
+        return InstallationPrefixInfo(dataPath_.c_str(), true);
+    }
+    const char* commandLine() const override { return context_.commandLine(); }
 
-    private:
-        const IProgramContext           &context_;
-        std::string                      dataPath_;
+private:
+    const IProgramContext& context_;
+    std::string            dataPath_;
 };
 
 //! Prints the command-line options for the unit test binary.
-void printHelp(const Options &options)
+void printHelp(const Optionsoptions)
 {
-    const std::string &program = getProgramContext().displayName();
+    const std::stringprogram = getProgramContext().displayName();
     std::fprintf(stderr,
                  "\nYou can use the following GROMACS-specific command-line flags\n"
                  "to control the behavior of the tests:\n\n");
     TextWriter             writer(&TextOutputFile::standardError());
-    CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, NULL, program);
+    CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, nullptr, program);
     context.setModuleDisplayName(program);
     CommandLineHelpWriter(options).writeHelp(context);
 }
@@ -154,61 +143,99 @@ void printHelp(const Options &options)
 // Never releases ownership.
 std::unique_ptr<TestProgramContext> g_testContext;
 
-}       // namespace
+/*! \brief Makes GoogleTest non-failures more verbose
+ *
+ * By default, GoogleTest does not echo messages appended to explicit
+ * assertions of success with SUCCEEDED() e.g.
+ *
+ *    GTEST_SKIP() << "reason why";
+ *
+ * produces no output. This test listener changes that behavior, so
+ * that the message is echoed.
+ *
+ * When run with multiple ranks, only the master rank should use this
+ * listener, else the output can be very noisy. */
+class SuccessListener : public testing::EmptyTestEventListener
+{
+    void OnTestPartResult(const testing::TestPartResult& result) override
+    {
+        if (result.type() == testing::TestPartResult::kSuccess)
+        {
+            printf("%s\n", result.message());
+        }
+    }
+};
+
+} // namespace
 
 //! \cond internal
-void initTestUtils(const char *dataPath, const char *tempPath, bool usesMpi,
-                   int *argc, char ***argv)
+void initTestUtils(const char* dataPath,
+                   const char* tempPath,
+                   bool        usesMpi,
+                   bool        usesHardwareDetection,
+                   int*        argc,
+                   char***     argv)
 {
-#ifndef NDEBUG
-    gmx_feenableexcept();
-#endif
-    const CommandLineProgramContext &context = initForCommandLine(argc, argv);
+    if (gmxShouldEnableFPExceptions())
+    {
+        gmx_feenableexcept();
+    }
+    const CommandLineProgramContext& context = initForCommandLine(argc, argv);
     try
     {
         if (!usesMpi && gmx_node_num() > 1)
         {
+            // We cannot continue, since some tests might be using
+            // MPI_COMM_WORLD, which could deadlock if we would only
+            // continue with the master rank here.
             if (gmx_node_rank() == 0)
             {
-                fprintf(stderr, "NOTE: You are running %s on %d MPI ranks, "
+                fprintf(stderr,
+                        "NOTE: You are running %s on %d MPI ranks, "
                         "but it is does not contain MPI-enabled tests. "
-                        "Rank 0 will run the tests, other ranks will exit.",
-                        context.programName(), gmx_node_num());
-            }
-            else
-            {
-                finalizeForCommandLine();
-                std::exit(0);
+                        "The test will now exit.\n",
+                        context.programName(),
+                        gmx_node_num());
             }
+            finalizeForCommandLine();
+            std::exit(1);
         }
-        g_testContext.reset(new TestProgramContext(context));
+        if (usesHardwareDetection)
+        {
+            callAddGlobalTestEnvironment();
+        }
+        g_testContext = std::make_unique<TestProgramContext>(context);
         setProgramContext(g_testContext.get());
         // Use the default finder that does not respect GMXLIB, since the tests
         // generally can only get confused by a different set of data files.
-        setLibraryFileFinder(NULL);
+        setLibraryFileFinder(nullptr);
         ::testing::InitGoogleMock(argc, *argv);
-        if (dataPath != NULL)
+        if (dataPath != nullptr)
         {
-            TestFileManager::setInputDataDirectory(
-                    Path::join(CMAKE_SOURCE_DIR, dataPath));
+            TestFileManager::setInputDataDirectory(Path::join(CMAKE_SOURCE_DIR, dataPath));
         }
-        if (tempPath != NULL)
+        if (tempPath != nullptr)
         {
             TestFileManager::setGlobalOutputTempDirectory(tempPath);
         }
+        TestFileManager::setTestSimulationDatabaseDirectory(GMX_TESTSIMULATIONDATABASE_DIR);
+
         bool        bHelp = false;
         std::string sourceRoot;
-        Options     options(NULL, NULL);
+        bool        echoReasons = false;
+        Options     options;
         // TODO: A single option that accepts multiple names would be nicer.
         // Also, we recognize -help, but GTest doesn't, which leads to a bit
         // unintuitive behavior.
-        options.addOption(BooleanOption("h").store(&bHelp)
-                              .description("Print GROMACS-specific unit test options"));
+        options.addOption(BooleanOption("h").store(&bHelp).description(
+                "Print GROMACS-specific unit test options"));
         options.addOption(BooleanOption("help").store(&bHelp).hidden());
         options.addOption(BooleanOption("?").store(&bHelp).hidden());
         // TODO: Make this into a FileNameOption (or a DirectoryNameOption).
-        options.addOption(StringOption("src-root").store(&sourceRoot)
-                              .description("Override source tree location (for data files)"));
+        options.addOption(
+                StringOption("src-root").store(&sourceRoot).description("Override source tree location (for data files)"));
+        options.addOption(
+                BooleanOption("echo-reasons").store(&echoReasons).description("When succeeding or skipping a test, echo the reason"));
         // The potential MPI test event listener must be initialized first,
         // because it should appear in the start of the event listener list,
         // before other event listeners that may generate test failures
@@ -226,7 +253,7 @@ void initTestUtils(const char *dataPath, const char *tempPath, bool usesMpi,
             CommandLineParser(&options).parse(argc, *argv);
             options.finish();
         }
-        catch (const UserInputError &)
+        catch (const UserInputError&)
         {
             printHelp(options);
             throw;
@@ -238,11 +265,15 @@ void initTestUtils(const char *dataPath, const char *tempPath, bool usesMpi,
         if (!sourceRoot.empty())
         {
             g_testContext->overrideSourceRoot(sourceRoot);
-            TestFileManager::setInputDataDirectory(
-                    Path::join(sourceRoot, dataPath));
+            TestFileManager::setInputDataDirectory(Path::join(sourceRoot, dataPath));
+        }
+        // Echo success messages only from the master MPI rank
+        if (echoReasons && (gmx_node_rank() == 0))
+        {
+            testing::UnitTest::GetInstance()->listeners().Append(new SuccessListener);
         }
     }
-    catch (const std::exception &ex)
+    catch (const std::exceptionex)
     {
         printFatalErrorMessage(stderr, ex);
         int retcode = processExceptionAtExitForCommandLine(ex);
@@ -256,7 +287,7 @@ void initTestUtils(const char *dataPath, const char *tempPath, bool usesMpi,
 
 void finalizeTestUtils()
 {
-    setProgramContext(NULL);
+    setProgramContext(nullptr);
     g_testContext.reset();
     finalizeForCommandLine();
 }