Skip to content

Commit e4017f0

Browse files
authored
Use regular expressions to parse the YAML headers of the tests (#4910)
instead of the imported thirdparty YAML parser. JerryScript-DCO-1.0-Signed-off-by: Csaba Osztrogonác [email protected]
1 parent 95cc5e9 commit e4017f0

File tree

1 file changed

+35
-289
lines changed

1 file changed

+35
-289
lines changed

tools/runners/test262-harness.py

Lines changed: 35 additions & 289 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,14 +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-
4233

4334
import logging
4435
import argparse
@@ -55,275 +46,14 @@
5546
import signal
5647
import multiprocessing
5748

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-
6549

6650
# The timeout of each test case
6751
TEST262_CASE_TIMEOUT = 180
6852

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

32858
class Test262Error(Exception):
32959
def __init__(self, message):
@@ -490,19 +220,35 @@ def __init__(self, suite, name, full_path, strict_mode, command_template, module
490220
self.name = name
491221
self.full_path = full_path
492222
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
501223
self.command_template = command_template
502224
self.module_flag = module_flag
503-
225+
self.test_record = {}
226+
self.parse_test_record()
504227
self.validate()
505228

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+
506252
def negative_match(self, stderr):
507253
neg = re.compile(self.get_negative_type())
508254
return re.search(neg, stderr)
@@ -537,19 +283,19 @@ def is_negative(self):
537283
return 'negative' in self.test_record
538284

539285
def is_only_strict(self):
540-
return 'onlyStrict' in self.test_record
286+
return 'onlyStrict' in self.test_record["flags"]
541287

542288
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()
544290

545291
def is_raw(self):
546-
return 'raw' in self.test_record
292+
return 'raw' in self.test_record["flags"]
547293

548294
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
550296

551297
def is_module(self):
552-
return 'module' in self.test_record
298+
return 'module' in self.test_record["flags"]
553299

554300
def get_include_list(self):
555301
if self.test_record.get('includes'):

0 commit comments

Comments
 (0)