2017-09-20 17:09:34 +02:00
|
|
|
#include "./outputcheck.h"
|
2016-06-12 01:56:57 +02:00
|
|
|
#include "./testutils.h"
|
|
|
|
|
2017-01-27 18:51:54 +01:00
|
|
|
#include "../conversion/stringbuilder.h"
|
|
|
|
|
2016-06-12 01:56:57 +02:00
|
|
|
#include "../application/argumentparser.h"
|
2016-12-23 09:55:12 +01:00
|
|
|
#include "../application/argumentparserprivate.h"
|
2017-06-25 15:12:38 +02:00
|
|
|
#include "../application/commandlineutils.h"
|
2016-06-12 01:56:57 +02:00
|
|
|
#include "../application/failure.h"
|
|
|
|
#include "../application/fakeqtconfigarguments.h"
|
|
|
|
|
2017-10-19 00:48:05 +02:00
|
|
|
#include "../io/ansiescapecodes.h"
|
2016-07-03 22:36:48 +02:00
|
|
|
#include "../io/path.h"
|
|
|
|
|
2016-06-12 01:56:57 +02:00
|
|
|
#include "resources/config.h"
|
|
|
|
|
|
|
|
#include <cppunit/TestFixture.h>
|
2017-05-04 22:44:00 +02:00
|
|
|
#include <cppunit/extensions/HelperMacros.h>
|
2016-06-12 01:56:57 +02:00
|
|
|
|
2016-07-17 01:26:34 +02:00
|
|
|
#include <cstdlib>
|
2017-05-04 22:44:00 +02:00
|
|
|
#include <cstring>
|
2016-06-12 01:56:57 +02:00
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace ApplicationUtilities;
|
2017-01-27 18:51:54 +01:00
|
|
|
using namespace ConversionUtilities;
|
2017-09-20 17:09:34 +02:00
|
|
|
using namespace TestUtilities;
|
2017-07-28 19:27:56 +02:00
|
|
|
using namespace TestUtilities::Literals;
|
2016-06-12 01:56:57 +02:00
|
|
|
|
|
|
|
using namespace CPPUNIT_NS;
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief The ArgumentParserTests class tests the ArgumentParser and Argument classes.
|
|
|
|
*/
|
2017-05-04 22:44:00 +02:00
|
|
|
class ArgumentParserTests : public TestFixture {
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_TEST_SUITE(ArgumentParserTests);
|
|
|
|
CPPUNIT_TEST(testArgument);
|
|
|
|
CPPUNIT_TEST(testParsing);
|
|
|
|
CPPUNIT_TEST(testCallbacks);
|
2016-07-03 22:36:48 +02:00
|
|
|
CPPUNIT_TEST(testBashCompletion);
|
2017-06-25 01:19:21 +02:00
|
|
|
CPPUNIT_TEST(testHelp);
|
2017-06-25 15:12:38 +02:00
|
|
|
CPPUNIT_TEST(testSetMainArguments);
|
2018-01-29 16:23:10 +01:00
|
|
|
CPPUNIT_TEST(testNoColorArgument);
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_TEST_SUITE_END();
|
|
|
|
|
|
|
|
public:
|
|
|
|
void setUp();
|
|
|
|
void tearDown();
|
|
|
|
|
|
|
|
void testArgument();
|
|
|
|
void testParsing();
|
|
|
|
void testCallbacks();
|
2016-07-03 22:36:48 +02:00
|
|
|
void testBashCompletion();
|
2017-06-25 01:19:21 +02:00
|
|
|
void testHelp();
|
2017-06-25 15:12:38 +02:00
|
|
|
void testSetMainArguments();
|
2018-01-29 16:23:10 +01:00
|
|
|
void testNoColorArgument();
|
2016-06-12 01:56:57 +02:00
|
|
|
|
|
|
|
private:
|
|
|
|
void callback();
|
|
|
|
};
|
|
|
|
|
|
|
|
CPPUNIT_TEST_SUITE_REGISTRATION(ArgumentParserTests);
|
|
|
|
|
|
|
|
void ArgumentParserTests::setUp()
|
2017-05-04 22:44:00 +02:00
|
|
|
{
|
|
|
|
}
|
2016-06-12 01:56:57 +02:00
|
|
|
|
|
|
|
void ArgumentParserTests::tearDown()
|
2017-05-04 22:44:00 +02:00
|
|
|
{
|
|
|
|
}
|
2016-06-12 01:56:57 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Tests the behaviour of the argument class.
|
|
|
|
*/
|
|
|
|
void ArgumentParserTests::testArgument()
|
|
|
|
{
|
|
|
|
Argument argument("test", 't', "some description");
|
|
|
|
CPPUNIT_ASSERT_EQUAL(argument.isRequired(), false);
|
|
|
|
argument.setConstraints(1, 10);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(argument.isRequired(), true);
|
|
|
|
Argument subArg("sub", 's', "sub arg");
|
|
|
|
argument.addSubArgument(&subArg);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(subArg.parents().at(0), &argument);
|
|
|
|
CPPUNIT_ASSERT(!subArg.conflictsWithArgument());
|
2016-07-17 01:26:34 +02:00
|
|
|
CPPUNIT_ASSERT(!argument.firstValue());
|
2017-06-25 15:12:38 +02:00
|
|
|
argument.setEnvironmentVariable("FOO_ENV_VAR");
|
|
|
|
setenv("FOO_ENV_VAR", "foo", true);
|
|
|
|
CPPUNIT_ASSERT(!strcmp(argument.firstValue(), "foo"));
|
|
|
|
ArgumentOccurrence occurrence(0, vector<Argument *>(), nullptr);
|
|
|
|
occurrence.values.emplace_back("bar");
|
|
|
|
argument.m_occurrences.emplace_back(move(occurrence));
|
|
|
|
CPPUNIT_ASSERT(!strcmp(argument.firstValue(), "bar"));
|
2016-06-12 01:56:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Tests parsing command line arguments.
|
|
|
|
*/
|
|
|
|
void ArgumentParserTests::testParsing()
|
|
|
|
{
|
2016-06-14 00:43:32 +02:00
|
|
|
// setup parser with some test argument definitions
|
2016-06-12 01:56:57 +02:00
|
|
|
ArgumentParser parser;
|
|
|
|
SET_APPLICATION_INFO;
|
|
|
|
QT_CONFIG_ARGUMENTS qtConfigArgs;
|
|
|
|
HelpArgument helpArg(parser);
|
|
|
|
Argument verboseArg("verbose", 'v', "be verbose");
|
|
|
|
verboseArg.setCombinable(true);
|
|
|
|
Argument fileArg("file", 'f', "specifies the path of the file to be opened");
|
2017-05-04 22:44:00 +02:00
|
|
|
fileArg.setValueNames({ "path" });
|
2016-06-12 01:56:57 +02:00
|
|
|
fileArg.setRequiredValueCount(1);
|
2016-07-17 01:26:34 +02:00
|
|
|
fileArg.setEnvironmentVariable("PATH");
|
2016-06-12 01:56:57 +02:00
|
|
|
Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
|
2017-05-04 22:44:00 +02:00
|
|
|
filesArg.setValueNames({ "path 1", "path 2" });
|
2017-09-20 17:07:06 +02:00
|
|
|
filesArg.setRequiredValueCount(Argument::varValueCount);
|
2016-06-12 01:56:57 +02:00
|
|
|
Argument outputFileArg("output-file", 'o', "specifies the path of the output file");
|
2017-05-04 22:44:00 +02:00
|
|
|
outputFileArg.setValueNames({ "path" });
|
2016-06-12 01:56:57 +02:00
|
|
|
outputFileArg.setRequiredValueCount(1);
|
|
|
|
outputFileArg.setRequired(true);
|
|
|
|
outputFileArg.setCombinable(true);
|
|
|
|
Argument printFieldNamesArg("print-field-names", '\0', "prints available field names");
|
|
|
|
Argument displayFileInfoArg("display-file-info", 'i', "displays general file information");
|
2016-11-15 22:02:40 +01:00
|
|
|
Argument notAlbumArg("album", 'a', "should not be confused with album value");
|
2016-06-12 01:56:57 +02:00
|
|
|
displayFileInfoArg.setDenotesOperation(true);
|
2017-05-04 22:44:00 +02:00
|
|
|
displayFileInfoArg.setSubArguments({ &fileArg, &verboseArg, ¬AlbumArg });
|
2016-06-12 01:56:57 +02:00
|
|
|
Argument fieldsArg("fields", '\0', "specifies the fields");
|
2017-09-20 17:07:06 +02:00
|
|
|
fieldsArg.setRequiredValueCount(Argument::varValueCount);
|
2017-05-04 22:44:00 +02:00
|
|
|
fieldsArg.setValueNames({ "title", "album", "artist", "trackpos" });
|
2016-06-14 00:43:32 +02:00
|
|
|
fieldsArg.setImplicit(true);
|
2016-06-12 01:56:57 +02:00
|
|
|
Argument displayTagInfoArg("get", 'p', "displays the values of all specified tag fields (displays all fields if none specified)");
|
|
|
|
displayTagInfoArg.setDenotesOperation(true);
|
2017-05-04 22:44:00 +02:00
|
|
|
displayTagInfoArg.setSubArguments({ &fieldsArg, &filesArg, &verboseArg, ¬AlbumArg });
|
2017-11-29 22:52:50 +01:00
|
|
|
NoColorArgument noColorArg;
|
|
|
|
parser.setMainArguments({ &qtConfigArgs.qtWidgetsGuiArg(), &printFieldNamesArg, &displayTagInfoArg, &displayFileInfoArg, &helpArg, &noColorArg });
|
2016-06-12 01:56:57 +02:00
|
|
|
|
2018-01-29 16:23:10 +01:00
|
|
|
// no args present
|
|
|
|
parser.parseArgs(0, nullptr);
|
|
|
|
CPPUNIT_ASSERT(!parser.executable());
|
|
|
|
CPPUNIT_ASSERT(!parser.specifiedOperation());
|
|
|
|
CPPUNIT_ASSERT_EQUAL(0u, parser.actualArgumentCount());
|
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// error about uncombinable arguments
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv[] = { "tageditor", "get", "album", "title", "diskpos", "-f", "somefile" };
|
2016-06-12 01:56:57 +02:00
|
|
|
// try to parse, this should fail
|
|
|
|
try {
|
|
|
|
parser.parseArgs(7, argv);
|
|
|
|
CPPUNIT_FAIL("Exception expected.");
|
2017-05-04 22:44:00 +02:00
|
|
|
} catch (const Failure &e) {
|
2018-01-29 16:23:10 +01:00
|
|
|
CPPUNIT_ASSERT_EQUAL("The argument \"files\" can not be combined with \"fields\"."s, string(e.what()));
|
|
|
|
// test printing btw
|
|
|
|
stringstream ss;
|
|
|
|
ss << e;
|
|
|
|
CPPUNIT_ASSERT_EQUAL(
|
|
|
|
"Error: Unable to parse arguments: The argument \"files\" can not be combined with \"fields\".\nSee --help for available commands.\n"s,
|
|
|
|
ss.str());
|
2016-06-12 01:56:57 +02:00
|
|
|
}
|
2018-01-29 16:23:10 +01:00
|
|
|
CPPUNIT_ASSERT(parser.isUncombinableMainArgPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// arguments read correctly after successful parse
|
2016-06-12 01:56:57 +02:00
|
|
|
filesArg.setCombinable(true);
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-06-12 01:56:57 +02:00
|
|
|
parser.parseArgs(7, argv);
|
|
|
|
// check results
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!strcmp(parser.executable(), "tageditor"));
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT(!verboseArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fieldsArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album"));
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "title"));
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(2), "diskpos"));
|
|
|
|
CPPUNIT_ASSERT_THROW(displayTagInfoArg.values().at(3), out_of_range);
|
2018-01-29 16:23:10 +01:00
|
|
|
CPPUNIT_ASSERT_EQUAL(&displayTagInfoArg, parser.specifiedOperation());
|
2016-06-12 01:56:57 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// skip empty args
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv2[] = { "tageditor", "", "-p", "album", "title", "diskpos", "", "--files", "somefile" };
|
2016-06-12 01:56:57 +02:00
|
|
|
// reparse the args
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-06-23 22:06:59 +02:00
|
|
|
parser.parseArgs(9, argv2);
|
2016-06-12 01:56:57 +02:00
|
|
|
// check results again
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!verboseArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fieldsArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album"));
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "title"));
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(2), "diskpos"));
|
2016-06-23 22:06:59 +02:00
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(3), ""));
|
|
|
|
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(4), out_of_range);
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT(filesArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!strcmp(filesArg.values().at(0), "somefile"));
|
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// error about unknown argument: forget get/-p
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv3[] = { "tageditor", "album", "title", "diskpos", "--files", "somefile" };
|
2016-06-12 01:56:57 +02:00
|
|
|
try {
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-06-12 01:56:57 +02:00
|
|
|
parser.parseArgs(6, argv3);
|
|
|
|
CPPUNIT_FAIL("Exception expected.");
|
2017-05-04 22:44:00 +02:00
|
|
|
} catch (const Failure &e) {
|
2017-04-05 23:45:23 +02:00
|
|
|
CPPUNIT_ASSERT_EQUAL("The specified argument \"album\" is unknown."s, string(e.what()));
|
2016-06-12 01:56:57 +02:00
|
|
|
}
|
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// warning about unknown argument
|
2016-07-03 22:36:48 +02:00
|
|
|
parser.setUnknownArgumentBehavior(UnknownArgumentBehavior::Warn);
|
2016-06-14 00:43:32 +02:00
|
|
|
// redirect stderr to check whether warnings are printed correctly
|
|
|
|
stringstream buffer;
|
|
|
|
streambuf *regularCerrBuffer = cerr.rdbuf(buffer.rdbuf());
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2017-10-19 00:48:05 +02:00
|
|
|
EscapeCodes::enabled = false;
|
2016-06-14 00:43:32 +02:00
|
|
|
try {
|
|
|
|
parser.parseArgs(6, argv3);
|
2017-05-04 22:44:00 +02:00
|
|
|
} catch (...) {
|
2016-06-14 00:43:32 +02:00
|
|
|
cerr.rdbuf(regularCerrBuffer);
|
|
|
|
throw;
|
|
|
|
}
|
2016-12-07 21:06:21 +01:00
|
|
|
cerr.rdbuf(regularCerrBuffer);
|
2017-10-19 00:48:05 +02:00
|
|
|
CPPUNIT_ASSERT_EQUAL("Warning: The specified argument \"album\" is unknown and will be ignored.\n"s
|
|
|
|
"Warning: The specified argument \"title\" is unknown and will be ignored.\n"s
|
|
|
|
"Warning: The specified argument \"diskpos\" is unknown and will be ignored.\n"s
|
|
|
|
"Warning: The specified argument \"--files\" is unknown and will be ignored.\n"s
|
|
|
|
"Warning: The specified argument \"somefile\" is unknown and will be ignored.\n"s,
|
2017-05-04 22:44:00 +02:00
|
|
|
buffer.str());
|
2016-06-12 01:56:57 +02:00
|
|
|
// none of the arguments should be present now
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!fieldsArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!filesArg.isPresent());
|
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// combined abbreviations like "-vf"
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv4[] = { "tageditor", "-i", "-vf", "test" };
|
2016-07-03 22:36:48 +02:00
|
|
|
parser.setUnknownArgumentBehavior(UnknownArgumentBehavior::Fail);
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-06-12 01:56:57 +02:00
|
|
|
parser.parseArgs(4, argv4);
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT(displayFileInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(verboseArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!filesArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fileArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fileArg.values().at(0), "test"));
|
|
|
|
CPPUNIT_ASSERT_THROW(fileArg.values().at(1), out_of_range);
|
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// constraint checking: no multiple occurrences (not resetting verboseArg on purpose)
|
2016-06-12 01:56:57 +02:00
|
|
|
displayFileInfoArg.reset(), fileArg.reset();
|
|
|
|
try {
|
|
|
|
parser.parseArgs(4, argv4);
|
|
|
|
CPPUNIT_FAIL("Exception expected.");
|
2017-05-04 22:44:00 +02:00
|
|
|
} catch (const Failure &e) {
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" mustn't be specified more than 1 time."));
|
|
|
|
}
|
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// constraint checking: no contraint (not resetting verboseArg on purpose)
|
|
|
|
displayFileInfoArg.reset(), fileArg.reset();
|
2017-09-20 17:07:06 +02:00
|
|
|
verboseArg.setConstraints(0, Argument::varValueCount);
|
2016-06-12 01:56:57 +02:00
|
|
|
parser.parseArgs(4, argv4);
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// constraint checking: mandatory argument
|
2016-06-12 01:56:57 +02:00
|
|
|
verboseArg.setRequired(true);
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-06-12 01:56:57 +02:00
|
|
|
parser.parseArgs(4, argv4);
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// contraint checking: error about missing mandatory argument
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv5[] = { "tageditor", "-i", "-f", "test" };
|
2016-06-12 01:56:57 +02:00
|
|
|
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
|
|
|
|
try {
|
|
|
|
parser.parseArgs(4, argv5);
|
|
|
|
CPPUNIT_FAIL("Exception expected.");
|
2017-05-04 22:44:00 +02:00
|
|
|
} catch (const Failure &e) {
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" must be specified at least 1 time."));
|
|
|
|
}
|
2016-12-07 21:06:21 +01:00
|
|
|
verboseArg.setRequired(false);
|
2016-06-12 01:56:57 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// combined abbreviation with nesting "-pf"
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv10[] = { "tageditor", "-pf", "test" };
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-10-29 23:54:30 +02:00
|
|
|
parser.parseArgs(3, argv10);
|
|
|
|
CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!fileArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(filesArg.isPresent());
|
2017-11-29 20:29:51 +01:00
|
|
|
CPPUNIT_ASSERT_EQUAL(1_st, filesArg.values(0).size());
|
2016-10-29 23:54:30 +02:00
|
|
|
CPPUNIT_ASSERT(!strcmp(filesArg.values(0).front(), "test"));
|
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// constraint checking: no complains about missing -i
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv6[] = { "tageditor", "-g" };
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-06-12 01:56:57 +02:00
|
|
|
parser.parseArgs(2, argv6);
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2016-06-12 01:56:57 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// constraint checking: dependend arguments (-f requires -i or -p)
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv7[] = { "tageditor", "-f", "test" };
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-06-12 01:56:57 +02:00
|
|
|
try {
|
|
|
|
parser.parseArgs(3, argv7);
|
|
|
|
CPPUNIT_FAIL("Exception expected.");
|
2017-05-04 22:44:00 +02:00
|
|
|
} catch (const Failure &e) {
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2017-04-05 23:45:23 +02:00
|
|
|
CPPUNIT_ASSERT_EQUAL("The specified argument \"-f\" is unknown."s, string(e.what()));
|
2016-06-12 01:56:57 +02:00
|
|
|
}
|
2016-06-14 00:43:32 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// equation sign syntax
|
2017-11-29 20:29:51 +01:00
|
|
|
const char *argv11[] = { "tageditor", "-if=test-v" };
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-10-29 23:54:30 +02:00
|
|
|
parser.parseArgs(2, argv11);
|
|
|
|
CPPUNIT_ASSERT(!filesArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fileArg.isPresent());
|
2017-11-29 20:29:51 +01:00
|
|
|
CPPUNIT_ASSERT(!verboseArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
|
|
|
|
CPPUNIT_ASSERT_EQUAL("test-v"s, string(fileArg.values(0).front()));
|
|
|
|
const char *argv15[] = { "tageditor", "-i", "--file=test", "-v" };
|
|
|
|
parser.resetArgs();
|
|
|
|
parser.parseArgs(4, argv15);
|
|
|
|
CPPUNIT_ASSERT(!filesArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fileArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(verboseArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
|
|
|
|
CPPUNIT_ASSERT_EQUAL("test"s, string(fileArg.values(0).front()));
|
2016-10-29 23:54:30 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// specifying value directly after abbreviation
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv12[] = { "tageditor", "-iftest" };
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-10-29 23:54:30 +02:00
|
|
|
parser.parseArgs(2, argv12);
|
|
|
|
CPPUNIT_ASSERT(!filesArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fileArg.isPresent());
|
2017-11-29 20:29:51 +01:00
|
|
|
CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
|
2016-10-29 23:54:30 +02:00
|
|
|
CPPUNIT_ASSERT(!strcmp(fileArg.values(0).front(), "test"));
|
|
|
|
|
2017-11-29 22:52:50 +01:00
|
|
|
// specifying top-level argument after abbreviation
|
|
|
|
const char *argv17[] = { "tageditor", "-if=test-v", "--no-color" };
|
|
|
|
parser.resetArgs();
|
|
|
|
parser.parseArgs(3, argv17);
|
|
|
|
CPPUNIT_ASSERT(!filesArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fileArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!verboseArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(noColorArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
|
|
|
|
CPPUNIT_ASSERT_EQUAL("test-v"s, string(fileArg.values(0).front()));
|
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// default argument
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv8[] = { "tageditor" };
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-06-14 00:43:32 +02:00
|
|
|
parser.parseArgs(1, argv8);
|
|
|
|
CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
|
|
|
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!verboseArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!filesArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!fileArg.isPresent());
|
2017-05-04 22:44:00 +02:00
|
|
|
if (getenv("PATH")) {
|
2016-07-17 01:26:34 +02:00
|
|
|
CPPUNIT_ASSERT(fileArg.firstValue());
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fileArg.firstValue(), getenv("PATH")));
|
|
|
|
} else {
|
|
|
|
CPPUNIT_ASSERT(!fileArg.firstValue());
|
|
|
|
}
|
2016-06-14 00:43:32 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// constraint checking: required value count with sufficient number of provided parameters
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv13[] = { "tageditor", "get", "--fields", "album=test", "title", "diskpos", "--files", "somefile" };
|
2016-06-14 00:43:32 +02:00
|
|
|
verboseArg.setRequired(false);
|
2016-12-07 21:06:21 +01:00
|
|
|
parser.resetArgs();
|
2016-11-14 22:38:21 +01:00
|
|
|
parser.parseArgs(8, argv13);
|
2016-06-14 00:43:32 +02:00
|
|
|
// this should still work without complaints
|
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
|
|
|
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!verboseArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fieldsArg.isPresent());
|
2016-11-14 22:38:21 +01:00
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album=test"));
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "title"));
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(2), "diskpos"));
|
2016-06-23 22:06:59 +02:00
|
|
|
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(3), out_of_range);
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(filesArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!strcmp(filesArg.values().at(0), "somefile"));
|
2016-11-14 22:38:21 +01:00
|
|
|
CPPUNIT_ASSERT(!notAlbumArg.isPresent());
|
2016-06-14 00:43:32 +02:00
|
|
|
|
2016-12-07 21:06:21 +01:00
|
|
|
// constraint checking: required value count with insufficient number of provided parameters
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv9[] = { "tageditor", "-p", "album", "title", "diskpos" };
|
2016-12-07 21:06:21 +01:00
|
|
|
fieldsArg.setRequiredValueCount(4);
|
|
|
|
parser.resetArgs();
|
2016-06-14 00:43:32 +02:00
|
|
|
try {
|
|
|
|
parser.parseArgs(5, argv9);
|
|
|
|
CPPUNIT_FAIL("Exception expected.");
|
2017-05-04 22:44:00 +02:00
|
|
|
} catch (const Failure &e) {
|
2016-06-14 00:43:32 +02:00
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
2017-11-29 20:29:51 +01:00
|
|
|
CPPUNIT_ASSERT_EQUAL(
|
|
|
|
"Not all parameter for argument \"fields\" provided. You have to provide the following parameter: title album artist trackpos"s,
|
|
|
|
string(e.what()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// constraint checking: truncated argument not wrongly detected
|
|
|
|
const char *argv16[] = { "tageditor", "--hel", "-p", "album", "title", "diskpos" };
|
|
|
|
fieldsArg.setRequiredValueCount(Argument::varValueCount);
|
|
|
|
parser.resetArgs();
|
|
|
|
try {
|
|
|
|
parser.parseArgs(6, argv16);
|
|
|
|
CPPUNIT_FAIL("Exception expected.");
|
|
|
|
} catch (const Failure &e) {
|
|
|
|
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
|
|
|
|
CPPUNIT_ASSERT_EQUAL("The specified argument \"--hel\" is unknown."s, string(e.what()));
|
2016-06-14 00:43:32 +02:00
|
|
|
}
|
2017-04-06 00:01:06 +02:00
|
|
|
|
|
|
|
// nested operations
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv14[] = { "tageditor", "get", "fields", "album=test", "-f", "somefile" };
|
2017-04-06 00:01:06 +02:00
|
|
|
parser.resetArgs();
|
|
|
|
fieldsArg.setDenotesOperation(true);
|
|
|
|
parser.parseArgs(6, argv14);
|
|
|
|
CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fieldsArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album=test"));
|
|
|
|
|
|
|
|
// implicit flag still works when argument doesn't denote operation
|
|
|
|
parser.resetArgs();
|
|
|
|
fieldsArg.setDenotesOperation(false);
|
|
|
|
parser.parseArgs(6, argv14);
|
|
|
|
CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(fieldsArg.isPresent());
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "fields"));
|
|
|
|
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "album=test"));
|
2016-06-12 01:56:57 +02:00
|
|
|
}
|
|
|
|
|
2016-07-03 22:36:48 +02:00
|
|
|
/*!
|
|
|
|
* \brief Tests whether callbacks are called correctly.
|
|
|
|
*/
|
2016-06-12 01:56:57 +02:00
|
|
|
void ArgumentParserTests::testCallbacks()
|
|
|
|
{
|
|
|
|
ArgumentParser parser;
|
|
|
|
Argument callbackArg("with-callback", 't', "callback test");
|
|
|
|
callbackArg.setRequiredValueCount(2);
|
2017-05-04 22:44:00 +02:00
|
|
|
callbackArg.setCallback([](const ArgumentOccurrence &occurrence) {
|
2017-11-29 20:29:51 +01:00
|
|
|
CPPUNIT_ASSERT_EQUAL(0_st, occurrence.index);
|
2016-07-31 23:20:31 +02:00
|
|
|
CPPUNIT_ASSERT(occurrence.path.empty());
|
2017-11-29 20:29:51 +01:00
|
|
|
CPPUNIT_ASSERT_EQUAL(2_st, occurrence.values.size());
|
2016-07-31 23:20:31 +02:00
|
|
|
CPPUNIT_ASSERT(!strcmp(occurrence.values[0], "val1"));
|
|
|
|
CPPUNIT_ASSERT(!strcmp(occurrence.values[1], "val2"));
|
2016-06-12 01:56:57 +02:00
|
|
|
throw 42;
|
|
|
|
});
|
|
|
|
Argument noCallbackArg("no-callback", 'l', "callback test");
|
|
|
|
noCallbackArg.setRequiredValueCount(2);
|
2017-05-04 22:44:00 +02:00
|
|
|
parser.setMainArguments({ &callbackArg, &noCallbackArg });
|
2016-06-12 01:56:57 +02:00
|
|
|
|
|
|
|
// test whether callback is invoked when argument with callback is specified
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv[] = { "test", "-t", "val1", "val2" };
|
2016-06-12 01:56:57 +02:00
|
|
|
try {
|
|
|
|
parser.parseArgs(4, argv);
|
2017-05-04 22:44:00 +02:00
|
|
|
} catch (int i) {
|
2016-06-12 01:56:57 +02:00
|
|
|
CPPUNIT_ASSERT_EQUAL(i, 42);
|
|
|
|
}
|
|
|
|
|
|
|
|
// test whether callback is not invoked when argument with callback is not specified
|
|
|
|
callbackArg.reset();
|
2017-05-04 22:44:00 +02:00
|
|
|
const char *argv2[] = { "test", "-l", "val1", "val2" };
|
2016-06-12 01:56:57 +02:00
|
|
|
parser.parseArgs(4, argv2);
|
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
/*!
|
|
|
|
* \brief Used to check whether the exit() function is called when printing bash completion.
|
|
|
|
*/
|
|
|
|
static bool exitCalled = false;
|
|
|
|
|
2016-07-03 22:36:48 +02:00
|
|
|
/*!
|
|
|
|
* \brief Tests bash completion.
|
2017-07-28 19:27:56 +02:00
|
|
|
* \remarks This tests makes assumptions about the order and the exact output format.
|
2016-07-03 22:36:48 +02:00
|
|
|
*/
|
|
|
|
void ArgumentParserTests::testBashCompletion()
|
|
|
|
{
|
|
|
|
ArgumentParser parser;
|
|
|
|
HelpArgument helpArg(parser);
|
|
|
|
Argument verboseArg("verbose", 'v', "be verbose");
|
|
|
|
verboseArg.setCombinable(true);
|
|
|
|
Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
|
2017-07-28 19:27:56 +02:00
|
|
|
filesArg.setRequiredValueCount(Argument::varValueCount);
|
2016-07-03 22:36:48 +02:00
|
|
|
filesArg.setCombinable(true);
|
|
|
|
Argument nestedSubArg("nested-sub", '\0', "nested sub arg");
|
|
|
|
Argument subArg("sub", '\0', "sub arg");
|
2017-05-04 22:44:00 +02:00
|
|
|
subArg.setSubArguments({ &nestedSubArg });
|
2016-07-03 22:36:48 +02:00
|
|
|
Argument displayFileInfoArg("display-file-info", 'i', "displays general file information");
|
|
|
|
displayFileInfoArg.setDenotesOperation(true);
|
2017-05-04 22:44:00 +02:00
|
|
|
displayFileInfoArg.setSubArguments({ &filesArg, &verboseArg, &subArg });
|
2016-07-03 22:36:48 +02:00
|
|
|
Argument fieldsArg("fields", '\0', "specifies the fields");
|
2017-07-28 19:27:56 +02:00
|
|
|
fieldsArg.setRequiredValueCount(Argument::varValueCount);
|
2016-07-03 22:36:48 +02:00
|
|
|
fieldsArg.setPreDefinedCompletionValues("title album artist trackpos");
|
|
|
|
fieldsArg.setImplicit(true);
|
|
|
|
Argument valuesArg("values", '\0', "specifies the fields");
|
2017-07-28 19:27:56 +02:00
|
|
|
valuesArg.setRequiredValueCount(Argument::varValueCount);
|
2016-07-03 22:36:48 +02:00
|
|
|
valuesArg.setPreDefinedCompletionValues("title album artist trackpos");
|
2017-07-28 17:32:07 +02:00
|
|
|
valuesArg.setImplicit(false);
|
2016-07-03 22:36:48 +02:00
|
|
|
valuesArg.setValueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues | ValueCompletionBehavior::AppendEquationSign);
|
2018-01-29 16:23:10 +01:00
|
|
|
Argument selectorsArg("selectors", '\0', "has some more pre-defined values");
|
|
|
|
selectorsArg.setRequiredValueCount(Argument::varValueCount);
|
|
|
|
selectorsArg.setPreDefinedCompletionValues("tag=id3v1 tag=id3v2 tag=matroska target=file target=track");
|
|
|
|
selectorsArg.setImplicit(false);
|
|
|
|
selectorsArg.setValueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues);
|
|
|
|
selectorsArg.setCallback(
|
|
|
|
[&selectorsArg](const ArgumentOccurrence &) { selectorsArg.setPreDefinedCompletionValues("tag=matroska tag=mp4 tag=vorbis"); });
|
2016-07-03 22:36:48 +02:00
|
|
|
Argument getArg("get", 'g', "gets tag values");
|
2017-05-04 22:44:00 +02:00
|
|
|
getArg.setSubArguments({ &fieldsArg, &filesArg });
|
2016-07-03 22:36:48 +02:00
|
|
|
Argument setArg("set", 's', "sets tag values");
|
2018-01-29 16:23:10 +01:00
|
|
|
setArg.setSubArguments({ &valuesArg, &filesArg, &selectorsArg });
|
2016-11-26 00:14:45 +01:00
|
|
|
|
2017-05-04 22:44:00 +02:00
|
|
|
parser.setMainArguments({ &helpArg, &displayFileInfoArg, &getArg, &setArg });
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// fail due to operation flags not set
|
|
|
|
const char *const argv1[] = { "se" };
|
|
|
|
ArgumentReader reader(parser, argv1, argv1 + 1, true);
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=()\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(1, argv1, 0, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// correct operation arg flags
|
|
|
|
getArg.setDenotesOperation(true);
|
|
|
|
setArg.setDenotesOperation(true);
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('set' )\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv1, argv1 + 1).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(1, argv1, 0, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// argument at current cursor position already specified -> the completion should just return the argument
|
|
|
|
const char *const argv2[] = { "set" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('set' )\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv2, argv2 + 1).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(1, argv2, 0, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// advance the cursor position -> the completion should propose the next argument
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2018-01-29 16:23:10 +01:00
|
|
|
const OutputCheck c("COMPREPLY=('--files' '--selectors' '--values' )\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv2, argv2 + 1).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(1, argv2, 1, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// nested operations should be proposed as operations
|
|
|
|
parser.resetArgs();
|
|
|
|
filesArg.setDenotesOperation(true);
|
|
|
|
{
|
2018-01-29 16:23:10 +01:00
|
|
|
const OutputCheck c("COMPREPLY=('files' '--selectors' '--values' )\n");
|
2017-04-06 00:01:06 +02:00
|
|
|
reader.reset(argv2, argv2 + 1).read();
|
|
|
|
parser.printBashCompletion(1, argv2, 1, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2017-04-06 00:01:06 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// specifying no args should propose all main arguments
|
|
|
|
parser.resetArgs();
|
|
|
|
filesArg.setDenotesOperation(false);
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('display-file-info' 'get' 'set' '--help' )\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(nullptr, nullptr).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(0, nullptr, 0, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// pre-defined values
|
|
|
|
const char *const argv3[] = { "get", "--fields" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--files' )\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv3, argv3 + 2).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(2, argv3, 2, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// pre-defined values with equation sign, one letter already present
|
|
|
|
const char *const argv4[] = { "set", "--values", "a" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('album=' 'artist=' ); compopt -o nospace\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv4, argv4 + 3).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(3, argv4, 2, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2018-01-29 16:23:10 +01:00
|
|
|
// pre-defined values containing equation sign, equation sign already present
|
|
|
|
const char *const argv12[] = { "set", "--selectors", "tag=id3" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
|
|
|
const OutputCheck c("COMPREPLY=('tag=id3v1' 'tag=id3v2' )\n");
|
|
|
|
reader.reset(argv12, argv12 + 3).read();
|
|
|
|
parser.printBashCompletion(3, argv12, 2, reader);
|
|
|
|
}
|
|
|
|
|
|
|
|
// recombining pre-defined values containing equation sign, equation sign already present
|
|
|
|
const char *const argv13[] = { "set", "--selectors", "tag", "=", "id3" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
|
|
|
const OutputCheck c("COMPREPLY=('id3v1' 'id3v2' )\n");
|
|
|
|
reader.reset(argv13, argv13 + 5).read();
|
|
|
|
parser.printBashCompletion(5, argv13, 4, reader);
|
|
|
|
}
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
|
|
|
const OutputCheck c("COMPREPLY=('id3v1' 'id3v2' 'matroska' )\n");
|
|
|
|
reader.reset(argv13, argv13 + 5).read();
|
|
|
|
parser.printBashCompletion(5, argv13, 3, reader);
|
|
|
|
}
|
|
|
|
|
|
|
|
// computing pre-defined values just in time using callback
|
|
|
|
selectorsArg.setValueCompletionBehavior(selectorsArg.valueCompletionBehaviour() | ValueCompletionBehavior::InvokeCallback);
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
|
|
|
const OutputCheck c("COMPREPLY=('matroska' 'mp4' 'vorbis' )\n");
|
|
|
|
reader.reset(argv13, argv13 + 5).read();
|
|
|
|
parser.printBashCompletion(5, argv13, 3, reader);
|
|
|
|
}
|
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// pre-defined values for implicit argument
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--fields' '--files' )\n");
|
2017-07-28 17:32:07 +02:00
|
|
|
reader.reset(argv3, argv3 + 1).read();
|
|
|
|
parser.printBashCompletion(1, argv3, 2, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// file names
|
|
|
|
string iniFilePath = TestUtilities::testFilePath("test.ini");
|
|
|
|
iniFilePath.resize(iniFilePath.size() - 4);
|
|
|
|
string mkvFilePath = TestUtilities::testFilePath("test 'with quote'.mkv");
|
|
|
|
mkvFilePath.resize(mkvFilePath.size() - 17);
|
|
|
|
parser.resetArgs();
|
|
|
|
const char *const argv5[] = { "get", "--files", iniFilePath.c_str() };
|
|
|
|
{
|
|
|
|
// order for file names is not specified
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('" % mkvFilePath % " '\"'\"'with quote'\"'\"'.mkv' '" % iniFilePath + ".ini' ); compopt -o filenames\n",
|
2017-07-28 19:27:56 +02:00
|
|
|
"COMPREPLY=('" % iniFilePath % ".ini' '" % mkvFilePath + " '\"'\"'with quote'\"'\"'.mkv' ); compopt -o filenames\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv5, argv5 + 3).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(3, argv5, 2, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// sub arguments
|
|
|
|
const char *const argv6[] = { "set", "--" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2018-01-29 16:23:10 +01:00
|
|
|
const OutputCheck c("COMPREPLY=('--files' '--selectors' '--values' )\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv6, argv6 + 2).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(2, argv6, 1, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// nested sub arguments
|
|
|
|
const char *const argv7[] = { "-i", "--sub", "--" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('--files' '--nested-sub' '--verbose' )\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv7, argv7 + 3).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(3, argv7, 2, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-07-03 22:36:48 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// started pre-defined values with equation sign, one letter already present, last value matches
|
|
|
|
const char *const argv8[] = { "set", "--values", "t" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('title=' 'trackpos=' ); compopt -o nospace\n");
|
2016-12-23 09:55:12 +01:00
|
|
|
reader.reset(argv8, argv8 + 3).read();
|
2016-12-23 22:41:06 +01:00
|
|
|
parser.printBashCompletion(3, argv8, 2, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-11-26 00:14:45 +01:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// combined abbreviations
|
|
|
|
const char *const argv9[] = { "-gf" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('-gf' )\n");
|
2016-12-23 22:41:06 +01:00
|
|
|
reader.reset(argv9, argv9 + 1).read();
|
|
|
|
parser.printBashCompletion(1, argv9, 0, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c([](const string &actualOutput) { CPPUNIT_ASSERT_EQUAL(0_st, actualOutput.find("COMPREPLY=('--fields' ")); });
|
2016-12-23 22:41:06 +01:00
|
|
|
reader.reset(argv9, argv9 + 1).read();
|
|
|
|
parser.printBashCompletion(1, argv9, 1, reader);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
2016-12-23 22:41:06 +01:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// override exit function to prevent readArgs() from terminating the test run
|
|
|
|
exitFunction = [](int) { exitCalled = true; };
|
2017-07-28 18:24:52 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// call completion via readArgs() with current word index
|
|
|
|
const char *const argv10[] = { "/some/path/tageditor", "--bash-completion-for", "0" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('display-file-info' 'get' 'set' '--help' )\n");
|
2017-07-28 18:24:52 +02:00
|
|
|
parser.readArgs(3, argv10);
|
2017-07-28 19:27:56 +02:00
|
|
|
}
|
|
|
|
CPPUNIT_ASSERT(!strcmp("/some/path/tageditor", parser.executable()));
|
|
|
|
CPPUNIT_ASSERT(exitCalled);
|
2017-07-28 18:24:52 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// call completion via readArgs() without current word index
|
|
|
|
const char *const argv11[] = { "/some/path/tageditor", "--bash-completion-for", "ge" };
|
|
|
|
parser.resetArgs();
|
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("COMPREPLY=('get' )\n");
|
2017-07-28 19:27:56 +02:00
|
|
|
parser.readArgs(3, argv11);
|
2016-07-03 22:36:48 +02:00
|
|
|
}
|
|
|
|
}
|
2017-06-25 01:19:21 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Tests --help output.
|
|
|
|
*/
|
|
|
|
void ArgumentParserTests::testHelp()
|
|
|
|
{
|
2017-06-25 15:12:38 +02:00
|
|
|
// identation
|
|
|
|
Indentation indent;
|
|
|
|
indent = indent + 3;
|
|
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(4 + 3), indent.level);
|
|
|
|
|
2017-06-25 01:19:21 +02:00
|
|
|
// setup parser
|
|
|
|
ArgumentParser parser;
|
|
|
|
HelpArgument helpArg(parser);
|
|
|
|
helpArg.setRequired(true);
|
|
|
|
OperationArgument verboseArg("verbose", 'v', "be verbose", "actually not an operation");
|
|
|
|
verboseArg.setCombinable(true);
|
|
|
|
ConfigValueArgument nestedSubArg("nested-sub", '\0', "nested sub arg", { "value1", "value2" });
|
2017-09-20 17:07:06 +02:00
|
|
|
nestedSubArg.setRequiredValueCount(Argument::varValueCount);
|
2018-01-29 16:23:10 +01:00
|
|
|
Argument subArg("foo", 'f', "dummy");
|
|
|
|
subArg.setName("sub");
|
|
|
|
subArg.setAbbreviation('\0');
|
|
|
|
subArg.setDescription("sub arg");
|
|
|
|
subArg.setExample("sub arg example");
|
2017-06-25 01:19:21 +02:00
|
|
|
subArg.setRequired(true);
|
|
|
|
subArg.addSubArgument(&nestedSubArg);
|
|
|
|
Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
|
|
|
|
filesArg.setCombinable(true);
|
|
|
|
filesArg.addSubArgument(&subArg);
|
2017-06-25 15:12:38 +02:00
|
|
|
filesArg.setSubArguments({ &subArg }); // test re-assignment btw
|
2017-06-25 01:19:21 +02:00
|
|
|
Argument envArg("env", '\0', "env");
|
|
|
|
envArg.setEnvironmentVariable("FILES");
|
|
|
|
envArg.setRequiredValueCount(2);
|
2017-06-25 15:12:38 +02:00
|
|
|
envArg.appendValueName("file");
|
2017-06-25 01:19:21 +02:00
|
|
|
parser.addMainArgument(&helpArg);
|
|
|
|
parser.addMainArgument(&verboseArg);
|
|
|
|
parser.addMainArgument(&filesArg);
|
|
|
|
parser.addMainArgument(&envArg);
|
2017-07-28 20:47:49 +02:00
|
|
|
dependencyVersions = { "somelib", "some other lib" };
|
2017-06-25 01:19:21 +02:00
|
|
|
|
2017-07-28 19:27:56 +02:00
|
|
|
// parse args and assert output
|
2017-06-25 01:19:21 +02:00
|
|
|
const char *const argv[] = { "app", "-h" };
|
2017-07-28 19:27:56 +02:00
|
|
|
{
|
2017-09-20 17:09:34 +02:00
|
|
|
const OutputCheck c("\e[1m" APP_NAME ", version " APP_VERSION "\n"
|
|
|
|
"\e[0mLinked against: somelib, some other lib\n"
|
|
|
|
"\n\e[0m"
|
2018-01-28 00:38:05 +01:00
|
|
|
"Available operations:\n"
|
2017-09-20 17:09:34 +02:00
|
|
|
"\e[1mverbose, -v\e[0m\n"
|
|
|
|
" be verbose\n"
|
|
|
|
" example: actually not an operation\n"
|
|
|
|
"\n"
|
2018-01-28 00:38:05 +01:00
|
|
|
"Available top-level options:\n"
|
2017-09-20 17:09:34 +02:00
|
|
|
"\e[1m--files, -f\e[0m\n"
|
|
|
|
" specifies the path of the file(s) to be opened\n"
|
|
|
|
" \e[1m--sub\e[0m\n"
|
|
|
|
" sub arg\n"
|
|
|
|
" particularities: mandatory if parent argument is present\n"
|
|
|
|
" \e[1m--nested-sub\e[0m [value1] [value2] ...\n"
|
|
|
|
" nested sub arg\n"
|
2018-01-29 16:23:10 +01:00
|
|
|
" example: sub arg example\n"
|
2017-09-20 17:09:34 +02:00
|
|
|
"\n"
|
|
|
|
"\e[1m--env\e[0m [file] [value 2]\n"
|
|
|
|
" env\n"
|
|
|
|
" default environment variable: FILES\n"
|
|
|
|
"\n"
|
|
|
|
"Project website: " APP_URL "\n");
|
2017-10-19 00:48:05 +02:00
|
|
|
EscapeCodes::enabled = true;
|
2017-07-28 19:27:56 +02:00
|
|
|
parser.parseArgs(2, argv);
|
|
|
|
}
|
2018-01-29 16:23:10 +01:00
|
|
|
|
|
|
|
verboseArg.setDenotesOperation(false);
|
|
|
|
{
|
|
|
|
const OutputCheck c(APP_NAME ", version " APP_VERSION "\n"
|
|
|
|
"Linked against: somelib, some other lib\n"
|
|
|
|
"\n"
|
|
|
|
"Available arguments:\n"
|
|
|
|
"--verbose, -v\n"
|
|
|
|
" be verbose\n"
|
|
|
|
" example: actually not an operation\n"
|
|
|
|
"\n"
|
|
|
|
"--files, -f\n"
|
|
|
|
" specifies the path of the file(s) to be opened\n"
|
|
|
|
" --sub\n"
|
|
|
|
" sub arg\n"
|
|
|
|
" particularities: mandatory if parent argument is present\n"
|
|
|
|
" --nested-sub [value1] [value2] ...\n"
|
|
|
|
" nested sub arg\n"
|
|
|
|
" example: sub arg example\n"
|
|
|
|
"\n"
|
|
|
|
"--env [file] [value 2]\n"
|
|
|
|
" env\n"
|
|
|
|
" default environment variable: FILES\n"
|
|
|
|
"\n"
|
|
|
|
"Project website: " APP_URL "\n");
|
|
|
|
EscapeCodes::enabled = false;
|
|
|
|
parser.resetArgs();
|
|
|
|
parser.parseArgs(2, argv);
|
|
|
|
}
|
2017-06-25 01:19:21 +02:00
|
|
|
}
|
2017-06-25 15:12:38 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Tests some corner cases in setMainArguments() which are not already checked in the other tests.
|
|
|
|
*/
|
|
|
|
void ArgumentParserTests::testSetMainArguments()
|
|
|
|
{
|
|
|
|
ArgumentParser parser;
|
|
|
|
HelpArgument helpArg(parser);
|
|
|
|
Argument subArg("sub-arg", 's', "mandatory sub arg");
|
|
|
|
subArg.setRequired(true);
|
|
|
|
helpArg.addSubArgument(&subArg);
|
|
|
|
parser.addMainArgument(&helpArg);
|
|
|
|
parser.setMainArguments({});
|
|
|
|
CPPUNIT_ASSERT_MESSAGE("clear main args", parser.mainArguments().empty());
|
|
|
|
parser.setMainArguments({ &helpArg });
|
|
|
|
CPPUNIT_ASSERT_MESSAGE("no default due to required sub arg", !parser.defaultArgument());
|
|
|
|
subArg.setConstraints(0, 20);
|
|
|
|
parser.setMainArguments({ &helpArg });
|
|
|
|
CPPUNIT_ASSERT_MESSAGE("default if no required sub arg", &helpArg == parser.defaultArgument());
|
|
|
|
}
|
2018-01-29 16:23:10 +01:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Tests whether NocolorArgument toggles escape codes correctly.
|
|
|
|
*/
|
|
|
|
void ArgumentParserTests::testNoColorArgument()
|
|
|
|
{
|
|
|
|
// assume escape codes are enabled by default
|
|
|
|
EscapeCodes::enabled = true;
|
|
|
|
|
|
|
|
// ensure the initialization is not skipped
|
|
|
|
NoColorArgument::s_instance = nullptr;
|
|
|
|
|
|
|
|
{
|
|
|
|
unsetenv("ENABLE_ESCAPE_CODES");
|
|
|
|
NoColorArgument noColorArg;
|
|
|
|
noColorArg.apply();
|
|
|
|
CPPUNIT_ASSERT_MESSAGE("default used if not present", EscapeCodes::enabled);
|
|
|
|
noColorArg.m_occurrences.emplace_back(0);
|
|
|
|
noColorArg.apply();
|
|
|
|
CPPUNIT_ASSERT_MESSAGE("default negated if present", !EscapeCodes::enabled);
|
|
|
|
const NoColorArgument secondInstance;
|
|
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("s_instance not altered by 2nd instance", &noColorArg, NoColorArgument::s_instance);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
setenv("ENABLE_ESCAPE_CODES", " 0 ", 1);
|
|
|
|
const NoColorArgument noColorArg;
|
|
|
|
CPPUNIT_ASSERT(!EscapeCodes::enabled);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
setenv("ENABLE_ESCAPE_CODES", " 1 ", 1);
|
|
|
|
const NoColorArgument noColorArg;
|
|
|
|
CPPUNIT_ASSERT(EscapeCodes::enabled);
|
|
|
|
}
|
|
|
|
}
|