1# -*- coding: utf-8 -*- 2 3#------------------------------------------------------------------------- 4# drawElements Quality Program utilities 5# -------------------------------------- 6# 7# Copyright 2016 The Android Open Source Project 8# 9# Licensed under the Apache License, Version 2.0 (the "License"); 10# you may not use this file except in compliance with the License. 11# You may obtain a copy of the License at 12# 13# http://www.apache.org/licenses/LICENSE-2.0 14# 15# Unless required by applicable law or agreed to in writing, software 16# distributed under the License is distributed on an "AS IS" BASIS, 17# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18# See the License for the specific language governing permissions and 19# limitations under the License. 20# 21#------------------------------------------------------------------------- 22 23from ctsbuild.common import * 24from ctsbuild.config import ANY_GENERATOR 25from ctsbuild.build import build 26from build_caselists import Module, getModuleByName, getBuildConfig, genCaseList, getCaseListPath, DEFAULT_BUILD_DIR, DEFAULT_TARGET 27from fnmatch import fnmatch 28from copy import copy 29from collections import defaultdict 30 31import argparse 32import xml.etree.cElementTree as ElementTree 33import xml.dom.minidom as minidom 34 35APK_NAME = "com.drawelements.deqp.apk" 36 37GENERATED_FILE_WARNING = """ 38 This file has been automatically generated. Edit with caution. 39 """ 40 41class Project: 42 def __init__ (self, path, copyright = None): 43 self.path = path 44 self.copyright = copyright 45 46class Configuration: 47 def __init__ (self, name, filters, glconfig = None, rotation = None, surfacetype = None, required = False, runtime = None, runByDefault = True, listOfGroupsToSplit = []): 48 self.name = name 49 self.glconfig = glconfig 50 self.rotation = rotation 51 self.surfacetype = surfacetype 52 self.required = required 53 self.filters = filters 54 self.expectedRuntime = runtime 55 self.runByDefault = runByDefault 56 self.listOfGroupsToSplit = listOfGroupsToSplit 57 58class Package: 59 def __init__ (self, module, configurations): 60 self.module = module 61 self.configurations = configurations 62 63class Mustpass: 64 def __init__ (self, project, version, packages): 65 self.project = project 66 self.version = version 67 self.packages = packages 68 69class Filter: 70 TYPE_INCLUDE = 0 71 TYPE_EXCLUDE = 1 72 73 def __init__ (self, type, filename): 74 self.type = type 75 self.filename = filename 76 77class TestRoot: 78 def __init__ (self): 79 self.children = [] 80 81class TestGroup: 82 def __init__ (self, name): 83 self.name = name 84 self.children = [] 85 86class TestCase: 87 def __init__ (self, name): 88 self.name = name 89 self.configurations = [] 90 91class GLESVersion: 92 def __init__(self, major, minor): 93 self.major = major 94 self.minor = minor 95 96 def encode (self): 97 return (self.major << 16) | (self.minor) 98 99def getModuleGLESVersion (module): 100 versions = { 101 'dEQP-EGL': GLESVersion(2,0), 102 'dEQP-GLES2': GLESVersion(2,0), 103 'dEQP-GLES3': GLESVersion(3,0), 104 'dEQP-GLES31': GLESVersion(3,1) 105 } 106 return versions[module.name] if module.name in versions else None 107 108def getSrcDir (mustpass): 109 return os.path.join(mustpass.project.path, mustpass.version, "src") 110 111def getTmpDir (mustpass): 112 return os.path.join(mustpass.project.path, mustpass.version, "tmp") 113 114def getModuleShorthand (module): 115 assert module.name[:5] == "dEQP-" 116 return module.name[5:].lower() 117 118def getCaseListFileName (package, configuration): 119 return "%s-%s.txt" % (getModuleShorthand(package.module), configuration.name) 120 121def getDstCaseListPath (mustpass): 122 return os.path.join(mustpass.project.path, mustpass.version) 123 124def getCTSPackageName (package): 125 return "com.drawelements.deqp." + getModuleShorthand(package.module) 126 127def getCommandLine (config): 128 cmdLine = "" 129 130 if config.glconfig != None: 131 cmdLine += "--deqp-gl-config-name=%s " % config.glconfig 132 133 if config.rotation != None: 134 cmdLine += "--deqp-screen-rotation=%s " % config.rotation 135 136 if config.surfacetype != None: 137 cmdLine += "--deqp-surface-type=%s " % config.surfacetype 138 139 cmdLine += "--deqp-watchdog=enable" 140 141 return cmdLine 142 143def readCaseDict (filename): 144 # read all cases and organize them in a tree; this is needed for chunked mustpass 145 # groups are stored as dictionaries and cases as list of strings with full case paths 146 groupTree = {} 147 # limit how deep constructed tree should be - this later simplifies applying filters; 148 # if in future we will need to split to separate .txt files deeper groups thet this value should be increased 149 limitGroupTreeDepth = 3 150 # create helper stack that will contain references to currently filled groups, from top to bottom 151 groupStack = None 152 # cretae variable that will hold currentlt processed line from the file 153 processedLine = None 154 with open(filename, 'rt') as f: 155 for nextLine in f: 156 # to be able to build tree structure we need to know what is the next line in the file this is 157 # why the first line read from the file will be actually processed during the second iteration 158 if processedLine is None: 159 processedLine = nextLine 160 # to simplify code use this section to also extract root node name 161 rootName = processedLine[7:processedLine.rfind('.')] 162 groupTree[rootName] = {} 163 groupStack = [groupTree[rootName]] 164 continue 165 # check if currently processed line is a test case or a group 166 processedEntryType = processedLine[:6] 167 if processedEntryType == "TEST: ": 168 # append this test case to the last group on the stack 169 groupStack[-1].append(processedLine[6:].strip()) 170 elif processedEntryType == "GROUP:": 171 # count number of dots in path to determine what is the depth of current group in the tree 172 processedGroupDepth = processedLine.count('.') 173 # limit tree construction just to specified level 174 availableLimit = limitGroupTreeDepth - processedGroupDepth 175 if availableLimit > 0: 176 # check how deep is stack currently 177 groupStackDepth = len(groupStack) 178 # if stack is deeper then depth of current group then we need to pop number of items 179 if processedGroupDepth < groupStackDepth: 180 groupStack = groupStack[:groupStackDepth-(groupStackDepth-processedGroupDepth)] 181 # get group that will have new child - this is the last group on the stack 182 parentGroup = groupStack[-1] 183 # add new dict that will contain other groups or list of cases depending on the next line 184 # and available depth limit (if are about to reach limit we won't add group dictionaries 185 # but just add all cases from deeper groups to the group at this depth) 186 processedGroupName = processedLine[7:-1] 187 parentGroup[processedGroupName] = {} if (nextLine[:6] == "GROUP:") and (availableLimit > 1) else [] 188 # add new group to the stack (items in groupStack can be either list or dict) 189 groupStack.append(parentGroup[processedGroupName]) 190 # before going to the next line set procesedLine for the next iteration 191 processedLine = nextLine 192 # handle last test case - we need to do it after the loop as in the loop we needed to know what is the next line 193 assert(processedLine[:6] == "TEST: ") 194 groupStack[-1].append(processedLine[6:].strip()) 195 return groupTree 196 197def getCaseDict (buildCfg, generator, module): 198 build(buildCfg, generator, [module.binName]) 199 genCaseList(buildCfg, generator, module, "txt") 200 return readCaseDict(getCaseListPath(buildCfg, module, "txt")) 201 202def readPatternList (filename): 203 ptrns = [] 204 with open(filename, 'rt') as f: 205 for line in f: 206 line = line.strip() 207 if len(line) > 0 and line[0] != '#': 208 ptrns.append(line) 209 return ptrns 210 211 212def constructNewDict(oldDict, listOfCases, op = lambda a: not a): 213 # Helper function used to construct case dictionary without specific cases 214 rootName = list(oldDict.keys())[0] 215 newDict = {rootName : {}} 216 newDictStack = [newDict] 217 oldDictStack = [oldDict] 218 while True: 219 # mak sure that both stacks have same number of items 220 assert(len(oldDictStack) == len(newDictStack)) 221 # when all items from stack were processed then we can exit the loop 222 if len(oldDictStack) == 0: 223 break 224 # grab last item from both stacks 225 itemOnOldStack = oldDictStack.pop() 226 itemOnNewStack = newDictStack.pop() 227 # if item on stack is dictionary then it represents groups and 228 # we need to reconstruct them in new dictionary 229 if type(itemOnOldStack) is dict: 230 assert(type(itemOnNewStack) is dict) 231 listOfGroups = list(itemOnOldStack.keys()) 232 for groupName in listOfGroups: 233 # create list or dictionary depending on contnent of child group 234 doesGroupsContainCases = type(itemOnOldStack[groupName]) is list 235 itemOnNewStack[groupName] = [] if doesGroupsContainCases else {} 236 # append groups on stacks 237 assert(type(itemOnNewStack[groupName]) == type(itemOnOldStack[groupName])) 238 newDictStack.append(itemOnNewStack[groupName]) 239 oldDictStack.append(itemOnOldStack[groupName]) 240 else: 241 # if item on stack is list then it represents group that contain cases we need 242 # to apply filter on each of them to make sure only proper cases are appended 243 assert(type(itemOnOldStack) is list) 244 assert(type(itemOnNewStack) is list) 245 for caseName in itemOnOldStack: 246 if op(caseName in listOfCases): 247 itemOnNewStack.append(caseName) 248 return newDict 249 250def constructSet(caseDict, perGroupOperation): 251 casesSet = set() 252 dictStack = [caseDict] 253 while True: 254 # when all items from stack were processed then we can exit the loop 255 if len(dictStack) == 0: 256 break 257 # grab last item from stack 258 itemOnStack = dictStack.pop() 259 # if item on stack is dictionary then it represents groups and we need to add them to stack 260 if type(itemOnStack) is dict: 261 for groupName in itemOnStack.keys(): 262 dictStack.append(itemOnStack[groupName]) 263 else: 264 # if item on stack is a list of cases we can add them to set containing all cases 265 assert(type(itemOnStack) is list) 266 casesSet = perGroupOperation(casesSet, itemOnStack) 267 return casesSet 268 269def applyPatterns (caseDict, patterns, filename, op): 270 matched = set() 271 errors = [] 272 trivialPtrns = [p for p in patterns if p.find('*') < 0] 273 regularPtrns = [p for p in patterns if p.find('*') >= 0] 274 275 # Construct helper set that contains cases from all groups 276 unionOperation = lambda resultCasesSet, groupCaseList: resultCasesSet.union(set(groupCaseList)) 277 allCasesSet = constructSet(caseDict, unionOperation) 278 279 # Apply trivial patterns - plain case paths without wildcard 280 for path in trivialPtrns: 281 if path in allCasesSet: 282 if path in matched: 283 errors.append((path, "Same case specified more than once")) 284 matched.add(path) 285 else: 286 errors.append((path, "Test case not found")) 287 288 # Construct new dictionary but without already matched paths 289 curDict = constructNewDict(caseDict, matched) 290 291 # Apply regular patterns - paths with wildcard 292 for pattern in regularPtrns: 293 294 # Helper function that checks if cases from case group match pattern 295 def matchOperation(resultCasesSet, groupCaseList): 296 for caseName in groupCaseList: 297 if fnmatch(caseName, pattern): 298 resultCasesSet.add(caseName) 299 return resultCasesSet 300 301 matchedThisPtrn = constructSet(curDict, matchOperation) 302 303 if len(matchedThisPtrn) == 0: 304 errors.append((pattern, "Pattern didn't match any cases")) 305 306 matched = matched | matchedThisPtrn 307 308 # To speed up search construct smaller case dictionary without already matched paths 309 curDict = constructNewDict(curDict, matched) 310 311 for pattern, reason in errors: 312 print("ERROR: %s: %s" % (reason, pattern)) 313 314 if len(errors) > 0: 315 die("Found %s invalid patterns while processing file %s" % (len(errors), filename)) 316 317 # Construct final dictionary using aproperiate operation 318 return constructNewDict(caseDict, matched, op) 319 320def applyInclude (caseDict, patterns, filename): 321 return applyPatterns(caseDict, patterns, filename, lambda b: b) 322 323def applyExclude (caseDict, patterns, filename): 324 return applyPatterns(caseDict, patterns, filename, lambda b: not b) 325 326def readPatternLists (mustpass): 327 lists = {} 328 for package in mustpass.packages: 329 for cfg in package.configurations: 330 for filter in cfg.filters: 331 if not filter.filename in lists: 332 lists[filter.filename] = readPatternList(os.path.join(getSrcDir(mustpass), filter.filename)) 333 return lists 334 335def applyFilters (caseDict, patternLists, filters): 336 res = copy(caseDict) 337 for filter in filters: 338 ptrnList = patternLists[filter.filename] 339 if filter.type == Filter.TYPE_INCLUDE: 340 res = applyInclude(res, ptrnList, filter.filename) 341 else: 342 assert filter.type == Filter.TYPE_EXCLUDE 343 res = applyExclude(res, ptrnList, filter.filename) 344 return res 345 346def appendToHierarchy (root, casePath): 347 def findChild (node, name): 348 for child in node.children: 349 if child.name == name: 350 return child 351 return None 352 353 curNode = root 354 components = casePath.split('.') 355 356 for component in components[:-1]: 357 nextNode = findChild(curNode, component) 358 if not nextNode: 359 nextNode = TestGroup(component) 360 curNode.children.append(nextNode) 361 curNode = nextNode 362 363 if not findChild(curNode, components[-1]): 364 curNode.children.append(TestCase(components[-1])) 365 366def buildTestHierachy (caseList): 367 root = TestRoot() 368 for case in caseList: 369 appendToHierarchy(root, case) 370 return root 371 372def buildTestCaseMap (root): 373 caseMap = {} 374 375 def recursiveBuild (curNode, prefix): 376 curPath = prefix + curNode.name 377 if isinstance(curNode, TestCase): 378 caseMap[curPath] = curNode 379 else: 380 for child in curNode.children: 381 recursiveBuild(child, curPath + '.') 382 383 for child in root.children: 384 recursiveBuild(child, '') 385 386 return caseMap 387 388def include (filename): 389 return Filter(Filter.TYPE_INCLUDE, filename) 390 391def exclude (filename): 392 return Filter(Filter.TYPE_EXCLUDE, filename) 393 394def insertXMLHeaders (mustpass, doc): 395 if mustpass.project.copyright != None: 396 doc.insert(0, ElementTree.Comment(mustpass.project.copyright)) 397 doc.insert(1, ElementTree.Comment(GENERATED_FILE_WARNING)) 398 399def prettifyXML (doc): 400 uglyString = ElementTree.tostring(doc, 'utf-8') 401 reparsed = minidom.parseString(uglyString) 402 return reparsed.toprettyxml(indent='\t', encoding='utf-8') 403 404def genSpecXML (mustpass): 405 mustpassElem = ElementTree.Element("Mustpass", version = mustpass.version) 406 insertXMLHeaders(mustpass, mustpassElem) 407 408 for package in mustpass.packages: 409 packageElem = ElementTree.SubElement(mustpassElem, "TestPackage", name = package.module.name) 410 411 for config in package.configurations: 412 configElem = ElementTree.SubElement(packageElem, "Configuration", 413 caseListFile = getCaseListFileName(package, config), 414 commandLine = getCommandLine(config), 415 name = config.name) 416 417 return mustpassElem 418 419def addOptionElement (parent, optionName, optionValue): 420 ElementTree.SubElement(parent, "option", name=optionName, value=optionValue) 421 422def genAndroidTestXml (mustpass): 423 RUNNER_CLASS = "com.drawelements.deqp.runner.DeqpTestRunner" 424 configElement = ElementTree.Element("configuration") 425 426 # have the deqp package installed on the device for us 427 preparerElement = ElementTree.SubElement(configElement, "target_preparer") 428 preparerElement.set("class", "com.android.tradefed.targetprep.suite.SuiteApkInstaller") 429 addOptionElement(preparerElement, "cleanup-apks", "true") 430 addOptionElement(preparerElement, "test-file-name", "com.drawelements.deqp.apk") 431 432 # Target preparer for incremental dEQP 433 preparerElement = ElementTree.SubElement(configElement, "target_preparer") 434 preparerElement.set("class", "com.android.compatibility.common.tradefed.targetprep.FilePusher") 435 addOptionElement(preparerElement, "cleanup", "true") 436 addOptionElement(preparerElement, "disable", "true") 437 addOptionElement(preparerElement, "push", "deqp-binary32->/data/local/tmp/deqp-binary32") 438 addOptionElement(preparerElement, "push", "deqp-binary64->/data/local/tmp/deqp-binary64") 439 addOptionElement(preparerElement, "push", "gles2->/data/local/tmp/gles2") 440 addOptionElement(preparerElement, "push", "gles3->/data/local/tmp/gles3") 441 addOptionElement(preparerElement, "push", "gles3-incremental-deqp.txt->/data/local/tmp/gles3-incremental-deqp.txt") 442 addOptionElement(preparerElement, "push", "gles31->/data/local/tmp/gles31") 443 addOptionElement(preparerElement, "push", "internal->/data/local/tmp/internal") 444 addOptionElement(preparerElement, "push", "vk-incremental-deqp.txt->/data/local/tmp/vk-incremental-deqp.txt") 445 addOptionElement(preparerElement, "push", "vulkan->/data/local/tmp/vulkan") 446 preparerElement = ElementTree.SubElement(configElement, "target_preparer") 447 preparerElement.set("class", "com.android.compatibility.common.tradefed.targetprep.IncrementalDeqpPreparer") 448 addOptionElement(preparerElement, "disable", "true") 449 450 # add in metadata option for component name 451 ElementTree.SubElement(configElement, "option", name="test-suite-tag", value="cts") 452 ElementTree.SubElement(configElement, "option", key="component", name="config-descriptor:metadata", value="deqp") 453 ElementTree.SubElement(configElement, "option", key="parameter", name="config-descriptor:metadata", value="not_instant_app") 454 ElementTree.SubElement(configElement, "option", key="parameter", name="config-descriptor:metadata", value="multi_abi") 455 ElementTree.SubElement(configElement, "option", key="parameter", name="config-descriptor:metadata", value="secondary_user") 456 ElementTree.SubElement(configElement, "option", key="parameter", name="config-descriptor:metadata", value="no_foldable_states") 457 controllerElement = ElementTree.SubElement(configElement, "object") 458 controllerElement.set("class", "com.android.tradefed.testtype.suite.module.TestFailureModuleController") 459 controllerElement.set("type", "module_controller") 460 addOptionElement(controllerElement, "screenshot-on-failure", "false") 461 462 for package in mustpass.packages: 463 for config in package.configurations: 464 if not config.runByDefault: 465 continue 466 467 testElement = ElementTree.SubElement(configElement, "test") 468 testElement.set("class", RUNNER_CLASS) 469 addOptionElement(testElement, "deqp-package", package.module.name) 470 caseListFile = getCaseListFileName(package,config) 471 addOptionElement(testElement, "deqp-caselist-file", caseListFile) 472 if caseListFile.startswith("gles3"): 473 addOptionElement(testElement, "incremental-deqp-include-file", "gles3-incremental-deqp.txt") 474 elif caseListFile.startswith("vk"): 475 addOptionElement(testElement, "incremental-deqp-include-file", "vk-incremental-deqp.txt") 476 # \todo [2015-10-16 kalle]: Replace with just command line? - requires simplifications in the runner/tests as well. 477 if config.glconfig != None: 478 addOptionElement(testElement, "deqp-gl-config-name", config.glconfig) 479 480 if config.surfacetype != None: 481 addOptionElement(testElement, "deqp-surface-type", config.surfacetype) 482 483 if config.rotation != None: 484 addOptionElement(testElement, "deqp-screen-rotation", config.rotation) 485 486 if config.expectedRuntime != None: 487 addOptionElement(testElement, "runtime-hint", config.expectedRuntime) 488 489 if config.required: 490 addOptionElement(testElement, "deqp-config-required", "true") 491 492 insertXMLHeaders(mustpass, configElement) 493 494 return configElement 495 496def genMustpass (mustpass, moduleCaseDicts): 497 print("Generating mustpass '%s'" % mustpass.version) 498 499 patternLists = readPatternLists(mustpass) 500 501 for package in mustpass.packages: 502 allCasesInPkgDict = moduleCaseDicts[package.module] 503 504 for config in package.configurations: 505 506 # construct dictionary with all filters applyed 507 filteredCaseDict = applyFilters(allCasesInPkgDict, patternLists, config.filters) 508 509 # construct components of path to main destination file 510 mainDstFilePath = getDstCaseListPath(mustpass) 511 mainDstFileName = getCaseListFileName(package, config) 512 mainDstFile = os.path.join(mainDstFilePath, mainDstFileName) 513 mainGruopSubDir = mainDstFileName[:-4] 514 515 # if case paths should be split to multiple files then main 516 # destination file will contain paths to individual files containing cases 517 if len(config.listOfGroupsToSplit) > 0: 518 # make sure directory for group files exists 519 rootGroupPath = os.path.join(mainDstFilePath, mainGruopSubDir) 520 if not os.path.exists(rootGroupPath): 521 os.makedirs(rootGroupPath) 522 523 # iterate over case dictionary and split it to .txt files acording to 524 # groups that were specified in config.listOfGroupsToSplit 525 splitedGroupsDict = {} 526 dictStack = [filteredCaseDict] 527 helperListStack = [ [] ] 528 while True: 529 # when all items from stack were processed then we can exit the loop 530 if len(dictStack) == 0: 531 break 532 assert(len(dictStack) == len(helperListStack)) 533 # grab last item from stack 534 itemOnStack = dictStack.pop() 535 caseListFromHelperStack = helperListStack.pop() 536 # if item on stack is dictionary then it represents groups and we need to add them to stack 537 if type(itemOnStack) is dict: 538 for groupName in sorted(itemOnStack): 539 540 # check if this group should be split to multiple .txt files 541 if groupName in config.listOfGroupsToSplit: 542 # we can split only groups that contain other groups, 543 # listOfGroupsToSplit should not contain groups that contain test cases 544 assert(type(itemOnStack[groupName]) is dict) 545 # add child groups of this group to splitedGroupsDict 546 for childGroupName in itemOnStack[groupName]: 547 # make sure that child group should not be splited 548 # (if it should then this will be handle in one of the next iterations) 549 if childGroupName not in config.listOfGroupsToSplit: 550 splitedGroupsDict[childGroupName] = [] 551 552 # add this group to stack used for iteration over casses tree 553 dictStack.append(itemOnStack[groupName]) 554 555 # decide what list we should append to helperListStack; 556 # if this group represents one of individual .txt files then grab 557 # propper array of cases from splitedGroupsDict and add it to helper stack; 558 # if groupName is not in splitedGroupsDict then use the same list as was used 559 # by parent group (we are merging casses from those groups to single .txt file) 560 helperListStack.append(splitedGroupsDict.get(groupName, caseListFromHelperStack)) 561 else: 562 # if item on stack is a list of cases we can add them to proper list 563 assert(type(itemOnStack) is list) 564 caseListFromHelperStack.extend(itemOnStack) 565 566 print(" Writing separated caselists:") 567 groupPathsList = [] 568 for groupPath in splitedGroupsDict: 569 # skip groups that after filtering have no casses left 570 if len(splitedGroupsDict[groupPath]) == 0: 571 continue 572 # remove root node name from the beginning of group and replace all '_' with '-' 573 processedGroupPath = groupPath[groupPath.find('.')+1:].replace('_', '-') 574 # split group paths 575 groupList = processedGroupPath.split('.') 576 groupSubDir = '/' 577 # create subdirectories if there is more then one group name in groupList 578 path = rootGroupPath 579 if len(groupList) > 1: 580 for groupName in groupList[:-1]: 581 # make sure directory for group files exists 582 groupSubDir = groupSubDir + groupName + '/' 583 path = os.path.join(path, groupName) 584 if not os.path.exists(path): 585 os.makedirs(path) 586 # construct path to .txt file and save all cases 587 groupDstFileName = groupList[-1] + ".txt" 588 groupDstFileFullDir = os.path.join(path, groupDstFileName) 589 groupPathsList.append(mainGruopSubDir + groupSubDir + groupDstFileName) 590 print(" " + groupDstFileFullDir) 591 writeFile(groupDstFileFullDir, "\n".join(splitedGroupsDict[groupPath]) + "\n") 592 593 # write file containing names of all group files 594 print(" Writing file containing list of separated case files: " + mainDstFile) 595 groupPathsList.sort() 596 writeFile(mainDstFile, "\n".join(groupPathsList) + "\n") 597 else: 598 # merge all cases to single case list 599 filteredCaseList = [] 600 dictStack = [filteredCaseDict] 601 while True: 602 # when all items from stack were processed then we can exit the loop 603 if len(dictStack) == 0: 604 break 605 # grab last item from stack 606 itemOnStack = dictStack.pop() 607 # if item on stack is dictionary then it represents groups and we need to add them to stack 608 if type(itemOnStack) is dict: 609 for groupName in itemOnStack.keys(): 610 dictStack.append(itemOnStack[groupName]) 611 else: 612 # if item on stack is a list of cases we can add them to filteredCaseList 613 assert(type(itemOnStack) is list) 614 filteredCaseList.extend(itemOnStack) 615 # write file containing all cases 616 if len(filteredCaseList) > 0: 617 print(" Writing deqp caselist: " + mainDstFile) 618 writeFile(mainDstFile, "\n".join(filteredCaseList) + "\n") 619 620 specXML = genSpecXML(mustpass) 621 specFilename = os.path.join(mustpass.project.path, mustpass.version, "mustpass.xml") 622 623 print(" Writing spec: " + specFilename) 624 writeFile(specFilename, prettifyXML(specXML).decode()) 625 626 # TODO: Which is the best selector mechanism? 627 if (mustpass.version == "master"): 628 androidTestXML = genAndroidTestXml(mustpass) 629 androidTestFilename = os.path.join(mustpass.project.path, "AndroidTest.xml") 630 631 print(" Writing AndroidTest.xml: " + androidTestFilename) 632 writeFile(androidTestFilename, prettifyXML(androidTestXML).decode()) 633 634 print("Done!") 635 636def genMustpassLists (mustpassLists, generator, buildCfg): 637 moduleCaseDicts = {} 638 639 # Getting case lists involves invoking build, so we want to cache the results 640 for mustpass in mustpassLists: 641 for package in mustpass.packages: 642 if not package.module in moduleCaseDicts: 643 moduleCaseDicts[package.module] = getCaseDict(buildCfg, generator, package.module) 644 645 for mustpass in mustpassLists: 646 genMustpass(mustpass, moduleCaseDicts) 647 648def parseCmdLineArgs (): 649 parser = argparse.ArgumentParser(description = "Build Android CTS mustpass", 650 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 651 parser.add_argument("-b", 652 "--build-dir", 653 dest="buildDir", 654 default=DEFAULT_BUILD_DIR, 655 help="Temporary build directory") 656 parser.add_argument("-t", 657 "--build-type", 658 dest="buildType", 659 default="Debug", 660 help="Build type") 661 parser.add_argument("-c", 662 "--deqp-target", 663 dest="targetName", 664 default=DEFAULT_TARGET, 665 help="dEQP build target") 666 return parser.parse_args() 667 668def parseBuildConfigFromCmdLineArgs (): 669 args = parseCmdLineArgs() 670 return getBuildConfig(args.buildDir, args.targetName, args.buildType) 671