xref: /aosp_15_r20/external/deqp/external/openglcts/scripts/mustpass.py (revision 35238bce31c2a825756842865a792f8cf7f89930)
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