Updated infrastructure for comparing mdrun output in Google Tests
authorMark Abraham <mark.j.abraham@gmail.com>
Thu, 29 Mar 2018 07:36:22 +0000 (09:36 +0200)
committerMark Abraham <mark.j.abraham@gmail.com>
Mon, 16 Jul 2018 16:30:30 +0000 (18:30 +0200)
Made some GoogleMock matchers so its easy to check containers of reals
and rvecs.

Implemented better infrastructure for reading and comparing energies
and trajectories. In particular, the functionality for reading frames
and comparing them is now separated.

Added a new templated helper function for comparing either
energies or trajectories between pairs of frames that should match.

Removed use of std::pair, which didn't really help anything.

Reordered names of files in CMakeLists.txt, per intention to have
things alphabetical.

Added unordered_map to the set of C++ headers that the checker
understands, now that we use it.

Change-Id: If8d0bf076c07e7326473e613a83b46a6794f8505

15 files changed:
docs/doxygen/includesorter.py
src/gromacs/trajectory/trajectoryframe.h
src/programs/mdrun/tests/CMakeLists.txt
src/programs/mdrun/tests/energycomparison.cpp [new file with mode: 0644]
src/programs/mdrun/tests/energycomparison.h [new file with mode: 0644]
src/programs/mdrun/tests/energyreader.cpp
src/programs/mdrun/tests/energyreader.h
src/programs/mdrun/tests/mdruncomparison.h
src/programs/mdrun/tests/trajectorycomparison.cpp [new file with mode: 0644]
src/programs/mdrun/tests/trajectorycomparison.h [new file with mode: 0644]
src/programs/mdrun/tests/trajectoryreader.cpp
src/programs/mdrun/tests/trajectoryreader.h
src/testutils/CMakeLists.txt
src/testutils/testmatchers.cpp [new file with mode: 0644]
src/testutils/testmatchers.h [new file with mode: 0644]

index c4d4aafd33beb1d9a8936714dd74c6814a00c575..4bc26636c5b2c63438249a89980234012ac094be 100755 (executable)
@@ -110,7 +110,8 @@ class GroupedSorter(object):
             'limits', 'list', 'map', 'memory', 'mutex',
             'new', 'numeric', 'ostream', 'random',
             'regex', 'set', 'sstream', 'stdexcept', 'streambuf', 'string', 'strstream',
-            'thread', 'tuple', 'type_traits', 'typeindex', 'typeinfo', 'vector', 'utility']
+            'thread', 'tuple', 'type_traits', 'typeindex', 'typeinfo', 'vector',
+            'unordered_map', 'utility']
 
     def __init__(self, style='pub-priv', absolute=False):
         """Initialize a sorted with the given style."""
index 04fbf601ca4ef2a8ba83c5915e5322c9c5c320d8..965785ade393029a7a1b0a03467215a45b209306 100644 (file)
@@ -138,10 +138,9 @@ class TrajectoryFrame
         bool hasBox() const;
         //! Return a handle to the frame's box, which is all zero if the frame has no box.
         const BoxMatrix &box() const;
-        // TODO make this private when updating trajectory comparison code
+    private:
         //! Handle to trajectory data
         const t_trxframe &frame_;
-    private:
         //! Box matrix data from the frame_.
         BoxMatrix         box_;
 };
