Custom program context for unit tests
authorTeemu Murtola <teemu.murtola@gmail.com>
Mon, 29 Dec 2014 06:14:48 +0000 (08:14 +0200)
committerTeemu Murtola <teemu.murtola@gmail.com>
Wed, 7 Jan 2015 06:32:54 +0000 (07:32 +0100)
This improves the usability and robustness of the tests:
 - The data files in share/top/ are now always looked up from the source
   directory, without relying on any detection of the binary location.
 - It is possible to override the location of the source directory for
   some cross-compilation scenarios, which changes both the lookup
   location of share/top/ and the test input/reference files.

Change-Id: I1be5861ee594217bf27895563feb2c09fafe86c7

src/testutils/TestMacros.cmake
src/testutils/testfilemanager.cpp
src/testutils/testfilemanager.h
src/testutils/testinit.cpp

index b3acdbefd16539468521696d034ef14e18d7900b..4f438dd08ba2affc61f9cd57265513ae31b58fc1 100644 (file)
@@ -1,7 +1,7 @@
 #
 # This file is part of the GROMACS molecular simulation package.
 #
-# Copyright (c) 2011,2012,2013,2014, by the GROMACS development team, led by
+# Copyright (c) 2011,2012,2013,2014,2015, 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.
@@ -46,13 +46,15 @@ function (gmx_build_unit_test NAME EXENAME)
         add_executable(${EXENAME} ${UNITTEST_TARGET_OPTIONS} ${ARGN} ${TESTUTILS_DIR}/unittest_main.cpp)
         set_property(TARGET ${EXENAME} APPEND PROPERTY COMPILE_DEFINITIONS "${GMOCK_COMPILE_DEFINITIONS}")
         target_link_libraries(${EXENAME} ${TESTUTILS_LIBS} libgromacs ${GMOCK_LIBRARIES} ${GMX_EXE_LINKER_FLAGS})
+        file(RELATIVE_PATH _input_files_path ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
         set(_temporary_files_path "${CMAKE_CURRENT_BINARY_DIR}/Testing/Temporary")
         file(MAKE_DIRECTORY ${_temporary_files_path})
-
         # Note that the quotation marks in the next line form part of
         # the defined symbol, so that the macro replacement in the
         # source file is as a string.
-        set(EXTRA_COMPILE_DEFINITIONS TEST_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}" TEST_TEMP_PATH="${_temporary_files_path}")
+        set(EXTRA_COMPILE_DEFINITIONS
+            TEST_DATA_PATH="${_input_files_path}"
+            TEST_TEMP_PATH="${_temporary_files_path}")
 
         set_property(TARGET ${EXENAME} APPEND PROPERTY COMPILE_DEFINITIONS "${EXTRA_COMPILE_DEFINITIONS}")
     endif()
index 0a2eb0f9e38b05839fad9df8d928b4092ce314a2..da988ea99ae12f9f15848147faedd1b156936d92 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by
+ * Copyright (c) 2012,2013,2014,2015, 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.
@@ -76,7 +76,7 @@ class TestFileManager::Impl
 {
     public:
         //! Global test input data path set with setDataInputDirectory().
-        static const char *s_inputDirectory;
+        static std::string s_inputDirectory;
 
         //! Global temporary output directory for tests, set with setGlobalOutputTempDirectory().
         static const char *s_globalOutputTempDirectory;
@@ -111,7 +111,7 @@ class TestFileManager::Impl
         std::string outputTempDirectory_;
 };
 
-const char *TestFileManager::Impl::s_inputDirectory            = NULL;
+std::string TestFileManager::Impl::s_inputDirectory;
 const char *TestFileManager::Impl::s_globalOutputTempDirectory = NULL;
 /** Controls whether TestFileManager should delete temporary files
     after the test finishes. */
@@ -200,8 +200,8 @@ std::string TestFileManager::getInputFilePath(const char *filename)
 
 const char *TestFileManager::getInputDataDirectory()
 {
-    GMX_RELEASE_ASSERT(Impl::s_inputDirectory != NULL, "Path for test input files is not set");
-    return Impl::s_inputDirectory;
+    GMX_RELEASE_ASSERT(!Impl::s_inputDirectory.empty(), "Path for test input files is not set");
+    return Impl::s_inputDirectory.c_str();
 }
 
 const char *TestFileManager::getGlobalOutputTempDirectory()
@@ -215,7 +215,7 @@ const char *TestFileManager::getOutputTempDirectory() const
     return impl_->outputTempDirectory_.c_str();
 }
 
