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