index 778fd80411bebd8905f368ff706c7d75b662d636..8d5b6f801b0588269abe386073778c4b9b552a81 100644 (file)
@@ -48,17 +48,19 @@ set(exename "mdrun-test")
 gmx_add_gtest_executable(
     ${exename}
     # files with code for tests
-    tabulated_bonded_interactions.cpp
+    compressed_x_output.cpp
+    energycomparison.cpp
     grompp.cpp
     initialconstraints.cpp
+    interactiveMD.cpp
     rerun.cpp
-    trajectory_writing.cpp
-    trajectoryreader.cpp
-    compressed_x_output.cpp
     swapcoords.cpp
-    interactiveMD.cpp
+    tabulated_bonded_interactions.cpp
     termination.cpp
     tpitest.cpp
+    trajectory_writing.cpp
+    trajectorycomparison.cpp
+    trajectoryreader.cpp
     # pseudo-library for code for testing mdrun
     $<TARGET_OBJECTS:mdrun_test_objlib>
     # pseudo-library for code for mdrun
@@ -72,10 +74,10 @@ set(exename "mdrun-mpi-test")
 gmx_add_gtest_executable(
     ${exename} MPI
     # files with code for tests
+    domain_decomposition.cpp
     multisim.cpp
     multisimtest.cpp
     replicaexchange.cpp
-    domain_decomposition.cpp
     # pseudo-library for code for testing mdrun
     $<TARGET_OBJECTS:mdrun_test_objlib>
     # pseudo-library for code for mdrun
diff --git a/src/programs/mdrun/tests/energycomparison.cpp b/src/programs/mdrun/tests/energycomparison.cpp
new file mode 100644 (file)
index 0000000..2fe371a
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2018, 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+
+/*! \internal \file
+ * \brief Implementions of related classes for tests that want to
+ * inspect energies produced by mdrun.
+ *
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
+ * \ingroup module_mdrun_integration_tests
+ */
+#include "gmxpre.h"
+
+#include "energycomparison.h"
+
+#include "gromacs/trajectory/energyframe.h"
+
+#include "testutils/testasserts.h"
+
+namespace gmx
+{
+namespace test
+{
+
+void compareEnergyFrames(const EnergyFrame      &reference,
+                         const EnergyFrame      &test,
+                         const EnergyTolerances &tolerances)
+{
+    for (auto referenceIt = reference.begin(); referenceIt != reference.end(); ++referenceIt)
+    {
+        auto &energyName = referenceIt->first;
+        SCOPED_TRACE("Comparing " +  energyName + " between frames");
+        auto  testIt = test.find(energyName);
+        if (testIt != test.end())
+        {
+            auto &energyValueInReference = referenceIt->second;
+            auto &energyValueInTest      = testIt->second;
+            EXPECT_REAL_EQ_TOL(energyValueInReference, energyValueInTest, tolerances.at(energyName));
+        }
+        else
+        {
+            ADD_FAILURE() << "Could not find energy component from reference frame in test frame";
+        }
+    }
+}
+
+} // namespace
+} // namespace
diff --git a/src/programs/mdrun/tests/energycomparison.h b/src/programs/mdrun/tests/energycomparison.h
new file mode 100644 (file)
index 0000000..a8522cc
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2018, 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+
+/*! \internal \file
+ * \brief Declares function for comparing energy frames.
+ *
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
+ * \ingroup module_mdrun_integration_tests
+ */
+#ifndef GMX_PROGRAMS_MDRUN_TESTS_ENERGYCOMPARISON_H
+#define GMX_PROGRAMS_MDRUN_TESTS_ENERGYCOMPARISON_H
+
+#include <map>
+#include <string>
+
+#include "testutils/testasserts.h"
+
+namespace gmx
+{
+
+class EnergyFrame;
+
+namespace test
+{
+
+//! Convenience type
+using EnergyTolerances = std::map<std::string, FloatingPointTolerance>;
+
+/*! \brief Compare all fields of reference with all matching fields from test
+ *
+ * Ignore any key found in either \c reference or \c test that is not
+ * found in the other. For all keys found in both frames, compare the
+ * values with EXPECT_REAL_EQ_TOL and the given tolerance for that
+ * key. */
+void compareEnergyFrames(const EnergyFrame      &reference,
+                         const EnergyFrame      &test,
+                         const EnergyTolerances &tolerances);
+
+} // namespace
+} // namespace
+
+#endif
index 3edbc9ce9fed40ae944ae711b9b597bc29e03e7d..2069aaaeb7f6603277e32b197cbd9066c729ad23 100644 (file)
@@ -50,6 +50,7 @@
 #include <string>
 #include <vector>
 
+#include "gromacs/compat/make_unique.h"
 #include "gromacs/fileio/enxio.h"
 #include "gromacs/trajectory/energyframe.h"
 #include "gromacs/utility/exceptions.h"
@@ -119,7 +120,8 @@ openEnergyFileToReadFields(const std::string              &filename,
         GMX_THROW(APIError(requiredEnergiesNotFound));
     }
 
-    return EnergyFrameReaderPtr(new EnergyFrameReader(indicesOfEnergyFields, energyFile.release()));
+    return EnergyFrameReaderPtr(compat::make_unique<EnergyFrameReader>(indicesOfEnergyFields,
+                                                                       energyFile.release()));
 }
 
 //! Helper function to obtain resources
@@ -192,24 +194,5 @@ EnergyFrameReader::frame()
     return EnergyFrame(*enxframeGuard_.get(), indicesOfEnergyFields_);
 }
 
-void compareFrames(const std::pair<EnergyFrame, EnergyFrame> &frames,
-                   FloatingPointTolerance tolerance)
-{
-    auto &reference = frames.first;
-    auto &test      = frames.second;
-
-    for (auto referenceIt = reference.begin(); referenceIt != reference.end(); ++referenceIt)
-    {
-        auto testIt = test.find(referenceIt->first);
-        if (testIt != test.end())
-        {
-            auto energyFieldInReference = referenceIt->second;
-            auto energyFieldInTest      = testIt->second;
-            EXPECT_REAL_EQ_TOL(energyFieldInReference, energyFieldInTest, tolerance)
-            << referenceIt->first << " didn't match between reference run " << reference.frameName() << " and test run " << test.frameName();
-        }
-    }
-}
-
 } // namespace
 } // namespace
