diff options
Diffstat (limited to 'testing/mozbase/mozrunner/mozrunner/application.py')
-rw-r--r-- | testing/mozbase/mozrunner/mozrunner/application.py | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/testing/mozbase/mozrunner/mozrunner/application.py b/testing/mozbase/mozrunner/mozrunner/application.py new file mode 100644 index 000000000..6734487ae --- /dev/null +++ b/testing/mozbase/mozrunner/mozrunner/application.py @@ -0,0 +1,265 @@ +# 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 abc import ABCMeta, abstractmethod +from distutils.spawn import find_executable +import glob +import os +import posixpath + +from mozdevice import DeviceManagerADB, DMError, DroidADB +from mozprofile import ( + Profile, + FirefoxProfile, + MetroFirefoxProfile, + ThunderbirdProfile +) + +here = os.path.abspath(os.path.dirname(__file__)) + + +def get_app_context(appname): + context_map = {'default': DefaultContext, + 'b2g': B2GContext, + 'firefox': FirefoxContext, + 'thunderbird': ThunderbirdContext, + 'metro': MetroContext, + 'fennec': FennecContext} + if appname not in context_map: + raise KeyError("Application '%s' not supported!" % appname) + return context_map[appname] + + +class DefaultContext(object): + profile_class = Profile + + +class RemoteContext(object): + __metaclass__ = ABCMeta + _dm = None + _remote_profile = None + _adb = None + profile_class = Profile + dm_class = DeviceManagerADB + _bindir = None + remote_test_root = '' + remote_process = None + + @property + def bindir(self): + if self._bindir is None: + paths = [find_executable('emulator')] + paths = [p for p in paths if p is not None if os.path.isfile(p)] + if not paths: + self._bindir = '' + else: + self._bindir = os.path.dirname(paths[0]) + return self._bindir + + @property + def adb(self): + if not self._adb: + paths = [os.environ.get('ADB'), + os.environ.get('ADB_PATH'), + self.which('adb')] + paths = [p for p in paths if p is not None if os.path.isfile(p)] + if not paths: + raise OSError( + 'Could not find the adb binary, make sure it is on your' + 'path or set the $ADB_PATH environment variable.') + self._adb = paths[0] + return self._adb + + @property + def dm(self): + if not self._dm: + self._dm = self.dm_class(adbPath=self.adb, autoconnect=False) + return self._dm + + @property + def remote_profile(self): + if not self._remote_profile: + self._remote_profile = posixpath.join(self.remote_test_root, + 'profile') + return self._remote_profile + + def which(self, binary): + paths = os.environ.get('PATH', {}).split(os.pathsep) + if self.bindir is not None and os.path.abspath(self.bindir) not in paths: + paths.insert(0, os.path.abspath(self.bindir)) + os.environ['PATH'] = os.pathsep.join(paths) + + return find_executable(binary) + + @abstractmethod + def stop_application(self): + """ Run (device manager) command to stop application. """ + pass + + +class FennecContext(RemoteContext): + _remote_profiles_ini = None + _remote_test_root = None + + def __init__(self, app=None, adb_path=None, avd_home=None): + self._adb = adb_path + self.avd_home = avd_home + self.dm_class = DroidADB + self.remote_process = app or self.dm._packageName + + def stop_application(self): + self.dm.stopApplication(self.remote_process) + + @property + def remote_test_root(self): + if not self._remote_test_root: + self._remote_test_root = self.dm.getDeviceRoot() + return self._remote_test_root + + @property + def remote_profiles_ini(self): + if not self._remote_profiles_ini: + self._remote_profiles_ini = posixpath.join( + self.dm.getAppRoot(self.remote_process), + 'files', 'mozilla', 'profiles.ini' + ) + return self._remote_profiles_ini + + +class B2GContext(RemoteContext): + _remote_settings_db = None + + def __init__(self, b2g_home=None, adb_path=None): + self.homedir = b2g_home or os.environ.get('B2G_HOME') + + 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 = None + self._fastboot = None + + self.remote_binary = '/system/bin/b2g.sh' + self.remote_bundles_dir = '/system/b2g/distribution/bundles' + self.remote_busybox = '/system/bin/busybox' + self.remote_process = '/system/b2g/b2g' + self.remote_profiles_ini = '/data/b2g/mozilla/profiles.ini' + self.remote_settings_json = '/system/b2g/defaults/settings.json' + self.remote_idb_dir = '/data/local/storage/permanent/chrome/idb' + self.remote_test_root = '/data/local/tests' + self.remote_webapps_dir = '/data/local/webapps' + + self.remote_backup_files = [ + self.remote_settings_json, + self.remote_webapps_dir, + ] + + @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 and self.homedir is not None: + self._update_tools = os.path.join(self.homedir, 'tools', 'update-tools') + return self._update_tools + + @property + def bindir(self): + 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') + paths = glob.glob(path) + if paths: + self._bindir = paths[0] + return self._bindir + + @property + def remote_settings_db(self): + if not self._remote_settings_db: + for filename in self.dm.listFiles(self.remote_idb_dir): + if filename.endswith('ssegtnti.sqlite'): + self._remote_settings_db = posixpath.join(self.remote_idb_dir, filename) + break + else: + raise DMError("Could not find settings db in '%s'!" % self.remote_idb_dir) + return self._remote_settings_db + + def stop_application(self): + self.dm.shellCheckOutput(['stop', 'b2g']) + + def setup_profile(self, profile): + # 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.dm.fileExists(posixpath.join(self.remote_profile, 'settings.json')): + # On devices, settings.json is only read from the profile if + # the system location doesn't exist. + if self.dm.fileExists(self.remote_settings_json): + self.dm.removeFile(self.remote_settings_json) + + # Delete existing settings db and create a new empty one to force new + # settings to be loaded. + self.dm.removeFile(self.remote_settings_db) + self.dm.shellCheckOutput(['touch', self.remote_settings_db]) + + # On devices, the webapps are located in /data/local/webapps instead of the profile. + # In some cases we may need to replace the existing webapps, in others we may just + # need to leave them in the profile. If the system app is present in the profile + # webapps, it's a good indication that they should replace the existing ones wholesale. + profile_webapps = posixpath.join(self.remote_profile, 'webapps') + if self.dm.dirExists(posixpath.join(profile_webapps, 'system.gaiamobile.org')): + self.dm.removeDir(self.remote_webapps_dir) + self.dm.moveTree(profile_webapps, self.remote_webapps_dir) + + # On devices extensions are installed in the system dir + extension_dir = os.path.join(profile.profile, 'extensions', 'staged') + if os.path.isdir(extension_dir): + # Copy the extensions to the B2G bundles dir. + for filename in os.listdir(extension_dir): + path = posixpath.join(self.remote_bundles_dir, filename) + if self.dm.fileExists(path): + self.dm.removeFile(path) + self.dm.pushDir(extension_dir, self.remote_bundles_dir) + + def cleanup_profile(self): + # Delete any bundled extensions + extension_dir = posixpath.join(self.remote_profile, 'extensions', 'staged') + if self.dm.dirExists(extension_dir): + for filename in self.dm.listFiles(extension_dir): + try: + self.dm.removeDir(posixpath.join(self.remote_bundles_dir, filename)) + except DMError: + pass + + if self.dm.fileExists(posixpath.join(self.remote_profile, 'settings.json')): + # Force settings.db to be restored to defaults + self.dm.removeFile(self.remote_settings_db) + self.dm.shellCheckOutput(['touch', self.remote_settings_db]) + + +class FirefoxContext(object): + profile_class = FirefoxProfile + + +class ThunderbirdContext(object): + profile_class = ThunderbirdProfile + + +class MetroContext(object): + profile_class = MetroFirefoxProfile + + def __init__(self, binary=None): + self.binary = binary or os.environ.get('BROWSER_PATH', None) + + def wrap_command(self, command): + immersive_helper_path = os.path.join(os.path.dirname(here), + 'resources', + 'metrotestharness.exe') + command[:0] = [immersive_helper_path, '-firefoxpath'] + return command |