Bug 1445944 - [mozprofile] Pull functionality out of Profile and into an abstract 'BaseProfile' class r=rwood
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 13 Apr 2018 13:26:41 -0400
changeset 467472 62c954751e6535370566d267024579e88a01b1a6
parent 467471 96f6bb303871f2ffade8413902135803422a46eb
child 467473 fdad1264866f03b4e7e9a5fae25bcffa8156ec2d
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrwood
bugs1445944
milestone61.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 1445944 - [mozprofile] Pull functionality out of Profile and into an abstract 'BaseProfile' class r=rwood In addition to Profile, this will be implemented by the ChromeProfile class in the next commit. This way we can test for 'isinstance(profile, BaseProfile)' when we just want to test for a profile regardless of application. Ideally I would have preferred 'Profile' itself to be the base class (and co-opt FirefoxProfile to be the new defacto class for firefox profiles), but this would break backwards compatibility. MozReview-Commit-ID: 6TTFq2PQOGM
testing/mozbase/mozprofile/mozprofile/profile.py
testing/mozbase/mozprofile/tests/test_profile.py
--- a/testing/mozbase/mozprofile/mozprofile/profile.py
+++ b/testing/mozbase/mozprofile/mozprofile/profile.py
@@ -1,33 +1,113 @@
 # 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 absolute_import
 
 import os
 import platform
+import tempfile
 import time
-import tempfile
 import uuid
+from abc import ABCMeta, abstractmethod
+from shutil import copytree
+
+import mozfile
 
 from .addons import AddonManager
-import mozfile
 from .permissions import Permissions
 from .prefs import Preferences
-from shutil import copytree
 
-__all__ = ['Profile',
+__all__ = ['BaseProfile',
+           'Profile',
            'FirefoxProfile',
            'ThunderbirdProfile',
            'create_profile']
 
 
-class Profile(object):
+class BaseProfile(object):
+    __metaclass__ = ABCMeta
+
+    def __init__(self, profile=None, addons=None, preferences=None, restore=True):
+        self._addons = addons
+
+        # Prepare additional preferences
+        if preferences:
+            if isinstance(preferences, dict):
+                # unordered
+                preferences = preferences.items()
+
+            # sanity check
+            assert not [i for i in preferences if len(i) != 2]
+        else:
+            preferences = []
+        self._preferences = preferences
+
+        # Handle profile creation
+        self.restore = restore
+        self.create_new = not profile
+        if profile:
+            # Ensure we have a full path to the profile
+            self.profile = os.path.abspath(os.path.expanduser(profile))
+        else:
+            self.profile = tempfile.mkdtemp(suffix='.mozrunner')
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        self.cleanup()
+
+    def __del__(self):
+        self.cleanup()
+
+    def cleanup(self):
+        """Cleanup operations for the profile."""
+
+        if self.restore:
+            # If it's a temporary profile we have to remove it
+            if self.create_new:
+                mozfile.remove(self.profile)
+
+    @abstractmethod
+    def _reset(self):
+        pass
+
+    def reset(self):
+        """
+        reset the profile to the beginning state
+        """
+        self.cleanup()
+        self._reset()
+
+    @classmethod
+    def clone(cls, path_from, path_to=None, ignore=None, **kwargs):
+        """Instantiate a temporary profile via cloning
+        - path: path of the basis to clone
+        - ignore: callable passed to shutil.copytree
+        - kwargs: arguments to the profile constructor
+        """
+        if not path_to:
+            tempdir = tempfile.mkdtemp()  # need an unused temp dir name
+            mozfile.remove(tempdir)  # copytree requires that dest does not exist
+            path_to = tempdir
+        copytree(path_from, path_to, ignore=ignore)
+
+        c = cls(path_to, **kwargs)
+        c.create_new = True  # deletes a cloned profile when restore is True
+        return c
+
+    def exists(self):
+        """returns whether the profile exists or not"""
+        return os.path.exists(self.profile)
+
+
+class Profile(BaseProfile):
     """Handles all operations regarding profile.
 
     Creating new profiles, installing add-ons, setting preferences and
     handling cleanup.
 
     The files associated with the profile will be removed automatically after
     the object is garbage collected: ::
 
@@ -53,47 +133,27 @@ class Profile(object):
         :param addons: String of one or list of addons to install
         :param preferences: Dictionary or class of preferences
         :param locations: ServerLocations object
         :param proxy: Setup a proxy
         :param restore: Flag for removing all custom settings during cleanup
         :param whitelistpaths: List of paths to pass to Firefox to allow read
             access to from the content process sandbox.
         """
-        self._addons = addons
+        super(Profile, self).__init__(
+            profile=profile, addons=addons, preferences=preferences, restore=restore)
+
         self._locations = locations
         self._proxy = proxy
-
-        # Prepare additional preferences
-        if preferences:
-            if isinstance(preferences, dict):
-                # unordered
-                preferences = preferences.items()
-
-            # sanity check
-            assert not [i for i in preferences if len(i) != 2]
-        else:
-            preferences = []
-        self._preferences = preferences
         self._whitelistpaths = whitelistpaths
 
-        # Handle profile creation
-        self.create_new = not profile
-        if profile:
-            # Ensure we have a full path to the profile
-            self.profile = os.path.abspath(os.path.expanduser(profile))
-        else:
-            self.profile = tempfile.mkdtemp(suffix='.mozrunner')
+        # Initialize all class members
+        self._reset()
 
-        self.restore = restore
-
-        # Initialize all class members
-        self._internal_init()
-
-    def _internal_init(self):
+    def _reset(self):
         """Internal: Initialize all class members to their default value"""
 
         if not os.path.exists(self.profile):
             os.makedirs(self.profile)
 
         # Preferences files written to
         self.written_prefs = set()
 
@@ -131,82 +191,39 @@ class Profile(object):
                                  ",".join(self._whitelistpaths)))
         self.set_preferences(prefs_js, 'prefs.js')
         self.set_preferences(user_js)
 
         # handle add-on installation
         self.addons = AddonManager(self.profile, restore=self.restore)
         self.addons.install(self._addons)
 
