|
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