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