diff options
Diffstat (limited to 'testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorcontentshell.py')
-rw-r--r-- | testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorcontentshell.py | 328 |
1 files changed, 0 insertions, 328 deletions
diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorcontentshell.py b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorcontentshell.py deleted file mode 100644 index 82a6aebcdb..0000000000 --- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorcontentshell.py +++ /dev/null @@ -1,328 +0,0 @@ -# mypy: allow-untyped-defs - -from .base import RefTestExecutor, RefTestImplementation, CrashtestExecutor, TestharnessExecutor -from .executorchrome import make_sanitizer_mixin -from .protocol import Protocol, ProtocolPart -from time import time -from queue import Empty -from base64 import b64encode -import json - - -class CrashError(BaseException): - pass - -class LeakError(BaseException): - pass - -def _read_line(io_queue, deadline=None, encoding=None, errors="strict", raise_crash_leak=True): - """Reads a single line from the io queue. The read must succeed before `deadline` or - a TimeoutError is raised. The line is returned as a bytestring or optionally with the - specified `encoding`. If `raise_crash_leak` is set, a CrashError is raised if the line - happens to be a crash message, or a LeakError is raised if the line happens to be a - leak message. - """ - current_time = time() - - if deadline and current_time > deadline: - raise TimeoutError() - - try: - line = io_queue.get(True, deadline - current_time if deadline else None) - if raise_crash_leak and line.startswith(b"#CRASHED"): - raise CrashError() - if raise_crash_leak and line.startswith(b"#LEAK"): - raise LeakError() - except Empty as e: - raise TimeoutError() from e - - return line.decode(encoding, errors) if encoding else line - - -class ContentShellTestPart(ProtocolPart): - """This protocol part is responsible for running tests via content_shell's protocol mode. - - For more details, see: - https://chromium.googlesource.com/chromium/src.git/+/HEAD/content/web_test/browser/test_info_extractor.h - """ - name = "content_shell_test" - eof_marker = '#EOF\n' # Marker sent by content_shell after blocks. - - def __init__(self, parent): - super().__init__(parent) - self.stdout_queue = parent.browser.stdout_queue - self.stdin_queue = parent.browser.stdin_queue - - def do_test(self, command, timeout=None): - """Send a command to content_shell and return the resulting outputs. - - A command consists of a URL to navigate to, followed by an optional - expected image hash and 'print' mode specifier. The syntax looks like: - http://web-platform.test:8000/test.html['<hash>['print]] - """ - self._send_command(command) - - deadline = time() + timeout if timeout else None - # The first block can also contain audio data but not in WPT. - text = self._read_block(deadline) - image = self._read_block(deadline) - - return text, image - - def _send_command(self, command): - """Sends a single `command`, i.e. a URL to open, to content_shell. - """ - self.stdin_queue.put((command + "\n").encode("utf-8")) - - def _read_block(self, deadline=None): - """Tries to read a single block of content from stdout before the `deadline`. - """ - while True: - line = _read_line(self.stdout_queue, deadline, "latin-1").rstrip() - - if line == "Content-Type: text/plain": - return self._read_text_block(deadline) - - if line == "Content-Type: image/png": - return self._read_image_block(deadline) - - if line == "#EOF": - return None - - def _read_text_block(self, deadline=None): - """Tries to read a plain text block in utf-8 encoding before the `deadline`. - """ - result = "" - - while True: - line = _read_line(self.stdout_queue, deadline, "utf-8", "replace", False) - - if line.endswith(self.eof_marker): - result += line[:-len(self.eof_marker)] - break - elif line.endswith('#EOF\r\n'): - result += line[:-len('#EOF\r\n')] - self.logger.warning('Got a CRLF-terminated #EOF - this is a driver bug.') - break - - result += line - - return result - - def _read_image_block(self, deadline=None): - """Tries to read an image block (as a binary png) before the `deadline`. - """ - content_length_line = _read_line(self.stdout_queue, deadline, "utf-8") - assert content_length_line.startswith("Content-Length:") - content_length = int(content_length_line[15:]) - - result = bytearray() - - while True: - line = _read_line(self.stdout_queue, deadline, raise_crash_leak=False) - excess = len(line) + len(result) - content_length - - if excess > 0: - # This is the line that contains the EOF marker. - assert excess == len(self.eof_marker) - result += line[:-excess] - break - - result += line - - return result - - -class ContentShellErrorsPart(ProtocolPart): - """This protocol part is responsible for collecting the errors reported by content_shell. - """ - name = "content_shell_errors" - - def __init__(self, parent): - super().__init__(parent) - self.stderr_queue = parent.browser.stderr_queue - - def read_errors(self): - """Reads the entire content of the stderr queue as is available right now (no blocking). - """ - result = "" - - while not self.stderr_queue.empty(): - # There is no potential for race conditions here because this is the only place - # where we read from the stderr queue. - result += _read_line(self.stderr_queue, None, "utf-8", "replace", False) - - return result - - -class ContentShellBasePart(ProtocolPart): - """This protocol part provides functionality common to all executors. - - In particular, this protocol part implements `wait()`, which, when - `--pause-after-test` is enabled, test runners block on until the next test - should run. - """ - name = "base" - - def __init__(self, parent): - super().__init__(parent) - self.io_stopped = parent.browser.io_stopped - - def wait(self): - # This worker is unpaused when the browser window is closed, which this - # `multiprocessing.Event` signals. - self.io_stopped.wait() - # Never rerun the test. - return False - - -class ContentShellProtocol(Protocol): - implements = [ - ContentShellBasePart, - ContentShellTestPart, - ContentShellErrorsPart, - ] - init_timeout = 10 # Timeout (seconds) to wait for #READY message. - - def connect(self): - """Waits for content_shell to emit its "#READY" message which signals that it is fully - initialized. We wait for a maximum of self.init_timeout seconds. - """ - deadline = time() + self.init_timeout - - while True: - if _read_line(self.browser.stdout_queue, deadline).rstrip() == b"#READY": - break - - def after_connect(self): - pass - - def teardown(self): - # Close the queue properly to avoid broken pipe spam in the log. - self.browser.stdin_queue.close() - self.browser.stdin_queue.join_thread() - - def is_alive(self): - """Checks if content_shell is alive by determining if the IO pipes are still - open. This does not guarantee that the process is responsive. - """ - return self.browser.io_stopped.is_set() - - -def _convert_exception(test, exception, errors): - """Converts our TimeoutError and CrashError exceptions into test results. - """ - if isinstance(exception, TimeoutError): - return (test.make_result("EXTERNAL-TIMEOUT", errors), []) - if isinstance(exception, CrashError): - return (test.make_result("CRASH", errors), []) - if isinstance(exception, LeakError): - # TODO: the internal error is to force a restart, but it doesn't correctly - # describe what the issue is. Need to find a way to return a "FAIL", - # and restart the content_shell after the test run. - return (test.make_result("INTERNAL-ERROR", errors), []) - raise exception - - -def timeout_for_test(executor, test): - if executor.debug_info and executor.debug_info.interactive: - return None - return test.timeout * executor.timeout_multiplier - - -class ContentShellCrashtestExecutor(CrashtestExecutor): - def __init__(self, logger, browser, server_config, timeout_multiplier=1, debug_info=None, - **kwargs): - super().__init__(logger, browser, server_config, timeout_multiplier, debug_info, **kwargs) - self.protocol = ContentShellProtocol(self, browser) - - def do_test(self, test): - try: - _ = self.protocol.content_shell_test.do_test(self.test_url(test), - timeout_for_test(self, test)) - self.protocol.content_shell_errors.read_errors() - return self.convert_result(test, {"status": "PASS", "message": None}) - except BaseException as exception: - return _convert_exception(test, exception, self.protocol.content_shell_errors.read_errors()) - - -_SanitizerMixin = make_sanitizer_mixin(ContentShellCrashtestExecutor) - - -class ContentShellRefTestExecutor(RefTestExecutor, _SanitizerMixin): # type: ignore - def __init__(self, logger, browser, server_config, timeout_multiplier=1, screenshot_cache=None, - debug_info=None, reftest_screenshot="unexpected", **kwargs): - super().__init__(logger, browser, server_config, timeout_multiplier, screenshot_cache, - debug_info, reftest_screenshot, **kwargs) - self.implementation = RefTestImplementation(self) - self.protocol = ContentShellProtocol(self, browser) - - def reset(self): - self.implementation.reset() - - def do_test(self, test): - try: - result = self.implementation.run_test(test) - self.protocol.content_shell_errors.read_errors() - return self.convert_result(test, result) - except BaseException as exception: - return _convert_exception(test, exception, self.protocol.content_shell_errors.read_errors()) - - def screenshot(self, test, viewport_size, dpi, page_ranges): - # Currently, the page size and DPI are hardcoded for print-reftests: - # https://chromium.googlesource.com/chromium/src/+/4e1b7bc33d42b401d7d9ad1dcba72883add3e2af/content/web_test/renderer/test_runner.cc#100 - # Content shell has an internal `window.testRunner.setPrintingSize(...)` - # API, but it's not callable with protocol mode. - assert dpi is None - command = self.test_url(test) - if self.is_print: - # Currently, `content_shell` uses the expected image hash to avoid - # dumping a matching image as an optimization. In Chromium, the - # hash can be computed from an expected screenshot checked into the - # source tree (i.e., without looking at a reference). This is not - # possible in `wpt`, so pass an empty hash here to force a dump. - command += "''print" - - _, image = self.protocol.content_shell_test.do_test(command, - timeout_for_test(self, test)) - if not image: - return False, ("ERROR", self.protocol.content_shell_errors.read_errors()) - return True, b64encode(image).decode() - - -class ContentShellPrintRefTestExecutor(ContentShellRefTestExecutor): - is_print = True - - -class ContentShellTestharnessExecutor(TestharnessExecutor, _SanitizerMixin): # type: ignore - # Chromium's `testdriver-vendor.js` partially implements testdriver support - # with internal APIs [1]. - # - # [1]: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Relying-on-Blink_Specific-Testing-APIs - supports_testdriver = True - - def __init__(self, logger, browser, server_config, timeout_multiplier=1, debug_info=None, - **kwargs): - super().__init__(logger, browser, server_config, timeout_multiplier, debug_info, **kwargs) - self.protocol = ContentShellProtocol(self, browser) - - def do_test(self, test): - try: - text, _ = self.protocol.content_shell_test.do_test(self.test_url(test), - timeout_for_test(self, test)) - errors = self.protocol.content_shell_errors.read_errors() - if not text: - return (test.make_result("ERROR", errors), []) - - result_url, status, message, stack, subtest_results = json.loads(text) - if result_url != test.url: - # Suppress `convert_result`'s URL validation. - # See `testharnessreport-content-shell.js` for details. - self.logger.warning('Got results from %s, expected %s' % (result_url, test.url)) - self.logger.warning('URL mismatch may be a false positive ' - 'if the test navigates') - result_url = test.url - raw_result = result_url, status, message, stack, subtest_results - return self.convert_result(test, raw_result) - except BaseException as exception: - return _convert_exception(test, exception, self.protocol.content_shell_errors.read_errors()) |