generate-wrappers-and-manifest.py (15700B)
1 #!/usr/bin/env python3 2 # 3 # This Source Code Form is subject to the terms of the Mozilla Public 4 # License, v. 2.0. If a copy of the MPL was not distributed with this 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 # 7 # Write a Mochitest manifest for WebGL conformance test files. 8 9 import os 10 import re 11 import shutil 12 from pathlib import Path 13 14 # All paths in this file are based where this file is run. 15 WRAPPER_TEMPLATE_FILE = "mochi-wrapper.html.template" 16 MANIFEST_TEMPLATE_FILE = "mochitest.toml.template" 17 ERRATA_FILE = "mochitest-errata.toml" 18 DEST_MANIFEST_PATHSTR = "generated-mochitest.toml" 19 20 BASE_TEST_LIST_PATHSTR = "checkout/00_test_list.txt" 21 GENERATED_PATHSTR = "generated" 22 WEBGL2_TEST_MANGLE = "2_" 23 PATH_SEP_MANGLING = "__" 24 25 SUPPORT_DIRS = [ 26 "checkout", 27 ] 28 29 EXTRA_SUPPORT_FILES = [ 30 "always-fail.html", 31 "iframe-passthrough.css", 32 "mochi-single.html", 33 ] 34 35 ACCEPTABLE_ERRATA_KEYS = set([ 36 "fail-if", 37 "prefs", 38 "skip-if", 39 "tags", 40 ]) 41 42 43 def ChooseSubsuite(name): 44 # name: generated/test_2_conformance2__vertex_arrays__vertex-array-object.html 45 assert " " not in name, name 46 47 split = name.split("__") 48 49 version = "1" 50 if "/test_2_" in split[0]: 51 version = "2" 52 53 category = "core" 54 55 split[0] = split[0].split("/")[1] 56 if "deqp" in split[0]: 57 if version == "1": 58 # There's few enough that we'll just merge them with webgl1-ext. 59 category = "ext" 60 else: 61 category = "deqp" 62 elif "conformance" in split[0]: 63 if split[1] in ("glsl", "glsl3", "ogles"): 64 category = "ext" 65 elif split[1] == "textures" and split[2] != "misc": 66 category = "ext" 67 68 return f"webgl{version}-{category}" 69 70 71 ######################################################################## 72 # GetTestList 73 74 75 def GetTestList(): 76 split = BASE_TEST_LIST_PATHSTR.rsplit("/", 1) 77 basePath = "." 78 testListFile = split[-1] 79 if len(split) == 2: 80 basePath = split[0] 81 82 allowWebGL1 = True 83 allowWebGL2 = True 84 alwaysFailEntry = TestEntry("always-fail.html", True, False) 85 testList = [alwaysFailEntry] 86 AccumTests(basePath, testListFile, allowWebGL1, allowWebGL2, testList) 87 88 for x in testList: 89 x.path = os.path.relpath(x.path, basePath).replace(os.sep, "/") 90 continue 91 92 return testList 93 94 95 ############################## 96 # Internals 97 98 99 def IsVersionLess(a, b): 100 aSplit = [int(x) for x in a.split(".")] 101 bSplit = [int(x) for x in b.split(".")] 102 103 while len(aSplit) < len(bSplit): 104 aSplit.append(0) 105 106 while len(aSplit) > len(bSplit): 107 bSplit.append(0) 108 109 for i in range(len(aSplit)): 110 aVal = aSplit[i] 111 bVal = bSplit[i] 112 113 if aVal == bVal: 114 continue 115 116 return aVal < bVal 117 118 return False 119 120 121 class TestEntry: 122 def __init__(self, path, webgl1, webgl2): 123 self.path = path 124 self.webgl1 = webgl1 125 self.webgl2 = webgl2 126 127 128 def AccumTests(pathStr, listFile, allowWebGL1, allowWebGL2, out_testList): 129 listPathStr = pathStr + "/" + listFile 130 131 listPath = listPathStr.replace("/", os.sep) 132 assert os.path.exists(listPath), "Bad `listPath`: " + listPath 133 134 with open(listPath) as fIn: 135 lineNum = 0 136 for line in fIn: 137 lineNum += 1 138 139 curLine = line.strip() 140 if not curLine: 141 continue 142 if curLine.startswith("//"): 143 continue 144 if curLine.startswith("#"): 145 continue 146 147 webgl1 = allowWebGL1 148 webgl2 = allowWebGL2 149 parts = curLine.split() 150 while parts[0].startswith("--"): # '--min-version 1.0.2 foo.html' 151 flag = parts.pop(0) 152 if flag == "--min-version": 153 minVersion = parts.pop(0) 154 if not IsVersionLess(minVersion, "2.0.0"): # >= 2.0.0 155 webgl1 = False 156 break 157 elif flag == "--max-version": 158 maxVersion = parts.pop(0) 159 if IsVersionLess(maxVersion, "2.0.0"): 160 webgl2 = False 161 break 162 elif flag == "--slow": 163 continue # TODO 164 else: 165 text = f"Unknown flag '{flag}': {listPath}:{lineNum}: {line}" 166 assert False, text 167 continue 168 169 assert webgl1 or webgl2 170 assert len(parts) == 1, parts 171 testOrManifest = parts[0] 172 173 split = testOrManifest.rsplit(".", 1) 174 assert len(split) == 2, "Bad split for `line`: " + line 175 (name, ext) = split 176 177 if ext == "html": 178 newTestFilePathStr = pathStr + "/" + testOrManifest 179 entry = TestEntry(newTestFilePathStr, webgl1, webgl2) 180 out_testList.append(entry) 181 continue 182 183 assert ext == "txt", "Bad `ext` on `line`: " + line 184 185 split = testOrManifest.rsplit("/", 1) 186 nextListFile = split[-1] 187 nextPathStr = "" 188 if len(split) != 1: 189 nextPathStr = split[0] 190 191 nextPathStr = pathStr + "/" + nextPathStr 192 AccumTests(nextPathStr, nextListFile, webgl1, webgl2, out_testList) 193 continue 194 195 196 ######################################################################## 197 # Templates 198 199 200 def FillTemplate(inFilePath, templateDict, outFilePath): 201 templateShell = ImportTemplate(inFilePath) 202 OutputFilledTemplate(templateShell, templateDict, outFilePath) 203 204 205 def ImportTemplate(inFilePath): 206 with open(inFilePath) as f: 207 return TemplateShell(f) 208 209 210 def OutputFilledTemplate(templateShell, templateDict, outFilePath): 211 spanStrList = templateShell.Fill(templateDict) 212 213 with open(outFilePath, "w", newline="\n") as f: 214 f.writelines(spanStrList) 215 216 217 ############################## 218 # Internals 219 220 221 def WrapWithIndent(lines, indentLen): 222 split = lines.split("\n") 223 if len(split) == 1: 224 return lines 225 226 ret = [split[0]] 227 indentSpaces = " " * indentLen 228 for line in split[1:]: 229 ret.append(indentSpaces + line) 230 231 return "\n".join(ret) 232 233 234 templateRE = re.compile("(%%.*?%%)") 235 assert templateRE.split(" foo = %%BAR%%;") == [" foo = ", "%%BAR%%", ";"] 236 237 238 class TemplateShellSpan: 239 def __init__(self, span): 240 self.span = span 241 242 self.isLiteralSpan = True 243 if self.span.startswith("%%") and self.span.endswith("%%"): 244 self.isLiteralSpan = False 245 self.span = self.span[2:-2] 246 247 def Fill(self, templateDict, indentLen): 248 if self.isLiteralSpan: 249 return self.span 250 251 assert self.span in templateDict, "'" + self.span + "' not in dict!" 252 253 filling = templateDict[self.span] 254 255 return WrapWithIndent(filling, indentLen) 256 257 258 class TemplateShell: 259 def __init__(self, iterableLines): 260 spanList = [] 261 curLiteralSpan = [] 262 for line in iterableLines: 263 split = templateRE.split(line) 264 265 for cur in split: 266 isTemplateSpan = cur.startswith("%%") and cur.endswith("%%") 267 if not isTemplateSpan: 268 curLiteralSpan.append(cur) 269 continue 270 271 if curLiteralSpan: 272 span = "".join(curLiteralSpan) 273 span = TemplateShellSpan(span) 274 spanList.append(span) 275 curLiteralSpan = [] 276 277 assert len(cur) >= 4 278 279 span = TemplateShellSpan(cur) 280 spanList.append(span) 281 continue 282 continue 283 284 if curLiteralSpan: 285 span = "".join(curLiteralSpan) 286 span = TemplateShellSpan(span) 287 spanList.append(span) 288 289 self.spanList = spanList 290 291 # Returns spanStrList. 292 293 def Fill(self, templateDict): 294 indentLen = 0 295 ret = [] 296 for span_ in self.spanList: 297 span = span_.Fill(templateDict, indentLen) 298 ret.append(span) 299 300 # Get next `indentLen`. 301 try: 302 lineStartPos = span.rindex("\n") + 1 303 304 # let span = 'foo\nbar' 305 # len(span) is 7 306 # lineStartPos is 4 307 indentLen = len(span) - lineStartPos 308 except ValueError: 309 indentLen += len(span) 310 continue 311 312 return ret 313 314 315 ######################################################################## 316 # Output 317 318 319 def IsWrapperWebGL2(wrapperPath): 320 return wrapperPath.startswith(GENERATED_PATHSTR + "/test_" + WEBGL2_TEST_MANGLE) 321 322 323 def WriteWrapper(entryPath, webgl2, templateShell, wrapperPathAccum): 324 mangledPath = entryPath.replace("/", PATH_SEP_MANGLING) 325 maybeWebGL2Mangle = "" 326 if webgl2: 327 maybeWebGL2Mangle = WEBGL2_TEST_MANGLE 328 329 # Mochitests must start with 'test_' or similar, or the test 330 # runner will ignore our tests. 331 # The error text is "is not a valid test". 332 wrapperFileName = "test_" + maybeWebGL2Mangle + mangledPath 333 334 wrapperPath = GENERATED_PATHSTR + "/" + wrapperFileName 335 print("Adding wrapper: " + wrapperPath) 336 337 args = "" 338 if webgl2: 339 args = "?webglVersion=2" 340 341 templateDict = { 342 "TEST_PATH": entryPath, 343 "ARGS": args, 344 } 345 346 OutputFilledTemplate(templateShell, templateDict, wrapperPath) 347 348 if webgl2: 349 assert IsWrapperWebGL2(wrapperPath) 350 351 wrapperPathAccum.append(wrapperPath) 352 353 354 def WriteWrappers(testEntryList): 355 templateShell = ImportTemplate(WRAPPER_TEMPLATE_FILE) 356 357 generatedDirPath = GENERATED_PATHSTR.replace("/", os.sep) 358 if not os.path.exists(generatedDirPath): 359 os.mkdir(generatedDirPath) 360 assert os.path.isdir(generatedDirPath) 361 362 wrapperPathList = [] 363 for entry in testEntryList: 364 if entry.webgl1: 365 WriteWrapper(entry.path, False, templateShell, wrapperPathList) 366 if entry.webgl2: 367 WriteWrapper(entry.path, True, templateShell, wrapperPathList) 368 continue 369 370 print(f"{len(wrapperPathList)} wrappers written.\n") 371 return wrapperPathList 372 373 374 kManifestRelPathStr = os.path.relpath(".", os.path.dirname(DEST_MANIFEST_PATHSTR)) 375 kManifestRelPathStr = kManifestRelPathStr.replace(os.sep, "/") 376 377 378 def ManifestPathStr(pathStr): 379 pathStr = kManifestRelPathStr + "/" + pathStr 380 return os.path.normpath(pathStr).replace(os.sep, "/") 381 382 383 def WriteManifest(wrapperPathStrList, supportPathStrList): 384 destPathStr = DEST_MANIFEST_PATHSTR 385 print("Generating manifest: " + destPathStr) 386 387 errataMap = LoadErrata() 388 389 # DEFAULT_ERRATA 390 defaultSectionName = "DEFAULT" 391 392 defaultSectionLines = [] 393 if defaultSectionName in errataMap: 394 defaultSectionLines = errataMap[defaultSectionName] 395 del errataMap[defaultSectionName] 396 397 defaultSectionStr = "\n".join(defaultSectionLines) 398 399 # SUPPORT_FILES 400 supportPathStrList = [ManifestPathStr(x) for x in supportPathStrList] 401 supportPathStrList = sorted(supportPathStrList) 402 supportFilesStr = '",\n "'.join(supportPathStrList) 403 supportFilesStr = '[\n "' + supportFilesStr + '",\n]' 404 405 # MANIFEST_TESTS 406 manifestTestLineList = [] 407 wrapperPathStrList = sorted(wrapperPathStrList) 408 for wrapperPathStr in wrapperPathStrList: 409 wrapperManifestPathStr = ManifestPathStr(wrapperPathStr) 410 sectionName = '\n["' + wrapperManifestPathStr + '"]' 411 manifestTestLineList.append(sectionName) 412 413 errataLines = [] 414 415 subsuite = ChooseSubsuite(wrapperPathStr) 416 errataLines.append('subsuite = "' + subsuite + '"') 417 418 if wrapperPathStr in errataMap: 419 assert subsuite 420 errataLines += errataMap[wrapperPathStr] 421 del errataMap[wrapperPathStr] 422 423 manifestTestLineList += errataLines 424 continue 425 426 if errataMap: 427 print("Errata left in map:") 428 for x in errataMap.keys(): 429 print(" " * 4 + x) 430 assert False 431 432 manifestTestsStr = "\n".join(manifestTestLineList) 433 434 # Fill the template. 435 templateDict = { 436 "DEFAULT_ERRATA": defaultSectionStr, 437 "SUPPORT_FILES": supportFilesStr, 438 "MANIFEST_TESTS": manifestTestsStr, 439 } 440 441 destPath = destPathStr.replace("/", os.sep) 442 FillTemplate(MANIFEST_TEMPLATE_FILE, templateDict, destPath) 443 444 445 ############################## 446 # Internals 447 448 449 def LoadTOML(path): 450 curSectionName = None 451 curSectionMap = {} 452 lineNum = 0 453 ret = {} 454 ret[curSectionName] = (lineNum, curSectionMap) 455 multiLineVal = False 456 key = "" 457 val = "" 458 459 with open(path) as f: 460 for rawLine in f: 461 lineNum += 1 462 if multiLineVal: 463 val += "\n" + rawLine.rstrip() 464 if rawLine.find("]") >= 0: 465 multiLineVal = False 466 curSectionMap[key] = (lineNum, val) 467 else: 468 line = rawLine.strip() 469 if not line: 470 continue 471 if line[0] in [";", "#"]: 472 continue 473 if line[0] == "[": 474 assert line[-1] == "]", f"{path}:{lineNum}" 475 curSectionName = line[1:-1].strip('"') 476 assert curSectionName not in ret, ( 477 f"Line {lineNum}: Duplicate section: {line}" 478 ) 479 curSectionMap = {} 480 ret[curSectionName] = (lineNum, curSectionMap) 481 continue 482 split = line.split("=", 1) 483 key = split[0].strip() 484 val = "" 485 if len(split) == 2: 486 val = split[1].strip() 487 if val.find("[") >= 0 and val.find("]") < 0: 488 multiLineVal = True 489 else: 490 curSectionMap[key] = (lineNum, val) 491 492 return ret 493 494 495 def LoadErrata(): 496 tomlMap = LoadTOML(ERRATA_FILE) 497 498 ret = {} 499 500 for sectionName, (sectionLineNum, sectionMap) in tomlMap.items(): 501 curLines = [] 502 503 if sectionName is None: 504 continue 505 elif sectionName != "DEFAULT": 506 path = sectionName.replace("/", os.sep) 507 assert os.path.exists(path), ( 508 f"Errata line {sectionLineNum}: Invalid file: {sectionName}" 509 ) 510 511 for key, (lineNum, val) in sectionMap.items(): 512 assert key in ACCEPTABLE_ERRATA_KEYS, f"Line {lineNum}: {key}" 513 514 curLine = f"{key} = {val}" 515 curLines.append(curLine) 516 continue 517 518 ret[sectionName] = curLines 519 continue 520 521 return ret 522 523 524 ######################################################################## 525 526 527 def GetSupportFileList(): 528 ret = EXTRA_SUPPORT_FILES[:] 529 530 for pathStr in SUPPORT_DIRS: 531 ret += GetFilePathListForDir(pathStr) 532 continue 533 534 for pathStr in ret: 535 path = pathStr.replace("/", os.sep) 536 assert os.path.exists(path), path + "\n\n\n" + "pathStr: " + str(pathStr) 537 continue 538 539 return ret 540 541 542 def GetFilePathListForDir(baseDir): 543 ret = [] 544 for root, folders, files in os.walk(baseDir): 545 for f in files: 546 filePath = os.path.join(root, f) 547 filePath = filePath.replace(os.sep, "/") 548 ret.append(filePath) 549 550 return ret 551 552 553 if __name__ == "__main__": 554 file_dir = Path(__file__).parent 555 os.chdir(str(file_dir)) 556 shutil.rmtree(file_dir / "generated", True) 557 558 testEntryList = GetTestList() 559 wrapperPathStrList = WriteWrappers(testEntryList) 560 561 supportPathStrList = GetSupportFileList() 562 WriteManifest(wrapperPathStrList, supportPathStrList) 563 564 print("Done!")