From 02111d844385029a20b649909b2266d18af28b6f Mon Sep 17 00:00:00 2001 From: Malini Das Date: Tue, 22 Jul 2014 17:27:25 -0400 Subject: [PATCH] Bug 1030442 - Be able to set and clear prefs at gecko startup, r=jgriffin --- .../client/marionette/geckoinstance.py | 39 ++++++++++- .../client/marionette/marionette.py | 67 +++++++++++++++++++ .../tests/unit/test_profile_management.py | 32 +++++++++ .../marionette/tests/unit/unit-tests.ini | 2 + 4 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 testing/marionette/client/marionette/tests/unit/test_profile_management.py diff --git a/testing/marionette/client/marionette/geckoinstance.py b/testing/marionette/client/marionette/geckoinstance.py index ac6f48397183..321dc873e6f9 100644 --- a/testing/marionette/client/marionette/geckoinstance.py +++ b/testing/marionette/client/marionette/geckoinstance.py @@ -2,6 +2,9 @@ # 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 copy import deepcopy +import errno +import platform import os import time @@ -21,18 +24,21 @@ class GeckoInstance(object): "browser.warnOnQuit": False} def __init__(self, host, port, bin, profile, app_args=None, symbols_path=None, - gecko_log=None): + gecko_log=None, prefs=None): self.marionette_host = host self.marionette_port = port self.bin = bin self.profile_path = profile + self.prefs = prefs self.app_args = app_args or [] self.runner = None self.symbols_path = symbols_path self.gecko_log = gecko_log def start(self): - profile_args = {"preferences": self.required_prefs} + profile_args = {"preferences": deepcopy(self.required_prefs)} + if self.prefs: + profile_args["preferences"].update(self.prefs) if not self.profile_path: profile_args["restore"] = False profile = Profile(**profile_args) @@ -48,7 +54,27 @@ class GeckoInstance(object): self.gecko_log = os.path.realpath(self.gecko_log) if os.access(self.gecko_log, os.F_OK): - os.remove(self.gecko_log) + if platform.system() is 'Windows': + # NOTE: windows has a weird filesystem where it happily 'closes' + # the file, but complains if you try to delete it. You get a + # 'file still in use' error. Sometimes you can wait a bit and + # a retry will succeed. + # If all retries fail, we'll just continue without removing + # the file. In this case, if we are restarting the instance, + # then the new logs just get appended to the old file. + tries = 0 + while tries < 10: + try: + os.remove(self.gecko_log) + break + except WindowsError as e: + if e.errno == errno.EACCES: + tries += 1 + time.sleep(0.5) + else: + raise e + else: + os.remove(self.gecko_log) env = os.environ.copy() @@ -75,6 +101,13 @@ class GeckoInstance(object): self.runner.stop() self.runner.cleanup() + def restart(self, prefs=None): + self.close() + if prefs: + self.prefs = prefs + else: + self.prefs = None + self.start() class B2GDesktopInstance(GeckoInstance): required_prefs = {"focusmanager.testmode": True} diff --git a/testing/marionette/client/marionette/marionette.py b/testing/marionette/client/marionette/marionette.py index 0fd9155bd22b..804a50bd0cf9 100644 --- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -4,6 +4,7 @@ import ConfigParser import datetime +import json import os import socket import StringIO @@ -702,6 +703,14 @@ class Marionette(object): raise errors.MarionetteException(message=message, status=status, stacktrace=stacktrace) raise errors.MarionetteException(message=response, status=500) + def _reset_timeouts(self): + if self.timeout is not None: + self.timeouts(self.TIMEOUT_SEARCH, self.timeout) + self.timeouts(self.TIMEOUT_SCRIPT, self.timeout) + self.timeouts(self.TIMEOUT_PAGE, self.timeout) + else: + self.timeouts(self.TIMEOUT_PAGE, 30000) + def check_for_crash(self): returncode = None name = None @@ -719,6 +728,64 @@ class Marionette(object): (name, returncode)) return crashed + def enforce_gecko_prefs(self, prefs): + """ + Checks if the running instance has the given prefs. If not, it will kill the + currently running instance, and spawn a new instance with the requested preferences. + + : param prefs: A dictionary whose keys are preference names. + """ + if not self.instance: + raise errors.MarionetteException("enforce_gecko_prefs can only be called " \ + "on gecko instances launched by Marionette") + pref_exists = True + self.set_context(self.CONTEXT_CHROME) + for pref, value in prefs.iteritems(): + if type(value) is not str: + value = json.dumps(value) + pref_exists = self.execute_script(""" + let prefInterface = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + let pref = '%s'; + let value = '%s'; + let type = prefInterface.getPrefType(pref); + switch(type) { + case prefInterface.PREF_STRING: + return value == prefInterface.getCharPref(pref).toString(); + case prefInterface.PREF_BOOL: + return value == prefInterface.getBoolPref(pref).toString(); + case prefInterface.PREF_INT: + return value == prefInterface.getIntPref(pref).toString(); + case prefInterface.PREF_INVALID: + return false; + } + """ % (pref, value)) + if not pref_exists: + break + self.set_context(self.CONTEXT_CONTENT) + if not pref_exists: + self.delete_session() + self.instance.restart(prefs) + assert(self.wait_for_port()), "Timed out waiting for port!" + self.start_session() + self._reset_timeouts() + + def restart_with_clean_profile(self): + """ + This will terminate the currently running instance, and spawn a new instance + with a clean profile. + + : param prefs: A dictionary whose keys are preference names. + """ + if not self.instance: + raise errors.MarionetteException("enforce_gecko_prefs can only be called " \ + "on gecko instances launched by Marionette") + self.delete_session() + self.instance.restart() + assert(self.wait_for_port()), "Timed out waiting for port!" + self.start_session() + self._reset_timeouts() + def absolute_url(self, relative_url): ''' Returns an absolute url for files served from Marionette's www directory. diff --git a/testing/marionette/client/marionette/tests/unit/test_profile_management.py b/testing/marionette/client/marionette/tests/unit/test_profile_management.py new file mode 100644 index 000000000000..b1cbd46b8bb8 --- /dev/null +++ b/testing/marionette/client/marionette/tests/unit/test_profile_management.py @@ -0,0 +1,32 @@ +# 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/. + +import os +from errors import JavascriptException +from marionette_test import MarionetteTestCase + +class TestLog(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.enforce_gecko_prefs({"marionette.test.bool": True, "marionette.test.string": "testing", "marionette.test.int": 3}) + + def test_preferences_are_set(self): + bool_value = self.marionette.execute_script("return SpecialPowers.getBoolPref('marionette.test.bool');") + string_value = self.marionette.execute_script("return SpecialPowers.getCharPref('marionette.test.string');") + int_value = self.marionette.execute_script("return SpecialPowers.getIntPref('marionette.test.int');") + self.assertTrue(bool_value) + self.assertEqual(string_value, "testing") + self.assertEqual(int_value, 3) + + def test_change_preset(self): + bool_value = self.marionette.execute_script("return SpecialPowers.getBoolPref('marionette.test.bool');") + self.assertTrue(bool_value) + self.marionette.enforce_gecko_prefs({"marionette.test.bool": False}) + bool_value = self.marionette.execute_script("return SpecialPowers.getBoolPref('marionette.test.bool');") + self.assertFalse(bool_value) + + def test_clean_profile(self): + self.marionette.restart_with_clean_profile() + with self.assertRaisesRegexp(JavascriptException, "Error getting pref"): + bool_value = self.marionette.execute_script("return SpecialPowers.getBoolPref('marionette.test.bool');") diff --git a/testing/marionette/client/marionette/tests/unit/unit-tests.ini b/testing/marionette/client/marionette/tests/unit/unit-tests.ini index 100c71796741..e716d15763ca 100644 --- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini +++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini @@ -115,5 +115,7 @@ browser = false [test_execute_isolate.py] [test_click_scrolling.py] +[test_profile_management.py] +b2g = false [include:oop/manifest.ini]