Add support for simpler test case list syntax
authorPyry Haulos <phaulos@google.com>
Fri, 31 Oct 2014 00:45:41 +0000 (17:45 -0700)
committerPyry Haulos <phaulos@google.com>
Fri, 31 Oct 2014 16:57:08 +0000 (09:57 -0700)
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

executor/tools/xeCommandLineExecutor.cpp
framework/common/tcuCommandLine.cpp
framework/common/tcuTestCase.cpp
framework/common/tcuTestCase.hpp
framework/delibs/decpp/deCommandLine.cpp
framework/delibs/decpp/deCommandLine.hpp
framework/egl/egluGLContextFactory.cpp
modules/egl/teglTestPackage.cpp
modules/internal/ditFrameworkTests.cpp

index 28caa56..de8f082 100644 (file)
@@ -81,19 +81,19 @@ void registerOptions (de::cmdline::Parser& parser)
                { "no",         false   }
        };
 
-       parser << Option<StartServer>   ("s",           "start-server", "Start local execserver")
-                  << Option<Host>                      ("c",           "connect",              "Connect to host",                                                      "127.0.0.1")
-                  << Option<Port>                      ("p",           "port",                 "Select TCP port to use",                                       "50016")
-                  << Option<CaseListDir>       ("cd",          "caselistdir",  "Path to test case XML files",                          ".")
-                  << Option<TestSet>           ("t",           "testset",              "Test set",                                                                     parseCommaSeparatedList)
-                  << Option<ExcludeSet>        ("e",           "exclude",              "Comma-separated list of exclude filters",      parseCommaSeparatedList)
+       parser << Option<StartServer>   ("s",           "start-server", "Start local execserver",                                                               "")
+                  << Option<Host>                      ("c",           "connect",              "Connect to host",                                                                              "127.0.0.1")
+                  << Option<Port>                      ("p",           "port",                 "Select TCP port to use",                                                               "50016")
+                  << Option<CaseListDir>       ("cd",          "caselistdir",  "Path to test case XML files",                                                  ".")
+                  << Option<TestSet>           ("t",           "testset",              "Test set",                                                                                             parseCommaSeparatedList,        "")
+                  << Option<ExcludeSet>        ("e",           "exclude",              "Comma-separated list of exclude filters",                              parseCommaSeparatedList,        "")
                   << Option<ContinueFile>      (DE_NULL,       "continue",             "Continue execution by initializing results from existing test log")
-                  << Option<TestLogFile>       ("o",           "out",                  "Output test log filename")
-                  << Option<InfoLogFile>       ("i",           "info",                 "Output info log filename")
-                  << Option<Summary>           (DE_NULL,       "summary",              "Print summary at the end",                                     s_yesNo,        "yes")
-                  << Option<BinaryName>        ("b",           "binaryname",   "Test binary path, relative to working directory")
-                  << Option<WorkingDir>        ("wd",          "workdir",              "Working directory for test execution")
-                  << Option<CmdLineArgs>       (DE_NULL,       "cmdline",              "Additional command line arguments for test binary");
+                  << Option<TestLogFile>       ("o",           "out",                  "Output test log filename",                                                             "")
+                  << Option<InfoLogFile>       ("i",           "info",                 "Output info log filename",                                                             "")
+                  << Option<Summary>           (DE_NULL,       "summary",              "Print summary at the end",                                                             s_yesNo,        "yes")
+                  << Option<BinaryName>        ("b",           "binaryname",   "Test binary path, relative to working directory",              "")
+                  << Option<WorkingDir>        ("wd",          "workdir",              "Working directory for test execution",                                 "")
+                  << Option<CmdLineArgs>       (DE_NULL,       "cmdline",              "Additional command line arguments for test binary",    "");
 }
 
 } // opt
index c598e79..3bcc06a 100644 (file)
@@ -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<StdinCaseList>                (DE_NULL,       "deqp-stdin-caselist",                  "Read case list (in trie format) from stdin")
                << Option<LogFilename>                  (DE_NULL,       "deqp-log-filename",                    "Write test results to given file",                                     "TestResults.qpa")
                << Option<RunMode>                              (DE_NULL,       "deqp-runmode",                                 "Execute tests, or write list of test cases into a file",
-                                                                                                                                                                       s_runModes, "execute")
+                                                                                                                                                                                                                                                                               s_runModes,                     "execute")
                << Option<WatchDog>                             (DE_NULL,       "deqp-watchdog",                                "Enable test watchdog",                                                         s_enableNames,          "disable")
                << Option<CrashHandler>                 (DE_NULL,       "deqp-crashhandler",                    "Enable crash handling",                                                        s_enableNames,          "disable")
