Apply clang-format to source tree
[alexxy/gromacs.git] / src / gromacs / commandline / tests / pargs.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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.
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 /*! \internal \file
36  * \brief
37  * Tests parse_common_args().
38  *
39  * Currently, negative tests are not possible, because those trigger
40  * gmx_fatal() and terminate the whole test binary.
41  *
42  * \author Teemu Murtola <teemu.murtola@gmail.com>
43  * \ingroup module_commandline
44  */
45 #include "gmxpre.h"
46
47 #include "gromacs/commandline/pargs.h"
48
49 #include <string>
50
51 #include <gtest/gtest.h>
52
53 #include "gromacs/utility/arrayref.h"
54 #include "gromacs/utility/path.h"
55 #include "gromacs/utility/stringutil.h"
56 #include "gromacs/utility/textwriter.h"
57
58 #include "testutils/cmdlinetest.h"
59 #include "testutils/testasserts.h"
60 #include "testutils/testfilemanager.h"
61
62 namespace
63 {
64
65 using gmx::test::CommandLine;
66
67 class ParseCommonArgsTest : public ::testing::Test
68 {
69 public:
70     enum FileArgumentType
71     {
72         efFull,
73         efNoExtension,
74         efEmptyValue
75     };
76
77     ParseCommonArgsTest() : oenv_(nullptr), fileCount_(0) {}
78     ~ParseCommonArgsTest() override { output_env_done(oenv_); }
79
80     int nfile() const { return fileCount_; }
81
82     void parseFromArgs(unsigned long flags, gmx::ArrayRef<t_filenm> fnm, gmx::ArrayRef<t_pargs> pa)
83     {
84         fileCount_ = fnm.size();
85         bool bOk   = parse_common_args(&args_.argc(), args_.argv(), flags, fnm.size(), fnm.data(),
86                                      pa.size(), pa.data(), 0, nullptr, 0, nullptr, &oenv_);
87         EXPECT_TRUE(bOk);
88     }
89     void parseFromArray(gmx::ArrayRef<const char* const> cmdline,
90                         unsigned long                    flags,
91                         gmx::ArrayRef<t_filenm>          fnm,
92                         gmx::ArrayRef<t_pargs>           pa)
93     {
94         args_.initFromArray(cmdline);
95         parseFromArgs(flags, fnm, pa);
96     }
97     std::string addFileArg(const char* name, const char* extension, FileArgumentType type)
98     {
99         std::string filename(tempFiles_.getTemporaryFilePath(extension));
100         gmx::TextWriter::writeFileFromString(filename, "Dummy file");
101         if (name != nullptr)
102         {
103             args_.append(name);
104             switch (type)
105             {
106                 case efFull: args_.append(filename); break;
107                 case efNoExtension: args_.append(gmx::Path::stripExtension(filename)); break;
108                 case efEmptyValue: break;
109             }
110         }
111         return filename;
112     }
113
114     // This must be a member that persists until the end of the test,
115     // because string arguments are not duplicated in the output.
116     CommandLine args_;
117
118 private:
119     gmx_output_env_t*          oenv_;
120     size_t                     fileCount_;
121     gmx::test::TestFileManager tempFiles_;
122 };
123
124 /********************************************************************
125  * Tests for different types of options
126  */
127
128 TEST_F(ParseCommonArgsTest, ParsesIntegerArgs)
129 {
130     int               value1 = 0, value2 = 0, value3 = 3;
131     t_pargs           pa[]      = { { "-i1", FALSE, etINT, { &value1 }, "Description" },
132                      { "-i2", FALSE, etINT, { &value2 }, "Description" },
133                      { "-i3", FALSE, etINT, { &value3 }, "Description" } };
134     const char* const cmdline[] = { "test", "-i1", "2", "-i2", "-3" };
135     parseFromArray(cmdline, 0, {}, pa);
136     EXPECT_EQ(2, value1);
137     EXPECT_EQ(-3, value2);
138     EXPECT_EQ(3, value3);
139 }
140
141 TEST_F(ParseCommonArgsTest, ParsesInt64Args)
142 {
143     int64_t           value1 = 0, value2 = 0, value3 = 3;
144     t_pargs           pa[]      = { { "-i1", FALSE, etINT64, { &value1 }, "Description" },
145                      { "-i2", FALSE, etINT64, { &value2 }, "Description" },
146                      { "-i3", FALSE, etINT64, { &value3 }, "Description" } };
147     const char* const cmdline[] = { "test", "-i1", "2", "-i2", "-3" };
148     parseFromArray(cmdline, 0, {}, pa);
149     EXPECT_EQ(2, value1);
150     EXPECT_EQ(-3, value2);
151     EXPECT_EQ(3, value3);
152 }
153
154 TEST_F(ParseCommonArgsTest, ParsesRealArgs)
155 {
156     real              value1 = 0.0, value2 = 0.0, value3 = 2.5;
157     t_pargs           pa[]      = { { "-r1", FALSE, etREAL, { &value1 }, "Description" },
158                      { "-r2", FALSE, etREAL, { &value2 }, "Description" },
159                      { "-r3", FALSE, etREAL, { &value3 }, "Description" } };
160     const char* const cmdline[] = { "test", "-r1", "2", "-r2", "-.5" };
161     parseFromArray(cmdline, 0, {}, pa);
162     EXPECT_EQ(2.0, value1);
163     EXPECT_EQ(-0.5, value2);
164     EXPECT_EQ(2.5, value3);
165 }
166
167 TEST_F(ParseCommonArgsTest, ParsesStringArgs)
168 {
169     const char *      value1 = "def", *value2 = "", *value3 = "default";
170     t_pargs           pa[]      = { { "-s1", FALSE, etSTR, { &value1 }, "Description" },
171                      { "-s2", FALSE, etSTR, { &value2 }, "Description" },
172                      { "-s3", FALSE, etSTR, { &value3 }, "Description" } };
173     const char* const cmdline[] = { "test", "-s1", "", "-s2", "test" };
174     parseFromArray(cmdline, 0, {}, pa);
175     EXPECT_STREQ("", value1);
176     EXPECT_STREQ("test", value2);
177     EXPECT_STREQ("default", value3);
178 }
179
180 TEST_F(ParseCommonArgsTest, ParsesBooleanArgs)
181 {
182     gmx_bool          value1 = TRUE, value2 = FALSE, value3 = TRUE;
183     t_pargs           pa[]      = { { "-b1", FALSE, etBOOL, { &value1 }, "Description" },
184                      { "-b2", FALSE, etBOOL, { &value2 }, "Description" },
185                      { "-b3", FALSE, etBOOL, { &value3 }, "Description" } };
186     const char* const cmdline[] = { "test", "-nob1", "-b2" };
187     parseFromArray(cmdline, 0, {}, pa);
188     EXPECT_FALSE(value1);
189     EXPECT_TRUE(value2);
190     EXPECT_TRUE(value3);
191 }
192
193 TEST_F(ParseCommonArgsTest, ParsesVectorArgs)
194 {
195     rvec              value1 = { 0, 0, 0 }, value2 = { 0, 0, 0 }, value3 = { 1, 2, 3 };
196     t_pargs           pa[]      = { { "-v1", FALSE, etRVEC, { &value1 }, "Description" },
197                      { "-v2", FALSE, etRVEC, { &value2 }, "Description" },
198                      { "-v3", FALSE, etRVEC, { &value3 }, "Description" } };
199     const char* const cmdline[] = { "test", "-v1", "2", "1", "3", "-v2", "1" };
200     parseFromArray(cmdline, 0, {}, pa);
201     EXPECT_EQ(2.0, value1[XX]);
202     EXPECT_EQ(1.0, value1[YY]);
203     EXPECT_EQ(3.0, value1[ZZ]);
204     EXPECT_EQ(1.0, value2[XX]);
205     EXPECT_EQ(1.0, value2[YY]);
206     EXPECT_EQ(1.0, value2[ZZ]);
207     EXPECT_EQ(1.0, value3[XX]);
208     EXPECT_EQ(2.0, value3[YY]);
209     EXPECT_EQ(3.0, value3[ZZ]);
210 }
211
212 TEST_F(ParseCommonArgsTest, ParsesTimeArgs)
213 {
214     real              value1 = 1.0, value2 = 2.0, value3 = 2.5;
215     t_pargs           pa[]      = { { "-t1", FALSE, etTIME, { &value1 }, "Description" },
216                      { "-t2", FALSE, etTIME, { &value2 }, "Description" },
217                      { "-t3", FALSE, etTIME, { &value3 }, "Description" } };
218     const char* const cmdline[] = { "test", "-t1", "2", "-t2", "-.5" };
219     parseFromArray(cmdline, 0, {}, pa);
220     EXPECT_EQ(2.0, value1);
221     EXPECT_EQ(-0.5, value2);
222     EXPECT_EQ(2.5, value3);
223 }
224
225 TEST_F(ParseCommonArgsTest, ParsesTimeArgsWithTimeUnit)
226 {
227     real              value1 = 1.0, value2 = 2.0, value3 = 2.5;
228     t_pargs           pa[]      = { { "-t1", FALSE, etTIME, { &value1 }, "Description" },
229                      { "-t2", FALSE, etTIME, { &value2 }, "Description" },
230                      { "-t3", FALSE, etTIME, { &value3 }, "Description" } };
231     const char* const cmdline[] = { "test", "-t1", "2", "-t2", "-.5", "-tu", "ns" };
232     parseFromArray(cmdline, PCA_TIME_UNIT, {}, pa);
233     EXPECT_EQ(2000.0, value1);
234     EXPECT_EQ(-500.0, value2);
235     EXPECT_EQ(2.5, value3);
236 }
237
238 TEST_F(ParseCommonArgsTest, ParsesEnumArgs)
239 {
240     const char*       value1[]  = { nullptr, "none", "on", "off", nullptr };
241     const char*       value2[]  = { nullptr, "def", "value", "value_other", nullptr };
242     const char*       value3[]  = { nullptr, "default", "value", nullptr };
243     t_pargs           pa[]      = { { "-s1", FALSE, etENUM, { value1 }, "Description" },
244                      { "-s2", FALSE, etENUM, { value2 }, "Description" },
245                      { "-s3", FALSE, etENUM, { value3 }, "Description" } };
246     const char* const cmdline[] = { "test", "-s1", "off", "-s2", "val" };
247     parseFromArray(cmdline, 0, {}, pa);
248     EXPECT_STREQ("off", value1[0]);
249     EXPECT_STREQ("value", value2[0]);
250     EXPECT_STREQ("default", value3[0]);
251     EXPECT_EQ(value1[nenum(value1)], value1[0]);
252     EXPECT_EQ(value2[nenum(value2)], value2[0]);
253     EXPECT_EQ(value3[nenum(value3)], value3[0]);
254 }
255
256 /********************************************************************
257  * Tests for file name options (output files, not dependent on file system)
258  */
259
260 TEST_F(ParseCommonArgsTest, ParsesFileArgs)
261 {
262     t_filenm          fnm[]     = { { efXVG, "-o1", "out1", ffOPTWR },
263                        { efXVG, "-o2", "out2", ffOPTWR },
264                        { efXVG, "-om", "outm", ffWRMULT },
265                        { efXVG, "-om2", "outm2", ffWRMULT } };
266     const char* const cmdline[] = { "test", "-o1",   "-o2",       "test",
267                                     "-om",  "test1", "test2.xvg", "-om2" };
268     parseFromArray(cmdline, 0, fnm, {});
269     EXPECT_STREQ("out1.xvg", opt2fn_null("-o1", nfile(), fnm));
270     EXPECT_STREQ("test.xvg", opt2fn_null("-o2", nfile(), fnm));
271     gmx::ArrayRef<const std::string> files = opt2fns("-om", nfile(), fnm);
272     EXPECT_EQ(2, files.size());
273     if (files.size() != 2)
274     {
275         EXPECT_STREQ("test1.xvg", files[0].c_str());
276         EXPECT_STREQ("test2.xvg", files[1].c_str());
277     }
278     EXPECT_STREQ("outm2.xvg", opt2fn("-om2", nfile(), fnm));
279 }
280
281 TEST_F(ParseCommonArgsTest, ParsesFileArgsWithDefaults)
282 {
283     t_filenm          fnm[]     = { { efTPS, nullptr, nullptr, ffWRITE },
284                        { efTRX, "-f2", nullptr, ffOPTWR },
285                        { efTRX, "-f3", "trj3", ffWRITE },
286                        { efXVG, "-o", "out", ffWRITE },
287                        { efXVG, "-om", "outm", ffWRMULT } };
288     const char* const cmdline[] = { "test" };
289     parseFromArray(cmdline, 0, fnm, {});
290     EXPECT_STREQ("topol.tpr", ftp2fn(efTPS, nfile(), fnm));
291     EXPECT_STREQ("traj.xtc", opt2fn("-f2", nfile(), fnm));
292     EXPECT_EQ(nullptr, opt2fn_null("-f2", nfile(), fnm));
293     EXPECT_STREQ("trj3.xtc", opt2fn("-f3", nfile(), fnm));
294     EXPECT_STREQ("out.xvg", opt2fn("-o", nfile(), fnm));
295     EXPECT_STREQ("outm.xvg", opt2fn("-om", nfile(), fnm));
296 }
297
298 TEST_F(ParseCommonArgsTest, ParsesFileArgsWithDefaultFileName)
299 {
300     t_filenm          fnm[]     = { { efTPS, "-s", nullptr, ffWRITE },
301                        { efTRX, "-f2", nullptr, ffWRITE },
302                        { efTRX, "-f3", "trj3", ffWRITE },
303                        { efXVG, "-o", "out", ffWRITE },
304                        { efXVG, "-om", "outm", ffWRMULT } };
305     const char* const cmdline[] = { "test", "-deffnm", "def", "-f2", "other", "-o" };
306     parseFromArray(cmdline, PCA_CAN_SET_DEFFNM, fnm, {});
307     EXPECT_STREQ("def.tpr", ftp2fn(efTPS, nfile(), fnm));
308     EXPECT_STREQ("other.xtc", opt2fn("-f2", nfile(), fnm));
309     EXPECT_STREQ("def.xtc", opt2fn("-f3", nfile(), fnm));
310     EXPECT_STREQ("def.xvg", opt2fn("-o", nfile(), fnm));
311     EXPECT_STREQ("def.xvg", opt2fn("-om", nfile(), fnm));
312 }
313
314 TEST_F(ParseCommonArgsTest, ParseFileArgsWithCustomDefaultExtension)
315 {
316     t_filenm          fnm[]     = { { efTRX, "-o1", "conf1.gro", ffWRITE },
317                        { efTRX, "-o2", "conf2.pdb", ffWRITE },
318                        { efTRX, "-o3", "conf3.gro", ffWRITE } };
319     const char* const cmdline[] = { "test", "-o2", "-o3", "test" };
320     parseFromArray(cmdline, PCA_CAN_SET_DEFFNM, fnm, {});
321     EXPECT_STREQ("conf1.gro", opt2fn("-o1", nfile(), fnm));
322     EXPECT_STREQ("conf2.pdb", opt2fn("-o2", nfile(), fnm));
323     EXPECT_STREQ("test.gro", opt2fn("-o3", nfile(), fnm));
324 }
325
326 /********************************************************************
327  * Tests for file name options (input files, dependent on file system contents)
328  */
329
330 TEST_F(ParseCommonArgsTest, HandlesNonExistentInputFiles)
331 {
332     t_filenm          fnm[] = { { efTPS, "-s", nullptr, ffREAD }, { efTRX, "-f", "trj", ffREAD },
333                        { efTRX, "-f2", "trj2", ffREAD }, { efTRX, "-f3", "trj3", ffREAD },
334                        { efTRX, "-f4", "trj4", ffREAD }, { efGRO, "-g", "cnf", ffREAD },
335                        { efGRO, "-g2", "cnf2", ffREAD } };
336     const char* const cmdline[] = { "test", "-f2", "-f3", "other", "-f4", "trj.gro", "-g2", "foo" };
337     parseFromArray(cmdline, PCA_DISABLE_INPUT_FILE_CHECKING, fnm, {});
338     EXPECT_STREQ("topol.tpr", ftp2fn(efTPS, nfile(), fnm));
339     EXPECT_STREQ("trj.xtc", opt2fn("-f", nfile(), fnm));
340     EXPECT_STREQ("trj2.xtc", opt2fn("-f2", nfile(), fnm));
341     EXPECT_STREQ("other.xtc", opt2fn("-f3", nfile(), fnm));
342     EXPECT_STREQ("trj.gro", opt2fn("-f4", nfile(), fnm));
343     EXPECT_STREQ("cnf.gro", opt2fn("-g", nfile(), fnm));
344     EXPECT_STREQ("foo.gro", opt2fn("-g2", nfile(), fnm));
345 }
346
347 TEST_F(ParseCommonArgsTest, HandlesNonExistentOptionalInputFiles)
348 {
349     t_filenm fnm[] = { { efTPS, "-s", nullptr, ffOPTRD }, { efTRX, "-f", "trj", ffOPTRD } };
350     const char* const cmdline[] = { "test" };
351     parseFromArray(cmdline, 0, fnm, {});
352     EXPECT_STREQ("topol.tpr", ftp2fn(efTPS, nfile(), fnm));
353     EXPECT_STREQ("trj.xtc", opt2fn("-f", nfile(), fnm));
354 }
355
356 TEST_F(ParseCommonArgsTest, AcceptsNonExistentInputFilesIfSpecified)
357 {
358     t_filenm          fnm[]     = { { efCPT, "-c", "file1", ffOPTRD | ffALLOW_MISSING },
359                        { efCPT, "-c2", "file2", ffOPTRD | ffALLOW_MISSING },
360                        { efCPT, "-c3", "file3", ffOPTRD | ffALLOW_MISSING },
361                        { efCPT, "-c4", "file4", ffOPTRD | ffALLOW_MISSING },
362                        { efTRX, "-f", "trj", ffOPTRD | ffALLOW_MISSING } };
363     const char* const cmdline[] = { "test",        "-c2",        "-c3",
364                                     "nonexistent", "-c4",        "nonexistent.cpt",
365                                     "-f",          "nonexistent" };
366     parseFromArray(cmdline, 0, fnm, {});
367     EXPECT_STREQ("file1.cpt", opt2fn("-c", nfile(), fnm));
368     EXPECT_STREQ("file2.cpt", opt2fn("-c2", nfile(), fnm));
369     EXPECT_STREQ("nonexistent.cpt", opt2fn("-c3", nfile(), fnm));
370     EXPECT_STREQ("nonexistent.cpt", opt2fn("-c4", nfile(), fnm));
371     EXPECT_STREQ("nonexistent.xtc", opt2fn("-f", nfile(), fnm));
372 }
373
374 TEST_F(ParseCommonArgsTest, HandlesCompressedFiles)
375 {
376     t_filenm fnm[] = { { efTRX, "-f", nullptr, ffREAD }, { efGRO, "-g", nullptr, ffREAD } };
377     args_.append("test");
378     std::string expectedF = addFileArg("-f", ".pdb.gz", efFull);
379     std::string expectedG = addFileArg("-g", ".gro.Z", efFull);
380     expectedF             = gmx::Path::stripExtension(expectedF);
381     expectedG             = gmx::Path::stripExtension(expectedG);
382     parseFromArgs(0, fnm, {});
383     EXPECT_EQ(expectedF, opt2fn("-f", nfile(), fnm));
384     EXPECT_EQ(expectedG, opt2fn("-g", nfile(), fnm));
385 }
386
387 TEST_F(ParseCommonArgsTest, AcceptsUnknownTrajectoryExtension)
388 {
389     t_filenm fnm[] = { { efTRX, "-f", nullptr, ffREAD } };
390     args_.append("test");
391     std::string expected = addFileArg("-f", ".foo", efFull);
392     parseFromArgs(0, fnm, {});
393     EXPECT_EQ(expected, opt2fn("-f", nfile(), fnm));
394 }
395
396 TEST_F(ParseCommonArgsTest, CompletesExtensionFromExistingFile)
397 {
398     args_.append("test");
399     std::string expected1 = addFileArg("-f1", "1.xtc", efNoExtension);
400     std::string expected2 = addFileArg("-f2", "2.gro", efNoExtension);
401     std::string expected3 = addFileArg("-f3", "3.tng", efNoExtension);
402     std::string expected4 = addFileArg("-f4", ".gro", efEmptyValue);
403     std::string def4      = gmx::Path::stripExtension(expected4);
404     t_filenm    fnm[]     = { { efTRX, "-f1", nullptr, ffREAD },
405                        { efTRX, "-f2", nullptr, ffREAD },
406                        { efTRX, "-f3", nullptr, ffREAD },
407                        { efTRX, "-f4", def4.c_str(), ffREAD } };
408     parseFromArgs(0, fnm, {});
409     EXPECT_EQ(expected1, opt2fn("-f1", nfile(), fnm));
410     EXPECT_EQ(expected2, opt2fn("-f2", nfile(), fnm));
411     EXPECT_EQ(expected3, opt2fn("-f3", nfile(), fnm));
412     EXPECT_EQ(expected4, opt2fn("-f4", nfile(), fnm));
413 }
414
415 TEST_F(ParseCommonArgsTest, CompletesExtensionFromExistingFileWithDefaultFileName)
416 {
417     t_filenm fnm[] = { { efTRX, "-f1", nullptr, ffREAD },
418                        { efSTO, "-f2", "foo", ffREAD },
419                        { efTRX, "-f3", nullptr, ffREAD },
420                        { efSTX, "-f4", nullptr, ffREAD } };
421     args_.append("test");
422     std::string expected1 = addFileArg("-f1", "1.trr", efNoExtension);
423     std::string expected2 = addFileArg("-f2", ".pdb", efEmptyValue);
424     std::string expected3 = addFileArg("-f3", ".trr", efEmptyValue);
425     std::string expected4 = addFileArg(nullptr, ".pdb", efEmptyValue);
426     std::string deffnm    = gmx::Path::stripExtension(expected3);
427     args_.append("-deffnm");
428     args_.append(deffnm);
429     parseFromArgs(PCA_CAN_SET_DEFFNM, fnm, {});
430     EXPECT_EQ(expected1, opt2fn("-f1", nfile(), fnm));
431     EXPECT_EQ(expected2, opt2fn("-f2", nfile(), fnm));
432     EXPECT_EQ(expected3, opt2fn("-f3", nfile(), fnm));
433     EXPECT_EQ(expected4, opt2fn("-f4", nfile(), fnm));
434 }
435
436 // This is needed e.g. for tune_pme, which passes unknown arguments on
437 // to child mdrun processes that it spawns.
438 TEST_F(ParseCommonArgsTest, CanKeepUnknownArgs)
439 {
440     int      ivalue = 0;
441     gmx_bool bvalue = FALSE;
442     t_pargs  pa[]   = {
443         { "-i", FALSE, etINT, { &ivalue }, "Description" },
444         { "-b", FALSE, etBOOL, { &bvalue }, "Description" },
445     };
446     t_filenm fnm[] = { { efXVG, "-o1", "out1", ffOPTWR }, { efXVG, "-o2", "out2", ffOPTWR } };
447     const char* const cmdline[] = { "test", "foo", "-unk", "-o1",   "-unk2", "-o2",
448                                     "test", "-i",  "2",    "-unk3", "-b",    "-unk4" };
449     parseFromArray(cmdline, PCA_NOEXIT_ON_ARGS, fnm, pa);
450     EXPECT_EQ(2, ivalue);
451     EXPECT_TRUE(bvalue);
452     EXPECT_STREQ("out1.xvg", opt2fn_null("-o1", nfile(), fnm));
453     EXPECT_STREQ("test.xvg", opt2fn_null("-o2", nfile(), fnm));
454     EXPECT_EQ(6, args_.argc());
455     EXPECT_STREQ(cmdline[0], args_.arg(0));
456     EXPECT_STREQ(cmdline[1], args_.arg(1));
457     EXPECT_STREQ(cmdline[2], args_.arg(2));
458     EXPECT_STREQ(cmdline[4], args_.arg(3));
459     EXPECT_STREQ(cmdline[9], args_.arg(4));
460     EXPECT_STREQ(cmdline[11], args_.arg(5));
461 }
462
463 } // namespace