diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /testing/web-platform/harness/wptrunner/executors/base.py | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/web-platform/harness/wptrunner/executors/base.py')
-rw-r--r-- | testing/web-platform/harness/wptrunner/executors/base.py | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/testing/web-platform/harness/wptrunner/executors/base.py b/testing/web-platform/harness/wptrunner/executors/base.py new file mode 100644 index 000000000..f0ce16658 --- /dev/null +++ b/testing/web-platform/harness/wptrunner/executors/base.py @@ -0,0 +1,329 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import hashlib +import json +import os +import traceback +import urlparse +from abc import ABCMeta, abstractmethod + +from ..testrunner import Stop + +here = os.path.split(__file__)[0] + + +def executor_kwargs(test_type, server_config, cache_manager, **kwargs): + timeout_multiplier = kwargs["timeout_multiplier"] + if timeout_multiplier is None: + timeout_multiplier = 1 + + executor_kwargs = {"server_config": server_config, + "timeout_multiplier": timeout_multiplier, + "debug_info": kwargs["debug_info"]} + + if test_type == "reftest": + executor_kwargs["screenshot_cache"] = cache_manager.dict() + + return executor_kwargs + + +def strip_server(url): + """Remove the scheme and netloc from a url, leaving only the path and any query + or fragment. + + url - the url to strip + + e.g. http://example.org:8000/tests?id=1#2 becomes /tests?id=1#2""" + + url_parts = list(urlparse.urlsplit(url)) + url_parts[0] = "" + url_parts[1] = "" + return urlparse.urlunsplit(url_parts) + + +class TestharnessResultConverter(object): + harness_codes = {0: "OK", + 1: "ERROR", + 2: "TIMEOUT"} + + test_codes = {0: "PASS", + 1: "FAIL", + 2: "TIMEOUT", + 3: "NOTRUN"} + + def __call__(self, test, result): + """Convert a JSON result into a (TestResult, [SubtestResult]) tuple""" + result_url, status, message, stack, subtest_results = result + assert result_url == test.url, ("Got results from %s, expected %s" % + (result_url, test.url)) + harness_result = test.result_cls(self.harness_codes[status], message) + return (harness_result, + [test.subtest_result_cls(name, self.test_codes[status], message, stack) + for name, status, message, stack in subtest_results]) + + +testharness_result_converter = TestharnessResultConverter() + + +def reftest_result_converter(self, test, result): + return (test.result_cls(result["status"], result["message"], + extra=result.get("extra")), []) + + +def pytest_result_converter(self, test, data): + harness_data, subtest_data = data + + if subtest_data is None: + subtest_data = [] + + harness_result = test.result_cls(*harness_data) + subtest_results = [test.subtest_result_cls(*item) for item in subtest_data] + + return (harness_result, subtest_results) + + +class ExecutorException(Exception): + def __init__(self, status, message): + self.status = status + self.message = message + + +class TestExecutor(object): + __metaclass__ = ABCMeta + + test_type = None + convert_result = None + + def __init__(self, browser, server_config, timeout_multiplier=1, + debug_info=None): + """Abstract Base class for object that actually executes the tests in a + specific browser. Typically there will be a different TestExecutor + subclass for each test type and method of executing tests. + + :param browser: ExecutorBrowser instance providing properties of the + browser that will be tested. + :param server_config: Dictionary of wptserve server configuration of the + form stored in TestEnvironment.external_config + :param timeout_multiplier: Multiplier relative to base timeout to use + when setting test timeout. + """ + self.runner = None + self.browser = browser + self.server_config = server_config + self.timeout_multiplier = timeout_multiplier + self.debug_info = debug_info + self.last_environment = {"protocol": "http", + "prefs": {}} + self.protocol = None # This must be set in subclasses + + @property + def logger(self): + """StructuredLogger for this executor""" + if self.runner is not None: + return self.runner.logger + + def setup(self, runner): + """Run steps needed before tests can be started e.g. connecting to + browser instance + + :param runner: TestRunner instance that is going to run the tests""" + self.runner = runner + if self.protocol is not None: + self.protocol.setup(runner) + + def teardown(self): + """Run cleanup steps after tests have finished""" + if self.protocol is not None: + self.protocol.teardown() + + def run_test(self, test): + """Run a particular test. + + :param test: The test to run""" + if test.environment != self.last_environment: + self.on_environment_change(test.environment) + + try: + result = self.do_test(test) + except Exception as e: + result = self.result_from_exception(test, e) + + if result is Stop: + return result + + # log result of parent test + if result[0].status == "ERROR": + self.logger.debug(result[0].message) + + self.last_environment = test.environment + + self.runner.send_message("test_ended", test, result) + + def server_url(self, protocol): + return "%s://%s:%s" % (protocol, + self.server_config["host"], + self.server_config["ports"][protocol][0]) + + def test_url(self, test): + return urlparse.urljoin(self.server_url(test.environment["protocol"]), test.url) + + @abstractmethod + def do_test(self, test): + """Test-type and protocol specific implementation of running a + specific test. + + :param test: The test to run.""" + pass + + def on_environment_change(self, new_environment): + pass + + def result_from_exception(self, test, e): + if hasattr(e, "status") and e.status in test.result_cls.statuses: + status = e.status + else: + status = "ERROR" + message = unicode(getattr(e, "message", "")) + if message: + message += "\n" + message += traceback.format_exc(e) + return test.result_cls(status, message), [] + + +class TestharnessExecutor(TestExecutor): + convert_result = testharness_result_converter + + +class RefTestExecutor(TestExecutor): + convert_result = reftest_result_converter + + def __init__(self, browser, server_config, timeout_multiplier=1, screenshot_cache=None, + debug_info=None): + TestExecutor.__init__(self, browser, server_config, + timeout_multiplier=timeout_multiplier, + debug_info=debug_info) + + self.screenshot_cache = screenshot_cache + + +class RefTestImplementation(object): + def __init__(self, executor): + self.timeout_multiplier = executor.timeout_multiplier + self.executor = executor + # Cache of url:(screenshot hash, screenshot). Typically the + # screenshot is None, but we set this value if a test fails + # and the screenshot was taken from the cache so that we may + # retrieve the screenshot from the cache directly in the future + self.screenshot_cache = self.executor.screenshot_cache + self.message = None + + @property + def logger(self): + return self.executor.logger + + def get_hash(self, test, viewport_size, dpi): + timeout = test.timeout * self.timeout_multiplier + key = (test.url, viewport_size, dpi) + + if key not in self.screenshot_cache: + success, data = self.executor.screenshot(test, viewport_size, dpi) + + if not success: + return False, data + + screenshot = data + hash_value = hashlib.sha1(screenshot).hexdigest() + + self.screenshot_cache[key] = (hash_value, None) + + rv = (hash_value, screenshot) + else: + rv = self.screenshot_cache[key] + + self.message.append("%s %s" % (test.url, rv[0])) + return True, rv + + def is_pass(self, lhs_hash, rhs_hash, relation): + assert relation in ("==", "!=") + self.message.append("Testing %s %s %s" % (lhs_hash, relation, rhs_hash)) + return ((relation == "==" and lhs_hash == rhs_hash) or + (relation == "!=" and lhs_hash != rhs_hash)) + + def run_test(self, test): + viewport_size = test.viewport_size + dpi = test.dpi + self.message = [] + + # Depth-first search of reference tree, with the goal + # of reachings a leaf node with only pass results + + stack = list(((test, item[0]), item[1]) for item in reversed(test.references)) + while stack: + hashes = [None, None] + screenshots = [None, None] + + nodes, relation = stack.pop() + + for i, node in enumerate(nodes): + success, data = self.get_hash(node, viewport_size, dpi) + if success is False: + return {"status": data[0], "message": data[1]} + + hashes[i], screenshots[i] = data + + if self.is_pass(hashes[0], hashes[1], relation): + if nodes[1].references: + stack.extend(list(((nodes[1], item[0]), item[1]) for item in reversed(nodes[1].references))) + else: + # We passed + return {"status":"PASS", "message": None} + + # We failed, so construct a failure message + + for i, (node, screenshot) in enumerate(zip(nodes, screenshots)): + if screenshot is None: + success, screenshot = self.retake_screenshot(node, viewport_size, dpi) + if success: + screenshots[i] = screenshot + + log_data = [{"url": nodes[0].url, "screenshot": screenshots[0]}, relation, + {"url": nodes[1].url, "screenshot": screenshots[1]}] + + return {"status": "FAIL", + "message": "\n".join(self.message), + "extra": {"reftest_screenshots": log_data}} + + def retake_screenshot(self, node, viewport_size, dpi): + success, data = self.executor.screenshot(node, viewport_size, dpi) + if not success: + return False, data + + key = (node.url, viewport_size, dpi) + hash_val, _ = self.screenshot_cache[key] + self.screenshot_cache[key] = hash_val, data + return True, data + + +class WdspecExecutor(TestExecutor): + convert_result = pytest_result_converter + + +class Protocol(object): + def __init__(self, executor, browser): + self.executor = executor + self.browser = browser + + @property + def logger(self): + return self.executor.logger + + def setup(self, runner): + pass + + def teardown(self): + pass + + def wait(self): + pass |