index 3b6bad900e479583df88147be4d00979259ee0e3..7a2590055408666a039cb0aa79b4bcb7645e1a59 100644 (file)
@@ -54,9 +54,9 @@
 
 #include <cstdint>
 
-#include <map>
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include "gromacs/fileio/enxio.h"
@@ -72,6 +72,9 @@ class EnergyFrame;
 namespace test
 {
 
+//! Convenience type
+using EnergyTolerances = std::unordered_map<std::string, FloatingPointTolerance>;
+
 //! Forward declaration
 class EnergyFrameReader;
 //! Convenience smart pointer typedef
@@ -151,14 +154,6 @@ class EnergyFrameReader
         GMX_DISALLOW_COPY_AND_ASSIGN(EnergyFrameReader);
 };
 
-/*! \brief Compare all fields of reference with all matching fields from test
- *
- * Ignore any key found in either \c reference or \c test that is not
- * found in the other. For all keys found in both frames, compare the
- * values with EXPECT_REAL_EQ_TOL and the given tolerance. */
-void compareFrames(const std::pair<EnergyFrame, EnergyFrame> &frames,
-                   FloatingPointTolerance tolerance);
-
 } // namespace
 } // namespace
 
index f7a36f0f6bb8775820a9bf1082a89bb82f7371da..2dcfff52632a0f3904cc6b9ef51d1b514557098a 100644 (file)
@@ -104,6 +104,73 @@ prepareMdpFieldValues(const char *simulationName,
 std::string
 prepareMdpFileContents(const MdpFieldValues &mdpFieldValues);
 
+/*! \internal
+ * \brief Manages returning a pair of frames from two
+ * equivalent simulations that are meaningful to compare. */
+template <class FrameReader, class Frame>
+class FramePairManager
+{
+    public:
+        //! Convenience typedef
+        typedef std::unique_ptr<FrameReader> FrameReaderPtr;
+        //! Constructor
+        FramePairManager(FrameReaderPtr first,
+                         FrameReaderPtr second) :
+            first_(std::move(first)),
+            second_(std::move(second))
+        {}
+    private:
+        /*! \brief Probe for a pair of valid frames, and return true if both are found.
+         *
+         * Give a test failure if exactly one frame is found, because
+         * that file is longer than the other one, and this is not
+         * expected behaviour. */
+        bool shouldContinueComparing()
+        {
+            if (first_->readNextFrame())
+            {
+                if (second_->readNextFrame())
+                {
+                    // Two valid next frames exist, so we should continue comparing.
+                    return true;
+                }
+                else
+                {
+                    ADD_FAILURE() << "first file had at least one more frame than second file";
+                }
+            }
+            else
+            {
+                if (second_->readNextFrame())
+                {
+                    ADD_FAILURE() << "second file had at least one more frame than first file";
+                }
+                else
+                {
+                    // Both files ran out of frames at the same time, which is the expected behaviour.
+                }
+            }
+            // At least one file is out of frames, so should not continue comparing.
+            return false;
+        }
+    public:
+        //! Compare all possible pairs of frames using \c compareTwoFrames.
+        void compareAllFramePairs(std::function<void(const Frame &, const Frame &)> compareTwoFrames)
+        {
+            while (shouldContinueComparing())
+            {
+                auto firstFrame  = first_->frame();
+                auto secondFrame = second_->frame();
+                SCOPED_TRACE("Comparing frames from two runs '" + firstFrame.frameName() + "' and '" + secondFrame.frameName() + "'");
+                compareTwoFrames(firstFrame, secondFrame);
+            }
+
+        }
+    private:
+        FrameReaderPtr first_;
+        FrameReaderPtr second_;
+};
+
 } // namespace test
 } // namespace gmx
 
