Bug 1429759 - Allow Marionette tests to change the user profile. r=automatedtester

If Marionette handles the instance of the application including the
user profile, the tests should be able to change the user profile.

This will enable tests to cover specific bugs in the application as
caused by the profile name and path.

To prevent dataloss changing the profile should only be allowed
when the application is not running.

MozReview-Commit-ID: JWQGV4JWP61

--HG--
extra : rebase_source : 18270e9790f11fffc2a33a231dbc8229a1bf12d2
This commit is contained in:
Henrik Skupin 2018-01-11 15:21:11 +01:00
Родитель 4bee56a645
Коммит a1474a6ed5
3 изменённых файлов: 298 добавлений и 55 удалений

Просмотреть файл

@ -16,6 +16,8 @@ from mozdevice import DMError
from mozprofile import Profile from mozprofile import Profile
from mozrunner import Runner, FennecEmulatorRunner from mozrunner import Runner, FennecEmulatorRunner
import errors
class GeckoInstance(object): class GeckoInstance(object):
required_prefs = { required_prefs = {
@ -124,15 +126,7 @@ class GeckoInstance(object):
self.marionette_host = host self.marionette_host = host
self.marionette_port = port self.marionette_port = port
# Alternative to default temporary directory
self.workspace = workspace
self.addons = addons self.addons = addons
# Check if it is a Profile object or a path to profile
self.profile = None
if isinstance(profile, Profile):
self.profile = profile
else:
self.profile_path = profile
self.prefs = prefs self.prefs = prefs
self.required_prefs = deepcopy(self.required_prefs) self.required_prefs = deepcopy(self.required_prefs)
if prefs: if prefs:
@ -142,9 +136,14 @@ class GeckoInstance(object):
self._gecko_log = None self._gecko_log = None
self.verbose = verbose self.verbose = verbose
self.headless = headless self.headless = headless
# keep track of errors to decide whether instance is unresponsive # keep track of errors to decide whether instance is unresponsive
self.unresponsive_count = 0 self.unresponsive_count = 0
# Alternative to default temporary directory
self.workspace = workspace
self.profile = profile
@property @property
def gecko_log(self): def gecko_log(self):
if self._gecko_log: if self._gecko_log:
@ -165,42 +164,87 @@ class GeckoInstance(object):
self._gecko_log = path self._gecko_log = path
return self._gecko_log return self._gecko_log
def _update_profile(self): @property
profile_args = {"preferences": deepcopy(self.required_prefs)} def profile(self):
profile_args["preferences"]["marionette.defaultPrefs.port"] = self.marionette_port return self._profile
@profile.setter
def profile(self, value):
self._update_profile(value)
def _update_profile(self, profile=None, profile_name=None):
"""Check if the profile has to be created, or replaced
:param profile: A Profile instance to be used.
:param name: Profile name to be used in the path.
"""
if self.runner and self.runner.is_running():
raise errors.MarionetteException("The used profile can only be updated "
"when the instance is not running")
if isinstance(profile, Profile):
# Only replace the profile if it is not the current one
if hasattr(self, "_profile") and profile is self._profile:
return
else:
profile_args = self.profile_args
profile_path = profile
# If a path to a profile is given then clone it
if isinstance(profile_path, basestring):
profile_args["path_from"] = profile_path
profile_args["path_to"] = tempfile.mkdtemp(
suffix=".{}".format(profile_name or os.path.basename(profile_path)),
dir=self.workspace)
# The target must not exist yet
os.rmdir(profile_args["path_to"])
profile = Profile.clone(**profile_args)
# Otherwise create a new profile
else:
profile_args["profile"] = tempfile.mkdtemp(
suffix=".{}".format(profile_name or "mozrunner"),
dir=self.workspace)
profile = Profile(**profile_args)
profile.create_new = True
if hasattr(self, "_profile") and self._profile:
self._profile.cleanup()
self._profile = profile
def switch_profile(self, profile_name=None, clone_from=None):
if isinstance(clone_from, Profile):
clone_from = clone_from.profile
self._update_profile(clone_from, profile_name=profile_name)
@property
def profile_args(self):
args = {"preferences": deepcopy(self.required_prefs)}
args["preferences"]["marionette.defaultPrefs.port"] = self.marionette_port
if self.prefs: if self.prefs:
profile_args["preferences"].update(self.prefs) args["preferences"].update(self.prefs)
if self.verbose: if self.verbose:
level = "TRACE" if self.verbose >= 2 else "DEBUG" level = "TRACE" if self.verbose >= 2 else "DEBUG"
profile_args["preferences"]["marionette.logging"] = level args["preferences"]["marionette.logging"] = level
if "-jsdebugger" in self.app_args: if "-jsdebugger" in self.app_args:
profile_args["preferences"].update({ args["preferences"].update({
"devtools.browsertoolbox.panel": "jsdebugger", "devtools.browsertoolbox.panel": "jsdebugger",
"devtools.debugger.remote-enabled": True, "devtools.debugger.remote-enabled": True,
"devtools.chrome.enabled": True, "devtools.chrome.enabled": True,
"devtools.debugger.prompt-connection": False, "devtools.debugger.prompt-connection": False,
"marionette.debugging.clicktostart": True, "marionette.debugging.clicktostart": True,
}) })
if self.addons:
profile_args["addons"] = self.addons
if hasattr(self, "profile_path") and self.profile is None: if self.addons:
if not self.profile_path: args["addons"] = self.addons
if self.workspace:
profile_args["profile"] = tempfile.mkdtemp( return args
suffix=".mozrunner-{:.0f}".format(time.time()),
dir=self.workspace)
self.profile = Profile(**profile_args)
else:
profile_args["path_from"] = self.profile_path
profile_name = "{}-{:.0f}".format(
os.path.basename(self.profile_path),
time.time()
)
if self.workspace:
profile_args["path_to"] = os.path.join(self.workspace,
profile_name)
self.profile = Profile.clone(**profile_args)
@classmethod @classmethod
def create(cls, app=None, *args, **kwargs): def create(cls, app=None, *args, **kwargs):
@ -218,7 +262,7 @@ class GeckoInstance(object):
return instance_class(*args, **kwargs) return instance_class(*args, **kwargs)
def start(self): def start(self):
self._update_profile() self._update_profile(self.profile)
self.runner = self.runner_class(**self._get_runner_args()) self.runner = self.runner_class(**self._get_runner_args())
self.runner.start() self.runner.start()
@ -279,12 +323,12 @@ class GeckoInstance(object):
:param prefs: Dictionary of preference names and values. :param prefs: Dictionary of preference names and values.
:param clean: If True, reset the profile before starting. :param clean: If True, reset the profile before starting.
""" """
self.close(clean=clean)
if prefs: if prefs:
self.prefs = prefs self.prefs = prefs
else: else:
self.prefs = None self.prefs = None
self.close(clean=clean)
self.start() self.start()
@ -347,7 +391,7 @@ class FennecInstance(GeckoInstance):
return self._package_name return self._package_name
def start(self): def start(self):
self._update_profile() self._update_profile(self.profile)
self.runner = self.runner_class(**self._get_runner_args()) self.runner = self.runner_class(**self._get_runner_args())
try: try:
if self.connect_to_running_emulator: if self.connect_to_running_emulator:

Просмотреть файл

@ -165,3 +165,38 @@ class TestPreferences(MarionetteTestCase):
pass pass
self.assertEquals(self.marionette.get_pref(self.prefs["string"]), "abc") self.assertEquals(self.marionette.get_pref(self.prefs["string"]), "abc")
class TestEnforcePreferences(MarionetteTestCase):
def setUp(self):
super(TestEnforcePreferences, self).setUp()
self.marionette.enforce_gecko_prefs({
"marionette.test.bool": True,
"marionette.test.int": 3,
"marionette.test.string": "testing",
})
self.marionette.set_context("chrome")
def tearDown(self):
self.marionette.quit(clean=True)
super(TestEnforcePreferences, self).tearDown()
def test_preferences_are_set(self):
self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
self.assertEqual(self.marionette.get_pref("marionette.test.string"), "testing")
self.assertEqual(self.marionette.get_pref("marionette.test.int"), 3)
def test_change_preference(self):
self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
self.marionette.enforce_gecko_prefs({"marionette.test.bool": False})
self.assertFalse(self.marionette.get_pref("marionette.test.bool"))
def test_restart_with_clean_profile(self):
self.marionette.restart(clean=True)
self.assertEqual(self.marionette.get_pref("marionette.test.bool"), None)

Просмотреть файл

@ -2,33 +2,197 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # 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/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import shutil
import tempfile
import mozprofile
from marionette_harness import MarionetteTestCase from marionette_harness import MarionetteTestCase
class TestProfileManagement(MarionetteTestCase): class BaseProfileManagement(MarionetteTestCase):
def setUp(self): def setUp(self):
MarionetteTestCase.setUp(self) super(BaseProfileManagement, self).setUp()
self.marionette.enforce_gecko_prefs(
{"marionette.test.bool": True,
"marionette.test.string": "testing",
"marionette.test.int": 3
})
self.marionette.set_context("chrome")
def test_preferences_are_set(self): self.orig_profile_path = self.profile_path
self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
self.assertEqual(self.marionette.get_pref("marionette.test.string"), "testing")
self.assertEqual(self.marionette.get_pref("marionette.test.int"), 3)
def test_change_preference(self): # Create external profile and mark it as not-removable
self.assertTrue(self.marionette.get_pref("marionette.test.bool")) tmp_dir = tempfile.mkdtemp(suffix="external")
shutil.rmtree(tmp_dir, ignore_errors=True)
self.marionette.enforce_gecko_prefs({"marionette.test.bool": False}) self.external_profile = mozprofile.Profile(profile=tmp_dir)
self.external_profile.create_new = False
self.assertFalse(self.marionette.get_pref("marionette.test.bool")) def tearDown(self):
shutil.rmtree(self.external_profile.profile, ignore_errors=True)
def test_clean_profile(self): self.marionette.profile = None
super(BaseProfileManagement, self).tearDown()
@property
def profile(self):
return self.marionette.instance.profile
@property
def profile_path(self):
return self.marionette.instance.profile.profile
class WorkspaceProfileManagement(BaseProfileManagement):
def setUp(self):
super(WorkspaceProfileManagement, self).setUp()
self.workspace = tempfile.mkdtemp()
self.marionette.instance.workspace = self.workspace
def tearDown(self):
self.marionette.instance.workspace = None
shutil.rmtree(self.workspace, ignore_errors=True)
super(WorkspaceProfileManagement, self).tearDown()
class TestQuitRestartWithoutWorkspace(BaseProfileManagement):
def test_quit_keeps_same_profile(self):
self.marionette.quit()
self.marionette.start_session()
self.assertEqual(self.profile_path, self.orig_profile_path)
self.assertTrue(os.path.exists(self.orig_profile_path))
def test_quit_clean_creates_new_profile(self):
self.marionette.quit(clean=True)
self.marionette.start_session()
self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertFalse(os.path.exists(self.orig_profile_path))
def test_restart_keeps_same_profile(self):
self.marionette.restart()
self.assertEqual(self.profile_path, self.orig_profile_path)
self.assertTrue(os.path.exists(self.orig_profile_path))
def test_restart_clean_creates_new_profile(self):
self.marionette.restart(clean=True) self.marionette.restart(clean=True)
self.assertEqual(self.marionette.get_pref("marionette.test.bool"), None) self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertFalse(os.path.exists(self.orig_profile_path))
class TestQuitRestartWithWorkspace(WorkspaceProfileManagement):
def test_quit_keeps_same_profile(self):
self.marionette.quit()
self.marionette.start_session()
self.assertEqual(self.profile_path, self.orig_profile_path)
self.assertNotIn(self.workspace, self.profile_path)
self.assertTrue(os.path.exists(self.orig_profile_path))
def test_quit_clean_creates_new_profile(self):
self.marionette.quit(clean=True)
self.marionette.start_session()
self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertIn(self.workspace, self.profile_path)
self.assertFalse(os.path.exists(self.orig_profile_path))
def test_restart_keeps_same_profile(self):
self.marionette.restart()
self.assertEqual(self.profile_path, self.orig_profile_path)
self.assertNotIn(self.workspace, self.profile_path)
self.assertTrue(os.path.exists(self.orig_profile_path))
def test_restart_clean_creates_new_profile(self):
self.marionette.restart(clean=True)
self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertIn(self.workspace, self.profile_path)
self.assertFalse(os.path.exists(self.orig_profile_path))
class TestSwitchProfileWithoutWorkspace(BaseProfileManagement):
def setUp(self):
super(TestSwitchProfileWithoutWorkspace, self).setUp()
self.marionette.quit()
def tearDown(self):
super(TestSwitchProfileWithoutWorkspace, self).tearDown()
def test_new_random_profile_name(self):
self.marionette.instance.switch_profile()
self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertFalse(os.path.exists(self.orig_profile_path))
def test_new_named_profile(self):
self.marionette.instance.switch_profile("foobar")
self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertIn("foobar", self.profile_path)
self.assertFalse(os.path.exists(self.orig_profile_path))
def test_clone_existing_profile(self):
self.marionette.instance.switch_profile(clone_from=self.external_profile)
self.assertIn(os.path.basename(self.external_profile.profile), self.profile_path)
self.assertTrue(os.path.exists(self.external_profile.profile))
def test_replace_with_current_profile(self):
self.marionette.instance.profile = self.profile
self.assertEqual(self.profile_path, self.orig_profile_path)
self.assertTrue(os.path.exists(self.orig_profile_path))
def test_replace_with_external_profile(self):
self.marionette.instance.profile = self.external_profile
self.assertEqual(self.profile_path, self.external_profile.profile)
self.assertFalse(os.path.exists(self.orig_profile_path))
# Set a new profile and ensure the external profile has not been deleted
self.marionette.instance.profile = None
self.assertNotEqual(self.profile_path, self.external_profile.profile)
self.assertTrue(os.path.exists(self.external_profile.profile))
class TestSwitchProfileWithWorkspace(WorkspaceProfileManagement):
def setUp(self):
super(TestSwitchProfileWithWorkspace, self).setUp()
self.marionette.quit()
def test_new_random_profile_name(self):
self.marionette.instance.switch_profile()
self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertIn(self.workspace, self.profile_path)
self.assertFalse(os.path.exists(self.orig_profile_path))
def test_new_named_profile(self):
self.marionette.instance.switch_profile("foobar")
self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertIn("foobar", self.profile_path)
self.assertIn(self.workspace, self.profile_path)
self.assertFalse(os.path.exists(self.orig_profile_path))
def test_clone_existing_profile(self):
self.marionette.instance.switch_profile(clone_from=self.external_profile)
self.assertNotEqual(self.profile_path, self.orig_profile_path)
self.assertIn(self.workspace, self.profile_path)
self.assertIn(os.path.basename(self.external_profile.profile), self.profile_path)
self.assertTrue(os.path.exists(self.external_profile.profile))