cpp-utilities/tests/argumentparsertests.cpp

317 lines
13 KiB
C++
Raw Normal View History

2016-06-12 01:56:57 +02:00
#include "./testutils.h"
#include "../application/argumentparser.h"
#include "../application/failure.h"
#include "../application/fakeqtconfigarguments.h"
#include "resources/config.h"
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
#include <cstring>
using namespace std;
using namespace ApplicationUtilities;
using namespace CPPUNIT_NS;
/*!
* \brief The ArgumentParserTests class tests the ArgumentParser and Argument classes.
*/
class ArgumentParserTests : public TestFixture
{
CPPUNIT_TEST_SUITE(ArgumentParserTests);
CPPUNIT_TEST(testArgument);
CPPUNIT_TEST(testParsing);
CPPUNIT_TEST(testCallbacks);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testArgument();
void testParsing();
void testCallbacks();
private:
void callback();
};
CPPUNIT_TEST_SUITE_REGISTRATION(ArgumentParserTests);
void ArgumentParserTests::setUp()
{}
void ArgumentParserTests::tearDown()
{}
/*!
* \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());
}
/*!
* \brief Tests parsing command line arguments.
*/
void ArgumentParserTests::testParsing()
{
// 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");
fileArg.setValueNames({"path"});
fileArg.setRequiredValueCount(1);
Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
filesArg.setValueNames({"path 1", "path 2"});
filesArg.setRequiredValueCount(-1);
Argument outputFileArg("output-file", 'o', "specifies the path of the output file");
outputFileArg.setValueNames({"path"});
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");
displayFileInfoArg.setDenotesOperation(true);
displayFileInfoArg.setSubArguments({&fileArg, &verboseArg});
Argument fieldsArg("fields", '\0', "specifies the fields");
fieldsArg.setRequiredValueCount(-1);
fieldsArg.setValueNames({"title", "album", "artist", "trackpos"});
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);
displayTagInfoArg.setSubArguments({&fieldsArg, &filesArg, &verboseArg});
parser.setMainArguments({&qtConfigArgs.qtWidgetsGuiArg(), &printFieldNamesArg, &displayTagInfoArg, &displayFileInfoArg, &helpArg});
// define some argument values
const char *argv[] = {"tageditor", "get", "album", "title", "diskpos", "-f", "somefile"};
// try to parse, this should fail
try {
parser.parseArgs(7, argv);
CPPUNIT_FAIL("Exception expected.");
} catch(const Failure &e) {
CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"files\" can not be combined with \"fields\"."));
}
// try to parse again, but adjust the configuration for a successful parse
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
filesArg.setCombinable(true);
parser.parseArgs(7, argv);
// check results
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
2016-06-12 01:56:57 +02:00
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
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);
// define the same arguments in a different way
const char *argv2[] = {"tageditor", "-p", "album", "title", "diskpos", "--file", "somefile"};
// reparse the args
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
parser.parseArgs(7, argv2);
// check results again
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"));
CPPUNIT_ASSERT_THROW(displayTagInfoArg.values().at(3), out_of_range);
CPPUNIT_ASSERT(filesArg.isPresent());
CPPUNIT_ASSERT(!strcmp(filesArg.values().at(0), "somefile"));
// forget "get"/"-p"
const char *argv3[] = {"tageditor", "album", "title", "diskpos", "--file", "somefile"};
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
// a parsing error should occur because the argument "album" is not defined
try {
parser.parseArgs(6, argv3);
CPPUNIT_FAIL("Exception expected.");
} catch(const Failure &e) {
CPPUNIT_ASSERT(!strcmp(e.what(), "The specified argument \"album\" is unknown and will be ignored."));
}
// repeat the test, but this time just ignore the undefined argument
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
parser.setIgnoreUnknownArguments(true);
// redirect stderr to check whether warnings are printed correctly
stringstream buffer;
streambuf *regularCerrBuffer = cerr.rdbuf(buffer.rdbuf());
try {
parser.parseArgs(6, argv3);
cerr.rdbuf(regularCerrBuffer);
} catch(...) {
cerr.rdbuf(regularCerrBuffer);
throw;
}
CPPUNIT_ASSERT(!strcmp(buffer.str().data(), "The specified argument \"album\" is unknown and will be ignored.\n"
"The specified argument \"title\" is unknown and will be ignored.\n"
"The specified argument \"diskpos\" is unknown and will be ignored.\n"
"The specified argument \"--file\" is unknown and will be ignored.\n"
"The specified argument \"somefile\" is unknown and will be ignored.\n"));
2016-06-12 01:56:57 +02:00
// none of the arguments should be present now
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());
// test abbreviations like "-vf"
const char *argv4[] = {"tageditor", "-i", "-vf", "test"};
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
parser.setIgnoreUnknownArguments(false);
parser.parseArgs(4, argv4);
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);
// don't reset verbose argument to test constraint checking
displayFileInfoArg.reset(), fileArg.reset();
try {
parser.parseArgs(4, argv4);
CPPUNIT_FAIL("Exception expected.");
} catch(const Failure &e) {
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."));
}
// relax constraint
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
verboseArg.setConstraints(0, -1);
parser.parseArgs(4, argv4);
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
2016-06-12 01:56:57 +02:00
// make verbose mandatory
verboseArg.setRequired(true);
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
parser.parseArgs(4, argv4);
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
2016-06-12 01:56:57 +02:00
// make it complain about missing argument
const char *argv5[] = {"tageditor", "-i", "-f", "test"};
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
try {
parser.parseArgs(4, argv5);
CPPUNIT_FAIL("Exception expected.");
} catch(const Failure &e) {
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."));
}
// it should not complain if -i is not present
const char *argv6[] = {"tageditor", "-g"};
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
parser.parseArgs(2, argv6);
CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
2016-06-12 01:56:57 +02:00
// it should not be possible to specify -f without -i or -p
const char *argv7[] = {"tageditor", "-f", "test"};
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset(), qtConfigArgs.qtWidgetsGuiArg().reset();
2016-06-12 01:56:57 +02:00
try {
parser.parseArgs(3, argv7);
CPPUNIT_FAIL("Exception expected.");
} catch(const Failure &e) {
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
2016-06-12 01:56:57 +02:00
CPPUNIT_ASSERT(!strcmp(e.what(), "The specified argument \"-f\" is unknown and will be ignored."));
}
// test default argument
const char *argv8[] = {"tageditor"};
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
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());
// test required value count constraint with sufficient number of provided parameters
qtConfigArgs.qtWidgetsGuiArg().reset();
fieldsArg.setRequiredValueCount(3);
verboseArg.setRequired(false);
parser.parseArgs(7, argv2);
// 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());
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);
CPPUNIT_ASSERT(filesArg.isPresent());
CPPUNIT_ASSERT(!strcmp(filesArg.values().at(0), "somefile"));
// test required value count constraint with insufficient number of provided parameters
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
fieldsArg.setRequiredValueCount(4);
const char *argv9[] = {"tageditor", "-p", "album", "title", "diskpos"};
try {
parser.parseArgs(5, argv9);
CPPUNIT_FAIL("Exception expected.");
} catch(const Failure &e) {
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT(!strcmp(e.what(), "Not all parameter for argument \"fields\" provided. You have to provide the following parameter: title album artist trackpos"));
}
2016-06-12 01:56:57 +02:00
}
void ArgumentParserTests::testCallbacks()
{
ArgumentParser parser;
Argument callbackArg("with-callback", 't', "callback test");
callbackArg.setRequiredValueCount(2);
callbackArg.setCallback([] (const vector<const char *> &values) {
CPPUNIT_ASSERT(values.size() == 2);
CPPUNIT_ASSERT(!strcmp(values[0], "val1"));
CPPUNIT_ASSERT(!strcmp(values[1], "val2"));
throw 42;
});
Argument noCallbackArg("no-callback", 'l', "callback test");
noCallbackArg.setRequiredValueCount(2);
parser.setMainArguments({&callbackArg, &noCallbackArg});
// test whether callback is invoked when argument with callback is specified
const char *argv[] = {"test", "-t", "val1", "val2"};
try {
parser.parseArgs(4, argv);
} catch(int i) {
CPPUNIT_ASSERT_EQUAL(i, 42);
}
// test whether callback is not invoked when argument with callback is not specified
callbackArg.reset();
const char *argv2[] = {"test", "-l", "val1", "val2"};
parser.parseArgs(4, argv2);
}