diff --git a/src/programs/mdrun/tests/trajectorycomparison.cpp b/src/programs/mdrun/tests/trajectorycomparison.cpp
new file mode 100644 (file)
index 0000000..e6905b2
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2016,2017,2018, 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+
+/*! \internal \file
+ * \brief Implemention of functions for comparing trajectories
+ * produced by mdrun.
+ *
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
+ * \ingroup module_mdrun_integration_tests
+ */
+#include "gmxpre.h"
+
+#include "trajectorycomparison.h"
+
+#include <gmock/gmock.h>
+
+#include "gromacs/pbcutil/pbc.h"
+#include "gromacs/trajectory/trajectoryframe.h"
+
+#include "testutils/testasserts.h"
+#include "testutils/testmatchers.h"
+
+namespace gmx
+{
+namespace test
+{
+
+using ::testing::Pointwise;
+
+/*! \brief Compares the box from \c reference and \c test
+ * according to the \c matchSettings and \c tolerance.
+ *
+ * \todo This could be streamlined when we have a proper 3D matrix
+ * class and view. */
+static void compareBox(const TrajectoryFrame              &reference,
+                       const TrajectoryFrame              &test,
+                       const TrajectoryFrameMatchSettings &matchSettings,
+                       const FloatingPointTolerance        tolerance)
+{
+    if (!matchSettings.mustCompareBox)
+    {
+        return;
+    }
+    bool canCompareBox = true;
+    if (!reference.hasBox())
+    {
+        ADD_FAILURE() << "Comparing the box was required, "
+        "but the reference frame did not have one";
+        canCompareBox = false;
+    }
+    if (!test.hasBox())
+    {
+        ADD_FAILURE() << "Comparing the box was required, "
+        "but the test frame did not have one";
+        canCompareBox = false;
+    }
+    if (!canCompareBox)
+    {
+        return;
+    }
+
+    // Do the comparing.
+    for (int d = 0; d < DIM; ++d)
+    {
+        for (int dd = 0; dd < DIM; ++dd)
+        {
+            EXPECT_REAL_EQ_TOL(reference.box()[d][dd], test.box()[d][dd], tolerance);
+        }
+    }
+}
+
+/*! \brief Help put all atom positions in \c frame into its box.
+ *
+ * This can perhaps go away when frame->x is a container. */
+static std::vector<RVec>
+putAtomsInBox(const TrajectoryFrame &frame)
+{
+    std::vector<RVec> x(frame.x().begin(), frame.x().end());
+    matrix            box;
+    for (int d = 0; d < DIM; ++d)
+    {
+        for (int dd = 0; dd < DIM; ++dd)
+        {
+            box[d][dd] = frame.box()[d][dd];
+        }
+    }
+    // Note we don't need to compare bPBC because put_atoms_in_box
+    // implements a fallback if nothing specific was set in the
+    // trajectory frame.
+    put_atoms_in_box(frame.pbc(), box, x);
+    return x;
+}
+
+/*! \brief Compares the positions from \c reference and \c test
+ * according to the \c matchSettings and \c tolerance. */
+static void comparePositions(const TrajectoryFrame              &reference,
+                             const TrajectoryFrame              &test,
+                             const TrajectoryFrameMatchSettings &matchSettings,
+                             const FloatingPointTolerance        tolerance)
+{
+    bool canHandlePbc = true;
+    if (!reference.hasBox())
+    {
+        if (matchSettings.mustComparePositions)
+        {
+            ADD_FAILURE() << "Comparing positions required PBC handling, "
+            "but the reference frame did not have a box";
+        }
+        canHandlePbc = false;
+    }
+    if (!test.hasBox())
+    {
+        if (matchSettings.mustComparePositions)
+        {
+            ADD_FAILURE() << "Comparing positions required PBC handling, "
+            "but the test frame did not have a box";
+        }
+        canHandlePbc = false;
+    }
+
+    if (matchSettings.requirePbcHandling && !canHandlePbc)
+    {
+        ADD_FAILURE() << "Cannot compare positions for the above reason(s)";
+        return;
+    }
+
+    if ((matchSettings.handlePbcIfPossible || matchSettings.requirePbcHandling) && canHandlePbc)
+    {
+        EXPECT_THAT(putAtomsInBox(test), Pointwise(RVecEq(tolerance), putAtomsInBox(reference)));
+    }
+    else
+    {
+        EXPECT_THAT(test.x(), Pointwise(RVecEq(tolerance), reference.x()));
+    }
+}
+
+/*! \brief Compares the velocities from \c reference and \c test
+ * according to the \c matchSettings and \c tolerance. */
+static void compareVelocities(const TrajectoryFrame              &reference,
+                              const TrajectoryFrame              &test,
+                              const TrajectoryFrameMatchSettings &matchSettings,
+                              const FloatingPointTolerance        tolerance)
+{
+    if (!matchSettings.mustCompareVelocities)
+    {
+        return;
+    }
+    EXPECT_THAT(test.v(), Pointwise(RVecEq(tolerance), reference.v()));
+}
+
+/*! \brief Compares the forces from \c reference and \c test
+ * according to the \c matchSettings and \c tolerance. */
+static void compareForces(const TrajectoryFrame              &reference,
+                          const TrajectoryFrame              &test,
+                          const TrajectoryFrameMatchSettings &matchSettings,
+                          const FloatingPointTolerance        tolerance)
+{
+    if (!matchSettings.mustCompareForces)
+    {
+        return;
+    }
+    EXPECT_THAT(test.f(), Pointwise(RVecEq(tolerance), reference.f()));
+}
+
+
+void compareTrajectoryFrames(const TrajectoryFrame              &reference,
+                             const TrajectoryFrame              &test,
+                             const TrajectoryFrameMatchSettings &matchSettings,
+                             const TrajectoryTolerances         &tolerances)
+{
+    SCOPED_TRACE("Comparing reference frame " + reference.frameName() + " and test frame " + test.frameName());
+    EXPECT_EQ(reference.step(), test.step());
+    EXPECT_EQ(reference.time(), test.time());
+    compareBox(reference, test, matchSettings, tolerances.box);
+    comparePositions(reference, test, matchSettings, tolerances.positions);
+    compareVelocities(reference, test, matchSettings, tolerances.velocities);
+    compareForces(reference, test, matchSettings, tolerances.forces);
+}
+
+} // namespace
+} // namespace
diff --git a/src/programs/mdrun/tests/trajectorycomparison.h b/src/programs/mdrun/tests/trajectorycomparison.h
new file mode 100644 (file)
index 0000000..cfa018c
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2016,2017,2018, 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+
+/*! \internal \file
+ * \brief Declares types and functions for comparing trajectories
+ * produced by mdrun.
+ *
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
+ * \ingroup module_mdrun_integration_tests
+ */
+#ifndef GMX_PROGRAMS_MDRUN_TESTS_TRAJECTORYCOMPARISON_H
+#define GMX_PROGRAMS_MDRUN_TESTS_TRAJECTORYCOMPARISON_H
+
+#include "testutils/testasserts.h"
+
+namespace gmx
+{
+
+class TrajectoryFrame;
+
+namespace test
+{
+
+/*! \internal
+ * \brief Helper struct for testing different trajectory components with different tolerances. */
+struct TrajectoryTolerances
+{
+    /*!@{*/
+    /*! \brief Tolerances for reproduction of different quantities. */
+    FloatingPointTolerance box, positions, velocities, forces;
+    /*!@}*/
+};
+
+/*! \internal
+ * \brief Helper struct to specify the expected behaviour of compareFrames().
+ *
+ * By default, nothing is required to be compared, but the comparer will
+ * compare what it can with the frames it is given.
+ *
+ * Handling PBC refers to putting all the atoms in the simulation box,
+ * which requires that both the PBC type and a simulation box are
+ * available from the trajectory frame. */
+struct TrajectoryFrameMatchSettings
+{
+    //! Whether boxes must be compared.
+    bool mustCompareBox;
+    //! Whether positions must be compared.
+    bool mustComparePositions;
+    //! Whether PBC will be handled if it can be handled.
+    bool handlePbcIfPossible;
+    //! Whether PBC handling must occur for a valid comparison.
+    bool requirePbcHandling;
+    //! Whether velocities must be compared.
+    bool mustCompareVelocities;
+    //! Whether forces must be compared.
+    bool mustCompareForces;
+};
+
+/*! \brief Compare the fields of the two frames for equality given
+ * the \c matchSettings and \c tolerances.
+ *
+ * The two frames are required to have valid and matching values for
+ * time and step. According to \c matchSettings, box, positions,
+ * velocities and/or forces will be compared between frames, using the
+ * \c tolerances. Comparisons will only occur when both frames have
+ * the requisite data, and will be expected to be equal within the
+ * matching component of \c tolerances. If a comparison fails, a
+ * GoogleTest expectation failure will be given. If a comparison is
+ * required by \c matchSettings but cannot be done because either (or
+ * both) frames lack the requisite data, descriptive expectation
+ * failures will be given. */
+void compareTrajectoryFrames(const TrajectoryFrame              &reference,
+                             const TrajectoryFrame              &test,
+                             const TrajectoryFrameMatchSettings &matchSettings,
+                             const TrajectoryTolerances         &tolerances);
+
+} // namespace
+} // namespace
+
+#endif
index 6dfdd28b087c6f03cb807ab2138e3069bf24c0dd..cf43cf89ee574ff324889daf95e2aa9b036d1011 100644 (file)
@@ -51,9 +51,9 @@
 #include "gromacs/fileio/trxio.h"
 #include "gromacs/trajectory/trajectoryframe.h"
 #include "gromacs/utility/exceptions.h"
