Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
michael@0 | 1 | #!/usr/bin/python |
michael@0 | 2 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 5 | import os |
michael@0 | 6 | from optparse import OptionParser |
michael@0 | 7 | from subprocess import Popen, PIPE |
michael@0 | 8 | import xml.dom.minidom |
michael@0 | 9 | import html5lib |
michael@0 | 10 | import shutil |
michael@0 | 11 | import sys |
michael@0 | 12 | import re |
michael@0 | 13 | |
michael@0 | 14 | # FIXME: |
michael@0 | 15 | # * Import more tests rather than just the very limited set currently |
michael@0 | 16 | # chosen. |
michael@0 | 17 | # * Read in a (checked-in) input file with a list of test assertions |
michael@0 | 18 | # expected to fail. |
michael@0 | 19 | # * Read in a (checked-in) input file with a list of reference choices |
michael@0 | 20 | # for tests with multiple rel="match" references. (But still go |
michael@0 | 21 | # though all those references in case they, in turn, have references.) |
michael@0 | 22 | |
michael@0 | 23 | # Eventually we should import all the tests that have references. (At |
michael@0 | 24 | # least for a subset of secs. And we probably want to organize the |
michael@0 | 25 | # directory structure by spec to avoid constant file moves when files |
michael@0 | 26 | # move in the W3C repository. And we probably also want to import each |
michael@0 | 27 | # test only once, even if it covers more than one spec.) |
michael@0 | 28 | |
michael@0 | 29 | # But for now, let's just import a few sets of tests. |
michael@0 | 30 | |
michael@0 | 31 | gSubtrees = [ |
michael@0 | 32 | os.path.join("approved", "css3-namespace", "src"), |
michael@0 | 33 | #os.path.join("approved", "css3-multicol", "src"), |
michael@0 | 34 | os.path.join("contributors", "opera", "submitted", "css3-conditional"), |
michael@0 | 35 | #os.path.join("contributors", "opera", "submitted", "multicol") |
michael@0 | 36 | ] |
michael@0 | 37 | |
michael@0 | 38 | gPrefixedProperties = [ |
michael@0 | 39 | "column-count", |
michael@0 | 40 | "column-fill", |
michael@0 | 41 | "column-gap", |
michael@0 | 42 | "column-rule", |
michael@0 | 43 | "column-rule-color", |
michael@0 | 44 | "column-rule-style", |
michael@0 | 45 | "column-rule-width", |
michael@0 | 46 | "columns", |
michael@0 | 47 | "column-span", |
michael@0 | 48 | "column-width" |
michael@0 | 49 | ] |
michael@0 | 50 | |
michael@0 | 51 | gDefaultPreferences = { |
michael@0 | 52 | "css3-conditional": "pref(layout.css.supports-rule.enabled,true)" |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | gLog = None |
michael@0 | 56 | gFailList = {} |
michael@0 | 57 | gDestPath = None |
michael@0 | 58 | gSrcPath = None |
michael@0 | 59 | support_dirs_mapped = set() |
michael@0 | 60 | filemap = {} |
michael@0 | 61 | speclinkmap = {} |
michael@0 | 62 | propsaddedfor = [] |
michael@0 | 63 | tests = [] |
michael@0 | 64 | gOptions = None |
michael@0 | 65 | gArgs = None |
michael@0 | 66 | gTestfiles = [] |
michael@0 | 67 | gTestFlags = {} |
michael@0 | 68 | |
michael@0 | 69 | def log_output_of(subprocess): |
michael@0 | 70 | global gLog |
michael@0 | 71 | subprocess.wait() |
michael@0 | 72 | if (subprocess.returncode != 0): |
michael@0 | 73 | raise StandardError("error while running subprocess") |
michael@0 | 74 | gLog.write(subprocess.stdout.readline().rstrip()) |
michael@0 | 75 | |
michael@0 | 76 | def write_log_header(): |
michael@0 | 77 | global gLog, gSrcPath |
michael@0 | 78 | gLog.write("Importing revision: ") |
michael@0 | 79 | log_output_of(Popen(["hg", "parent", "--template={node}"], |
michael@0 | 80 | stdout=PIPE, cwd=gSrcPath)) |
michael@0 | 81 | gLog.write("\nfrom repository: ") |
michael@0 | 82 | log_output_of(Popen(["hg", "paths", "default"], |
michael@0 | 83 | stdout=PIPE, cwd=gSrcPath)) |
michael@0 | 84 | gLog.write("\n") |
michael@0 | 85 | |
michael@0 | 86 | def remove_existing_dirs(): |
michael@0 | 87 | global gDestPath |
michael@0 | 88 | # Remove existing directories that we're going to regenerate. This |
michael@0 | 89 | # is necessary so that we can give errors in cases where our import |
michael@0 | 90 | # might copy two files to the same location, which we do by giving |
michael@0 | 91 | # errors if a copy would overwrite a file. |
michael@0 | 92 | for dirname in os.listdir(gDestPath): |
michael@0 | 93 | fulldir = os.path.join(gDestPath, dirname) |
michael@0 | 94 | if not os.path.isdir(fulldir): |
michael@0 | 95 | continue |
michael@0 | 96 | shutil.rmtree(fulldir) |
michael@0 | 97 | |
michael@0 | 98 | def populate_test_files(): |
michael@0 | 99 | global gSubtrees, gTestfiles |
michael@0 | 100 | for subtree in gSubtrees: |
michael@0 | 101 | for dirpath, dirnames, filenames in os.walk(subtree, topdown=True): |
michael@0 | 102 | if "support" in dirnames: |
michael@0 | 103 | dirnames.remove("support") |
michael@0 | 104 | if "reftest" in dirnames: |
michael@0 | 105 | dirnames.remove("reftest") |
michael@0 | 106 | for f in filenames: |
michael@0 | 107 | if f == "README" or \ |
michael@0 | 108 | f.find("-ref.") != -1: |
michael@0 | 109 | continue |
michael@0 | 110 | gTestfiles.append(os.path.join(dirpath, f)) |
michael@0 | 111 | |
michael@0 | 112 | gTestfiles.sort() |
michael@0 | 113 | |
michael@0 | 114 | def copy_file(test, srcfile, destname, isSupportFile=False): |
michael@0 | 115 | global gDestPath, gLog, gSrcPath |
michael@0 | 116 | if not srcfile.startswith(gSrcPath): |
michael@0 | 117 | raise StandardError("Filename " + srcfile + " does not start with " + gSrcPath) |
michael@0 | 118 | logname = srcfile[len(gSrcPath):] |
michael@0 | 119 | gLog.write("Importing " + logname + " to " + destname + "\n") |
michael@0 | 120 | destfile = os.path.join(gDestPath, destname) |
michael@0 | 121 | destdir = os.path.dirname(destfile) |
michael@0 | 122 | if not os.path.exists(destdir): |
michael@0 | 123 | os.makedirs(destdir) |
michael@0 | 124 | if os.path.exists(destfile): |
michael@0 | 125 | raise StandardError("file " + destfile + " already exists") |
michael@0 | 126 | copy_and_prefix(test, srcfile, destfile, gPrefixedProperties, isSupportFile) |
michael@0 | 127 | |
michael@0 | 128 | def copy_support_files(test, dirname, spec): |
michael@0 | 129 | if dirname in support_dirs_mapped: |
michael@0 | 130 | return |
michael@0 | 131 | support_dirs_mapped.add(dirname) |
michael@0 | 132 | support_dir = os.path.join(dirname, "support") |
michael@0 | 133 | if not os.path.exists(support_dir): |
michael@0 | 134 | return |
michael@0 | 135 | for dirpath, dirnames, filenames in os.walk(support_dir): |
michael@0 | 136 | for fn in filenames: |
michael@0 | 137 | if fn == "LOCK": |
michael@0 | 138 | continue |
michael@0 | 139 | full_fn = os.path.join(dirpath, fn) |
michael@0 | 140 | copy_file(test, full_fn, os.path.join(spec, "support", full_fn[len(support_dir)+1:]), True) |
michael@0 | 141 | |
michael@0 | 142 | def map_file(fn, spec): |
michael@0 | 143 | if fn in filemap: |
michael@0 | 144 | return filemap[fn] |
michael@0 | 145 | destname = os.path.join(spec, os.path.basename(fn)) |
michael@0 | 146 | filemap[fn] = destname |
michael@0 | 147 | load_flags_for(fn, spec) |
michael@0 | 148 | copy_file(destname, fn, destname, False) |
michael@0 | 149 | copy_support_files(destname, os.path.dirname(fn), spec) |
michael@0 | 150 | return destname |
michael@0 | 151 | |
michael@0 | 152 | def load_flags_for(fn, spec): |
michael@0 | 153 | global gTestFlags |
michael@0 | 154 | document = get_document_for(fn, spec) |
michael@0 | 155 | destname = os.path.join(spec, os.path.basename(fn)) |
michael@0 | 156 | gTestFlags[destname] = [] |
michael@0 | 157 | |
michael@0 | 158 | for meta in document.getElementsByTagName("meta"): |
michael@0 | 159 | name = meta.getAttribute("name") |
michael@0 | 160 | if name == "flags": |
michael@0 | 161 | gTestFlags[destname] = meta.getAttribute("content").split() |
michael@0 | 162 | |
michael@0 | 163 | def get_document_for(fn, spec): |
michael@0 | 164 | document = None # an xml.dom.minidom document |
michael@0 | 165 | if fn.endswith(".htm") or fn.endswith(".html"): |
michael@0 | 166 | # An HTML file |
michael@0 | 167 | f = open(fn, "r") |
michael@0 | 168 | parser = html5lib.HTMLParser(tree=html5lib.treebuilders.getTreeBuilder("dom")) |
michael@0 | 169 | document = parser.parse(f) |
michael@0 | 170 | f.close() |
michael@0 | 171 | else: |
michael@0 | 172 | # An XML file |
michael@0 | 173 | document = xml.dom.minidom.parse(fn) |
michael@0 | 174 | return document |
michael@0 | 175 | |
michael@0 | 176 | def add_test_items(fn, spec): |
michael@0 | 177 | document = get_document_for(fn, spec) |
michael@0 | 178 | refs = [] |
michael@0 | 179 | notrefs = [] |
michael@0 | 180 | for link in document.getElementsByTagName("link"): |
michael@0 | 181 | rel = link.getAttribute("rel") |
michael@0 | 182 | if rel == "help" and spec == None: |
michael@0 | 183 | specurl = link.getAttribute("href") |
michael@0 | 184 | startidx = specurl.find("/TR/") |
michael@0 | 185 | if startidx != -1: |
michael@0 | 186 | startidx = startidx + 4 |
michael@0 | 187 | endidx = specurl.find("/", startidx) |
michael@0 | 188 | if endidx != -1: |
michael@0 | 189 | spec = str(specurl[startidx:endidx]) |
michael@0 | 190 | if rel == "match": |
michael@0 | 191 | arr = refs |
michael@0 | 192 | elif rel == "mismatch": |
michael@0 | 193 | arr = notrefs |
michael@0 | 194 | else: |
michael@0 | 195 | continue |
michael@0 | 196 | arr.append(os.path.join(os.path.dirname(fn), str(link.getAttribute("href")))) |
michael@0 | 197 | if len(refs) > 1: |
michael@0 | 198 | raise StandardError("Need to add code to specify which reference we want to match.") |
michael@0 | 199 | if spec is None: |
michael@0 | 200 | raise StandardError("Could not associate test with specification") |
michael@0 | 201 | for ref in refs: |
michael@0 | 202 | tests.append(["==", map_file(fn, spec), map_file(ref, spec)]) |
michael@0 | 203 | for notref in notrefs: |
michael@0 | 204 | tests.append(["!=", map_file(fn, spec), map_file(notref, spec)]) |
michael@0 | 205 | # Add chained references too |
michael@0 | 206 | for ref in refs: |
michael@0 | 207 | add_test_items(ref, spec=spec) |
michael@0 | 208 | for notref in notrefs: |
michael@0 | 209 | add_test_items(notref, spec=spec) |
michael@0 | 210 | |
michael@0 | 211 | def copy_and_prefix(test, aSourceFileName, aDestFileName, aProps, isSupportFile=False): |
michael@0 | 212 | global gTestFlags |
michael@0 | 213 | newFile = open(aDestFileName, 'w') |
michael@0 | 214 | unPrefixedFile = open(aSourceFileName) |
michael@0 | 215 | testName = aDestFileName[len(gDestPath)+1:] |
michael@0 | 216 | ahemFontAdded = False |
michael@0 | 217 | for line in unPrefixedFile: |
michael@0 | 218 | replacementLine = line |
michael@0 | 219 | searchRegex = "\s*<style\s*" |
michael@0 | 220 | |
michael@0 | 221 | if not isSupportFile and not ahemFontAdded and 'ahem' in gTestFlags[test] and re.search(searchRegex, line): |
michael@0 | 222 | # First put our ahem font declation before the first <style> |
michael@0 | 223 | # element |
michael@0 | 224 | ahemFontDecl = "<style type=\"text/css\"><![CDATA[\n@font-face "\ |
michael@0 | 225 | "{\n font-family: Ahem;\n src: url("\ |
michael@0 | 226 | "\"../../../fonts/Ahem.ttf\");\n}\n]]></style>\n" |
michael@0 | 227 | newFile.write(ahemFontDecl) |
michael@0 | 228 | ahemFontAdded = True |
michael@0 | 229 | |
michael@0 | 230 | for rule in aProps: |
michael@0 | 231 | replacementLine = replacementLine.replace(rule, "-moz-" + rule) |
michael@0 | 232 | newFile.write(replacementLine) |
michael@0 | 233 | |
michael@0 | 234 | newFile.close() |
michael@0 | 235 | unPrefixedFile.close() |
michael@0 | 236 | |
michael@0 | 237 | def read_options(): |
michael@0 | 238 | global gArgs, gOptions |
michael@0 | 239 | op = OptionParser() |
michael@0 | 240 | op.usage = \ |
michael@0 | 241 | '''%prog <clone of hg repository> |
michael@0 | 242 | Import reftests from a W3C hg repository clone. The location of |
michael@0 | 243 | the local clone of the hg repository must be given on the command |
michael@0 | 244 | line.''' |
michael@0 | 245 | (gOptions, gArgs) = op.parse_args() |
michael@0 | 246 | if len(gArgs) != 1: |
michael@0 | 247 | op.error("Too few arguments specified.") |
michael@0 | 248 | |
michael@0 | 249 | def setup_paths(): |
michael@0 | 250 | global gSubtrees, gDestPath, gSrcPath |
michael@0 | 251 | # FIXME: generate gSrcPath with consistent trailing / regardless of input. |
michael@0 | 252 | # (We currently expect the argument to have a trailing slash.) |
michael@0 | 253 | gSrcPath = gArgs[0] |
michael@0 | 254 | if not os.path.isdir(gSrcPath) or \ |
michael@0 | 255 | not os.path.isdir(os.path.join(gSrcPath, ".hg")): |
michael@0 | 256 | raise StandardError("source path does not appear to be a mercurial clone") |
michael@0 | 257 | |
michael@0 | 258 | gDestPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "received") |
michael@0 | 259 | newSubtrees = [] |
michael@0 | 260 | for relPath in gSubtrees: |
michael@0 | 261 | newSubtrees[len(gSubtrees):] = [os.path.join(gSrcPath, relPath)] |
michael@0 | 262 | gSubtrees = newSubtrees |
michael@0 | 263 | |
michael@0 | 264 | def setup_log(): |
michael@0 | 265 | global gLog |
michael@0 | 266 | # Since we're going to commit the tests, we should also commit |
michael@0 | 267 | # information about where they came from. |
michael@0 | 268 | gLog = open(os.path.join(gDestPath, "import.log"), "w") |
michael@0 | 269 | |
michael@0 | 270 | def read_fail_list(): |
michael@0 | 271 | global gFailList |
michael@0 | 272 | dirname = os.path.realpath(__file__).split(os.path.sep) |
michael@0 | 273 | dirname = os.path.sep.join(dirname[:len(dirname)-1]) |
michael@0 | 274 | failListFile = open(os.path.join(dirname, "failures.list"), "r") |
michael@0 | 275 | gFailList = [x for x in [x.lstrip().rstrip() for x in failListFile] if bool(x) |
michael@0 | 276 | and not x.startswith("#")] |
michael@0 | 277 | failListFile.close() |
michael@0 | 278 | |
michael@0 | 279 | def main(): |
michael@0 | 280 | global gDestPath, gLog, gTestfiles, gTestFlags, gFailList |
michael@0 | 281 | read_options() |
michael@0 | 282 | setup_paths() |
michael@0 | 283 | read_fail_list() |
michael@0 | 284 | setup_log() |
michael@0 | 285 | write_log_header() |
michael@0 | 286 | remove_existing_dirs() |
michael@0 | 287 | populate_test_files() |
michael@0 | 288 | |
michael@0 | 289 | for t in gTestfiles: |
michael@0 | 290 | add_test_items(t, spec=None) |
michael@0 | 291 | |
michael@0 | 292 | listfile = open(os.path.join(gDestPath, "reftest.list"), "w") |
michael@0 | 293 | listfile.write("# THIS FILE IS AUTOGENERATED BY {0}\n# DO NOT EDIT!\n".format(os.path.basename(__file__))) |
michael@0 | 294 | lastDefaultPreferences = None |
michael@0 | 295 | for test in tests: |
michael@0 | 296 | defaultPreferences = gDefaultPreferences.get(test[1].split("/")[0], None) |
michael@0 | 297 | if defaultPreferences != lastDefaultPreferences: |
michael@0 | 298 | if defaultPreferences is None: |
michael@0 | 299 | listfile.write("\ndefault-preferences\n\n") |
michael@0 | 300 | else: |
michael@0 | 301 | listfile.write("\ndefault-preferences {0}\n\n".format(defaultPreferences)) |
michael@0 | 302 | lastDefaultPreferences = defaultPreferences |
michael@0 | 303 | key = 0 |
michael@0 | 304 | while not test[key] in gTestFlags.keys() and key < len(test): |
michael@0 | 305 | key = key + 1 |
michael@0 | 306 | testKey = test[key] |
michael@0 | 307 | if 'ahem' in gTestFlags[testKey]: |
michael@0 | 308 | test = ["HTTP(../../..)"] + test |
michael@0 | 309 | if testKey in gFailList: |
michael@0 | 310 | test = ["fails"] + test |
michael@0 | 311 | listfile.write(" ".join(test) + "\n") |
michael@0 | 312 | listfile.close() |
michael@0 | 313 | |
michael@0 | 314 | gLog.close() |
michael@0 | 315 | |
michael@0 | 316 | if __name__ == '__main__': |
michael@0 | 317 | main() |