Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
107 changes: 107 additions & 0 deletions tools/serve/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from localpaths import repo_root # type: ignore

from manifest.sourcefile import read_script_metadata, js_meta_re, parse_variants # type: ignore
from manifest.test262 import parse as test262_parse # type: ignore
from wptserve import server as wptserve, handlers
from wptserve import stash
from wptserve import config
Expand Down Expand Up @@ -327,6 +328,81 @@ class ExtensionHandler(HtmlWrapperHandler):
"""


class Test262WindowHandler(HtmlWrapperHandler):
path_replace = [(".test262.html", ".js", ".test262-test.html")]
wrapper = """<!doctype html>
<meta charset=utf-8>
<title>Test</title>
<script src="/resources/test262/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
%(meta)s
%(script)s
<div id=log></div>
<iframe id="test262-iframe" src="%(path)s"></iframe>"""


class Test262WindowTestBaseHandler(HtmlWrapperHandler):
# For SHAB
headers = [('Cross-Origin-Opener-Policy', 'same-origin'),
('Cross-Origin-Embedder-Policy', 'require-corp')]

# Define a common HTML structure (testharness setup, etc.) that can be
# extended by subclasses. This avoids duplicating boilerplate. For example,
# Test262WindowModuleTestHandler reuses this `pre_wrapper` but appends a
# module script.
pre_wrapper = """<!doctype html>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is split into multiple variables so we can reuse this part in subclasses? Would be good to mention that in a comment, otherwise it looks rather strange.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

<meta charset=utf-8>
<title>Test</title>
<script src="/resources/test262/testharness-client.js"></script>
<script src="/third_party/test262/harness/assert.js"></script>
<script src="/third_party/test262/harness/sta.js"></script>
<script src="/resources/test262/harness-adapter.js"></script>
%(meta)s
%(script)s"""
wrapper = pre_wrapper + """<script>test262Setup()</script>
<script src="%(path)s"></script>
<script>test262Done()</script>"""

def _get_metadata(self, request):
path = self._get_filesystem_path(request)
with open(path, encoding='utf-8') as f:
test_record = test262_parse(logging.getLogger(), f.read(), path)
yield from (('script', "/third_party/test262/harness/%s" % filename)
for filename in (test_record.includes or []))

expected_error = (test_record.negative or {}).get('type', None)
if expected_error is not None:
yield ('negative', expected_error)

def _meta_replacement(self, key: str, value: str) -> Optional[str]:
if key == 'negative':
return """<script>test262Negative('%s')</script>""" % value
return None


class Test262WindowTestHandler(Test262WindowTestBaseHandler):
path_replace = [(".test262-test.html", ".js")]


class Test262WindowModuleHandler(Test262WindowHandler):
path_replace = [(".test262-module.html", ".js", ".test262-module-test.html")]

class Test262WindowModuleTestHandler(Test262WindowTestBaseHandler):
path_replace = [(".test262-module-test.html", ".js")]
wrapper = Test262WindowTestBaseHandler.pre_wrapper + """<script type="module">
test262Setup();
import {} from "%(path)s";
test262Done();
</script>"""


class Test262StrictWindowHandler(Test262WindowHandler):
path_replace = [(".test262.strict.html", ".js", ".test262-test.strict.html")]

class Test262StrictWindowTestHandler(Test262WindowTestBaseHandler):
path_replace = [(".test262-test.strict.html", ".js", ".test262.strict.js")]


class WindowModulesHandler(HtmlWrapperHandler):
global_type = "window-module"
path_replace = [(".any.window-module.html", ".any.js")]
Expand Down Expand Up @@ -574,6 +650,30 @@ class ShadowRealmInAudioWorkletHandler(HtmlWrapperHandler):
"""


class Test262StrictHandler(WrapperHandler):
path_replace = [(".test262.strict.js", ".js")]
headers = [('Content-Type', 'text/javascript')]
wrapper = """
"use strict";
%(script)s
"""

def _meta_replacement(self, key, value):
return None

def _get_script(self, request):
"""
Reads the entire content of the associated JavaScript file to be
prepended with "use strict".
"""
path = self._get_filesystem_path(request)
try:
with open(path, encoding='utf-8') as f:
yield f.read()
except OSError:
raise HTTPException(404)


class BaseWorkerHandler(WrapperHandler):
headers = [("Content-Type", "text/javascript")]

