[wptrunner] Force stopping the browser when wdspec test hits external timeout.
Web-platform tests use a timeout per test module while pytest
supports a timeout for test methods. As long as a file wide
timeout isn't supported wptrunner has to force kill the browser
when a wdspec test runs into an external timeout.
Differential Revision: https://phabricator.services.mozilla.com/D139714
bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1754712
gecko-commit: 21dda7d9394c8678424160933c1a487661d59bd6
gecko-reviewers: jgraham
diff --git a/tools/wptrunner/wptrunner/browsers/firefox.py b/tools/wptrunner/wptrunner/browsers/firefox.py
index 0c103bd..afc7b58 100644
--- a/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -883,12 +883,13 @@
super().start(group_metadata, **kwargs)
def stop(self, force=False):
- # Initially wait for any WebDriver session to cleanly shutdown
- # When this is called the executor is usually sending a end session
+ # Initially wait for any WebDriver session to cleanly shutdown if the
+ # process doesn't have to be force stopped.
+ # When this is called the executor is usually sending an end session
# command to the browser. We don't have a synchronisation mechanism
# that allows us to know that process is ongoing, so poll the status
# endpoint until there isn't a session, before killing the driver.
- if self.is_alive():
+ if self.is_alive() and not force:
end_time = time.time() + BrowserInstance.shutdown_timeout
while time.time() < end_time:
self.logger.debug("Waiting for WebDriver session to end")
diff --git a/tools/wptrunner/wptrunner/testrunner.py b/tools/wptrunner/wptrunner/testrunner.py
index ca214b7..0c75683 100644
--- a/tools/wptrunner/wptrunner/testrunner.py
+++ b/tools/wptrunner/wptrunner/testrunner.py
@@ -247,9 +247,9 @@
initializing = namedtuple("initializing",
["test", "test_group", "group_metadata", "failure_count"])
running = namedtuple("running", ["test", "test_group", "group_metadata"])
- restarting = namedtuple("restarting", ["test", "test_group", "group_metadata"])
+ restarting = namedtuple("restarting", ["test", "test_group", "group_metadata", "force_stop"])
error = namedtuple("error", [])
- stop = namedtuple("stop", [])
+ stop = namedtuple("stop", ["force_stop"])
RunnerManagerState = _RunnerManagerState()
@@ -351,7 +351,7 @@
RunnerManagerState.before_init: self.start_init,
RunnerManagerState.initializing: self.init,
RunnerManagerState.running: self.run_test,
- RunnerManagerState.restarting: self.restart_runner
+ RunnerManagerState.restarting: self.restart_runner,
}
self.state = RunnerManagerState.before_init()
@@ -386,8 +386,8 @@
raise
finally:
self.logger.debug("TestRunnerManager main loop terminating, starting cleanup")
- clean = isinstance(self.state, RunnerManagerState.stop)
- self.stop_runner(force=not clean)
+ force_stop = not isinstance(self.state, RunnerManagerState.stop) or self.state.force_stop
+ self.stop_runner(force=force_stop)
self.teardown()
self.logger.debug("TestRunnerManager main loop terminated")
@@ -418,12 +418,15 @@
self.logger.debug("Got command: %r" % command)
except IOError:
self.logger.error("Got IOError from poll")
- return RunnerManagerState.restarting(0)
+ return RunnerManagerState.restarting(self.state.test,
+ self.state.test_group,
+ self.state.group_metadata,
+ False)
except Empty:
if (self.debug_info and self.debug_info.interactive and
self.browser.started and not self.browser.is_alive()):
self.logger.debug("Debugger exited")
- return RunnerManagerState.stop()
+ return RunnerManagerState.stop(False)
if (isinstance(self.state, RunnerManagerState.running) and
not self.test_runner_proc.is_alive()):
@@ -443,7 +446,10 @@
self.logger.critical("Last test did not complete")
return RunnerManagerState.error()
self.logger.warning("More tests found, but runner process died, restarting")
- return RunnerManagerState.restarting(0)
+ return RunnerManagerState.restarting(self.state.test,
+ self.state.test_group,
+ self.state.group_metadata,
+ False)
else:
f = (dispatch.get(self.state.__class__, {}).get(command) or
dispatch.get(None, {}).get(command))
@@ -460,7 +466,7 @@
test, test_group, group_metadata = self.get_next_test()
self.recording.set(["testrunner", "init"])
if test is None:
- return RunnerManagerState.stop()
+ return RunnerManagerState.stop(True)
else:
return RunnerManagerState.initializing(test, test_group, group_metadata, 0)
@@ -551,7 +557,8 @@
self.logger.info("Restarting browser for new test environment")
return RunnerManagerState.restarting(self.state.test,
self.state.test_group,
- self.state.group_metadata)
+ self.state.group_metadata,
+ False)
self.recording.set(["testrunner", "test"] + self.state.test.id.split("/")[1:])
self.logger.test_start(self.state.test.id)
@@ -685,6 +692,7 @@
file_result.status in ("CRASH", "EXTERNAL-TIMEOUT", "INTERNAL-ERROR") or
((subtest_unexpected or is_unexpected) and
self.restart_on_unexpected))
+ force_stop = test.test_type == "wdspec" and file_result.status == "EXTERNAL-TIMEOUT"
self.recording.set(["testrunner", "after-test"])
if (not file_result.status == "CRASH" and
@@ -693,7 +701,7 @@
self.logger.info("Pausing until the browser exits")
self.send_message("wait")
else:
- return self.after_test_end(test, restart_before_next)
+ return self.after_test_end(test, restart_before_next, force_stop=force_stop)
def wait_finished(self, rerun=False):
assert isinstance(self.state, RunnerManagerState.running)
@@ -703,7 +711,7 @@
# post-stop processing
return self.after_test_end(self.state.test, not rerun, force_rerun=rerun)
- def after_test_end(self, test, restart, force_rerun=False):
+ def after_test_end(self, test, restart, force_rerun=False, force_stop=False):
assert isinstance(self.state, RunnerManagerState.running)
# Mixing manual reruns and automatic reruns is confusing; we currently assume
# that as long as we've done at least the automatic run count in total we can
@@ -711,7 +719,7 @@
if not force_rerun and self.run_count >= self.rerun:
test, test_group, group_metadata = self.get_next_test()
if test is None:
- return RunnerManagerState.stop()
+ return RunnerManagerState.stop(force_stop)
if test_group is not self.state.test_group:
# We are starting a new group of tests, so force a restart
self.logger.info("Restarting browser for new test group")
@@ -720,14 +728,14 @@
test_group = self.state.test_group
group_metadata = self.state.group_metadata
if restart:
- return RunnerManagerState.restarting(test, test_group, group_metadata)
+ return RunnerManagerState.restarting(test, test_group, group_metadata, force_stop)
else:
return RunnerManagerState.running(test, test_group, group_metadata)
def restart_runner(self):
"""Stop and restart the TestRunner"""
assert isinstance(self.state, RunnerManagerState.restarting)
- self.stop_runner()
+ self.stop_runner(force=self.state.force_stop)
return RunnerManagerState.initializing(self.state.test, self.state.test_group, self.state.group_metadata, 0)
def log(self, data):
@@ -794,7 +802,7 @@
def runner_teardown(self):
self.ensure_runner_stopped()
- return RunnerManagerState.stop()
+ return RunnerManagerState.stop(False)
def send_message(self, command, *args):
"""Send a message to the remote queue (to Executor)."""