|
16 | 16 |
|
17 | 17 |
|
18 | 18 | # This file is based on work under the following copyright and permission notice: |
19 | | -# https://github.com/test262-utils/test262-harness-py |
20 | | -# test262.py, _monkeyYaml.py, parseTestRecord.py |
| 19 | +# https://github.com/test262-utils/test262-harness-py/blob/master/src/test262.py |
21 | 20 |
|
22 | 21 | # license of test262.py: |
23 | 22 | # Copyright 2009 the Sputnik authors. All rights reserved. |
|
31 | 30 | # Copyright (c) 2012 Ecma International. All rights reserved. |
32 | 31 | # This code is governed by the BSD license found in the LICENSE file. |
33 | 32 |
|
34 | | -# license of _monkeyYaml.py: |
35 | | -# Copyright 2014 by Sam Mikes. All rights reserved. |
36 | | -# This code is governed by the BSD license found in the LICENSE file. |
37 | | - |
38 | | -# license of parseTestRecord.py: |
39 | | -# Copyright 2011 by Google, Inc. All rights reserved. |
40 | | -# This code is governed by the BSD license found in the LICENSE file. |
41 | | - |
42 | 33 |
|
43 | 34 | import logging |
44 | 35 | import argparse |
|
55 | 46 | import signal |
56 | 47 | import multiprocessing |
57 | 48 |
|
58 | | -####################################################################### |
59 | | -# based on _monkeyYaml.py |
60 | | -####################################################################### |
61 | | - |
62 | | -M_YAML_LIST_PATTERN = re.compile(r"^\[(.*)\]$") |
63 | | -M_YAML_MULTILINE_LIST = re.compile(r"^ *- (.*)$") |
64 | | - |
65 | 49 |
|
66 | 50 | # The timeout of each test case |
67 | 51 | TEST262_CASE_TIMEOUT = 180 |
68 | 52 |
|
69 | | - |
70 | | -def yaml_load(string): |
71 | | - return my_read_dict(string.splitlines())[1] |
72 | | - |
73 | | - |
74 | | -def my_read_dict(lines, indent=""): |
75 | | - dictionary = {} |
76 | | - key = None |
77 | | - empty_lines = 0 |
78 | | - |
79 | | - while lines: |
80 | | - if not lines[0].startswith(indent): |
81 | | - break |
82 | | - |
83 | | - line = lines.pop(0) |
84 | | - if my_is_all_spaces(line): |
85 | | - empty_lines += 1 |
86 | | - continue |
87 | | - |
88 | | - result = re.match(r"(.*?):(.*)", line) |
89 | | - |
90 | | - if result: |
91 | | - if not dictionary: |
92 | | - dictionary = {} |
93 | | - key = result.group(1).strip() |
94 | | - value = result.group(2).strip() |
95 | | - (lines, value) = my_read_value(lines, value, indent) |
96 | | - dictionary[key] = value |
97 | | - else: |
98 | | - if dictionary and key and key in dictionary: |
99 | | - char = " " if empty_lines == 0 else "\n" * empty_lines |
100 | | - dictionary[key] += char + line.strip() |
101 | | - else: |
102 | | - raise Exception("monkeyYaml is confused at " + line) |
103 | | - empty_lines = 0 |
104 | | - |
105 | | - if not dictionary: |
106 | | - dictionary = None |
107 | | - |
108 | | - return lines, dictionary |
109 | | - |
110 | | - |
111 | | -def my_read_value(lines, value, indent): |
112 | | - if value in (">", "|"): |
113 | | - (lines, value) = my_multiline(lines, value == "|") |
114 | | - value = value + "\n" |
115 | | - return (lines, value) |
116 | | - if lines and not value: |
117 | | - if my_maybe_list(lines[0]): |
118 | | - return my_multiline_list(lines, value) |
119 | | - indent_match = re.match("(" + indent + r"\s+)", lines[0]) |
120 | | - if indent_match: |
121 | | - if ":" in lines[0]: |
122 | | - return my_read_dict(lines, indent_match.group(1)) |
123 | | - return my_multiline(lines, False) |
124 | | - return lines, my_read_one_line(value) |
125 | | - |
126 | | - |
127 | | -def my_maybe_list(value): |
128 | | - return M_YAML_MULTILINE_LIST.match(value) |
129 | | - |
130 | | - |
131 | | -def my_multiline_list(lines, value): |
132 | | - # assume no explcit indentor (otherwise have to parse value) |
133 | | - value = [] |
134 | | - indent = 0 |
135 | | - while lines: |
136 | | - line = lines.pop(0) |
137 | | - leading = my_leading_spaces(line) |
138 | | - if my_is_all_spaces(line): |
139 | | - pass |
140 | | - elif leading < indent: |
141 | | - lines.insert(0, line) |
142 | | - break |
143 | | - else: |
144 | | - indent = indent or leading |
145 | | - value += [my_read_one_line(my_remove_list_header(indent, line))] |
146 | | - return (lines, value) |
147 | | - |
148 | | - |
149 | | -def my_remove_list_header(indent, line): |
150 | | - line = line[indent:] |
151 | | - return M_YAML_MULTILINE_LIST.match(line).group(1) |
152 | | - |
153 | | - |
154 | | -def my_read_one_line(value): |
155 | | - if M_YAML_LIST_PATTERN.match(value): |
156 | | - return my_flow_list(value) |
157 | | - if re.match(r"^[-0-9]*$", value): |
158 | | - try: |
159 | | - value = int(value) |
160 | | - except ValueError: |
161 | | - pass |
162 | | - elif re.match(r"^[-.0-9eE]*$", value): |
163 | | - try: |
164 | | - value = float(value) |
165 | | - except ValueError: |
166 | | - pass |
167 | | - elif re.match(r"^('|\").*\1$", value): |
168 | | - value = value[1:-1] |
169 | | - return value |
170 | | - |
171 | | - |
172 | | -def my_flow_list(value): |
173 | | - result = M_YAML_LIST_PATTERN.match(value) |
174 | | - values = result.group(1).split(",") |
175 | | - return [my_read_one_line(v.strip()) for v in values] |
176 | | - |
177 | | - |
178 | | -def my_multiline(lines, preserve_newlines=False): |
179 | | - # assume no explcit indentor (otherwise have to parse value) |
180 | | - value = "" |
181 | | - indent = my_leading_spaces(lines[0]) |
182 | | - was_empty = None |
183 | | - |
184 | | - while lines: |
185 | | - line = lines.pop(0) |
186 | | - is_empty = my_is_all_spaces(line) |
187 | | - |
188 | | - if is_empty: |
189 | | - if preserve_newlines: |
190 | | - value += "\n" |
191 | | - elif my_leading_spaces(line) < indent: |
192 | | - lines.insert(0, line) |
193 | | - break |
194 | | - else: |
195 | | - if preserve_newlines: |
196 | | - if was_empty is not None: |
197 | | - value += "\n" |
198 | | - else: |
199 | | - if was_empty: |
200 | | - value += "\n" |
201 | | - elif was_empty is False: |
202 | | - value += " " |
203 | | - value += line[(indent):] |
204 | | - |
205 | | - was_empty = is_empty |
206 | | - |
207 | | - return (lines, value) |
208 | | - |
209 | | - |
210 | | -def my_is_all_spaces(line): |
211 | | - return len(line.strip()) == 0 |
212 | | - |
213 | | - |
214 | | -def my_leading_spaces(line): |
215 | | - return len(line) - len(line.lstrip(' ')) |
216 | | - |
217 | | - |
218 | | -####################################################################### |
219 | | -# based on parseTestRecord.py |
220 | | -####################################################################### |
221 | | - |
222 | | -# Matches trailing whitespace and any following blank lines. |
223 | | -_BLANK_LINES = r"([ \t]*[\r\n]{1,2})*" |
224 | | - |
225 | | -# Matches the YAML frontmatter block. |
226 | | -# It must be non-greedy because test262-es2015/built-ins/Object/assign/Override.js contains a comment like yaml pattern |
227 | | -_YAML_PATTERN = re.compile(r"/\*---(.*?)---\*/" + _BLANK_LINES, re.DOTALL) |
228 | | - |
229 | | -# Matches all known variants for the license block. |
230 | | -# https://github.com/tc39/test262/blob/705d78299cf786c84fa4df473eff98374de7135a/tools/lint/lib/checks/license.py |
231 | | -_LICENSE_PATTERN = re.compile( |
232 | | - r'// Copyright( \([C]\))? (\w+) .+\. {1,2}All rights reserved\.[\r\n]{1,2}' + |
233 | | - r'(' + |
234 | | - r'// This code is governed by the( BSD)? license found in the LICENSE file\.' + |
235 | | - r'|' + |
236 | | - r'// See LICENSE for details.' + |
237 | | - r'|' + |
238 | | - r'// Use of this source code is governed by a BSD-style license that can be[\r\n]{1,2}' + |
239 | | - r'// found in the LICENSE file\.' + |
240 | | - r'|' + |
241 | | - r'// See LICENSE or https://github\.com/tc39/test262/blob/master/LICENSE' + |
242 | | - r')' + _BLANK_LINES, re.IGNORECASE) |
243 | | - |
244 | | - |
245 | | -def yaml_attr_parser(test_record, attrs, name, onerror=print): |
246 | | - parsed = yaml_load(attrs) |
247 | | - if parsed is None: |
248 | | - onerror(f"Failed to parse yaml in name {name}") |
249 | | - return |
250 | | - |
251 | | - for key in parsed: |
252 | | - value = parsed[key] |
253 | | - if key == "info": |
254 | | - key = "commentary" |
255 | | - test_record[key] = value |
256 | | - |
257 | | - if 'flags' in test_record: |
258 | | - for flag in test_record['flags']: |
259 | | - test_record[flag] = "" |
260 | | - |
261 | | - |
262 | | -def find_license(src): |
263 | | - match = _LICENSE_PATTERN.search(src) |
264 | | - if not match: |
265 | | - return None |
266 | | - |
267 | | - return match.group(0) |
268 | | - |
269 | | - |
270 | | -def find_attrs(src): |
271 | | - match = _YAML_PATTERN.search(src) |
272 | | - if not match: |
273 | | - return (None, None) |
274 | | - |
275 | | - return (match.group(0), match.group(1).strip()) |
276 | | - |
277 | | - |
278 | | -def parse_test_record(src, name, onerror=print): |
279 | | - # Find the license block. |
280 | | - header = find_license(src) |
281 | | - |
282 | | - # Find the YAML frontmatter. |
283 | | - (frontmatter, attrs) = find_attrs(src) |
284 | | - |
285 | | - # YAML frontmatter is required for all tests. |
286 | | - if frontmatter is None: |
287 | | - onerror(f"Missing frontmatter: {name}") |
288 | | - |
289 | | - # The license shuold be placed before the frontmatter and there shouldn't be |
290 | | - # any extra content between the license and the frontmatter. |
291 | | - if header is not None and frontmatter is not None: |
292 | | - header_idx = src.index(header) |
293 | | - frontmatter_idx = src.index(frontmatter) |
294 | | - if header_idx > frontmatter_idx: |
295 | | - onerror(f"Unexpected license after frontmatter: {name}") |
296 | | - |
297 | | - # Search for any extra test content, but ignore whitespace only or comment lines. |
298 | | - extra = src[header_idx + len(header): frontmatter_idx] |
299 | | - if extra and any(line.strip() and not line.lstrip().startswith("//") for line in extra.split("\n")): |
300 | | - onerror( |
301 | | - f"Unexpected test content between license and frontmatter: {name}") |
302 | | - |
303 | | - # Remove the license and YAML parts from the actual test content. |
304 | | - test = src |
305 | | - if frontmatter is not None: |
306 | | - test = test.replace(frontmatter, '') |
307 | | - if header is not None: |
308 | | - test = test.replace(header, '') |
309 | | - |
310 | | - test_record = {} |
311 | | - test_record['header'] = header.strip() if header else '' |
312 | | - test_record['test'] = test |
313 | | - |
314 | | - if attrs: |
315 | | - yaml_attr_parser(test_record, attrs, name, onerror) |
316 | | - |
317 | | - # Report if the license block is missing in non-generated tests. |
318 | | - if header is None and "generated" not in test_record and "hashbang" not in name: |
319 | | - onerror(f"No license found in: {name}") |
320 | | - |
321 | | - return test_record |
322 | | - |
323 | | - |
324 | | -####################################################################### |
325 | | -# based on test262.py |
326 | | -####################################################################### |
| 53 | +TEST_RE = re.compile(r"(?P<test1>.*)\/\*---(?P<header>.+)---\*\/(?P<test2>.*)", re.DOTALL) |
| 54 | +YAML_INCLUDES_RE = re.compile(r"includes:\s+\[(?P<includes>.+)\]") |
| 55 | +YAML_FLAGS_RE = re.compile(r"flags:\s+\[(?P<flags>.+)\]") |
| 56 | +YAML_NEGATIVE_RE = re.compile(r"negative:.*phase:\s+(?P<phase>\w+).*type:\s+(?P<type>\w+)", re.DOTALL) |
327 | 57 |
|
328 | 58 | class Test262Error(Exception): |
329 | 59 | def __init__(self, message): |
@@ -490,19 +220,35 @@ def __init__(self, suite, name, full_path, strict_mode, command_template, module |
490 | 220 | self.name = name |
491 | 221 | self.full_path = full_path |
492 | 222 | self.strict_mode = strict_mode |
493 | | - with open(self.full_path, "r", newline='', encoding='utf8') as file_desc: |
494 | | - self.contents = file_desc.read() |
495 | | - test_record = parse_test_record(self.contents, name) |
496 | | - self.test = test_record["test"] |
497 | | - del test_record["test"] |
498 | | - del test_record["header"] |
499 | | - test_record.pop("commentary", None) # do not throw if missing |
500 | | - self.test_record = test_record |
501 | 223 | self.command_template = command_template |
502 | 224 | self.module_flag = module_flag |
503 | | - |
| 225 | + self.test_record = {} |
| 226 | + self.parse_test_record() |
504 | 227 | self.validate() |
505 | 228 |
|
| 229 | + def parse_test_record(self): |
| 230 | + with open(self.full_path, "r", newline='', encoding='utf8') as file_desc: |
| 231 | + full_test = file_desc.read() |
| 232 | + |
| 233 | + match = TEST_RE.search(full_test) |
| 234 | + header = match.group("header") |
| 235 | + self.test = match.group("test1") + match.group("test2") |
| 236 | + |
| 237 | + match = YAML_INCLUDES_RE.search(header) |
| 238 | + |
| 239 | + if match: |
| 240 | + self.test_record["includes"] = [inc.strip() for inc in match.group("includes").split(",") if inc] |
| 241 | + |
| 242 | + match = YAML_FLAGS_RE.search(header) |
| 243 | + self.test_record["flags"] = [flag.strip() for flag in match.group("flags").split(",") if flag] if match else [] |
| 244 | + |
| 245 | + match = YAML_NEGATIVE_RE.search(header) |
| 246 | + if match: |
| 247 | + self.test_record["negative"] = { |
| 248 | + "phase" : match.group("phase"), |
| 249 | + "type" : match.group("type") |
| 250 | + } |
| 251 | + |
506 | 252 | def negative_match(self, stderr): |
507 | 253 | neg = re.compile(self.get_negative_type()) |
508 | 254 | return re.search(neg, stderr) |
@@ -537,19 +283,19 @@ def is_negative(self): |
537 | 283 | return 'negative' in self.test_record |
538 | 284 |
|
539 | 285 | def is_only_strict(self): |
540 | | - return 'onlyStrict' in self.test_record |
| 286 | + return 'onlyStrict' in self.test_record["flags"] |
541 | 287 |
|
542 | 288 | def is_no_strict(self): |
543 | | - return 'noStrict' in self.test_record or self.is_raw() |
| 289 | + return 'noStrict' in self.test_record["flags"] or self.is_raw() |
544 | 290 |
|
545 | 291 | def is_raw(self): |
546 | | - return 'raw' in self.test_record |
| 292 | + return 'raw' in self.test_record["flags"] |
547 | 293 |
|
548 | 294 | def is_async_test(self): |
549 | | - return 'async' in self.test_record or '$DONE' in self.test |
| 295 | + return 'async' in self.test_record["flags"] or '$DONE' in self.test |
550 | 296 |
|
551 | 297 | def is_module(self): |
552 | | - return 'module' in self.test_record |
| 298 | + return 'module' in self.test_record["flags"] |
553 | 299 |
|
554 | 300 | def get_include_list(self): |
555 | 301 | if self.test_record.get('includes'): |
|
0 commit comments