-               << Option<BaseSeed>                             (DE_NULL,       "deqp-base-seed",                               "Base seed for test cases that use randomization")
-               << Option<TestIterationCount>   (DE_NULL,       "deqp-test-iteration-count",    "Iteration count for cases that support variable number of iterations")
+               << Option<BaseSeed>                             (DE_NULL,       "deqp-base-seed",                               "Base seed for test cases that use randomization",                                              "0")
+               << Option<TestIterationCount>   (DE_NULL,       "deqp-test-iteration-count",    "Iteration count for cases that support variable number of iterations", "0")
                << Option<Visibility>                   (DE_NULL,       "deqp-visibility",                              "Default test window visibility",                                       s_visibilites,          "windowed")
-               << Option<SurfaceWidth>                 (DE_NULL,       "deqp-surface-width",                   "Use given surface width if possible",  "-1")
-               << Option<SurfaceHeight>                (DE_NULL,       "deqp-surface-height",                  "Use given surface height if possible", "-1")
+               << Option<SurfaceWidth>                 (DE_NULL,       "deqp-surface-width",                   "Use given surface width if possible",                                                                  "-1")
+               << Option<SurfaceHeight>                (DE_NULL,       "deqp-surface-height",                  "Use given surface height if possible",                                                                 "-1")
                << Option<SurfaceType>                  (DE_NULL,       "deqp-surface-type",                    "Use given surface type",                                                       s_surfaceTypes,         "window")
                << Option<ScreenRotation>               (DE_NULL,       "deqp-screen-rotation",                 "Screen rotation for platforms that support it",        s_screenRotations,      "0")
                << Option<GLContextType>                (DE_NULL,       "deqp-gl-context-type",                 "OpenGL context type for platforms that support multiple")
-               << Option<GLConfigID>                   (DE_NULL,       "deqp-gl-config-id",                    "OpenGL (ES) render config ID (EGL config id on EGL platforms)",        "-1")
+               << Option<GLConfigID>                   (DE_NULL,       "deqp-gl-config-id",                    "OpenGL (ES) render config ID (EGL config id on EGL platforms)",                "-1")
                << Option<GLConfigName>                 (DE_NULL,       "deqp-gl-config-name",                  "Symbolic OpenGL (ES) render config name")
                << Option<GLContextFlags>               (DE_NULL,       "deqp-gl-context-flags",                "OpenGL context flags (comma-separated, supports debug and robust)")
-               << Option<CLPlatformID>                 (DE_NULL,       "deqp-cl-platform-id",                  "Execute tests on given OpenCL platform (IDs start from 1)",            "1")
-               << Option<CLDeviceIDs>                  (DE_NULL,       "deqp-cl-device-ids",                   "Execute tests on given CL devices (comma-separated, IDs start from 1)",        parseIntList)
+               << Option<CLPlatformID>                 (DE_NULL,       "deqp-cl-platform-id",                  "Execute tests on given OpenCL platform (IDs start from 1)",                    "1")
+               << Option<CLDeviceIDs>                  (DE_NULL,       "deqp-cl-device-ids",                   "Execute tests on given CL devices (comma-separated, IDs start from 1)",        parseIntList,   "")
                << Option<CLBuildOptions>               (DE_NULL,       "deqp-cl-build-options",                "Extra build options for OpenCL compiler")
                << Option<EGLDisplayType>               (DE_NULL,       "deqp-egl-display-type",                "EGL native display type")
                << Option<EGLWindowType>                (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<CaseTreeNode*>&       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<CaseTreeNode*>                      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<CaseTreeNode*>   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<char>::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<char>::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<CaseTreeNode*>   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<char>::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<char>::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<CaseTreeNode*>::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<char>::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<opt::LogImages>())
                m_logFlags |= QP_TEST_LOG_EXCLUDE_IMAGES;
 