-#include "gromacs/utility/stringutil.h"
 
 #include "testutils/testasserts.h"
+#include "testutils/testmatchers.h"
 
 namespace gmx
 {
@@ -82,8 +82,6 @@ void done_trxframe(t_trxframe *fr)
     sfree(fr);
 }
 
-// === TrajectoryFrameReader ===
-
 TrajectoryFrameReader::TrajectoryFrameReader(const std::string &filename)
     : filename_(filename),
       trajectoryFileGuard_(nullptr),
@@ -147,7 +145,7 @@ TrajectoryFrameReader::frame()
     }
     if (!nextFrameExists_)
     {
-        GMX_THROW(APIError("There is no next frame, so there should have been no attempt to use the data, e.g. by reacting to a call to readNextFrame()."));
+        GMX_THROW(APIError("There is no next frame, so there should have been no attempt to get it. Perhaps the return value of readNextFrame() was misused."));
     }
 
     // Prepare for reading future frames
@@ -158,42 +156,5 @@ TrajectoryFrameReader::frame()
     return TrajectoryFrame(*trxframeGuard_.get());
 }
 
-void compareFrames(const std::pair<TrajectoryFrame, TrajectoryFrame> &frames,
-                   FloatingPointTolerance tolerance)
-{
-    auto &reference = frames.first;
-    auto &test      = frames.second;
-
-    // NB We checked earlier for both frames that bStep and bTime are set
-
-    EXPECT_EQ(reference.frame_.step, test.frame_.step)
-    << "step didn't match between reference run " << reference.frameName() << " and test run " << test.frameName();
-
-    EXPECT_EQ(reference.frame_.time, test.frame_.time)
-    << "time didn't match between reference run " << reference.frameName() << " and test run " << test.frameName();
-
-    for (int i = 0; i < reference.frame_.natoms && i < test.frame_.natoms; ++i)
-    {
-        for (int d = 0; d < DIM; ++d)
-        {
-            if (reference.frame_.bX && test.frame_.bX)
-            {
-                EXPECT_REAL_EQ_TOL(reference.frame_.x[i][d], test.frame_.x[i][d], tolerance)
-                << " x[" << i << "][" << d <<"] didn't match between reference run " << reference.frameName() << " and test run " << test.frameName();
-            }
-            if (reference.frame_.bV && test.frame_.bV)
-            {
-                EXPECT_REAL_EQ_TOL(reference.frame_.v[i][d], test.frame_.v[i][d], tolerance)
-                << " v[" << i << "][" << d <<"] didn't match between reference run " << reference.frameName() << " and test run " << test.frameName();
-            }
-            if (reference.frame_.bF && test.frame_.bF)
-            {
-                EXPECT_REAL_EQ_TOL(reference.frame_.f[i][d], test.frame_.f[i][d], tolerance)
-                << " f[" << i << "][" << d <<"] didn't match between reference run " << reference.frameName() << " and test run " << test.frameName();
-            }
-        }
-    }
-}
-
 } // namespace
 } // namespace
