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