-       if ((m_cmdLine.getOption<opt::CasePath>().empty()?0:1) +
-               (m_cmdLine.getOption<opt::CaseList>().empty()?0:1) +
-               (m_cmdLine.getOption<opt::CaseListFile>().empty()?0:1) +
+       if ((m_cmdLine.hasOption<opt::CasePath>()?1:0) +
+               (m_cmdLine.hasOption<opt::CaseList>()?1:0) +
+               (m_cmdLine.hasOption<opt::CaseListFile>()?1:0) +
                (m_cmdLine.getOption<opt::StdinCaseList>()?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<opt::CaseList>().empty())
+               if (m_cmdLine.hasOption<opt::CaseList>())
                {
                        std::istringstream str(m_cmdLine.getOption<opt::CaseList>());
 
-                       m_caseTree = parseCaseTree(str);
+                       m_caseTree = parseCaseList(str);
                }
-               else if (!m_cmdLine.getOption<opt::CaseListFile>().empty())
+               else if (m_cmdLine.hasOption<opt::CaseListFile>())
                {
                        std::ifstream in(m_cmdLine.getOption<opt::CaseListFile>().c_str(), std::ios_base::binary);
 
                        if (!in.is_open() || !in.good())
                                throw Exception("Failed to open case list file '" + m_cmdLine.getOption<opt::CaseListFile>() + "'");
 
-                       m_caseTree = parseCaseTree(in);
+                       m_caseTree = parseCaseList(in);
                }
                else if (m_cmdLine.getOption<opt::StdinCaseList>())
                {
-                       m_caseTree = parseCaseTree(std::cin);
+                       m_caseTree = parseCaseList(std::cin);
                }
-               else if (!m_cmdLine.getOption<opt::CasePath>().empty())
+               else if (m_cmdLine.hasOption<opt::CasePath>())
                        m_casePaths = de::MovePtr<const CasePaths>(new CasePaths(m_cmdLine.getOption<opt::CasePath>()));
        }
        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<opt::GLConfigID>();                                }
 int                                            CommandLine::getCLPlatformId                    (void) const    { return m_cmdLine.getOption<opt::CLPlatformID>();                              }
 const std::vector<int>&        CommandLine::getCLDeviceIds                             (void) const    { return m_cmdLine.getOption<opt::CLDeviceIDs>();                               }
-const char*                            CommandLine::getEGLDisplayType                  (void) const    { return m_cmdLine.getOption<opt::EGLDisplayType>().c_str();    }
-const char*                            CommandLine::getEGLWindowType                   (void) const    { return m_cmdLine.getOption<opt::EGLWindowType>().c_str();             }
-const char*                            CommandLine::getEGLPixmapType                   (void) const    { return m_cmdLine.getOption<opt::EGLPixmapType>().c_str();             }
 bool                                   CommandLine::isOutOfMemoryTestEnabled   (void) const    { return m_cmdLine.getOption<opt::TestOOM>();                                   }
 
 const char* CommandLine::getGLContextType (void) const
 {
-       if (!m_cmdLine.getOption<opt::GLContextType>().empty())
+       if (m_cmdLine.hasOption<opt::GLContextType>())
                return m_cmdLine.getOption<opt::GLContextType>().c_str();
        else
                return DE_NULL;
 }
 const char* CommandLine::getGLConfigName (void) const
 {
-       if (!m_cmdLine.getOption<opt::GLConfigName>().empty())
+       if (m_cmdLine.hasOption<opt::GLConfigName>())
                return m_cmdLine.getOption<opt::GLConfigName>().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<opt::GLContextFlags>().empty())
+       if (m_cmdLine.hasOption<opt::GLContextFlags>())
                return m_cmdLine.getOption<opt::GLContextFlags>().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<opt::CLBuildOptions>().empty())
+       if (m_cmdLine.hasOption<opt::CLBuildOptions>())
                return m_cmdLine.getOption<opt::CLBuildOptions>().c_str();
        else
                return DE_NULL;
 }
 
-static bool checkTestGroupName (const CaseTreeNode* node, const char* groupName)
+const char* CommandLine::getEGLDisplayType (void) const
 {
-       for (vector<CaseTreeNode*>::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<opt::EGLDisplayType>())
+               return m_cmdLine.getOption<opt::EGLDisplayType>().c_str();
+       else
+               return DE_NULL;
 }
 
