2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
5 * Copyright (c) 2018,2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Tests parse_common_args().
40 * Currently, negative tests are not possible, because those trigger
41 * gmx_fatal() and terminate the whole test binary.
43 * \author Teemu Murtola <teemu.murtola@gmail.com>
44 * \ingroup module_commandline
48 #include "gromacs/commandline/pargs.h"
52 #include <gtest/gtest.h>
54 #include "gromacs/utility/arrayref.h"
55 #include "gromacs/utility/path.h"
56 #include "gromacs/utility/stringutil.h"
57 #include "gromacs/utility/textwriter.h"
59 #include "testutils/cmdlinetest.h"
60 #include "testutils/testasserts.h"
61 #include "testutils/testfilemanager.h"
66 using gmx::test::CommandLine
;
68 class ParseCommonArgsTest
: public ::testing::Test
78 ParseCommonArgsTest() : oenv_(nullptr), fileCount_(0) {}
79 ~ParseCommonArgsTest() override
{ output_env_done(oenv_
); }
81 int nfile() const { return fileCount_
; }
83 void parseFromArgs(unsigned long flags
, gmx::ArrayRef
<t_filenm
> fnm
, gmx::ArrayRef
<t_pargs
> pa
)
85 fileCount_
= fnm
.size();
86 bool bOk
= parse_common_args(&args_
.argc(), args_
.argv(), flags
, fnm
.size(), fnm
.data(),
87 pa
.size(), pa
.data(), 0, nullptr, 0, nullptr, &oenv_
);
90 void parseFromArray(gmx::ArrayRef
<const char* const> cmdline
,
92 gmx::ArrayRef
<t_filenm
> fnm
,
93 gmx::ArrayRef
<t_pargs
> pa
)
95 args_
.initFromArray(cmdline
);
96 parseFromArgs(flags
, fnm
, pa
);
98 std::string
addFileArg(const char* name
, const char* extension
, FileArgumentType type
)
100 std::string
filename(tempFiles_
.getTemporaryFilePath(extension
));
101 gmx::TextWriter::writeFileFromString(filename
, "Dummy file");
107 case efFull
: args_
.append(filename
); break;
108 case efNoExtension
: args_
.append(gmx::Path::stripExtension(filename
)); break;
109 case efEmptyValue
: break;
115 // This must be a member that persists until the end of the test,
116 // because string arguments are not duplicated in the output.
120 gmx_output_env_t
* oenv_
;
122 gmx::test::TestFileManager tempFiles_
;
125 /********************************************************************
126 * Tests for different types of options
129 TEST_F(ParseCommonArgsTest
, ParsesIntegerArgs
)
131 int value1
= 0, value2
= 0, value3
= 3;
132 t_pargs pa
[] = { { "-i1", FALSE
, etINT
, { &value1
}, "Description" },
133 { "-i2", FALSE
, etINT
, { &value2
}, "Description" },
134 { "-i3", FALSE
, etINT
, { &value3
}, "Description" } };
135 const char* const cmdline
[] = { "test", "-i1", "2", "-i2", "-3" };
136 parseFromArray(cmdline
, 0, {}, pa
);
137 EXPECT_EQ(2, value1
);
138 EXPECT_EQ(-3, value2
);
139 EXPECT_EQ(3, value3
);
142 TEST_F(ParseCommonArgsTest
, ParsesInt64Args
)
144 int64_t value1
= 0, value2
= 0, value3
= 3;
145 t_pargs pa
[] = { { "-i1", FALSE
, etINT64
, { &value1
}, "Description" },
146 { "-i2", FALSE
, etINT64
, { &value2
}, "Description" },
147 { "-i3", FALSE
, etINT64
, { &value3
}, "Description" } };
148 const char* const cmdline
[] = { "test", "-i1", "2", "-i2", "-3" };
149 parseFromArray(cmdline
, 0, {}, pa
);
150 EXPECT_EQ(2, value1
);
151 EXPECT_EQ(-3, value2
);
152 EXPECT_EQ(3, value3
);
155 TEST_F(ParseCommonArgsTest
, ParsesRealArgs
)
157 real value1
= 0.0, value2
= 0.0, value3
= 2.5;
158 t_pargs pa
[] = { { "-r1", FALSE
, etREAL
, { &value1
}, "Description" },
159 { "-r2", FALSE
, etREAL
, { &value2
}, "Description" },
160 { "-r3", FALSE
, etREAL
, { &value3
}, "Description" } };
161 const char* const cmdline
[] = { "test", "-r1", "2", "-r2", "-.5" };
162 parseFromArray(cmdline
, 0, {}, pa
);
163 EXPECT_EQ(2.0, value1
);
164 EXPECT_EQ(-0.5, value2
);
165 EXPECT_EQ(2.5, value3
);
168 TEST_F(ParseCommonArgsTest
, ParsesStringArgs
)
170 const char * value1
= "def", *value2
= "", *value3
= "default";
171 t_pargs pa
[] = { { "-s1", FALSE
, etSTR
, { &value1
}, "Description" },
172 { "-s2", FALSE
, etSTR
, { &value2
}, "Description" },
173 { "-s3", FALSE
, etSTR
, { &value3
}, "Description" } };
174 const char* const cmdline
[] = { "test", "-s1", "", "-s2", "test" };
175 parseFromArray(cmdline
, 0, {}, pa
);
176 EXPECT_STREQ("", value1
);
177 EXPECT_STREQ("test", value2
);
178 EXPECT_STREQ("default", value3
);
181 TEST_F(ParseCommonArgsTest
, ParsesBooleanArgs
)
183 gmx_bool value1
= TRUE
, value2
= FALSE
, value3
= TRUE
;
184 t_pargs pa
[] = { { "-b1", FALSE
, etBOOL
, { &value1
}, "Description" },
185 { "-b2", FALSE
, etBOOL
, { &value2
}, "Description" },
186 { "-b3", FALSE
, etBOOL
, { &value3
}, "Description" } };
187 const char* const cmdline
[] = { "test", "-nob1", "-b2" };
188 parseFromArray(cmdline
, 0, {}, pa
);
189 EXPECT_FALSE(value1
);
194 TEST_F(ParseCommonArgsTest
, ParsesVectorArgs
)
196 rvec value1
= { 0, 0, 0 }, value2
= { 0, 0, 0 }, value3
= { 1, 2, 3 };
197 t_pargs pa
[] = { { "-v1", FALSE
, etRVEC
, { &value1
}, "Description" },
198 { "-v2", FALSE
, etRVEC
, { &value2
}, "Description" },
199 { "-v3", FALSE
, etRVEC
, { &value3
}, "Description" } };
200 const char* const cmdline
[] = { "test", "-v1", "2", "1", "3", "-v2", "1" };
201 parseFromArray(cmdline
, 0, {}, pa
);
202 EXPECT_EQ(2.0, value1
[XX
]);
203 EXPECT_EQ(1.0, value1
[YY
]);
204 EXPECT_EQ(3.0, value1
[ZZ
]);
205 EXPECT_EQ(1.0, value2
[XX
]);
206 EXPECT_EQ(1.0, value2
[YY
]);
207 EXPECT_EQ(1.0, value2
[ZZ
]);
208 EXPECT_EQ(1.0, value3
[XX
]);
209 EXPECT_EQ(2.0, value3
[YY
]);
210 EXPECT_EQ(3.0, value3
[ZZ
]);
213 TEST_F(ParseCommonArgsTest
, ParsesTimeArgs
)
215 real value1
= 1.0, value2
= 2.0, value3
= 2.5;
216 t_pargs pa
[] = { { "-t1", FALSE
, etTIME
, { &value1
}, "Description" },
217 { "-t2", FALSE
, etTIME
, { &value2
}, "Description" },
218 { "-t3", FALSE
, etTIME
, { &value3
}, "Description" } };
219 const char* const cmdline
[] = { "test", "-t1", "2", "-t2", "-.5" };
220 parseFromArray(cmdline
, 0, {}, pa
);
221 EXPECT_EQ(2.0, value1
);
222 EXPECT_EQ(-0.5, value2
);
223 EXPECT_EQ(2.5, value3
);
226 TEST_F(ParseCommonArgsTest
, ParsesTimeArgsWithTimeUnit
)
228 real value1
= 1.0, value2
= 2.0, value3
= 2.5;
229 t_pargs pa
[] = { { "-t1", FALSE
, etTIME
, { &value1
}, "Description" },
230 { "-t2", FALSE
, etTIME
, { &value2
}, "Description" },
231 { "-t3", FALSE
, etTIME
, { &value3
}, "Description" } };
232 const char* const cmdline
[] = { "test", "-t1", "2", "-t2", "-.5", "-tu", "ns" };
233 parseFromArray(cmdline
, PCA_TIME_UNIT
, {}, pa
);
234 EXPECT_EQ(2000.0, value1
);
235 EXPECT_EQ(-500.0, value2
);
236 EXPECT_EQ(2.5, value3
);
239 TEST_F(ParseCommonArgsTest
, ParsesEnumArgs
)
241 const char* value1
[] = { nullptr, "none", "on", "off", nullptr };
242 const char* value2
[] = { nullptr, "def", "value", "value_other", nullptr };
243 const char* value3
[] = { nullptr, "default", "value", nullptr };
244 t_pargs pa
[] = { { "-s1", FALSE
, etENUM
, { value1
}, "Description" },
245 { "-s2", FALSE
, etENUM
, { value2
}, "Description" },
246 { "-s3", FALSE
, etENUM
, { value3
}, "Description" } };
247 const char* const cmdline
[] = { "test", "-s1", "off", "-s2", "val" };
248 parseFromArray(cmdline
, 0, {}, pa
);
249 EXPECT_STREQ("off", value1
[0]);
250 EXPECT_STREQ("value", value2
[0]);
251 EXPECT_STREQ("default", value3
[0]);
252 EXPECT_EQ(value1
[nenum(value1
)], value1
[0]);
253 EXPECT_EQ(value2
[nenum(value2
)], value2
[0]);
254 EXPECT_EQ(value3
[nenum(value3
)], value3
[0]);
257 /********************************************************************
258 * Tests for file name options (output files, not dependent on file system)
261 TEST_F(ParseCommonArgsTest
, ParsesFileArgs
)
263 t_filenm fnm
[] = { { efXVG
, "-o1", "out1", ffOPTWR
},
264 { efXVG
, "-o2", "out2", ffOPTWR
},
265 { efXVG
, "-om", "outm", ffWRMULT
},
266 { efXVG
, "-om2", "outm2", ffWRMULT
} };
267 const char* const cmdline
[] = { "test", "-o1", "-o2", "test",
268 "-om", "test1", "test2.xvg", "-om2" };
269 parseFromArray(cmdline
, 0, fnm
, {});
270 EXPECT_STREQ("out1.xvg", opt2fn_null("-o1", nfile(), fnm
));
271 EXPECT_STREQ("test.xvg", opt2fn_null("-o2", nfile(), fnm
));
272 gmx::ArrayRef
<const std::string
> files
= opt2fns("-om", nfile(), fnm
);
273 EXPECT_EQ(2, files
.size());
274 if (files
.size() != 2)
276 EXPECT_STREQ("test1.xvg", files
[0].c_str());
277 EXPECT_STREQ("test2.xvg", files
[1].c_str());
279 EXPECT_STREQ("outm2.xvg", opt2fn("-om2", nfile(), fnm
));
282 TEST_F(ParseCommonArgsTest
, ParsesFileArgsWithDefaults
)
284 t_filenm fnm
[] = { { efTPS
, nullptr, nullptr, ffWRITE
},
285 { efTRX
, "-f2", nullptr, ffOPTWR
},
286 { efTRX
, "-f3", "trj3", ffWRITE
},
287 { efXVG
, "-o", "out", ffWRITE
},
288 { efXVG
, "-om", "outm", ffWRMULT
} };
289 const char* const cmdline
[] = { "test" };
290 parseFromArray(cmdline
, 0, fnm
, {});
291 EXPECT_STREQ("topol.tpr", ftp2fn(efTPS
, nfile(), fnm
));
292 EXPECT_STREQ("traj.xtc", opt2fn("-f2", nfile(), fnm
));
293 EXPECT_EQ(nullptr, opt2fn_null("-f2", nfile(), fnm
));
294 EXPECT_STREQ("trj3.xtc", opt2fn("-f3", nfile(), fnm
));
295 EXPECT_STREQ("out.xvg", opt2fn("-o", nfile(), fnm
));
296 EXPECT_STREQ("outm.xvg", opt2fn("-om", nfile(), fnm
));
299 TEST_F(ParseCommonArgsTest
, ParsesFileArgsWithDefaultFileName
)
301 t_filenm fnm
[] = { { efTPS
, "-s", nullptr, ffWRITE
},
302 { efTRX
, "-f2", nullptr, ffWRITE
},
303 { efTRX
, "-f3", "trj3", ffWRITE
},
304 { efXVG
, "-o", "out", ffWRITE
},
305 { efXVG
, "-om", "outm", ffWRMULT
} };
306 const char* const cmdline
[] = { "test", "-deffnm", "def", "-f2", "other", "-o" };
307 parseFromArray(cmdline
, PCA_CAN_SET_DEFFNM
, fnm
, {});
308 EXPECT_STREQ("def.tpr", ftp2fn(efTPS
, nfile(), fnm
));
309 EXPECT_STREQ("other.xtc", opt2fn("-f2", nfile(), fnm
));
310 EXPECT_STREQ("def.xtc", opt2fn("-f3", nfile(), fnm
));
311 EXPECT_STREQ("def.xvg", opt2fn("-o", nfile(), fnm
));
312 EXPECT_STREQ("def.xvg", opt2fn("-om", nfile(), fnm
));
315 TEST_F(ParseCommonArgsTest
, ParseFileArgsWithCustomDefaultExtension
)
317 t_filenm fnm
[] = { { efTRX
, "-o1", "conf1.gro", ffWRITE
},
318 { efTRX
, "-o2", "conf2.pdb", ffWRITE
},
319 { efTRX
, "-o3", "conf3.gro", ffWRITE
} };
320 const char* const cmdline
[] = { "test", "-o2", "-o3", "test" };
321 parseFromArray(cmdline
, PCA_CAN_SET_DEFFNM
, fnm
, {});
322 EXPECT_STREQ("conf1.gro", opt2fn("-o1", nfile(), fnm
));
323 EXPECT_STREQ("conf2.pdb", opt2fn("-o2", nfile(), fnm
));
324 EXPECT_STREQ("test.gro", opt2fn("-o3", nfile(), fnm
));
327 /********************************************************************
328 * Tests for file name options (input files, dependent on file system contents)
331 TEST_F(ParseCommonArgsTest
, HandlesNonExistentInputFiles
)
333 t_filenm fnm
[] = { { efTPS
, "-s", nullptr, ffREAD
}, { efTRX
, "-f", "trj", ffREAD
},
334 { efTRX
, "-f2", "trj2", ffREAD
}, { efTRX
, "-f3", "trj3", ffREAD
},
335 { efTRX
, "-f4", "trj4", ffREAD
}, { efGRO
, "-g", "cnf", ffREAD
},
336 { efGRO
, "-g2", "cnf2", ffREAD
} };
337 const char* const cmdline
[] = { "test", "-f2", "-f3", "other", "-f4", "trj.gro", "-g2", "foo" };
338 parseFromArray(cmdline
, PCA_DISABLE_INPUT_FILE_CHECKING
, fnm
, {});
339 EXPECT_STREQ("topol.tpr", ftp2fn(efTPS
, nfile(), fnm
));
340 EXPECT_STREQ("trj.xtc", opt2fn("-f", nfile(), fnm
));
341 EXPECT_STREQ("trj2.xtc", opt2fn("-f2", nfile(), fnm
));
342 EXPECT_STREQ("other.xtc", opt2fn("-f3", nfile(), fnm
));
343 EXPECT_STREQ("trj.gro", opt2fn("-f4", nfile(), fnm
));
344 EXPECT_STREQ("cnf.gro", opt2fn("-g", nfile(), fnm
));
345 EXPECT_STREQ("foo.gro", opt2fn("-g2", nfile(), fnm
));
348 TEST_F(ParseCommonArgsTest
, HandlesNonExistentOptionalInputFiles
)
350 t_filenm fnm
[] = { { efTPS
, "-s", nullptr, ffOPTRD
}, { efTRX
, "-f", "trj", ffOPTRD
} };
351 const char* const cmdline
[] = { "test" };
352 parseFromArray(cmdline
, 0, fnm
, {});
353 EXPECT_STREQ("topol.tpr", ftp2fn(efTPS
, nfile(), fnm
));
354 EXPECT_STREQ("trj.xtc", opt2fn("-f", nfile(), fnm
));
357 TEST_F(ParseCommonArgsTest
, AcceptsNonExistentInputFilesIfSpecified
)
359 t_filenm fnm
[] = { { efCPT
, "-c", "file1", ffOPTRD
| ffALLOW_MISSING
},
360 { efCPT
, "-c2", "file2", ffOPTRD
| ffALLOW_MISSING
},
361 { efCPT
, "-c3", "file3", ffOPTRD
| ffALLOW_MISSING
},
362 { efCPT
, "-c4", "file4", ffOPTRD
| ffALLOW_MISSING
},
363 { efTRX
, "-f", "trj", ffOPTRD
| ffALLOW_MISSING
} };
364 const char* const cmdline
[] = { "test", "-c2", "-c3",
365 "nonexistent", "-c4", "nonexistent.cpt",
366 "-f", "nonexistent" };
367 parseFromArray(cmdline
, 0, fnm
, {});
368 EXPECT_STREQ("file1.cpt", opt2fn("-c", nfile(), fnm
));
369 EXPECT_STREQ("file2.cpt", opt2fn("-c2", nfile(), fnm
));
370 EXPECT_STREQ("nonexistent.cpt", opt2fn("-c3", nfile(), fnm
));
371 EXPECT_STREQ("nonexistent.cpt", opt2fn("-c4", nfile(), fnm
));
372 EXPECT_STREQ("nonexistent.xtc", opt2fn("-f", nfile(), fnm
));
375 TEST_F(ParseCommonArgsTest
, HandlesCompressedFiles
)
377 t_filenm fnm
[] = { { efTRX
, "-f", nullptr, ffREAD
}, { efGRO
, "-g", nullptr, ffREAD
} };
378 args_
.append("test");
379 std::string expectedF
= addFileArg("-f", ".pdb.gz", efFull
);
380 std::string expectedG
= addFileArg("-g", ".gro.Z", efFull
);
381 expectedF
= gmx::Path::stripExtension(expectedF
);
382 expectedG
= gmx::Path::stripExtension(expectedG
);
383 parseFromArgs(0, fnm
, {});
384 EXPECT_EQ(expectedF
, opt2fn("-f", nfile(), fnm
));
385 EXPECT_EQ(expectedG
, opt2fn("-g", nfile(), fnm
));
388 TEST_F(ParseCommonArgsTest
, AcceptsUnknownTrajectoryExtension
)
390 t_filenm fnm
[] = { { efTRX
, "-f", nullptr, ffREAD
} };
391 args_
.append("test");
392 std::string expected
= addFileArg("-f", ".foo", efFull
);
393 parseFromArgs(0, fnm
, {});
394 EXPECT_EQ(expected
, opt2fn("-f", nfile(), fnm
));
397 TEST_F(ParseCommonArgsTest
, CompletesExtensionFromExistingFile
)
399 args_
.append("test");
400 std::string expected1
= addFileArg("-f1", "1.xtc", efNoExtension
);
401 std::string expected2
= addFileArg("-f2", "2.gro", efNoExtension
);
402 std::string expected3
= addFileArg("-f3", "3.tng", efNoExtension
);
403 std::string expected4
= addFileArg("-f4", ".gro", efEmptyValue
);
404 std::string def4
= gmx::Path::stripExtension(expected4
);
405 t_filenm fnm
[] = { { efTRX
, "-f1", nullptr, ffREAD
},
406 { efTRX
, "-f2", nullptr, ffREAD
},
407 { efTRX
, "-f3", nullptr, ffREAD
},
408 { efTRX
, "-f4", def4
.c_str(), ffREAD
} };
409 parseFromArgs(0, fnm
, {});
410 EXPECT_EQ(expected1
, opt2fn("-f1", nfile(), fnm
));
411 EXPECT_EQ(expected2
, opt2fn("-f2", nfile(), fnm
));
412 EXPECT_EQ(expected3
, opt2fn("-f3", nfile(), fnm
));
413 EXPECT_EQ(expected4
, opt2fn("-f4", nfile(), fnm
));
416 TEST_F(ParseCommonArgsTest
, CompletesExtensionFromExistingFileWithDefaultFileName
)
418 t_filenm fnm
[] = { { efTRX
, "-f1", nullptr, ffREAD
},
419 { efSTO
, "-f2", "foo", ffREAD
},
420 { efTRX
, "-f3", nullptr, ffREAD
},
421 { efSTX
, "-f4", nullptr, ffREAD
} };
422 args_
.append("test");
423 std::string expected1
= addFileArg("-f1", "1.trr", efNoExtension
);
424 std::string expected2
= addFileArg("-f2", ".pdb", efEmptyValue
);
425 std::string expected3
= addFileArg("-f3", ".trr", efEmptyValue
);
426 std::string expected4
= addFileArg(nullptr, ".pdb", efEmptyValue
);
427 std::string deffnm
= gmx::Path::stripExtension(expected3
);
428 args_
.append("-deffnm");
429 args_
.append(deffnm
);
430 parseFromArgs(PCA_CAN_SET_DEFFNM
, fnm
, {});
431 EXPECT_EQ(expected1
, opt2fn("-f1", nfile(), fnm
));
432 EXPECT_EQ(expected2
, opt2fn("-f2", nfile(), fnm
));
433 EXPECT_EQ(expected3
, opt2fn("-f3", nfile(), fnm
));
434 EXPECT_EQ(expected4
, opt2fn("-f4", nfile(), fnm
));
437 // This is needed e.g. for tune_pme, which passes unknown arguments on
438 // to child mdrun processes that it spawns.
439 TEST_F(ParseCommonArgsTest
, CanKeepUnknownArgs
)
442 gmx_bool bvalue
= FALSE
;
444 { "-i", FALSE
, etINT
, { &ivalue
}, "Description" },
445 { "-b", FALSE
, etBOOL
, { &bvalue
}, "Description" },
447 t_filenm fnm
[] = { { efXVG
, "-o1", "out1", ffOPTWR
}, { efXVG
, "-o2", "out2", ffOPTWR
} };
448 const char* const cmdline
[] = { "test", "foo", "-unk", "-o1", "-unk2", "-o2",
449 "test", "-i", "2", "-unk3", "-b", "-unk4" };
450 parseFromArray(cmdline
, PCA_NOEXIT_ON_ARGS
, fnm
, pa
);
451 EXPECT_EQ(2, ivalue
);
453 EXPECT_STREQ("out1.xvg", opt2fn_null("-o1", nfile(), fnm
));
454 EXPECT_STREQ("test.xvg", opt2fn_null("-o2", nfile(), fnm
));
455 EXPECT_EQ(6, args_
.argc());
456 EXPECT_STREQ(cmdline
[0], args_
.arg(0));
457 EXPECT_STREQ(cmdline
[1], args_
.arg(1));
458 EXPECT_STREQ(cmdline
[2], args_
.arg(2));
459 EXPECT_STREQ(cmdline
[4], args_
.arg(3));
460 EXPECT_STREQ(cmdline
[9], args_
.arg(4));
461 EXPECT_STREQ(cmdline
[11], args_
.arg(5));