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