-    def __enter__(self):
-        return self
-
-    def __exit__(self, type, value, traceback):
-        self.cleanup()
-
-    def __del__(self):
-        self.cleanup()
-
-    # cleanup
-
     def cleanup(self):
         """Cleanup operations for the profile."""
 
         if self.restore:
             # If copies of those class instances exist ensure we correctly
             # reset them all (see bug 934484)
             self.clean_preferences()
             if getattr(self, 'addons', None) is not None:
                 self.addons.clean()
             if getattr(self, 'permissions', None) is not None:
                 self.permissions.clean_db()
-
-            # If it's a temporary profile we have to remove it
-            if self.create_new:
-                mozfile.remove(self.profile)
-
-    def reset(self):
-        """
-        reset the profile to the beginning state
-        """
-        self.cleanup()
-
-        self._internal_init()
+        super(Profile, self).cleanup()
 
     def clean_preferences(self):
         """Removed preferences added by mozrunner."""
         for filename in self.written_prefs:
             if not os.path.exists(os.path.join(self.profile, filename)):
                 # file has been deleted
                 break
             while True:
                 if not self.pop_preferences(filename):
                     break
 
-    @classmethod
-    def clone(cls, path_from, path_to=None, ignore=None, **kwargs):
-        """Instantiate a temporary profile via cloning
-        - path: path of the basis to clone
-        - ignore: callable passed to shutil.copytree
-        - kwargs: arguments to the profile constructor
-        """
-        if not path_to:
-            tempdir = tempfile.mkdtemp()  # need an unused temp dir name
-            mozfile.remove(tempdir)  # copytree requires that dest does not exist
-            path_to = tempdir
-        copytree(path_from, path_to, ignore=ignore)
-
-        c = cls(path_to, **kwargs)
-        c.create_new = True  # deletes a cloned profile when restore is True
-        return c
-
-    def exists(self):
-        """returns whether the profile exists or not"""
-        return os.path.exists(self.profile)
-
     # methods for preferences
 
     def set_preferences(self, preferences, filename='user.js'):
         """Adds preferences dict to profile preferences"""
 
         # append to the file
         prefs_file = os.path.join(self.profile, filename)
         f = open(prefs_file, 'a')
--- a/testing/mozbase/mozprofile/tests/test_profile.py
+++ b/testing/mozbase/mozprofile/tests/test_profile.py
@@ -7,16 +7,17 @@
 from __future__ import absolute_import
 
 import os
 
 import mozunit
 import pytest
 
 from mozprofile import (
+    BaseProfile,
     Profile,
     FirefoxProfile,
     ThunderbirdProfile,
     create_profile,
 )
 
 
 def test_with_profile_should_cleanup():
@@ -46,15 +47,15 @@ def test_create_profile(tmpdir, app, cls
     path = tmpdir.strpath
 
     if cls is None:
         with pytest.raises(NotImplementedError):
             create_profile(app)
         return
 
     profile = create_profile(app, profile=path)
-    assert isinstance(profile, Profile)
+    assert isinstance(profile, BaseProfile)
     assert profile.__class__ == cls
     assert profile.profile == path
 
 
 if __name__ == '__main__':
     mozunit.main()