-static bool checkTestCaseName (const CaseTreeNode* node, const char* caseName)
+const char* CommandLine::getEGLWindowType (void) const
 {
-       for (vector<CaseTreeNode*>::const_iterator childIter = node->getChildren().begin(); childIter != node->getChildren().end(); ++childIter)
-       {
-               const CaseTreeNode* const child = *childIter;
+       if (m_cmdLine.hasOption<opt::EGLWindowType>())
+               return m_cmdLine.getOption<opt::EGLWindowType>().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<opt::EGLPixmapType>())
+               return m_cmdLine.getOption<opt::EGLPixmapType>().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
index 2138e26..a8aeeb7 100644 (file)
@@ -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;
index f333f22..431f7d1 100644 (file)
@@ -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
  *
index dd2b4e2..d601780 100644 (file)
@@ -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<Help>());
@@ -332,10 +338,11 @@ void parseType<int> (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<TestStringOpt> ("s",   "string",       "String option")
-                          << Option<TestIntOpt>        ("i",   "int",          "Int option")
-                          << Option<TestBoolOpt>       ("b",   "bool",         "Test boolean flag")
-                          << Option<TestNamedOpt>      ("n",   "named",        "Test named opt",       DE_ARRAY_BEGIN(s_namedValues),  DE_ARRAY_END(s_namedValues),    "one");
+               parser << Option<TestStringOpt>         ("s",   "string",       "String option")
+                          << Option<TestStringDefOpt>  ("x",   "xyz",          "String option w/ default value",       "foo")
+                          << Option<TestIntOpt>                ("i",   "int",          "Int option")
+                          << Option<TestBoolOpt>               ("b",   "bool",         "Test boolean flag")
+                          << Option<TestNamedOpt>              ("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<TestStringOpt>() == "");
-                       DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 0);
-                       DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>() == false);
+                       DE_TEST_ASSERT(!cmdLine.hasOption<TestStringOpt>());
+                       DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
                        DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == 1);
+                       DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>() == false);
+                       DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "foo");
                }
 
                // Basic parsing
@@ -437,6 +446,7 @@ void selfTest (void)
                        DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 9);
                        DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
                        DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == ~0ull);
+                       DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "foo");
                }
 
                // End of argument list (--)
@@ -451,7 +461,7 @@ void selfTest (void)
 
                        DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "foo");
                        DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
-                       DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 0); // Not specified
+                       DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
 
                        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<TestStringOpt>() == "--");
                        DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
-                       DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 0); // Not specified
+                       DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
 
                        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<TestStringOpt>() == "");
+                       DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "");
                        DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
                }
        }
