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