Bug 1036982 - Mozrunner should setup settings.json and webapps from profile properly with B2G emulators/devices, r=jgriffin
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Thu, 17 Jul 2014 11:40:24 -0400
changeset 216562 162358e77cc05ba0be1acea4b3287a1792bc79f7
parent 216561 361c12a206fb69ceaf73b07d847f9fd0934e060b
child 216563 59e1f31ca0e622bd5174cf34381edf949819efec
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgriffin
bugs1036982
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1036982 - Mozrunner should setup settings.json and webapps from profile properly with B2G emulators/devices, r=jgriffin
testing/mozbase/mozrunner/mozrunner/application.py
testing/mozbase/mozrunner/mozrunner/devices/base.py
--- a/testing/mozbase/mozrunner/mozrunner/application.py
+++ b/testing/mozbase/mozrunner/mozrunner/application.py
@@ -2,17 +2,17 @@
 # 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 distutils.spawn import find_executable
 import glob
 import os
 import posixpath
 
-from mozdevice import DeviceManagerADB
+from mozdevice import DeviceManagerADB, DMError
 from mozprofile import (
     Profile,
     FirefoxProfile,
     MetroFirefoxProfile,
     ThunderbirdProfile
 )
 
 here = os.path.abspath(os.path.dirname(__file__))
@@ -31,34 +31,43 @@ def get_app_context(appname):
 class DefaultContext(object):
     profile_class = Profile
 
 
 class B2GContext(object):
     _bindir = None
     _dm = None
     _remote_profile = None
+    _remote_settings_db = None
     profile_class = Profile
 
     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_process = '/system/b2g/b2g'
         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/persistent/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
@@ -95,33 +104,89 @@ class B2GContext(object):
         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
 
+    @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 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)
 
     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
 
--- a/testing/mozbase/mozrunner/mozrunner/devices/base.py
+++ b/testing/mozbase/mozrunner/mozrunner/devices/base.py
@@ -73,26 +73,16 @@ class Device(object):
         """
         self.dm.remount()
 
         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)
 
-        extension_dir = os.path.join(profile.profile, 'extensions', 'staged')
-        if os.path.isdir(extension_dir):
-            # Copy the extensions to the B2G bundles dir.
-            # need to write to read-only dir
-            for filename in os.listdir(extension_dir):
-                path = posixpath.join(self.app_ctx.remote_bundles_dir, filename)
-                if self.dm.fileExists(path):
-                    self.dm.shellCheckOutput(['rm', '-rf', path])
-            self.dm.pushDir(extension_dir, self.app_ctx.remote_bundles_dir)
-
         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):
                 break
             time.sleep(1)
         else:
             print "timed out waiting for profiles.ini"
@@ -108,16 +98,23 @@ class Device(object):
                 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)
 
+        # 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')]
 
     def connect(self):
         """
         Connects to a running device. If no serial was specified in the
         constructor, defaults to the first entry in `adb devices`.
         """
@@ -208,17 +205,17 @@ class Device(object):
                 traceback.print_exc()
             time.sleep(1)
         return False
 
     def backup_file(self, remote_path):
         if not self.restore:
             return
 
-        if self.dm.fileExists(remote_path):
+        if self.dm.fileExists(remote_path) or self.dm.dirExists(remote_path):
             self.dm.copyTree(remote_path, '%s.orig' % remote_path)
             self.backup_files.add(remote_path)
         else:
             self.added_files.add(remote_path)
 
     def cleanup(self):
         """
         Cleanup the device.
@@ -232,27 +229,23 @@ class Device(object):
             return
 
         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.dm.fileExists('%s.orig' % backup_file):
+            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)
 
-        # Delete any bundled extensions
-        extension_dir = posixpath.join(self.app_ctx.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.app_ctx.remote_bundles_dir, filename))
-                except DMError:
-                    pass
+        # 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)
 
     def _rotate_log(self, srclog, index=1):
         """
         Rotate a logfile, by recursively rotating logs further in the sequence,
         deleting the last file if necessary.
         """