Bug 1915045 Ensure decode tasks are scheduled on BufferingState::Enter() r=media...
[gecko.git] / dom / canvas / test / webgl-conf / generate-wrappers-and-manifest.py
blob0f6a38feb5b7e4451f3459431c884f9d0fd3c8bd
1 #!/usr/bin/env python3
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/.
7 # Write a Mochitest manifest for WebGL conformance test files.
9 import os
10 import re
11 import shutil
12 from pathlib import Path
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"
20 BASE_TEST_LIST_PATHSTR = "checkout/00_test_list.txt"
21 GENERATED_PATHSTR = "generated"
22 WEBGL2_TEST_MANGLE = "2_"
23 PATH_SEP_MANGLING = "__"
25 SUPPORT_DIRS = [
26 "checkout",
29 EXTRA_SUPPORT_FILES = [
30 "always-fail.html",
31 "iframe-passthrough.css",
32 "mochi-single.html",
35 ACCEPTABLE_ERRATA_KEYS = set(
37 "fail-if",
38 "skip-if",
39 "prefs",
44 def ChooseSubsuite(name):
45 # name: generated/test_2_conformance2__vertex_arrays__vertex-array-object.html
46 assert " " not in name, name
48 split = name.split("__")
50 version = "1"
51 if "/test_2_" in split[0]:
52 version = "2"
54 category = "core"
56 split[0] = split[0].split("/")[1]
57 if "deqp" in split[0]:
58 if version == "1":
59 # There's few enough that we'll just merge them with webgl1-ext.
60 category = "ext"
61 else:
62 category = "deqp"
63 elif "conformance" in split[0]:
64 if split[1] in ("glsl", "glsl3", "ogles"):
65 category = "ext"
66 elif split[1] == "textures" and split[2] != "misc":
67 category = "ext"
69 return "webgl{}-{}".format(version, category)
72 ########################################################################
73 # GetTestList
76 def GetTestList():
77 split = BASE_TEST_LIST_PATHSTR.rsplit("/", 1)
78 basePath = "."
79 testListFile = split[-1]
80 if len(split) == 2:
81 basePath = split[0]
83 allowWebGL1 = True
84 allowWebGL2 = True
85 alwaysFailEntry = TestEntry("always-fail.html", True, False)
86 testList = [alwaysFailEntry]
87 AccumTests(basePath, testListFile, allowWebGL1, allowWebGL2, testList)
89 for x in testList:
90 x.path = os.path.relpath(x.path, basePath).replace(os.sep, "/")
91 continue
93 return testList
96 ##############################
97 # Internals
100 def IsVersionLess(a, b):
101 aSplit = [int(x) for x in a.split(".")]
102 bSplit = [int(x) for x in b.split(".")]
104 while len(aSplit) < len(bSplit):
105 aSplit.append(0)
107 while len(aSplit) > len(bSplit):
108 bSplit.append(0)
110 for i in range(len(aSplit)):
111 aVal = aSplit[i]
112 bVal = bSplit[i]
114 if aVal == bVal:
115 continue
117 return aVal < bVal
119 return False
122 class TestEntry:
123 def __init__(self, path, webgl1, webgl2):
124 self.path = path
125 self.webgl1 = webgl1
126 self.webgl2 = webgl2
127 return
130 def AccumTests(pathStr, listFile, allowWebGL1, allowWebGL2, out_testList):
131 listPathStr = pathStr + "/" + listFile
133 listPath = listPathStr.replace("/", os.sep)
134 assert os.path.exists(listPath), "Bad `listPath`: " + listPath
136 with open(listPath, "r") as fIn:
137 lineNum = 0
138 for line in fIn:
139 lineNum += 1
141 curLine = line.strip()
142 if not curLine:
143 continue
144 if curLine.startswith("//"):
145 continue
146 if curLine.startswith("#"):
147 continue
149 webgl1 = allowWebGL1
150 webgl2 = allowWebGL2
151 parts = curLine.split()
152 while parts[0].startswith("--"): # '--min-version 1.0.2 foo.html'
153 flag = parts.pop(0)
154 if flag == "--min-version":
155 minVersion = parts.pop(0)
156 if not IsVersionLess(minVersion, "2.0.0"): # >= 2.0.0
157 webgl1 = False
158 break
159 elif flag == "--max-version":
160 maxVersion = parts.pop(0)
161 if IsVersionLess(maxVersion, "2.0.0"):
162 webgl2 = False
163 break
164 elif flag == "--slow":
165 continue # TODO
166 else:
167 text = "Unknown flag '{}': {}:{}: {}".format(
168 flag, listPath, lineNum, line
170 assert False, text
171 continue
173 assert webgl1 or webgl2
174 assert len(parts) == 1, parts
175 testOrManifest = parts[0]
177 split = testOrManifest.rsplit(".", 1)
178 assert len(split) == 2, "Bad split for `line`: " + line
179 (name, ext) = split
181 if ext == "html":
182 newTestFilePathStr = pathStr + "/" + testOrManifest
183 entry = TestEntry(newTestFilePathStr, webgl1, webgl2)
184 out_testList.append(entry)
185 continue
187 assert ext == "txt", "Bad `ext` on `line`: " + line
189 split = testOrManifest.rsplit("/", 1)
190 nextListFile = split[-1]
191 nextPathStr = ""
192 if len(split) != 1:
193 nextPathStr = split[0]
195 nextPathStr = pathStr + "/" + nextPathStr
196 AccumTests(nextPathStr, nextListFile, webgl1, webgl2, out_testList)
197 continue
199 return
202 ########################################################################
203 # Templates
206 def FillTemplate(inFilePath, templateDict, outFilePath):
207 templateShell = ImportTemplate(inFilePath)
208 OutputFilledTemplate(templateShell, templateDict, outFilePath)
209 return
212 def ImportTemplate(inFilePath):
213 with open(inFilePath, "r") as f:
214 return TemplateShell(f)
217 def OutputFilledTemplate(templateShell, templateDict, outFilePath):
218 spanStrList = templateShell.Fill(templateDict)
220 with open(outFilePath, "w", newline="\n") as f:
221 f.writelines(spanStrList)
222 return
225 ##############################
226 # Internals
229 def WrapWithIndent(lines, indentLen):
230 split = lines.split("\n")
231 if len(split) == 1:
232 return lines
234 ret = [split[0]]
235 indentSpaces = " " * indentLen
236 for line in split[1:]:
237 ret.append(indentSpaces + line)
239 return "\n".join(ret)
242 templateRE = re.compile("(%%.*?%%)")
243 assert templateRE.split(" foo = %%BAR%%;") == [" foo = ", "%%BAR%%", ";"]
246 class TemplateShellSpan:
247 def __init__(self, span):
248 self.span = span
250 self.isLiteralSpan = True
251 if self.span.startswith("%%") and self.span.endswith("%%"):
252 self.isLiteralSpan = False
253 self.span = self.span[2:-2]
255 return
257 def Fill(self, templateDict, indentLen):
258 if self.isLiteralSpan:
259 return self.span
261 assert self.span in templateDict, "'" + self.span + "' not in dict!"
263 filling = templateDict[self.span]
265 return WrapWithIndent(filling, indentLen)
268 class TemplateShell:
269 def __init__(self, iterableLines):
270 spanList = []
271 curLiteralSpan = []
272 for line in iterableLines:
273 split = templateRE.split(line)
275 for cur in split:
276 isTemplateSpan = cur.startswith("%%") and cur.endswith("%%")
277 if not isTemplateSpan:
278 curLiteralSpan.append(cur)
279 continue
281 if curLiteralSpan:
282 span = "".join(curLiteralSpan)
283 span = TemplateShellSpan(span)
284 spanList.append(span)
285 curLiteralSpan = []
287 assert len(cur) >= 4
289 span = TemplateShellSpan(cur)
290 spanList.append(span)
291 continue
292 continue
294 if curLiteralSpan:
295 span = "".join(curLiteralSpan)
296 span = TemplateShellSpan(span)
297 spanList.append(span)
299 self.spanList = spanList
300 return
302 # Returns spanStrList.
304 def Fill(self, templateDict):
305 indentLen = 0
306 ret = []
307 for span in self.spanList:
308 span = span.Fill(templateDict, indentLen)
309 ret.append(span)
311 # Get next `indentLen`.
312 try:
313 lineStartPos = span.rindex("\n") + 1
315 # let span = 'foo\nbar'
316 # len(span) is 7
317 # lineStartPos is 4
318 indentLen = len(span) - lineStartPos
319 except ValueError:
320 indentLen += len(span)
321 continue
323 return ret
326 ########################################################################
327 # Output
330 def IsWrapperWebGL2(wrapperPath):
331 return wrapperPath.startswith(GENERATED_PATHSTR + "/test_" + WEBGL2_TEST_MANGLE)
334 def WriteWrapper(entryPath, webgl2, templateShell, wrapperPathAccum):
335 mangledPath = entryPath.replace("/", PATH_SEP_MANGLING)
336 maybeWebGL2Mangle = ""
337 if webgl2:
338 maybeWebGL2Mangle = WEBGL2_TEST_MANGLE
340 # Mochitests must start with 'test_' or similar, or the test
341 # runner will ignore our tests.
342 # The error text is "is not a valid test".
343 wrapperFileName = "test_" + maybeWebGL2Mangle + mangledPath
345 wrapperPath = GENERATED_PATHSTR + "/" + wrapperFileName
346 print("Adding wrapper: " + wrapperPath)
348 args = ""
349 if webgl2:
350 args = "?webglVersion=2"
352 templateDict = {
353 "TEST_PATH": entryPath,
354 "ARGS": args,
357 OutputFilledTemplate(templateShell, templateDict, wrapperPath)
359 if webgl2:
360 assert IsWrapperWebGL2(wrapperPath)
362 wrapperPathAccum.append(wrapperPath)
363 return
366 def WriteWrappers(testEntryList):
367 templateShell = ImportTemplate(WRAPPER_TEMPLATE_FILE)
369 generatedDirPath = GENERATED_PATHSTR.replace("/", os.sep)
370 if not os.path.exists(generatedDirPath):
371 os.mkdir(generatedDirPath)
372 assert os.path.isdir(generatedDirPath)
374 wrapperPathList = []
375 for entry in testEntryList:
376 if entry.webgl1:
377 WriteWrapper(entry.path, False, templateShell, wrapperPathList)
378 if entry.webgl2:
379 WriteWrapper(entry.path, True, templateShell, wrapperPathList)
380 continue
382 print("{} wrappers written.\n".format(len(wrapperPathList)))
383 return wrapperPathList
386 kManifestRelPathStr = os.path.relpath(".", os.path.dirname(DEST_MANIFEST_PATHSTR))
387 kManifestRelPathStr = kManifestRelPathStr.replace(os.sep, "/")
390 def ManifestPathStr(pathStr):
391 pathStr = kManifestRelPathStr + "/" + pathStr
392 return os.path.normpath(pathStr).replace(os.sep, "/")
395 def WriteManifest(wrapperPathStrList, supportPathStrList):
396 destPathStr = DEST_MANIFEST_PATHSTR
397 print("Generating manifest: " + destPathStr)
399 errataMap = LoadErrata()
401 # DEFAULT_ERRATA
402 defaultSectionName = "DEFAULT"
404 defaultSectionLines = []
405 if defaultSectionName in errataMap:
406 defaultSectionLines = errataMap[defaultSectionName]
407 del errataMap[defaultSectionName]
409 defaultSectionStr = "\n".join(defaultSectionLines)
411 # SUPPORT_FILES
412 supportPathStrList = [ManifestPathStr(x) for x in supportPathStrList]
413 supportPathStrList = sorted(supportPathStrList)
414 supportFilesStr = '",\n "'.join(supportPathStrList)
415 supportFilesStr = '[\n "' + supportFilesStr + '",\n]'
417 # MANIFEST_TESTS
418 manifestTestLineList = []
419 wrapperPathStrList = sorted(wrapperPathStrList)
420 for wrapperPathStr in wrapperPathStrList:
421 wrapperManifestPathStr = ManifestPathStr(wrapperPathStr)
422 sectionName = '\n["' + wrapperManifestPathStr + '"]'
423 manifestTestLineList.append(sectionName)
425 errataLines = []
427 subsuite = ChooseSubsuite(wrapperPathStr)
428 errataLines.append('subsuite = "' + subsuite + '"')
430 if wrapperPathStr in errataMap:
431 assert subsuite
432 errataLines += errataMap[wrapperPathStr]
433 del errataMap[wrapperPathStr]
435 manifestTestLineList += errataLines
436 continue
438 if errataMap:
439 print("Errata left in map:")
440 for x in errataMap.keys():
441 print(" " * 4 + x)
442 assert False
444 manifestTestsStr = "\n".join(manifestTestLineList)
446 # Fill the template.
447 templateDict = {
448 "DEFAULT_ERRATA": defaultSectionStr,
449 "SUPPORT_FILES": supportFilesStr,
450 "MANIFEST_TESTS": manifestTestsStr,
453 destPath = destPathStr.replace("/", os.sep)
454 FillTemplate(MANIFEST_TEMPLATE_FILE, templateDict, destPath)
455 return
458 ##############################
459 # Internals
462 def LoadTOML(path):
463 curSectionName = None
464 curSectionMap = {}
465 lineNum = 0
466 ret = {}
467 ret[curSectionName] = (lineNum, curSectionMap)
468 multiLineVal = False
469 key = ""
470 val = ""
472 with open(path, "r") as f:
473 for rawLine in f:
474 lineNum += 1
475 if multiLineVal:
476 val += "\n" + rawLine.rstrip()
477 if rawLine.find("]") >= 0:
478 multiLineVal = False
479 curSectionMap[key] = (lineNum, val)
480 else:
481 line = rawLine.strip()
482 if not line:
483 continue
484 if line[0] in [";", "#"]:
485 continue
486 if line[0] == "[":
487 assert line[-1] == "]", "{}:{}".format(path, lineNum)
488 curSectionName = line[1:-1].strip('"')
489 assert (
490 curSectionName not in ret
491 ), "Line {}: Duplicate section: {}".format(lineNum, line)
492 curSectionMap = {}
493 ret[curSectionName] = (lineNum, curSectionMap)
494 continue
495 split = line.split("=", 1)
496 key = split[0].strip()
497 val = ""
498 if len(split) == 2:
499 val = split[1].strip()
500 if val.find("[") >= 0 and val.find("]") < 0:
501 multiLineVal = True
502 else:
503 curSectionMap[key] = (lineNum, val)
505 return ret
508 def LoadErrata():
509 tomlMap = LoadTOML(ERRATA_FILE)
511 ret = {}
513 for sectionName, (sectionLineNum, sectionMap) in tomlMap.items():
514 curLines = []
516 if sectionName is None:
517 continue
518 elif sectionName != "DEFAULT":
519 path = sectionName.replace("/", os.sep)
520 assert os.path.exists(path), "Errata line {}: Invalid file: {}".format(
521 sectionLineNum, sectionName
524 for key, (lineNum, val) in sectionMap.items():
525 assert key in ACCEPTABLE_ERRATA_KEYS, "Line {}: {}".format(lineNum, key)
527 curLine = "{} = {}".format(key, val)
528 curLines.append(curLine)
529 continue
531 ret[sectionName] = curLines
532 continue
534 return ret
537 ########################################################################
540 def GetSupportFileList():
541 ret = EXTRA_SUPPORT_FILES[:]
543 for pathStr in SUPPORT_DIRS:
544 ret += GetFilePathListForDir(pathStr)
545 continue
547 for pathStr in ret:
548 path = pathStr.replace("/", os.sep)
549 assert os.path.exists(path), path + "\n\n\n" + "pathStr: " + str(pathStr)
550 continue
552 return ret
555 def GetFilePathListForDir(baseDir):
556 ret = []
557 for root, folders, files in os.walk(baseDir):
558 for f in files:
559 filePath = os.path.join(root, f)
560 filePath = filePath.replace(os.sep, "/")
561 ret.append(filePath)
563 return ret
566 if __name__ == "__main__":
567 file_dir = Path(__file__).parent
568 os.chdir(str(file_dir))
569 shutil.rmtree(file_dir / "generated", True)
571 testEntryList = GetTestList()
572 wrapperPathStrList = WriteWrappers(testEntryList)
574 supportPathStrList = GetSupportFileList()
575 WriteManifest(wrapperPathStrList, supportPathStrList)
577 print("Done!")