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