-void TestFileManager::setInputDataDirectory(const char *path)
+void TestFileManager::setInputDataDirectory(const std::string &path)
 {
     // There is no need to protect this by a mutex, as this is called in early
     // initialization of the tests.
index 5d77315a620def5167a47fd1c4e1330023311799..c98626f3f51e6bd433b8b140b9f8d57a36b2532a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by
+ * Copyright (c) 2012,2013,2014,2015, 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.
@@ -195,7 +195,7 @@ class TestFileManager
          * This function is automatically called by unittest_main.cpp through
          * initTestUtils().
          */
-        static void setInputDataDirectory(const char *path);
+        static void setInputDataDirectory(const std::string &path);
 
         /*! \brief Returns the path to the global test output
          * temporary directory for future TestFileManager objects.
index 85464087b55de63ea9c152dc8fed09590b590019..20e5f2eaaeb656a2f8ac6c710500faea6b56825f 100644 (file)
 
 #include "testinit.h"
 
+#include "config.h"
+
 #include <cstdio>
 #include <cstdlib>
 
+#include <boost/scoped_ptr.hpp>
 #include <gmock/gmock.h>
 
 #include "gromacs/commandline/cmdlinehelpcontext.h"
 #include "gromacs/commandline/cmdlinehelpwriter.h"
 #include "gromacs/commandline/cmdlineinit.h"
 #include "gromacs/commandline/cmdlineparser.h"
+#include "gromacs/commandline/cmdlineprogramcontext.h"
 #include "gromacs/math/utilities.h"
 #include "gromacs/options/basicoptions.h"
 #include "gromacs/options/options.h"
 #include "gromacs/utility/errorcodes.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/file.h"
+#include "gromacs/utility/path.h"
 #include "gromacs/utility/programcontext.h"
 
 #include "testutils/mpi-printer.h"
@@ -72,6 +77,65 @@ namespace test
 
 namespace
 {
+
+/*! \brief
+ * Custom program context for test binaries.
+ *
+ * This context overrides the defaultLibraryDataPath() 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
+ * option to the test binary.
+ *
+ * \ingroup module_testutils
+ */
+class TestProgramContext : public ProgramContextInterface
+{
+    public:
+        /*! \brief
+         * Initializes a test program context.
+         *
+         * \param[in] context  Current \Gromacs program context.
+         */
+        explicit TestProgramContext(const ProgramContextInterface &context)
+            : context_(context),
+              dataPath_(Path::join(CMAKE_SOURCE_DIR, "share/top"))
+        {
+        }
+
+        /*! \brief
+         * Sets the source directory root from which to look for data files.
+         */
+        void overrideSourceRoot(const std::string &sourceRoot)
+        {
+            dataPath_ = Path::join(sourceRoot, "share/top");
+        }
+
+        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 const char *defaultLibraryDataPath() const
+        {
+            return dataPath_.c_str();
+        }
+        virtual const char *commandLine() const
+        {
+            return context_.commandLine();
+        }
+
+    private:
+        const ProgramContextInterface   &context_;
+        std::string                      dataPath_;
+};
+
 //! Prints the command-line options for the unit test binary.
 void printHelp(const Options &options)
 {
@@ -83,6 +147,10 @@ void printHelp(const Options &options)
     context.setModuleDisplayName(getProgramContext().displayName());
     CommandLineHelpWriter(options).writeHelp(context);
 }
+
+//! Global program context instance for test binaries.
+boost::scoped_ptr<TestProgramContext> g_testContext;
+
 }       // namespace
 
 //! \cond internal
@@ -91,20 +159,24 @@ void initTestUtils(const char *dataPath, const char *tempPath, int *argc, char *
 #ifndef NDEBUG
     gmx_feenableexcept();
 #endif
-    gmx::initForCommandLine(argc, argv);
+    const CommandLineProgramContext &context = initForCommandLine(argc, argv);
     try
     {
+        g_testContext.reset(new TestProgramContext(context));
+        setProgramContext(g_testContext.get());
         ::testing::InitGoogleMock(argc, *argv);
         if (dataPath != NULL)
         {
-            TestFileManager::setInputDataDirectory(dataPath);
+            TestFileManager::setInputDataDirectory(
+                    Path::join(CMAKE_SOURCE_DIR, dataPath));
         }
         if (tempPath != NULL)
         {
             TestFileManager::setGlobalOutputTempDirectory(tempPath);
         }
-        bool    bHelp = false;
-        Options options(NULL, NULL);
+        bool        bHelp = false;
+        std::string sourceRoot;
+        Options     options(NULL, NULL);
         // 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.
@@ -112,6 +184,9 @@ void initTestUtils(const char *dataPath, const char *tempPath, int *argc, char *
                               .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)"));
         // TODO: Consider removing this option from test binaries that do not need it.
         initReferenceData(&options);
         initTestOptions(&options);
@@ -129,19 +204,32 @@ void initTestUtils(const char *dataPath, const char *tempPath, int *argc, char *
         {
             printHelp(options);
         }
+        if (!sourceRoot.empty())
+        {
+            g_testContext->overrideSourceRoot(sourceRoot);
+            TestFileManager::setInputDataDirectory(
+                    Path::join(sourceRoot, dataPath));
+        }
         setFatalErrorHandler(NULL);
         initMPIOutput();
     }
     catch (const std::exception &ex)
     {
         printFatalErrorMessage(stderr, ex);
-        std::exit(processExceptionAtExitForCommandLine(ex));
+        int retcode = processExceptionAtExitForCommandLine(ex);
+        // TODO: It could be nice to destroy things in proper order such that
+        // g_testContext would not contain hanging references at this point,
+        // but in practice that should not matter.
+        g_testContext.reset();
+        std::exit(retcode);
     }
 }
 
 void finalizeTestUtils()
 {
-    gmx::finalizeForCommandLine();
+    setProgramContext(NULL);
+    g_testContext.reset();
+    finalizeForCommandLine();
 }
 //! \endcond