Skip to content

Commit 09b80b5

Browse files
committed
Bug 1930013 [wpt PR 49044] - [wptrunner] Decouple testdriver infrastructure from testharness, a=testonly
Automatic update from web-platform-tests [wptrunner] Decouple testdriver infrastructure from testharness (#49044) * [wptrunner] Extract executor testdriver logic into mixin This no-op refactor will allow other WebDriver executors, not just the testharness executor, to perform testdriver actions. * [wptrunner] Split `message-queue.js` from `testharnessreport.js` This will allow non-testharness tests to use `testdriver.js` without needing extra scripts. Evaluating `message-queue.js` is idempotent so that, when using testharness with testdriver, the second inclusion is a no-op. Because resource scripts are cached, the size increase should not meaningfully affect test performance. -- wpt-commits: e51161a84e07661f8699839c511c89767ec498a8 wpt-pr: 49044 UltraBlame original commit: 246f9bd1024deec8badb8d2ec9c62c3b41fe9f7e
1 parent 2240627 commit 09b80b5

File tree

7 files changed

+226
-199
lines changed

7 files changed

+226
-199
lines changed

testing/web-platform/tests/tools/wptrunner/wptrunner/environment.py

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
sys.path.insert(0, repo_root)
2424
from tools import localpaths
2525

26-
from wptserve.handlers import StringHandler
27-
2826
serve = None
2927

3028

@@ -226,29 +224,47 @@ def get_routes(self):
226224
self.config.aliases,
227225
self.config)
228226

