Prune change-management.rst and update links.
[alexxy/gromacs.git] / src / programs / mdrun / tests / termination.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,2017,2018,2019,2020, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35
36 /*! \internal \file
37  * \brief
38  * Tests for the mdrun termination functionality
39  *
40  * \todo This approach is not very elegant, but "stuff doesn't
41  * segfault or give a fatal error" is a useful result. We can improve
42  * it when we can mock out more do_md() functionality. Before that,
43  * we'd probably prefer not to run this test case in per-patchset
44  * verification, but this is the best we can do for now.
45  *
46  * \author Mark Abraham <mark.j.abraham@gmail.com>
47  * \ingroup module_mdrun_integration_tests
48  */
49 #include "gmxpre.h"
50
51 #include <gtest/gtest.h>
52
53 #include "gromacs/utility/path.h"
54 #include "gromacs/utility/stringutil.h"
55 #include "gromacs/utility/textreader.h"
56
57 #include "testutils/testasserts.h"
58 #include "testutils/testfilemanager.h"
59
60 #include "moduletest.h"
61 #include "terminationhelper.h"
62
63 namespace gmx
64 {
65 namespace test
66 {
67
68 //! Build a simple .mdp file
69 static void organizeMdpFile(SimulationRunner* runner, int nsteps = 2)
70 {
71     // Make sure -maxh has a chance to propagate
72     runner->useStringAsMdpFile(
73             formatString("nsteps = %d\n"
74                          "tcoupl = v-rescale\n"
75                          "tc-grps = System\n"
76                          "tau-t = 1\n"
77                          "ref-t = 298\n",
78                          nsteps));
79 }
80
81 //! Convenience typedef
82 typedef MdrunTestFixture MdrunTerminationTest;
83
84 TEST_F(MdrunTerminationTest, CheckpointRestartAppendsByDefault)
85 {
86     runner_.cptFileName_ = fileManager_.getTemporaryFilePath(".cpt");
87
88     runner_.useTopGroAndNdxFromDatabase("spc2");
89     organizeMdpFile(&runner_);
90     EXPECT_EQ(0, runner_.callGrompp());
91
92     SCOPED_TRACE("Running the first simulation part");
93     {
94         CommandLine firstPart;
95         firstPart.append("mdrun");
96         firstPart.addOption("-cpo", runner_.cptFileName_);
97         ASSERT_EQ(0, runner_.callMdrun(firstPart));
98         ASSERT_TRUE(File::exists(runner_.cptFileName_, File::returnFalseOnError))
99                 << runner_.cptFileName_ << " was not found and should be";
100     }
101     SCOPED_TRACE("Running the second simulation part with default appending behavior");
102     {
103         runner_.changeTprNsteps(4);
104
105         CommandLine secondPart;
106         secondPart.append("mdrun");
107         secondPart.addOption("-cpi", runner_.cptFileName_);
108         ASSERT_EQ(0, runner_.callMdrun(secondPart));
109
110         auto logFileContents = TextReader::readFileToString(runner_.logFileName_);
111         EXPECT_NE(
112                 std::string::npos,
113                 logFileContents.find("Restarting from checkpoint, appending to previous log file"))
114                 << "appending was not detected";
115     }
116 }
117
118 TEST_F(MdrunTerminationTest, WritesCheckpointAfterMaxhTerminationAndThenRestarts)
119 {
120     runner_.cptFileName_ = fileManager_.getTemporaryFilePath(".cpt");
121
122     runner_.useTopGroAndNdxFromDatabase("spc2");
123     organizeMdpFile(&runner_, 100);
124     EXPECT_EQ(0, runner_.callGrompp());
125
126     SCOPED_TRACE("Running the first simulation part with -maxh");
127     {
128         CommandLine firstPart;
129         firstPart.append("mdrun");
130         firstPart.addOption("-cpo", runner_.cptFileName_);
131         // Ensure maxh will trigger the halt, and that the signal will
132         // have time to be propagated.
133         //
134         // TODO It would be nicer to set nstlist in the .mdp file, but
135         // then it is not a command.
136         firstPart.addOption("-maxh", 1e-7);
137         firstPart.addOption("-nstlist", 1);
138         ASSERT_EQ(0, runner_.callMdrun(firstPart));
139         EXPECT_EQ(true, File::exists(runner_.cptFileName_, File::returnFalseOnError))
140                 << runner_.cptFileName_ << " was not found";
141     }
142
143     SCOPED_TRACE("Running the second simulation part");
144     {
145         runner_.changeTprNsteps(102);
146
147         CommandLine secondPart;
148         secondPart.append("mdrun");
149         secondPart.addOption("-cpi", runner_.cptFileName_);
150         ASSERT_EQ(0, runner_.callMdrun(secondPart));
151
152         auto logFileContents = TextReader::readFileToString(runner_.logFileName_);
153         EXPECT_NE(std::string::npos, logFileContents.find("Writing checkpoint, step 102"))
154                 << "completion of restarted simulation was not detected";
155     }
156 }
157
158 TEST_F(MdrunTerminationTest, CheckpointRestartWithNoAppendWorksAndCannotLaterAppend)
159 {
160     runner_.cptFileName_ = fileManager_.getTemporaryFilePath(".cpt");
161
162     runner_.useTopGroAndNdxFromDatabase("spc2");
163     organizeMdpFile(&runner_);
164     EXPECT_EQ(0, runner_.callGrompp());
165
166     SCOPED_TRACE("Running the first simulation part");
167     {
168         CommandLine firstPart;
169         firstPart.append("mdrun");
170         firstPart.addOption("-cpo", runner_.cptFileName_);
171         ASSERT_EQ(0, runner_.callMdrun(firstPart));
172         EXPECT_EQ(true, File::exists(runner_.cptFileName_, File::returnFalseOnError))
173                 << runner_.cptFileName_ << " was not found";
174     }
175
176     SCOPED_TRACE("Running the second simulation part with -noappend");
177     {
178         runner_.changeTprNsteps(4);
179
180         CommandLine secondPart;
181         secondPart.append("mdrun");
182         secondPart.addOption("-cpi", runner_.cptFileName_);
183         secondPart.addOption("-cpo", runner_.cptFileName_);
184         secondPart.append("-noappend");
185         ASSERT_EQ(0, runner_.callMdrun(secondPart));
186
187         auto expectedLogFileName = fileManager_.getTemporaryFilePath(".part0002.log");
188         ASSERT_EQ(true, File::exists(expectedLogFileName, File::returnFalseOnError))
189                 << expectedLogFileName << " was not found";
190         auto expectedEdrFileName = fileManager_.getTemporaryFilePath(".part0002.edr");
191         ASSERT_EQ(true, File::exists(expectedEdrFileName, File::returnFalseOnError))
192                 << expectedEdrFileName << " was not found";
193     }
194
195     SCOPED_TRACE("Running the third simulation part with -append, which will fail");
196     runner_.logFileName_ = fileManager_.getTemporaryFilePath(".part0002.log");
197     runner_.changeTprNsteps(6);
198
199     {
200         CommandLine thirdPart;
201         thirdPart.append("mdrun");
202         thirdPart.addOption("-cpi", runner_.cptFileName_);
203         thirdPart.addOption("-cpo", runner_.cptFileName_);
204         thirdPart.append("-append");
205         EXPECT_THROW_GMX(runner_.callMdrun(thirdPart), InconsistentInputError);
206     }
207     SCOPED_TRACE("Running the third simulation part with -noappend");
208     {
209         CommandLine thirdPart;
210         thirdPart.append("mdrun");
211         thirdPart.addOption("-cpi", runner_.cptFileName_);
212         thirdPart.addOption("-cpo", runner_.cptFileName_);
213         thirdPart.append("-noappend");
214         runner_.edrFileName_ = fileManager_.getTemporaryFilePath(".part0003.edr");
215         ASSERT_EQ(0, runner_.callMdrun(thirdPart));
216
217         auto expectedLogFileName = fileManager_.getTemporaryFilePath(".part0003.log");
218         EXPECT_EQ(true, File::exists(expectedLogFileName, File::returnFalseOnError))
219                 << expectedLogFileName << " was not found";
220         auto expectedEdrFileName = fileManager_.getTemporaryFilePath(".part0003.edr");
221         ASSERT_EQ(true, File::exists(expectedEdrFileName, File::returnFalseOnError))
222                 << expectedEdrFileName << " was not found";
223     }
224     SCOPED_TRACE("Running the fourth simulation part with default appending");
225     runner_.changeTprNsteps(8);
226     {
227         CommandLine fourthPart;
228         fourthPart.append("mdrun");
229         fourthPart.addOption("-cpi", runner_.cptFileName_);
230         fourthPart.addOption("-cpo", runner_.cptFileName_);
231         // TODO this is necessary, but ought not be. Is this the issue in Issue #2804?
232         fourthPart.append("-noappend");
233         runner_.edrFileName_ = fileManager_.getTemporaryFilePath(".part0004.edr");
234         runner_.logFileName_ = fileManager_.getTemporaryFilePath(".part0004.log");
235         ASSERT_EQ(0, runner_.callMdrun(fourthPart));
236
237         auto expectedLogFileName = fileManager_.getTemporaryFilePath(".part0004.log");
238         ASSERT_EQ(true, File::exists(expectedLogFileName, File::returnFalseOnError))
239                 << expectedLogFileName << " was not found";
240         auto expectedEdrFileName = fileManager_.getTemporaryFilePath(".part0004.edr");
241         ASSERT_EQ(true, File::exists(expectedEdrFileName, File::returnFalseOnError))
242                 << expectedEdrFileName << " was not found";
243     }
244     SCOPED_TRACE("Running the fifth simulation part with no extra steps");
245     {
246         CommandLine fifthPart;
247         fifthPart.append("mdrun");
248         fifthPart.addOption("-cpi", runner_.cptFileName_);
249         fifthPart.addOption("-cpo", runner_.cptFileName_);
250         // TODO this is necessary, but ought not be. Is this the issue in Issue #2804?
251         fifthPart.append("-noappend");
252         runner_.edrFileName_ = fileManager_.getTemporaryFilePath(".part0005.edr");
253         runner_.logFileName_ = fileManager_.getTemporaryFilePath(".part0005.log");
254         ASSERT_EQ(0, runner_.callMdrun(fifthPart));
255
256         auto expectedLogFileName = fileManager_.getTemporaryFilePath(".part0005.log");
257         ASSERT_EQ(true, File::exists(expectedLogFileName, File::returnFalseOnError))
258                 << expectedLogFileName << " was not found";
259         auto expectedEdrFileName = fileManager_.getTemporaryFilePath(".part0005.edr");
260         ASSERT_EQ(true, File::exists(expectedEdrFileName, File::returnFalseOnError))
261                 << expectedEdrFileName << " was not found";
262     }
263 }
264
265 TEST_F(MdrunTerminationTest, CheckpointRestartWorksEvenWithMissingCheckpointFile)
266 {
267     runner_.cptFileName_ = fileManager_.getTemporaryFilePath(".cpt");
268
269     runner_.useTopGroAndNdxFromDatabase("spc2");
270     organizeMdpFile(&runner_);
271     EXPECT_EQ(0, runner_.callGrompp());
272
273     SCOPED_TRACE("Running the first simulation part");
274     {
275         CommandLine firstPart;
276         firstPart.append("mdrun");
277         firstPart.addOption("-cpo", runner_.cptFileName_);
278         ASSERT_EQ(0, runner_.callMdrun(firstPart));
279         EXPECT_EQ(true, File::exists(runner_.cptFileName_, File::returnFalseOnError))
280                 << runner_.cptFileName_ << " was not found";
281     }
282
283     SCOPED_TRACE("Running the second simulation part after deleting the checkpoint file");
284     {
285         runner_.changeTprNsteps(4);
286
287         CommandLine secondPart;
288         secondPart.append("mdrun");
289         secondPart.addOption("-cpi", runner_.cptFileName_);
290         secondPart.addOption("-cpo", runner_.cptFileName_);
291
292         // Remove the checkpoint, so technically this can no longer be
293         // a restart. But it starts again from the beginning anyway.
294         //
295         // TODO what do we want the behaviour to be?
296         std::remove(runner_.cptFileName_.c_str());
297
298         ASSERT_EQ(0, runner_.callMdrun(secondPart));
299         auto logFileContents = TextReader::readFileToString(runner_.logFileName_);
300         EXPECT_EQ(
301                 std::string::npos,
302                 logFileContents.find("Restarting from checkpoint, appending to previous log file"))
303                 << "appending was not detected";
304     }
305 }
306
307 TEST_F(MdrunTerminationTest, CheckpointRestartWorksEvenWithAppendAndMissingCheckpointFile)
308 {
309     runner_.cptFileName_ = fileManager_.getTemporaryFilePath(".cpt");
310
311     runner_.useTopGroAndNdxFromDatabase("spc2");
312     organizeMdpFile(&runner_);
313     EXPECT_EQ(0, runner_.callGrompp());
314
315     SCOPED_TRACE("Running the first simulation part");
316     {
317         CommandLine firstPart;
318         firstPart.append("mdrun");
319         firstPart.addOption("-cpo", runner_.cptFileName_);
320         ASSERT_EQ(0, runner_.callMdrun(firstPart));
321         EXPECT_EQ(true, File::exists(runner_.cptFileName_, File::returnFalseOnError))
322                 << runner_.cptFileName_ << " was not found";
323     }
324
325     SCOPED_TRACE(
326             "Running the second simulation part with -append after deleting the checkpoint file");
327     {
328         runner_.changeTprNsteps(4);
329
330         CommandLine secondPart;
331         secondPart.append("mdrun");
332         secondPart.addOption("-cpi", runner_.cptFileName_);
333         secondPart.addOption("-cpo", runner_.cptFileName_);
334         secondPart.append("-append");
335
336         // Remove the checkpoint, so this can no longer be a
337         // restart.
338         std::remove(runner_.cptFileName_.c_str());
339
340         EXPECT_THROW_GMX(runner_.callMdrun(secondPart), InconsistentInputError);
341     }
342 }
343
344 TEST_F(MdrunTerminationTest, RunWithNoAppendCreatesPartFiles)
345 {
346     runner_.cptFileName_ = fileManager_.getTemporaryFilePath(".cpt");
347
348     runner_.useTopGroAndNdxFromDatabase("spc2");
349     organizeMdpFile(&runner_);
350     EXPECT_EQ(0, runner_.callGrompp());
351
352     SCOPED_TRACE("Running the first simulation part with -noappend");
353     {
354         CommandLine firstPart;
355         firstPart.append("mdrun");
356         firstPart.addOption("-cpo", runner_.cptFileName_);
357         firstPart.append("-noappend");
358         ASSERT_EQ(0, runner_.callMdrun(firstPart));
359         auto expectedLogFileName = fileManager_.getTemporaryFilePath(".part0001.log");
360         ASSERT_EQ(true, File::exists(expectedLogFileName, File::returnFalseOnError))
361                 << expectedLogFileName << " was not found";
362         auto expectedEdrFileName = fileManager_.getTemporaryFilePath(".part0001.edr");
363         ASSERT_EQ(true, File::exists(expectedEdrFileName, File::returnFalseOnError))
364                 << expectedEdrFileName << " was not found";
365         EXPECT_EQ(true, File::exists(runner_.cptFileName_, File::returnFalseOnError))
366                 << runner_.cptFileName_ << " was not found";
367     }
368
369     SCOPED_TRACE("Running the second simulation part with -noappend");
370     {
371         runner_.changeTprNsteps(4);
372
373         CommandLine secondPart;
374         secondPart.append("mdrun");
375         secondPart.addOption("-cpi", runner_.cptFileName_);
376         secondPart.addOption("-cpo", runner_.cptFileName_);
377         secondPart.append("-noappend");
378         ASSERT_EQ(0, runner_.callMdrun(secondPart));
379
380         auto expectedLogFileName = fileManager_.getTemporaryFilePath(".part0002.log");
381         ASSERT_EQ(true, File::exists(expectedLogFileName, File::returnFalseOnError))
382                 << expectedLogFileName << " was not found";
383         auto expectedEdrFileName = fileManager_.getTemporaryFilePath(".part0002.edr");
384         ASSERT_EQ(true, File::exists(expectedEdrFileName, File::returnFalseOnError))
385                 << expectedEdrFileName << " was not found";
386     }
387 }
388
389 } // namespace test
390 } // namespace gmx