Skip to content

Commit

Permalink
Experimental changes for making mozrunner work with devices
Browse files Browse the repository at this point in the history
  • Loading branch information
jgraham committed Jun 24, 2014
1 parent 0c3673b commit dccdbcf
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 65 deletions.
48 changes: 34 additions & 14 deletions testing/mozbase/mozrunner/mozrunner/application.py
Expand Up @@ -41,15 +41,13 @@ class B2GContext(object):

def __init__(self, b2g_home=None, adb_path=None):
self.homedir = b2g_home or os.environ.get('B2G_HOME')
if not self.homedir:
raise EnvironmentError('Must define B2G_HOME or pass the b2g_home parameter')

if not os.path.isdir(self.homedir):
if self.homedir is not None and not os.path.isdir(self.homedir):
raise OSError('Homedir \'%s\' does not exist!' % self.homedir)

self._adb = adb_path
self.update_tools = os.path.join(self.homedir, 'tools', 'update-tools')
self.fastboot = self.which('fastboot')
self._update_tools = None
self._fastboot = None

self.remote_binary = '/system/bin/b2g.sh'
self.remote_process = '/system/b2g/b2g'
Expand All @@ -58,6 +56,18 @@ def __init__(self, b2g_home=None, adb_path=None):
self.remote_profiles_ini = '/data/b2g/mozilla/profiles.ini'
self.remote_test_root = '/data/local/tests'

@property
def fastboot(self):
if self._fastboot is None:
self._fastboot = self.which('fastboot')
return self._fastboot

@property
def update_tools(self):
if self._update_tools is None:
self._update_tools = os.path.join(self.homedir, 'tools', 'update-tools')
return self._update_tools

@property
def adb(self):
if not self._adb:
Expand All @@ -73,7 +83,7 @@ def adb(self):

@property
def bindir(self):
if not self._bindir:
if self._bindir is None and self.homedir is not None:
# TODO get this via build configuration
path = os.path.join(self.homedir, 'out', 'host', '*', 'bin')
self._bindir = glob.glob(path)[0]
Expand All @@ -93,18 +103,28 @@ def remote_profile(self):


def which(self, binary):
if self.bindir not in sys.path:
if self.bindir is not None and self.bindir not in sys.path:
sys.path.insert(0, self.bindir)

return find_executable(binary, os.pathsep.join(sys.path))
rv = find_executable(binary, os.pathsep.join(sys.path))
if not rv:
rv = find_executable(binary, os.environ.get("PATH"))
return rv

def stop_application(self):
self.dm.shellCheckOutput(['stop', 'b2g'])

# For some reason user.js in the profile doesn't get picked up.
# Manually copy it over to prefs.js. See bug 1009730 for more details.
self.dm.moveTree(posixpath.join(self.remote_profile, 'user.js'),
posixpath.join(self.remote_profile, 'prefs.js'))
if self.bindir is None:
# Assume this is a real device
print("Restarting device")
self.dm.reboot(wait=True)
import subprocess
print(subprocess.call(["adb", "wait-for-device"]))
else:
self.dm.shellCheckOutput(['stop', 'b2g'])

# For some reason user.js in the profile doesn't get picked up.
# Manually copy it over to prefs.js. See bug 1009730 for more details.
self.dm.moveTree(posixpath.join(self.remote_profile, 'user.js'),
posixpath.join(self.remote_profile, 'prefs.js'))


class FirefoxContext(object):
Expand Down
4 changes: 2 additions & 2 deletions testing/mozbase/mozrunner/mozrunner/base/device.py
Expand Up @@ -60,8 +60,8 @@ def command(self):
return cmd

def start(self, *args, **kwargs):
if not self.device.proc:
self.device.start()
self.device.connect()
print("Installing device profile")
self.device.setup_profile(self.profile)
self.app_ctx.stop_application()

Expand Down
51 changes: 50 additions & 1 deletion testing/mozbase/mozrunner/mozrunner/devices/base.py
Expand Up @@ -5,19 +5,23 @@
import datetime
import os
import posixpath
import shutil
import socket
import subprocess
import tempfile
import time
import traceback

from mozdevice import DMError
from mozprocess import ProcessHandler

