diff --git a/tools/testrunner/local/command.py b/tools/testrunner/local/command.py index af0981e83d4b..1ee8efdccf48 100644 --- a/tools/testrunner/local/command.py +++ b/tools/testrunner/local/command.py @@ -231,6 +231,54 @@ def taskkill_windows(process, verbose=False, force=True): logging.info('Return code: %d', tk.returncode) +class IOSCommand(BaseCommand): + + def execute(self): + if self.verbose: + print('# %s' % self) + + process = self._start_process() + + with handle_sigterm(process, self._abort, self.handle_sigterm): + # Variable to communicate with the timer. + timeout_occured = [False] + timer = threading.Timer(self.timeout, self._abort, + [process, timeout_occured]) + timer.start() + + start_time = time.time() + stdout, stderr = process.communicate() + duration = time.time() - start_time + + timer.cancel() + + # TODO(crbug.com/1445694): if iossim returns with code 65, force a + # successful exit instead. + if (process.returncode == 65): + process.returncode = 0 + + return output.Output(process.returncode, timeout_occured[0], + stdout.decode('utf-8', 'replace'), + stderr.decode('utf-8', 'replace'), process.pid, + duration) + + def _start_process(self): + try: + return subprocess.Popen( + args=self._get_popen_args(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=self._get_env(), + shell=True, + # Make the new shell create its own process group. This allows to kill + # all spawned processes reliably (https://crbug.com/v8/8292). + preexec_fn=os.setsid, + ) + except Exception as e: + sys.stderr.write('Error executing: %s\n' % self) + raise e + + class WindowsCommand(BaseCommand): def _start_process(self, **kwargs): # Try to change the error mode to avoid dialogs on fatal errors. Don't @@ -353,6 +401,8 @@ def setup(target_os, device): if target_os == 'android': AndroidCommand.driver = Driver.instance(device) Command = AndroidCommand + elif target_os == 'ios': + Command = IOSCommand elif target_os == 'windows': Command = WindowsCommand else: diff --git a/tools/testrunner/local/context.py b/tools/testrunner/local/context.py index 81a4d1bbe718..6c3b03ab01ec 100644 --- a/tools/testrunner/local/context.py +++ b/tools/testrunner/local/context.py @@ -10,7 +10,7 @@ import sys from ..local.android import Driver -from .command import AndroidCommand, PosixCommand, WindowsCommand, taskkill_windows +from .command import AndroidCommand, IOSCommand, PosixCommand, WindowsCommand, taskkill_windows from .pool import DefaultExecutionPool from ..testproc.util import list_processes_linux @@ -67,11 +67,20 @@ def handle_context(self, options): AndroidCommand.driver.tear_down() +class IOSContext(DefaultOSContext): + + def __init__(self): + super().__init__(IOSCommand) + + def terminate_process(self, process): + os.kill(process.pid, signal.SIGTERM) + # TODO(liviurau): Add documentation with diagrams to describe how context and # its components gets initialized and eventually teared down and how does it # interact with both tests and underlying platform specific concerns. def find_os_context_factory(target_os): - registry = dict(android=AndroidOSContext, windows=WindowsContext) + registry = dict( + android=AndroidOSContext, ios=IOSContext, windows=WindowsContext) default = LinuxContext return registry.get(target_os, default)