• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 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:
45	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
52
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	RUNNER_CLASS = "com.drawelements.deqp.runner.DeqpTestRunner"
369	configElement = ElementTree.Element("configuration")
370
371	for package in mustpass.packages:
372		for config in package.configurations:
373			testElement = ElementTree.SubElement(configElement, "test")
374			testElement.set("class", RUNNER_CLASS)
375			addOptionElement(testElement, "deqp-package", package.module.name)
376			addOptionElement(testElement, "deqp-caselist-file", getCaseListFileName(package,config))
377			# \todo [2015-10-16 kalle]: Replace with just command line? - requires simplifications in the runner/tests as well.
378			if config.glconfig != None:
379				addOptionElement(testElement, "deqp-gl-config-name", config.glconfig)
380
381			if config.surfacetype != None:
382				addOptionElement(testElement, "deqp-surface-type", config.surfacetype)
383
384			if config.rotation != None:
385				addOptionElement(testElement, "deqp-screen-rotation", config.rotation)
386
387			if config.expectedRuntime != None:
388				addOptionElement(testElement, "runtime-hint", config.expectedRuntime)
389
390	insertXMLHeaders(mustpass, configElement)
391
392	return configElement
393
394def genMustpass (mustpass, moduleCaseLists):
395	print "Generating mustpass '%s'" % mustpass.version
396
397	patternLists = readPatternLists(mustpass)
398
399	for package in mustpass.packages:
400		allCasesInPkg		= moduleCaseLists[package.module]
401		matchingByConfig	= {}
402		allMatchingSet		= set()
403
404		for config in package.configurations:
405			filtered	= applyFilters(allCasesInPkg, patternLists, config.filters)
406			dstFile		= getDstCaseListPath(mustpass, package, config)
407
408			print "  Writing deqp caselist: " + dstFile
409			writeFile(dstFile, "\n".join(filtered) + "\n")
410
411			matchingByConfig[config]	= filtered
412			allMatchingSet				= allMatchingSet | set(filtered)
413
414		allMatchingCases	= [c for c in allCasesInPkg if c in allMatchingSet] # To preserve ordering
415		root				= buildTestHierachy(allMatchingCases)
416		testCaseMap			= buildTestCaseMap(root)
417
418		for config in package.configurations:
419			for case in matchingByConfig[config]:
420				testCaseMap[case].configurations.append(config)
421
422		# NOTE: CTS v2 does not need package XML files. Remove when transition is complete.
423		packageXml	= genCTSPackageXML(mustpass, package, root)
424		xmlFilename	= os.path.join(mustpass.project.path, mustpass.version, getCTSPackageName(package) + ".xml")
425
426		print "  Writing CTS caselist: " + xmlFilename
427		writeFile(xmlFilename, prettifyXML(packageXml))
428
429	specXML			= genSpecXML(mustpass)
430	specFilename	= os.path.join(mustpass.project.path, mustpass.version, "mustpass.xml")
431
432	print "  Writing spec: " + specFilename
433	writeFile(specFilename, prettifyXML(specXML))
434
435	# TODO: Which is the best selector mechanism?
436	if (mustpass.version == "master"):
437		androidTestXML		= genAndroidTestXml(mustpass)
438		androidTestFilename	= os.path.join(mustpass.project.path, "AndroidTest.xml")
439
440		print "  Writing AndroidTest.xml: " + androidTestFilename
441		writeFile(androidTestFilename, prettifyXML(androidTestXML))
442
443	print "Done!"
444
445def genMustpassLists (mustpassLists, generator, buildCfg):
446	moduleCaseLists = {}
447
448	# Getting case lists involves invoking build, so we want to cache the results
449	for mustpass in mustpassLists:
450		for package in mustpass.packages:
451			if not package.module in moduleCaseLists:
452				moduleCaseLists[package.module] = getCaseList(buildCfg, generator, package.module)
453
454	for mustpass in mustpassLists:
455		genMustpass(mustpass, moduleCaseLists)
456