|
@@ -212,465 +212,6 @@ diff --git a/testing/mozbase/mozrunner/mozrunner/application.py b/testing/mozbas
|
|
|
class FirefoxContext(object):
|
|
|
profile_class = FirefoxProfile
|
|
|
|
|
|
-diff --git a/testing/mozbase/mozrunner/mozrunner/base/device.py b/testing/mozbase/mozrunner/mozrunner/base/device.py
|
|
|
---- a/testing/mozbase/mozrunner/mozrunner/base/device.py
|
|
|
-+++ b/testing/mozbase/mozrunner/mozrunner/base/device.py
|
|
|
-@@ -52,18 +52,18 @@ class DeviceRunner(BaseRunner):
|
|
|
- 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])
|
|
|
-+ if self.app_ctx.device_serial:
|
|
|
-+ cmd.extend(['-s', self.app_ctx.device_serial])
|
|
|
- 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, BaseEmulator) and not self.device.connected:
|
|
|
-@@ -82,17 +82,17 @@ class DeviceRunner(BaseRunner):
|
|
|
- pid = 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:
|
|
|
-+ if not self.is_running():
|
|
|
- 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")
|
|
|
- return pid
|
|
|
-
|
|
|
- def stop(self, sig=None):
|
|
|
- def _wait_for_shutdown(pid, timeout=10):
|
|
|
-@@ -101,18 +101,17 @@ class DeviceRunner(BaseRunner):
|
|
|
- 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)
|
|
|
-+ self.app_ctx.device.pkill(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
|
|
|
-@@ -165,18 +164,18 @@ class FennecRunner(DeviceRunner):
|
|
|
-
|
|
|
- def __init__(self, cmdargs=None, **kwargs):
|
|
|
- super(FennecRunner, self).__init__(**kwargs)
|
|
|
- self.cmdargs = cmdargs or []
|
|
|
-
|
|
|
- @property
|
|
|
- def command(self):
|
|
|
- cmd = [self.app_ctx.adb]
|
|
|
-- if self.app_ctx.dm._deviceSerial:
|
|
|
-- cmd.extend(["-s", self.app_ctx.dm._deviceSerial])
|
|
|
-+ if self.app_ctx.device_serial:
|
|
|
-+ cmd.extend(["-s", self.app_ctx.device_serial])
|
|
|
- cmd.append("shell")
|
|
|
- app = "%s/org.mozilla.gecko.BrowserApp" % self.app_ctx.remote_process
|
|
|
- am_subcommand = ["am", "start", "-a", "android.activity.MAIN", "-n", app]
|
|
|
- app_params = ["-no-remote", "-profile", self.app_ctx.remote_profile]
|
|
|
- app_params.extend(self.cmdargs)
|
|
|
- am_subcommand.extend(["--es", "args", "'%s'" % " ".join(app_params)])
|
|
|
- # Append env variables in the form |--es env0 MOZ_CRASHREPORTER=1|
|
|
|
- for (count, (k, v)) in enumerate(self._device_env.iteritems()):
|
|
|
-diff --git a/testing/mozbase/mozrunner/mozrunner/base/device.py.1440714.later b/testing/mozbase/mozrunner/mozrunner/base/device.py.1440714.later
|
|
|
-new file mode 100644
|
|
|
---- /dev/null
|
|
|
-+++ b/testing/mozbase/mozrunner/mozrunner/base/device.py.1440714.later
|
|
|
-@@ -0,0 +1,26 @@
|
|
|
-+--- device.py
|
|
|
-++++ device.py
|
|
|
-+@@ -111,20 +111,20 @@ class DeviceRunner(BaseRunner):
|
|
|
-+
|
|
|
-+ @property
|
|
|
-+ def returncode(self):
|
|
|
-+ """The returncode of the remote process.
|
|
|
-+
|
|
|
-+ A value of None indicates the process is still running. Otherwise 0 is
|
|
|
-+ returned, because there is no known way yet to retrieve the real exit code.
|
|
|
-+ """
|
|
|
-+- if self.app_ctx.dm.processExist(self.app_ctx.remote_process) is None:
|
|
|
-+- return 0
|
|
|
-++ if self.app_ctx.device.process_exist(self.app_ctx.remote_process):
|
|
|
-++ return None
|
|
|
-+
|
|
|
-+- return None
|
|
|
-++ return 0
|
|
|
-+
|
|
|
-+ def wait(self, timeout=None):
|
|
|
-+ """Wait for the remote process to exit.
|
|
|
-+
|
|
|
-+ :param timeout: if not None, will return after timeout seconds.
|
|
|
-+
|
|
|
-+ :returns: the process return code or None if timeout was reached
|
|
|
-+ and the process is still running.
|
|
|
-diff --git a/testing/mozbase/mozrunner/mozrunner/devices/base.py b/testing/mozbase/mozrunner/mozrunner/devices/base.py
|
|
|
---- a/testing/mozbase/mozrunner/mozrunner/devices/base.py
|
|
|
-+++ b/testing/mozbase/mozrunner/mozrunner/devices/base.py
|
|
|
-@@ -9,44 +9,44 @@ import datetime
|
|
|
- import os
|
|
|
- import posixpath
|
|
|
- import re
|
|
|
- import shutil
|
|
|
- import subprocess
|
|
|
- import tempfile
|
|
|
- import time
|
|
|
-
|
|
|
--from mozdevice import DMError
|
|
|
-+from mozdevice import ADBHost, ADBError
|
|
|
- from mozprocess import ProcessHandler
|
|
|
-
|
|
|
-
|
|
|
- class Device(object):
|
|
|
- connected = False
|
|
|
- logcat_proc = None
|
|
|
-
|
|
|
- def __init__(self, app_ctx, logdir=None, serial=None, restore=True):
|
|
|
- self.app_ctx = app_ctx
|
|
|
-- self.dm = self.app_ctx.dm
|
|
|
-+ self.device = self.app_ctx.device
|
|
|
- self.restore = restore
|
|
|
- self.serial = serial
|
|
|
- self.logdir = os.path.abspath(os.path.expanduser(logdir))
|
|
|
- self.added_files = set()
|
|
|
- self.backup_files = set()
|
|
|
-
|
|
|
- @property
|
|
|
- def remote_profiles(self):
|
|
|
- """
|
|
|
- A list of remote profiles on the device.
|
|
|
- """
|
|
|
- remote_ini = self.app_ctx.remote_profiles_ini
|
|
|
-- if not self.dm.fileExists(remote_ini):
|
|
|
-+ if not self.device.is_file(remote_ini):
|
|
|
- raise IOError("Remote file '%s' not found" % remote_ini)
|
|
|
-
|
|
|
- local_ini = tempfile.NamedTemporaryFile()
|
|
|
-- self.dm.getFile(remote_ini, local_ini.name)
|
|
|
-+ self.device.pull(remote_ini, local_ini.name)
|
|
|
- cfg = ConfigParser()
|
|
|
- cfg.read(local_ini.name)
|
|
|
-
|
|
|
- profiles = []
|
|
|
- for section in cfg.sections():
|
|
|
- if cfg.has_option(section, 'Path'):
|
|
|
- if cfg.has_option(section, 'IsRelative') and cfg.getint(section, 'IsRelative'):
|
|
|
- profiles.append(posixpath.join(posixpath.dirname(remote_ini),
|
|
|
-@@ -58,100 +58,102 @@ class Device(object):
|
|
|
- def pull_minidumps(self):
|
|
|
- """
|
|
|
- Saves any minidumps found in the remote profile on the local filesystem.
|
|
|
-
|
|
|
- :returns: Path to directory containing the dumps.
|
|
|
- """
|
|
|
- remote_dump_dir = posixpath.join(self.app_ctx.remote_profile, 'minidumps')
|
|
|
- local_dump_dir = tempfile.mkdtemp()
|
|
|
-- self.dm.getDirectory(remote_dump_dir, local_dump_dir)
|
|
|
-+ try:
|
|
|
-+ self.device.pull(remote_dump_dir, local_dump_dir)
|
|
|
-+ except ADBError as e:
|
|
|
-+ # OK if directory not present -- sometimes called before browser start
|
|
|
-+ if 'does not exist' not in str(e):
|
|
|
-+ raise
|
|
|
- if os.listdir(local_dump_dir):
|
|
|
-- for f in self.dm.listFiles(remote_dump_dir):
|
|
|
-- self.dm.removeFile(posixpath.join(remote_dump_dir, f))
|
|
|
-+ self.device.rm(remote_dump_dir, recursive=True)
|
|
|
- return local_dump_dir
|
|
|
-
|
|
|
- def setup_profile(self, profile):
|
|
|
- """
|
|
|
- Copy profile to the device and update the remote profiles.ini
|
|
|
- to point to the new profile.
|
|
|
-
|
|
|
- :param profile: mozprofile object to copy over.
|
|
|
- """
|
|
|
-- self.dm.remount()
|
|
|
-+ if self.device.is_dir(self.app_ctx.remote_profile):
|
|
|
-+ self.device.rm(self.app_ctx.remote_profile, recursive=True)
|
|
|
-
|
|
|
-- if self.dm.dirExists(self.app_ctx.remote_profile):
|
|
|
-- self.dm.shellCheckOutput(['rm', '-r', self.app_ctx.remote_profile])
|
|
|
--
|
|
|
-- self.dm.pushDir(profile.profile, self.app_ctx.remote_profile)
|
|
|
-+ self.device.push(profile.profile, self.app_ctx.remote_profile)
|
|
|
-
|
|
|
- timeout = 5 # seconds
|
|
|
- starttime = datetime.datetime.now()
|
|
|
- while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
|
|
|
-- if self.dm.fileExists(self.app_ctx.remote_profiles_ini):
|
|
|
-+ if self.device.is_file(self.app_ctx.remote_profiles_ini):
|
|
|
- break
|
|
|
- time.sleep(1)
|
|
|
-- else:
|
|
|
-+ local_profiles_ini = tempfile.NamedTemporaryFile()
|
|
|
-+ if not self.device.is_file(self.app_ctx.remote_profiles_ini):
|
|
|
-+ # Unless fennec is already running, and/or remote_profiles_ini is
|
|
|
-+ # not inside the remote_profile (deleted above), this is entirely
|
|
|
-+ # normal.
|
|
|
- print("timed out waiting for profiles.ini")
|
|
|
--
|
|
|
-- local_profiles_ini = tempfile.NamedTemporaryFile()
|
|
|
-- self.dm.getFile(self.app_ctx.remote_profiles_ini, local_profiles_ini.name)
|
|
|
-+ else:
|
|
|
-+ self.device.pull(self.app_ctx.remote_profiles_ini, local_profiles_ini.name)
|
|
|
-
|
|
|
- config = ProfileConfigParser()
|
|
|
- config.read(local_profiles_ini.name)
|
|
|
- for section in config.sections():
|
|
|
- if 'Profile' in section:
|
|
|
- config.set(section, 'IsRelative', 0)
|
|
|
- config.set(section, 'Path', self.app_ctx.remote_profile)
|
|
|
-
|
|
|
- new_profiles_ini = tempfile.NamedTemporaryFile()
|
|
|
- config.write(open(new_profiles_ini.name, 'w'))
|
|
|
-
|
|
|
- self.backup_file(self.app_ctx.remote_profiles_ini)
|
|
|
-- self.dm.pushFile(new_profiles_ini.name, self.app_ctx.remote_profiles_ini)
|
|
|
-+ self.device.push(new_profiles_ini.name, self.app_ctx.remote_profiles_ini)
|
|
|
-
|
|
|
- # Ideally all applications would read the profile the same way, but in practice
|
|
|
- # this isn't true. Perform application specific profile-related setup if necessary.
|
|
|
- if hasattr(self.app_ctx, 'setup_profile'):
|
|
|
- for remote_path in self.app_ctx.remote_backup_files:
|
|
|
- self.backup_file(remote_path)
|
|
|
- self.app_ctx.setup_profile(profile)
|
|
|
-
|
|
|
- def _get_online_devices(self):
|
|
|
-- return [d[0] for d in self.dm.devices()
|
|
|
-- if d[1] != 'offline'
|
|
|
-- if not d[0].startswith('emulator')]
|
|
|
-+ adbhost = ADBHost()
|
|
|
-+ devices = adbhost.devices()
|
|
|
-+ return [d['device_serial'] for d in devices
|
|
|
-+ if d['state'] != 'offline'
|
|
|
-+ if not d['device_serial'].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.connected:
|
|
|
- return
|
|
|
-
|
|
|
-- if self.serial:
|
|
|
-- serial = self.serial
|
|
|
-- else:
|
|
|
-- online_devices = self._get_online_devices()
|
|
|
-- if not online_devices:
|
|
|
-- raise IOError("No devices connected. Ensure the device is on and "
|
|
|
-- "remote debugging via adb is enabled in the settings.")
|
|
|
-- serial = online_devices[0]
|
|
|
-+ online_devices = self._get_online_devices()
|
|
|
-+ if not online_devices:
|
|
|
-+ raise IOError("No devices connected. Ensure the device is on and "
|
|
|
-+ "remote debugging via adb is enabled in the settings.")
|
|
|
-+ self.serial = online_devices[0]
|
|
|
-
|
|
|
-- self.dm._deviceSerial = serial
|
|
|
-- self.dm.connect()
|
|
|
- self.connected = True
|
|
|
-
|
|
|
- if self.logdir:
|
|
|
- # save logcat
|
|
|
-- logcat_log = os.path.join(self.logdir, '%s.log' % serial)
|
|
|
-+ logcat_log = os.path.join(self.logdir, '%s.log' % self.serial)
|
|
|
- if os.path.isfile(logcat_log):
|
|
|
- self._rotate_log(logcat_log)
|
|
|
-- self.logcat_proc = self.start_logcat(serial, logfile=logcat_log)
|
|
|
-+ self.logcat_proc = self.start_logcat(self.serial, logfile=logcat_log)
|
|
|
-
|
|
|
- def start_logcat(self, serial, logfile=None, stream=None, filterspec=None):
|
|
|
- logcat_args = [self.app_ctx.adb, '-s', '%s' % serial,
|
|
|
- 'logcat', '-v', 'time', '-b', 'main', '-b', 'radio']
|
|
|
- # only log filterspec
|
|
|
- if filterspec:
|
|
|
- logcat_args.extend(['-s', filterspec])
|
|
|
- process_args = {}
|
|
|
-@@ -162,35 +164,17 @@ class Device(object):
|
|
|
- proc = ProcessHandler(logcat_args, **process_args)
|
|
|
- proc.run()
|
|
|
- return proc
|
|
|
-
|
|
|
- def reboot(self):
|
|
|
- """
|
|
|
- Reboots the device via adb.
|
|
|
- """
|
|
|
-- self.dm.reboot(wait=True)
|
|
|
--
|
|
|
-- def install_busybox(self, busybox):
|
|
|
-- """
|
|
|
-- Installs busybox on the device.
|
|
|
--
|
|
|
-- :param busybox: Path to busybox binary to install.
|
|
|
-- """
|
|
|
-- self.dm.remount()
|
|
|
-- print('pushing %s' % self.app_ctx.remote_busybox)
|
|
|
-- self.dm.pushFile(busybox, self.app_ctx.remote_busybox, retryLimit=10)
|
|
|
-- # TODO for some reason using dm.shellCheckOutput doesn't work,
|
|
|
-- # while calling adb shell directly does.
|
|
|
-- args = [self.app_ctx.adb, '-s', self.dm._deviceSerial,
|
|
|
-- 'shell', 'cd /system/bin; chmod 555 busybox;'
|
|
|
-- 'for x in `./busybox --list`; do ln -s ./busybox $x; done']
|
|
|
-- adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
-- adb.wait()
|
|
|
-- self.dm._verifyZip()
|
|
|
-+ self.device.reboot()
|
|
|
-
|
|
|
- def wait_for_net(self):
|
|
|
- active = False
|
|
|
- time_out = 0
|
|
|
- while not active and time_out < 40:
|
|
|
- proc = subprocess.Popen([self.app_ctx.adb, 'shell', '/system/bin/netcfg'],
|
|
|
- stdout=subprocess.PIPE)
|
|
|
- proc.stdout.readline() # ignore first line
|
|
|
-@@ -203,50 +187,47 @@ class Device(object):
|
|
|
- time_out += 1
|
|
|
- time.sleep(1)
|
|
|
- return active
|
|
|
-
|
|
|
- def backup_file(self, remote_path):
|
|
|
- if not self.restore:
|
|
|
- return
|
|
|
-
|
|
|
-- if self.dm.fileExists(remote_path) or self.dm.dirExists(remote_path):
|
|
|
-- self.dm.copyTree(remote_path, '%s.orig' % remote_path)
|
|
|
-+ if self.device.exists(remote_path):
|
|
|
-+ self.device.cp(remote_path, '%s.orig' % remote_path, recursive=True)
|
|
|
- self.backup_files.add(remote_path)
|
|
|
- else:
|
|
|
- self.added_files.add(remote_path)
|
|
|
-
|
|
|
- def cleanup(self):
|
|
|
- """
|
|
|
- Cleanup the device.
|
|
|
- """
|
|
|
- if not self.restore:
|
|
|
- return
|
|
|
-
|
|
|
- try:
|
|
|
-- self.dm._verifyDevice()
|
|
|
-- except DMError:
|
|
|
-- return
|
|
|
-+ self.device.remount()
|
|
|
-+ # Restore the original profile
|
|
|
-+ for added_file in self.added_files:
|
|
|
-+ self.device.rm(added_file)
|
|
|
-
|
|
|
-- self.dm.remount()
|
|
|
-- # Restore the original profile
|
|
|
-- for added_file in self.added_files:
|
|
|
-- self.dm.removeFile(added_file)
|
|
|
-+ for backup_file in self.backup_files:
|
|
|
-+ if self.device.exists('%s.orig' % backup_file):
|
|
|
-+ self.device.mv('%s.orig' % backup_file, backup_file)
|
|
|
-
|
|
|
-- for backup_file in self.backup_files:
|
|
|
-- if self.dm.fileExists('%s.orig' % backup_file) or \
|
|
|
-- self.dm.dirExists('%s.orig' % backup_file):
|
|
|
-- self.dm.moveTree('%s.orig' % backup_file, backup_file)
|
|
|
-+ # Perform application specific profile cleanup if necessary
|
|
|
-+ if hasattr(self.app_ctx, 'cleanup_profile'):
|
|
|
-+ self.app_ctx.cleanup_profile()
|
|
|
-
|
|
|
-- # Perform application specific profile cleanup if necessary
|
|
|
-- if hasattr(self.app_ctx, 'cleanup_profile'):
|
|
|
-- self.app_ctx.cleanup_profile()
|
|
|
--
|
|
|
-- # Remove the test profile
|
|
|
-- self.dm.removeDir(self.app_ctx.remote_profile)
|
|
|
-+ # Remove the test profile
|
|
|
-+ self.device.rm(self.app_ctx.remote_profile, recursive=True)
|
|
|
-+ except Exception as e:
|
|
|
-+ print("cleanup aborted: %s" % str(e))
|
|
|
-
|
|
|
- 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')]
|
|
|
-diff --git a/testing/mozbase/mozrunner/mozrunner/devices/emulator.py b/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
|
|
|
---- a/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
|
|
|
-+++ b/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
|
|
|
-@@ -7,16 +7,17 @@ from __future__ import absolute_import
|
|
|
- from telnetlib import Telnet
|
|
|
- import datetime
|
|
|
- import os
|
|
|
- import shutil
|
|
|
- import subprocess
|
|
|
- import tempfile
|
|
|
- import time
|
|
|
-
|
|
|
-+from mozdevice import ADBHost
|
|
|
- from mozprocess import ProcessHandler
|
|
|
-
|
|
|
- from .base import Device
|
|
|
- from .emulator_battery import EmulatorBattery
|
|
|
- from .emulator_geo import EmulatorGeo
|
|
|
- from .emulator_screen import EmulatorScreen
|
|
|
- from ..errors import TimeoutException
|
|
|
-
|
|
|
-@@ -136,30 +137,30 @@ class BaseEmulator(Device):
|
|
|
- raise TimeoutException(
|
|
|
- 'timed out waiting for emulator to start')
|
|
|
- devices = set(self._get_online_devices())
|
|
|
- devices = devices - original_devices
|
|
|
- self.serial = devices.pop()
|
|
|
- self.connect()
|
|
|
-
|
|
|
- def _get_online_devices(self):
|
|
|
-- return [d[0] for d in self.dm.devices() if d[1] != 'offline' if
|
|
|
-- d[0].startswith('emulator')]
|
|
|
-+ adbhost = ADBHost()
|
|
|
-+ return [d['device_serial'] for d in adbhost.devices() if d['state'] != 'offline' if
|
|
|
-+ d['device_serial'].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.connected:
|
|
|
- return
|
|
|
-
|
|
|
- super(BaseEmulator, self).connect()
|
|
|
-- serial = self.serial or self.dm._deviceSerial
|
|
|
-- self.port = int(serial[serial.rindex('-') + 1:])
|
|
|
-+ self.port = int(self.serial[self.serial.rindex('-') + 1:])
|
|
|
-
|
|
|
- def cleanup(self):
|
|
|
- """
|
|
|
- Cleans up and kills the emulator, if it was started by mozrunner.
|
|
|
- """
|
|
|
- super(BaseEmulator, self).cleanup()
|
|
|
- if self.proc:
|
|
|
- self.proc.kill()
|
|
|
diff --git a/testing/mozbase/mozrunner/mozrunner/runners.py b/testing/mozbase/mozrunner/mozrunner/runners.py
|
|
|
--- a/testing/mozbase/mozrunner/mozrunner/runners.py
|
|
|
+++ b/testing/mozbase/mozrunner/mozrunner/runners.py
|