Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revive Test262 integration #35621

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c14ab14
Script that creates web-platform-test wrappers for test262's tests
dpino Dec 19, 2017
323d4ca
Remove tabs
dpino Jan 11, 2018
2b0fee3
Remove unneeded meta tags
dpino Jan 11, 2018
459584b
Include test262-harness at global level
dpino Jan 11, 2018
d1c2076
Parametrized host and port
dpino Jan 11, 2018
a9400b4
Blacklist harness and copy _FIXTURE.js files
dpino Jan 11, 2018
6a749ca
Use Python's YAML and JSON libraries
dpino Jan 12, 2018
8ba46ad
Simplify SyntaxError handling
dpino Jan 12, 2018
a41cc55
Refactor test262_as_html
dpino Jan 15, 2018
788e7ac
Include test262-harness.js
dpino Jan 15, 2018
2947abe
Redefine and avoid calling installAPI if test is a Worker
dpino Jan 15, 2018
bd744ce
Return global.62
dpino Jan 15, 2018
47b9621
Remove trailing whitespaces
dpino Jan 16, 2018
5f968b8
Remove custom tojson function
dpino Jan 16, 2018
add7ec1
Replace carriage-return
dpino Jan 16, 2018
be13eae
ES Shell is not supported in a Worker context
dpino Jan 18, 2018
5a18c61
Return MIME type 'module' if flags includes 'module'
dpino Jan 18, 2018
93b7423
Implement IsHTMLDDA
dpino Jan 18, 2018
e452070
Tests marked with 'raw' flag are only executed in non-strict mode
dpino Jan 19, 2018
c35b20e
Properly handling of async tests
dpino Jan 19, 2018
552f3c6
Properly handling of negative tests
dpino Jan 19, 2018
feed957
Reintroduce handling of early errors
dpino Jan 21, 2018
daedd45
Caught early tests on SyntaxError too
dpino Jan 21, 2018
d91d047
IsHTMLDDA should return null if argument is null or empty string
dpino Jan 25, 2018
586a081
global should be the global object
dpino Jan 27, 2018
7ccaa23
Generate tests for any path
dpino Jan 24, 2018
06506f4
Improve module tests results
dpino Jan 23, 2018
cd1f125
Fix run_in_eval
dpino Jan 25, 2018
6c5bff0
Merge commit 'refs/pull/8980/head' of github.com:web-platform-tests/w…
foolip Aug 25, 2022
76f89c6
Run 2to3
foolip Aug 25, 2022
d3d09d5
Integrate with ./wpt update-built
foolip Aug 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
374 changes: 374 additions & 0 deletions js/tools/test262.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
#!/usr/bin/env python

import os
import re
import string
import subprocess
import sys
import tempfile
import threading
import yaml
import json

from shutil import copyfile
from stat import *

here = os.path.dirname(__file__)

'''
This tool reads out test262 suite and generates wrappers for web-platform-tests.
Each WPT wrapper runs the target test262 test within 4 agents: IFrame, Window,
DedicatedWorker and SharedWorker. Each tests is run either in strict and
non-strict mode, unless the target test indicates otherwise.
'''

TAB_WIDTH = 2
DEST_PATH = os.path.join(here, '..', 'test262')

def trim(text):
lines = text.split("\n")
return "\n".join(lines[1:len(lines)-1])

usage_text = trim('''
Usage: test262-to-wpt [test262-dir]
''')

def usage():
print(usage_text)
exit(1)

'''
Parses a Test262 test.

A test262 tests are usually composed by two parts: a comment header, that
describes several properties of the test, and a body, which defines the proper
test itself.

The test header is defined in YAML and contains properties such as esid (test ID),
description, features, flags, etc. Some of these properties are relevant to know
what harnessing libraries to import from the test262 suite (features), whether
the test should only be tested in strict mode or non-strict mode (flags), etc

Some of the beforementioned properties would need to get serialized to the
resulting web-platform-test as a JSON object.

The parser also fetches the body of the test.
'''
# Parses a Test262 test.
class Test262Parser(object):

def __init__(self, text):
match = re.search('---\*/', text)
if match:
self.header = text[:match.end(0)]
self.body = text[match.end(0)+1:]
else:
self.header = ""
self.body = text
self.attrs = self.parse_header()

def parse_header(self):
if len(self.header) == 0:
return {}
return yaml.safe_load(self.yaml_section()) or {}

def yaml_section(self):
is_yaml = False
ret = []
for line in self.header.split('\n'):
if re.search(r'/*---', line):
is_yaml = True
elif is_yaml and re.search(r'---*/', line):
break
elif is_yaml:
ret.append(line)
return "\n".join(ret)