Expand Down Expand Up @@ -787,6 +887,13 @@ def add_mount_point(self, url_base, path):
("GET", "*.worker.html", WorkersHandler),
("GET", "*.worker-module.html", WorkerModulesHandler),
("GET", "*.window.html", WindowHandler),
("GET", "*.test262.html", Test262WindowHandler),
("GET", "*.test262-test.html", Test262WindowTestHandler),
("GET", "*.test262-module.html", Test262WindowModuleHandler),
("GET", "*.test262-module-test.html", Test262WindowModuleTestHandler),
("GET", "*.test262.strict.html", Test262StrictWindowHandler),
("GET", "*.test262-test.strict.html", Test262StrictWindowTestHandler),
("GET", "*.test262.strict.js", Test262StrictHandler),
("GET", "*.extension.html", ExtensionHandler),
("GET", "*.any.html", AnyHtmlHandler),
("GET", "*.any.sharedworker.html", SharedWorkersHandler),
Expand Down
201 changes: 200 additions & 1 deletion tools/serve/test_serve.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
# mypy: allow-untyped-defs

import builtins
import io
import logging
import os
import pickle
import platform
from unittest.mock import MagicMock, patch
from typing import Generator, List, Tuple, Type

import pytest

import localpaths # type: ignore
from . import serve
from .serve import ConfigBuilder, inject_script
from .serve import (
ConfigBuilder,
Test262WindowHandler,
Test262WindowTestHandler,
Test262WindowModuleHandler,
Test262WindowModuleTestHandler,
Test262StrictWindowHandler,
Test262StrictWindowTestHandler,
Test262StrictHandler,
WrapperHandler,
inject_script)


logger = logging.getLogger()
Expand Down Expand Up @@ -154,3 +168,188 @@ def test_inject_script_parse_error():
# On a parse error, the script should not be injected and the original content should be
# returned.
assert INJECT_SCRIPT_MARKER not in inject_script(html.replace(INJECT_SCRIPT_MARKER, b""), INJECT_SCRIPT_MARKER)


@pytest.fixture
def test262_handlers() -> Generator[Tuple[str, str], None, None]:
tests_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "tests", "testdata"))
url_base = "/"

mock_file_contents = {
os.path.normpath(os.path.join(tests_root, "test262", "basic.js")): """/*---\ndescription: A basic test
includes: [assert.js, sta.js]
---*/
assert.sameValue(1, 1);
""",
os.path.normpath(os.path.join(tests_root, "test262", "negative.js")): """/*---\ndescription: A negative test
negative:
phase: runtime
type: TypeError
---*/
throw new TypeError();
""",
os.path.normpath(os.path.join(tests_root, "test262", "module.js")): """/*---\ndescription: A module test
flags: [module]
---*/
import {} from 'some-module';
""",
os.path.normpath(os.path.join(tests_root, "test262", "teststrict.js")): """/*---\ndescription: A strict mode test
flags: [onlyStrict]
includes: [propertyHelper.js]
---*/
console.log('hello');
"""
}

# Store original functions to be called if our mock doesn't handle the file
original_open = builtins.open
original_exists = os.path.exists
original_isdir = os.path.isdir

def custom_open(file, mode='r', *args, **kwargs):
normalized_file = os.path.normpath(file)
if normalized_file in mock_file_contents:
if 'b' in mode:
return io.BytesIO(mock_file_contents[normalized_file].encode('utf-8'))
else:
return io.StringIO(mock_file_contents[normalized_file])
return original_open(file, mode, *args, **kwargs)

def custom_exists(path):
normalized_path = os.path.normpath(path)
return normalized_path in mock_file_contents or original_exists(path)

def custom_isdir(path):
normalized_path = os.path.normpath(path)
expected_dir = os.path.normpath(os.path.join(tests_root, "test262"))
return normalized_path == expected_dir or original_isdir(path)

with patch('builtins.open', side_effect=custom_open), \
patch('os.path.exists', side_effect=custom_exists), \
patch('os.path.isdir', side_effect=custom_isdir):
yield tests_root, url_base


def _create_mock_request(path: str) -> MagicMock:
mock_request = MagicMock()
mock_request.url_parts.path = path
mock_request.url_parts.query = ""
return mock_request


def _test_handler_path_replace(handler_cls: Type[WrapperHandler],
tests_root: str,
url_base: str,
expected: List[Tuple[str, str]]) -> None:
handler = handler_cls(base_path=tests_root, url_base=url_base)
assert handler.path_replace == expected

def _test_handler_wrapper_content(handler_cls: Type[WrapperHandler],
tests_root: str,
url_base: str,
request_path: str,
expected_content: List[str]) -> None:
handler = handler_cls(base_path=tests_root, url_base=url_base)
mock_request = _create_mock_request(request_path)
mock_response = MagicMock()
handler.handle_request(mock_request, mock_response) # type: ignore[no-untyped-call]
content = mock_response.content
for item in expected_content:
assert item in content

