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