index b209015f137c55c2f5ddc465ec5efaadf1f2df5e..9de1f9e47eccb33400e6a393c12166db8feb94ac 100644 (file)
@@ -38,9 +38,8 @@
  * trajectories produced by mdrun.
  *
  * Intended usage is to create a TrajectoryFrameReader. Successive
- * calls to its TrajectoryFrameReader::readNextFrame() and
- * TrajectoryFrameReader::frame() methods will return a handle to a
- * t_trxframe object for each frame.
+ * calls to its readNextFrameStub and frame methods will return a handle
+ * to a t_trxframe object for each frame.
  *
  * \author Mark Abraham <mark.j.abraham@gmail.com>
  * \ingroup module_mdrun_integration_tests
 #include "gromacs/fileio/oenv.h"
 #include "gromacs/fileio/trxio.h"
 #include "gromacs/trajectory/trajectoryframe.h"
+#include "gromacs/utility/classhelpers.h"
 #include "gromacs/utility/unique_cptr.h"
 
-#include "testutils/testasserts.h"
-
-//! Forward declaration
-struct gmx_output_env_t;
-
 namespace gmx
 {
 
@@ -93,10 +88,10 @@ class TrajectoryFrameReader
          * API, which does the file opening as a side effect of
          * reading the first frame.
          *
-         * If true is returned, then frame() should be called
+         * If true is returned, then TrajectoryFrame frame() should be called
          * to get access to the data. If false is returned, then no
          * further data exists and no further call to
-         * readNextFrame() or frame() should occur.
+         * readNextFrameStub or TrajectoryFrame frame() should occur.
          *
          * \throws FileIOError upon reading the first frame, if the trajectory file cannot be opened
          * \throws APIError    if an earlier probe has not been properly handled
@@ -107,9 +102,9 @@ class TrajectoryFrameReader
          *
          * If the next frame has not been probed for, then probe for
          * it. If no next frame exists, then throw APIError, because
-         * user code should have called readNextFrame() itself if this
+         * user code should have called readNextFrameStub itself if this
          * is possible. (This permits user code to avoid making calls
-         * to readNextFrame() in a case where it already knows that
+         * to readNextFrameStub in a case where it already knows that
          * the frame exists.)
          *
          * \throws APIError  if no next frame exists, or if it lacks either time or step number. */
@@ -141,16 +136,6 @@ class TrajectoryFrameReader
 //! Convenience smart pointer typedef
 typedef std::unique_ptr<TrajectoryFrameReader> TrajectoryFrameReaderPtr;
 
-/*! \brief Compare the fields of the two frames for equality within
- * the \c tolerance.
- *
- * The two frames are required to have valid and matching values for
- * time and step. Positions, velocities and/or forces will be compared
- * when present in both frames, and expected to be equal within \c
- * tolerance. */
-void compareFrames(const std::pair<TrajectoryFrame, TrajectoryFrame> &frames,
-                   FloatingPointTolerance tolerance);
-
 } // namespace
 } // namespace
 
