2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, 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.
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.
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.
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.
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.
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.
37 * Tests parse_common_args().
39 * Currently, negative tests are not possible, because those trigger
40 * gmx_fatal() and terminate the whole test binary.
42 * \author Teemu Murtola <teemu.murtola@gmail.com>
43 * \ingroup module_commandline
47 #include "gromacs/commandline/pargs.h"
51 #include <gtest/gtest.h>
53 #include "gromacs/utility/arrayref.h"
54 #include "gromacs/utility/path.h"
55 #include "gromacs/utility/stringutil.h"
56 #include "gromacs/utility/textwriter.h"
58 #include "testutils/cmdlinetest.h"
59 #include "testutils/testasserts.h"
60 #include "testutils/testfilemanager.h"
65 using gmx::test::CommandLine
;
67 class ParseCommonArgsTest
: public ::testing::Test
78 : oenv_(nullptr), fileCount_(0)
81 ~ParseCommonArgsTest() override
83 output_env_done(oenv_
);
86 int nfile() const { return fileCount_
; }
88 void parseFromArgs(unsigned long flags
,
89 gmx::ArrayRef
<t_filenm
> fnm
,
90 gmx::ArrayRef
<t_pargs
> pa
)
92 fileCount_
= fnm
.size();
93 bool bOk
= parse_common_args(&args_
.argc(), args_
.argv(), flags
,
94 fnm
.size(), fnm
.data(),
96 0, nullptr, 0, nullptr, &oenv_
);
99 void parseFromArray(gmx::ArrayRef
<const char *const> cmdline
,
101 gmx::ArrayRef
<t_filenm
> fnm
,
102 gmx::ArrayRef
<t_pargs
> pa
)
104 args_
.initFromArray(cmdline
);
105 parseFromArgs(flags
, fnm
, pa
);
107 std::string
addFileArg(const char *name
, const char *extension
,
108 FileArgumentType type
)
110 std::string
filename(tempFiles_
.getTemporaryFilePath(extension
));
111 gmx::TextWriter::writeFileFromString(filename
, "Dummy file");
118 args_
.append(filename
);
121 args_
.append(gmx::Path::stripExtension(filename
));
130 // This must be a member that persists until the end of the test,
131 // because string arguments are not duplicated in the output.
135 gmx_output_env_t
*oenv_
;
137 gmx::test::TestFileManager tempFiles_
;
140 /********************************************************************
141 * Tests for different types of options
144 TEST_F(ParseCommonArgsTest
, ParsesIntegerArgs
)
146 int value1
= 0, value2
= 0, value3
= 3;
148 { "-i1", FALSE
, etINT
, {&value1
}, "Description" },
149 { "-i2", FALSE
, etINT
, {&value2
}, "Description" },
150 { "-i3", FALSE
, etINT
, {&value3
}, "Description" }
152 const char *const cmdline
[] = {
153 "test", "-i1", "2", "-i2", "-3"
155 parseFromArray(cmdline
, 0, {}, pa
);
156 EXPECT_EQ( 2, value1
);
157 EXPECT_EQ(-3, value2
);
158 EXPECT_EQ( 3, value3
);
161 TEST_F(ParseCommonArgsTest
, ParsesInt64Args
)
163 int64_t value1
= 0, value2
= 0, value3
= 3;
165 { "-i1", FALSE
, etINT64
, {&value1
}, "Description" },
166 { "-i2", FALSE
, etINT64
, {&value2
}, "Description" },
167 { "-i3", FALSE
, etINT64
, {&value3
}, "Description" }
169 const char *const cmdline
[] = {
170 "test", "-i1", "2", "-i2", "-3"
172 parseFromArray(cmdline
, 0, {}, pa
);
173 EXPECT_EQ( 2, value1
);
174 EXPECT_EQ(-3, value2
);
175 EXPECT_EQ( 3, value3
);
178 TEST_F(ParseCommonArgsTest
, ParsesRealArgs
)
180 real value1
= 0.0, value2
= 0.0, value3
= 2.5;
182 { "-r1", FALSE
, etREAL
, {&value1
}, "Description" },
183 { "-r2", FALSE
, etREAL
, {&value2
}, "Description" },
184 { "-r3", FALSE
, etREAL
, {&value3
}, "Description" }
186 const char *const cmdline
[] = {
187 "test", "-r1", "2", "-r2", "-.5"
189 parseFromArray(cmdline
, 0, {}, pa
);
190 EXPECT_EQ( 2.0, value1
);
191 EXPECT_EQ(-0.5, value2
);
192 EXPECT_EQ( 2.5, value3
);
195 TEST_F(ParseCommonArgsTest
, ParsesStringArgs
)
197 const char *value1
= "def", *value2
= "", *value3
= "default";
199 { "-s1", FALSE
, etSTR
, {&value1
}, "Description" },
200 { "-s2", FALSE
, etSTR
, {&value2
}, "Description" },
201 { "-s3", FALSE
, etSTR
, {&value3
}, "Description" }
203 const char *const cmdline
[] = {
204 "test", "-s1", "", "-s2", "test"
206 parseFromArray(cmdline
, 0, {}, pa
);
207 EXPECT_STREQ("", value1
);
208 EXPECT_STREQ("test", value2
);
209 EXPECT_STREQ("default", value3
);
212 TEST_F(ParseCommonArgsTest
, ParsesBooleanArgs
)
214 gmx_bool value1
= TRUE
, value2
= FALSE
, value3
= TRUE
;
216 { "-b1", FALSE
, etBOOL
, {&value1
}, "Description" },
217 { "-b2", FALSE
, etBOOL
, {&value2
}, "Description" },
218 { "-b3", FALSE
, etBOOL
, {&value3
}, "Description" }
220 const char *const cmdline
[] = {
221 "test", "-nob1", "-b2"
223 parseFromArray(cmdline
, 0, {}, pa
);
224 EXPECT_FALSE(value1
);
229 TEST_F(ParseCommonArgsTest
, ParsesVectorArgs
)
231 rvec value1
= {0, 0, 0}, value2
= {0, 0, 0}, value3
= {1, 2, 3};
233 { "-v1", FALSE
, etRVEC
, {&value1
}, "Description" },
234 { "-v2", FALSE
, etRVEC
, {&value2
}, "Description" },
235 { "-v3", FALSE
, etRVEC
, {&value3
}, "Description" }
237 const char *const cmdline
[] = {
238 "test", "-v1", "2", "1", "3", "-v2", "1"
240 parseFromArray(cmdline
, 0, {}, pa
);
241 EXPECT_EQ(2.0, value1
[XX
]);
242 EXPECT_EQ(1.0, value1
[YY
]);
243 EXPECT_EQ(3.0, value1
[ZZ
]);
244 EXPECT_EQ(1.0, value2
[XX
]);
245 EXPECT_EQ(1.0, value2
[YY
]);
246 EXPECT_EQ(1.0, value2
[ZZ
]);
247 EXPECT_EQ(1.0, value3
[XX
]);
248 EXPECT_EQ(2.0, value3
[YY
]);
249 EXPECT_EQ(3.0, value3
[ZZ
]);
252 TEST_F(ParseCommonArgsTest
, ParsesTimeArgs
)
254 real value1
= 1.0, value2
= 2.0, value3
= 2.5;
256 { "-t1", FALSE
, etTIME
, {&value1
}, "Description" },
257 { "-t2", FALSE
, etTIME
, {&value2
}, "Description" },
258 { "-t3", FALSE
, etTIME
, {&value3
}, "Description" }
260 const char *const cmdline
[] = {
261 "test", "-t1", "2", "-t2", "-.5"
263 parseFromArray(cmdline
, 0, {}, pa
);
264 EXPECT_EQ( 2.0, value1
);
265 EXPECT_EQ(-0.5, value2
);
266 EXPECT_EQ( 2.5, value3
);
269 TEST_F(ParseCommonArgsTest
, ParsesTimeArgsWithTimeUnit
)
271 real value1
= 1.0, value2
= 2.0, value3
= 2.5;
273 { "-t1", FALSE
, etTIME
, {&value1
}, "Description" },
274 { "-t2", FALSE
, etTIME
, {&value2
}, "Description" },
275 { "-t3", FALSE
, etTIME
, {&value3
}, "Description" }
277 const char *const cmdline
[] = {
278 "test", "-t1", "2", "-t2", "-.5", "-tu", "ns"
280 parseFromArray(cmdline
, PCA_TIME_UNIT
, {}, pa
);
281 EXPECT_EQ( 2000.0, value1
);
282 EXPECT_EQ(-500.0, value2
);
283 EXPECT_EQ( 2.5, value3
);
286 TEST_F(ParseCommonArgsTest
, ParsesEnumArgs
)
288 const char *value1
[] = {nullptr, "none", "on", "off", nullptr };
289 const char *value2
[] = {nullptr, "def", "value", "value_other", nullptr };
290 const char *value3
[] = {nullptr, "default", "value", nullptr };
292 { "-s1", FALSE
, etENUM
, {value1
}, "Description" },
293 { "-s2", FALSE
, etENUM
, {value2
}, "Description" },
294 { "-s3", FALSE
, etENUM
, {value3
}, "Description" }
296 const char *const cmdline
[] = {
297 "test", "-s1", "off", "-s2", "val"
299 parseFromArray(cmdline
, 0, {}, pa
);
300 EXPECT_STREQ("off", value1
[0]);
301 EXPECT_STREQ("value", value2
[0]);
302 EXPECT_STREQ("default", value3
[0]);
303 EXPECT_EQ(value1
[nenum(value1
)], value1
[0]);
304 EXPECT_EQ(value2
[nenum(value2
)], value2
[0]);
305 EXPECT_EQ(value3
[nenum(value3
)], value3
[0]);
308 /********************************************************************
309 * Tests for file name options (output files, not dependent on file system)
312 TEST_F(ParseCommonArgsTest
, ParsesFileArgs
)
315 { efXVG
, "-o1", "out1", ffOPTWR
},
316 { efXVG
, "-o2", "out2", ffOPTWR
},
317 { efXVG
, "-om", "outm", ffWRMULT
},
318 { efXVG
, "-om2", "outm2", ffWRMULT
}
320 const char *const cmdline
[] = {
321 "test", "-o1", "-o2", "test", "-om", "test1", "test2.xvg", "-om2"
323 parseFromArray(cmdline
, 0, fnm
, {});
324 EXPECT_STREQ("out1.xvg", opt2fn_null("-o1", nfile(), fnm
));
325 EXPECT_STREQ("test.xvg", opt2fn_null("-o2", nfile(), fnm
));
326 gmx::ArrayRef
<const std::string
> files
= opt2fns("-om", nfile(), fnm
);
327 EXPECT_EQ(2, files
.size());
328 if (files
.size() != 2)
330 EXPECT_STREQ("test1.xvg", files
[0].c_str());
331 EXPECT_STREQ("test2.xvg", files
[1].c_str());
333 EXPECT_STREQ("outm2.xvg", opt2fn("-om2", nfile(), fnm
));
336 TEST_F(ParseCommonArgsTest
, ParsesFileArgsWithDefaults
)
339 { efTPS
, nullptr, nullptr, ffWRITE
},
340 { efTRX
, "-f2", nullptr, ffOPTWR
},
341 { efTRX
, "-f3", "trj3", ffWRITE
},
342 { efXVG
, "-o", "out", ffWRITE
},
343 { efXVG
, "-om", "outm", ffWRMULT
}
345 const char *const cmdline
[] = {
348 parseFromArray(cmdline
, 0, fnm
, {});
349 EXPECT_STREQ("topol.tpr", ftp2fn(efTPS
, nfile(), fnm
));
350 EXPECT_STREQ("traj.xtc", opt2fn("-f2", nfile(), fnm
));
351 EXPECT_EQ(nullptr, opt2fn_null("-f2", nfile(), fnm
));
352 EXPECT_STREQ("trj3.xtc", opt2fn("-f3", nfile(), fnm
));
353 EXPECT_STREQ("out.xvg", opt2fn("-o", nfile(), fnm
));
354 EXPECT_STREQ("outm.xvg", opt2fn("-om", nfile(), fnm
));
357 TEST_F(ParseCommonArgsTest
, ParsesFileArgsWithDefaultFileName
)
360 { efTPS
, "-s", nullptr, ffWRITE
},
361 { efTRX
, "-f2", nullptr, ffWRITE
},
362 { efTRX
, "-f3", "trj3", ffWRITE
},
363 { efXVG
, "-o", "out", ffWRITE
},
364 { efXVG
, "-om", "outm", ffWRMULT
}
366 const char *const cmdline
[] = {
367 "test", "-deffnm", "def", "-f2", "other", "-o"
369 parseFromArray(cmdline
, PCA_CAN_SET_DEFFNM
, fnm
, {});
370 EXPECT_STREQ("def.tpr", ftp2fn(efTPS
, nfile(), fnm
));
371 EXPECT_STREQ("other.xtc", opt2fn("-f2", nfile(), fnm
));
372 EXPECT_STREQ("def.xtc", opt2fn("-f3", nfile(), fnm
));
373 EXPECT_STREQ("def.xvg", opt2fn("-o", nfile(), fnm
));
374 EXPECT_STREQ("def.xvg", opt2fn("-om", nfile(), fnm
));
377 TEST_F(ParseCommonArgsTest
, ParseFileArgsWithCustomDefaultExtension
)
380 { efTRX
, "-o1", "conf1.gro", ffWRITE
},
381 { efTRX
, "-o2", "conf2.pdb", ffWRITE
},
382 { efTRX
, "-o3", "conf3.gro", ffWRITE
}
384 const char *const cmdline
[] = {
385 "test", "-o2", "-o3", "test"
387 parseFromArray(cmdline
, PCA_CAN_SET_DEFFNM
, fnm
, {});
388 EXPECT_STREQ("conf1.gro", opt2fn("-o1", nfile(), fnm
));
389 EXPECT_STREQ("conf2.pdb", opt2fn("-o2", nfile(), fnm
));
390 EXPECT_STREQ("test.gro", opt2fn("-o3", nfile(), fnm
));
393 /********************************************************************
394 * Tests for file name options (input files, dependent on file system contents)
397 TEST_F(ParseCommonArgsTest
, HandlesNonExistentInputFiles
)
400 { efTPS
, "-s", nullptr, ffREAD
},
401 { efTRX
, "-f", "trj", ffREAD
},
402 { efTRX
, "-f2", "trj2", ffREAD
},
403 { efTRX
, "-f3", "trj3", ffREAD
},
404 { efTRX
, "-f4", "trj4", ffREAD
},
405 { efGRO
, "-g", "cnf", ffREAD
},
406 { efGRO
, "-g2", "cnf2", ffREAD
}
408 const char *const cmdline
[] = {
409 "test", "-f2", "-f3", "other", "-f4", "trj.gro", "-g2", "foo"
411 parseFromArray(cmdline
, PCA_DISABLE_INPUT_FILE_CHECKING
, fnm
, {});
412 EXPECT_STREQ("topol.tpr", ftp2fn(efTPS
, nfile(), fnm
));
413 EXPECT_STREQ("trj.xtc", opt2fn("-f", nfile(), fnm
));
414 EXPECT_STREQ("trj2.xtc", opt2fn("-f2", nfile(), fnm
));
415 EXPECT_STREQ("other.xtc", opt2fn("-f3", nfile(), fnm
));
416 EXPECT_STREQ("trj.gro", opt2fn("-f4", nfile(), fnm
));
417 EXPECT_STREQ("cnf.gro", opt2fn("-g", nfile(), fnm
));
418 EXPECT_STREQ("foo.gro", opt2fn("-g2", nfile(), fnm
));
421 TEST_F(ParseCommonArgsTest
, HandlesNonExistentOptionalInputFiles
)
424 { efTPS
, "-s", nullptr, ffOPTRD
},
425 { efTRX
, "-f", "trj", ffOPTRD
}
427 const char *const cmdline
[] = {
430 parseFromArray(cmdline
, 0, fnm
, {});
431 EXPECT_STREQ("topol.tpr", ftp2fn(efTPS
, nfile(), fnm
));
432 EXPECT_STREQ("trj.xtc", opt2fn("-f", nfile(), fnm
));
435 TEST_F(ParseCommonArgsTest
, AcceptsNonExistentInputFilesIfSpecified
)
438 { efCPT
, "-c", "file1", ffOPTRD
| ffALLOW_MISSING
},
439 { efCPT
, "-c2", "file2", ffOPTRD
| ffALLOW_MISSING
},
440 { efCPT
, "-c3", "file3", ffOPTRD
| ffALLOW_MISSING
},
441 { efCPT
, "-c4", "file4", ffOPTRD
| ffALLOW_MISSING
},
442 { efTRX
, "-f", "trj", ffOPTRD
| ffALLOW_MISSING
}
444 const char *const cmdline
[] = {
445 "test", "-c2", "-c3", "nonexistent", "-c4", "nonexistent.cpt", "-f", "nonexistent"
447 parseFromArray(cmdline
, 0, fnm
, {});
448 EXPECT_STREQ("file1.cpt", opt2fn("-c", nfile(), fnm
));
449 EXPECT_STREQ("file2.cpt", opt2fn("-c2", nfile(), fnm
));
450 EXPECT_STREQ("nonexistent.cpt", opt2fn("-c3", nfile(), fnm
));
451 EXPECT_STREQ("nonexistent.cpt", opt2fn("-c4", nfile(), fnm
));
452 EXPECT_STREQ("nonexistent.xtc", opt2fn("-f", nfile(), fnm
));
455 TEST_F(ParseCommonArgsTest
, HandlesCompressedFiles
)
458 { efTRX
, "-f", nullptr, ffREAD
},
459 { efGRO
, "-g", nullptr, ffREAD
}
461 args_
.append("test");
462 std::string expectedF
= addFileArg("-f", ".pdb.gz", efFull
);
463 std::string expectedG
= addFileArg("-g", ".gro.Z", efFull
);
464 expectedF
= gmx::Path::stripExtension(expectedF
);
465 expectedG
= gmx::Path::stripExtension(expectedG
);
466 parseFromArgs(0, fnm
, {});
467 EXPECT_EQ(expectedF
, opt2fn("-f", nfile(), fnm
));
468 EXPECT_EQ(expectedG
, opt2fn("-g", nfile(), fnm
));
471 TEST_F(ParseCommonArgsTest
, AcceptsUnknownTrajectoryExtension
)
474 { efTRX
, "-f", nullptr, ffREAD
}
476 args_
.append("test");
477 std::string expected
= addFileArg("-f", ".foo", efFull
);
478 parseFromArgs(0, fnm
, {});
479 EXPECT_EQ(expected
, opt2fn("-f", nfile(), fnm
));
482 TEST_F(ParseCommonArgsTest
, CompletesExtensionFromExistingFile
)
484 args_
.append("test");
485 std::string expected1
= addFileArg("-f1", "1.xtc", efNoExtension
);
486 std::string expected2
= addFileArg("-f2", "2.gro", efNoExtension
);
487 std::string expected3
= addFileArg("-f3", "3.tng", efNoExtension
);
488 std::string expected4
= addFileArg("-f4", ".gro", efEmptyValue
);
489 std::string def4
= gmx::Path::stripExtension(expected4
);
491 { efTRX
, "-f1", nullptr, ffREAD
},
492 { efTRX
, "-f2", nullptr, ffREAD
},
493 { efTRX
, "-f3", nullptr, ffREAD
},
494 { efTRX
, "-f4", def4
.c_str(), ffREAD
}
496 parseFromArgs(0, fnm
, {});
497 EXPECT_EQ(expected1
, opt2fn("-f1", nfile(), fnm
));
498 EXPECT_EQ(expected2
, opt2fn("-f2", nfile(), fnm
));
499 EXPECT_EQ(expected3
, opt2fn("-f3", nfile(), fnm
));
500 EXPECT_EQ(expected4
, opt2fn("-f4", nfile(), fnm
));
503 TEST_F(ParseCommonArgsTest
, CompletesExtensionFromExistingFileWithDefaultFileName
)
506 { efTRX
, "-f1", nullptr, ffREAD
},
507 { efSTO
, "-f2", "foo", ffREAD
},
508 { efTRX
, "-f3", nullptr, ffREAD
},
509 { efSTX
, "-f4", nullptr, ffREAD
}
511 args_
.append("test");
512 std::string expected1
= addFileArg("-f1", "1.trr", efNoExtension
);
513 std::string expected2
= addFileArg("-f2", ".pdb", efEmptyValue
);
514 std::string expected3
= addFileArg("-f3", ".trr", efEmptyValue
);
515 std::string expected4
= addFileArg(nullptr, ".pdb", efEmptyValue
);
516 std::string deffnm
= gmx::Path::stripExtension(expected3
);
517 args_
.append("-deffnm");
518 args_
.append(deffnm
);
519 parseFromArgs(PCA_CAN_SET_DEFFNM
, fnm
, {});
520 EXPECT_EQ(expected1
, opt2fn("-f1", nfile(), fnm
));
521 EXPECT_EQ(expected2
, opt2fn("-f2", nfile(), fnm
));
522 EXPECT_EQ(expected3
, opt2fn("-f3", nfile(), fnm
));
523 EXPECT_EQ(expected4
, opt2fn("-f4", nfile(), fnm
));
526 // This is needed e.g. for tune_pme, which passes unknown arguments on
527 // to child mdrun processes that it spawns.
528 TEST_F(ParseCommonArgsTest
, CanKeepUnknownArgs
)
531 gmx_bool bvalue
= FALSE
;
533 { "-i", FALSE
, etINT
, {&ivalue
}, "Description" },
534 { "-b", FALSE
, etBOOL
, {&bvalue
}, "Description" },
537 { efXVG
, "-o1", "out1", ffOPTWR
},
538 { efXVG
, "-o2", "out2", ffOPTWR
}
540 const char *const cmdline
[] = {
541 "test", "foo", "-unk", "-o1", "-unk2", "-o2", "test",
542 "-i", "2", "-unk3", "-b", "-unk4"
544 parseFromArray(cmdline
, PCA_NOEXIT_ON_ARGS
, fnm
, pa
);
545 EXPECT_EQ(2, ivalue
);
547 EXPECT_STREQ("out1.xvg", opt2fn_null("-o1", nfile(), fnm
));
548 EXPECT_STREQ("test.xvg", opt2fn_null("-o2", nfile(), fnm
));
549 EXPECT_EQ(6, args_
.argc());
550 EXPECT_STREQ(cmdline
[0], args_
.arg(0));
551 EXPECT_STREQ(cmdline
[1], args_
.arg(1));
552 EXPECT_STREQ(cmdline
[2], args_
.arg(2));
553 EXPECT_STREQ(cmdline
[4], args_
.arg(3));
554 EXPECT_STREQ(cmdline
[9], args_
.arg(4));
555 EXPECT_STREQ(cmdline
[11], args_
.arg(5));