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
23import sys
24import os
25import xml.etree.cElementTree as ElementTree
26import xml.dom.minidom as minidom
27
28from build_caselists import Module, getModuleByName, getBuildConfig, genCaseList, getCaseListPath, DEFAULT_BUILD_DIR, DEFAULT_TARGET, GLCTS_BIN_NAME
29
30sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..", "scripts"))
31
32from build.common import *
33from build.config import ANY_GENERATOR
34from build.build import build
35from fnmatch import fnmatch
36from copy import copy
37
38GENERATED_FILE_WARNING = """\
39/* WARNING: This is auto-generated file. Do not modify, since changes will
40 * be lost! Modify the generating script instead.
41 */"""
42
43class Project:
44	def __init__ (self, name, path, incpath, devicepath, copyright = None):
45		self.name		= name
46		self.path		= path
47		self.incpath	= incpath
48		self.devicepath	= devicepath
49		self.copyright	= copyright
50
51class Configuration:
52	def __init__ (self, name, filters, glconfig = None, rotation = "unspecified", surfacetype = None, surfacewidth = None, surfaceheight = None, baseseed = None, fboconfig = None, required = False, runtime = None, os = "any", skip = "none"):
53		self.name				= name
54		self.glconfig			= glconfig
55		self.rotation			= rotation
56		self.surfacetype		= surfacetype
57		self.required			= required
58		self.surfacewidth		= surfacewidth
59		self.surfaceheight		= surfaceheight
60		self.baseseed			= baseseed
61		self.fboconfig			= fboconfig
62		self.filters			= filters
63		self.expectedRuntime	= runtime
64		self.os					= os
65		self.skipPlatform		= skip
66
67class Package:
68	def __init__ (self, module, configurations, useforfirsteglconfig = True):
69		self.module					= module
70		self.useforfirsteglconfig	= useforfirsteglconfig
71		self.configurations			= configurations
72
73class Mustpass:
74	def __init__ (self, project, version, packages, isCurrent):
75		self.project		= project
76		self.version		= version
77		self.packages		= packages
78		self.isCurrent		= isCurrent
79
80class Filter:
81	TYPE_INCLUDE = 0
82	TYPE_EXCLUDE = 1
83
84	def __init__ (self, type, filename):
85		self.type		= type
86		self.filename	= filename
87
88def getSrcDir (mustpass):
89	return os.path.join(mustpass.project.path, mustpass.version, "src")
90
91def getTmpDir (mustpass):
92	return os.path.join(mustpass.project.path, mustpass.version, "tmp")
93
94def getModuleShorthand (module):
95	return module.api.lower()
96
97def getCaseListFileName (package, configuration):
98	return "%s-%s.txt" % (getModuleShorthand(package.module), configuration.name)
99
100def getDstDir(mustpass):
101	return os.path.join(mustpass.project.path, mustpass.version)
102
103def getDstCaseListPath (mustpass, package, configuration):
104	return os.path.join(getDstDir(mustpass), getCaseListFileName(package, configuration))
105
106def getCommandLine (config):
107	cmdLine = ""
108
109	if config.glconfig != None:
110		cmdLine += "--deqp-gl-config-name=%s " % config.glconfig
111
112	if config.rotation != None:
113		cmdLine += "--deqp-screen-rotation=%s " % config.rotation
114
115	if config.surfacetype != None:
116		cmdLine += "--deqp-surface-type=%s " % config.surfacetype
117
118	if config.surfacewidth != None:
119		cmdLine += "--deqp-surface-width=%s " % config.surfacewidth
120
121	if config.surfaceheight != None:
122		cmdLine += "--deqp-surface-height=%s " % config.surfaceheight
123
124	if config.baseseed != None:
125		cmdLine += "--deqp-base-seed=%s " % config.baseseed
126
127	if config.fboconfig != None:
128		cmdLine += "--deqp-gl-config-name=%s --deqp-surface-type=fbo " % config.fboconfig
129
130	cmdLine += "--deqp-watchdog=disable"
131
132	return cmdLine
133
134def readCaseList (filename):
135	cases = []
136	with open(filename, 'rt') as f:
137		for line in f:
138			if line[:6] == "TEST: ":
139				cases.append(line[6:].strip())
140	return cases
141
142def getCaseList (buildCfg, generator, module):
143	return readCaseList(getCaseListPath(buildCfg, module, "txt"))
144
145def readPatternList (filename):
146	ptrns = []
147	with open(filename, 'rt') as f:
148		for line in f:
149			line = line.strip()
150			if len(line) > 0 and line[0] != '#':
151				ptrns.append(line)
152	return ptrns
153
154def applyPatterns (caseList, patterns, filename, op):
155	matched			= set()
156	errors			= []
157	curList			= copy(caseList)
158	trivialPtrns	= [p for p in patterns if p.find('*') < 0]
159	regularPtrns	= [p for p in patterns if p.find('*') >= 0]
160
161	# Apply trivial (just case paths)
162	allCasesSet		= set(caseList)
163	for path in trivialPtrns:
164		if path in allCasesSet:
165			if path in matched:
166				errors.append((path, "Same case specified more than once"))
167			matched.add(path)
168		else:
169			errors.append((path, "Test case not found"))
170
171	curList = [c for c in curList if c not in matched]
172
173	for pattern in regularPtrns:
174		matchedThisPtrn = set()
175
176		for case in curList:
177			if fnmatch(case, pattern):
178				matchedThisPtrn.add(case)
179
180		if len(matchedThisPtrn) == 0:
181			errors.append((pattern, "Pattern didn't match any cases"))
182
183		matched	= matched | matchedThisPtrn
184		curList = [c for c in curList if c not in matched]
185
186	for pattern, reason in errors:
187		print("ERROR: %s: %s" % (reason, pattern))
188
189	if len(errors) > 0:
190		die("Found %s invalid patterns while processing file %s" % (len(errors), filename))
191
192	return [c for c in caseList if op(c in matched)]
193
194def applyInclude (caseList, patterns, filename):
195	return applyPatterns(caseList, patterns, filename, lambda b: b)
196
197def applyExclude (caseList, patterns, filename):
198	return applyPatterns(caseList, patterns, filename, lambda b: not b)
199
200def readPatternLists (mustpass):
201	lists = {}
202	for package in mustpass.packages:
203		for cfg in package.configurations:
204			for filter in cfg.filters:
205				if not filter.filename in lists:
206					lists[filter.filename] = readPatternList(os.path.join(getSrcDir(mustpass), filter.filename))
207	return lists
208
209def applyFilters (caseList, patternLists, filters):
210	res = copy(caseList)
211	for filter in filters:
212		ptrnList = patternLists[filter.filename]
213		if filter.type == Filter.TYPE_INCLUDE:
214			res = applyInclude(res, ptrnList, filter.filename)
215		else:
216			assert filter.type == Filter.TYPE_EXCLUDE
217			res = applyExclude(res, ptrnList, filter.filename)
218	return res
219
220
221def include (filename):
222	return Filter(Filter.TYPE_INCLUDE, filename)
223
224def exclude (filename):
225	return Filter(Filter.TYPE_EXCLUDE, filename)
226
227def insertXMLHeaders (mustpass, doc):
228	if mustpass.project.copyright != None:
229		doc.insert(0, ElementTree.Comment(mustpass.project.copyright))
230	doc.insert(1, ElementTree.Comment(GENERATED_FILE_WARNING))
231
232def prettifyXML (doc):
233	uglyString	= ElementTree.tostring(doc, 'utf-8')
234	reparsed	= minidom.parseString(uglyString)
235	return reparsed.toprettyxml(indent='\t', encoding='utf-8')
236
237def genSpecXML (mustpass):
238	mustpassElem = ElementTree.Element("Mustpass", version = mustpass.version)
239	insertXMLHeaders(mustpass, mustpassElem)
240
241	packageElem = ElementTree.SubElement(mustpassElem, "TestPackage", name = mustpass.project.name)
242
243	for package in mustpass.packages:
244		for config in package.configurations:
245			configElem = ElementTree.SubElement(packageElem, "Configuration",
246							caseListFile			= getCaseListFileName(package, config),
247							commandLine				= getCommandLine(config),
248							name					= config.name,
249							os						= str(config.os),
250							useForFirstEGLConfig	= str(package.useforfirsteglconfig)
251							)
252
253	return mustpassElem
254
255def getIncludeGuardName (headerFile):
256	return '_' + os.path.basename(headerFile).upper().replace('.', '_')
257
258def convertToCamelcase(s):
259    return ''.join(w.capitalize() or '_' for w in s.split('_'))
260
261def getApiType(apiName):
262	if apiName == "GLES2":
263		return "glu::ApiType::es(2, 0)"
264	if apiName == "GLES3":
265		return "glu::ApiType::es(3, 0)"
266	if apiName == "GLES31":
267		return "glu::ApiType::es(3, 1)"
268	if apiName == "GLES32":
269		return "glu::ApiType::es(3, 2)"
270	if apiName == "GL46":
271		return "glu::ApiType::core(4, 6)"
272	if apiName == "GL45":
273		return "glu::ApiType::core(4, 5)"
274	if apiName == "GL44":
275		return "glu::ApiType::core(4, 4)"
276	if apiName == "GL43":
277		return "glu::ApiType::core(4, 3)"
278	if apiName == "GL42":
279		return "glu::ApiType::core(4, 2)"
280	if apiName == "GL41":
281		return "glu::ApiType::core(4, 1)"
282	if apiName == "GL40":
283		return "glu::ApiType::core(4, 0)"
284	if apiName == "GL33":
285		return "glu::ApiType::core(3, 3)"
286	if apiName == "GL32":
287		return "glu::ApiType::core(3, 2)"
288	if apiName == "GL31":
289		return "glu::ApiType::core(3, 1)"
290	if apiName == "GL30":
291		return "glu::ApiType::core(3, 0)"
292	if apiName == "EGL":
293		return "glu::ApiType()"
294
295	raise Exception("Unknown API %s" % apiName)
296	return "Unknown"
297
298def getConfigName(cfgName):
299	if cfgName == None:
300		return "DE_NULL"
301	else:
302		return '"' + cfgName + '"'
303
304def getIntBaseSeed(baseSeed):
305	if baseSeed == None:
306		return "-1"
307	else:
308		return baseSeed
309
310def genSpecCPPIncludeFile (specFilename, mustpass):
311	fileBody = ""
312
313	includeGuard = getIncludeGuardName(specFilename)
314	fileBody += "#ifndef %s\n" % includeGuard
315	fileBody += "#define %s\n" % includeGuard
316	fileBody += mustpass.project.copyright
317	fileBody += "\n\n"
318	fileBody += GENERATED_FILE_WARNING
319	fileBody += "\n\n"
320	fileBody += 'const char* mustpassDir = "' + mustpass.project.devicepath + '/' + mustpass.version + '/";\n\n'
321
322	gtf_wrapper_open = "#if defined(DEQP_GTF_AVAILABLE)\n"
323	gtf_wrapper_close = "#endif // defined(DEQP_GTF_AVAILABLE)\n"
324	android_wrapper_open = "#if DE_OS == DE_OS_ANDROID\n"
325	android_wrapper_close = "#endif // DE_OS == DE_OS_ANDROID\n"
326	skip_x11_wrapper_open = "#ifndef DEQP_SUPPORT_X11\n"
327	skip_x11_wrapper_close = "#endif // DEQP_SUPPORT_X11\n"
328	TABLE_ELEM_PATTERN	= "{apiType} {configName} {glConfigName} {screenRotation} {baseSeed} {fboConfig} {surfaceWidth} {surfaceHeight}"
329
330	emitOtherCfgTbl = False
331	firstCfgDecl = "static const RunParams %s_first_cfg[] = " % mustpass.project.name.lower().replace(' ','_')
332	firstCfgTbl = "{\n"
333
334	otherCfgDecl = "static const RunParams %s_other_cfg[] = " % mustpass.project.name.lower().replace(' ','_')
335	otherCfgTbl = "{\n"
336
337	for package in mustpass.packages:
338		for config in package.configurations:
339			pApiType = getApiType(package.module.api) + ','
340			pConfigName = '"' + config.name + '",'
341			pGLConfig = getConfigName(config.glconfig) + ','
342			pRotation = '"' + config.rotation + '",'
343			pSeed =  getIntBaseSeed(config.baseseed) + ','
344			pFBOConfig = getConfigName(config.fboconfig) + ','
345			pWidth = config.surfacewidth + ','
346			pHeight = config.surfaceheight
347			elemFinal = ""
348			elemContent = TABLE_ELEM_PATTERN.format(apiType = pApiType, configName = pConfigName, glConfigName = pGLConfig, screenRotation = pRotation, baseSeed = pSeed, fboConfig = pFBOConfig, surfaceWidth = pWidth, surfaceHeight = pHeight)
349			elem = "\t{ " + elemContent + " },\n"
350			if package.module.name[:3] == "GTF":
351				elemFinal += gtf_wrapper_open
352
353			if config.os == "android":
354				elemFinal += android_wrapper_open
355
356			if config.skipPlatform == "x11":
357				elemFinal += skip_x11_wrapper_open
358
359			elemFinal += elem
360
361			if config.skipPlatform == "x11":
362				elemFinal += skip_x11_wrapper_close
363
364			if config.os == "android":
365				elemFinal += android_wrapper_close
366
367			if package.module.name[:3] == "GTF":
368				elemFinal += gtf_wrapper_close
369
370			if package.useforfirsteglconfig == True:
371				firstCfgTbl += elemFinal
372			else:
373				otherCfgTbl += elemFinal
374				emitOtherCfgTbl = True
375
376	firstCfgTbl += "};\n"
377	otherCfgTbl += "};\n"
378
379	fileBody += firstCfgDecl
380	fileBody += firstCfgTbl
381
382	if emitOtherCfgTbl == True:
383		fileBody += "\n"
384		fileBody += otherCfgDecl
385		fileBody += otherCfgTbl
386
387	fileBody += "\n"
388	fileBody += "#endif // %s\n" % includeGuard
389	return fileBody
390
391
392def genSpecCPPIncludes (mustpassLists):
393	for mustpass in mustpassLists:
394		if mustpass.isCurrent == True:
395			specFilename	= os.path.join(mustpass.project.incpath, "glc%s.hpp" % convertToCamelcase(mustpass.project.name.lower().replace(' ','_')))
396			hpp = genSpecCPPIncludeFile(specFilename, mustpass)
397
398			print("  Writing spec: " + specFilename)
399			writeFile(specFilename, hpp)
400			print("Done!")
401
402def genMustpass (mustpass, moduleCaseLists):
403	print("Generating mustpass '%s'" % mustpass.version)
404
405	patternLists = readPatternLists(mustpass)
406
407	for package in mustpass.packages:
408		allCasesInPkg	= moduleCaseLists[package.module]
409
410		for config in package.configurations:
411			filtered	= applyFilters(allCasesInPkg, patternLists, config.filters)
412			dstFile		= getDstCaseListPath(mustpass, package, config)
413
414			print("  Writing deqp caselist: " + dstFile)
415			writeFile(dstFile, "\n".join(filtered) + "\n")
416
417	specXML			= genSpecXML(mustpass)
418	specFilename	= os.path.join(mustpass.project.path, mustpass.version, "mustpass.xml")
419
420	print("  Writing spec: " + specFilename)
421	writeFile(specFilename, prettifyXML(specXML).decode())
422
423	print("Done!")
424
425def genMustpassLists (mustpassLists, generator, buildCfg):
426	moduleCaseLists = {}
427
428	# Getting case lists involves invoking build, so we want to cache the results
429	build(buildCfg, generator, [GLCTS_BIN_NAME])
430	genCaseList(buildCfg, generator, "txt")
431	for mustpass in mustpassLists:
432		for package in mustpass.packages:
433			if not package.module in moduleCaseLists:
434				moduleCaseLists[package.module] = getCaseList(buildCfg, generator, package.module)
435
436	for mustpass in mustpassLists:
437		genMustpass(mustpass, moduleCaseLists)
438
439
440	genSpecCPPIncludes(mustpassLists)
441