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.
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
= "__"
29 EXTRA_SUPPORT_FILES
= [
31 "iframe-passthrough.css",
35 ACCEPTABLE_ERRATA_KEYS
= set(
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("__")
51 if "/test_2_" in split
[0]:
56 split
[0] = split
[0].split("/")[1]
57 if "deqp" in split
[0]:
59 # There's few enough that we'll just merge them with webgl1-ext.
63 elif "conformance" in split
[0]:
64 if split
[1] in ("glsl", "glsl3", "ogles"):
66 elif split
[1] == "textures" and split
[2] != "misc":
69 return "webgl{}-{}".format(version
, category
)
72 ########################################################################
77 split
= BASE_TEST_LIST_PATHSTR
.rsplit("/", 1)
79 testListFile
= split
[-1]
85 alwaysFailEntry
= TestEntry("always-fail.html", True, False)
86 testList
= [alwaysFailEntry
]
87 AccumTests(basePath
, testListFile
, allowWebGL1
, allowWebGL2
, testList
)
90 x
.path
= os
.path
.relpath(x
.path
, basePath
).replace(os
.sep
, "/")
96 ##############################
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
):
107 while len(aSplit
) > len(bSplit
):
110 for i
in range(len(aSplit
)):
123 def __init__(self
, path
, webgl1
, webgl2
):
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
:
141 curLine
= line
.strip()
144 if curLine
.startswith("//"):
146 if curLine
.startswith("#"):
151 parts
= curLine
.split()
152 while parts
[0].startswith("--"): # '--min-version 1.0.2 foo.html'
154 if flag
== "--min-version":
155 minVersion
= parts
.pop(0)
156 if not IsVersionLess(minVersion
, "2.0.0"): # >= 2.0.0
159 elif flag
== "--max-version":
160 maxVersion
= parts
.pop(0)
161 if IsVersionLess(maxVersion
, "2.0.0"):
164 elif flag
== "--slow":
167 text
= "Unknown flag '{}': {}:{}: {}".format(
168 flag
, listPath
, lineNum
, line
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
182 newTestFilePathStr
= pathStr
+ "/" + testOrManifest
183 entry
= TestEntry(newTestFilePathStr
, webgl1
, webgl2
)
184 out_testList
.append(entry
)
187 assert ext
== "txt", "Bad `ext` on `line`: " + line
189 split
= testOrManifest
.rsplit("/", 1)
190 nextListFile
= split
[-1]
193 nextPathStr
= split
[0]
195 nextPathStr
= pathStr
+ "/" + nextPathStr
196 AccumTests(nextPathStr
, nextListFile
, webgl1
, webgl2
, out_testList
)
202 ########################################################################
206 def FillTemplate(inFilePath
, templateDict
, outFilePath
):
207 templateShell
= ImportTemplate(inFilePath
)
208 OutputFilledTemplate(templateShell
, templateDict
, outFilePath
)
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
)
225 ##############################
229 def WrapWithIndent(lines
, indentLen
):
230 split
= lines
.split("\n")
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
):
250 self
.isLiteralSpan
= True
251 if self
.span
.startswith("%%") and self
.span
.endswith("%%"):
252 self
.isLiteralSpan
= False
253 self
.span
= self
.span
[2:-2]
257 def Fill(self
, templateDict
, indentLen
):
258 if self
.isLiteralSpan
:
261 assert self
.span
in templateDict
, "'" + self
.span
+ "' not in dict!"
263 filling
= templateDict
[self
.span
]
265 return WrapWithIndent(filling
, indentLen
)
269 def __init__(self
, iterableLines
):
272 for line
in iterableLines
:
273 split
= templateRE
.split(line
)
276 isTemplateSpan
= cur
.startswith("%%") and cur
.endswith("%%")
277 if not isTemplateSpan
:
278 curLiteralSpan
.append(cur
)
282 span
= "".join(curLiteralSpan
)
283 span
= TemplateShellSpan(span
)
284 spanList
.append(span
)
289 span
= TemplateShellSpan(cur
)
290 spanList
.append(span
)
295 span
= "".join(curLiteralSpan
)
296 span
= TemplateShellSpan(span
)
297 spanList
.append(span
)
299 self
.spanList
= spanList
302 # Returns spanStrList.
304 def Fill(self
, templateDict
):
307 for span
in self
.spanList
:
308 span
= span
.Fill(templateDict
, indentLen
)
311 # Get next `indentLen`.
313 lineStartPos
= span
.rindex("\n") + 1
315 # let span = 'foo\nbar'
318 indentLen
= len(span
) - lineStartPos
320 indentLen
+= len(span
)
326 ########################################################################
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
= ""
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
)
350 args
= "?webglVersion=2"
353 "TEST_PATH": entryPath
,
357 OutputFilledTemplate(templateShell
, templateDict
, wrapperPath
)
360 assert IsWrapperWebGL2(wrapperPath
)
362 wrapperPathAccum
.append(wrapperPath
)
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
)
375 for entry
in testEntryList
:
377 WriteWrapper(entry
.path
, False, templateShell
, wrapperPathList
)
379 WriteWrapper(entry
.path
, True, templateShell
, wrapperPathList
)
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()
402 defaultSectionName
= "DEFAULT"
404 defaultSectionLines
= []
405 if defaultSectionName
in errataMap
:
406 defaultSectionLines
= errataMap
[defaultSectionName
]
407 del errataMap
[defaultSectionName
]
409 defaultSectionStr
= "\n".join(defaultSectionLines
)
412 supportPathStrList
= [ManifestPathStr(x
) for x
in supportPathStrList
]
413 supportPathStrList
= sorted(supportPathStrList
)
414 supportFilesStr
= '",\n "'.join(supportPathStrList
)
415 supportFilesStr
= '[\n "' + supportFilesStr
+ '",\n]'
418 manifestTestLineList
= []
419 wrapperPathStrList
= sorted(wrapperPathStrList
)
420 for wrapperPathStr
in wrapperPathStrList
:
421 wrapperManifestPathStr
= ManifestPathStr(wrapperPathStr
)
422 sectionName
= '\n["' + wrapperManifestPathStr
+ '"]'
423 manifestTestLineList
.append(sectionName
)
427 subsuite
= ChooseSubsuite(wrapperPathStr
)
428 errataLines
.append('subsuite = "' + subsuite
+ '"')
430 if wrapperPathStr
in errataMap
:
432 errataLines
+= errataMap
[wrapperPathStr
]
433 del errataMap
[wrapperPathStr
]
435 manifestTestLineList
+= errataLines
439 print("Errata left in map:")
440 for x
in errataMap
.keys():
444 manifestTestsStr
= "\n".join(manifestTestLineList
)
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
)
458 ##############################
463 curSectionName
= None
467 ret
[curSectionName
] = (lineNum
, curSectionMap
)
472 with
open(path
, "r") as f
:
476 val
+= "\n" + rawLine
.rstrip()
477 if rawLine
.find("]") >= 0:
479 curSectionMap
[key
] = (lineNum
, val
)
481 line
= rawLine
.strip()
484 if line
[0] in [";", "#"]:
487 assert line
[-1] == "]", "{}:{}".format(path
, lineNum
)
488 curSectionName
= line
[1:-1].strip('"')
490 curSectionName
not in ret
491 ), "Line {}: Duplicate section: {}".format(lineNum
, line
)
493 ret
[curSectionName
] = (lineNum
, curSectionMap
)
495 split
= line
.split("=", 1)
496 key
= split
[0].strip()
499 val
= split
[1].strip()
500 if val
.find("[") >= 0 and val
.find("]") < 0:
503 curSectionMap
[key
] = (lineNum
, val
)
509 tomlMap
= LoadTOML(ERRATA_FILE
)
513 for sectionName
, (sectionLineNum
, sectionMap
) in tomlMap
.items():
516 if sectionName
is None:
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
)
531 ret
[sectionName
] = curLines
537 ########################################################################
540 def GetSupportFileList():
541 ret
= EXTRA_SUPPORT_FILES
[:]
543 for pathStr
in SUPPORT_DIRS
:
544 ret
+= GetFilePathListForDir(pathStr
)
548 path
= pathStr
.replace("/", os
.sep
)
549 assert os
.path
.exists(path
), path
+ "\n\n\n" + "pathStr: " + str(pathStr
)
555 def GetFilePathListForDir(baseDir
):
557 for root
, folders
, files
in os
.walk(baseDir
):
559 filePath
= os
.path
.join(root
, f
)
560 filePath
= filePath
.replace(os
.sep
, "/")
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
)