class Device(object):
def __init__(self, app_ctx, restore=True):
def __init__(self, app_ctx, logdir=None, serial=None, restore=True):
self.app_ctx = app_ctx
self.dm = self.app_ctx.dm
self.restore = restore
self.serial = serial
self.logdir = logdir
self.added_files = set()
self.backup_files = set()

Expand Down Expand Up @@ -106,6 +110,31 @@ def setup_profile(self, profile):
self.backup_file(self.app_ctx.remote_profiles_ini)
self.dm.pushFile(new_profiles_ini.name, self.app_ctx.remote_profiles_ini)

def _get_online_devices(self):
return [d[0] for d in self.dm.devices() if d[1] != 'offline' if not d[0].startswith('emulator')]

def connect(self):
"""
Connects to a running device. If no serial was specified in the
constructor, defaults to the first entry in `adb devices`.
"""
if self.dm.connected:
return

serial = self.serial or self._get_online_devices()[0]
self.dm._deviceSerial = serial
self.dm.connect()

if self.logdir:
# save logcat
logcat_log = os.path.join(self.logdir, '%s.log' % serial)
if os.path.isfile(logcat_log):
self._rotate_log(logcat_log)
logcat_args = [self.app_ctx.adb, '-s', '%s' % serial,
'logcat', '-v', 'threadtime']
self.logcat_proc = ProcessHandler(logcat_args, logfile=logcat_log)
self.logcat_proc.run()

def install_busybox(self, busybox):
"""
Installs busybox on the device.
Expand Down Expand Up @@ -196,6 +225,26 @@ def cleanup(self):
# Remove the test profile
self.dm.removeDir(self.app_ctx.remote_profile)

def _rotate_log(self, srclog, index=1):
"""
Rotate a logfile, by recursively rotating logs further in the sequence,
deleting the last file if necessary.
"""
basename = os.path.basename(srclog)
basename = basename[:-len('.log')]
if index > 1:
basename = basename[:-len('.1')]
basename = '%s.%d.log' % (basename, index)

destlog = os.path.join(self.logdir, basename)
if os.path.isfile(destlog):
if index == 3:
os.remove(destlog)
else:
self._rotate_log(destlog, index+1)
shutil.move(srclog, destlog)



class ProfileConfigParser(RawConfigParser):
"""
Expand Down
68 changes: 21 additions & 47 deletions testing/mozbase/mozrunner/mozrunner/devices/emulator.py
Expand Up @@ -45,8 +45,8 @@ class Emulator(Device):
telnet = None

def __init__(self, app_ctx, arch, resolution=None, sdcard=None, userdata=None,
logdir=None, no_window=None, binary=None):
Device.__init__(self, app_ctx)
no_window=None, binary=None, **kwargs):
Device.__init__(self, app_ctx, **kwargs)

self.arch = ArchContext(arch, self.app_ctx, binary=binary)
self.resolution = resolution or '320x480'
Expand All @@ -57,7 +57,6 @@ def __init__(self, app_ctx, arch, resolution=None, sdcard=None, userdata=None,
if userdata:
self.userdata = tempfile.NamedTemporaryFile(prefix='qemu-userdata')
shutil.copyfile(userdata, self.userdata)
self.logdir = logdir
self.no_window = no_window

self.battery = EmulatorBattery(self)
Expand Down Expand Up @@ -85,14 +84,14 @@ def args(self):
'-qemu'] + self.arch.extra_args)
return qemu_args

def _get_online_devices(self):
return set([d[0] for d in self.dm.devices() if d[1] != 'offline'])

def start(self):
"""
Starts a new emulator.
"""
original_devices = self._get_online_devices()
if self.proc:
return

original_devices = set(self._get_online_devices())

qemu_log = None
qemu_proc_args = {}
Expand All @@ -107,39 +106,33 @@ def start(self):
self.proc = ProcessHandler(self.args, **qemu_proc_args)
self.proc.run()

devices = self._get_online_devices()
devices = set(self._get_online_devices())
now = datetime.datetime.now()
while (devices - original_devices) == set([]):
time.sleep(1)
if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
raise TimeoutException('timed out waiting for emulator to start')
devices = self._get_online_devices()
self.connect(devices - original_devices)
devices = set(self._get_online_devices())
devices = devices - original_devices
self.serial = devices.pop()

