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
--- 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()