blob: 4c671293c70e8050b88552cf0875e93b71c9eac3 [file] [log] [blame]
Pyry Haulos2bbb9d22016-01-14 13:48:08 -08001# -*- 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 build.common import *
24from build.config import ANY_GENERATOR
25from build.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
29
30import xml.etree.cElementTree as ElementTree
31import xml.dom.minidom as minidom
32
33APK_NAME = "com.drawelements.deqp.apk"
34
35GENERATED_FILE_WARNING = """
36 This file has been automatically generated. Edit with caution.
37 """
38
39class Project:
40 def __init__ (self, path, copyright = None):
41 self.path = path
42 self.copyright = copyright
43
44class Configuration:
Kalle Raita25196612016-04-04 17:09:44 -070045 def __init__ (self, name, filters, glconfig = None, rotation = None, surfacetype = None, runtime = None):
46 self.name = name
47 self.glconfig = glconfig
48 self.rotation = rotation
49 self.surfacetype = surfacetype
50 self.filters = filters
51 self.expectedRuntime = runtime
Pyry Haulos2bbb9d22016-01-14 13:48:08 -080052
53class Package:
54 def __init__ (self, module, configurations):
55 self.module = module
56 self.configurations = configurations
57
58class Mustpass:
59 def __init__ (self, project, version, packages):
60 self.project = project
61 self.version = version
62 self.packages = packages
63
64class Filter:
65 TYPE_INCLUDE = 0
66 TYPE_EXCLUDE = 1
67
68 def __init__ (self, type, filename):
69 self.type = type
70 self.filename = filename
71
72class TestRoot:
73 def __init__ (self):
74 self.children = []
75
76class TestGroup:
77 def __init__ (self, name):
78 self.name = name
79 self.children = []
80
81class TestCase:
82 def __init__ (self, name):
83 self.name = name
84 self.configurations = []
85
86class GLESVersion:
87 def __init__(self, major, minor):
88 self.major = major
89 self.minor = minor
90
91 def encode (self):
92 return (self.major << 16) | (self.minor)
93
94def getModuleGLESVersion (module):
95 versions = {
96 'dEQP-EGL': GLESVersion(2,0),
97 'dEQP-GLES2': GLESVersion(2,0),
98 'dEQP-GLES3': GLESVersion(3,0),
99 'dEQP-GLES31': GLESVersion(3,1)
100 }
101 return versions[module.name] if module.name in versions else None
102
103def getSrcDir (mustpass):
104 return os.path.join(mustpass.project.path, mustpass.version, "src")
105
106def getTmpDir (mustpass):
107 return os.path.join(mustpass.project.path, mustpass.version, "tmp")
108
109def getModuleShorthand (module):
110 assert module.name[:5] == "dEQP-"
111 return module.name[5:].lower()
112
113def getCaseListFileName (package, configuration):
114 return "%s-%s.txt" % (getModuleShorthand(package.module), configuration.name)
115
116def getDstCaseListPath (mustpass, package, configuration):
117 return os.path.join(mustpass.project.path, mustpass.version, getCaseListFileName(package, configuration))
118
119def getCTSPackageName (package):
120 return "com.drawelements.deqp." + getModuleShorthand(package.module)
121
122def getCommandLine (config):
123 cmdLine = ""
124
125 if config.glconfig != None:
126 cmdLine += "--deqp-gl-config-name=%s " % config.glconfig
127
128 if config.rotation != None:
129 cmdLine += "--deqp-screen-rotation=%s " % config.rotation
130
131 if config.surfacetype != None:
132 cmdLine += "--deqp-surface-type=%s " % config.surfacetype
133
134 cmdLine += "--deqp-watchdog=enable"
135
136 return cmdLine
137
138def readCaseList (filename):
139 cases = []
140 with open(filename, 'rb') as f:
141 for line in f:
142 if line[:6] == "TEST: ":
143 cases.append(line[6:].strip())
144 return cases
145
146def getCaseList (buildCfg, generator, module):
147 build(buildCfg, generator, [module.binName])
148 genCaseList(buildCfg, generator, module, "txt")
149 return readCaseList(getCaseListPath(buildCfg, module, "txt"))
150
151def readPatternList (filename):
152 ptrns = []
153 with open(filename, 'rb') as f:
154 for line in f:
155 line = line.strip()
156 if len(line) > 0 and line[0] != '#':
157 ptrns.append(line)
158 return ptrns
159
160def applyPatterns (caseList, patterns, filename, op):
161 matched = set()
162 errors = []
163 curList = copy(caseList)
164 trivialPtrns = [p for p in patterns if p.find('*') < 0]
165 regularPtrns = [p for p in patterns if p.find('*') >= 0]
166
167 # Apply trivial (just case paths)
168 allCasesSet = set(caseList)
169 for path in trivialPtrns:
170 if path in allCasesSet:
171 if path in matched:
172 errors.append((path, "Same case specified more than once"))
173 matched.add(path)
174 else:
175 errors.append((path, "Test case not found"))
176
177 curList = [c for c in curList if c not in matched]
178
179 for pattern in regularPtrns:
180 matchedThisPtrn = set()
181
182 for case in curList:
183 if fnmatch(case, pattern):
184 matchedThisPtrn.add(case)
185
186 if len(matchedThisPtrn) == 0:
187 errors.append((pattern, "Pattern didn't match any cases"))
188
189 matched = matched | matchedThisPtrn
190 curList = [c for c in curList if c not in matched]
191
192 for pattern, reason in errors:
193 print "ERROR: %s: %s" % (reason, pattern)
194
195 if len(errors) > 0:
196 die("Found %s invalid patterns while processing file %s" % (len(errors), filename))
197
198 return [c for c in caseList if op(c in matched)]
199
200def applyInclude (caseList, patterns, filename):
201 return applyPatterns(caseList, patterns, filename, lambda b: b)
202
203def applyExclude (caseList, patterns, filename):
204 return applyPatterns(caseList, patterns, filename, lambda b: not b)
205
206def readPatternLists (mustpass):
207 lists = {}
208 for package in mustpass.packages:
209 for cfg in package.configurations:
210 for filter in cfg.filters:
211 if not filter.filename in lists:
212 lists[filter.filename] = readPatternList(os.path.join(getSrcDir(mustpass), filter.filename))
213 return lists
214
215def applyFilters (caseList, patternLists, filters):
216 res = copy(caseList)
217 for filter in filters:
218 ptrnList = patternLists[filter.filename]
219 if filter.type == Filter.TYPE_INCLUDE:
220 res = applyInclude(res, ptrnList, filter.filename)
221 else:
222 assert filter.type == Filter.TYPE_EXCLUDE
223 res = applyExclude(res, ptrnList, filter.filename)
224 return res
225
226def appendToHierarchy (root, casePath):
227 def findChild (node, name):
228 for child in node.children:
229 if child.name == name:
230 return child
231 return None
232
233 curNode = root
234 components = casePath.split('.')
235
236 for component in components[:-1]:
237 nextNode = findChild(curNode, component)
238 if not nextNode:
239 nextNode = TestGroup(component)
240 curNode.children.append(nextNode)
241 curNode = nextNode
242
243 if not findChild(curNode, components[-1]):
244 curNode.children.append(TestCase(components[-1]))
245
246def buildTestHierachy (caseList):
247 root = TestRoot()
248 for case in caseList:
249 appendToHierarchy(root, case)
250 return root
251
252def buildTestCaseMap (root):
253 caseMap = {}
254
255 def recursiveBuild (curNode, prefix):
256 curPath = prefix + curNode.name
257 if isinstance(curNode, TestCase):
258 caseMap[curPath] = curNode
259 else:
260 for child in curNode.children:
261 recursiveBuild(child, curPath + '.')
262
263 for child in root.children:
264 recursiveBuild(child, '')
265
266 return caseMap
267
268def include (filename):
269 return Filter(Filter.TYPE_INCLUDE, filename)
270
271def exclude (filename):
272 return Filter(Filter.TYPE_EXCLUDE, filename)
273
274def insertXMLHeaders (mustpass, doc):
275 if mustpass.project.copyright != None:
276 doc.insert(0, ElementTree.Comment(mustpass.project.copyright))
277 doc.insert(1, ElementTree.Comment(GENERATED_FILE_WARNING))
278
279def prettifyXML (doc):
280 uglyString = ElementTree.tostring(doc, 'utf-8')
281 reparsed = minidom.parseString(uglyString)
282 return reparsed.toprettyxml(indent='\t', encoding='utf-8')
283
284def genCTSPackageXML (mustpass, package, root):
285 def isLeafGroup (testGroup):
286 numGroups = 0
287 numTests = 0
288
289 for child in testGroup.children:
290 if isinstance(child, TestCase):
291 numTests += 1
292 else:
293 numGroups += 1
294
295 assert numGroups + numTests > 0
296
297 if numGroups > 0 and numTests > 0:
298 die("Mixed groups and cases in %s" % testGroup.name)
299
300 return numGroups == 0
301
302 def makeConfiguration (parentElem, config):
303 attributes = {}
304
305 if config.glconfig != None:
306 attributes['glconfig'] = config.glconfig
307
308 if config.rotation != None:
309 attributes['rotation'] = config.rotation
310
311 if config.surfacetype != None:
312 attributes['surfacetype'] = config.surfacetype
313
314 return ElementTree.SubElement(parentElem, "TestInstance", attributes)
315
316 def makeTestCase (parentElem, testCase):
317 caseElem = ElementTree.SubElement(parentElem, "Test", name=testCase.name)
318 for config in testCase.configurations:
319 makeConfiguration(caseElem, config)
320 return caseElem
321
322 def makeTestGroup (parentElem, testGroup):
323 groupElem = ElementTree.SubElement(parentElem, "TestCase" if isLeafGroup(testGroup) else "TestSuite", name=testGroup.name)
324 for child in testGroup.children:
325 if isinstance(child, TestCase):
326 makeTestCase(groupElem, child)
327 else:
328 makeTestGroup(groupElem, child)
329 return groupElem
330
331 pkgElem = ElementTree.Element("TestPackage",
332 name = package.module.name,
333 appPackageName = getCTSPackageName(package),
334 testType = "deqpTest")
335
336 pkgElem.set("xmlns:deqp", "http://drawelements.com/deqp")
337 insertXMLHeaders(mustpass, pkgElem)
338
339 glesVersion = getModuleGLESVersion(package.module)
340
341 if glesVersion != None:
342 pkgElem.set("deqp:glesVersion", str(glesVersion.encode()))
343
344 for child in root.children:
345 makeTestGroup(pkgElem, child)
346
347 return pkgElem
348
349def genSpecXML (mustpass):
350 mustpassElem = ElementTree.Element("Mustpass", version = mustpass.version)
351 insertXMLHeaders(mustpass, mustpassElem)
352
353 for package in mustpass.packages:
354 packageElem = ElementTree.SubElement(mustpassElem, "TestPackage", name = package.module.name)
355
356 for config in package.configurations:
357 configElem = ElementTree.SubElement(packageElem, "Configuration",
358 name = config.name,
359 caseListFile = getCaseListFileName(package, config),
360 commandLine = getCommandLine(config))
361
362 return mustpassElem
363
364def addOptionElement (parent, optionName, optionValue):
365 ElementTree.SubElement(parent, "option", name=optionName, value=optionValue)
366
367def genAndroidTestXml (mustpass):
368 INSTALLER_CLASS = "com.android.compatibility.common.tradefed.targetprep.ApkInstaller"
369 RUNNER_CLASS = "com.drawelements.deqp.runner.DeqpTestRunner"
370 configElement = ElementTree.Element("configuration")
371 preparerElement = ElementTree.SubElement(configElement, "target_preparer")
372 preparerElement.set("class", INSTALLER_CLASS)
373 addOptionElement(preparerElement, "cleanup-apks", "true")
374 addOptionElement(preparerElement, "test-file-name", APK_NAME)
375
376 for package in mustpass.packages:
377 for config in package.configurations:
378 testElement = ElementTree.SubElement(configElement, "test")
379 testElement.set("class", RUNNER_CLASS)
380 addOptionElement(testElement, "deqp-package", package.module.name)
381 addOptionElement(testElement, "deqp-caselist-file", getCaseListFileName(package,config))
382 # \todo [2015-10-16 kalle]: Replace with just command line? - requires simplifications in the runner/tests as well.
383 if config.glconfig != None:
384 addOptionElement(testElement, "deqp-gl-config-name", config.glconfig)
385
386 if config.surfacetype != None:
387 addOptionElement(testElement, "deqp-surface-type", config.surfacetype)
388
389 if config.rotation != None:
390 addOptionElement(testElement, "deqp-screen-rotation", config.rotation)
391
Kalle Raita25196612016-04-04 17:09:44 -0700392 if config.expectedRuntime != None:
393 addOptionElement(testElement, "runtime-hint", config.expectedRuntime)
394
Pyry Haulos2bbb9d22016-01-14 13:48:08 -0800395 insertXMLHeaders(mustpass, configElement)
396
397 return configElement
398
399def genMustpass (mustpass, moduleCaseLists):
400 print "Generating mustpass '%s'" % mustpass.version
401
402 patternLists = readPatternLists(mustpass)
403
404 for package in mustpass.packages:
405 allCasesInPkg = moduleCaseLists[package.module]
406 matchingByConfig = {}
407 allMatchingSet = set()
408
409 for config in package.configurations:
410 filtered = applyFilters(allCasesInPkg, patternLists, config.filters)
411 dstFile = getDstCaseListPath(mustpass, package, config)
412
413 print " Writing deqp caselist: " + dstFile
414 writeFile(dstFile, "\n".join(filtered) + "\n")
415
416 matchingByConfig[config] = filtered
417 allMatchingSet = allMatchingSet | set(filtered)
418
419 allMatchingCases = [c for c in allCasesInPkg if c in allMatchingSet] # To preserve ordering
420 root = buildTestHierachy(allMatchingCases)
421 testCaseMap = buildTestCaseMap(root)
422
423 for config in package.configurations:
424 for case in matchingByConfig[config]:
425 testCaseMap[case].configurations.append(config)
426
427 # NOTE: CTS v2 does not need package XML files. Remove when transition is complete.
428 packageXml = genCTSPackageXML(mustpass, package, root)
429 xmlFilename = os.path.join(mustpass.project.path, mustpass.version, getCTSPackageName(package) + ".xml")
430
431 print " Writing CTS caselist: " + xmlFilename
432 writeFile(xmlFilename, prettifyXML(packageXml))
433
434 specXML = genSpecXML(mustpass)
435 specFilename = os.path.join(mustpass.project.path, mustpass.version, "mustpass.xml")
436
437 print " Writing spec: " + specFilename
438 writeFile(specFilename, prettifyXML(specXML))
439
440 # TODO: Which is the best selector mechanism?
Mika Isojärvifb2d85c2016-02-04 15:35:09 -0800441 if (mustpass.version == "master"):
Pyry Haulos2bbb9d22016-01-14 13:48:08 -0800442 androidTestXML = genAndroidTestXml(mustpass)
443 androidTestFilename = os.path.join(mustpass.project.path, "AndroidTest.xml")
444
445 print " Writing AndroidTest.xml: " + androidTestFilename
446 writeFile(androidTestFilename, prettifyXML(androidTestXML))
447
448 print "Done!"
449
450def genMustpassLists (mustpassLists, generator, buildCfg):
451 moduleCaseLists = {}
452
453 # Getting case lists involves invoking build, so we want to cache the results
454 for mustpass in mustpassLists:
455 for package in mustpass.packages:
456 if not package.module in moduleCaseLists:
457 moduleCaseLists[package.module] = getCaseList(buildCfg, generator, package.module)
458
459 for mustpass in mustpassLists:
460 genMustpass(mustpass, moduleCaseLists)