def _test_handler_get_metadata(handler_cls: Type[WrapperHandler],
tests_root: str,
url_base: str,
request_path: str,
expected_metadata: List[Tuple[str, str]]) -> None:
handler = handler_cls(tests_root, url_base)
mock_request = _create_mock_request(request_path)
metadata = list(handler._get_metadata(mock_request)) # type: ignore[no-untyped-call]
for item in expected_metadata:
assert item in metadata
assert len(expected_metadata) == len(metadata), f"{expected_metadata} != {metadata}"

Test262WindowHandler.__test__ = False # type: ignore[attr-defined]
Test262WindowTestHandler.__test__ = False # type: ignore[attr-defined]
Test262WindowModuleHandler.__test__ = False # type: ignore[attr-defined]
Test262WindowModuleTestHandler.__test__ = False # type: ignore[attr-defined]
Test262StrictWindowHandler.__test__ = False # type: ignore[attr-defined]
Test262StrictWindowTestHandler.__test__ = False # type: ignore[attr-defined]
Test262StrictHandler.__test__ = False # type: ignore[attr-defined]

@pytest.mark.parametrize("handler_cls, expected", [
(Test262WindowHandler, [(".test262.html", ".js", ".test262-test.html")]),
(Test262WindowTestHandler, [(".test262-test.html", ".js")]),
(Test262WindowModuleHandler, [(".test262-module.html", ".js", ".test262-module-test.html")]),
(Test262WindowModuleTestHandler, [(".test262-module-test.html", ".js")]),
(Test262StrictWindowHandler, [(".test262.strict.html", ".js", ".test262-test.strict.html")]),
(Test262StrictWindowTestHandler, [(".test262-test.strict.html", ".js", ".test262.strict.js")]),
])
def test_path_replace(test262_handlers, handler_cls, expected):
tests_root, url_base = test262_handlers
_test_handler_path_replace(handler_cls, tests_root, url_base, expected)


@pytest.mark.parametrize("handler_cls, request_path, expected_metadata", [
(
Test262WindowTestHandler,
"/test262/basic.test262-test.html",
[('script', '/third_party/test262/harness/assert.js'), ('script', '/third_party/test262/harness/sta.js')]
),
(
Test262WindowTestHandler,
"/test262/negative.test262-test.html",
[('negative', 'TypeError')]
),
(
Test262StrictWindowTestHandler,
"/test262/teststrict.test262-test.strict.html",
[('script', '/third_party/test262/harness/propertyHelper.js')]
),
])
def test_get_metadata(test262_handlers, handler_cls, request_path, expected_metadata):
tests_root, url_base = test262_handlers
_test_handler_get_metadata(handler_cls, tests_root, url_base, request_path, expected_metadata)


@pytest.mark.parametrize("handler_cls, request_path, expected_substrings", [
# Test262WindowHandler: Should contain the iframe pointing to the test
(
Test262WindowHandler,
"/test262/basic.test262.html",
['<iframe id="test262-iframe" src="/test262/basic.test262-test.html"></iframe>']
),
# Test262WindowTestHandler: Should contain script tags
(
Test262WindowTestHandler,
"/test262/basic.test262-test.html",
['<script src="/test262/basic.js"></script>', '<script>test262Setup()</script>', '<script>test262Done()</script>']
),
# Test262WindowModuleTestHandler: Should contain module import
(
Test262WindowModuleTestHandler,
"/test262/module.test262-module-test.html",
['<script type="module">', 'import {} from "/test262/module.js";', 'test262Setup();', 'test262Done();']
),
# Verification of the 'negative' replacement in the HTML
(
Test262WindowTestHandler,
"/test262/negative.test262-test.html",
["<script>test262Negative('TypeError')</script>"]
),
# Strict HTML Case: points to the .strict.js variant
(
Test262StrictWindowTestHandler,
"/test262/teststrict.test262-test.strict.html",
['src="/test262/teststrict.test262.strict.js"']
),
# Strict JS Case: The handler that serves the actual script
(
Test262StrictHandler,
"/test262/teststrict.test262.strict.js",
['"use strict";', "console.log('hello');"]
),
])
def test_wrapper_content(test262_handlers, handler_cls, request_path, expected_substrings):
tests_root, url_base = test262_handlers
_test_handler_wrapper_content(handler_cls, tests_root, url_base, request_path, expected_substrings)
Loading