227+
testharnessreport_format_args = {
228+
"output": self.pause_after_test,
229+
"timeout_multiplier": self.testharness_timeout_multipler,
230+
"explicit_timeout": "true" if self.debug_info is not None else "false",
231+
"debug": "true" if self.debug_test else "false",
232+
}
229233
for path, format_args, content_type, route in [
230234
("testharness_runner.html", {}, "text/html", "/testharness_runner.html"),
231235
("print_pdf_runner.html", {}, "text/html", "/print_pdf_runner.html"),
232-
(os.path.join(here, "..", "..", "third_party", "pdf_js", "pdf.js"), None,
236+
(os.path.join(here, "..", "..", "third_party", "pdf_js", "pdf.js"), {},
233237
"text/javascript", "/_pdf_js/pdf.js"),
234-
(os.path.join(here, "..", "..", "third_party", "pdf_js", "pdf.worker.js"), None,
238+
(os.path.join(here, "..", "..", "third_party", "pdf_js", "pdf.worker.js"), {},
235239
"text/javascript", "/_pdf_js/pdf.worker.js"),
236-
(self.options.get("testharnessreport", "testharnessreport.js"),
237-
{"output": self.pause_after_test,
238-
"timeout_multiplier": self.testharness_timeout_multipler,
239-
"explicit_timeout": "true" if self.debug_info is not None else "false",
240-
"debug": "true" if self.debug_test else "false"},
241-
"text/javascript;charset=utf8",
242-
"/resources/testharnessreport.js")]:
243-
path = os.path.normpath(os.path.join(here, path))
240+
(
241+
self.options.get("testharnessreport", [
242+
243+
244+
os.path.join("executors", "message-queue.js"),
245+
"testharnessreport.js"]),
246+
testharnessreport_format_args,
247+
"text/javascript;charset=utf8",
248+
"/resources/testharnessreport.js",
249+
),
250+
(
251+
[os.path.join(repo_root, "resources", "testdriver.js"),
252+
253+
os.path.join("executors", "message-queue.js"),
254+
"testdriver-extra.js"],
255+
{},
256+
"text/javascript",
257+
"/resources/testdriver.js",
258+
),
259+
]:
260+
paths = [path] if isinstance(path, str) else path
261+
abs_paths = [os.path.normpath(os.path.join(here, path)) for path in paths]
244262

245263

246264
headers = {"Cache-Control": "max-age=3600"}
247-
route_builder.add_static(path, format_args, content_type, route,
265+
route_builder.add_static(abs_paths, format_args, content_type, route,
248266
headers=headers)
249267

250-
route_builder.add_handler("GET", "/resources/testdriver.js", TestdriverLoader())
251-
252268
for url_base, test_root in self.test_paths.items():
253269
if url_base == "/":
254270
continue
@@ -316,27 +332,6 @@ def test_servers(self):
316332
return failed, pending
317333

318334

319-
class TestdriverLoader:
320-
"""A special static handler for serving `/resources/testdriver.js`.
321-
322-
This handler lazily reads `testdriver{,-extra}.js` so that wptrunner doesn't
323-
need to pass the entire file contents to child `wptserve` processes, which
324-
can slow `wptserve` startup by several seconds (crbug.com/1479850).
325-
"""
326-
def __init__(self):
327-
self._handler = None
328-
329-
def __call__(self, request, response):
330-
if not self._handler:
331-
data = b""
332-
with open(os.path.join(repo_root, "resources", "testdriver.js"), "rb") as fp:
333-
data += fp.read()
334-
with open(os.path.join(here, "testdriver-extra.js"), "rb") as fp:
335-
data += fp.read()
336-
self._handler = StringHandler(data, "text/javascript")
337-
return self._handler(request, response)
338-
339-
340335
def wait_for_service(logger: StructuredLogger,
341336
host: str,
342337
port: int,

testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorchrome.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ def send_message(self, cmd_id, message_type, status, message=None):
111111
else:
112112
self.webdriver.execute_script(message_script)
113113

114-
def _get_next_message_classic(self, url):
114+
def _get_next_message_classic(self, url, script_resume):
115115
try:
116116
message_script, self._pending_message = self._pending_message, ""
117-
return self.parent.base.execute_script(message_script + self.script_resume,
117+
return self.parent.base.execute_script(message_script + script_resume,
118118
asynchronous=True,
119119
args=[strip_server(url)])
120120
except error.JavascriptErrorException as js_error:

testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py

Lines changed: 101 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -437,24 +437,22 @@ def release(self):
437437
class WebDriverTestDriverProtocolPart(TestDriverProtocolPart):
438438
def setup(self):
439439
self.webdriver = self.parent.webdriver
440-
with open(os.path.join(here, "testharness_webdriver_resume.js")) as f:
441-
self.script_resume = f.read()
442440

443-
def get_next_message(self, url, test_window):
441+
def get_next_message(self, url, script_resume, test_window):
444442
if hasattr(self.parent, "bidi_script"):
445443

446-
return self._get_next_message_bidi(url, test_window)
444+
return self._get_next_message_bidi(url, script_resume, test_window)
447445
else:
448-
return self._get_next_message_classic(url)
446+
return self._get_next_message_classic(url, script_resume)
449447

450-
def _get_next_message_classic(self, url):
448+
def _get_next_message_classic(self, url, script_resume):
451449
"""
452450
Get the next message from the test_driver using the classic WebDriver async script execution. This will block
453451
the event loop until the test_driver send a message.
454452
"""
455-
return self.parent.base.execute_script(self.script_resume, asynchronous=True, args=[strip_server(url)])
453+
return self.parent.base.execute_script(script_resume, asynchronous=True, args=[strip_server(url)])
456454

457-
def _get_next_message_bidi(self, url, test_window):
455+
def _get_next_message_bidi(self, url, script_resume, test_window):
458456
"""
459457
Get the next message from the test_driver using async call. This will not block the event loop, which allows for
460458
processing the events from the test_runner to test_driver while waiting for the next test_driver commands.
@@ -470,7 +468,7 @@ def _get_next_message_bidi(self, url, test_window):
470468
%s
471469
}).apply(null, args);
472470
})
473-
}""" % self.script_resume
471+
}""" % script_resume
474472

475473
bidi_url_argument = {
476474
"type": "string",
@@ -837,64 +835,19 @@ def run_func(self):
837835
self.result_flag.set()
838836

839837

840-
class WebDriverTestharnessExecutor(TestharnessExecutor):
841-
supports_testdriver = True
842-
protocol_cls = WebDriverProtocol
843-
844-
def __init__(self, logger, browser, server_config, timeout_multiplier=1,
845-
close_after_done=True, capabilities=None, debug_info=None,
846-
cleanup_after_test=True, **kwargs):
847-
"""WebDriver-based executor for testharness.js tests"""
848-
TestharnessExecutor.__init__(self, logger, browser, server_config,
849-
timeout_multiplier=timeout_multiplier,
850-
debug_info=debug_info)
851-
self.protocol = self.protocol_cls(self, browser, capabilities)
852-
with open(os.path.join(here, "window-loaded.js")) as f:
853-
self.window_loaded_script = f.read()
854-
855-
856-
self.close_after_done = close_after_done
857-
self.cleanup_after_test = cleanup_after_test
858-
859-
def is_alive(self):
860-
return self.protocol.is_alive()
861-
862-
def on_environment_change(self, new_environment):
863-
if new_environment["protocol"] != self.last_environment["protocol"]:
864-
self.protocol.testharness.load_runner(new_environment["protocol"])
865-
866-
def do_test(self, test):
867-
url = self.test_url(test)
868-
869-
success, data = WebDriverRun(self.logger,
870-
self.do_testharness,
871-
self.protocol,
872-
url,
873-
test.timeout * self.timeout_multiplier,
874-
self.extra_timeout).run()
875-
876-
if success:
877-
data, extra = data
878-
return self.convert_result(test, data, extra=extra)
879838

880-
return (test.make_result(*data), [])
881839

882-
def do_testharness(self, protocol, url, timeout):
883-
884-
885-
protocol.testharness.close_old_windows()
840+
class TestDriverExecutorMixin:
841+
def __init__(self, script_resume: str):
842+
self.script_resume = script_resume
886843

844+
def run_testdriver(self, protocol, url, timeout):
887845

888846
if hasattr(protocol, 'bidi_events'):
889847

890848
protocol.loop.run_until_complete(protocol.bidi_events.unsubscribe_all())
891849

892-
893850
test_window = self.get_or_create_test_window(protocol)
894-
self.protocol.base.set_window(test_window)
895-
896-
protocol.base.execute_script(self.window_loaded_script, asynchronous=True)
897-
898851

899852
unexpected_exceptions = []
900853

@@ -948,7 +901,8 @@ async def process_bidi_event(method, params):
948901

949902
raise unexpected_exceptions[0]
950903

951-
test_driver_message = protocol.testdriver.get_next_message(url, test_window)
904+
test_driver_message = protocol.testdriver.get_next_message(url, self.script_resume,
905+
test_window)
952906
self.logger.debug("Receive message from testdriver: %s" % test_driver_message)
953907

954908

@@ -981,36 +935,100 @@ async def process_bidi_event(method, params):
981935

982936
protocol.loop.run_until_complete(protocol.bidi_events.unsubscribe_all())
983937

984-
extra = {}
985-
if leak_part := getattr(protocol, "leak", None):
986-
testharness_window = protocol.base.current_window
987-
extra_windows = set(protocol.base.window_handles())
988-
extra_windows -= {protocol.testharness.runner_handle, testharness_window}
989-
protocol.testharness.close_windows(extra_windows)
990-
try:
991-
protocol.base.set_window(testharness_window)
992-
if counters := leak_part.check():
993-
extra["leak_counters"] = counters
994-
except webdriver_error.NoSuchWindowException:
995-
pass
996-
finally:
997-
protocol.base.set_window(protocol.testharness.runner_handle)
998-
999-
1000-
1001-
1002-
1003-
if self.cleanup_after_test:
1004-
protocol.testharness.close_old_windows()
1005-
1006938
if len(unexpected_exceptions) > 0:
1007939

1008940
raise unexpected_exceptions[0]
1009941

1010-
return rv, extra
942+
return rv
1011943

1012944
def get_or_create_test_window(self, protocol):
1013-
return protocol.base.create_window()
945+
return protocol.base.current_window
946+
947+
948+
class WebDriverTestharnessExecutor(TestharnessExecutor, TestDriverExecutorMixin):
949+
supports_testdriver = True
950+
protocol_cls = WebDriverProtocol
951+
952+
def __init__(self, logger, browser, server_config, timeout_multiplier=1,
953+
close_after_done=True, capabilities=None, debug_info=None,
954+
cleanup_after_test=True, **kwargs):
955+
"""WebDriver-based executor for testharness.js tests"""
956+
TestharnessExecutor.__init__(self, logger, browser, server_config,
957+
timeout_multiplier=timeout_multiplier,
958+
debug_info=debug_info)
959+
self.protocol = self.protocol_cls(self, browser, capabilities)
960+
with open(os.path.join(here, "testharness_webdriver_resume.js")) as f:
961+
script_resume = f.read()
962+
TestDriverExecutorMixin.__init__(self, script_resume)
963+
with open(os.path.join(here, "window-loaded.js")) as f:
964+
self.window_loaded_script = f.read()
965+
966+
self.close_after_done = close_after_done
967+
self.cleanup_after_test = cleanup_after_test
968+
969+
def is_alive(self):
970+
return self.protocol.is_alive()
971+
972+
def on_environment_change(self, new_environment):
973+
if new_environment["protocol"] != self.last_environment["protocol"]:
974+
self.protocol.testharness.load_runner(new_environment["protocol"])
975+
976+
def do_test(self, test):
977+
url = self.test_url(test)
978+
979+
success, data = WebDriverRun(self.logger,
980+
self.do_testharness,
981+
self.protocol,
982+
url,
983+
test.timeout * self.timeout_multiplier,
984+
self.extra_timeout).run()
985+
986+
if success:
987+
data, extra = data
988+
return self.convert_result(test, data, extra=extra)
989+
990+
return (test.make_result(*data), [])
991+
992+
def do_testharness(self, protocol, url, timeout):
993+
try:
994+
995+
996+
protocol.testharness.close_old_windows()
997+
raw_results = self.run_testdriver(protocol, url, timeout)
998+
extra = {}
999+
if counters := self._check_for_leaks(protocol):
1000+
extra["leak_counters"] = counters
1001+
return raw_results, extra
1002+
finally:
1003+
1004+
1005+
1006+
1007+
if self.cleanup_after_test:
1008+
protocol.testharness.close_old_windows()
1009+
1010+
def _check_for_leaks(self, protocol):
1011+
leak_part = getattr(protocol, "leak", None)
1012+
if not leak_part:
1013+
return None
1014+
testharness_window = protocol.base.current_window
1015+
extra_windows = set(protocol.base.window_handles())
1016+
extra_windows -= {protocol.testharness.runner_handle, testharness_window}
1017+
protocol.testharness.close_windows(extra_windows)
1018+
try:
1019+
protocol.base.set_window(testharness_window)
1020+
return leak_part.check()
1021+
except webdriver_error.NoSuchWindowException:
1022+
return None
1023+
finally:
1024+
protocol.base.set_window(protocol.testharness.runner_handle)
1025+
1026+
def get_or_create_test_window(self, protocol):
1027+
test_window = protocol.base.create_window()
1028+
protocol.base.set_window(test_window)
1029+
1030+
protocol.base.execute_script(self.window_loaded_script, asynchronous=True)
1031+
return test_window
10141032

10151033

10161034
class WebDriverRefTestExecutor(RefTestExecutor):

0 commit comments

Comments
 (0)