1 # -*- coding: utf-8 -*-
3 #-------------------------------------------------------------------------
4 # drawElements Quality Program utilities
5 # --------------------------------------
7 # Copyright 2016 The Android Open Source Project
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
13 # http://www.apache.org/licenses/LICENSE-2.0
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.
21 #-------------------------------------------------------------------------
23 from build.common import *
24 from build.config import ANY_GENERATOR
25 from build.build import build
26 from build_caselists import Module, getModuleByName, getBuildConfig, genCaseList, getCaseListPath, DEFAULT_BUILD_DIR, DEFAULT_TARGET
27 from fnmatch import fnmatch
30 import xml.etree.cElementTree as ElementTree
31 import xml.dom.minidom as minidom
33 APK_NAME = "com.drawelements.deqp.apk"
35 GENERATED_FILE_WARNING = """
36 This file has been automatically generated. Edit with caution.
40 def __init__ (self, path, copyright = None):
42 self.copyright = copyright
45 def __init__ (self, name, filters, glconfig = None, rotation = None, surfacetype = None, required = False, runtime = None):
47 self.glconfig = glconfig
48 self.rotation = rotation
49 self.surfacetype = surfacetype
50 self.required = required
51 self.filters = filters
52 self.expectedRuntime = runtime
55 def __init__ (self, module, configurations):
57 self.configurations = configurations
60 def __init__ (self, project, version, packages):
61 self.project = project
62 self.version = version
63 self.packages = packages
69 def __init__ (self, type, filename):
71 self.filename = filename
78 def __init__ (self, name):
83 def __init__ (self, name):
85 self.configurations = []
88 def __init__(self, major, minor):
93 return (self.major << 16) | (self.minor)
95 def getModuleGLESVersion (module):
97 'dEQP-EGL': GLESVersion(2,0),
98 'dEQP-GLES2': GLESVersion(2,0),
99 'dEQP-GLES3': GLESVersion(3,0),
100 'dEQP-GLES31': GLESVersion(3,1)
102 return versions[module.name] if module.name in versions else None
104 def getSrcDir (mustpass):
105 return os.path.join(mustpass.project.path, mustpass.version, "src")
107 def getTmpDir (mustpass):
108 return os.path.join(mustpass.project.path, mustpass.version, "tmp")
110 def getModuleShorthand (module):
111 assert module.name[:5] == "dEQP-"
112 return module.name[5:].lower()
114 def getCaseListFileName (package, configuration):
115 return "%s-%s.txt" % (getModuleShorthand(package.module), configuration.name)
117 def getDstCaseListPath (mustpass, package, configuration):
118 return os.path.join(mustpass.project.path, mustpass.version, getCaseListFileName(package, configuration))
120 def getCTSPackageName (package):
121 return "com.drawelements.deqp." + getModuleShorthand(package.module)
123 def getCommandLine (config):
126 if config.glconfig != None:
127 cmdLine += "--deqp-gl-config-name=%s " % config.glconfig
129 if config.rotation != None:
130 cmdLine += "--deqp-screen-rotation=%s " % config.rotation
132 if config.surfacetype != None:
133 cmdLine += "--deqp-surface-type=%s " % config.surfacetype
135 cmdLine += "--deqp-watchdog=enable"
139 def readCaseList (filename):
141 with open(filename, 'rb') as f:
143 if line[:6] == "TEST: ":
144 cases.append(line[6:].strip())
147 def getCaseList (buildCfg, generator, module):
148 build(buildCfg, generator, [module.binName])
149 genCaseList(buildCfg, generator, module, "txt")
150 return readCaseList(getCaseListPath(buildCfg, module, "txt"))
152 def readPatternList (filename):
154 with open(filename, 'rb') as f:
157 if len(line) > 0 and line[0] != '#':
161 def applyPatterns (caseList, patterns, filename, op):
164 curList = copy(caseList)
165 trivialPtrns = [p for p in patterns if p.find('*') < 0]
166 regularPtrns = [p for p in patterns if p.find('*') >= 0]
168 # Apply trivial (just case paths)
169 allCasesSet = set(caseList)
170 for path in trivialPtrns:
171 if path in allCasesSet:
173 errors.append((path, "Same case specified more than once"))
176 errors.append((path, "Test case not found"))
178 curList = [c for c in curList if c not in matched]
180 for pattern in regularPtrns:
181 matchedThisPtrn = set()
184 if fnmatch(case, pattern):
185 matchedThisPtrn.add(case)
187 if len(matchedThisPtrn) == 0:
188 errors.append((pattern, "Pattern didn't match any cases"))
190 matched = matched | matchedThisPtrn
191 curList = [c for c in curList if c not in matched]
193 for pattern, reason in errors:
194 print "ERROR: %s: %s" % (reason, pattern)
197 die("Found %s invalid patterns while processing file %s" % (len(errors), filename))
199 return [c for c in caseList if op(c in matched)]
201 def applyInclude (caseList, patterns, filename):
202 return applyPatterns(caseList, patterns, filename, lambda b: b)
204 def applyExclude (caseList, patterns, filename):
205 return applyPatterns(caseList, patterns, filename, lambda b: not b)
207 def readPatternLists (mustpass):
209 for package in mustpass.packages:
210 for cfg in package.configurations:
211 for filter in cfg.filters:
212 if not filter.filename in lists:
213 lists[filter.filename] = readPatternList(os.path.join(getSrcDir(mustpass), filter.filename))
216 def applyFilters (caseList, patternLists, filters):
218 for filter in filters:
219 ptrnList = patternLists[filter.filename]
220 if filter.type == Filter.TYPE_INCLUDE:
221 res = applyInclude(res, ptrnList, filter.filename)
223 assert filter.type == Filter.TYPE_EXCLUDE
224 res = applyExclude(res, ptrnList, filter.filename)
227 def appendToHierarchy (root, casePath):
228 def findChild (node, name):
229 for child in node.children:
230 if child.name == name:
235 components = casePath.split('.')
237 for component in components[:-1]:
238 nextNode = findChild(curNode, component)
240 nextNode = TestGroup(component)
241 curNode.children.append(nextNode)
244 if not findChild(curNode, components[-1]):
245 curNode.children.append(TestCase(components[-1]))
247 def buildTestHierachy (caseList):
249 for case in caseList:
250 appendToHierarchy(root, case)
253 def buildTestCaseMap (root):
256 def recursiveBuild (curNode, prefix):
257 curPath = prefix + curNode.name
258 if isinstance(curNode, TestCase):
259 caseMap[curPath] = curNode
261 for child in curNode.children:
262 recursiveBuild(child, curPath + '.')
264 for child in root.children:
265 recursiveBuild(child, '')
269 def include (filename):
270 return Filter(Filter.TYPE_INCLUDE, filename)
272 def exclude (filename):
273 return Filter(Filter.TYPE_EXCLUDE, filename)
275 def insertXMLHeaders (mustpass, doc):
276 if mustpass.project.copyright != None:
277 doc.insert(0, ElementTree.Comment(mustpass.project.copyright))
278 doc.insert(1, ElementTree.Comment(GENERATED_FILE_WARNING))
280 def prettifyXML (doc):
281 uglyString = ElementTree.tostring(doc, 'utf-8')
282 reparsed = minidom.parseString(uglyString)
283 return reparsed.toprettyxml(indent='\t', encoding='utf-8')
285 def genSpecXML (mustpass):
286 mustpassElem = ElementTree.Element("Mustpass", version = mustpass.version)
287 insertXMLHeaders(mustpass, mustpassElem)
289 for package in mustpass.packages:
290 packageElem = ElementTree.SubElement(mustpassElem, "TestPackage", name = package.module.name)
292 for config in package.configurations:
293 configElem = ElementTree.SubElement(packageElem, "Configuration",
295 caseListFile = getCaseListFileName(package, config),
296 commandLine = getCommandLine(config))
300 def addOptionElement (parent, optionName, optionValue):
301 ElementTree.SubElement(parent, "option", name=optionName, value=optionValue)
303 def genAndroidTestXml (mustpass):
304 RUNNER_CLASS = "com.drawelements.deqp.runner.DeqpTestRunner"
305 configElement = ElementTree.Element("configuration")
307 for package in mustpass.packages:
308 for config in package.configurations:
309 testElement = ElementTree.SubElement(configElement, "test")
310 testElement.set("class", RUNNER_CLASS)
311 addOptionElement(testElement, "deqp-package", package.module.name)
312 addOptionElement(testElement, "deqp-caselist-file", getCaseListFileName(package,config))
313 # \todo [2015-10-16 kalle]: Replace with just command line? - requires simplifications in the runner/tests as well.
314 if config.glconfig != None:
315 addOptionElement(testElement, "deqp-gl-config-name", config.glconfig)
317 if config.surfacetype != None:
318 addOptionElement(testElement, "deqp-surface-type", config.surfacetype)
320 if config.rotation != None:
321 addOptionElement(testElement, "deqp-screen-rotation", config.rotation)
323 if config.expectedRuntime != None:
324 addOptionElement(testElement, "runtime-hint", config.expectedRuntime)
327 addOptionElement(testElement, "deqp-config-required", "true")
329 insertXMLHeaders(mustpass, configElement)
333 def genMustpass (mustpass, moduleCaseLists):
334 print "Generating mustpass '%s'" % mustpass.version
336 patternLists = readPatternLists(mustpass)
338 for package in mustpass.packages:
339 allCasesInPkg = moduleCaseLists[package.module]
341 for config in package.configurations:
342 filtered = applyFilters(allCasesInPkg, patternLists, config.filters)
343 dstFile = getDstCaseListPath(mustpass, package, config)
345 print " Writing deqp caselist: " + dstFile
346 writeFile(dstFile, "\n".join(filtered) + "\n")
348 specXML = genSpecXML(mustpass)
349 specFilename = os.path.join(mustpass.project.path, mustpass.version, "mustpass.xml")
351 print " Writing spec: " + specFilename
352 writeFile(specFilename, prettifyXML(specXML))
354 # TODO: Which is the best selector mechanism?
355 if (mustpass.version == "master"):
356 androidTestXML = genAndroidTestXml(mustpass)
357 androidTestFilename = os.path.join(mustpass.project.path, "AndroidTest.xml")
359 print " Writing AndroidTest.xml: " + androidTestFilename
360 writeFile(androidTestFilename, prettifyXML(androidTestXML))
364 def genMustpassLists (mustpassLists, generator, buildCfg):
367 # Getting case lists involves invoking build, so we want to cache the results
368 for mustpass in mustpassLists:
369 for package in mustpass.packages:
370 if not package.module in moduleCaseLists:
371 moduleCaseLists[package.module] = getCaseList(buildCfg, generator, package.module)
373 for mustpass in mustpassLists:
374 genMustpass(mustpass, moduleCaseLists)