1 /*-------------------------------------------------------------------------
2 * drawElements Quality Program Tester Core
3 * ----------------------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Command line parsing.
22 *//*--------------------------------------------------------------------*/
23
24 #include "tcuCommandLine.hpp"
25 #include "tcuPlatform.hpp"
26 #include "tcuTestCase.hpp"
27 #include "tcuResource.hpp"
28 #include "deFilePath.hpp"
29 #include "deStringUtil.hpp"
30 #include "deString.h"
31 #include "deInt32.h"
32 #include "deCommandLine.h"
33 #include "qpTestLog.h"
34 #include "qpDebugOut.h"
35
36 #include <string>
37 #include <vector>
38 #include <sstream>
39 #include <fstream>
40 #include <iostream>
41 #include <algorithm>
42
43 using std::string;
44 using std::vector;
45
46 // OOM tests are enabled by default only on platforms that don't do memory overcommit (Win32)
47 #if (DE_OS == DE_OS_WIN32)
48 # define TEST_OOM_DEFAULT "enable"
49 #else
50 # define TEST_OOM_DEFAULT "disable"
51 #endif
52
53 namespace tcu
54 {
55
56 namespace opt
57 {
58
59 DE_DECLARE_COMMAND_LINE_OPT(CasePath, std::string);
60 DE_DECLARE_COMMAND_LINE_OPT(CaseList, std::string);
61 DE_DECLARE_COMMAND_LINE_OPT(CaseListFile, std::string);
62 DE_DECLARE_COMMAND_LINE_OPT(CaseListResource, std::string);
63 DE_DECLARE_COMMAND_LINE_OPT(StdinCaseList, bool);
64 DE_DECLARE_COMMAND_LINE_OPT(LogFilename, std::string);
65 DE_DECLARE_COMMAND_LINE_OPT(RunMode, tcu::RunMode);
66 DE_DECLARE_COMMAND_LINE_OPT(ExportFilenamePattern, std::string);
67 DE_DECLARE_COMMAND_LINE_OPT(WatchDog, bool);
68 DE_DECLARE_COMMAND_LINE_OPT(CrashHandler, bool);
69 DE_DECLARE_COMMAND_LINE_OPT(BaseSeed, int);
70 DE_DECLARE_COMMAND_LINE_OPT(TestIterationCount, int);
71 DE_DECLARE_COMMAND_LINE_OPT(Visibility, WindowVisibility);
72 DE_DECLARE_COMMAND_LINE_OPT(SurfaceWidth, int);
73 DE_DECLARE_COMMAND_LINE_OPT(SurfaceHeight, int);
74 DE_DECLARE_COMMAND_LINE_OPT(SurfaceType, tcu::SurfaceType);
75 DE_DECLARE_COMMAND_LINE_OPT(ScreenRotation, tcu::ScreenRotation);
76 DE_DECLARE_COMMAND_LINE_OPT(GLContextType, std::string);
77 DE_DECLARE_COMMAND_LINE_OPT(GLConfigID, int);
78 DE_DECLARE_COMMAND_LINE_OPT(GLConfigName, std::string);
79 DE_DECLARE_COMMAND_LINE_OPT(GLContextFlags, std::string);
80 DE_DECLARE_COMMAND_LINE_OPT(CLPlatformID, int);
81 DE_DECLARE_COMMAND_LINE_OPT(CLDeviceIDs, std::vector<int>);
82 DE_DECLARE_COMMAND_LINE_OPT(CLBuildOptions, std::string);
83 DE_DECLARE_COMMAND_LINE_OPT(EGLDisplayType, std::string);
84 DE_DECLARE_COMMAND_LINE_OPT(EGLWindowType, std::string);
85 DE_DECLARE_COMMAND_LINE_OPT(EGLPixmapType, std::string);
86 DE_DECLARE_COMMAND_LINE_OPT(LogImages, bool);
87 DE_DECLARE_COMMAND_LINE_OPT(LogShaderSources, bool);
88 DE_DECLARE_COMMAND_LINE_OPT(TestOOM, bool);
89 DE_DECLARE_COMMAND_LINE_OPT(ArchiveDir, std::string);
90 DE_DECLARE_COMMAND_LINE_OPT(VKDeviceID, int);
91 DE_DECLARE_COMMAND_LINE_OPT(VKDeviceGroupID, int);
92 DE_DECLARE_COMMAND_LINE_OPT(LogFlush, bool);
93 DE_DECLARE_COMMAND_LINE_OPT(Validation, bool);
94 DE_DECLARE_COMMAND_LINE_OPT(PrintValidationErrors, bool);
95 DE_DECLARE_COMMAND_LINE_OPT(ShaderCache, bool);
96 DE_DECLARE_COMMAND_LINE_OPT(ShaderCacheFilename, std::string);
97 DE_DECLARE_COMMAND_LINE_OPT(Optimization, int);
98 DE_DECLARE_COMMAND_LINE_OPT(OptimizeSpirv, bool);
99 DE_DECLARE_COMMAND_LINE_OPT(ShaderCacheTruncate, bool);
100 DE_DECLARE_COMMAND_LINE_OPT(RenderDoc, bool);
101 DE_DECLARE_COMMAND_LINE_OPT(CaseFraction, std::vector<int>);
102 DE_DECLARE_COMMAND_LINE_OPT(CaseFractionMandatoryTests, std::string);
103 DE_DECLARE_COMMAND_LINE_OPT(WaiverFile, std::string);
104
parseIntList(const char * src,std::vector<int> * dst)105 static void parseIntList (const char* src, std::vector<int>* dst)
106 {
107 std::istringstream str (src);
108 std::string val;
109
110 while (std::getline(str, val, ','))
111 {
112 int intVal = 0;
113 de::cmdline::parseType(val.c_str(), &intVal);
114 dst->push_back(intVal);
115 }
116 }
117
registerOptions(de::cmdline::Parser & parser)118 void registerOptions (de::cmdline::Parser& parser)
119 {
120 using de::cmdline::Option;
121 using de::cmdline::NamedValue;
122
123 static const NamedValue<bool> s_enableNames[] =
124 {
125 { "enable", true },
126 { "disable", false }
127 };
128 static const NamedValue<tcu::RunMode> s_runModes[] =
129 {
130 { "execute", RUNMODE_EXECUTE },
131 { "xml-caselist", RUNMODE_DUMP_XML_CASELIST },
132 { "txt-caselist", RUNMODE_DUMP_TEXT_CASELIST },
133 { "stdout-caselist",RUNMODE_DUMP_STDOUT_CASELIST}
134 };
135 static const NamedValue<WindowVisibility> s_visibilites[] =
136 {
137 { "windowed", WINDOWVISIBILITY_WINDOWED },
138 { "fullscreen", WINDOWVISIBILITY_FULLSCREEN },
139 { "hidden", WINDOWVISIBILITY_HIDDEN }
140 };
141 static const NamedValue<tcu::SurfaceType> s_surfaceTypes[] =
142 {
143 { "window", SURFACETYPE_WINDOW },
144 { "pixmap", SURFACETYPE_OFFSCREEN_NATIVE },
145 { "pbuffer", SURFACETYPE_OFFSCREEN_GENERIC },
146 { "fbo", SURFACETYPE_FBO }
147 };
148 static const NamedValue<tcu::ScreenRotation> s_screenRotations[] =
149 {
150 { "unspecified", SCREENROTATION_UNSPECIFIED },
151 { "0", SCREENROTATION_0 },
152 { "90", SCREENROTATION_90 },
153 { "180", SCREENROTATION_180 },
154 { "270", SCREENROTATION_270 }
155 };
156
157 parser
158 << Option<CasePath> ("n", "deqp-case", "Test case(s) to run, supports wildcards (e.g. dEQP-GLES2.info.*)")
159 << Option<CaseList> (DE_NULL, "deqp-caselist", "Case list to run in trie format (e.g. {dEQP-GLES2{info{version,renderer}}})")
160 << Option<CaseListFile> (DE_NULL, "deqp-caselist-file", "Read case list (in trie format) from given file")
161 << Option<CaseListResource> (DE_NULL, "deqp-caselist-resource", "Read case list (in trie format) from given file located application's assets")
162 << Option<StdinCaseList> (DE_NULL, "deqp-stdin-caselist", "Read case list (in trie format) from stdin")
163 << Option<LogFilename> (DE_NULL, "deqp-log-filename", "Write test results to given file", "TestResults.qpa")
164 << Option<RunMode> (DE_NULL, "deqp-runmode", "Execute tests, or write list of test cases into a file",
165 s_runModes, "execute")
166 << Option<ExportFilenamePattern> (DE_NULL, "deqp-caselist-export-file", "Set the target file name pattern for caselist export", "${packageName}-cases.${typeExtension}")
167 << Option<WatchDog> (DE_NULL, "deqp-watchdog", "Enable test watchdog", s_enableNames, "disable")
168 << Option<CrashHandler> (DE_NULL, "deqp-crashhandler", "Enable crash handling", s_enableNames, "disable")
169 << Option<BaseSeed> (DE_NULL, "deqp-base-seed", "Base seed for test cases that use randomization", "0")
170 << Option<TestIterationCount> (DE_NULL, "deqp-test-iteration-count", "Iteration count for cases that support variable number of iterations", "0")
171 << Option<Visibility> (DE_NULL, "deqp-visibility", "Default test window visibility", s_visibilites, "windowed")
172 << Option<SurfaceWidth> (DE_NULL, "deqp-surface-width", "Use given surface width if possible", "-1")
173 << Option<SurfaceHeight> (DE_NULL, "deqp-surface-height", "Use given surface height if possible", "-1")
174 << Option<SurfaceType> (DE_NULL, "deqp-surface-type", "Use given surface type", s_surfaceTypes, "window")
175 << Option<ScreenRotation> (DE_NULL, "deqp-screen-rotation", "Screen rotation for platforms that support it", s_screenRotations, "0")
176 << Option<GLContextType> (DE_NULL, "deqp-gl-context-type", "OpenGL context type for platforms that support multiple")
177 << Option<GLConfigID> (DE_NULL, "deqp-gl-config-id", "OpenGL (ES) render config ID (EGL config id on EGL platforms)", "-1")
178 << Option<GLConfigName> (DE_NULL, "deqp-gl-config-name", "Symbolic OpenGL (ES) render config name")
179 << Option<GLContextFlags> (DE_NULL, "deqp-gl-context-flags", "OpenGL context flags (comma-separated, supports debug and robust)")
180 << Option<CLPlatformID> (DE_NULL, "deqp-cl-platform-id", "Execute tests on given OpenCL platform (IDs start from 1)", "1")
181 << Option<CLDeviceIDs> (DE_NULL, "deqp-cl-device-ids", "Execute tests on given CL devices (comma-separated, IDs start from 1)", parseIntList, "")
182 << Option<CLBuildOptions> (DE_NULL, "deqp-cl-build-options", "Extra build options for OpenCL compiler")
183 << Option<EGLDisplayType> (DE_NULL, "deqp-egl-display-type", "EGL native display type")
184 << Option<EGLWindowType> (DE_NULL, "deqp-egl-window-type", "EGL native window type")
185 << Option<EGLPixmapType> (DE_NULL, "deqp-egl-pixmap-type", "EGL native pixmap type")
186 << Option<VKDeviceID> (DE_NULL, "deqp-vk-device-id", "Vulkan device ID (IDs start from 1)", "1")
187 << Option<VKDeviceGroupID> (DE_NULL, "deqp-vk-device-group-id", "Vulkan device Group ID (IDs start from 1)", "1")
188 << Option<LogImages> (DE_NULL, "deqp-log-images", "Enable or disable logging of result images", s_enableNames, "enable")
189 << Option<LogShaderSources> (DE_NULL, "deqp-log-shader-sources", "Enable or disable logging of shader sources", s_enableNames, "enable")
190 << Option<TestOOM> (DE_NULL, "deqp-test-oom", "Run tests that exhaust memory on purpose", s_enableNames, TEST_OOM_DEFAULT)
191 << Option<ArchiveDir> (DE_NULL, "deqp-archive-dir", "Path to test resource files", ".")
192 << Option<LogFlush> (DE_NULL, "deqp-log-flush", "Enable or disable log file fflush", s_enableNames, "enable")
193 << Option<Validation> (DE_NULL, "deqp-validation", "Enable or disable test case validation", s_enableNames, "disable")
194 << Option<PrintValidationErrors> (DE_NULL, "deqp-print-validation-errors", "Print validation errors to standard error")
195 << Option<Optimization> (DE_NULL, "deqp-optimization-recipe", "Shader optimization recipe (0=disabled, 1=performance, 2=size)", "0")
196 << Option<OptimizeSpirv> (DE_NULL, "deqp-optimize-spirv", "Apply optimization to spir-v shaders as well", s_enableNames, "disable")
197 << Option<ShaderCache> (DE_NULL, "deqp-shadercache", "Enable or disable shader cache", s_enableNames, "enable")
198 << Option<ShaderCacheFilename> (DE_NULL, "deqp-shadercache-filename", "Write shader cache to given file", "shadercache.bin")
199 << Option<ShaderCacheTruncate> (DE_NULL, "deqp-shadercache-truncate", "Truncate shader cache before running tests", s_enableNames, "enable")
200 << Option<RenderDoc> (DE_NULL, "deqp-renderdoc", "Enable RenderDoc frame markers", s_enableNames, "disable")
201 << Option<CaseFraction> (DE_NULL, "deqp-fraction", "Run a fraction of the test cases (e.g. N,M means run group%M==N)", parseIntList, "")
202 << Option<CaseFractionMandatoryTests> (DE_NULL, "deqp-fraction-mandatory-caselist-file", "Case list file that must be run for each fraction", "")
203 << Option<WaiverFile> (DE_NULL, "deqp-waiver-file", "Read waived tests from given file", "");
204 }
205
registerLegacyOptions(de::cmdline::Parser & parser)206 void registerLegacyOptions (de::cmdline::Parser& parser)
207 {
208 using de::cmdline::Option;
209
210 parser
211 << Option<GLConfigID> (DE_NULL, "deqp-egl-config-id", "Legacy name for --deqp-gl-config-id", "-1")
212 << Option<GLConfigName> (DE_NULL, "deqp-egl-config-name", "Legacy name for --deqp-gl-config-name");
213 }
214
215 } // opt
216
217 // \todo [2014-02-13 pyry] This could be useful elsewhere as well.
218 class DebugOutStreambuf : public std::streambuf
219 {
220 public:
221 DebugOutStreambuf (void);
222 ~DebugOutStreambuf (void);
223
224 protected:
225 std::streamsize xsputn (const char* s, std::streamsize count);
226 int overflow (int ch = -1);
227
228 private:
229 void flushLine (void);
230
231 std::ostringstream m_curLine;
232 };
233
DebugOutStreambuf(void)234 DebugOutStreambuf::DebugOutStreambuf (void)
235 {
236 }
237
~DebugOutStreambuf(void)238 DebugOutStreambuf::~DebugOutStreambuf (void)
239 {
240 if (m_curLine.tellp() != std::streampos(0))
241 flushLine();
242 }
243
xsputn(const char * s,std::streamsize count)244 std::streamsize DebugOutStreambuf::xsputn (const char* s, std::streamsize count)
245 {
246 for (std::streamsize pos = 0; pos < count; pos++)
247 {
248 m_curLine.put(s[pos]);
249
250 if (s[pos] == '\n')
251 flushLine();
252 }
253
254 return count;
255 }
256
overflow(int ch)257 int DebugOutStreambuf::overflow (int ch)
258 {
259 if (ch == -1)
260 return -1;
261 else
262 {
263 DE_ASSERT((ch & 0xff) == ch);
264 const char chVal = (char)(deUint8)(ch & 0xff);
265 return xsputn(&chVal, 1) == 1 ? ch : -1;
266 }
267 }
268
flushLine(void)269 void DebugOutStreambuf::flushLine (void)
270 {
271 qpPrint(m_curLine.str().c_str());
272 m_curLine.str("");
273 }
274
275 class CaseTreeNode
276 {
277 public:
CaseTreeNode(const std::string & name)278 CaseTreeNode (const std::string& name) : m_name(name) {}
279 ~CaseTreeNode (void);
280
getName(void) const281 const std::string& getName (void) const { return m_name; }
hasChildren(void) const282 bool hasChildren (void) const { return !m_children.empty(); }
283
284 bool hasChild (const std::string& name) const;
285 const CaseTreeNode* getChild (const std::string& name) const;
286 CaseTreeNode* getChild (const std::string& name);
287
addChild(CaseTreeNode * child)288 void addChild (CaseTreeNode* child) { m_children.push_back(child); }
289
290 private:
291 CaseTreeNode (const CaseTreeNode&);
292 CaseTreeNode& operator= (const CaseTreeNode&);
293
294 enum { NOT_FOUND = -1 };
295
296 // \todo [2014-10-30 pyry] Speed up with hash / sorting
297 int findChildNdx (const std::string& name) const;
298
299 std::string m_name;
300 std::vector<CaseTreeNode*> m_children;
301 };
302
~CaseTreeNode(void)303 CaseTreeNode::~CaseTreeNode (void)
304 {
305 for (vector<CaseTreeNode*>::const_iterator i = m_children.begin(); i != m_children.end(); ++i)
306 delete *i;
307 }
308
findChildNdx(const std::string & name) const309 int CaseTreeNode::findChildNdx (const std::string& name) const
310 {
311 for (int ndx = 0; ndx < (int)m_children.size(); ++ndx)
312 {
313 if (m_children[ndx]->getName() == name)
314 return ndx;
315 }
316 return NOT_FOUND;
317 }
318
hasChild(const std::string & name) const319 inline bool CaseTreeNode::hasChild (const std::string& name) const
320 {
321 return findChildNdx(name) != NOT_FOUND;
322 }
323
getChild(const std::string & name) const324 inline const CaseTreeNode* CaseTreeNode::getChild (const std::string& name) const
325 {
326 const int ndx = findChildNdx(name);
327 return ndx == NOT_FOUND ? DE_NULL : m_children[ndx];
328 }
329
getChild(const std::string & name)330 inline CaseTreeNode* CaseTreeNode::getChild (const std::string& name)
331 {
332 const int ndx = findChildNdx(name);
333 return ndx == NOT_FOUND ? DE_NULL : m_children[ndx];
334 }
335
getCurrentComponentLen(const char * path)336 static int getCurrentComponentLen (const char* path)
337 {
338 int ndx = 0;
339 for (; path[ndx] != 0 && path[ndx] != '.'; ++ndx);
340 return ndx;
341 }
342
findNode(const CaseTreeNode * root,const char * path)343 static const CaseTreeNode* findNode (const CaseTreeNode* root, const char* path)
344 {
345 const CaseTreeNode* curNode = root;
346 const char* curPath = path;
347 int curLen = getCurrentComponentLen(curPath);
348
349 for (;;)
350 {
351 curNode = curNode->getChild(std::string(curPath, curPath+curLen));
352
353 if (!curNode)
354 break;
355
356 curPath += curLen;
357
358 if (curPath[0] == 0)
359 break;
360 else
361 {
362 DE_ASSERT(curPath[0] == '.');
363 curPath += 1;
364 curLen = getCurrentComponentLen(curPath);
365 }
366 }
367
368 return curNode;
369 }
370
parseCaseTrie(CaseTreeNode * root,std::istream & in)371 static void parseCaseTrie (CaseTreeNode* root, std::istream& in)
372 {
373 vector<CaseTreeNode*> nodeStack;
374 string curName;
375 bool expectNode = true;
376
377 if (in.get() != '{')
378 throw std::invalid_argument("Malformed case trie");
379
380 nodeStack.push_back(root);
381
382 while (!nodeStack.empty())
383 {
384 const int curChr = in.get();
385
386 if (curChr == std::char_traits<char>::eof() || curChr == 0)
387 throw std::invalid_argument("Unterminated case tree");
388
389 if (curChr == '{' || curChr == ',' || curChr == '}')
390 {
391 if (!curName.empty() && expectNode)
392 {
393 CaseTreeNode* const newChild = new CaseTreeNode(curName);
394
395 try
396 {
397 nodeStack.back()->addChild(newChild);
398 }
399 catch (...)
400 {
401 delete newChild;
402 throw;
403 }
404
405 if (curChr == '{')
406 nodeStack.push_back(newChild);
407
408 curName.clear();
409 }
410 else if (curName.empty() == expectNode)
411 throw std::invalid_argument(expectNode ? "Empty node name" : "Missing node separator");
412
413 if (curChr == '}')
414 {
415 expectNode = false;
416 nodeStack.pop_back();
417
418 // consume trailing new line
419 if (nodeStack.empty())
420 {
421 if (in.peek() == '\r')
422 in.get();
423 if (in.peek() == '\n')
424 in.get();
425 }
426 }
427 else
428 expectNode = true;
429 }
430 else if (isValidTestCaseNameChar((char)curChr))
431 curName += (char)curChr;
432 else
433 throw std::invalid_argument("Illegal character in node name");
434 }
435 }
436
parseCaseList(CaseTreeNode * root,std::istream & in,bool reportDuplicates)437 static void parseCaseList (CaseTreeNode* root, std::istream& in, bool reportDuplicates)
438 {
439 // \note Algorithm assumes that cases are sorted by groups, but will
440 // function fine, albeit more slowly, if that is not the case.
441 vector<CaseTreeNode*> nodeStack;
442 int stackPos = 0;
443 string curName;
444
445 nodeStack.resize(8, DE_NULL);
446
447 nodeStack[0] = root;
448
449 for (;;)
450 {
451 const int curChr = in.get();
452
453 if (curChr == std::char_traits<char>::eof() || curChr == 0 || curChr == '\n' || curChr == '\r')
454 {
455 if (curName.empty())
456 throw std::invalid_argument("Empty test case name");
457
458 if (!nodeStack[stackPos]->hasChild(curName))
459 {
460 CaseTreeNode* const newChild = new CaseTreeNode(curName);
461
462 try
463 {
464 nodeStack[stackPos]->addChild(newChild);
465 }
466 catch (...)
467 {
468 delete newChild;
469 throw;
470 }
471 }
472 else if (reportDuplicates)
473 throw std::invalid_argument("Duplicate test case");
474
475 curName.clear();
476 stackPos = 0;
477
478 if (curChr == '\r' && in.peek() == '\n')
479 in.get();
480
481 {
482 const int nextChr = in.peek();
483
484 if (nextChr == std::char_traits<char>::eof() || nextChr == 0)
485 break;
486 }
487 }
488 else if (curChr == '.')
489 {
490 if (curName.empty())
491 throw std::invalid_argument("Empty test group name");
492
493 if ((int)nodeStack.size() <= stackPos+1)
494 nodeStack.resize(nodeStack.size()*2, DE_NULL);
495
496 if (!nodeStack[stackPos+1] || nodeStack[stackPos+1]->getName() != curName)
497 {
498 CaseTreeNode* curGroup = nodeStack[stackPos]->getChild(curName);
499
500 if (!curGroup)
501 {
502 curGroup = new CaseTreeNode(curName);
503
504 try
505 {
506 nodeStack[stackPos]->addChild(curGroup);
507 }
508 catch (...)
509 {
510 delete curGroup;
511 throw;
512 }
513 }
514
515 nodeStack[stackPos+1] = curGroup;
516
517 if ((int)nodeStack.size() > stackPos+2)
518 nodeStack[stackPos+2] = DE_NULL; // Invalidate rest of entries
519 }
520
521 DE_ASSERT(nodeStack[stackPos+1]->getName() == curName);
522
523 curName.clear();
524 stackPos += 1;
525 }
526 else if (isValidTestCaseNameChar((char)curChr))
527 curName += (char)curChr;
528 else
529 throw std::invalid_argument("Illegal character in test case name");
530 }
531 }
532
parseCaseList(std::istream & in)533 static CaseTreeNode* parseCaseList (std::istream& in)
534 {
535 CaseTreeNode* const root = new CaseTreeNode("");
536 try
537 {
538 if (in.peek() == '{')
539 parseCaseTrie(root, in);
540 else
541 parseCaseList(root, in, true);
542
543 {
544 const int curChr = in.get();
545 if (curChr != std::char_traits<char>::eof() && curChr != 0)
546 throw std::invalid_argument("Trailing characters at end of case list");
547 }
548
549 return root;
550 }
551 catch (...)
552 {
553 delete root;
554 throw;
555 }
556 }
557
558 class CasePaths
559 {
560 public:
561 CasePaths(const string& pathList);
562 CasePaths(const vector<string>& pathList);
563 bool matches(const string& caseName, bool allowPrefix = false) const;
564
565 private:
566 const vector<string> m_casePatterns;
567 };
568
CasePaths(const string & pathList)569 CasePaths::CasePaths (const string& pathList)
570 : m_casePatterns(de::splitString(pathList, ','))
571 {
572 }
573
CasePaths(const vector<string> & pathList)574 CasePaths::CasePaths(const vector<string>& pathList)
575 : m_casePatterns(pathList)
576 {
577 }
578
579 // Match a single path component against a pattern component that may contain *-wildcards.
matchWildcards(string::const_iterator patternStart,string::const_iterator patternEnd,string::const_iterator pathStart,string::const_iterator pathEnd,bool allowPrefix)580 bool matchWildcards(string::const_iterator patternStart,
581 string::const_iterator patternEnd,
582 string::const_iterator pathStart,
583 string::const_iterator pathEnd,
584 bool allowPrefix)
585 {
586 string::const_iterator pattern = patternStart;
587 string::const_iterator path = pathStart;
588
589 while (pattern != patternEnd && path != pathEnd && *pattern == *path)
590 {
591 ++pattern;
592 ++path;
593 }
594
595 if (pattern == patternEnd)
596 return (path == pathEnd);
597 else if (*pattern == '*')
598 {
599 for (; path != pathEnd; ++path)
600 {
601 if (matchWildcards(pattern + 1, patternEnd, path, pathEnd, allowPrefix))
602 return true;
603 }
604
605 if (matchWildcards(pattern + 1, patternEnd, pathEnd, pathEnd, allowPrefix))
606 return true;
607 }
608 else if (path == pathEnd && allowPrefix)
609 return true;
610
611 return false;
612 }
613
614 #if defined(TCU_HIERARCHICAL_CASEPATHS)
615 // Match a list of pattern components to a list of path components. A pattern
616 // component may contain *-wildcards. A pattern component "**" matches zero or
617 // more whole path components.
patternMatches(vector<string>::const_iterator patternStart,vector<string>::const_iterator patternEnd,vector<string>::const_iterator pathStart,vector<string>::const_iterator pathEnd,bool allowPrefix)618 static bool patternMatches(vector<string>::const_iterator patternStart,
619 vector<string>::const_iterator patternEnd,
620 vector<string>::const_iterator pathStart,
621 vector<string>::const_iterator pathEnd,
622 bool allowPrefix)
623 {
624 vector<string>::const_iterator pattern = patternStart;
625 vector<string>::const_iterator path = pathStart;
626
627 while (pattern != patternEnd && path != pathEnd && *pattern != "**" &&
628 (*pattern == *path || matchWildcards(pattern->begin(), pattern->end(),
629 path->begin(), path->end(), false)))
630 {
631 ++pattern;
632 ++path;
633 }
634
635 if (path == pathEnd && (allowPrefix || pattern == patternEnd))
636 return true;
637 else if (pattern != patternEnd && *pattern == "**")
638 {
639 for (; path != pathEnd; ++path)
640 if (patternMatches(pattern + 1, patternEnd, path, pathEnd, allowPrefix))
641 return true;
642 if (patternMatches(pattern + 1, patternEnd, path, pathEnd, allowPrefix))
643 return true;
644 }
645
646 return false;
647 }
648 #endif
649
matches(const string & caseName,bool allowPrefix) const650 bool CasePaths::matches (const string& caseName, bool allowPrefix) const
651 {
652 const vector<string> components = de::splitString(caseName, '.');
653
654 for (size_t ndx = 0; ndx < m_casePatterns.size(); ++ndx)
655 {
656 #if defined(TCU_HIERARCHICAL_CASEPATHS)
657 const vector<string> patternComponents = de::splitString(m_casePatterns[ndx], '.');
658
659 if (patternMatches(patternComponents.begin(), patternComponents.end(),
660 components.begin(), components.end(), allowPrefix))
661 return true;
662 #else
663 if (matchWildcards(m_casePatterns[ndx].begin(), m_casePatterns[ndx].end(),
664 caseName.begin(), caseName.end(), allowPrefix))
665 return true;
666 #endif
667 }
668
669 return false;
670 }
671
672 /*--------------------------------------------------------------------*//*!
673 * \brief Construct command line
674 * \note CommandLine is not fully initialized until parse() has been called.
675 *//*--------------------------------------------------------------------*/
CommandLine(void)676 CommandLine::CommandLine (void)
677 : m_logFlags (0)
678 {
679 }
680
681 /*--------------------------------------------------------------------*//*!
682 * \brief Construct command line from standard argc, argv pair.
683 *
684 * Calls parse() with given arguments
685 * \param archive application's assets
686 * \param argc Number of arguments
687 * \param argv Command line arguments
688 *//*--------------------------------------------------------------------*/
CommandLine(int argc,const char * const * argv)689 CommandLine::CommandLine (int argc, const char* const* argv)
690 : m_logFlags (0)
691 {
692 if (argc > 1)
693 {
694 int loop = 1; // skip application name
695 while (true)
696 {
697 m_initialCmdLine += std::string(argv[loop++]);
698 if (loop >= argc)
699 break;
700 m_initialCmdLine += " ";
701 }
702 }
703
704 if (!parse(argc, argv))
705 throw Exception("Failed to parse command line");
706 }
707
708 /*--------------------------------------------------------------------*//*!
709 * \brief Construct command line from string.
710 *
711 * Calls parse() with given argument.
712 * \param archive application's assets
713 * \param cmdLine Full command line string.
714 *//*--------------------------------------------------------------------*/
CommandLine(const std::string & cmdLine)715 CommandLine::CommandLine (const std::string& cmdLine)
716 : m_initialCmdLine (cmdLine)
717 {
718 if (!parse(cmdLine))
719 throw Exception("Failed to parse command line");
720 }
721
~CommandLine(void)722 CommandLine::~CommandLine (void)
723 {
724 }
725
clear(void)726 void CommandLine::clear (void)
727 {
728 m_cmdLine.clear();
729 m_logFlags = 0;
730 }
731
getCommandLine(void) const732 const de::cmdline::CommandLine& CommandLine::getCommandLine (void) const
733 {
734 return m_cmdLine;
735 }
736
getInitialCmdLine(void) const737 const std::string& CommandLine::getInitialCmdLine(void) const
738 {
739 return m_initialCmdLine;
740 }
741
registerExtendedOptions(de::cmdline::Parser & parser)742 void CommandLine::registerExtendedOptions (de::cmdline::Parser& parser)
743 {
744 DE_UNREF(parser);
745 }
746
747 /*--------------------------------------------------------------------*//*!
748 * \brief Parse command line from standard argc, argv pair.
749 * \note parse() must be called exactly once.
750 * \param argc Number of arguments
751 * \param argv Command line arguments
752 *//*--------------------------------------------------------------------*/
parse(int argc,const char * const * argv)753 bool CommandLine::parse (int argc, const char* const* argv)
754 {
755 DebugOutStreambuf sbuf;
756 std::ostream debugOut (&sbuf);
757 de::cmdline::Parser parser;
758
759 opt::registerOptions(parser);
760 opt::registerLegacyOptions(parser);
761 registerExtendedOptions(parser);
762
763 clear();
764
765 if (!parser.parse(argc-1, argv+1, &m_cmdLine, std::cerr))
766 {
767 debugOut << "\n" << de::FilePath(argv[0]).getBaseName() << " [options]\n\n";
768 parser.help(debugOut);
769
770 clear();
771 return false;
772 }
773
774 if (!m_cmdLine.getOption<opt::LogImages>())
775 m_logFlags |= QP_TEST_LOG_EXCLUDE_IMAGES;
776
777 if (!m_cmdLine.getOption<opt::LogShaderSources>())
778 m_logFlags |= QP_TEST_LOG_EXCLUDE_SHADER_SOURCES;
779
780 if (!m_cmdLine.getOption<opt::LogFlush>())
781 m_logFlags |= QP_TEST_LOG_NO_FLUSH;
782
783 if ((m_cmdLine.hasOption<opt::CasePath>()?1:0) +
784 (m_cmdLine.hasOption<opt::CaseList>()?1:0) +
785 (m_cmdLine.hasOption<opt::CaseListFile>()?1:0) +
786 (m_cmdLine.hasOption<opt::CaseListResource>()?1:0) +
787 (m_cmdLine.getOption<opt::StdinCaseList>()?1:0) > 1)
788 {
789 debugOut << "ERROR: multiple test case list options given!\n" << std::endl;
790 clear();
791 return false;
792 }
793
794 return true;
795 }
796
797 /*--------------------------------------------------------------------*//*!
798 * \brief Parse command line from string.
799 * \note parse() must be called exactly once.
800 * \param cmdLine Full command line string.
801 *//*--------------------------------------------------------------------*/
parse(const std::string & cmdLine)802 bool CommandLine::parse (const std::string& cmdLine)
803 {
804 deCommandLine* parsedCmdLine = deCommandLine_parse(cmdLine.c_str());
805 if (!parsedCmdLine)
806 throw std::bad_alloc();
807
808 bool isOk = false;
809 try
810 {
811 isOk = parse(parsedCmdLine->numArgs, parsedCmdLine->args);
812 }
813 catch (...)
814 {
815 deCommandLine_destroy(parsedCmdLine);
816 throw;
817 }
818
819 deCommandLine_destroy(parsedCmdLine);
820 return isOk;
821 }
822
getLogFileName(void) const823 const char* CommandLine::getLogFileName (void) const { return m_cmdLine.getOption<opt::LogFilename>().c_str(); }
getLogFlags(void) const824 deUint32 CommandLine::getLogFlags (void) const { return m_logFlags; }
getRunMode(void) const825 RunMode CommandLine::getRunMode (void) const { return m_cmdLine.getOption<opt::RunMode>(); }
getCaseListExportFile(void) const826 const char* CommandLine::getCaseListExportFile (void) const { return m_cmdLine.getOption<opt::ExportFilenamePattern>().c_str(); }
getVisibility(void) const827 WindowVisibility CommandLine::getVisibility (void) const { return m_cmdLine.getOption<opt::Visibility>(); }
isWatchDogEnabled(void) const828 bool CommandLine::isWatchDogEnabled (void) const { return m_cmdLine.getOption<opt::WatchDog>(); }
isCrashHandlingEnabled(void) const829 bool CommandLine::isCrashHandlingEnabled (void) const { return m_cmdLine.getOption<opt::CrashHandler>(); }
getBaseSeed(void) const830 int CommandLine::getBaseSeed (void) const { return m_cmdLine.getOption<opt::BaseSeed>(); }
getTestIterationCount(void) const831 int CommandLine::getTestIterationCount (void) const { return m_cmdLine.getOption<opt::TestIterationCount>(); }
getSurfaceWidth(void) const832 int CommandLine::getSurfaceWidth (void) const { return m_cmdLine.getOption<opt::SurfaceWidth>(); }
getSurfaceHeight(void) const833 int CommandLine::getSurfaceHeight (void) const { return m_cmdLine.getOption<opt::SurfaceHeight>(); }
getSurfaceType(void) const834 SurfaceType CommandLine::getSurfaceType (void) const { return m_cmdLine.getOption<opt::SurfaceType>(); }
getScreenRotation(void) const835 ScreenRotation CommandLine::getScreenRotation (void) const { return m_cmdLine.getOption<opt::ScreenRotation>(); }
getGLConfigId(void) const836 int CommandLine::getGLConfigId (void) const { return m_cmdLine.getOption<opt::GLConfigID>(); }
getCLPlatformId(void) const837 int CommandLine::getCLPlatformId (void) const { return m_cmdLine.getOption<opt::CLPlatformID>(); }
getCLDeviceIds(void) const838 const std::vector<int>& CommandLine::getCLDeviceIds (void) const { return m_cmdLine.getOption<opt::CLDeviceIDs>(); }
getVKDeviceId(void) const839 int CommandLine::getVKDeviceId (void) const { return m_cmdLine.getOption<opt::VKDeviceID>(); }
getVKDeviceGroupId(void) const840 int CommandLine::getVKDeviceGroupId (void) const { return m_cmdLine.getOption<opt::VKDeviceGroupID>(); }
isValidationEnabled(void) const841 bool CommandLine::isValidationEnabled (void) const { return m_cmdLine.getOption<opt::Validation>(); }
printValidationErrors(void) const842 bool CommandLine::printValidationErrors (void) const { return m_cmdLine.getOption<opt::PrintValidationErrors>(); }
isOutOfMemoryTestEnabled(void) const843 bool CommandLine::isOutOfMemoryTestEnabled (void) const { return m_cmdLine.getOption<opt::TestOOM>(); }
isShadercacheEnabled(void) const844 bool CommandLine::isShadercacheEnabled (void) const { return m_cmdLine.getOption<opt::ShaderCache>(); }
getShaderCacheFilename(void) const845 const char* CommandLine::getShaderCacheFilename (void) const { return m_cmdLine.getOption<opt::ShaderCacheFilename>().c_str(); }
isShaderCacheTruncateEnabled(void) const846 bool CommandLine::isShaderCacheTruncateEnabled (void) const { return m_cmdLine.getOption<opt::ShaderCacheTruncate>(); }
getOptimizationRecipe(void) const847 int CommandLine::getOptimizationRecipe (void) const { return m_cmdLine.getOption<opt::Optimization>(); }
isSpirvOptimizationEnabled(void) const848 bool CommandLine::isSpirvOptimizationEnabled (void) const { return m_cmdLine.getOption<opt::OptimizeSpirv>(); }
isRenderDocEnabled(void) const849 bool CommandLine::isRenderDocEnabled (void) const { return m_cmdLine.getOption<opt::RenderDoc>(); }
getWaiverFileName(void) const850 const char* CommandLine::getWaiverFileName (void) const { return m_cmdLine.getOption<opt::WaiverFile>().c_str(); }
getCaseFraction(void) const851 const std::vector<int>& CommandLine::getCaseFraction (void) const { return m_cmdLine.getOption<opt::CaseFraction>(); }
getCaseFractionMandatoryTests(void) const852 const char* CommandLine::getCaseFractionMandatoryTests (void) const { return m_cmdLine.getOption<opt::CaseFractionMandatoryTests>().c_str(); }
getArchiveDir(void) const853 const char* CommandLine::getArchiveDir (void) const { return m_cmdLine.getOption<opt::ArchiveDir>().c_str(); }
854
getGLContextType(void) const855 const char* CommandLine::getGLContextType (void) const
856 {
857 if (m_cmdLine.hasOption<opt::GLContextType>())
858 return m_cmdLine.getOption<opt::GLContextType>().c_str();
859 else
860 return DE_NULL;
861 }
getGLConfigName(void) const862 const char* CommandLine::getGLConfigName (void) const
863 {
864 if (m_cmdLine.hasOption<opt::GLConfigName>())
865 return m_cmdLine.getOption<opt::GLConfigName>().c_str();
866 else
867 return DE_NULL;
868 }
869
getGLContextFlags(void) const870 const char* CommandLine::getGLContextFlags (void) const
871 {
872 if (m_cmdLine.hasOption<opt::GLContextFlags>())
873 return m_cmdLine.getOption<opt::GLContextFlags>().c_str();
874 else
875 return DE_NULL;
876 }
877
getCLBuildOptions(void) const878 const char* CommandLine::getCLBuildOptions (void) const
879 {
880 if (m_cmdLine.hasOption<opt::CLBuildOptions>())
881 return m_cmdLine.getOption<opt::CLBuildOptions>().c_str();
882 else
883 return DE_NULL;
884 }
885
getEGLDisplayType(void) const886 const char* CommandLine::getEGLDisplayType (void) const
887 {
888 if (m_cmdLine.hasOption<opt::EGLDisplayType>())
889 return m_cmdLine.getOption<opt::EGLDisplayType>().c_str();
890 else
891 return DE_NULL;
892 }
893
getEGLWindowType(void) const894 const char* CommandLine::getEGLWindowType (void) const
895 {
896 if (m_cmdLine.hasOption<opt::EGLWindowType>())
897 return m_cmdLine.getOption<opt::EGLWindowType>().c_str();
898 else
899 return DE_NULL;
900 }
901
getEGLPixmapType(void) const902 const char* CommandLine::getEGLPixmapType (void) const
903 {
904 if (m_cmdLine.hasOption<opt::EGLPixmapType>())
905 return m_cmdLine.getOption<opt::EGLPixmapType>().c_str();
906 else
907 return DE_NULL;
908 }
909
checkTestGroupName(const CaseTreeNode * root,const char * groupPath)910 static bool checkTestGroupName (const CaseTreeNode* root, const char* groupPath)
911 {
912 const CaseTreeNode* node = findNode(root, groupPath);
913 return node && node->hasChildren();
914 }
915
checkTestCaseName(const CaseTreeNode * root,const char * casePath)916 static bool checkTestCaseName (const CaseTreeNode* root, const char* casePath)
917 {
918 const CaseTreeNode* node = findNode(root, casePath);
919 return node && !node->hasChildren();
920 }
921
createCaseListFilter(const tcu::Archive & archive) const922 de::MovePtr<CaseListFilter> CommandLine::createCaseListFilter (const tcu::Archive& archive) const
923 {
924 return de::MovePtr<CaseListFilter>(new CaseListFilter(m_cmdLine, archive));
925 }
926
checkTestGroupName(const char * groupName) const927 bool CaseListFilter::checkTestGroupName (const char* groupName) const
928 {
929 bool result = false;
930 if (m_casePaths)
931 result = m_casePaths->matches(groupName, true);
932 else if (m_caseTree)
933 result = ( groupName[0] == 0 || tcu::checkTestGroupName(m_caseTree, groupName) );
934 else
935 return true;
936 if (!result && m_caseFractionMandatoryTests.get() != DE_NULL)
937 result = m_caseFractionMandatoryTests->matches(groupName, true);
938 return result;
939 }
940
checkTestCaseName(const char * caseName) const941 bool CaseListFilter::checkTestCaseName (const char* caseName) const
942 {
943 bool result = false;
944 if (m_casePaths)
945 result = m_casePaths->matches(caseName, false);
946 else if (m_caseTree)
947 result = tcu::checkTestCaseName(m_caseTree, caseName);
948 else
949 return true;
950 if (!result && m_caseFractionMandatoryTests.get() != DE_NULL)
951 result = m_caseFractionMandatoryTests->matches(caseName, false);
952 return result;
953 }
954
checkCaseFraction(int i,const std::string & testCaseName) const955 bool CaseListFilter::checkCaseFraction (int i, const std::string& testCaseName) const
956 {
957 return m_caseFraction.size() != 2 ||
958 ((i % m_caseFraction[1]) == m_caseFraction[0]) ||
959 (m_caseFractionMandatoryTests.get()!=DE_NULL && m_caseFractionMandatoryTests->matches(testCaseName));
960 }
961
CaseListFilter(void)962 CaseListFilter::CaseListFilter (void)
963 : m_caseTree(DE_NULL)
964 {
965 }
966
CaseListFilter(const de::cmdline::CommandLine & cmdLine,const tcu::Archive & archive)967 CaseListFilter::CaseListFilter (const de::cmdline::CommandLine& cmdLine, const tcu::Archive& archive)
968 : m_caseTree(DE_NULL)
969 {
970 if (cmdLine.hasOption<opt::CaseList>())
971 {
972 std::istringstream str(cmdLine.getOption<opt::CaseList>());
973
974 m_caseTree = parseCaseList(str);
975 }
976 else if (cmdLine.hasOption<opt::CaseListFile>())
977 {
978 std::ifstream in(cmdLine.getOption<opt::CaseListFile>().c_str(), std::ios_base::binary);
979
980 if (!in.is_open() || !in.good())
981 throw Exception("Failed to open case list file '" + cmdLine.getOption<opt::CaseListFile>() + "'");
982
983 m_caseTree = parseCaseList(in);
984 }
985 else if (cmdLine.hasOption<opt::CaseListResource>())
986 {
987 // \todo [2016-11-14 pyry] We are cloning potentially large buffers here. Consider writing
988 // istream adaptor for tcu::Resource.
989 de::UniquePtr<Resource> caseListResource (archive.getResource(cmdLine.getOption<opt::CaseListResource>().c_str()));
990 const int bufferSize = caseListResource->getSize();
991 std::vector<char> buffer ((size_t)bufferSize);
992
993 if (buffer.empty())
994 throw Exception("Empty case list resource");
995
996 caseListResource->read(reinterpret_cast<deUint8*>(&buffer[0]), bufferSize);
997
998 {
999 std::istringstream in (std::string(&buffer[0], (size_t)bufferSize));
1000
1001 m_caseTree = parseCaseList(in);
1002 }
1003 }
1004 else if (cmdLine.getOption<opt::StdinCaseList>())
1005 {
1006 m_caseTree = parseCaseList(std::cin);
1007 }
1008 else if (cmdLine.hasOption<opt::CasePath>())
1009 m_casePaths = de::MovePtr<const CasePaths>(new CasePaths(cmdLine.getOption<opt::CasePath>()));
1010
1011 m_caseFraction = cmdLine.getOption<opt::CaseFraction>();
1012
1013 if (m_caseFraction.size() == 2 &&
1014 (m_caseFraction[0] < 0 || m_caseFraction[1] <= 0 || m_caseFraction[0] >= m_caseFraction[1] ))
1015 throw Exception("Invalid case fraction. First element must be non-negative and less than second element. Second element must be greater than 0.");
1016
1017 if (m_caseFraction.size() != 0 && m_caseFraction.size() != 2)
1018 throw Exception("Invalid case fraction. Must have two components.");
1019
1020 if (m_caseFraction.size() == 2)
1021 {
1022 std::string caseFractionMandatoryTestsFilename = cmdLine.getOption<opt::CaseFractionMandatoryTests>();
1023
1024 if (!caseFractionMandatoryTestsFilename.empty())
1025 {
1026 std::ifstream fileStream(caseFractionMandatoryTestsFilename.c_str(), std::ios_base::binary);
1027 if (!fileStream.is_open() || !fileStream.good())
1028 throw Exception("Failed to open case fraction mandatory test list: '" + caseFractionMandatoryTestsFilename + "'");
1029
1030 std::vector<std::string> cfPaths;
1031 std::string line;
1032
1033 while (std::getline(fileStream, line))
1034 {
1035 line.erase(std::remove(std::begin(line), std::end(line), '\r'), std::end(line));
1036 cfPaths.push_back(line);
1037 }
1038 if (!cfPaths.empty())
1039 {
1040 m_caseFractionMandatoryTests = de::MovePtr<const CasePaths>(new CasePaths(cfPaths));
1041 if (m_caseTree != DE_NULL)
1042 {
1043 fileStream.clear();
1044 fileStream.seekg(0, fileStream.beg);
1045 parseCaseList(m_caseTree, fileStream, false);
1046 }
1047 }
1048 }
1049 }
1050 }
1051
~CaseListFilter(void)1052 CaseListFilter::~CaseListFilter (void)
1053 {
1054 delete m_caseTree;
1055 }
1056
1057 } // tcu
1058