index 4f19aac..50bfca5 100644 (file)
@@ -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<typename Name>
        void                            set                                             (typename TypedFieldTraits<Name>::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<typename OptType>
-void dispatchGetDefault (TypedFieldMap* dst)
+void dispatchSetDefault (TypedFieldMap* dst)
 {
        typename OptTraits<OptType>::ValueType* value = new typename OptTraits<OptType>::ValueType();
        try
@@ -384,7 +384,9 @@ void Parser::addOption (const Option<OptType>& option)
        opt.namedValuesEnd              = (const void*)option.namedValuesEnd;
        opt.namedValueStride    = sizeof(*option.namedValues);
        opt.dispatchParse               = dispatchParse<OptType>;
-       opt.getDefault                  = dispatchGetDefault<OptType>;
+
+       if (opt.isFlag)
+               opt.setDefault          = dispatchSetDefault<OptType>;
 
        addOption(opt);
 }
@@ -401,8 +403,11 @@ public:
        const vector<string>&           getArgs                 (void) const    { return m_args;        }
 
        template<typename Option>
+       bool                                            hasOption               (void) const    { return m_options.contains<Option>();  }
+
+       template<typename Option>
        const typename TypedFieldTraits<Option>::ValueType&
-                                                               getOption               (void) const    { return m_options.get<Option>(); }
+                                                               getOption               (void) const    { return m_options.get<Option>();               }
 
 private:
        TypedFieldMap                           m_options;
index 6a23acc..bf9c1c7 100644 (file)
@@ -742,7 +742,7 @@ namespace
 template<typename Factory>
 const Factory* selectFactory (const tcu::FactoryRegistry<Factory>& registry, const char* objectTypeName, const char* cmdLineArg)
 {
-       if (cmdLineArg[0] != 0)
+       if (cmdLineArg)
        {
                const Factory* factory = registry.getFactoryByName(cmdLineArg);
 
index f1ff130..06ee088 100644 (file)
@@ -194,9 +194,9 @@ PackageContext::PackageContext (tcu::TestContext& testCtx)
        , m_caseWrapper (DE_NULL)
 {
        const eglu::NativeDisplayFactoryRegistry&       dpyFactoryRegistry      = testCtx.getPlatform().getEGLPlatform().getNativeDisplayFactoryRegistry();
-       const std::string                                                       displayFactoryName      = testCtx.getCommandLine().getEGLDisplayType();
-       const std::string                                                       windowFactoryName       = testCtx.getCommandLine().getEGLWindowType();
-       const std::string                                                       pixmapFactoryName       = testCtx.getCommandLine().getEGLPixmapType();
+       const char* const                                                       displayFactoryName      = testCtx.getCommandLine().getEGLDisplayType();
+       const char* const                                                       windowFactoryName       = testCtx.getCommandLine().getEGLWindowType();
+       const char* const                                                       pixmapFactoryName       = testCtx.getCommandLine().getEGLPixmapType();
 
        const eglu::NativeDisplayFactory*                       displayFactory          = DE_NULL;
        const eglu::NativeWindowFactory*                        windowFactory           = DE_NULL;
@@ -208,15 +208,15 @@ PackageContext::PackageContext (tcu::TestContext& testCtx)
                throw tcu::NotSupportedError("Platform doesn't have EGL any native display factories", DE_NULL, __FILE__, __LINE__);
        }
 
-       if (displayFactoryName.empty())
+       if (!displayFactoryName)
                displayFactory = dpyFactoryRegistry.getDefaultFactory();
        else
        {
-               displayFactory = dpyFactoryRegistry.getFactoryByName(displayFactoryName.c_str());
+               displayFactory = dpyFactoryRegistry.getFactoryByName(displayFactoryName);
 
                if (!displayFactory)
                {
-                       tcu::print("ERROR: Unknown/unsupported EGL native display type '%s'\n", displayFactoryName.c_str());
+                       tcu::print("ERROR: Unknown/unsupported EGL native display type '%s'\n", displayFactoryName);
                        tcu::print("Supported EGL native display types:\n");
 
                        for (int factoryNdx = 0; factoryNdx < (int)dpyFactoryRegistry.getFactoryCount(); factoryNdx++)
@@ -227,7 +227,7 @@ PackageContext::PackageContext (tcu::TestContext& testCtx)
                                tcu::print("  %s: %s\n", name, desc);
                        }
 
-                       throw tcu::NotSupportedError(("Unknown EGL native display type '" + displayFactoryName + "'.").c_str(), DE_NULL, __FILE__, __LINE__);
+                       throw tcu::NotSupportedError(("Unknown EGL native display type '" + std::string(displayFactoryName) + "'.").c_str(), DE_NULL, __FILE__, __LINE__);
                }
        }
 
@@ -235,13 +235,13 @@ PackageContext::PackageContext (tcu::TestContext& testCtx)
 
        if (!displayFactory->getNativeWindowRegistry().empty())
        {
-               windowFactory = windowFactoryName.empty() ? displayFactory->getNativeWindowRegistry().getDefaultFactory()
-                                                                                                 : displayFactory->getNativeWindowRegistry().getFactoryByName(windowFactoryName.c_str());
+               windowFactory = windowFactoryName ? displayFactory->getNativeWindowRegistry().getFactoryByName(windowFactoryName)
+                                                                                 : displayFactory->getNativeWindowRegistry().getDefaultFactory();
 
                if (!windowFactory)
                {
-                       DE_ASSERT(!windowFactoryName.empty());
-                       tcu::print("ERROR: Unknown/unsupported EGL native window type '%s'\n", windowFactoryName.c_str());
+                       DE_ASSERT(windowFactoryName);
+                       tcu::print("ERROR: Unknown/unsupported EGL native window type '%s'\n", windowFactoryName);
                        tcu::print("Supported EGL native window types for native display '%s':\n", displayFactory->getName());
 
                        for (int factoryNdx = 0; factoryNdx < (int)displayFactory->getNativeWindowRegistry().getFactoryCount(); factoryNdx++)
@@ -252,7 +252,7 @@ PackageContext::PackageContext (tcu::TestContext& testCtx)
                                tcu::print("  %s: %s\n", name, desc);
                        }
 
-                       throw tcu::NotSupportedError(("Unknown EGL native window type '" + windowFactoryName + "'").c_str(), DE_NULL, __FILE__, __LINE__);
+                       throw tcu::NotSupportedError(("Unknown EGL native window type '" + std::string(windowFactoryName) + "'").c_str(), DE_NULL, __FILE__, __LINE__);
                }
        }
        else
@@ -260,13 +260,13 @@ PackageContext::PackageContext (tcu::TestContext& testCtx)
 
        if (!displayFactory->getNativePixmapRegistry().empty())
        {
-               pixmapFactory = pixmapFactoryName.empty() ? displayFactory->getNativePixmapRegistry().getDefaultFactory()
-                                                                                                 : displayFactory->getNativePixmapRegistry().getFactoryByName(pixmapFactoryName.c_str());
+               pixmapFactory = pixmapFactoryName ? displayFactory->getNativePixmapRegistry().getFactoryByName(pixmapFactoryName)
+                                                                                 : displayFactory->getNativePixmapRegistry().getDefaultFactory();
 
                if (!pixmapFactory)
                {
-                       DE_ASSERT(!pixmapFactoryName.empty());
-                       tcu::print("ERROR: Unknown/unsupported EGL native pixmap type '%s'\n", pixmapFactoryName.c_str());
+                       DE_ASSERT(pixmapFactoryName);
+                       tcu::print("ERROR: Unknown/unsupported EGL native pixmap type '%s'\n", pixmapFactoryName);
                        tcu::print("Supported EGL native pixmap types for native display '%s':\n", displayFactory->getName());
 
                        for (int factoryNdx = 0; factoryNdx < (int)displayFactory->getNativePixmapRegistry().getFactoryCount(); factoryNdx++)
@@ -277,7 +277,7 @@ PackageContext::PackageContext (tcu::TestContext& testCtx)
                                tcu::print("  %s: %s\n", name, desc);
                        }
 
-                       throw tcu::NotSupportedError(("Unknown EGL native pixmap type '" + pixmapFactoryName + "'").c_str(), DE_NULL, __FILE__, __LINE__);
+                       throw tcu::NotSupportedError(("Unknown EGL native pixmap type '" + std::string(pixmapFactoryName) + "'").c_str(), DE_NULL, __FILE__, __LINE__);
                }
        }
        else
