Bug 1445944 - [mozprofile] Pull functionality out of Profile and into an abstract 'BaseProfile' class draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 13 Apr 2018 13:26:41 -0400
changeset 783206 e9333107b5bdbe49145de98ee8c77c9a606f209a
parent 783205 1cca70abe57ea916b86623d4ef1f5d96772a0d96
child 783207 72ce07ef3336ef9042d48479e99412d79a78cfe6
push id106641
push userahalberstadt@mozilla.com
push dateMon, 16 Apr 2018 19:49:26 +0000
bugs1445944
milestone61.0a1
Bug 1445944 - [mozprofile] Pull functionality out of Profile and into an abstract 'BaseProfile' class 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()