from __future__ import absolute_import, print_function from marionette_harness import MarionetteTestCase from contextlib import contextmanager class ExperimentStatus: UNENROLLED = 0 ENROLLED_CONTROL = 1 ENROLLED_TREATMENT = 2 DISQUALIFIED = 3 class Prefs: ENROLLMENT_STATUS = "fission.experiment.enrollmentStatus" STARTUP_ENROLLMENT_STATUS = "fission.experiment.startupEnrollmentStatus" FISSION_AUTOSTART = "fission.autostart" FISSION_AUTOSTART_SESSION = "fission.autostart.session" ENV_ENABLE_FISSION = "MOZ_FORCE_ENABLE_FISSION" ENV_DISABLE_E10S = "MOZ_FORCE_DISABLE_E10S" DECISION_STATUS = { "experimentControl": 1, "experimentTreatment": 2, "disabledByE10sEnv": 3, "enabledByEnv": 4, "disabledBySafeMode": 5, "enabledByDefault": 6, "disabledByDefault": 7, "enabledByUserPref": 8, "disabledByUserPref": 9, "disabledByE10sOther": 10, } class TestFissionAutostart(MarionetteTestCase): SANDBOX_NAME = "fission-autostart" def execute_script(self, code, *args, **kwargs): with self.marionette.using_context(self.marionette.CONTEXT_CHROME): return self.marionette.execute_script( code, new_sandbox=False, sandbox=self.SANDBOX_NAME, *args, **kwargs ) def get_fission_status(self): return self.execute_script( r""" let win = Services.wm.getMostRecentWindow("navigator:browser"); return { fissionAutostart: Services.appinfo.fissionAutostart, fissionExperimentStatus: Services.appinfo.fissionExperimentStatus, decisionStatus: Services.appinfo.fissionDecisionStatus, decisionStatusString: Services.appinfo.fissionDecisionStatusString, useRemoteSubframes: win.docShell.nsILoadContext.useRemoteSubframes, fissionAutostartSession: Services.prefs.getBoolPref("fission.autostart.session"), dynamicFissionAutostart: Services.prefs.getBoolPref("fission.autostart"), }; """ ) def check_fission_status(self, enabled, experiment, decision, dynamic=None): if dynamic is None: dynamic = enabled expected = { "fissionAutostart": enabled, "fissionExperimentStatus": experiment, "decisionStatus": DECISION_STATUS[decision], "decisionStatusString": decision, "useRemoteSubframes": enabled, "fissionAutostartSession": enabled, "dynamicFissionAutostart": dynamic, } status = self.get_fission_status() for prop, value in expected.items(): self.assertEqual( status[prop], value, "%s should have the value `%r`, but has `%r`" % (prop, value, status[prop]), ) def check_pref_locked(self): PREF = Prefs.FISSION_AUTOSTART if PREF in self.marionette.instance.required_prefs: return True res = self.execute_script( r""" const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); return { prefLocked: Services.prefs.prefIsLocked(arguments[0]), releaseOrBeta: AppConstants.RELEASE_OR_BETA, }; """, script_args=(PREF,), ) if res["prefLocked"]: self.assertTrue( res["releaseOrBeta"], "Preference should only be locked on release/beta" ) return True return False def set_env(self, env, value): self.execute_script( "env.set(arguments[0], arguments[1]);", script_args=(env, value) ) def get_env(self, env): return self.execute_script("return env.get(arguments[0]);", script_args=(env,)) def set_enrollment_status(self, status): self.marionette.set_pref(Prefs.ENROLLMENT_STATUS, status, default_branch=True) startup_status = self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS) self.assertEqual( startup_status, status, "Startup enrollment status (%r) should match new " "session status (%r)" % (startup_status, status), ) def restart(self, prefs=None, env=None): if prefs: self.marionette.set_prefs(prefs) if env: for name, value in env.items(): self.set_env(name, value) self.marionette.restart(in_app=True, clean=False) self.setUpSession() # Sanity check our environment. if prefs: for key, val in prefs.items(): if val is not None: self.assertEqual(self.marionette.get_pref(key), val) if env: for key, val in env.items(): self.assertEqual(self.get_env(key), val or "") def setUpSession(self): self.marionette.set_context(self.marionette.CONTEXT_CHROME) self.execute_script( r""" // We're running in a function, in a sandbox, that inherits from an // X-ray wrapped window. Anything we want to be globally available // needs to be defined on that window. ChromeUtils.import("resource://gre/modules/Services.jsm", window); window.env = Cc["@mozilla.org/process/environment;1"] .getService(Ci.nsIEnvironment); """ ) @contextmanager def full_restart(self): profile = self.marionette.instance.profile try: self.marionette.quit(in_app=True, clean=False) yield profile finally: self.marionette.start_session() self.setUpSession() def setUp(self): super(TestFissionAutostart, self).setUp() self.setUpSession() def tearDown(self): self.marionette.restart(clean=True) super(TestFissionAutostart, self).tearDown() def test_runtime_changes(self): """Tests that changes to preferences during runtime do not have any effect on the current session.""" if self.check_pref_locked(): # Need to be able to flip Fission prefs for this test to work. return self.check_fission_status( enabled=False, experiment=ExperimentStatus.UNENROLLED, decision="disabledByDefault", ) self.restart(prefs={Prefs.FISSION_AUTOSTART: True}) self.check_fission_status( enabled=True, experiment=ExperimentStatus.UNENROLLED, decision="enabledByUserPref", ) self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL) self.check_fission_status( enabled=True, experiment=ExperimentStatus.UNENROLLED, decision="enabledByUserPref", ) self.marionette.set_pref(Prefs.FISSION_AUTOSTART, False) self.check_fission_status( enabled=True, experiment=ExperimentStatus.UNENROLLED, decision="enabledByUserPref", dynamic=False, ) self.marionette.clear_pref(Prefs.FISSION_AUTOSTART) self.check_fission_status( enabled=True, experiment=ExperimentStatus.UNENROLLED, decision="enabledByUserPref", dynamic=False, ) self.restart() self.check_fission_status( enabled=False, experiment=ExperimentStatus.ENROLLED_CONTROL, decision="experimentControl", ) self.marionette.set_pref( Prefs.ENROLLMENT_STATUS, ExperimentStatus.UNENROLLED, default_branch=True ) self.check_fission_status( enabled=False, experiment=ExperimentStatus.ENROLLED_CONTROL, decision="experimentControl", ) self.set_env(ENV_ENABLE_FISSION, "1") self.check_fission_status( enabled=False, experiment=ExperimentStatus.ENROLLED_CONTROL, decision="experimentControl", ) def test_fission_precedence(self): if self.check_pref_locked(): # Need to be able to flip Fission prefs for this test to work. return self.check_fission_status( enabled=False, experiment=ExperimentStatus.UNENROLLED, decision="disabledByDefault", ) self.restart( prefs={Prefs.FISSION_AUTOSTART: False}, env={ENV_ENABLE_FISSION: "1"} ) self.check_fission_status( enabled=True, experiment=ExperimentStatus.UNENROLLED, decision="enabledByEnv", dynamic=False, ) self.restart( prefs={Prefs.FISSION_AUTOSTART: True}, env={ENV_ENABLE_FISSION: ""} ) self.check_fission_status( enabled=True, experiment=ExperimentStatus.UNENROLLED, decision="enabledByUserPref", ) self.restart(prefs={Prefs.FISSION_AUTOSTART: None}) self.check_fission_status( enabled=False, experiment=ExperimentStatus.UNENROLLED, decision="disabledByDefault", ) self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT) self.restart() self.check_fission_status( enabled=True, experiment=ExperimentStatus.ENROLLED_TREATMENT, decision="experimentTreatment", ) self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL) self.restart() self.check_fission_status( enabled=False, experiment=ExperimentStatus.ENROLLED_CONTROL, decision="experimentControl", ) self.marionette.set_pref(Prefs.FISSION_AUTOSTART, True) self.check_fission_status( enabled=False, experiment=ExperimentStatus.ENROLLED_CONTROL, decision="experimentControl", dynamic=True, ) self.assertEqual( self.marionette.get_pref(Prefs.ENROLLMENT_STATUS), ExperimentStatus.DISQUALIFIED, "Setting fission.autostart should disqualify", ) self.restart() self.check_fission_status( enabled=True, experiment=ExperimentStatus.DISQUALIFIED, decision="enabledByUserPref", ) app_version = self.execute_script("return Services.appinfo.version") self.restart(env={ENV_DISABLE_E10S: app_version}) self.check_fission_status( enabled=False, experiment=ExperimentStatus.DISQUALIFIED, decision="disabledByE10sEnv", dynamic=True, ) def test_fission_startup(self): if self.check_pref_locked(): # Need to be able to flip Fission prefs for this test to work. return # Starting the browser with STARTUP_ENROLLMENT_STATUS set to treatment # should make the current session run under treatment. with self.full_restart() as profile: profile.set_preferences( { Prefs.STARTUP_ENROLLMENT_STATUS: ExperimentStatus.ENROLLED_TREATMENT, }, filename="prefs.js", ) self.assertEqual( self.marionette.get_pref(Prefs.ENROLLMENT_STATUS), ExperimentStatus.UNENROLLED, "Dynamic pref should be unenrolled", ) self.assertEqual( self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS), ExperimentStatus.ENROLLED_TREATMENT, "Startup pref should be in treatment", ) self.check_fission_status( enabled=True, experiment=ExperimentStatus.ENROLLED_TREATMENT, decision="experimentTreatment", ) # If normandy doesn't re-set `ENROLLMENT_STATUS` during the session, it # should be cleared back to disabled after a restart. self.marionette.restart(in_app=True, clean=False) self.assertEqual( self.marionette.get_pref(Prefs.ENROLLMENT_STATUS), ExperimentStatus.UNENROLLED, "Should unenroll dynamic pref after shutdown", ) self.assertEqual( self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS), ExperimentStatus.UNENROLLED, "Should unenroll startup pref after shutdown", ) self.check_fission_status( enabled=False, experiment=ExperimentStatus.UNENROLLED, decision="disabledByDefault", ) # If the browser is started with a customized `fisison.autostart`, # while also enrolled in an experiment, the experiment should be # disqualified at startup. with self.full_restart() as profile: profile.set_preferences( { Prefs.FISSION_AUTOSTART: True, Prefs.STARTUP_ENROLLMENT_STATUS: ExperimentStatus.ENROLLED_TREATMENT, }, filename="prefs.js", ) self.assertEqual( self.marionette.get_pref(Prefs.ENROLLMENT_STATUS), ExperimentStatus.DISQUALIFIED, "Should disqualify dynamic pref on startup", ) self.assertEqual( self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS), ExperimentStatus.DISQUALIFIED, "Should disqualify startup pref on startup", ) self.check_fission_status( enabled=True, experiment=ExperimentStatus.DISQUALIFIED, decision="enabledByUserPref", )