|
1 | 1 | # mypy: allow-untyped-defs |
2 | 2 |
|
| 3 | +import builtins |
| 4 | +import io |
3 | 5 | import logging |
4 | 6 | import os |
5 | 7 | import pickle |
6 | 8 | import platform |
| 9 | +from unittest.mock import MagicMock, patch |
| 10 | +from typing import Generator, List, Tuple, Type |
7 | 11 |
|
8 | 12 | import pytest |
9 | 13 |
|
10 | 14 | import localpaths # type: ignore |
11 | 15 | from . import serve |
12 | | -from .serve import ConfigBuilder, inject_script |
| 16 | +from .serve import ( |
| 17 | + ConfigBuilder, |
| 18 | + WrapperHandler, |
| 19 | + inject_script, |
| 20 | + # Use 'T262' aliases to avoid naming collisions with the pytest collector |
| 21 | + Test262WindowHandler as T262WindowHandler, |
| 22 | + Test262WindowTestHandler as T262WindowTestHandler, |
| 23 | + Test262WindowModuleHandler as T262WindowModuleHandler, |
| 24 | + Test262WindowModuleTestHandler as T262WindowModuleTestHandler, |
| 25 | + Test262StrictWindowHandler as T262StrictWindowHandler, |
| 26 | + Test262StrictWindowTestHandler as T262StrictWindowTestHandler, |
| 27 | + Test262StrictHandler as T262StrictHandler) |
13 | 28 |
|
14 | 29 |
|
15 | 30 | logger = logging.getLogger() |
@@ -154,3 +169,181 @@ def test_inject_script_parse_error(): |
154 | 169 | # On a parse error, the script should not be injected and the original content should be |
155 | 170 | # returned. |
156 | 171 | assert INJECT_SCRIPT_MARKER not in inject_script(html.replace(INJECT_SCRIPT_MARKER, b""), INJECT_SCRIPT_MARKER) |
| 172 | + |
| 173 | + |
| 174 | +@pytest.fixture |
| 175 | +def test262_handlers() -> Generator[Tuple[str, str], None, None]: |
| 176 | + tests_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "tests", "testdata")) |
| 177 | + url_base = "/" |
| 178 | + |
| 179 | + mock_file_contents = { |
| 180 | + os.path.normpath(os.path.join(tests_root, "test262", "basic.js")): """/*---\ndescription: A basic test |
| 181 | +includes: [assert.js, sta.js] |
| 182 | +---*/ |
| 183 | +assert.sameValue(1, 1); |
| 184 | +""", |
| 185 | + os.path.normpath(os.path.join(tests_root, "test262", "negative.js")): """/*---\ndescription: A negative test |
| 186 | +negative: |
| 187 | + phase: runtime |
| 188 | + type: TypeError |
| 189 | +---*/ |
| 190 | +throw new TypeError(); |
| 191 | +""", |
| 192 | + os.path.normpath(os.path.join(tests_root, "test262", "module.js")): """/*---\ndescription: A module test |
| 193 | +flags: [module] |
| 194 | +---*/ |
| 195 | +import {} from 'some-module'; |
| 196 | +""", |
| 197 | + os.path.normpath(os.path.join(tests_root, "test262", "teststrict.js")): """/*---\ndescription: A strict mode test |
| 198 | +flags: [onlyStrict] |
| 199 | +includes: [propertyHelper.js] |
| 200 | +---*/ |
| 201 | +console.log('hello'); |
| 202 | +""" |
| 203 | + } |
| 204 | + |
| 205 | + # Store original functions to be called if our mock doesn't handle the file |
| 206 | + original_open = builtins.open |
| 207 | + original_exists = os.path.exists |
| 208 | + original_isdir = os.path.isdir |
| 209 | + |
| 210 | + def custom_open(file, mode='r', *args, **kwargs): |
| 211 | + normalized_file = os.path.normpath(file) |
| 212 | + if normalized_file in mock_file_contents: |
| 213 | + if 'b' in mode: |
| 214 | + return io.BytesIO(mock_file_contents[normalized_file].encode('ISO-8859-1')) |
| 215 | + else: |
| 216 | + return io.StringIO(mock_file_contents[normalized_file]) |
| 217 | + return original_open(file, mode, *args, **kwargs) |
| 218 | + |
| 219 | + def custom_exists(path): |
| 220 | + normalized_path = os.path.normpath(path) |
| 221 | + return normalized_path in mock_file_contents or original_exists(path) |
| 222 | + |
| 223 | + def custom_isdir(path): |
| 224 | + normalized_path = os.path.normpath(path) |
| 225 | + expected_dir = os.path.normpath(os.path.join(tests_root, "test262")) |
| 226 | + return normalized_path == expected_dir or original_isdir(path) |
| 227 | + |
| 228 | + with patch('builtins.open', side_effect=custom_open), \ |
| 229 | + patch('os.path.exists', side_effect=custom_exists), \ |
| 230 | + patch('os.path.isdir', side_effect=custom_isdir): |
| 231 | + yield tests_root, url_base |
| 232 | + |
| 233 | + |
| 234 | +def _create_mock_request(path: str) -> MagicMock: |
| 235 | + mock_request = MagicMock() |
| 236 | + mock_request.url_parts.path = path |
| 237 | + mock_request.url_parts.query = "" |
| 238 | + return mock_request |
| 239 | + |
| 240 | + |
| 241 | +def _test_handler_path_replace(handler_cls: Type[WrapperHandler], |
| 242 | + tests_root: str, |
| 243 | + url_base: str, |
| 244 | + expected: List[Tuple[str, str]]) -> None: |
| 245 | + handler = handler_cls(base_path=tests_root, url_base=url_base) |
| 246 | + assert handler.path_replace == expected |
| 247 | + |
| 248 | +def _test_handler_wrapper_content(handler_cls: Type[WrapperHandler], |
| 249 | + tests_root: str, |
| 250 | + url_base: str, |
| 251 | + request_path: str, |
| 252 | + expected_content: List[str]) -> None: |
| 253 | + handler = handler_cls(base_path=tests_root, url_base=url_base) |
| 254 | + mock_request = _create_mock_request(request_path) |
| 255 | + mock_response = MagicMock() |
| 256 | + handler.handle_request(mock_request, mock_response) # type: ignore[no-untyped-call] |
| 257 | + content = mock_response.content |
| 258 | + for item in expected_content: |
| 259 | + assert item in content |
| 260 | + |
| 261 | +def _test_handler_get_metadata(handler_cls: Type[WrapperHandler], |
| 262 | + tests_root: str, |
| 263 | + url_base: str, |
| 264 | + request_path: str, |
| 265 | + expected_metadata: List[Tuple[str, str]]) -> None: |
| 266 | + handler = handler_cls(tests_root, url_base) |
| 267 | + mock_request = _create_mock_request(request_path) |
| 268 | + metadata = list(handler._get_metadata(mock_request)) # type: ignore[no-untyped-call] |
| 269 | + for item in expected_metadata: |
| 270 | + assert item in metadata |
| 271 | + assert len(expected_metadata) == len(metadata), f"{expected_metadata} != {metadata}" |
| 272 | + |
| 273 | + |
| 274 | +@pytest.mark.parametrize("handler_cls, expected", [ |
| 275 | + (T262WindowHandler, [(".test262.html", ".js", ".test262-test.html")]), |
| 276 | + (T262WindowTestHandler, [(".test262-test.html", ".js")]), |
| 277 | + (T262WindowModuleHandler, [(".test262-module.html", ".js", ".test262-module-test.html")]), |
| 278 | + (T262WindowModuleTestHandler, [(".test262-module-test.html", ".js")]), |
| 279 | + (T262StrictWindowHandler, [(".test262.strict.html", ".js", ".test262-test.strict.html")]), |
| 280 | + (T262StrictWindowTestHandler, [(".test262-test.strict.html", ".js", ".test262.strict.js")]), |
| 281 | +]) |
| 282 | +def test_path_replace(test262_handlers, handler_cls, expected): |
| 283 | + tests_root, url_base = test262_handlers |
| 284 | + _test_handler_path_replace(handler_cls, tests_root, url_base, expected) |
| 285 | + |
| 286 | + |
| 287 | +@pytest.mark.parametrize("handler_cls, request_path, expected_metadata", [ |
| 288 | + ( |
| 289 | + T262WindowTestHandler, |
| 290 | + "/test262/basic.test262-test.html", |
| 291 | + [('script', '/third_party/test262/harness/assert.js'), ('script', '/third_party/test262/harness/sta.js')] |
| 292 | + ), |
| 293 | + ( |
| 294 | + T262WindowTestHandler, |
| 295 | + "/test262/negative.test262-test.html", |
| 296 | + [('negative', 'TypeError')] |
| 297 | + ), |
| 298 | + ( |
| 299 | + T262StrictWindowTestHandler, |
| 300 | + "/test262/teststrict.test262-test.strict.html", |
| 301 | + [('script', '/third_party/test262/harness/propertyHelper.js')] |
| 302 | + ), |
| 303 | +]) |
| 304 | +def test_get_metadata(test262_handlers, handler_cls, request_path, expected_metadata): |
| 305 | + tests_root, url_base = test262_handlers |
| 306 | + _test_handler_get_metadata(handler_cls, tests_root, url_base, request_path, expected_metadata) |
| 307 | + |
| 308 | + |
| 309 | +@pytest.mark.parametrize("handler_cls, request_path, expected_substrings", [ |
| 310 | + # T262WindowHandler: Should contain the iframe pointing to the test |
| 311 | + ( |
| 312 | + T262WindowHandler, |
| 313 | + "/test262/basic.test262.html", |
| 314 | + ['<iframe id="test262-iframe" src="/test262/basic.test262-test.html"></iframe>'] |
| 315 | + ), |
| 316 | + # T262WindowTestHandler: Should contain script tags |
| 317 | + ( |
| 318 | + T262WindowTestHandler, |
| 319 | + "/test262/basic.test262-test.html", |
| 320 | + ['<script src="/test262/basic.js"></script>', '<script>test262Setup()</script>', '<script>test262Done()</script>'] |
| 321 | + ), |
| 322 | + # T262WindowModuleTestHandler: Should contain module import |
| 323 | + ( |
| 324 | + T262WindowModuleTestHandler, |
| 325 | + "/test262/module.test262-module-test.html", |
| 326 | + ['<script type="module">', 'import {} from "/test262/module.js";', 'test262Setup();', 'test262Done();'] |
| 327 | + ), |
| 328 | + # Verification of the 'negative' replacement in the HTML |
| 329 | + ( |
| 330 | + T262WindowTestHandler, |
| 331 | + "/test262/negative.test262-test.html", |
| 332 | + ["<script>test262Negative('TypeError')</script>"] |
| 333 | + ), |
| 334 | + # Strict HTML Case: points to the .strict.js variant |
| 335 | + ( |
| 336 | + T262StrictWindowTestHandler, |
| 337 | + "/test262/teststrict.test262-test.strict.html", |
| 338 | + ['src="/test262/teststrict.test262.strict.js"'] |
| 339 | + ), |
| 340 | + # Strict JS Case: The handler that serves the actual script |
| 341 | + ( |
| 342 | + T262StrictHandler, |
| 343 | + "/test262/teststrict.test262.strict.js", |
| 344 | + ['"use strict";', "console.log('hello');"] |
| 345 | + ), |
| 346 | +]) |
| 347 | +def test_wrapper_content(test262_handlers, handler_cls, request_path, expected_substrings): |
| 348 | + tests_root, url_base = test262_handlers |
| 349 | + _test_handler_wrapper_content(handler_cls, tests_root, url_base, request_path, expected_substrings) |
0 commit comments