Skip to content

Commit e44c600

Browse files
committed
Simplify test262-harness.py
Use regular expressions to parse the YAML headers of the tests instead of the imported thirdparty YAML parser. JerryScript-DCO-1.0-Signed-off-by: Csaba Osztrogonác csaba.osztrogonac@h-lab.eu
1 parent 675337e commit e44c600

File tree

1 file changed

+34
-291
lines changed

1 file changed

+34
-291
lines changed

tools/runners/test262-harness.py

Lines changed: 34 additions & 291 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616

1717

1818
# 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
2120

2221
# license of test262.py:
2322
# Copyright 2009 the Sputnik authors. All rights reserved.
@@ -31,15 +30,6 @@
3130
# Copyright (c) 2012 Ecma International. All rights reserved.
3231
# This code is governed by the BSD license found in the LICENSE file.
3332

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-
4333
from __future__ import print_function
4434

4535
import logging
@@ -58,275 +48,13 @@
5848
import threading
5949
import multiprocessing
6050

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-
6951
# The timeout of each test case
7052
TEST262_CASE_TIMEOUT = 180
7153

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)
33058

33159
class Test262Error(Exception):
33260
def __init__(self, message):
@@ -495,19 +223,34 @@ def __init__(self, suite, name, full_path, strict_mode, command_template, module
495223
self.name = name
496224
self.full_path = full_path
497225
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
506226
self.command_template = command_template
507227
self.module_flag = module_flag
508-
228+
self.test_record = {}
229+
self.parse_test_record()
509230
self.validate()
510231

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+
511254
def negative_match(self, stderr):
512255
neg = re.compile(self.get_negative_type())
513256
return re.search(neg, stderr)
@@ -542,19 +285,19 @@ def is_negative(self):
542285
return 'negative' in self.test_record
543286

544287
def is_only_strict(self):
545-
return 'onlyStrict' in self.test_record
288+
return 'onlyStrict' in self.test_record["flags"]
546289

547290
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()
549292

550293
def is_raw(self):
551-
return 'raw' in self.test_record
294+
return 'raw' in self.test_record["flags"]
552295

553296
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
555298

556299
def is_module(self):
557-
return 'module' in self.test_record
300+
return 'module' in self.test_record["flags"]
558301

559302
def get_include_list(self):
560303
if self.test_record.get('includes'):

0 commit comments

Comments
 (0)