From: Pyry Haulos Date: Fri, 31 Oct 2014 00:45:41 +0000 (-0700) Subject: Add support for simpler test case list syntax X-Git-Tag: upstream/0.1.0~2086 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=ee2e173d445e87e1f98245f4377f66b081cc320d;p=platform%2Fupstream%2FVK-GL-CTS.git Add support for simpler test case list syntax This change adds support for mustpass-style (full test case path per line) test case format. All existing test case list command line options (--deqp-caselist, --deqp-stdin-caselist, --deqp-caselist-file) now accept both trie- and list-style inputs. Format is detected by looking at the first character; trie always starts with '{', list-style format is assumed otherwise. de::CommandLine default value behavior has been changed. Options that take values (i.e. not --flags) don't have value set, unless default value is provided when registering options. Calling getOption() on command line option whose value is not provided is now an error. hasOption() should be used first to check if a value was provided at all. Test case list parsing is now covered by tests in the internal module. Bug: 17958588 Change-Id: If6063165ff59cbd8e538d6f829c583541e104fd1 --- diff --git a/executor/tools/xeCommandLineExecutor.cpp b/executor/tools/xeCommandLineExecutor.cpp index 28caa56..de8f082 100644 --- a/executor/tools/xeCommandLineExecutor.cpp +++ b/executor/tools/xeCommandLineExecutor.cpp @@ -81,19 +81,19 @@ void registerOptions (de::cmdline::Parser& parser) { "no", false } }; - parser << Option ("s", "start-server", "Start local execserver") - << Option ("c", "connect", "Connect to host", "127.0.0.1") - << Option ("p", "port", "Select TCP port to use", "50016") - << Option ("cd", "caselistdir", "Path to test case XML files", ".") - << Option ("t", "testset", "Test set", parseCommaSeparatedList) - << Option ("e", "exclude", "Comma-separated list of exclude filters", parseCommaSeparatedList) + parser << Option ("s", "start-server", "Start local execserver", "") + << Option ("c", "connect", "Connect to host", "127.0.0.1") + << Option ("p", "port", "Select TCP port to use", "50016") + << Option ("cd", "caselistdir", "Path to test case XML files", ".") + << Option ("t", "testset", "Test set", parseCommaSeparatedList, "") + << Option ("e", "exclude", "Comma-separated list of exclude filters", parseCommaSeparatedList, "") << Option (DE_NULL, "continue", "Continue execution by initializing results from existing test log") - << Option ("o", "out", "Output test log filename") - << Option ("i", "info", "Output info log filename") - << Option (DE_NULL, "summary", "Print summary at the end", s_yesNo, "yes") - << Option ("b", "binaryname", "Test binary path, relative to working directory") - << Option ("wd", "workdir", "Working directory for test execution") - << Option (DE_NULL, "cmdline", "Additional command line arguments for test binary"); + << Option ("o", "out", "Output test log filename", "") + << Option ("i", "info", "Output info log filename", "") + << Option (DE_NULL, "summary", "Print summary at the end", s_yesNo, "yes") + << Option ("b", "binaryname", "Test binary path, relative to working directory", "") + << Option ("wd", "workdir", "Working directory for test execution", "") + << Option (DE_NULL, "cmdline", "Additional command line arguments for test binary", ""); } } // opt diff --git a/framework/common/tcuCommandLine.cpp b/framework/common/tcuCommandLine.cpp index c598e79..3bcc06a 100644 --- a/framework/common/tcuCommandLine.cpp +++ b/framework/common/tcuCommandLine.cpp @@ -23,6 +23,7 @@ #include "tcuCommandLine.hpp" #include "tcuPlatform.hpp" +#include "tcuTestCase.hpp" #include "deFilePath.hpp" #include "deStringUtil.hpp" #include "deString.h" @@ -139,22 +140,22 @@ void registerOptions (de::cmdline::Parser& parser) << Option (DE_NULL, "deqp-stdin-caselist", "Read case list (in trie format) from stdin") << Option (DE_NULL, "deqp-log-filename", "Write test results to given file", "TestResults.qpa") << Option (DE_NULL, "deqp-runmode", "Execute tests, or write list of test cases into a file", - s_runModes, "execute") + s_runModes, "execute") << Option (DE_NULL, "deqp-watchdog", "Enable test watchdog", s_enableNames, "disable") << Option (DE_NULL, "deqp-crashhandler", "Enable crash handling", s_enableNames, "disable") - << Option (DE_NULL, "deqp-base-seed", "Base seed for test cases that use randomization") - << Option (DE_NULL, "deqp-test-iteration-count", "Iteration count for cases that support variable number of iterations") + << Option (DE_NULL, "deqp-base-seed", "Base seed for test cases that use randomization", "0") + << Option (DE_NULL, "deqp-test-iteration-count", "Iteration count for cases that support variable number of iterations", "0") << Option (DE_NULL, "deqp-visibility", "Default test window visibility", s_visibilites, "windowed") - << Option (DE_NULL, "deqp-surface-width", "Use given surface width if possible", "-1") - << Option (DE_NULL, "deqp-surface-height", "Use given surface height if possible", "-1") + << Option (DE_NULL, "deqp-surface-width", "Use given surface width if possible", "-1") + << Option (DE_NULL, "deqp-surface-height", "Use given surface height if possible", "-1") << Option (DE_NULL, "deqp-surface-type", "Use given surface type", s_surfaceTypes, "window") << Option (DE_NULL, "deqp-screen-rotation", "Screen rotation for platforms that support it", s_screenRotations, "0") << Option (DE_NULL, "deqp-gl-context-type", "OpenGL context type for platforms that support multiple") - << Option (DE_NULL, "deqp-gl-config-id", "OpenGL (ES) render config ID (EGL config id on EGL platforms)", "-1") + << Option (DE_NULL, "deqp-gl-config-id", "OpenGL (ES) render config ID (EGL config id on EGL platforms)", "-1") << Option (DE_NULL, "deqp-gl-config-name", "Symbolic OpenGL (ES) render config name") << Option (DE_NULL, "deqp-gl-context-flags", "OpenGL context flags (comma-separated, supports debug and robust)") - << Option (DE_NULL, "deqp-cl-platform-id", "Execute tests on given OpenCL platform (IDs start from 1)", "1") - << Option (DE_NULL, "deqp-cl-device-ids", "Execute tests on given CL devices (comma-separated, IDs start from 1)", parseIntList) + << Option (DE_NULL, "deqp-cl-platform-id", "Execute tests on given OpenCL platform (IDs start from 1)", "1") + << Option (DE_NULL, "deqp-cl-device-ids", "Execute tests on given CL devices (comma-separated, IDs start from 1)", parseIntList, "") << Option (DE_NULL, "deqp-cl-build-options", "Extra build options for OpenCL compiler") << Option (DE_NULL, "deqp-egl-display-type", "EGL native display type") << Option (DE_NULL, "deqp-egl-window-type", "EGL native window type") @@ -238,15 +239,24 @@ public: CaseTreeNode (const std::string& name) : m_name(name) {} ~CaseTreeNode (void); - void addChild (CaseTreeNode* child) { m_children.push_back(child); } + const std::string& getName (void) const { return m_name; } + bool hasChildren (void) const { return !m_children.empty(); } + + bool hasChild (const std::string& name) const; + const CaseTreeNode* getChild (const std::string& name) const; + CaseTreeNode* getChild (const std::string& name); - const std::string& getName (void) const { return m_name; } - const std::vector& getChildren (void) const { return m_children; } + void addChild (CaseTreeNode* child) { m_children.push_back(child); } private: CaseTreeNode (const CaseTreeNode&); CaseTreeNode& operator= (const CaseTreeNode&); + enum { NOT_FOUND = -1 }; + + // \todo [2014-10-30 pyry] Speed up with hash / sorting + int findChildNdx (const std::string& name) const; + std::string m_name; std::vector m_children; }; @@ -257,64 +267,242 @@ CaseTreeNode::~CaseTreeNode (void) delete *i; } -static CaseTreeNode* parseCaseTree (std::istream& in) +int CaseTreeNode::findChildNdx (const std::string& name) const +{ + for (int ndx = 0; ndx < (int)m_children.size(); ++ndx) + { + if (m_children[ndx]->getName() == name) + return ndx; + } + return NOT_FOUND; +} + +inline bool CaseTreeNode::hasChild (const std::string& name) const +{ + return findChildNdx(name) != NOT_FOUND; +} + +inline const CaseTreeNode* CaseTreeNode::getChild (const std::string& name) const +{ + const int ndx = findChildNdx(name); + return ndx == NOT_FOUND ? DE_NULL : m_children[ndx]; +} + +inline CaseTreeNode* CaseTreeNode::getChild (const std::string& name) +{ + const int ndx = findChildNdx(name); + return ndx == NOT_FOUND ? DE_NULL : m_children[ndx]; +} + +static int getCurrentComponentLen (const char* path) +{ + int ndx = 0; + for (; path[ndx] != 0 && path[ndx] != '.'; ++ndx); + return ndx; +} + +static const CaseTreeNode* findNode (const CaseTreeNode* root, const char* path) +{ + const CaseTreeNode* curNode = root; + const char* curPath = path; + int curLen = getCurrentComponentLen(curPath); + + for (;;) + { + curNode = curNode->getChild(std::string(curPath, curPath+curLen)); + + if (!curNode) + break; + + curPath += curLen; + + if (curPath[0] == 0) + break; + else + { + DE_ASSERT(curPath[0] == '.'); + curPath += 1; + curLen = getCurrentComponentLen(curPath); + } + } + + return curNode; +} + +static void parseCaseTrie (CaseTreeNode* root, std::istream& in) { vector nodeStack; string curName; + bool expectNode = true; if (in.get() != '{') - throw std::invalid_argument("Malformed case tree"); + throw std::invalid_argument("Malformed case trie"); - nodeStack.reserve(1); - nodeStack.push_back(new CaseTreeNode("")); + nodeStack.push_back(root); - try + while (!nodeStack.empty()) { - for (;;) + const int curChr = in.get(); + + if (curChr == std::char_traits::eof() || curChr == 0) + throw std::invalid_argument("Unterminated case tree"); + + if (curChr == '{' || curChr == ',' || curChr == '}') { - const int curChr = in.get(); + if (!curName.empty() && expectNode) + { + CaseTreeNode* const newChild = new CaseTreeNode(curName); - if (curChr == std::char_traits::eof() || curChr == 0) - break; + try + { + nodeStack.back()->addChild(newChild); + } + catch (...) + { + delete newChild; + throw; + } - if (nodeStack.empty()) - throw std::invalid_argument("Trailing characters at end of case tree"); + if (curChr == '{') + nodeStack.push_back(newChild); - if (!curName.empty() && (curChr == '{' || curChr == ',' || curChr == '}')) + curName.clear(); + } + else if (curName.empty() == expectNode) + throw std::invalid_argument(expectNode ? "Empty node name" : "Missing node separator"); + + if (curChr == '}') { - // Create child and push to stack. - nodeStack.reserve(nodeStack.size()+1); - nodeStack.push_back(new CaseTreeNode(curName)); + expectNode = false; + nodeStack.pop_back(); + } + else + expectNode = true; + } + else if (isValidTestCaseNameChar((char)curChr)) + curName += (char)curChr; + else + throw std::invalid_argument("Illegal character in node name"); + } +} - curName.clear(); +static void parseCaseList (CaseTreeNode* root, std::istream& in) +{ + // \note Algorithm assumes that cases are sorted by groups, but will + // function fine, albeit more slowly, if that is not the case. + vector nodeStack; + int stackPos = 0; + string curName; + + nodeStack.resize(8, DE_NULL); + + nodeStack[0] = root; + + for (;;) + { + const int curChr = in.get(); + + if (curChr == std::char_traits::eof() || curChr == 0 || curChr == '\n' || curChr == '\r') + { + if (curName.empty()) + throw std::invalid_argument("Empty test case name"); + + if (nodeStack[stackPos]->hasChild(curName)) + throw std::invalid_argument("Duplicate test case"); + + CaseTreeNode* const newChild = new CaseTreeNode(curName); + + try + { + nodeStack[stackPos]->addChild(newChild); } + catch (...) + { + delete newChild; + throw; + } + + curName.clear(); + stackPos = 0; + + if (curChr == '\r' && in.peek() == '\n') + in.get(); - if (curChr == ',' || curChr == '}') { - // Attach to parent - if (nodeStack.size() < 2) - throw std::invalid_argument("Malformed case tree"); + const int nextChr = in.peek(); - (*(nodeStack.end()-2))->addChild(nodeStack.back()); - nodeStack.pop_back(); + if (nextChr == std::char_traits::eof() || nextChr == 0) + break; } - else if (curChr != '{') - curName += (char)curChr; } + else if (curChr == '.') + { + if (curName.empty()) + throw std::invalid_argument("Empty test group name"); - if (nodeStack.size() != 1 || nodeStack[0]->getName() != "") - throw std::invalid_argument("Unterminated case tree"); + if ((int)nodeStack.size() <= stackPos+1) + nodeStack.resize(nodeStack.size()*2, DE_NULL); + + if (!nodeStack[stackPos+1] || nodeStack[stackPos+1]->getName() != curName) + { + CaseTreeNode* curGroup = nodeStack[stackPos]->getChild(curName); + + if (!curGroup) + { + curGroup = new CaseTreeNode(curName); + + try + { + nodeStack[stackPos]->addChild(curGroup); + } + catch (...) + { + delete curGroup; + throw; + } + } + + nodeStack[stackPos+1] = curGroup; + + if ((int)nodeStack.size() > stackPos+2) + nodeStack[stackPos+2] = DE_NULL; // Invalidate rest of entries + } + + DE_ASSERT(nodeStack[stackPos+1]->getName() == curName); + + curName.clear(); + stackPos += 1; + } + else if (isValidTestCaseNameChar((char)curChr)) + curName += (char)curChr; + else + throw std::invalid_argument("Illegal character in test case name"); } - catch (...) +} + +static CaseTreeNode* parseCaseList (std::istream& in) +{ + CaseTreeNode* const root = new CaseTreeNode(""); + try { - // Nodes in stack are not attached to any parents and must be deleted individually. - for (vector::const_iterator i = nodeStack.begin(); i != nodeStack.end(); ++i) - delete *i; + if (in.peek() == '{') + parseCaseTrie(root, in); + else + parseCaseList(root, in); + + { + const int curChr = in.get(); + if (curChr != std::char_traits::eof() && curChr != 0) + throw std::invalid_argument("Trailing characters at end of case list"); + } + return root; + } + catch (...) + { + delete root; throw; } - - return nodeStack[0]; } class CasePaths @@ -507,9 +695,9 @@ bool CommandLine::parse (int argc, const char* const* argv) if (!m_cmdLine.getOption()) m_logFlags |= QP_TEST_LOG_EXCLUDE_IMAGES; - if ((m_cmdLine.getOption().empty()?0:1) + - (m_cmdLine.getOption().empty()?0:1) + - (m_cmdLine.getOption().empty()?0:1) + + if ((m_cmdLine.hasOption()?1:0) + + (m_cmdLine.hasOption()?1:0) + + (m_cmdLine.hasOption()?1:0) + (m_cmdLine.getOption()?1:0) > 1) { debugOut << "ERROR: multiple test case list options given!\n" << std::endl; @@ -519,26 +707,26 @@ bool CommandLine::parse (int argc, const char* const* argv) try { - if (!m_cmdLine.getOption().empty()) + if (m_cmdLine.hasOption()) { std::istringstream str(m_cmdLine.getOption()); - m_caseTree = parseCaseTree(str); + m_caseTree = parseCaseList(str); } - else if (!m_cmdLine.getOption().empty()) + else if (m_cmdLine.hasOption()) { std::ifstream in(m_cmdLine.getOption().c_str(), std::ios_base::binary); if (!in.is_open() || !in.good()) throw Exception("Failed to open case list file '" + m_cmdLine.getOption() + "'"); - m_caseTree = parseCaseTree(in); + m_caseTree = parseCaseList(in); } else if (m_cmdLine.getOption()) { - m_caseTree = parseCaseTree(std::cin); + m_caseTree = parseCaseList(std::cin); } - else if (!m_cmdLine.getOption().empty()) + else if (m_cmdLine.hasOption()) m_casePaths = de::MovePtr(new CasePaths(m_cmdLine.getOption())); } catch (const std::exception& e) @@ -592,21 +780,18 @@ ScreenRotation CommandLine::getScreenRotation (void) const { return m_cmdLin int CommandLine::getGLConfigId (void) const { return m_cmdLine.getOption(); } int CommandLine::getCLPlatformId (void) const { return m_cmdLine.getOption(); } const std::vector& CommandLine::getCLDeviceIds (void) const { return m_cmdLine.getOption(); } -const char* CommandLine::getEGLDisplayType (void) const { return m_cmdLine.getOption().c_str(); } -const char* CommandLine::getEGLWindowType (void) const { return m_cmdLine.getOption().c_str(); } -const char* CommandLine::getEGLPixmapType (void) const { return m_cmdLine.getOption().c_str(); } bool CommandLine::isOutOfMemoryTestEnabled (void) const { return m_cmdLine.getOption(); } const char* CommandLine::getGLContextType (void) const { - if (!m_cmdLine.getOption().empty()) + if (m_cmdLine.hasOption()) return m_cmdLine.getOption().c_str(); else return DE_NULL; } const char* CommandLine::getGLConfigName (void) const { - if (!m_cmdLine.getOption().empty()) + if (m_cmdLine.hasOption()) return m_cmdLine.getOption().c_str(); else return DE_NULL; @@ -614,7 +799,7 @@ const char* CommandLine::getGLConfigName (void) const const char* CommandLine::getGLContextFlags (void) const { - if (!m_cmdLine.getOption().empty()) + if (m_cmdLine.hasOption()) return m_cmdLine.getOption().c_str(); else return DE_NULL; @@ -622,50 +807,46 @@ const char* CommandLine::getGLContextFlags (void) const const char* CommandLine::getCLBuildOptions (void) const { - if (!m_cmdLine.getOption().empty()) + if (m_cmdLine.hasOption()) return m_cmdLine.getOption().c_str(); else return DE_NULL; } -static bool checkTestGroupName (const CaseTreeNode* node, const char* groupName) +const char* CommandLine::getEGLDisplayType (void) const { - for (vector::const_iterator childIter = node->getChildren().begin(); childIter != node->getChildren().end(); ++childIter) - { - const CaseTreeNode* const child = *childIter; - - if (deStringBeginsWith(groupName, child->getName().c_str())) - { - const int prefixLen = (int)child->getName().length(); - - if (groupName[prefixLen] == 0) - return true; - else if (groupName[prefixLen] == '.') - return checkTestGroupName(child, groupName + prefixLen + 1); - } - } - - return false; + if (m_cmdLine.hasOption()) + return m_cmdLine.getOption().c_str(); + else + return DE_NULL; } -static bool checkTestCaseName (const CaseTreeNode* node, const char* caseName) +const char* CommandLine::getEGLWindowType (void) const { - for (vector::const_iterator childIter = node->getChildren().begin(); childIter != node->getChildren().end(); ++childIter) - { - const CaseTreeNode* const child = *childIter; + if (m_cmdLine.hasOption()) + return m_cmdLine.getOption().c_str(); + else + return DE_NULL; +} - if (deStringBeginsWith(caseName, child->getName().c_str())) - { - const int prefixLen = (int)child->getName().length(); +const char* CommandLine::getEGLPixmapType (void) const +{ + if (m_cmdLine.hasOption()) + return m_cmdLine.getOption().c_str(); + else + return DE_NULL; +} - if (caseName[prefixLen] == 0 && child->getChildren().empty()) - return true; - else if (caseName[prefixLen] == '.') - return checkTestCaseName(child, caseName + prefixLen + 1); - } - } +static bool checkTestGroupName (const CaseTreeNode* root, const char* groupPath) +{ + const CaseTreeNode* node = findNode(root, groupPath); + return node && node->hasChildren(); +} - return false; +static bool checkTestCaseName (const CaseTreeNode* root, const char* casePath) +{ + const CaseTreeNode* node = findNode(root, casePath); + return node && !node->hasChildren(); } bool CommandLine::checkTestGroupName (const char* groupName) const diff --git a/framework/common/tcuTestCase.cpp b/framework/common/tcuTestCase.cpp index 2138e26..a8aeeb7 100644 --- a/framework/common/tcuTestCase.cpp +++ b/framework/common/tcuTestCase.cpp @@ -33,19 +33,11 @@ using namespace std; // TestNode. -inline bool isValidCaseNameChar (char c) -{ - return de::inRange(c, 'a', 'z') || - de::inRange(c, 'A', 'Z') || - de::inRange(c, '0', '9') || - c == '_' || c == '-'; -} - inline bool isValidCaseName (const char* name) { for (const char* p = name; *p != '\0'; p++) { - if (!isValidCaseNameChar(*p)) + if (!isValidTestCaseNameChar(*p)) return false; } return true; diff --git a/framework/common/tcuTestCase.hpp b/framework/common/tcuTestCase.hpp index f333f22..431f7d1 100644 --- a/framework/common/tcuTestCase.hpp +++ b/framework/common/tcuTestCase.hpp @@ -51,6 +51,14 @@ inline bool isTestNodeTypeExecutable (TestNodeType type) type == NODETYPE_ACCURACY; } +inline bool isValidTestCaseNameChar (char c) +{ + return de::inRange(c, 'a', 'z') || + de::inRange(c, 'A', 'Z') || + de::inRange(c, '0', '9') || + c == '_' || c == '-'; +} + /*--------------------------------------------------------------------*//*! * \brief Test case hierarchy node * diff --git a/framework/delibs/decpp/deCommandLine.cpp b/framework/delibs/decpp/deCommandLine.cpp index dd2b4e2..d601780 100644 --- a/framework/delibs/decpp/deCommandLine.cpp +++ b/framework/delibs/decpp/deCommandLine.cpp @@ -55,11 +55,17 @@ TypedFieldMap::TypedFieldMap (void) TypedFieldMap::~TypedFieldMap (void) { + clear(); +} + +void TypedFieldMap::clear (void) +{ for (Map::const_iterator iter = m_fields.begin(); iter != m_fields.end(); ++iter) { if (iter->second.value) iter->second.destructor(iter->second.value); } + m_fields.clear(); } bool TypedFieldMap::contains (const std::type_info* key) const @@ -138,8 +144,8 @@ bool Parser::parse (int numArgs, const char* const* args, CommandLine* dst, std: // Set default values. if (opt.defaultValue) opt.dispatchParse(&opt, opt.defaultValue, &dst->m_options); - else - opt.getDefault(&dst->m_options); + else if (opt.setDefault) + opt.setDefault(&dst->m_options); } DE_ASSERT(!dst->m_options.get()); @@ -332,10 +338,11 @@ void parseType (const char* src, int* dst) // Tests -DE_DECLARE_COMMAND_LINE_OPT(TestStringOpt, std::string); -DE_DECLARE_COMMAND_LINE_OPT(TestIntOpt, int); -DE_DECLARE_COMMAND_LINE_OPT(TestBoolOpt, bool); -DE_DECLARE_COMMAND_LINE_OPT(TestNamedOpt, deUint64); +DE_DECLARE_COMMAND_LINE_OPT(TestStringOpt, std::string); +DE_DECLARE_COMMAND_LINE_OPT(TestStringDefOpt, std::string); +DE_DECLARE_COMMAND_LINE_OPT(TestIntOpt, int); +DE_DECLARE_COMMAND_LINE_OPT(TestBoolOpt, bool); +DE_DECLARE_COMMAND_LINE_OPT(TestNamedOpt, deUint64); void selfTest (void) { @@ -396,10 +403,11 @@ void selfTest (void) { "huge", ~0ull } }; - parser << Option ("s", "string", "String option") - << Option ("i", "int", "Int option") - << Option ("b", "bool", "Test boolean flag") - << Option ("n", "named", "Test named opt", DE_ARRAY_BEGIN(s_namedValues), DE_ARRAY_END(s_namedValues), "one"); + parser << Option ("s", "string", "String option") + << Option ("x", "xyz", "String option w/ default value", "foo") + << Option ("i", "int", "Int option") + << Option ("b", "bool", "Test boolean flag") + << Option ("n", "named", "Test named opt", DE_ARRAY_BEGIN(s_namedValues), DE_ARRAY_END(s_namedValues), "one"); { std::ostringstream err; @@ -417,10 +425,11 @@ void selfTest (void) DE_TEST_ASSERT(parseOk); DE_TEST_ASSERT(err.str().empty()); - DE_TEST_ASSERT(cmdLine.getOption() == ""); - DE_TEST_ASSERT(cmdLine.getOption() == 0); - DE_TEST_ASSERT(cmdLine.getOption() == false); + DE_TEST_ASSERT(!cmdLine.hasOption()); + DE_TEST_ASSERT(!cmdLine.hasOption()); DE_TEST_ASSERT(cmdLine.getOption() == 1); + DE_TEST_ASSERT(cmdLine.getOption() == false); + DE_TEST_ASSERT(cmdLine.getOption() == "foo"); } // Basic parsing @@ -437,6 +446,7 @@ void selfTest (void) DE_TEST_ASSERT(cmdLine.getOption() == 9); DE_TEST_ASSERT(cmdLine.getOption()); DE_TEST_ASSERT(cmdLine.getOption() == ~0ull); + DE_TEST_ASSERT(cmdLine.getOption() == "foo"); } // End of argument list (--) @@ -451,7 +461,7 @@ void selfTest (void) DE_TEST_ASSERT(cmdLine.getOption() == "foo"); DE_TEST_ASSERT(cmdLine.getOption()); - DE_TEST_ASSERT(cmdLine.getOption() == 0); // Not specified + DE_TEST_ASSERT(!cmdLine.hasOption()); DE_TEST_ASSERT(cmdLine.getArgs().size() == 2); DE_TEST_ASSERT(cmdLine.getArgs()[0] == "--int=2"); @@ -470,7 +480,7 @@ void selfTest (void) DE_TEST_ASSERT(cmdLine.getOption() == "--"); DE_TEST_ASSERT(cmdLine.getOption()); - DE_TEST_ASSERT(cmdLine.getOption() == 0); // Not specified + DE_TEST_ASSERT(!cmdLine.hasOption()); DE_TEST_ASSERT(cmdLine.getArgs().size() == 1); DE_TEST_ASSERT(cmdLine.getArgs()[0] == "foo"); @@ -555,7 +565,7 @@ void selfTest (void) // Empty value --arg= { - const char* args[] = { "--string=", "-b" }; + const char* args[] = { "--string=", "-b", "-x", "" }; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); @@ -563,6 +573,7 @@ void selfTest (void) DE_TEST_ASSERT(parseOk); DE_TEST_ASSERT(err.str().empty()); DE_TEST_ASSERT(cmdLine.getOption() == ""); + DE_TEST_ASSERT(cmdLine.getOption() == ""); DE_TEST_ASSERT(cmdLine.getOption()); } } diff --git a/framework/delibs/decpp/deCommandLine.hpp b/framework/delibs/decpp/deCommandLine.hpp index 4f19aac..50bfca5 100644 --- a/framework/delibs/decpp/deCommandLine.hpp +++ b/framework/delibs/decpp/deCommandLine.hpp @@ -58,7 +58,7 @@ struct Option const char* shortName; const char* longName; const char* description; - const char* defaultValue; //!< Default value (parsed from string), or null if default should be T() + const char* defaultValue; //!< Default value (parsed from string), or null if should not be set // \note Either parse or namedValues must be null. ParseFunc parse; //!< Custom parsing function or null. @@ -165,7 +165,7 @@ public: ~TypedFieldMap (void); bool empty (void) const { return m_fields.empty(); } - void clear (void) { m_fields.clear(); } + void clear (void); template void set (typename TypedFieldTraits::ValueType* value); @@ -262,7 +262,7 @@ private: struct OptInfo; typedef void (*DispatchParseFunc) (const OptInfo* info, const char* src, TypedFieldMap* dst); - typedef void (*GetDefaultFunc) (TypedFieldMap* dst); + typedef void (*SetDefaultFunc) (TypedFieldMap* dst); struct OptInfo { @@ -279,7 +279,7 @@ private: size_t namedValueStride; DispatchParseFunc dispatchParse; - GetDefaultFunc getDefault; + SetDefaultFunc setDefault; OptInfo (void) : shortName (DE_NULL) @@ -292,7 +292,7 @@ private: , namedValuesEnd (DE_NULL) , namedValueStride (0) , dispatchParse (DE_NULL) - , getDefault (DE_NULL) + , setDefault (DE_NULL) {} }; @@ -340,7 +340,7 @@ void Parser::dispatchParse (const OptInfo* info, const char* src, TypedFieldMap* } template -void dispatchGetDefault (TypedFieldMap* dst) +void dispatchSetDefault (TypedFieldMap* dst) { typename OptTraits::ValueType* value = new typename OptTraits::ValueType(); try @@ -384,7 +384,9 @@ void Parser::addOption (const Option& option) opt.namedValuesEnd = (const void*)option.namedValuesEnd; opt.namedValueStride = sizeof(*option.namedValues); opt.dispatchParse = dispatchParse; - opt.getDefault = dispatchGetDefault; + + if (opt.isFlag) + opt.setDefault = dispatchSetDefault; addOption(opt); } @@ -401,8 +403,11 @@ public: const vector& getArgs (void) const { return m_args; } template + bool hasOption (void) const { return m_options.contains