blob: 8a01ffff85129f6adc7e43aed6e475ac5d29c26a [file] [log] [blame]
# 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()