###

# TODO: Put everything into one single template?
HEADER = trim('''
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>###TITLE###</title>

<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/test262-agent-harness.js?pipe=sub"></script>
<script src="/resources/test262-harness.js"></script>

</head>
<body>
</body>
<script type="text/javascript">
###HEADER###
###ATTRS###
function test262() {
''')
FOOTER = trim('''
}
window.addEventListener('load', function(e) {
###TESTS###
});
</script>
</html>
''')

ASYNC_TEST = trim('''
async_test(function(t) {
###TEST_CALL###(test262, attrs, t);
}, '###TITLE###');
''')

def run_in_iframe(title):
output = ASYNC_TEST
output = re.sub('###TITLE###', 'IFrame: ' + title, output)
output = re.sub('###TEST_CALL###', 'run_in_iframe', output)
return output

def run_in_iframe_strict(title):
output = ASYNC_TEST
output = re.sub('###TITLE###', 'IFrame (strict): ' + title, output)
output = re.sub('###TEST_CALL###', 'run_in_iframe_strict', output)
return output

def run_in_window(title):
output = ASYNC_TEST
output = re.sub('###TITLE###', 'Window: ' + title, output)
output = re.sub('###TEST_CALL###', 'run_in_window', output)
return output

def run_in_window_strict(title):
output = ASYNC_TEST
output = re.sub('###TITLE###', 'Window (strict): ' + title, output)
output = re.sub('###TEST_CALL###', 'run_in_window_strict', output)
return output

def run_in_worker(title):
output = ASYNC_TEST
output = re.sub('###TITLE###', 'Worker: ' + title, output)
output = re.sub('###TEST_CALL###', 'run_in_worker', output)
return output

def run_in_worker_strict(title):
output = ASYNC_TEST
output = re.sub('###TITLE###', 'Worker (strict): ' + title, output)
output = re.sub('###TEST_CALL###', 'run_in_worker_strict', output)
return output

def run_in_shared_worker(title):
output = ASYNC_TEST
output = re.sub('###TITLE###', 'SharedWorker: ' + title, output)
output = re.sub('###TEST_CALL###', 'run_in_shared_worker', output)
return output

def run_in_shared_worker_strict(title):
output = ASYNC_TEST
output = re.sub('###TITLE###', 'SharedWorker (strict): ' + title, output)
output = re.sub('###TEST_CALL###', 'run_in_shared_worker_strict', output)
return output

# Indents content n tabs. A tab is 2 spaces (TAB_WIDTH).
def indent(num_tabs, content):
# Duplicates char n times.
def dup(char, times):
ret = []
for i in range(0, times):
ret.append(char)
return "".join(ret)
ret = []
space = dup(" ", num_tabs * TAB_WIDTH)
for line in content.split("\n"):
ret.append(space + line)
return "\n".join(ret)

'''
Generates web-platform-test wrapper for test262 tests.

If a directory is passed as origin for the tests, the class recursively reads
out al the JavaScript files contained and converts them to web-platform-tests.

If a file is passed as origin, only that tests is converted.

The resulting tests are placed inside an output directory named 'js'.
'''
class WPTestBuilder(object):

def __init__(self, path):
self.path = path

def generate(self):
path = self.path
mode = os.stat(self.path)[ST_MODE]
if S_ISDIR(mode):
# Check root folder contains 'test'.
test_dir = path
if not re.search("test262/test", path):
test_dir = os.path.join(path, "test")
mode = os.stat(test_dir)[ST_MODE]
if not S_ISDIR(mode):
print(("Could not locate 'test' folder at %s" % path))
exit(1)
print("Generating web-platform-test wrappers for test262")
# Blacklist 'harness' from root folders.
folders = [test_dir]
for each in os.listdir(test_dir):
if each == 'harness':
continue
folders.append(os.path.join(test_dir, each))
# Generate wrappers.
for filename in self.listall(folders, {'ext': 'js', 'skip_hidden': True}):
# Files that have the suffix _FIXTURE are not tests, but should be preserved.
if re.search('_FIXTURE.js$', filename):
self.keep_file(filename)
continue
# If they are not fixtures, they're tests. However, it's necessary to keep the
# original file because some tests reference themselves.
if re.search('module-code', filename):
self.keep_file(filename)
self.generate_single_wpt(filename)
print("Output: " + self.destination_path(path))
elif S_ISREG(mode):
pos = path.find("test262/test")
if pos < 0:
print(("Not a test262 file: '%s'" % path))
exit(1)
print("Generating web-platform-tests wrapper for {0}".format(path))
print("Output: %s" % self.generate_single_wpt(path))
else:
print("Skipping %s" % path)