index 8d48a3a3c3d0e1f1895081ffbf6543f76249b8fa..73804b25dbf456c351baef90eac819af57412b86 100644 (file)
@@ -1,7 +1,7 @@
 #
 # This file is part of the GROMACS molecular simulation package.
 #
-# Copyright (c) 2011,2012,2013,2014,2015,2016,2017, by the GROMACS development team, led by
+# Copyright (c) 2011,2012,2013,2014,2015,2016,2017,2018, 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.
@@ -54,6 +54,7 @@ set(TESTUTILS_SOURCES
     testfilemanager.cpp
     testfileredirector.cpp
     testinit.cpp
+    testmatchers.cpp
     testoptions.cpp
     textblockmatchers.cpp
     xvgtest.cpp
diff --git a/src/testutils/testmatchers.cpp b/src/testutils/testmatchers.cpp
new file mode 100644 (file)
index 0000000..67a7997
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2018, 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+
+/*! \internal \file
+ * \brief Implements floating-point matchers from testmatchers.h for
+ * use with Google Mock.
+ *
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
+ * \ingroup module_testutils
+ */
+#include "gmxpre.h"
+
+#include "testmatchers.h"
+
+#include <memory>
+
+#include <gmock/gmock.h>
+
+#include "testutils/testasserts.h"
+
+namespace gmx
+{
+namespace test
+{
+
+/*! \brief Implementation class for RealEq matcher.
+ *
+ * See RealEq().
+ *
+ * The implementation is templated so that we can support all of real,
+ * float and double in the same build without duplication.
+ */
+template <typename FloatType>
+class FloatTypeMatcher : public testing::MatcherInterface < std::tuple < FloatType, FloatType>>
+{
+    public:
+        //! Constructor
+        FloatTypeMatcher(const FloatingPointTolerance &tolerance)
+            : tolerance_(tolerance) {}
+        //! Compare the two elements of \c arg, return whether they are equal, and comment on \c listener when they are not.
+        virtual bool MatchAndExplain(std::tuple<FloatType, FloatType> arg,
+                                     testing::MatchResultListener* listener) const
+        {
+            const FloatType        &value1 = std::get<0>(arg);
+            const FloatType        &value2 = std::get<1>(arg);
+            FloatingPointDifference diff(value1, value2);
+            if (tolerance_.isWithin(diff))
+            {
+                return true;
+            }
+            *listener->stream()
+            << "  Actual value: " << value2 << std::endl
+            << "Expected value: " << value1 << std::endl
+            << "    Difference: " << diff.toString() << std::endl
+            << "     Tolerance: " << tolerance_.toString(diff);
+            return false;
+        }
+        //! Describe to a human what matching means.
+        virtual void DescribeTo(::std::ostream* os) const
+        {
+            *os << "matches within tolerance";
+        }
+        //! Describe to a human what failing to match means.
+        virtual void DescribeNegationTo(::std::ostream* os) const
+        {
+            *os << "does not match within tolerance";
+        }
+    private:
+        //! Tolerance used in matching
+        FloatingPointTolerance tolerance_;
+};
+
+testing::Matcher < std::tuple < float, float>>
+FloatEq(const FloatingPointTolerance &tolerance)
+{
+    return testing::MakeMatcher(new FloatTypeMatcher<float>(tolerance));
+}
+
+testing::Matcher < std::tuple < double, double>>
+DoubleEq(const FloatingPointTolerance &tolerance)
+{
+    return testing::MakeMatcher(new FloatTypeMatcher<double>(tolerance));
+}
+
+testing::Matcher < std::tuple < real, real>>
+RealEq(const FloatingPointTolerance &tolerance)
+{
+    return testing::MakeMatcher(new FloatTypeMatcher<real>(tolerance));
+}
+
+/*! \brief Implementation class for RvecEq matcher
+ *
+ * See RvecEq().
+ */
+template <typename FloatType>
+class RVecMatcher :
+    public testing::MatcherInterface < std::tuple < BasicVector<FloatType>, BasicVector<FloatType>>>
+{
+    public:
+        //! Convenience type
+        using VectorType = BasicVector<FloatType>;
+        //! Constructor
+        RVecMatcher(const FloatingPointTolerance &tolerance)
+            : tolerance_(tolerance) {}
+        //! Compare the two elements of \c arg, return whether they are equal, and comment on \c listener when they are not.
+        virtual bool MatchAndExplain(std::tuple<VectorType, VectorType> arg,
+                                     testing::MatchResultListener* listener) const
+        {
+            const VectorType           &lhs = std::get<0>(arg);
+            const VectorType           &rhs = std::get<1>(arg);
+            FloatTypeMatcher<FloatType> floatTypeMatcher(tolerance_);
+            bool matches = true;
+            for (int d = 0; d < DIM; ++d)
+            {
+                auto floatTuple = std::make_tuple<FloatType, FloatType>(lhs[d], rhs[d]);
+                matches = matches && floatTypeMatcher.MatchAndExplain(floatTuple, listener);
+            }
+            return matches;
+        }
+        //! Describe to a human what matching means.
+        virtual void DescribeTo(::std::ostream* os) const
+        {
+            *os << "matches all elements within tolerance";
+        }
+        //! Describe to a human what failing to match means.
+        virtual void DescribeNegationTo(::std::ostream* os) const
+        {
+            *os << "does not match all elements within tolerance";
+        }
+    private:
+        //! Tolerance used in matching
+        FloatingPointTolerance tolerance_;
+};
+
+// Currently there's no need for explicit float or double flavours of
+// RVec comparison, but those would be simple to add later.
+testing::Matcher < std::tuple < RVec, RVec>>
+RVecEq(const FloatingPointTolerance &tolerance)
+{
+    return testing::MakeMatcher(new RVecMatcher<real>(tolerance));
+}
+
+} // namespace
+} // namespace
diff --git a/src/testutils/testmatchers.h b/src/testutils/testmatchers.h
new file mode 100644 (file)
index 0000000..c78d3e9
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2018, 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+
+/*! \libinternal \file
+ * \brief Extra GoogleMock matchers for unit tests.
+ *
+ * This file provides the usual kind of GoogleMock matchers that
+ * extend the usefulness of GoogleMock EXPECT_THAT constructs to the
+ * kinds of containers of reals commonly used. This means that test
+ * code can write one-liners rather than loops over whole containers.
+ *
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_testutils
+ */
+#ifndef GMX_TESTUTILS_TESTMATCHERS_H
+#define GMX_TESTUTILS_TESTMATCHERS_H
+
+#include <memory>
+
+#include <gmock/gmock.h>
+
+#include "gromacs/math/vectypes.h"
+#include "gromacs/utility/real.h"
+
+#include "testutils/testasserts.h"
+
+namespace gmx
+{
+namespace test
+{
+
+/*! \brief Make matcher for floats for use with GoogleMock that compare
+ * equal when \c tolerance is satisifed.
+ *
+ * Used like
+ *
+ *   EXPECT_THAT(testFloats, Pointwise(FloatEq(tolerance), referenceFloats));
+ */
+testing::Matcher < std::tuple < float, float>>
+FloatEq(const FloatingPointTolerance &tolerance);
+
+/*! \brief Make matcher for doubles for use with GoogleMock that compare
+ * equal when \c tolerance is satisifed.
+ *
+ * Used like
+ *
+ *   EXPECT_THAT(testDoubles, Pointwise(DoubleEq(tolerance), referenceDoubles));
+ */
+testing::Matcher < std::tuple < double, double>>
+DoubleEq(const FloatingPointTolerance &tolerance);
+
+/*! \brief Make matcher for reals for use with GoogleMock that compare
+ * equal when \c tolerance is satisifed.
+ *
+ * Used like
+ *
+ *   EXPECT_THAT(testReals, Pointwise(RealEq(tolerance), referenceReals));
+ */
+testing::Matcher < std::tuple < real, real>>
+RealEq(const FloatingPointTolerance &tolerance);
+
+/*! \brief Make matcher for RVecs for use with GoogleMock that compare
+ * equal when \c tolerance is satisifed.
+ *
+ * Used like
+ *
+ *   EXPECT_THAT(testRVecs, Pointwise(RVecEq(tolerance), referenceRVecs));
+ */
+testing::Matcher < std::tuple < RVec, RVec>>
+RVecEq(const FloatingPointTolerance &tolerance);
+
+} // namespace
+} // namespace
+
+#endif