tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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!")