C++ Utilities  4.10.0
Common C++ classes and routines used by my applications such as argument parser, IO and conversion utilities
argumentparsertests.cpp
Go to the documentation of this file.
1 #include "./outputcheck.h"
2 #include "./testutils.h"
3 
4 #include "../conversion/stringbuilder.h"
5 
6 #include "../application/argumentparser.h"
7 #include "../application/argumentparserprivate.h"
8 #include "../application/commandlineutils.h"
9 #include "../application/failure.h"
10 #include "../application/fakeqtconfigarguments.h"
11 
12 #include "../io/path.h"
13 
14 #include "resources/config.h"
15 
16 #include <cppunit/TestFixture.h>
17 #include <cppunit/extensions/HelperMacros.h>
18 
19 #include <cstdlib>
20 #include <cstring>
21 
22 using namespace std;
23 using namespace ApplicationUtilities;
24 using namespace ConversionUtilities;
25 using namespace TestUtilities;
26 using namespace TestUtilities::Literals;
27 
28 using namespace CPPUNIT_NS;
29 
33 class ArgumentParserTests : public TestFixture {
34  CPPUNIT_TEST_SUITE(ArgumentParserTests);
35  CPPUNIT_TEST(testArgument);
36  CPPUNIT_TEST(testParsing);
37  CPPUNIT_TEST(testCallbacks);
38  CPPUNIT_TEST(testBashCompletion);
39  CPPUNIT_TEST(testHelp);
40  CPPUNIT_TEST(testSetMainArguments);
41  CPPUNIT_TEST_SUITE_END();
42 
43 public:
44  void setUp();
45  void tearDown();
46 
47  void testArgument();
48  void testParsing();
49  void testCallbacks();
50  void testBashCompletion();
51  void testHelp();
52  void testSetMainArguments();
53 
54 private:
55  void callback();
56 };
57 
59 
61 {
62 }
63 
65 {
66 }
67 
72 {
73  Argument argument("test", 't', "some description");
74  CPPUNIT_ASSERT_EQUAL(argument.isRequired(), false);
75  argument.setConstraints(1, 10);
76  CPPUNIT_ASSERT_EQUAL(argument.isRequired(), true);
77  Argument subArg("sub", 's', "sub arg");
78  argument.addSubArgument(&subArg);
79  CPPUNIT_ASSERT_EQUAL(subArg.parents().at(0), &argument);
80  CPPUNIT_ASSERT(!subArg.conflictsWithArgument());
81  CPPUNIT_ASSERT(!argument.firstValue());
82  argument.setEnvironmentVariable("FOO_ENV_VAR");
83  setenv("FOO_ENV_VAR", "foo", true);
84  CPPUNIT_ASSERT(!strcmp(argument.firstValue(), "foo"));
85  ArgumentOccurrence occurrence(0, vector<Argument *>(), nullptr);
86  occurrence.values.emplace_back("bar");
87  argument.m_occurrences.emplace_back(move(occurrence));
88  CPPUNIT_ASSERT(!strcmp(argument.firstValue(), "bar"));
89 }
90 
95 {
96  // setup parser with some test argument definitions
97  ArgumentParser parser;
99  QT_CONFIG_ARGUMENTS qtConfigArgs;
100  HelpArgument helpArg(parser);
101  Argument verboseArg("verbose", 'v', "be verbose");
102  verboseArg.setCombinable(true);
103  Argument fileArg("file", 'f', "specifies the path of the file to be opened");
104  fileArg.setValueNames({ "path" });
105  fileArg.setRequiredValueCount(1);
106  fileArg.setEnvironmentVariable("PATH");
107  Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
108  filesArg.setValueNames({ "path 1", "path 2" });
109  filesArg.setRequiredValueCount(Argument::varValueCount);
110  Argument outputFileArg("output-file", 'o', "specifies the path of the output file");
111  outputFileArg.setValueNames({ "path" });
112  outputFileArg.setRequiredValueCount(1);
113  outputFileArg.setRequired(true);
114  outputFileArg.setCombinable(true);
115  Argument printFieldNamesArg("print-field-names", '\0', "prints available field names");
116  Argument displayFileInfoArg("display-file-info", 'i', "displays general file information");
117  Argument notAlbumArg("album", 'a', "should not be confused with album value");
118  displayFileInfoArg.setDenotesOperation(true);
119  displayFileInfoArg.setSubArguments({ &fileArg, &verboseArg, &notAlbumArg });
120  Argument fieldsArg("fields", '\0', "specifies the fields");
121  fieldsArg.setRequiredValueCount(Argument::varValueCount);
122  fieldsArg.setValueNames({ "title", "album", "artist", "trackpos" });
123  fieldsArg.setImplicit(true);
124  Argument displayTagInfoArg("get", 'p', "displays the values of all specified tag fields (displays all fields if none specified)");
125  displayTagInfoArg.setDenotesOperation(true);
126  displayTagInfoArg.setSubArguments({ &fieldsArg, &filesArg, &verboseArg, &notAlbumArg });
127  parser.setMainArguments({ &qtConfigArgs.qtWidgetsGuiArg(), &printFieldNamesArg, &displayTagInfoArg, &displayFileInfoArg, &helpArg });
128 
129  // error about uncombinable arguments
130  const char *argv[] = { "tageditor", "get", "album", "title", "diskpos", "-f", "somefile" };
131  // try to parse, this should fail
132  try {
133  parser.parseArgs(7, argv);
134  CPPUNIT_FAIL("Exception expected.");
135  } catch (const Failure &e) {
136  CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"files\" can not be combined with \"fields\"."));
137  }
138 
139  // arguments read correctly after successful parse
140  filesArg.setCombinable(true);
141  parser.resetArgs();
142  parser.parseArgs(7, argv);
143  // check results
144  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
145  CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
146  CPPUNIT_ASSERT(!strcmp(parser.executable(), "tageditor"));
147  CPPUNIT_ASSERT(!verboseArg.isPresent());
148  CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
149  CPPUNIT_ASSERT(fieldsArg.isPresent());
150  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album"));
151  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "title"));
152  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(2), "diskpos"));
153  CPPUNIT_ASSERT_THROW(displayTagInfoArg.values().at(3), out_of_range);
154 
155  // skip empty args
156  const char *argv2[] = { "tageditor", "", "-p", "album", "title", "diskpos", "", "--files", "somefile" };
157  // reparse the args
158  parser.resetArgs();
159  parser.parseArgs(9, argv2);
160  // check results again
161  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
162  CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
163  CPPUNIT_ASSERT(!verboseArg.isPresent());
164  CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
165  CPPUNIT_ASSERT(fieldsArg.isPresent());
166  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album"));
167  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "title"));
168  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(2), "diskpos"));
169  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(3), ""));
170  CPPUNIT_ASSERT_THROW(fieldsArg.values().at(4), out_of_range);
171  CPPUNIT_ASSERT(filesArg.isPresent());
172  CPPUNIT_ASSERT(!strcmp(filesArg.values().at(0), "somefile"));
173 
174  // error about unknown argument: forget get/-p
175  const char *argv3[] = { "tageditor", "album", "title", "diskpos", "--files", "somefile" };
176  try {
177  parser.resetArgs();
178  parser.parseArgs(6, argv3);
179  CPPUNIT_FAIL("Exception expected.");
180  } catch (const Failure &e) {
181  CPPUNIT_ASSERT_EQUAL("The specified argument \"album\" is unknown."s, string(e.what()));
182  }
183 
184  // warning about unknown argument
185  parser.setUnknownArgumentBehavior(UnknownArgumentBehavior::Warn);
186  // redirect stderr to check whether warnings are printed correctly
187  stringstream buffer;
188  streambuf *regularCerrBuffer = cerr.rdbuf(buffer.rdbuf());
189  parser.resetArgs();
190  try {
191  parser.parseArgs(6, argv3);
192  } catch (...) {
193  cerr.rdbuf(regularCerrBuffer);
194  throw;
195  }
196  cerr.rdbuf(regularCerrBuffer);
197  CPPUNIT_ASSERT_EQUAL("The specified argument \"album\" is unknown and will be ignored.\n"s
198  "The specified argument \"title\" is unknown and will be ignored.\n"s
199  "The specified argument \"diskpos\" is unknown and will be ignored.\n"s
200  "The specified argument \"--files\" is unknown and will be ignored.\n"s
201  "The specified argument \"somefile\" is unknown and will be ignored.\n"s,
202  buffer.str());
203  // none of the arguments should be present now
204  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
205  CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
206  CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
207  CPPUNIT_ASSERT(!fieldsArg.isPresent());
208  CPPUNIT_ASSERT(!filesArg.isPresent());
209 
210  // combined abbreviations like "-vf"
211  const char *argv4[] = { "tageditor", "-i", "-vf", "test" };
212  parser.setUnknownArgumentBehavior(UnknownArgumentBehavior::Fail);
213  parser.resetArgs();
214  parser.parseArgs(4, argv4);
215  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
216  CPPUNIT_ASSERT(displayFileInfoArg.isPresent());
217  CPPUNIT_ASSERT(verboseArg.isPresent());
218  CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
219  CPPUNIT_ASSERT(!filesArg.isPresent());
220  CPPUNIT_ASSERT(fileArg.isPresent());
221  CPPUNIT_ASSERT(!strcmp(fileArg.values().at(0), "test"));
222  CPPUNIT_ASSERT_THROW(fileArg.values().at(1), out_of_range);
223 
224  // constraint checking: no multiple occurrences (not resetting verboseArg on purpose)
225  displayFileInfoArg.reset(), fileArg.reset();
226  try {
227  parser.parseArgs(4, argv4);
228  CPPUNIT_FAIL("Exception expected.");
229  } catch (const Failure &e) {
230  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
231  CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" mustn't be specified more than 1 time."));
232  }
233 
234  // constraint checking: no contraint (not resetting verboseArg on purpose)
235  displayFileInfoArg.reset(), fileArg.reset();
236  verboseArg.setConstraints(0, Argument::varValueCount);
237  parser.parseArgs(4, argv4);
238  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
239 
240  // constraint checking: mandatory argument
241  verboseArg.setRequired(true);
242  parser.resetArgs();
243  parser.parseArgs(4, argv4);
244  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
245 
246  // contraint checking: error about missing mandatory argument
247  const char *argv5[] = { "tageditor", "-i", "-f", "test" };
248  displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
249  try {
250  parser.parseArgs(4, argv5);
251  CPPUNIT_FAIL("Exception expected.");
252  } catch (const Failure &e) {
253  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
254  CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" must be specified at least 1 time."));
255  }
256  verboseArg.setRequired(false);
257 
258  // combined abbreviation with nesting "-pf"
259  const char *argv10[] = { "tageditor", "-pf", "test" };
260  parser.resetArgs();
261  parser.parseArgs(3, argv10);
262  CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
263  CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
264  CPPUNIT_ASSERT(!fileArg.isPresent());
265  CPPUNIT_ASSERT(filesArg.isPresent());
266  CPPUNIT_ASSERT_EQUAL(filesArg.values(0).size(), static_cast<vector<const char *>::size_type>(1));
267  CPPUNIT_ASSERT(!strcmp(filesArg.values(0).front(), "test"));
268  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
269 
270  // constraint checking: no complains about missing -i
271  const char *argv6[] = { "tageditor", "-g" };
272  parser.resetArgs();
273  parser.parseArgs(2, argv6);
274  CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
275 
276  // constraint checking: dependend arguments (-f requires -i or -p)
277  const char *argv7[] = { "tageditor", "-f", "test" };
278  parser.resetArgs();
279  try {
280  parser.parseArgs(3, argv7);
281  CPPUNIT_FAIL("Exception expected.");
282  } catch (const Failure &e) {
283  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
284  CPPUNIT_ASSERT_EQUAL("The specified argument \"-f\" is unknown."s, string(e.what()));
285  }
286 
287  // equation sign syntax
288  const char *argv11[] = { "tageditor", "-if=test" };
289  parser.resetArgs();
290  parser.parseArgs(2, argv11);
291  CPPUNIT_ASSERT(!filesArg.isPresent());
292  CPPUNIT_ASSERT(fileArg.isPresent());
293  CPPUNIT_ASSERT_EQUAL(fileArg.values(0).size(), static_cast<vector<const char *>::size_type>(1));
294  CPPUNIT_ASSERT(!strcmp(fileArg.values(0).front(), "test"));
295 
296  // specifying value directly after abbreviation
297  const char *argv12[] = { "tageditor", "-iftest" };
298  parser.resetArgs();
299  parser.parseArgs(2, argv12);
300  CPPUNIT_ASSERT(!filesArg.isPresent());
301  CPPUNIT_ASSERT(fileArg.isPresent());
302  CPPUNIT_ASSERT_EQUAL(fileArg.values(0).size(), static_cast<vector<const char *>::size_type>(1));
303  CPPUNIT_ASSERT(!strcmp(fileArg.values(0).front(), "test"));
304 
305  // default argument
306  const char *argv8[] = { "tageditor" };
307  parser.resetArgs();
308  parser.parseArgs(1, argv8);
309  CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
310  CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
311  CPPUNIT_ASSERT(!verboseArg.isPresent());
312  CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
313  CPPUNIT_ASSERT(!filesArg.isPresent());
314  CPPUNIT_ASSERT(!fileArg.isPresent());
315  if (getenv("PATH")) {
316  CPPUNIT_ASSERT(fileArg.firstValue());
317  CPPUNIT_ASSERT(!strcmp(fileArg.firstValue(), getenv("PATH")));
318  } else {
319  CPPUNIT_ASSERT(!fileArg.firstValue());
320  }
321 
322  // constraint checking: required value count with sufficient number of provided parameters
323  const char *argv13[] = { "tageditor", "get", "--fields", "album=test", "title", "diskpos", "--files", "somefile" };
324  verboseArg.setRequired(false);
325  parser.resetArgs();
326  parser.parseArgs(8, argv13);
327  // this should still work without complaints
328  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
329  CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
330  CPPUNIT_ASSERT(!verboseArg.isPresent());
331  CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
332  CPPUNIT_ASSERT(fieldsArg.isPresent());
333  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album=test"));
334  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "title"));
335  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(2), "diskpos"));
336  CPPUNIT_ASSERT_THROW(fieldsArg.values().at(3), out_of_range);
337  CPPUNIT_ASSERT(filesArg.isPresent());
338  CPPUNIT_ASSERT(!strcmp(filesArg.values().at(0), "somefile"));
339  CPPUNIT_ASSERT(!notAlbumArg.isPresent());
340 
341  // constraint checking: required value count with insufficient number of provided parameters
342  const char *argv9[] = { "tageditor", "-p", "album", "title", "diskpos" };
343  fieldsArg.setRequiredValueCount(4);
344  parser.resetArgs();
345  try {
346  parser.parseArgs(5, argv9);
347  CPPUNIT_FAIL("Exception expected.");
348  } catch (const Failure &e) {
349  CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
350  CPPUNIT_ASSERT(!strcmp(e.what(),
351  "Not all parameter for argument \"fields\" provided. You have to provide the following parameter: title album artist trackpos"));
352  }
353 
354  // nested operations
355  const char *argv14[] = { "tageditor", "get", "fields", "album=test", "-f", "somefile" };
356  parser.resetArgs();
357  fieldsArg.setRequiredValueCount(Argument::varValueCount);
358  fieldsArg.setDenotesOperation(true);
359  parser.parseArgs(6, argv14);
360  CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
361  CPPUNIT_ASSERT(fieldsArg.isPresent());
362  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album=test"));
363 
364  // implicit flag still works when argument doesn't denote operation
365  parser.resetArgs();
366  fieldsArg.setDenotesOperation(false);
367  parser.parseArgs(6, argv14);
368  CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
369  CPPUNIT_ASSERT(fieldsArg.isPresent());
370  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "fields"));
371  CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "album=test"));
372 }
373 
378 {
379  ArgumentParser parser;
380  Argument callbackArg("with-callback", 't', "callback test");
381  callbackArg.setRequiredValueCount(2);
382  callbackArg.setCallback([](const ArgumentOccurrence &occurrence) {
383  CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), occurrence.index);
384  CPPUNIT_ASSERT(occurrence.path.empty());
385  CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), occurrence.values.size());
386  CPPUNIT_ASSERT(!strcmp(occurrence.values[0], "val1"));
387  CPPUNIT_ASSERT(!strcmp(occurrence.values[1], "val2"));
388  throw 42;
389  });
390  Argument noCallbackArg("no-callback", 'l', "callback test");
391  noCallbackArg.setRequiredValueCount(2);
392  parser.setMainArguments({ &callbackArg, &noCallbackArg });
393 
394  // test whether callback is invoked when argument with callback is specified
395  const char *argv[] = { "test", "-t", "val1", "val2" };
396  try {
397  parser.parseArgs(4, argv);
398  } catch (int i) {
399  CPPUNIT_ASSERT_EQUAL(i, 42);
400  }
401 
402  // test whether callback is not invoked when argument with callback is not specified
403  callbackArg.reset();
404  const char *argv2[] = { "test", "-l", "val1", "val2" };
405  parser.parseArgs(4, argv2);
406 }
407 
411 static bool exitCalled = false;
412 
418 {
419  ArgumentParser parser;
420  HelpArgument helpArg(parser);
421  Argument verboseArg("verbose", 'v', "be verbose");
422  verboseArg.setCombinable(true);
423  Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
424  filesArg.setRequiredValueCount(Argument::varValueCount);
425  filesArg.setCombinable(true);
426  Argument nestedSubArg("nested-sub", '\0', "nested sub arg");
427  Argument subArg("sub", '\0', "sub arg");
428  subArg.setSubArguments({ &nestedSubArg });
429  Argument displayFileInfoArg("display-file-info", 'i', "displays general file information");
430  displayFileInfoArg.setDenotesOperation(true);
431  displayFileInfoArg.setSubArguments({ &filesArg, &verboseArg, &subArg });
432  Argument fieldsArg("fields", '\0', "specifies the fields");
433  fieldsArg.setRequiredValueCount(Argument::varValueCount);
434  fieldsArg.setPreDefinedCompletionValues("title album artist trackpos");
435  fieldsArg.setImplicit(true);
436  Argument valuesArg("values", '\0', "specifies the fields");
437  valuesArg.setRequiredValueCount(Argument::varValueCount);
438  valuesArg.setPreDefinedCompletionValues("title album artist trackpos");
439  valuesArg.setImplicit(false);
440  valuesArg.setValueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues | ValueCompletionBehavior::AppendEquationSign);
441  Argument getArg("get", 'g', "gets tag values");
442  getArg.setSubArguments({ &fieldsArg, &filesArg });
443  Argument setArg("set", 's', "sets tag values");
444  setArg.setSubArguments({ &valuesArg, &filesArg });
445 
446  parser.setMainArguments({ &helpArg, &displayFileInfoArg, &getArg, &setArg });
447 
448  // fail due to operation flags not set
449  const char *const argv1[] = { "se" };
450  ArgumentReader reader(parser, argv1, argv1 + 1, true);
451  {
452  const OutputCheck c("COMPREPLY=()\n");
453  reader.read();
454  parser.printBashCompletion(1, argv1, 0, reader);
455  }
456 
457  // correct operation arg flags
458  getArg.setDenotesOperation(true);
459  setArg.setDenotesOperation(true);
460  {
461  const OutputCheck c("COMPREPLY=('set' )\n");
462  reader.reset(argv1, argv1 + 1).read();
463  parser.printBashCompletion(1, argv1, 0, reader);
464  }
465 
466  // argument at current cursor position already specified -> the completion should just return the argument
467  const char *const argv2[] = { "set" };
468  parser.resetArgs();
469  {
470  const OutputCheck c("COMPREPLY=('set' )\n");
471  reader.reset(argv2, argv2 + 1).read();
472  parser.printBashCompletion(1, argv2, 0, reader);
473  }
474 
475  // advance the cursor position -> the completion should propose the next argument
476  parser.resetArgs();
477  {
478  const OutputCheck c("COMPREPLY=('--files' '--values' )\n");
479  reader.reset(argv2, argv2 + 1).read();
480  parser.printBashCompletion(1, argv2, 1, reader);
481  }
482 
483  // nested operations should be proposed as operations
484  parser.resetArgs();
485  filesArg.setDenotesOperation(true);
486  {
487  const OutputCheck c("COMPREPLY=('files' '--values' )\n");
488  reader.reset(argv2, argv2 + 1).read();
489  parser.printBashCompletion(1, argv2, 1, reader);
490  }
491 
492  // specifying no args should propose all main arguments
493  parser.resetArgs();
494  filesArg.setDenotesOperation(false);
495  {
496  const OutputCheck c("COMPREPLY=('display-file-info' 'get' 'set' '--help' )\n");
497  reader.reset(nullptr, nullptr).read();
498  parser.printBashCompletion(0, nullptr, 0, reader);
499  }
500 
501  // pre-defined values
502  const char *const argv3[] = { "get", "--fields" };
503  parser.resetArgs();
504  {
505  const OutputCheck c("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--files' )\n");
506  reader.reset(argv3, argv3 + 2).read();
507  parser.printBashCompletion(2, argv3, 2, reader);
508  }
509 
510  // pre-defined values with equation sign, one letter already present
511  const char *const argv4[] = { "set", "--values", "a" };
512  parser.resetArgs();
513  {
514  const OutputCheck c("COMPREPLY=('album=' 'artist=' ); compopt -o nospace\n");
515  reader.reset(argv4, argv4 + 3).read();
516  parser.printBashCompletion(3, argv4, 2, reader);
517  }
518 
519  // pre-defined values for implicit argument
520  parser.resetArgs();
521  {
522  const OutputCheck c("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--fields' '--files' )\n");
523  reader.reset(argv3, argv3 + 1).read();
524  parser.printBashCompletion(1, argv3, 2, reader);
525  }
526 
527  // file names
528  string iniFilePath = TestUtilities::testFilePath("test.ini");
529  iniFilePath.resize(iniFilePath.size() - 4);
530  string mkvFilePath = TestUtilities::testFilePath("test 'with quote'.mkv");
531  mkvFilePath.resize(mkvFilePath.size() - 17);
532  parser.resetArgs();
533  const char *const argv5[] = { "get", "--files", iniFilePath.c_str() };
534  {
535  // order for file names is not specified
536  const OutputCheck c("COMPREPLY=('" % mkvFilePath % " '\"'\"'with quote'\"'\"'.mkv' '" % iniFilePath + ".ini' ); compopt -o filenames\n",
537  "COMPREPLY=('" % iniFilePath % ".ini' '" % mkvFilePath + " '\"'\"'with quote'\"'\"'.mkv' ); compopt -o filenames\n");
538  reader.reset(argv5, argv5 + 3).read();
539  parser.printBashCompletion(3, argv5, 2, reader);
540  }
541 
542  // sub arguments
543  const char *const argv6[] = { "set", "--" };
544  parser.resetArgs();
545  {
546  const OutputCheck c("COMPREPLY=('--files' '--values' )\n");
547  reader.reset(argv6, argv6 + 2).read();
548  parser.printBashCompletion(2, argv6, 1, reader);
549  }
550 
551  // nested sub arguments
552  const char *const argv7[] = { "-i", "--sub", "--" };
553  parser.resetArgs();
554  {
555  const OutputCheck c("COMPREPLY=('--files' '--nested-sub' '--verbose' )\n");
556  reader.reset(argv7, argv7 + 3).read();
557  parser.printBashCompletion(3, argv7, 2, reader);
558  }
559 
560  // started pre-defined values with equation sign, one letter already present, last value matches
561  const char *const argv8[] = { "set", "--values", "t" };
562  parser.resetArgs();
563  {
564  const OutputCheck c("COMPREPLY=('title=' 'trackpos=' ); compopt -o nospace\n");
565  reader.reset(argv8, argv8 + 3).read();
566  parser.printBashCompletion(3, argv8, 2, reader);
567  }
568 
569  // combined abbreviations
570  const char *const argv9[] = { "-gf" };
571  parser.resetArgs();
572  {
573  const OutputCheck c("COMPREPLY=('-gf' )\n");
574  reader.reset(argv9, argv9 + 1).read();
575  parser.printBashCompletion(1, argv9, 0, reader);
576  }
577  parser.resetArgs();
578  {
579  const OutputCheck c([](const string &actualOutput) { CPPUNIT_ASSERT_EQUAL(0_st, actualOutput.find("COMPREPLY=('--fields' ")); });
580  reader.reset(argv9, argv9 + 1).read();
581  parser.printBashCompletion(1, argv9, 1, reader);
582  }
583 
584  // override exit function to prevent readArgs() from terminating the test run
585  exitFunction = [](int) { exitCalled = true; };
586 
587  // call completion via readArgs() with current word index
588  const char *const argv10[] = { "/some/path/tageditor", "--bash-completion-for", "0" };
589  parser.resetArgs();
590  {
591  const OutputCheck c("COMPREPLY=('display-file-info' 'get' 'set' '--help' )\n");
592  parser.readArgs(3, argv10);
593  }
594  CPPUNIT_ASSERT(!strcmp("/some/path/tageditor", parser.executable()));
595  CPPUNIT_ASSERT(exitCalled);
596 
597  // call completion via readArgs() without current word index
598  const char *const argv11[] = { "/some/path/tageditor", "--bash-completion-for", "ge" };
599  parser.resetArgs();
600  {
601  const OutputCheck c("COMPREPLY=('get' )\n");
602  parser.readArgs(3, argv11);
603  }
604 }
605 
610 {
611  // identation
612  Indentation indent;
613  indent = indent + 3;
614  CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(4 + 3), indent.level);
615 
616  // setup parser
617  ArgumentParser parser;
618  HelpArgument helpArg(parser);
619  helpArg.setRequired(true);
620  OperationArgument verboseArg("verbose", 'v', "be verbose", "actually not an operation");
621  verboseArg.setCombinable(true);
622  ConfigValueArgument nestedSubArg("nested-sub", '\0', "nested sub arg", { "value1", "value2" });
623  nestedSubArg.setRequiredValueCount(Argument::varValueCount);
624  Argument subArg("sub", '\0', "sub arg");
625  subArg.setRequired(true);
626  subArg.addSubArgument(&nestedSubArg);
627  Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
628  filesArg.setCombinable(true);
629  filesArg.addSubArgument(&subArg);
630  filesArg.setSubArguments({ &subArg }); // test re-assignment btw
631  Argument envArg("env", '\0', "env");
632  envArg.setEnvironmentVariable("FILES");
633  envArg.setRequiredValueCount(2);
634  envArg.appendValueName("file");
635  parser.addMainArgument(&helpArg);
636  parser.addMainArgument(&verboseArg);
637  parser.addMainArgument(&filesArg);
638  parser.addMainArgument(&envArg);
639  dependencyVersions = { "somelib", "some other lib" };
640 
641  // parse args and assert output
642  const char *const argv[] = { "app", "-h" };
643  {
644  const OutputCheck c("\e[1m" APP_NAME ", version " APP_VERSION "\n"
645  "\e[0mLinked against: somelib, some other lib\n"
646  "\n\e[0m"
647  "Available arguments:\n"
648  "\e[1m--help, -h\e[0m\n"
649  " shows this information\n"
650  " particularities: mandatory\n"
651  "\n"
652  "\e[1mverbose, -v\e[0m\n"
653  " be verbose\n"
654  " example: actually not an operation\n"
655  "\n"
656  "\e[1m--files, -f\e[0m\n"
657  " specifies the path of the file(s) to be opened\n"
658  " \e[1m--sub\e[0m\n"
659  " sub arg\n"
660  " particularities: mandatory if parent argument is present\n"
661  " \e[1m--nested-sub\e[0m [value1] [value2] ...\n"
662  " nested sub arg\n"
663  "\n"
664  "\e[1m--env\e[0m [file] [value 2]\n"
665  " env\n"
666  " default environment variable: FILES\n"
667  "\n"
668  "Project website: " APP_URL "\n");
669  parser.parseArgs(2, argv);
670  }
671 }
672 
677 {
678  ArgumentParser parser;
679  HelpArgument helpArg(parser);
680  Argument subArg("sub-arg", 's', "mandatory sub arg");
681  subArg.setRequired(true);
682  helpArg.addSubArgument(&subArg);
683  parser.addMainArgument(&helpArg);
684  parser.setMainArguments({});
685  CPPUNIT_ASSERT_MESSAGE("clear main args", parser.mainArguments().empty());
686  parser.setMainArguments({ &helpArg });
687  CPPUNIT_ASSERT_MESSAGE("no default due to required sub arg", !parser.defaultArgument());
688  subArg.setConstraints(0, 20);
689  parser.setMainArguments({ &helpArg });
690  CPPUNIT_ASSERT_MESSAGE("default if no required sub arg", &helpArg == parser.defaultArgument());
691 }
void testBashCompletion()
Tests bash completion.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
#define QT_CONFIG_ARGUMENTS
The ConfigValueArgument class is an Argument where setCombinable() is true by default.
Argument * defaultArgument() const
Returns the default argument.
void setImplicit(bool value)
Sets whether the argument is an implicit argument.
std::size_t index
The index of the occurrence.
void testParsing()
Tests parsing command line arguments.
void setCombinable(bool value)
Sets whether this argument can be combined.
void setUnknownArgumentBehavior(UnknownArgumentBehavior behavior)
Sets how unknown arguments are treated.
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
virtual const char * what() const USE_NOTHROW
Returns a C-style character string describing the cause of the Failure.
Definition: failure.cpp:44
Contains currently only ArgumentParser and related classes.
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
STL namespace.
void testArgument()
Tests the behaviour of the argument class.
void testSetMainArguments()
Tests some corner cases in setMainArguments() which are not already checked in the other tests...
bool isRequired() const
Returns an indication whether the argument is mandatory.
void testCallbacks()
Tests whether callbacks are called correctly.
void setRequired(bool required)
Sets whether this argument is mandatory or not.
Contains literals to ease asserting with CPPUNIT_ASSERT_EQUAL.
Definition: testutils.h:193
void parseArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
The Indentation class allows printing indentation conveniently, eg.
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
The ArgumentParserTests class tests the ArgumentParser and Argument classes.
The OperationArgument class is an Argument where denotesOperation() is true by default.
void setConstraints(std::size_t minOccurrences, std::size_t maxOccurrences)
Sets the allowed number of occurrences.
Contains classes and functions utilizing creating of test applications.
Definition: testutils.h:11
void setValueCompletionBehavior(ValueCompletionBehavior valueCompletionBehaviour)
Sets the items to be considered when generating completion for the values.
#define SET_APPLICATION_INFO
SET_APPLICATION_INFO
void appendValueName(const char *valueName)
Appends a value name.
CPP_UTILITIES_EXPORT std::initializer_list< const char * > dependencyVersions
Specifies the dependency versions the application was linked against (used by ArgumentParser::printHe...
const std::vector< const char * > & values(std::size_t occurrence=0) const
Returns the parameter values for the specified occurrence of argument.
Contains several functions providing conversions between different data types.
CPP_UTILITIES_EXPORT void(* exitFunction)(int)
Specifies a function quit the application.
const char * executable() const
Returns the name of the current executable.
The Argument class is a wrapper for command line argument information.
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
void setPreDefinedCompletionValues(const char *preDefinedCompletionValues)
Assignes the values to be used when generating completion for the values.
ApplicationUtilities::ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
std::vector< Argument * > path
The "path" of the occurrence (the parent elements which have been specified before).
The StandardOutputCheck class asserts whether the (standard) output written in the enclosing code blo...
Definition: outputcheck.h:19
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
void setEnvironmentVariable(const char *environmentVariable)
Sets the environment variable queried when firstValue() is called.
The ArgumentOccurrence struct holds argument values for an occurrence of an argument.
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
The HelpArgument class prints help information for an argument parser when present (–help...
CPPUNIT_TEST_SUITE_REGISTRATION(ArgumentParserTests)
The Failure class is thrown by an ArgumentParser when a parsing error occurs.
Definition: failure.h:12
const ArgumentVector & mainArguments() const
Returns the main arguments.
void setDenotesOperation(bool denotesOperation)
Sets whether the argument denotes the operation.
void reset()
Resets occurrences (indices, values and paths).
void setRequiredValueCount(std::size_t requiredValueCount)
Sets the number of values which are required to be given for this argument.
The ArgumentReader class internally encapsulates the process of reading command line arguments...
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this arguments.
void read()
Reads the commands line arguments specified when constructing the object.
CPP_UTILITIES_EXPORT std::string testFilePath(const std::string &name)
Convenience function which returns the full path of the test file with the specified name...
Definition: testutils.h:92
The ArgumentParser class provides a means for handling command line arguments.
void testHelp()
Tests –help output.
void setValueNames(std::initializer_list< const char *> valueNames)
Sets the names of the requried values.
std::vector< const char * > values
The parameter values which have been specified after the occurrence of the argument.