| # 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/. |
| |
| from __future__ import print_function |
| |
| import datetime |
| import re |
| import signal |
| import sys |
| import tempfile |
| import time |
| |
| import mozfile |
| |
| from .runner import BaseRunner |
| from ..devices import Emulator |
| |
| class DeviceRunner(BaseRunner): |
| """ |
| The base runner class used for running gecko on |
| remote devices (or emulators), such as B2G. |
| """ |
| env = { 'MOZ_CRASHREPORTER': '1', |
| 'MOZ_CRASHREPORTER_NO_REPORT': '1', |
| 'MOZ_CRASHREPORTER_SHUTDOWN': '1', |
| 'MOZ_HIDE_RESULTS_TABLE': '1', |
| 'NSPR_LOG_MODULES': 'signaling:5,mtransport:5,datachannel:5,jsep:5,MediaPipelineFactory:5', |
| 'R_LOG_LEVEL': '6', |
| 'R_LOG_DESTINATION': 'stderr', |
| 'R_LOG_VERBOSE': '1', |
| 'NO_EM_RESTART': '1', } |
| |
| def __init__(self, device_class, device_args=None, **kwargs): |
| process_log = tempfile.NamedTemporaryFile(suffix='pidlog') |
| # the env will be passed to the device, it is not a *real* env |
| self._device_env = dict(DeviceRunner.env) |
| self._device_env['MOZ_PROCESS_LOG'] = process_log.name |
| # be sure we do not pass env to the parent class ctor |
| env = kwargs.pop('env', None) |
| if env: |
| self._device_env.update(env) |
| |
| process_args = {'stream': sys.stdout, |
| 'processOutputLine': self.on_output, |
| 'onFinish': self.on_finish, |
| 'onTimeout': self.on_timeout } |
| process_args.update(kwargs.get('process_args') or {}) |
| |
| kwargs['process_args'] = process_args |
| BaseRunner.__init__(self, **kwargs) |
| |
| device_args = device_args or {} |
| self.device = device_class(**device_args) |
| |
| @property |
| def command(self): |
| cmd = [self.app_ctx.adb] |
| if self.app_ctx.dm._deviceSerial: |
| cmd.extend(['-s', self.app_ctx.dm._deviceSerial]) |
| cmd.append('shell') |
| for k, v in self._device_env.iteritems(): |
| cmd.append('%s=%s' % (k, v)) |
| cmd.append(self.app_ctx.remote_binary) |
| return cmd |
| |
| def start(self, *args, **kwargs): |
| if isinstance(self.device, Emulator) and not self.device.connected: |
| self.device.start() |
| self.device.connect() |
| self.device.setup_profile(self.profile) |
| |
| # TODO: this doesn't work well when the device is running but dropped |
| # wifi for some reason. It would be good to probe the state of the device |
| # to see if we have the homescreen running, or something, before waiting here |
| self.device.wait_for_net() |
| |
| if not self.device.wait_for_net(): |
| raise Exception("Network did not come up when starting device") |
| |
| BaseRunner.start(self, *args, **kwargs) |
| |
| timeout = 10 # seconds |
| starttime = datetime.datetime.now() |
| while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout): |
| if self.is_running(): |
| break |
| time.sleep(1) |
| else: |
| print("timed out waiting for '%s' process to start" % self.app_ctx.remote_process) |
| |
| if not self.device.wait_for_net(): |
| raise Exception("Failed to get a network connection") |
| |
| def stop(self, sig=None): |
| def _wait_for_shutdown(pid, timeout=10): |
| start_time = datetime.datetime.now() |
| end_time = datetime.timedelta(seconds=timeout) |
| while datetime.datetime.now() - start_time < end_time: |
| if self.is_running() != pid: |
| return True |
| time.sleep(1) |
| return False |
| |
| remote_pid = self.is_running() |
| if remote_pid: |
| self.app_ctx.dm.killProcess( |
| self.app_ctx.remote_process, sig=sig) |
| if not _wait_for_shutdown(remote_pid) and sig is not None: |
| print("timed out waiting for '%s' process to exit, trying " |
| "without signal {}".format( |
| self.app_ctx.remote_process, sig)) |
| |
| # need to call adb stop otherwise the system will attempt to |
| # restart the process |
| remote_pid = self.is_running() or remote_pid |
| self.app_ctx.stop_application() |
| if not _wait_for_shutdown(remote_pid): |
| print("timed out waiting for '%s' process to exit".format( |
| self.app_ctx.remote_process)) |
| |
| def is_running(self): |
| return self.app_ctx.dm.processExist(self.app_ctx.remote_process) |
| |
| def on_output(self, line): |
| match = re.findall(r"TEST-START \| ([^\s]*)", line) |
| if match: |
| self.last_test = match[-1] |
| |
| def on_timeout(self): |
| self.stop(sig=signal.SIGABRT) |
| msg = "DeviceRunner TEST-UNEXPECTED-FAIL | %s | application timed out after %s seconds" |
| if self.timeout: |
| timeout = self.timeout |
| else: |
| timeout = self.output_timeout |
| msg = "%s with no output" % msg |
| |
| print(msg % (self.last_test, timeout)) |
| self.check_for_crashes() |
| |
| def on_finish(self): |
| self.check_for_crashes() |
| |
| def check_for_crashes(self, dump_save_path=None, test_name=None): |
| test_name = test_name or self.last_test |
| dump_dir = self.device.pull_minidumps() |
| crashed = BaseRunner.check_for_crashes( |
| self, |
| dump_directory=dump_dir, |
| dump_save_path=dump_save_path, |
| test_name=test_name) |
| mozfile.remove(dump_dir) |
| return crashed |
| |
| def cleanup(self, *args, **kwargs): |
| BaseRunner.cleanup(self, *args, **kwargs) |
| self.device.cleanup() |