index 3882c45..5776cdc 100644 (file)
  *//*--------------------------------------------------------------------*/
 
 #include "ditFrameworkTests.hpp"
-
 #include "tcuFloatFormat.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuCommandLine.hpp"
 
 namespace dit
 {
 
+namespace
+{
+
+using std::string;
+using std::vector;
+using tcu::TestLog;
+
+struct MatchCase
+{
+       enum Expected { NO_MATCH, MATCH_GROUP, MATCH_CASE, EXPECTED_LAST };
+
+       const char*     path;
+       Expected        expected;
+};
+
+const char* getMatchCaseExpectedDesc (MatchCase::Expected expected)
+{
+       static const char* descs[] =
+       {
+               "no match",
+               "group to match",
+               "case to match"
+       };
+       return de::getSizedArrayElement<MatchCase::EXPECTED_LAST>(descs, expected);
+}
+
+class CaseListParserCase : public tcu::TestCase
+{
+public:
+       CaseListParserCase (tcu::TestContext& testCtx, const char* name, const char* caseList, const MatchCase* subCases, int numSubCases)
+               : tcu::TestCase (testCtx, name, "")
+               , m_caseList    (caseList)
+               , m_subCases    (subCases)
+               , m_numSubCases (numSubCases)
+       {
+       }
+
+       IterateResult iterate (void)
+       {
+               TestLog&                        log             = m_testCtx.getLog();
+               tcu::CommandLine        cmdLine;
+               int                                     numPass = 0;
+
+               log << TestLog::Message << "Input:\n\"" << m_caseList << "\"" << TestLog::EndMessage;
+
+               {
+                       const char* argv[] =
+                       {
+                               "deqp",
+                               "--deqp-caselist",
+                               m_caseList
+                       };
+
+                       if (!cmdLine.parse(DE_LENGTH_OF_ARRAY(argv), argv))
+                               TCU_FAIL("Failed to parse case list");
+               }
+
+               for (int subCaseNdx = 0; subCaseNdx < m_numSubCases; subCaseNdx++)
+               {
+                       const MatchCase&        curCase         = m_subCases[subCaseNdx];
+                       bool                            matchGroup;
+                       bool                            matchCase;
+
+                       log << TestLog::Message << "Checking \"" << curCase.path << "\""
+                                                                       << ", expecting " << getMatchCaseExpectedDesc(curCase.expected)
+                               << TestLog::EndMessage;
+
+                       matchGroup      = cmdLine.checkTestGroupName(curCase.path);
+                       matchCase       = cmdLine.checkTestCaseName(curCase.path);
+
+                       if ((matchGroup == (curCase.expected == MatchCase::MATCH_GROUP)) &&
+                               (matchCase      == (curCase.expected == MatchCase::MATCH_CASE)))
+                       {
+                               log << TestLog::Message << "   pass" << TestLog::EndMessage;
+                               numPass += 1;
+                       }
+                       else
+                               log << TestLog::Message << "   FAIL!" << TestLog::EndMessage;
+               }
+
+               m_testCtx.setTestResult((numPass == m_numSubCases) ? QP_TEST_RESULT_PASS        : QP_TEST_RESULT_FAIL,
+                                                               (numPass == m_numSubCases) ? "All passed"                       : "Unexpected match result");
+
+               return STOP;
+       }
+
+private:
+       const char* const                       m_caseList;
+       const MatchCase* const          m_subCases;
+       const int                                       m_numSubCases;
+};
+
+class NegativeCaseListCase : public tcu::TestCase
+{
+public:
+       NegativeCaseListCase (tcu::TestContext& testCtx, const char* name, const char* caseList)
+               : tcu::TestCase (testCtx, name, "")
+               , m_caseList    (caseList)
+       {
+       }
+
+       IterateResult iterate (void)
+       {
+               TestLog&                        log             = m_testCtx.getLog();
+               tcu::CommandLine        cmdLine;
+
+               log << TestLog::Message << "Input:\n\"" << m_caseList << "\"" << TestLog::EndMessage;
+
+               {
+                       const char* argv[] =
+                       {
+                               "deqp",
+                               "--deqp-caselist",
+                               m_caseList
+                       };
+
+                       if (cmdLine.parse(DE_LENGTH_OF_ARRAY(argv), argv))
+                               m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Parsing passed, should have failed");
+                       else
+                               m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Parsing failed as expected");
+               }
+
+               return STOP;
+       }
+
+private:
+       const char* const       m_caseList;
+};
+
+class TrieParserTests : public tcu::TestCaseGroup
+{
+public:
+       TrieParserTests (tcu::TestContext& testCtx)
+               : tcu::TestCaseGroup(testCtx, "trie", "Test case trie parser tests")
+       {
+       }
+
+       void init (void)
+       {
+               {
+                       static const char* const        caseList        = "{test}";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "test",               MatchCase::MATCH_CASE   },
+                               { "test.cd",    MatchCase::NO_MATCH             },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "single_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "{a{b}}";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "simple_group_1", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "{a{b,c}}";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "simple_group_2", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "{a{b},c{d,e}}";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.c",        MatchCase::NO_MATCH             },
+                               { "a.d",        MatchCase::NO_MATCH             },
+                               { "a.e",        MatchCase::NO_MATCH             },
+                               { "c",          MatchCase::MATCH_GROUP  },
+                               { "c.b",        MatchCase::NO_MATCH             },
+                               { "c.d",        MatchCase::MATCH_CASE   },
+                               { "c.e",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "two_groups", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "{a,c{d,e}}";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_CASE   },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::NO_MATCH             },
+                               { "a.d",        MatchCase::NO_MATCH             },
+                               { "a.e",        MatchCase::NO_MATCH             },
+                               { "c",          MatchCase::MATCH_GROUP  },
+                               { "c.b",        MatchCase::NO_MATCH             },
+                               { "c.d",        MatchCase::MATCH_CASE   },
+                               { "c.e",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "case_group", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "{c{d,e},a}";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_CASE   },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::NO_MATCH             },
+                               { "a.d",        MatchCase::NO_MATCH             },
+                               { "a.e",        MatchCase::NO_MATCH             },
+                               { "c",          MatchCase::MATCH_GROUP  },
+                               { "c.b",        MatchCase::NO_MATCH             },
+                               { "c.d",        MatchCase::MATCH_CASE   },
+                               { "c.e",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "group_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+
+               // Negative tests
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_string",                    ""));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_root",                              "{}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_group",                             "{test{}}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name_1",              "{{}}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name_2",              "{{test}}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_1",             "{"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_2",             "{test"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_3",             "{test,"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_4",             "{test{a}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_5",             "{a,b"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_1",    "{test{"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_2",    "{test{a"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_3",    "{test{a,"));
+               addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_4",    "{test{a,b"));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_1",               "{a,,b}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_2",               "{,b}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_3",               "{a,}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "no_separator",                    "{a{b}c}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "invalid_char_1",                  "{a.b}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "invalid_char_2",                  "{a[]}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "trailing_char_1",                 "{a}}"));
+               addChild(new NegativeCaseListCase(m_testCtx, "trailing_char_2",                 "{a}x"));
+       }
+};
+
+class ListParserTests : public tcu::TestCaseGroup
+{
+public:
+       ListParserTests (tcu::TestContext& testCtx)
+               : tcu::TestCaseGroup(testCtx, "list", "Test case list parser tests")
+       {
+       }
+
+       void init (void)
+       {
+               {
+                       static const char* const        caseList        = "test";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "test",               MatchCase::MATCH_CASE   },
+                               { "test.cd",    MatchCase::NO_MATCH             },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "single_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "simple_group_1", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b\na.c";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "simple_group_2", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b\na.c";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "separator_ln", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b\ra.c";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "separator_cr", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b\r\na.c";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "separator_crlf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b\na.c\n";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "end_ln", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b\na.c\r";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "end_cr", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b\na.c\r\n";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.a",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "end_crlf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b\nc.d\nc.e";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_GROUP  },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::MATCH_CASE   },
+                               { "a.c",        MatchCase::NO_MATCH             },
+                               { "a.d",        MatchCase::NO_MATCH             },
+                               { "a.e",        MatchCase::NO_MATCH             },
+                               { "c",          MatchCase::MATCH_GROUP  },
+                               { "c.b",        MatchCase::NO_MATCH             },
+                               { "c.d",        MatchCase::MATCH_CASE   },
+                               { "c.e",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "two_groups", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a\nc.d\nc.e";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_CASE   },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::NO_MATCH             },
+                               { "a.d",        MatchCase::NO_MATCH             },
+                               { "a.e",        MatchCase::NO_MATCH             },
+                               { "c",          MatchCase::MATCH_GROUP  },
+                               { "c.b",        MatchCase::NO_MATCH             },
+                               { "c.d",        MatchCase::MATCH_CASE   },
+                               { "c.e",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "case_group", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "c.d\nc.e\na";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",          MatchCase::MATCH_CASE   },
+                               { "b",          MatchCase::NO_MATCH             },
+                               { "a.b",        MatchCase::NO_MATCH             },
+                               { "a.c",        MatchCase::NO_MATCH             },
+                               { "a.d",        MatchCase::NO_MATCH             },
+                               { "a.e",        MatchCase::NO_MATCH             },
+                               { "c",          MatchCase::MATCH_GROUP  },
+                               { "c.b",        MatchCase::NO_MATCH             },
+                               { "c.d",        MatchCase::MATCH_CASE   },
+                               { "c.e",        MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "group_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        = "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.x";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",                                                                                          MatchCase::MATCH_GROUP  },
+                               { "b",                                                                                          MatchCase::NO_MATCH             },
+                               { "a.b",                                                                                        MatchCase::MATCH_GROUP  },
+                               { "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.x",      MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "long_name", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        =
+                               "a.b.c.d.e\n"
+                               "a.b.c.f\n"
+                               "x.y.z\n"
+                               "a.b.c.d.g\n"
+                               "a.b.c.x\n";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",                          MatchCase::MATCH_GROUP  },
+                               { "a.b",                        MatchCase::MATCH_GROUP  },
+                               { "a.b.c.d.e",          MatchCase::MATCH_CASE   },
+                               { "a.b.c.d.g",          MatchCase::MATCH_CASE   },
+                               { "x.y",                        MatchCase::MATCH_GROUP  },
+                               { "x.y.z",                      MatchCase::MATCH_CASE   },
+                               { "a.b.c.f",            MatchCase::MATCH_CASE   },
+                               { "a.b.c.x",            MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "partial_prefix", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+               {
+                       static const char* const        caseList        =
+                               "a.a.c.d\n"
+                               "a.b.c.d\n";
+                       static const MatchCase          subCases[]      =
+                       {
+                               { "a",                          MatchCase::MATCH_GROUP  },
+                               { "a.a",                        MatchCase::MATCH_GROUP  },
+                               { "a.b.c.d",            MatchCase::MATCH_CASE   },
+                               { "a.b.c.d",            MatchCase::MATCH_CASE   },
+                       };
+                       addChild(new CaseListParserCase(m_testCtx, "reparenting", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
+               }
+
+               // Negative tests
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_string",                    ""));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_line",                              "\n"));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name",                ".test"));
+               addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name",                 "test."));
+       }
+};
+
+class CaseListParserTests : public tcu::TestCaseGroup
+{
+public:
+       CaseListParserTests (tcu::TestContext& testCtx)
+               : tcu::TestCaseGroup(testCtx, "case_list_parser", "Test case list parser tests")
+       {
+       }
+
+       void init (void)
+       {
+               addChild(new TrieParserTests(m_testCtx));
+               addChild(new ListParserTests(m_testCtx));
+       }
+};
+
 class CommonFrameworkTests : public tcu::TestCaseGroup
 {
 public:
@@ -40,9 +523,12 @@ public:
        {
                addChild(new SelfCheckCase(m_testCtx, "float_format","tcu::FloatFormat_selfTest()",
                                                                   tcu::FloatFormat_selfTest));
+               addChild(new CaseListParserTests(m_testCtx));
        }
 };
 
+} // anonymous
+
 FrameworkTests::FrameworkTests (tcu::TestContext& testCtx)
        : tcu::TestCaseGroup(testCtx, "framework", "Miscellaneous framework tests")
 {