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