Attempt to avoid timing out after successful iOS test runs by catching SIGTERM.
The test runner is sent SIGTERM typically 30s before the hard timeout
of a test run; knowing that, this CL attempts to catch that SIGTERM
and force kill a hung test process, allowing it to exit more gracefully.
(See:
https://cs.chromium.org/chromium/infra/luci/appengine/swarming/swarming_bot/bot_code/bot_main.py?l=891&rcl=599029f4b1abebfa812f3b2b9df9a5c6c7acbca4)
This should not affect the success/failure of a run, as that is
determined by "TEST EXECUTION SUCCEEDED" appearing in the stdout
for a test run. Return code is noted but not used for success.
I also add some minor logging in this CL for clarity.
Change-Id: I5ec882862db6693f09f69f6e191261f2c6ba43f9
Bug: 898549
Reviewed-on: https://chromium-review.googlesource.com/c/1448684
Commit-Queue: ericale <ericale@chromium.org>
Reviewed-by: Sergey Berezin <sergeyberezin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#628935}
diff --git a/ios/build/bots/scripts/test_runner.py b/ios/build/bots/scripts/test_runner.py
index 09052c9..88307b1f 100644
--- a/ios/build/bots/scripts/test_runner.py
+++ b/ios/build/bots/scripts/test_runner.py
@@ -139,7 +139,8 @@
class ShardingDisabledError(TestRunnerError):
"""Temporary error indicating that sharding is not yet implemented."""
def __init__(self):
- super(ShardingDisabledError, self).__init__('Sharding has not been implemented!')
+ super(ShardingDisabledError, self).__init__(
+ 'Sharding has not been implemented!')
def get_kif_test_filter(tests, invert=False):
@@ -456,6 +457,31 @@
"""
raise NotImplementedError
+ def set_sigterm_handler(self, handler):
+ """Sets the SIGTERM handler for the test runner.
+
+ This is its own separate function so it can be mocked in tests.
+
+ Args:
+ handler: The handler to be called when a SIGTERM is caught
+
+ Returns:
+ The previous SIGTERM handler for the test runner.
+ """
+ return signal.signal(signal.SIGTERM, handler)
+
+ def handle_sigterm(self, proc):
+ """Handles a SIGTERM sent while a test command is executing.
+
+ Will SIGKILL the currently executing test process, then
+ attempt to exit gracefully.
+
+ Args:
+ proc: The currently executing test process.
+ """
+ print "Sigterm caught during test run. Killing test process."
+ proc.kill()
+
def _run(self, cmd, shards=1):
"""Runs the specified command, parsing GTest output.
@@ -497,6 +523,9 @@
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
+ old_handler = self.set_sigterm_handler(
+ lambda _signum, _frame: self.handle_sigterm(proc))
+
while True:
line = proc.stdout.readline()
if not line:
@@ -506,7 +535,10 @@
print line
sys.stdout.flush()
+ print "Waiting for test process to terminate."
proc.wait()
+ print "Test process terminated."
+ self.set_sigterm_handler(old_handler)
sys.stdout.flush()
returncode = proc.returncode
@@ -1119,6 +1151,8 @@
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
+ old_handler = self.set_sigterm_handler(
+ lambda _signum, _frame: self.handle_sigterm(proc))
if self.xctest_path:
parser = xctest_utils.XCTestLogParser()
@@ -1135,6 +1169,7 @@
sys.stdout.flush()
proc.wait()
+ self.set_sigterm_handler(old_handler)
sys.stdout.flush()
self.wprgo_stop()
diff --git a/ios/build/bots/scripts/test_runner_test.py b/ios/build/bots/scripts/test_runner_test.py
index 446c43fc..1da6292 100755
--- a/ios/build/bots/scripts/test_runner_test.py
+++ b/ios/build/bots/scripts/test_runner_test.py
@@ -127,6 +127,8 @@
lambda _: 'fake-bundle-id')
self.mock(os.path, 'abspath', lambda path: '/abs/path/to/%s' % path)
self.mock(os.path, 'exists', lambda _: True)
+ self.mock(test_runner.TestRunner, 'set_sigterm_handler',
+ lambda self, handler: 0)
def test_app_not_found(self):
"""Ensures AppNotFoundError is raised."""
@@ -361,9 +363,14 @@
lambda _: 'fake-bundle-id')
self.mock(os.path, 'abspath', lambda path: '/abs/path/to/%s' % path)
self.mock(os.path, 'exists', lambda _: True)
- self.mock(test_runner.SimulatorTestRunner, 'getSimulator', lambda _: 'fake-id')
- self.mock(test_runner.SimulatorTestRunner, 'deleteSimulator', lambda a, b: True)
- self.mock(test_runner.WprProxySimulatorTestRunner, 'copy_trusted_certificate', lambda a, b: True)
+ self.mock(test_runner.TestRunner, 'set_sigterm_handler',
+ lambda self, handler: 0)
+ self.mock(test_runner.SimulatorTestRunner, 'getSimulator',
+ lambda _: 'fake-id')
+ self.mock(test_runner.SimulatorTestRunner, 'deleteSimulator',
+ lambda a, b: True)
+ self.mock(test_runner.WprProxySimulatorTestRunner,
+ 'copy_trusted_certificate', lambda a, b: True)
def test_replay_path_not_found(self):
"""Ensures ReplayPathNotFoundError is raised."""
@@ -466,8 +473,10 @@
'xcode-build',
'out-dir',
)
- self.mock(test_runner.WprProxySimulatorTestRunner, 'wprgo_start', lambda a,b: None)
- self.mock(test_runner.WprProxySimulatorTestRunner, 'wprgo_stop', lambda _: None)
+ self.mock(test_runner.WprProxySimulatorTestRunner, 'wprgo_start',
+ lambda a,b: None)
+ self.mock(test_runner.WprProxySimulatorTestRunner, 'wprgo_stop',
+ lambda _: None)
self.mock(os.path, 'isfile', lambda _: True)
self.mock(glob, 'glob', lambda _: ["file1", "file2"])