def keep_file(self, filename):
dirname = self.destination_path(filename)
dst = os.path.join(dirname, os.path.basename(filename))
self.mkdir(dirname)
content = self.replace_module_path_if_any(dirname, self.readfile(filename))
return self.savefile(dst, content)

def destination_path(self, filename):
filename = re.sub(".*test262/test/", "", filename)
return os.path.join(DEST_PATH, os.path.dirname(filename))

def mkdir(self, path):
process = subprocess.Popen(("mkdir -p {0}".format(path)).split())
process.wait()

def replace_module_path_if_any(self, dirname, content):
# Replace "[import|export] from './filename'".
content = re.sub(r"from\s+(['\"])\.\/(.*?\.js)(['\"])",
r"from \1http://localhost:8000/" + dirname + r"/\2\3", content)
# Replace "import './filename'".
content = re.sub(r"import (['\"])\.\/(.*?\.js)(['\"])",
r"import \1http://localhost:8000/" + dirname + r"/\2\3", content)
# Replace "export './filename'".
content = re.sub(r"export (['\"])\.\/(.*?\.js)(['\"])",
r"export \1http://localhost:8000/" + dirname + r"/\2\3", content)
return content

def savefile(self, output, content):
with open(output, 'wt') as fd:
fd.write(content)
return fd.name

def generate_single_wpt(self, filename):
dst = self.destination_file(filename)
dirname = os.path.dirname(dst)
self.mkdir(dirname)
content = self.replace_module_path_if_any(dirname, self.readfile(filename))
return self.savefile(dst, self.build(dst, content))

def destination_file(self, filename):
filename = re.sub(".*test262/test/", "", filename)
filename = re.sub('.js$', '.html', filename)
return os.path.join(DEST_PATH, filename)

def build(self, title, content):
test262 = Test262Parser(content)
def header():
attrs = tojson(test262.attrs)
ret = HEADER
ret = re.sub('###TITLE###', title, ret)
ret = ret.replace('###ATTRS###', indent(2, "let attrs = %s;" % tojson(test262.attrs)))
ret = ret.replace('###HEADER###', indent(2, test262.header))
return ret
def tojson(attrs):
exclude = ['description', 'info', 'esid', 'es6id']
for key in exclude:
attrs.pop(key, None)
return json.dumps(attrs) or '{}'
def body():
def escape(text):
text = re.sub(r'\\', r'\\\\', text)
text = re.sub(r'"', r'\"', text)
return text
def quote(line):
return "\"" + line + "\\n\""
def format(text):
output = []
lines = text.split("\n");
for line in text.split("\n"):
if len(line) > 0:
output.append(quote(line))
return " + \n".join(output)
return indent(4, "return \"\" +\n%s;" % format(escape(test262.body)))
def footer():
# By default tests are run in strict and non-strict modes,
# unless flags says otherwise.
ret = []
flags = 'flags' in test262.attrs and test262.attrs['flags'] or []
if 'onlyStrict' in flags:
ret.append(run_in_iframe_strict(title))
ret.append(run_in_window_strict(title))
ret.append(run_in_worker_strict(title))
ret.append(run_in_shared_worker_strict(title))
elif 'noStrict' in flags or 'raw' in flags:
ret.append(run_in_iframe(title))
ret.append(run_in_window(title))
ret.append(run_in_worker(title))
ret.append(run_in_shared_worker(title))
else:
ret.append(run_in_iframe_strict(title))
ret.append(run_in_iframe(title))
ret.append(run_in_window_strict(title))
ret.append(run_in_window(title))
ret.append(run_in_worker_strict(title))
ret.append(run_in_worker(title))
ret.append(run_in_shared_worker_strict(title))
ret.append(run_in_shared_worker(title))
return FOOTER.replace("###TESTS###", indent(3, "\n".join(ret)))
ret = []
ret.append(header())
ret.append(body())
ret.append(footer())
return "\n".join(ret)

def readfile(self, path):
with open(path) as fd:
content = fd.read()
# Replace CR + LF for LF only.
content = re.sub("\r\n", "\n", content)
# Replace CR for LF.
return re.sub("\r", "\n", content)

def listall(self, folders, opts):
opts = opts or {}
ret = []
ext = opts['ext']
skip_hidden = opts['skip_hidden']
for folder in folders:
for root, dirs, files in os.walk(folder):
for filename in files:
if skip_hidden and filename.startswith('.'):
continue
if ext and filename.endswith(ext):
ret.append(os.path.join(root, filename))
return ret

def main():
path = os.path.join(here, '..', '..', '..', '..', 'src', 'test262')
WPTestBuilder(path).generate()

if __name__ == "__main__":
main()
Loading