/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2017, by the GROMACS development team, led by
+ * 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.
#include <gtest/gtest.h>
#include "gromacs/fileio/warninp.h"
-#include "gromacs/mdrunutility/mdmodules.h"
+#include "gromacs/mdrun/mdmodules.h"
#include "gromacs/mdtypes/inputrec.h"
#include "gromacs/utility/cstringutil.h"
#include "gromacs/utility/stringutil.h"
#include "gromacs/utility/unique_cptr.h"
#include "testutils/refdata.h"
+#include "testutils/testasserts.h"
#include "testutils/testfilemanager.h"
-
namespace gmx
{
namespace test
class GetIrTest : public ::testing::Test
{
- public:
- GetIrTest() : fileManager_(), data_(), checker_(data_.rootChecker()),
- ir_(), mdModules_(), opts_(),
- wi_(init_warning(FALSE, 0)), wiGuard_(wi_)
+public:
+ GetIrTest() : wi_(init_warning(FALSE, 0)), wiGuard_(wi_)
+ {
+ snew(opts_.include, STRLEN);
+ snew(opts_.define, STRLEN);
+ }
+ ~GetIrTest() override
+ {
+ done_inputrec_strings();
+ sfree(opts_.include);
+ sfree(opts_.define);
+ }
+ //! Tells whether warnings and/or errors are expected from inputrec parsing and checking, and whether we should compare the output
+ enum class TestBehavior
+ {
+ NoErrorAndCompareOutput, //!< Expect no warnings/error and compare output
+ NoErrorAndDoNotCompareOutput, //!< Expect no warnings/error and do not compare output
+ ErrorAndCompareOutput, //!< Expect at least one warning/error and compare output
+ ErrorAndDoNotCompareOutput //!< Expect at least one warning/error and do not compare output
+ };
+
+ /*! \brief Test mdp reading and writing
+ *
+ * \todo Modernize read_inp and write_inp to use streams,
+ * which will make these tests run faster, because they don't
+ * use disk files. */
+ void runTest(const std::string& inputMdpFileContents,
+ const TestBehavior testBehavior = TestBehavior::NoErrorAndCompareOutput)
+ {
+ const bool expectError = testBehavior == TestBehavior::ErrorAndCompareOutput
+ || testBehavior == TestBehavior::ErrorAndDoNotCompareOutput;
+ const bool compareOutput = testBehavior == TestBehavior::ErrorAndCompareOutput
+ || testBehavior == TestBehavior::NoErrorAndCompareOutput;
+
+ std::string inputMdpFilename = fileManager_.getTemporaryFilePath("input.mdp");
+ std::string outputMdpFilename;
+ if (compareOutput)
{
- snew(opts_.include, STRLEN);
- snew(opts_.define, STRLEN);
- }
- ~GetIrTest()
- {
- done_inputrec_strings();
- sfree(opts_.include);
- sfree(opts_.define);
+ outputMdpFilename = fileManager_.getTemporaryFilePath("output.mdp");
}
- /*! \brief Test mdp reading and writing
- *
- * \todo Modernize read_inp and write_inp to use streams,
- * which will make these tests run faster, because they don't
- * use disk files. */
- void runTest(const std::string &inputMdpFileContents)
- {
- auto inputMdpFilename = fileManager_.getTemporaryFilePath("input.mdp");
- auto outputMdpFilename = fileManager_.getTemporaryFilePath("output.mdp");
+ TextWriter::writeFileFromString(inputMdpFilename, inputMdpFileContents);
+
+ get_ir(inputMdpFilename.c_str(),
+ outputMdpFilename.empty() ? nullptr : outputMdpFilename.c_str(),
+ &mdModules_,
+ &ir_,
+ &opts_,
+ WriteMdpHeader::no,
+ wi_);
- TextWriter::writeFileFromString(inputMdpFilename, inputMdpFileContents);
+ check_ir(inputMdpFilename.c_str(), mdModules_.notifiers(), &ir_, &opts_, wi_);
+ // Now check
+ bool failure = warning_errors_exist(wi_);
+ EXPECT_EQ(failure, expectError);
- get_ir(inputMdpFilename.c_str(), outputMdpFilename.c_str(),
- &mdModules_, &ir_, &opts_, WriteMdpHeader::no, wi_);
- bool failure = warning_errors_exist(wi_);
- checker_.checkBoolean(failure, "Error parsing mdp file");
+ if (compareOutput)
+ {
+ TestReferenceData data;
+ TestReferenceChecker checker(data.rootChecker());
+ checker.checkBoolean(failure, "Error parsing mdp file");
warning_reset(wi_);
auto outputMdpContents = TextReader::readFileToString(outputMdpFilename);
- checker_.checkString(outputMdpContents, "OutputMdpFile");
+ checker.checkString(outputMdpContents, "OutputMdpFile");
}
+ }
- TestFileManager fileManager_;
- TestReferenceData data_;
- TestReferenceChecker checker_;
- t_inputrec ir_;
- MDModules mdModules_;
- t_gromppopts opts_;
- warninp_t wi_;
- unique_cptr<warninp, free_warning> wiGuard_;
+ TestFileManager fileManager_;
+ t_inputrec ir_;
+ MDModules mdModules_;
+ t_gromppopts opts_;
+ warninp_t wi_;
+ unique_cptr<warninp, free_warning> wiGuard_;
};
TEST_F(GetIrTest, HandlesDifferentKindsOfMdpLines)
{
- const char *inputMdpFile[] = {
- "; File to run my simulation",
- "title = simulation",
- ";",
- "xtc_grps = System ; was Protein",
- "include = -I/home/me/stuff",
- "",
- "tau-t = 0.1 0.3",
- "tinit = 0.3",
- "init_step = 0",
- "nstcomm = 100",
- "integrator = steep"
- };
+ const char* inputMdpFile[] = { "; File to run my simulation",
+ "title = simulation",
+ "define = -DBOOLVAR -DVAR=VALUE",
+ ";",
+ "xtc_grps = System ; was Protein",
+ "include = -I/home/me/stuff",
+ "",
+ "tau-t = 0.1 0.3",
+ "ref-t = ;290 290",
+ "tinit = 0.3",
+ "init_step = 0",
+ "nstcomm = 100",
+ "integrator = steep" };
runTest(joinStrings(inputMdpFile, "\n"));
}
-// This case is used often by SimulationRunner::useEmptyMdpFile (see
-// comments there for explanation). When we remove the group scheme,
-// that usage will have to disappear, and so can this test.
-TEST_F(GetIrTest, HandlesOnlyCutoffScheme)
+TEST_F(GetIrTest, RejectsNonCommentLineWithNoEquals)
{
- const char *inputMdpFile = "cutoff-scheme = Group\n";
+ const char* inputMdpFile = "title simulation";
+ GMX_EXPECT_DEATH_IF_SUPPORTED(runTest(inputMdpFile), "No '=' to separate");
+}
+
+TEST_F(GetIrTest, AcceptsKeyWithoutValue)
+{
+ // Users are probably using lines like this
+ const char* inputMdpFile = "xtc_grps = ";
runTest(inputMdpFile);
}
-// TODO Stop accepting any of these
-TEST_F(GetIrTest, UserErrorsSilentlyTolerated)
+TEST_F(GetIrTest, RejectsValueWithoutKey)
+{
+ const char* inputMdpFile = "= -I/home/me/stuff";
+ GMX_EXPECT_DEATH_IF_SUPPORTED(runTest(inputMdpFile), "No .mdp parameter name was found");
+}
+
+TEST_F(GetIrTest, RejectsEmptyKeyAndEmptyValue)
{
- const char *inputMdpFile[] = {
- "title simulation",
- "xtc_grps = ",
- "= -I/home/me/stuff",
- "="
+ const char* inputMdpFile = " = ";
+ GMX_EXPECT_DEATH_IF_SUPPORTED(runTest(inputMdpFile),
+ "No .mdp parameter name or value was found");
+}
+
+TEST_F(GetIrTest, AcceptsDefineParametersWithValuesIncludingAssignment)
+{
+ const char* inputMdpFile[] = {
+ "define = -DBOOL -DVAR=VALUE",
};
runTest(joinStrings(inputMdpFile, "\n"));
}
-TEST_F(GetIrTest, EmptyInputWorks)
+TEST_F(GetIrTest, AcceptsEmptyLines)
{
- const char *inputMdpFile = "";
+ const char* inputMdpFile = "";
runTest(inputMdpFile);
}
-// This test observes how the electric-field keys behave, since they
+TEST_F(GetIrTest, MtsCheckNstcalcenergy)
+{
+ const char* inputMdpFile[] = {
+ "mts = yes", "mts-levels = 2", "mts-level2-factor = 2", "nstcalcenergy = 5"
+ };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::ErrorAndDoNotCompareOutput);
+}
+
+TEST_F(GetIrTest, MtsCheckNstenergy)
+{
+ const char* inputMdpFile[] = {
+ "mts = yes", "mts-levels = 2", "mts-level2-factor = 2", "nstenergy = 5"
+ };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::ErrorAndDoNotCompareOutput);
+}
+
+TEST_F(GetIrTest, MtsCheckNstpcouple)
+{
+ const char* inputMdpFile[] = { "mts = yes",
+ "mts-levels = 2",
+ "mts-level2-factor = 2",
+ "pcoupl = Berendsen",
+ "nstpcouple = 5" };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::ErrorAndDoNotCompareOutput);
+}
+
+TEST_F(GetIrTest, MtsCheckNstdhdl)
+{
+ const char* inputMdpFile[] = {
+ "mts = yes", "mts-level2-factor = 2", "free-energy = yes", "nstdhdl = 5"
+ };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::ErrorAndDoNotCompareOutput);
+}
+
+// These tests observe how the electric-field keys behave, since they
// are currently the only ones using the new Options-style handling.
-TEST_F(GetIrTest, ProducesOutputFromElectricField)
+TEST_F(GetIrTest, AcceptsElectricField)
{
- const char *inputMdpFile = "E-x = 1 1.2 -1";
+ const char* inputMdpFile = "electric-field-x = 1.2 0 0 0";
runTest(inputMdpFile);
}
-} // namespace
-} // namespace
+TEST_F(GetIrTest, AcceptsElectricFieldPulsed)
+{
+ const char* inputMdpFile = "electric-field-y = 3.7 2.0 6.5 1.0";
+ runTest(inputMdpFile);
+}
+
+TEST_F(GetIrTest, AcceptsElectricFieldOscillating)
+{
+ const char* inputMdpFile = "electric-field-z = 3.7 7.5 0 0";
+ runTest(inputMdpFile);
+}
+
+TEST_F(GetIrTest, RejectsDuplicateOldAndNewKeys)
+{
+ const char* inputMdpFile[] = { "verlet-buffer-drift = 1.3", "verlet-buffer-tolerance = 2.7" };
+ GMX_EXPECT_DEATH_IF_SUPPORTED(runTest(joinStrings(inputMdpFile, "\n")),
+ "A parameter is present with both");
+}
+
+TEST_F(GetIrTest, AcceptsImplicitSolventNo)
+{
+ const char* inputMdpFile = "implicit-solvent = no";
+ runTest(inputMdpFile);
+}
+
+TEST_F(GetIrTest, RejectsImplicitSolventYes)
+{
+ const char* inputMdpFile = "implicit-solvent = yes";
+ GMX_EXPECT_DEATH_IF_SUPPORTED(runTest(inputMdpFile), "Invalid enum");
+}
+
+TEST_F(GetIrTest, AcceptsMimic)
+{
+ const char* inputMdpFile[] = { "integrator = mimic", "QMMM-grps = QMatoms" };
+ runTest(joinStrings(inputMdpFile, "\n"));
+}
+
+#if HAVE_MUPARSER
+
+TEST_F(GetIrTest, AcceptsTransformationCoord)
+{
+ const char* inputMdpFile[] = {
+ "pull = yes",
+ "pull-ngroups = 2",
+ "pull-ncoords = 2",
+ "pull-coord1-geometry = distance",
+ "pull-coord1-groups = 1 2",
+ "pull-coord1-k = 1",
+ "pull-coord2-geometry = transformation",
+ "pull-coord2-expression = 1/(1+x1)",
+ "pull-coord2-k = 10",
+ };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::NoErrorAndDoNotCompareOutput);
+}
+
+TEST_F(GetIrTest, InvalidTransformationCoordWithConstraint)
+{
+ const char* inputMdpFile[] = {
+ "pull = yes",
+ "pull-ncoords = 1",
+ "pull-coord1-geometry = transformation",
+ "pull-coord1-type = constraint", // INVALID
+ "pull-coord1-expression = 10",
+ };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::ErrorAndDoNotCompareOutput);
+}
+
+TEST_F(GetIrTest, InvalidPullCoordWithConstraintInTransformationExpression)
+{
+ const char* inputMdpFile[] = {
+ "pull = yes",
+ "pull-ngroups = 2",
+ "pull-ncoords = 2",
+ "pull-coord1-geometry = distance",
+ "pull-coord1-type = constraint", // INVALID
+ "pull-coord1-groups = 1 2",
+ "pull-coord2-geometry = transformation",
+ "pull-coord2-expression = x1",
+ };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::ErrorAndDoNotCompareOutput);
+}
+
+TEST_F(GetIrTest, InvalidTransformationCoordDxValue)
+{
+ const char* inputMdpFile[] = {
+ "pull = yes",
+ "pull-ncoords = 1",
+ "pull-coord1-geometry = transformation",
+ "pull-coord1-expression = 10",
+ "pull-coord1-dx = 0", // INVALID
+ };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::ErrorAndDoNotCompareOutput);
+}
+
+TEST_F(GetIrTest, MissingTransformationCoordExpression)
+{
+ const char* inputMdpFile[] = {
+ "pull = yes",
+ "pull-ncoords = 1",
+ "pull-coord1-geometry = transformation",
+ };
+ runTest(joinStrings(inputMdpFile, "\n"), TestBehavior::ErrorAndDoNotCompareOutput);
+}
+#endif // HAVE_MUPARSER
+
+} // namespace test
+} // namespace gmx