def connect(self, devices=None):
def _get_online_devices(self):
return set([d[0] for d in self.dm.devices() if d[1] != 'offline' if d[0].startswith('emulator')])

def connect(self):
"""
Connects to an already running emulator.
Connects to an already running device. If no serial was specified in the
constructor, creates a new emulator.
"""
devices = list(devices or self._get_online_devices())
serial = [d for d in devices if d.startswith('emulator')][0]
self.dm._deviceSerial = serial
self.dm.connect()
self.port = int(serial[serial.rindex('-')+1:])
if not self.serial:
self.start()

Device.connect(self)

self.port = int(self.serial[self.serial.rindex('-')+1:])
self.geo.set_default_location()
self.screen.initialize()

print self.logdir
if self.logdir:
# save logcat
logcat_log = os.path.join(self.logdir, '%s.log' % serial)
if os.path.isfile(logcat_log):
self._rotate_log(logcat_log)
logcat_args = [self.app_ctx.adb, '-s', '%s' % serial,
'logcat', '-v', 'threadtime']
self.logcat_proc = ProcessHandler(logcat_args, logfile=logcat_log)
self.logcat_proc.run()

# setup DNS fix for networking
self.app_ctx.dm.shellCheckOutput(['setprop', 'net.dns1', '10.0.2.3'])

Expand Down Expand Up @@ -172,25 +165,6 @@ def cleanup(self):
if self.sdcard and os.path.isfile(self.sdcard):
os.remove(self.sdcard)

def _rotate_log(self, srclog, index=1):
"""
Rotate a logfile, by recursively rotating logs further in the sequence,
deleting the last file if necessary.
"""
basename = os.path.basename(srclog)
basename = basename[:-len('.log')]
if index > 1:
basename = basename[:-len('.1')]
basename = '%s.%d.log' % (basename, index)

destlog = os.path.join(self.logdir, basename)
if os.path.isfile(destlog):
if index == 3:
os.remove(destlog)
else:
self._rotate_log(destlog, index+1)
shutil.move(srclog, destlog)

# TODO this function is B2G specific and shouldn't live here
@uses_marionette
def wait_for_system_message(self, marionette):
Expand Down
30 changes: 29 additions & 1 deletion testing/mozbase/mozrunner/mozrunner/runners.py
Expand Up @@ -9,7 +9,7 @@

from .application import get_app_context
from .base import DeviceRunner, GeckoRuntimeRunner
from .devices import Emulator
from .devices import Emulator, Device


def Runner(*args, **kwargs):
Expand Down Expand Up @@ -143,11 +143,39 @@ def B2GEmulatorRunner(arch='arm',
device_args=device_args,
**kwargs)

def B2GDeviceRunner(b2g_home=None,
adb_path=None,
logdir=None,
serial=None,
**kwargs):
"""
Create a B2G device runner.
:param b2g_home: Path to root B2G repository.
:param logdir: Path to save logfiles such as logcat.
:param serial: Serial of device to connect to as seen in `adb devices`.
:param profile: Profile object to use.
:param env: Environment variables to pass into the b2g.sh process.
:param clean_profile: If True, restores profile back to original state.
:param process_class: Class used to launch the b2g.sh process.
:param process_args: Arguments to pass into the b2g.sh process.
:param symbols_path: Path to symbol files used for crash analysis.
:returns: A DeviceRunner for B2G devices.
"""
kwargs['app_ctx'] = get_app_context('b2g')(b2g_home, adb_path=adb_path)
device_args = { 'app_ctx': kwargs['app_ctx'],
'logdir': logdir,
'serial': serial }
return DeviceRunner(device_class=Device,
device_args=device_args,
**kwargs)


runners = {
'default': Runner,
'b2g_desktop': B2GDesktopRunner,
'b2g_emulator': B2GEmulatorRunner,
'b2g_device': B2GDeviceRunner,
'firefox': FirefoxRunner,
'metro': MetroRunner,
'thunderbird': ThunderbirdRunner,
Expand Down

0 comments on commit dccdbcf

Please sign in to comment.