diff --git a/js/tools/test262.py b/js/tools/test262.py
new file mode 100755
index 00000000000000..aa9764bc3b5d95
--- /dev/null
+++ b/js/tools/test262.py
@@ -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('''
+
+
+
+
+ ###TITLE###
+
+
+
+
+
+
+
+
+
+
+
+''')
+
+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()
diff --git a/resources/test262-agent-harness.js b/resources/test262-agent-harness.js
new file mode 100644
index 00000000000000..1af0a7f69fd028
--- /dev/null
+++ b/resources/test262-agent-harness.js
@@ -0,0 +1,358 @@
+// Helper file to run a test262 within an agent.
+//
+// The target test262 is in text format. The harnessing code composes an HTML
+// page containing the test262, runs it and reports for errors if there was
+// any. Similarly if the target agent is a Worker, the harnessing code embedes
+// the test code within the target worker, runs the test and checks out if
+// there were any errors.
+//
+// Currently supported agents are IFrame, Window, Worker and SharedWorker.
+
+// IFrame.
+function run_in_iframe_strict(test262, attrs, t) {
+ let opts = {}
+ opts.strict = true;
+ run_in_iframe(test262, attrs, t, opts);
+}
+
+// If 'negative' attribute was defined, the test must pass if 'message'
+// matches 'negative.type'.
+function is_negative(attrs, message) {
+ let negative = attrs.negative || {};
+ return message && message.indexOf(negative.type) >= 0;
+}
+
+function run_in_iframe(test262, attrs, t, opts) {
+ opts = opts || {};
+ // Rethrow error from iframe.
+ window.addEventListener('message', t.step_func(function(e) {
+ if (e.data[0] == 'error') {
+ throw new Error(e.data[1]);
+ }
+ }));
+ let iframe = document.createElement('iframe');
+ iframe.style = 'display: none';
+ content = test262_as_html(test262, attrs, opts.strict);
+ let blob = new Blob([content], {type: 'text/html'});
+ iframe.src = URL.createObjectURL(blob);
+ document.body.appendChild(iframe);
+
+ let w = iframe.contentWindow;
+ // Finish test on completed event.
+ w.addEventListener('completed', t.step_func(function(e) {
+ t.done();
+ }));
+ // If test failed, rethrow error.
+ let FAILED = 'iframe-failed' + opts.strict ? 'strict' : '';
+ window.addEventListener(FAILED, t.step_func(function(e) {
+ t.set_status(t.FAIL);
+ throw new Error(e.detail);
+ }));
+ // In case of error send it to parent window.
+ w.addEventListener('error', function(e) {
+ e.preventDefault();
+ if (is_negative(attrs, e.message)) {
+ t.done();
+ return;
+ }
+ top.dispatchEvent(new CustomEvent(FAILED, {detail: e.message}));
+ });
+}
+
+let HEADER = `
+
+
+
+
+
+
+
+