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