mirror of
https://github.com/ManchildProductions/UXP-Fixed.git
synced 2026-06-18 07:58:38 +00:00
Remove telemetry experiments framework
This commit is contained in:
@@ -1392,13 +1392,6 @@ pref("browser.translation.engine", "bing");
|
||||
// Determines if Telemetry pings can be archived locally.
|
||||
pref("toolkit.telemetry.archive.enabled", true);
|
||||
|
||||
// Telemetry experiments settings.
|
||||
pref("experiments.enabled", true);
|
||||
pref("experiments.manifest.fetchIntervalSeconds", 86400);
|
||||
pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%");
|
||||
// Whether experiments are supported by the current application profile.
|
||||
pref("experiments.supported", true);
|
||||
|
||||
// Enable GMP support in the addon manager.
|
||||
pref("media.gmp-provider.enabled", true);
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"rules": {
|
||||
"no-unused-vars": ["error", {
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
|
||||
"args": "none"
|
||||
}]
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
component {f7800463-3b97-47f9-9341-b7617e6d8d49} ExperimentsService.js
|
||||
contract @mozilla.org/browser/experiments-service;1 {f7800463-3b97-47f9-9341-b7617e6d8d49}
|
||||
category update-timer ExperimentsService @mozilla.org/browser/experiments-service;1,getService,experiments-update-timer,experiments.manifest.fetchIntervalSeconds,86400
|
||||
category profile-after-change ExperimentsService @mozilla.org/browser/experiments-service;1
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
|
||||
"resource:///modules/experiments/Experiments.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
||||
"resource://services-common/utils.js");
|
||||
|
||||
const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
|
||||
const PREF_ACTIVE_EXPERIMENT = "experiments.activeExperiment"; // whether we have an active experiment
|
||||
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
|
||||
const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified";
|
||||
const DELAY_INIT_MS = 30 * 1000;
|
||||
|
||||
// Whether the FHR/Telemetry unification features are enabled.
|
||||
// Changing this pref requires a restart.
|
||||
const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_TELEMETRY_UNIFIED, false);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(
|
||||
this, "gPrefs", () => {
|
||||
return new Preferences();
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(
|
||||
this, "gExperimentsEnabled", () => {
|
||||
// We can enable experiments if either unified Telemetry or FHR is on, and the user
|
||||
// has opted into Telemetry.
|
||||
return gPrefs.get(PREF_EXPERIMENTS_ENABLED, false) &&
|
||||
IS_UNIFIED_TELEMETRY && gPrefs.get(PREF_TELEMETRY_ENABLED, false);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(
|
||||
this, "gActiveExperiment", () => {
|
||||
return gPrefs.get(PREF_ACTIVE_EXPERIMENT);
|
||||
});
|
||||
|
||||
function ExperimentsService() {
|
||||
this._initialized = false;
|
||||
this._delayedInitTimer = null;
|
||||
}
|
||||
|
||||
ExperimentsService.prototype = {
|
||||
classID: Components.ID("{f7800463-3b97-47f9-9341-b7617e6d8d49}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
|
||||
|
||||
notify: function (timer) {
|
||||
if (!gExperimentsEnabled) {
|
||||
return;
|
||||
}
|
||||
if (OS.Constants.Path.profileDir === undefined) {
|
||||
throw Error("Update timer fired before profile was initialized?");
|
||||
}
|
||||
let instance = Experiments.instance();
|
||||
if (instance.isReady) {
|
||||
instance.updateManifest();
|
||||
}
|
||||
},
|
||||
|
||||
_delayedInit: function () {
|
||||
if (!this._initialized) {
|
||||
this._initialized = true;
|
||||
Experiments.instance(); // for side effects
|
||||
}
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "profile-after-change":
|
||||
if (gExperimentsEnabled) {
|
||||
Services.obs.addObserver(this, "quit-application", false);
|
||||
Services.obs.addObserver(this, "sessionstore-state-finalized", false);
|
||||
Services.obs.addObserver(this, "EM-loaded", false);
|
||||
|
||||
if (gActiveExperiment) {
|
||||
this._initialized = true;
|
||||
Experiments.instance(); // for side effects
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "sessionstore-state-finalized":
|
||||
if (!this._initialized) {
|
||||
CommonUtils.namedTimer(this._delayedInit, DELAY_INIT_MS, this, "_delayedInitTimer");
|
||||
}
|
||||
break;
|
||||
case "EM-loaded":
|
||||
if (!this._initialized) {
|
||||
Experiments.instance(); // for side effects
|
||||
this._initialized = true;
|
||||
|
||||
if (this._delayedInitTimer) {
|
||||
this._delayedInitTimer.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "quit-application":
|
||||
Services.obs.removeObserver(this, "quit-application");
|
||||
Services.obs.removeObserver(this, "sessionstore-state-finalized");
|
||||
Services.obs.removeObserver(this, "EM-loaded");
|
||||
if (this._delayedInitTimer) {
|
||||
this._delayedInitTimer.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExperimentsService]);
|
||||
@@ -1,16 +0,0 @@
|
||||
# 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/.
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# This is so hacky. Waiting on bug 988938.
|
||||
addondir = $(srcdir)/test/addons
|
||||
testdir = $(topobjdir)/_tests/xpcshell/browser/experiments/test/xpcshell
|
||||
|
||||
misc:: $(call mkdir_deps,$(testdir))
|
||||
$(EXIT_ON_ERROR) \
|
||||
for dir in $(addondir)/*; do \
|
||||
base=`basename $$dir`; \
|
||||
(cd $$dir && zip -qr $(testdir)/$$base.xpi *); \
|
||||
done
|
||||
@@ -1,13 +0,0 @@
|
||||
=====================
|
||||
Telemetry Experiments
|
||||
=====================
|
||||
|
||||
Telemetry Experiments is a feature of Firefox that allows the installation
|
||||
of add-ons called experiments to a subset of the Firefox population for
|
||||
the purposes of experimenting with changes and collecting data on specific
|
||||
aspects of application usage.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
manifest
|
||||
@@ -1,429 +0,0 @@
|
||||
.. _experiments_manifests:
|
||||
|
||||
=====================
|
||||
Experiments Manifests
|
||||
=====================
|
||||
|
||||
*Experiments Manifests* are documents that describe the set of active
|
||||
experiments a client may run.
|
||||
|
||||
*Experiments Manifests* are fetched periodically by clients. When
|
||||
fetched, clients look at the experiments within the manifest and
|
||||
determine which experiments are applicable. If an experiment is
|
||||
applicable, the client may download and start the experiment.
|
||||
|
||||
Manifest Format
|
||||
===============
|
||||
|
||||
Manifests are JSON documents where the main element is an object.
|
||||
|
||||
The *schema* of the object is versioned and defined by the presence
|
||||
of a top-level ``version`` property, whose integer value is the
|
||||
schema version used by that manifest. Each version is documented
|
||||
in the sections below.
|
||||
|
||||
Version 1
|
||||
---------
|
||||
|
||||
Version 1 is the original manifest format.
|
||||
|
||||
The following properties may exist in the root object:
|
||||
|
||||
experiments
|
||||
An array of objects describing candidate experiments. The format of
|
||||
these objects is documented below.
|
||||
|
||||
An array is used to create an explicit priority of experiments.
|
||||
Experiments listed at the beginning of the array take priority over
|
||||
experiments that follow.
|
||||
|
||||
Experiments Objects
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Each object in the ``experiments`` array may contain the following
|
||||
properties:
|
||||
|
||||
id
|
||||
(required) String identifier of this experiment. The identifier should
|
||||
be treated as opaque by clients. It is used to uniquely identify an
|
||||
experiment for all of time.
|
||||
|
||||
xpiURL
|
||||
(required) String URL of the XPI that implements this experiment.
|
||||
|
||||
If the experiment is activated, the client will download and install this
|
||||
XPI.
|
||||
|
||||
xpiHash
|
||||
(required) String hash of the XPI that implements this experiment.
|
||||
|
||||
The value is composed of a hash identifier followed by a colon
|
||||
followed by the hash value. e.g.
|
||||
`sha1:f677428b9172e22e9911039aef03f3736e7f78a7`. `sha1` and `sha256`
|
||||
are the two supported hashing mechanisms. The hash value is the hex
|
||||
encoding of the binary hash.
|
||||
|
||||
When the client downloads the XPI for the experiment, it should compare
|
||||
the hash of that XPI against this value. If the hashes don't match,
|
||||
the client should not install the XPI.
|
||||
|
||||
Clients may also use this hash as a means of determining when an
|
||||
experiment's XPI has changed and should be refreshed.
|
||||
|
||||
startTime
|
||||
Integer seconds since UNIX epoch that this experiment should
|
||||
start. Clients should not start an experiment if *now()* is less than
|
||||
this value.
|
||||
|
||||
maxStartTime
|
||||
(optional) Integer seconds since UNIX epoch after which this experiment
|
||||
should no longer start.
|
||||
|
||||
Some experiments may wish to impose hard deadlines after which no new
|
||||
clients should activate the experiment. This property may be used to
|
||||
facilitate that.
|
||||
|
||||
endTime
|
||||
Integer seconds since UNIX epoch after which this experiment
|
||||
should no longer run. Clients should cease an experiment when the current
|
||||
time is beyond this value.
|
||||
|
||||
maxActiveSeconds
|
||||
Integer seconds defining the max wall time this experiment should be
|
||||
active for.
|
||||
|
||||
The client should deactivate the experiment this many seconds after
|
||||
initial activation.
|
||||
|
||||
This value only involves wall time, not browser activity or session time.
|
||||
|
||||
appName
|
||||
Array of application names this experiment should run on.
|
||||
|
||||
An application name comes from ``nsIXULAppInfo.name``. It is a value
|
||||
like ``Firefox``, ``Fennec``, or `B2G`.
|
||||
|
||||
The client should compare its application name against the members of
|
||||
this array. If a match is found, the experiment is applicable.
|
||||
|
||||
minVersion
|
||||
(optional) String version number of the minimum application version this
|
||||
experiment should run on.
|
||||
|
||||
A version number is something like ``27.0.0`` or ``28``.
|
||||
|
||||
The client should compare its version number to this value. If the client's
|
||||
version is greater or equal to this version (using a version-aware comparison
|
||||
function), the experiment is applicable.
|
||||
|
||||
If this is not specified, there is no lower bound to versions this
|
||||
experiment should run on.
|
||||
|
||||
maxVersion
|
||||
(optional) String version number of the maximum application version this
|
||||
experiment should run on.
|
||||
|
||||
This is similar to ``minVersion`` except it sets the upper bound for
|
||||
application versions.
|
||||
|
||||
If the client's version is less than or equal to this version, the
|
||||
experiment is applicable.
|
||||
|
||||
If this is not specified, there is no upper bound to versions this
|
||||
experiment should run on.
|
||||
|
||||
version
|
||||
(optional) Array of application versions this experiment should run on.
|
||||
|
||||
This is similar to ``minVersion`` and ``maxVersion`` except only a
|
||||
whitelisted set of specific versions are allowed.
|
||||
|
||||
The client should compare its version to members of this array. If a match
|
||||
is found, the experiment is applicable.
|
||||
|
||||
minBuildID
|
||||
(optional) String minimum Build ID this experiment should run on.
|
||||
|
||||
Build IDs are values like ``201402261424``.
|
||||
|
||||
The client should perform a string comparison of its Build ID against this
|
||||
value. If its value is greater than or equal to this value, the experiment
|
||||
is applicable.
|
||||
|
||||
maxBuildID
|
||||
(optional) String maximum Build ID this experiment should run on.
|
||||
|
||||
This is similar to ``minBuildID`` except it sets the upper bound
|
||||
for Build IDs.
|
||||
|
||||
The client should perform a string comparison of its Build ID against
|
||||
this value. If its value is less than or equal to this value, the
|
||||
experiment is applicable.
|
||||
|
||||
buildIDs
|
||||
(optional) Array of Build IDs this experiment should run on.
|
||||
|
||||
This is similar to ``minBuildID`` and ``maxBuildID`` except only a
|
||||
whitelisted set of Build IDs are considered.
|
||||
|
||||
The client should compare its Build ID to members of this array. If a
|
||||
match is found, the experiment is applicable.
|
||||
|
||||
os
|
||||
(optional) Array of operating system identifiers this experiment should
|
||||
run on.
|
||||
|
||||
Values for this array come from ``nsIXULRuntime.OS``.
|
||||
|
||||
The client will compare its operating system identifier to members
|
||||
of this array. If a match is found, the experiment is applicable to the
|
||||
client.
|
||||
|
||||
channel
|
||||
(optional) Array of release channel identifiers this experiment should run
|
||||
on.
|
||||
|
||||
The client will compare its channel to members of this array. If a match
|
||||
is found, the experiment is applicable.
|
||||
|
||||
If this property is not defined, the client should assume the experiment
|
||||
is to run on all channels.
|
||||
|
||||
locale
|
||||
(optional) Array of locale identifiers this experiment should run on.
|
||||
|
||||
A locale identifier is a string like ``en-US`` or ``zh-CN`` and is
|
||||
obtained by looking at
|
||||
``nsIXULChromeRegistry.getSelectedLocale("global")``.
|
||||
|
||||
The client should compare its locale identifier to members of this array.
|
||||
If a match is found, the experiment is applicable.
|
||||
|
||||
If this property is not defined, the client should assume the experiment
|
||||
is to run on all locales.
|
||||
|
||||
sample
|
||||
(optional) Decimal number indicating the sampling rate for this experiment.
|
||||
|
||||
This will contain a value between ``0.0`` and ``1.0``. The client should
|
||||
generate a random decimal between ``0.0`` and ``1.0``. If the randomly
|
||||
generated number is less than or equal to the value of this field, the
|
||||
experiment is applicable.
|
||||
|
||||
disabled
|
||||
(optional) Boolean value indicating whether an experiment is disabled.
|
||||
|
||||
Normally, experiments are deactivated after a certain time has passed or
|
||||
after the experiment itself determines it no longer needs to run (perhaps
|
||||
it collected sufficient data already).
|
||||
|
||||
This property serves as a backup mechanism to remotely disable an
|
||||
experiment before it was scheduled to be disabled. It can be used to
|
||||
kill experiments that are found to be doing wrong or bad things or that
|
||||
aren't useful.
|
||||
|
||||
If this property is not defined or is false, the client should assume
|
||||
the experiment is active and a candidate for activation.
|
||||
|
||||
frozen
|
||||
(optional) Boolean value indicating this experiment is frozen and no
|
||||
longer accepting new enrollments.
|
||||
|
||||
If a client sees a true value in this field, it should not attempt to
|
||||
activate an experiment.
|
||||
|
||||
jsfilter
|
||||
(optional) JavaScript code that will be evaluated to determine experiment
|
||||
applicability.
|
||||
|
||||
This property contains the string representation of JavaScript code that
|
||||
will be evaluated in a sandboxed environment using JavaScript's
|
||||
``eval()``.
|
||||
|
||||
The string is expected to contain the definition of a JavaScript function
|
||||
``filter(context)``. This function receives as its argument an object
|
||||
holding application state. See the section below for the definition of
|
||||
this object.
|
||||
|
||||
The purpose of this property is to allow experiments to define complex
|
||||
rules and logic for evaluating experiment applicability in a manner
|
||||
that is privacy conscious and doesn't require the transmission of
|
||||
excessive data.
|
||||
|
||||
The return value of this filter indicates whether the experiment is
|
||||
applicable. Functions should return true if the experiment is
|
||||
applicable.
|
||||
|
||||
If an experiment is not applicable, they should throw an Error whose
|
||||
message contains the reason the experiment is not applicable. This
|
||||
message may be logged and sent to remote servers, so it should not
|
||||
contain private or otherwise sensitive data that wouldn't normally
|
||||
be submitted.
|
||||
|
||||
If a falsey (or undefined) value is returned, the client should
|
||||
assume the experiment is not applicable.
|
||||
|
||||
If this property is not defined, the client does not consider a custom
|
||||
JavaScript filter function when determining whether an experiment is
|
||||
applicable.
|
||||
|
||||
JavaScript Filter Context Objects
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The object passed to a ``jsfilter`` ``filter()`` function contains the
|
||||
following properties:
|
||||
|
||||
healthReportSubmissionEnabled
|
||||
This property contains a boolean indicating whether Firefox Health
|
||||
Report has its data submission flag enabled (whether Firefox Health
|
||||
Report is sending data to remote servers).
|
||||
|
||||
healthReportPayload
|
||||
This property contains the current Firefox Health Report payload.
|
||||
|
||||
The payload format is documented at :ref:`healthreport_dataformat`.
|
||||
|
||||
telemetryPayload
|
||||
This property contains the current Telemetry payload.
|
||||
|
||||
The evaluation sandbox for the JavaScript filters may be destroyed
|
||||
immediately after ``filter()`` returns. This function should not assume
|
||||
async code will finish.
|
||||
|
||||
Experiment Applicability and Client Behavior
|
||||
============================================
|
||||
|
||||
The point of an experiment manifest is to define which experiments are
|
||||
available and where and how to run them. This section explains those
|
||||
rules in more detail.
|
||||
|
||||
Many of the properties in *Experiment Objects* are related to determining
|
||||
whether an experiment should run on a given client. This evaluation is
|
||||
performed client side.
|
||||
|
||||
1. Multiple conditions in an experiment
|
||||
---------------------------------------
|
||||
|
||||
If multiple conditions are defined for an experiment, the client should
|
||||
combine each condition with a logical *AND*: all conditions must be
|
||||
satisfied for an experiment to run. If one condition fails, the experiment
|
||||
is not applicable.
|
||||
|
||||
2. Active experiment disappears from manifest
|
||||
---------------------------------------------
|
||||
|
||||
If a specific experiment disappears from the manifest, the client should
|
||||
continue conducting an already-active experiment. Furthermore, the
|
||||
client should remember what the expiration events were for an experiment
|
||||
and honor them.
|
||||
|
||||
The rationale here is that we want to prevent an accidental deletion
|
||||
or temporary failure on the server to inadvertantly deactivate
|
||||
supposed-to-be-active experiments. We also don't want premature deletion
|
||||
of an experiment from the manifest to result in indefinite activation
|
||||
periods.
|
||||
|
||||
3. Inactive experiment disappears from manifest
|
||||
-----------------------------------------------
|
||||
|
||||
If an inactive but scheduled-to-be-active experiment disappears from the
|
||||
manifest, the client should not activate the experiment.
|
||||
|
||||
If that experiment reappears in the manifest, the client should not
|
||||
treat that experiment any differently than any other new experiment. Put
|
||||
another way, the fact an inactive experiment disappears and then
|
||||
reappears should not be significant.
|
||||
|
||||
The rationale here is that server operators should have complete
|
||||
control of an inactive experiment up to it's go-live date.
|
||||
|
||||
4. Re-evaluating applicability on manifest refresh
|
||||
--------------------------------------------------
|
||||
|
||||
When an experiment manifest is refreshed or updated, the client should
|
||||
re-evaluate the applicability of each experiment therein.
|
||||
|
||||
The rationale here is that the server may change the parameters of an
|
||||
experiment and want clients to pick those up.
|
||||
|
||||
5. Activating a previously non-applicable experiment
|
||||
----------------------------------------------------
|
||||
|
||||
If the conditions of an experiment change or the state of the client
|
||||
changes to allow an experiment to transition from previously
|
||||
non-applicable to applicable, the experiment should be activated.
|
||||
|
||||
For example, if a client is running version 28 and the experiment
|
||||
initially requires version 29 or above, the client will not mark the
|
||||
experiment as applicable. But if the client upgrades to version 29 or if
|
||||
the manifest is updated to require 28 or above, the experiment will
|
||||
become applicable.
|
||||
|
||||
6. Deactivating a previously active experiment
|
||||
----------------------------------------------
|
||||
|
||||
If the conditions of an experiment change or the state of the client
|
||||
changes and an active experiment is no longer applicable, that
|
||||
experiment should be deactivated.
|
||||
|
||||
7. Calculation of sampling-based applicability
|
||||
----------------------------------------------
|
||||
|
||||
For calculating sampling-based applicability, the client will associate
|
||||
a random value between ``0.0`` and ``1.0`` for each observed experiment
|
||||
ID. This random value will be generated the first time sampling
|
||||
applicability is evaluated. This random value will be persisted and used
|
||||
in future applicability evaluations for this experiment.
|
||||
|
||||
By saving and re-using the value, the client is able to reliably and
|
||||
consistently evaluate applicability, even if the sampling threshold
|
||||
in the manifest changes.
|
||||
|
||||
Clients should retain the randomly-generated sampling value for
|
||||
experiments that no longer appear in a manifest for a period of at least
|
||||
30 days. The rationale is that if an experiment disappears and reappears
|
||||
from a manifest, the client will not have multiple opportunities to
|
||||
generate a random value that satisfies the sampling criteria.
|
||||
|
||||
8. Incompatible version numbers
|
||||
-------------------------------
|
||||
|
||||
If a client receives a manifest with a version number that it doesn't
|
||||
recognize, it should ignore the manifest.
|
||||
|
||||
9. Usage of old manifests
|
||||
-------------------------
|
||||
|
||||
If a client experiences an error fetching a manifest (server not
|
||||
available) or if the manifest is corrupt, not readable, or compatible,
|
||||
the client may use a previously-fetched (cached) manifest.
|
||||
|
||||
10. Updating XPIs
|
||||
-----------------
|
||||
|
||||
If the URL or hash of an active experiment's XPI changes, the client
|
||||
should fetch the new XPI, uninstall the old XPI, and install the new
|
||||
XPI.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Here is an example manifest::
|
||||
|
||||
{
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
"id": "da9d7f4f-f3f9-4f81-bacd-6f0626ffa360",
|
||||
"xpiURL": "https://experiments.mozilla.org/foo.xpi",
|
||||
"xpiHash": "sha1:cb1eb32b89d86d78b7326f416cf404548c5e0099",
|
||||
"startTime": 1393000000,
|
||||
"endTime": 1394000000,
|
||||
"appName": ["Firefox", "Fennec"],
|
||||
"minVersion": "28",
|
||||
"maxVersion": "30",
|
||||
"os": ["windows", "linux", "osx"],
|
||||
"jsfilter": "function filter(context) { return context.healthReportEnabled; }"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
# 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/.
|
||||
|
||||
HAS_MISC_RULE = True
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'Experiments.manifest',
|
||||
'ExperimentsService.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.experiments += [
|
||||
'Experiments.jsm',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
|
||||
|
||||
SPHINX_TREES['experiments'] = 'docs'
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>test-experiment-1@tests.mozilla.org</em:id>
|
||||
<em:version>1</em:version>
|
||||
<em:type>128</em:type>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test experiment 1</em:name>
|
||||
<em:description>Yet another experiment that experiments experimentally.</em:description>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>test-experiment-1@tests.mozilla.org</em:id>
|
||||
<em:version>1.1</em:version>
|
||||
<em:type>128</em:type>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test experiment 1.1</em:name>
|
||||
<em:description>And yet another experiment that experiments experimentally.</em:description>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>test-experiment-2@tests.mozilla.org</em:id>
|
||||
<em:version>1</em:version>
|
||||
<em:type>128</em:type>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test experiment 2</em:name>
|
||||
<em:description>And yet another experiment that experiments experimentally.</em:description>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
||||
@@ -1,35 +0,0 @@
|
||||
/* exported startup, shutdown, install, uninstall */
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
var gStarted = false;
|
||||
|
||||
function startup(data, reasonCode) {
|
||||
if (gStarted) {
|
||||
return;
|
||||
}
|
||||
gStarted = true;
|
||||
|
||||
// delay realstartup to trigger the race condition
|
||||
Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager)
|
||||
.mainThread.dispatch(realstartup, 0);
|
||||
}
|
||||
|
||||
function realstartup() {
|
||||
let experiments = Experiments.instance();
|
||||
let experiment = experiments._getActiveExperiment();
|
||||
if (experiment.branch) {
|
||||
Cu.reportError("Found pre-existing branch: " + experiment.branch);
|
||||
return;
|
||||
}
|
||||
|
||||
let branch = "racy-set";
|
||||
experiments.setExperimentBranch(experiment.id, branch)
|
||||
.then(null, Cu.reportError);
|
||||
}
|
||||
|
||||
function shutdown() { }
|
||||
function install() { }
|
||||
function uninstall() { }
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>test-experiment-racybranch@tests.mozilla.org</em:id>
|
||||
<em:version>1</em:version>
|
||||
<em:type>128</em:type>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test experiment racybranch</em:name>
|
||||
<em:description>An experiment that sets the experiment branch in a potentially racy way.</em:description>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
||||
@@ -1,15 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../testing/xpcshell/xpcshell.eslintrc.js"
|
||||
],
|
||||
|
||||
"rules": {
|
||||
"no-unused-vars": ["error", {
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
|
||||
"args": "none"
|
||||
}]
|
||||
}
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
"id": "test-experiment-1@tests.mozilla.org",
|
||||
"xpiURL": "https://experiments.mozilla.org/foo.xpi",
|
||||
"xpiHash": "sha1:cb1eb32b89d86d78b7326f416cf404548c5e0099",
|
||||
"startTime": 1393000000,
|
||||
"endTime": 1394000000,
|
||||
"appName": ["Firefox", "Fennec"],
|
||||
"minVersion": "28",
|
||||
"maxVersion": "30",
|
||||
"maxActiveSeconds": 60,
|
||||
"os": ["windows", "linux", "osx"],
|
||||
"channel": ["daily", "weekly", "nightly"],
|
||||
"jsfilter": "function filter(context) { return true; }"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* exported PREF_EXPERIMENTS_ENABLED, PREF_LOGGING_LEVEL, PREF_LOGGING_DUMP
|
||||
PREF_MANIFEST_URI, PREF_FETCHINTERVAL, EXPERIMENT1_ID,
|
||||
EXPERIMENT1_NAME, EXPERIMENT1_XPI_SHA1, EXPERIMENT1A_NAME,
|
||||
EXPERIMENT1A_XPI_SHA1, EXPERIMENT2_ID, EXPERIMENT2_XPI_SHA1,
|
||||
EXPERIMENT3_ID, EXPERIMENT4_ID, FAKE_EXPERIMENTS_1,
|
||||
FAKE_EXPERIMENTS_2, gAppInfo, removeCacheFile, defineNow,
|
||||
futureDate, dateToSeconds, loadAddonManager, promiseRestartManager,
|
||||
startAddonManagerOnly, getExperimentAddons, replaceExperiments */
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://testing-common/AddonManagerTesting.jsm");
|
||||
Cu.import("resource://testing-common/AddonTestUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
|
||||
const PREF_LOGGING_LEVEL = "experiments.logging.level";
|
||||
const PREF_LOGGING_DUMP = "experiments.logging.dump";
|
||||
const PREF_MANIFEST_URI = "experiments.manifest.uri";
|
||||
const PREF_FETCHINTERVAL = "experiments.manifest.fetchIntervalSeconds";
|
||||
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
|
||||
|
||||
function getExperimentPath(base) {
|
||||
let p = do_get_cwd();
|
||||
p.append(base);
|
||||
return p.path;
|
||||
}
|
||||
|
||||
function sha1File(path) {
|
||||
let f = Cc["@mozilla.org/file/local;1"]
|
||||
.createInstance(Ci.nsILocalFile);
|
||||
f.initWithPath(path);
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA1);
|
||||
|
||||
let is = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
is.init(f, -1, 0, 0);
|
||||
hasher.updateFromStream(is, Math.pow(2, 32) - 1);
|
||||
is.close();
|
||||
let bytes = hasher.finish(false);
|
||||
|
||||
let rv = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
rv += ("0" + bytes.charCodeAt(i).toString(16)).substr(-2);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
const EXPERIMENT1_ID = "test-experiment-1@tests.mozilla.org";
|
||||
const EXPERIMENT1_XPI_NAME = "experiment-1.xpi";
|
||||
const EXPERIMENT1_NAME = "Test experiment 1";
|
||||
const EXPERIMENT1_PATH = getExperimentPath(EXPERIMENT1_XPI_NAME);
|
||||
const EXPERIMENT1_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1_PATH);
|
||||
|
||||
|
||||
const EXPERIMENT1A_XPI_NAME = "experiment-1a.xpi";
|
||||
const EXPERIMENT1A_NAME = "Test experiment 1.1";
|
||||
const EXPERIMENT1A_PATH = getExperimentPath(EXPERIMENT1A_XPI_NAME);
|
||||
const EXPERIMENT1A_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1A_PATH);
|
||||
|
||||
const EXPERIMENT2_ID = "test-experiment-2@tests.mozilla.org"
|
||||
const EXPERIMENT2_XPI_NAME = "experiment-2.xpi";
|
||||
const EXPERIMENT2_PATH = getExperimentPath(EXPERIMENT2_XPI_NAME);
|
||||
const EXPERIMENT2_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT2_PATH);
|
||||
|
||||
const EXPERIMENT3_ID = "test-experiment-3@tests.mozilla.org";
|
||||
const EXPERIMENT4_ID = "test-experiment-4@tests.mozilla.org";
|
||||
|
||||
const FAKE_EXPERIMENTS_1 = [
|
||||
{
|
||||
id: "id1",
|
||||
name: "experiment1",
|
||||
description: "experiment 1",
|
||||
active: true,
|
||||
detailUrl: "https://dummy/experiment1",
|
||||
branch: "foo",
|
||||
},
|
||||
];
|
||||
|
||||
const FAKE_EXPERIMENTS_2 = [
|
||||
{
|
||||
id: "id2",
|
||||
name: "experiment2",
|
||||
description: "experiment 2",
|
||||
active: false,
|
||||
endDate: new Date(2014, 2, 11, 2, 4, 35, 42).getTime(),
|
||||
detailUrl: "https://dummy/experiment2",
|
||||
branch: null,
|
||||
},
|
||||
{
|
||||
id: "id1",
|
||||
name: "experiment1",
|
||||
description: "experiment 1",
|
||||
active: false,
|
||||
endDate: new Date(2014, 2, 10, 0, 0, 0, 0).getTime(),
|
||||
detailURL: "https://dummy/experiment1",
|
||||
branch: null,
|
||||
},
|
||||
];
|
||||
|
||||
var gAppInfo = null;
|
||||
|
||||
function removeCacheFile() {
|
||||
let path = OS.Path.join(OS.Constants.Path.profileDir, "experiments.json");
|
||||
return OS.File.remove(path);
|
||||
}
|
||||
|
||||
function patchPolicy(policy, data) {
|
||||
for (let key of Object.keys(data)) {
|
||||
Object.defineProperty(policy, key, {
|
||||
value: data[key],
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function defineNow(policy, time) {
|
||||
patchPolicy(policy, { now: () => new Date(time) });
|
||||
}
|
||||
|
||||
function futureDate(date, offset) {
|
||||
return new Date(date.getTime() + offset);
|
||||
}
|
||||
|
||||
function dateToSeconds(date) {
|
||||
return date.getTime() / 1000;
|
||||
}
|
||||
|
||||
var gGlobalScope = this;
|
||||
function loadAddonManager() {
|
||||
AddonTestUtils.init(gGlobalScope);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||
return AddonTestUtils.promiseStartupManager();
|
||||
}
|
||||
|
||||
const {
|
||||
promiseRestartManager,
|
||||
} = AddonTestUtils;
|
||||
|
||||
// Starts the addon manager without creating app info. We can't directly use
|
||||
// |loadAddonManager| defined above in test_conditions.js as it would make the test fail.
|
||||
function startAddonManagerOnly() {
|
||||
let addonManager = Cc["@mozilla.org/addons/integration;1"]
|
||||
.getService(Ci.nsIObserver)
|
||||
.QueryInterface(Ci.nsITimerCallback);
|
||||
addonManager.observe(null, "addons-startup", null);
|
||||
}
|
||||
|
||||
function getExperimentAddons(previous=false) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
|
||||
if (previous) {
|
||||
deferred.resolve(addons);
|
||||
} else {
|
||||
deferred.resolve(addons.filter(a => !a.appDisabled));
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function createAppInfo(ID="xpcshell@tests.mozilla.org", name="XPCShell",
|
||||
version="1.0", platformVersion="1.0") {
|
||||
AddonTestUtils.createAppInfo(ID, name, version, platformVersion);
|
||||
gAppInfo = AddonTestUtils.appInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the experiments on an Experiments with a new list.
|
||||
*
|
||||
* This monkeypatches getExperiments(). It doesn't monkeypatch the internal
|
||||
* experiments list. So its utility is not as great as it could be.
|
||||
*/
|
||||
function replaceExperiments(experiment, list) {
|
||||
Object.defineProperty(experiment, "getExperiments", {
|
||||
writable: true,
|
||||
value: () => {
|
||||
return Promise.resolve(list);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Experiments require Telemetry to be enabled, and that's not true for debug
|
||||
// builds. Let's just enable it here instead of going through each test.
|
||||
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
|
||||
@@ -1,151 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
||||
var gHttpServer = null;
|
||||
var gHttpRoot = null;
|
||||
var gPolicy = null;
|
||||
|
||||
function ManifestEntry(data) {
|
||||
this.id = data.id || EXPERIMENT1_ID;
|
||||
this.xpiURL = data.xpiURL || gHttpRoot + EXPERIMENT1_XPI_NAME;
|
||||
this.xpiHash = data.xpiHash || EXPERIMENT1_XPI_SHA1;
|
||||
this.appName = data.appName || ["XPCShell"];
|
||||
this.channel = data.appName || ["nightly"];
|
||||
this.startTime = data.startTime || new Date(2010, 0, 1, 12).getTime() / 1000;
|
||||
this.endTime = data.endTime || new Date(9001, 0, 1, 12).getTime() / 1000;
|
||||
this.maxActiveSeconds = data.maxActiveSeconds || 5 * SEC_IN_ONE_DAY;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
loadAddonManager();
|
||||
gPolicy = new Experiments.Policy();
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let port = gHttpServer.identity.primaryPort;
|
||||
gHttpRoot = "http://localhost:" + port + "/";
|
||||
gHttpServer.registerDirectory("/", do_get_cwd());
|
||||
do_register_cleanup(() => gHttpServer.stop(() => {}));
|
||||
|
||||
patchPolicy(gPolicy, {
|
||||
updatechannel: () => "nightly",
|
||||
});
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
});
|
||||
|
||||
function isApplicable(experiment) {
|
||||
let deferred = Promise.defer();
|
||||
experiment.isApplicable().then(
|
||||
result => deferred.resolve({ applicable: true, reason: null }),
|
||||
reason => deferred.resolve({ applicable: false, reason: reason })
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
add_task(function* test_startStop() {
|
||||
let baseDate = new Date(2014, 5, 1, 12);
|
||||
let startDate = futureDate(baseDate, 30 * MS_IN_ONE_DAY);
|
||||
let endDate = futureDate(baseDate, 60 * MS_IN_ONE_DAY);
|
||||
let manifestData = new ManifestEntry({
|
||||
startTime: dateToSeconds(startDate),
|
||||
endTime: dateToSeconds(endDate),
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
});
|
||||
let experiment = new Experiments.ExperimentEntry(gPolicy);
|
||||
experiment.initFromManifestData(manifestData);
|
||||
|
||||
// We need to associate it with the singleton so the onInstallStarted
|
||||
// Addon Manager listener will know about it.
|
||||
Experiments.instance()._experiments = new Map();
|
||||
Experiments.instance()._experiments.set(experiment.id, experiment);
|
||||
|
||||
let result;
|
||||
|
||||
defineNow(gPolicy, baseDate);
|
||||
result = yield isApplicable(experiment);
|
||||
Assert.equal(result.applicable, false, "Experiment should not be applicable.");
|
||||
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
|
||||
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
|
||||
|
||||
defineNow(gPolicy, futureDate(startDate, 5 * MS_IN_ONE_DAY));
|
||||
result = yield isApplicable(experiment);
|
||||
Assert.equal(result.applicable, true, "Experiment should now be applicable.");
|
||||
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
|
||||
|
||||
let changes = yield experiment.start();
|
||||
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
|
||||
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
|
||||
Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
|
||||
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
|
||||
Assert.ok(addons[0].isActive, "The add-on is active.");
|
||||
|
||||
changes = yield experiment.stop();
|
||||
Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on was uninstalled.");
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
|
||||
Assert.equal(addons.length, 0, "Experiment should be uninstalled from the Addon Manager.");
|
||||
|
||||
changes = yield experiment.start();
|
||||
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
|
||||
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
|
||||
Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
|
||||
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
|
||||
Assert.ok(addons[0].isActive, "The add-on is active.");
|
||||
|
||||
result = yield experiment.shouldStop();
|
||||
Assert.equal(result.shouldStop, false, "shouldStop should be false.");
|
||||
Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 1, "Experiment still in add-ons manager.");
|
||||
Assert.ok(addons[0].isActive, "The add-on is still active.");
|
||||
|
||||
defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY));
|
||||
result = yield experiment.shouldStop();
|
||||
Assert.equal(result.shouldStop, true, "shouldStop should now be true.");
|
||||
changes = yield experiment.stop();
|
||||
Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on should be uninstalled.");
|
||||
Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "Experiment add-on is uninstalled.");
|
||||
|
||||
// Ensure hash validation works.
|
||||
// We set an incorrect hash and expect the install to fail.
|
||||
experiment._manifestData.xpiHash = "sha1:41014dcc66b4dcedcd973491a1530a32f0517d8a";
|
||||
let errored = false;
|
||||
try {
|
||||
yield experiment.start();
|
||||
} catch (ex) {
|
||||
errored = true;
|
||||
}
|
||||
Assert.ok(experiment._failedStart, "Experiment failed to start.");
|
||||
Assert.ok(errored, "start() threw an exception.");
|
||||
|
||||
// Make sure "ignore hashes" mode works.
|
||||
gPolicy.ignoreHashes = true;
|
||||
changes = yield experiment.start();
|
||||
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL);
|
||||
yield experiment.stop();
|
||||
gPolicy.ignoreHashes = false;
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,399 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
|
||||
"resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
const MANIFEST_HANDLER = "manifests/handler";
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
||||
var gHttpServer = null;
|
||||
var gHttpRoot = null;
|
||||
var gDataRoot = null;
|
||||
var gPolicy = null;
|
||||
var gManifestObject = null;
|
||||
var gManifestHandlerURI = null;
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
loadAddonManager();
|
||||
yield removeCacheFile();
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let port = gHttpServer.identity.primaryPort;
|
||||
gHttpRoot = "http://localhost:" + port + "/";
|
||||
gDataRoot = gHttpRoot + "data/";
|
||||
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
|
||||
gHttpServer.registerDirectory("/data/", do_get_cwd());
|
||||
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify(gManifestObject));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
do_register_cleanup(() => gHttpServer.stop(() => {}));
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
|
||||
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
|
||||
|
||||
gPolicy = new Experiments.Policy();
|
||||
patchPolicy(gPolicy, {
|
||||
updatechannel: () => "nightly",
|
||||
oneshotTimer: (callback, timeout, thisObj, name) => {},
|
||||
});
|
||||
});
|
||||
|
||||
function checkExperimentListsEqual(list, list2) {
|
||||
Assert.equal(list.length, list2.length, "Lists should have the same length.")
|
||||
|
||||
for (let i=0; i<list.length; ++i) {
|
||||
for (let k of Object.keys(list[i])) {
|
||||
Assert.equal(list[i][k], list2[i][k],
|
||||
"Field '" + k + "' should match for list entry " + i + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkExperimentSerializations(experimentEntryIterator) {
|
||||
for (let experiment of experimentEntryIterator) {
|
||||
let experiment2 = new Experiments.ExperimentEntry(gPolicy);
|
||||
let jsonStr = JSON.stringify(experiment.toJSON());
|
||||
Assert.ok(experiment2.initFromCacheData(JSON.parse(jsonStr)),
|
||||
"Should have initialized successfully from JSON serialization.");
|
||||
Assert.equal(JSON.stringify(experiment), JSON.stringify(experiment2),
|
||||
"Object stringifications should match.");
|
||||
}
|
||||
}
|
||||
|
||||
function validateCache(cachedExperiments, experimentIds) {
|
||||
let cachedExperimentIds = new Set(cachedExperiments);
|
||||
Assert.equal(cachedExperimentIds.size, experimentIds.length,
|
||||
"The number of cached experiments does not match with the provided list");
|
||||
for (let id of experimentIds) {
|
||||
Assert.ok(cachedExperimentIds.has(id), "The cache must contain the experiment with id " + id);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up an experiments instance and check if it is properly restored from cache.
|
||||
|
||||
add_task(function* test_cache() {
|
||||
// The manifest data we test with.
|
||||
|
||||
gManifestObject = {
|
||||
"version": 1,
|
||||
experiments: [
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
|
||||
xpiHash: EXPERIMENT1_XPI_SHA1,
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
|
||||
xpiHash: EXPERIMENT2_XPI_SHA1,
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT3_ID,
|
||||
xpiURL: "https://inval.id/foo.xpi",
|
||||
xpiHash: "sha1:0000000000000000000000000000000000000000",
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Setup dates for the experiments.
|
||||
|
||||
let baseDate = new Date(2014, 5, 1, 12);
|
||||
let startDates = [];
|
||||
let endDates = [];
|
||||
|
||||
for (let i=0; i<gManifestObject.experiments.length; ++i) {
|
||||
let experiment = gManifestObject.experiments[i];
|
||||
startDates.push(futureDate(baseDate, (50 + (150 * i)) * MS_IN_ONE_DAY));
|
||||
endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY));
|
||||
experiment.startTime = dateToSeconds(startDates[i]);
|
||||
experiment.endTime = dateToSeconds(endDates[i]);
|
||||
}
|
||||
|
||||
// Data to compare the result of Experiments.getExperiments() against.
|
||||
|
||||
let experimentListData = [
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
name: "Test experiment 2",
|
||||
description: "And yet another experiment that experiments experimentally.",
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
name: EXPERIMENT1_NAME,
|
||||
description: "Yet another experiment that experiments experimentally.",
|
||||
},
|
||||
];
|
||||
|
||||
// Trigger update & re-init, clock set to before any activation.
|
||||
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
let experiments = new Experiments.Experiments(gPolicy);
|
||||
yield experiments.updateManifest();
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
checkExperimentSerializations(experiments._experiments.values());
|
||||
|
||||
yield promiseRestartManager();
|
||||
experiments = new Experiments.Experiments(gPolicy);
|
||||
|
||||
yield experiments._run();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
checkExperimentSerializations(experiments._experiments.values());
|
||||
|
||||
// Re-init, clock set for experiment 1 to start.
|
||||
|
||||
now = futureDate(startDates[0], 5 * MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield promiseRestartManager();
|
||||
experiments = new Experiments.Experiments(gPolicy);
|
||||
yield experiments._run();
|
||||
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
|
||||
|
||||
experimentListData[1].active = true;
|
||||
experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
|
||||
checkExperimentListsEqual(experimentListData.slice(1), list);
|
||||
checkExperimentSerializations(experiments._experiments.values());
|
||||
|
||||
let branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
|
||||
Assert.strictEqual(branch, null);
|
||||
|
||||
yield experiments.setExperimentBranch(EXPERIMENT1_ID, "testbranch");
|
||||
branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
|
||||
Assert.strictEqual(branch, "testbranch");
|
||||
|
||||
// Re-init, clock set for experiment 1 to stop.
|
||||
|
||||
now = futureDate(now, 20 * MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield promiseRestartManager();
|
||||
experiments = new Experiments.Experiments(gPolicy);
|
||||
yield experiments._run();
|
||||
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
|
||||
|
||||
experimentListData[1].active = false;
|
||||
experimentListData[1].endDate = now.getTime();
|
||||
checkExperimentListsEqual(experimentListData.slice(1), list);
|
||||
checkExperimentSerializations(experiments._experiments.values());
|
||||
|
||||
branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
|
||||
Assert.strictEqual(branch, "testbranch");
|
||||
|
||||
// Re-init, clock set for experiment 2 to start.
|
||||
|
||||
now = futureDate(startDates[1], 20 * MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield promiseRestartManager();
|
||||
experiments = new Experiments.Experiments(gPolicy);
|
||||
yield experiments._run();
|
||||
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
|
||||
|
||||
experimentListData[0].active = true;
|
||||
experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
|
||||
checkExperimentListsEqual(experimentListData, list);
|
||||
checkExperimentSerializations(experiments._experiments.values());
|
||||
|
||||
// Re-init, clock set for experiment 2 to stop.
|
||||
|
||||
now = futureDate(now, 20 * MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield promiseRestartManager();
|
||||
experiments = new Experiments.Experiments(gPolicy);
|
||||
yield experiments._run();
|
||||
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
|
||||
|
||||
experimentListData[0].active = false;
|
||||
experimentListData[0].endDate = now.getTime();
|
||||
checkExperimentListsEqual(experimentListData, list);
|
||||
checkExperimentSerializations(experiments._experiments.values());
|
||||
|
||||
// Cleanup.
|
||||
|
||||
yield experiments._toggleExperimentsEnabled(false);
|
||||
yield promiseRestartManager();
|
||||
yield removeCacheFile();
|
||||
});
|
||||
|
||||
add_task(function* test_expiration() {
|
||||
// The manifest data we test with.
|
||||
gManifestObject = {
|
||||
"version": 1,
|
||||
experiments: [
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
|
||||
xpiHash: EXPERIMENT1_XPI_SHA1,
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
|
||||
xpiHash: EXPERIMENT2_XPI_SHA1,
|
||||
maxActiveSeconds: 50 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
// The 3rd experiment will never run, so it's ok to use experiment's 2 data.
|
||||
{
|
||||
id: EXPERIMENT3_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
|
||||
xpiHash: EXPERIMENT2_XPI_SHA1,
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
// Data to compare the result of Experiments.getExperiments() against.
|
||||
let experimentListData = [
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
name: "Test experiment 2",
|
||||
description: "And yet another experiment that experiments experimentally.",
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
name: EXPERIMENT1_NAME,
|
||||
description: "Yet another experiment that experiments experimentally.",
|
||||
},
|
||||
];
|
||||
|
||||
// Setup dates for the experiments.
|
||||
let baseDate = new Date(2014, 5, 1, 12);
|
||||
let startDates = [];
|
||||
let endDates = [];
|
||||
|
||||
for (let i=0; i<gManifestObject.experiments.length; ++i) {
|
||||
let experiment = gManifestObject.experiments[i];
|
||||
// Spread out experiments in time so that one experiment can end and expire while
|
||||
// the next is still running.
|
||||
startDates.push(futureDate(baseDate, (50 + (200 * i)) * MS_IN_ONE_DAY));
|
||||
endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY));
|
||||
experiment.startTime = dateToSeconds(startDates[i]);
|
||||
experiment.endTime = dateToSeconds(endDates[i]);
|
||||
}
|
||||
|
||||
let now = null;
|
||||
let experiments = null;
|
||||
|
||||
let setDateAndRestartExperiments = new Task.async(function* (newDate) {
|
||||
now = newDate;
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield promiseRestartManager();
|
||||
experiments = new Experiments.Experiments(gPolicy);
|
||||
yield experiments._run();
|
||||
});
|
||||
|
||||
// Trigger update & re-init, clock set to before any activation.
|
||||
now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
experiments = new Experiments.Experiments(gPolicy);
|
||||
yield experiments.updateManifest();
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
// Re-init, clock set for experiment 1 to start...
|
||||
yield setDateAndRestartExperiments(startDates[0]);
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "The first experiment should have started.");
|
||||
|
||||
// ... init again, and set the clock so that the first experiment ends.
|
||||
yield setDateAndRestartExperiments(endDates[0]);
|
||||
|
||||
// The experiment just ended, it should still be in the cache, but marked
|
||||
// as finished.
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
|
||||
|
||||
experimentListData[1].active = false;
|
||||
experimentListData[1].endDate = now.getTime();
|
||||
checkExperimentListsEqual(experimentListData.slice(1), list);
|
||||
validateCache([...experiments._experiments.keys()], [EXPERIMENT1_ID, EXPERIMENT2_ID, EXPERIMENT3_ID]);
|
||||
|
||||
// Start the second experiment.
|
||||
yield setDateAndRestartExperiments(startDates[1]);
|
||||
|
||||
// The experiments cache should contain the finished experiment and the
|
||||
// one that's still running.
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
|
||||
|
||||
experimentListData[0].active = true;
|
||||
experimentListData[0].endDate = now.getTime() + 50 * MS_IN_ONE_DAY;
|
||||
checkExperimentListsEqual(experimentListData, list);
|
||||
|
||||
// Move the clock in the future, just 31 days after the start date of the second experiment,
|
||||
// so that the cache for the first experiment expires and the second experiment is still running.
|
||||
yield setDateAndRestartExperiments(futureDate(startDates[1], 31 * MS_IN_ONE_DAY));
|
||||
validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]);
|
||||
|
||||
// Make sure that the expired experiment is not reported anymore.
|
||||
let history = yield experiments.getExperiments();
|
||||
Assert.equal(history.length, 1, "Experiments older than 180 days must be removed from the cache.");
|
||||
|
||||
// Test that we don't write expired experiments in the cache.
|
||||
yield setDateAndRestartExperiments(now);
|
||||
validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]);
|
||||
|
||||
// The first experiment should be expired and not in the cache, it ended more than
|
||||
// 180 days ago. We should see the one still running in the cache.
|
||||
history = yield experiments.getExperiments();
|
||||
Assert.equal(history.length, 1, "Expired experiments must not be saved to cache.");
|
||||
checkExperimentListsEqual(experimentListData.slice(0, 1), history);
|
||||
|
||||
// Test that experiments that are cached locally but never ran are removed from cache
|
||||
// when they are removed from the manifest (this is cached data, not really history).
|
||||
gManifestObject["experiments"] = gManifestObject["experiments"].slice(1, 1);
|
||||
yield experiments.updateManifest();
|
||||
validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID]);
|
||||
|
||||
// Cleanup.
|
||||
yield experiments._toggleExperimentsEnabled(false);
|
||||
yield promiseRestartManager();
|
||||
yield removeCacheFile();
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
const MANIFEST_HANDLER = "manifests/handler";
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
||||
var gHttpServer = null;
|
||||
var gHttpRoot = null;
|
||||
var gDataRoot = null;
|
||||
var gPolicy = null;
|
||||
var gManifestObject = null;
|
||||
var gManifestHandlerURI = null;
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
loadAddonManager();
|
||||
yield removeCacheFile();
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let port = gHttpServer.identity.primaryPort;
|
||||
gHttpRoot = "http://localhost:" + port + "/";
|
||||
gDataRoot = gHttpRoot + "data/";
|
||||
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
|
||||
gHttpServer.registerDirectory("/data/", do_get_cwd());
|
||||
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify(gManifestObject));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
do_register_cleanup(() => gHttpServer.stop(() => {}));
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
|
||||
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
|
||||
|
||||
let ExperimentsScope = Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
let Experiments = ExperimentsScope.Experiments;
|
||||
|
||||
gPolicy = new Experiments.Policy();
|
||||
patchPolicy(gPolicy, {
|
||||
updatechannel: () => "nightly",
|
||||
delayCacheWrite: (promise) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
promise.then(
|
||||
(result) => { setTimeout(() => resolve(result), 500); },
|
||||
(err) => { reject(err); }
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
let now = new Date(2014, 5, 1, 12);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
let experimentName = "experiment-racybranch.xpi";
|
||||
let experimentPath = getExperimentPath(experimentName);
|
||||
let experimentHash = "sha1:" + sha1File(experimentPath);
|
||||
|
||||
gManifestObject = {
|
||||
version: 1,
|
||||
experiments: [
|
||||
{
|
||||
id: "test-experiment-racybranch@tests.mozilla.org",
|
||||
xpiURL: gDataRoot + "experiment-racybranch.xpi",
|
||||
xpiHash: experimentHash,
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
startTime: dateToSeconds(futureDate(now, -MS_IN_ONE_DAY)),
|
||||
endTime: dateToSeconds(futureDate(now, MS_IN_ONE_DAY)),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
do_print("gManifestObject: " + JSON.stringify(gManifestObject));
|
||||
|
||||
// In order for the addon manager to work properly, we hack
|
||||
// Experiments.instance which is used by the XPIProvider
|
||||
let experiments = new Experiments.Experiments(gPolicy);
|
||||
Assert.strictEqual(ExperimentsScope.gExperiments, null);
|
||||
ExperimentsScope.gExperiments = experiments;
|
||||
|
||||
yield experiments.updateManifest();
|
||||
let active = experiments._getActiveExperiment();
|
||||
Assert.ok(active);
|
||||
Assert.equal(active.branch, "racy-set");
|
||||
Assert.ok(!experiments._dirty);
|
||||
});
|
||||
@@ -1,325 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
|
||||
var gPolicy = null;
|
||||
|
||||
function ManifestEntry(data) {
|
||||
this.id = EXPERIMENT1_ID;
|
||||
this.xpiURL = "http://localhost:1/dummy.xpi";
|
||||
this.xpiHash = EXPERIMENT1_XPI_SHA1;
|
||||
this.startTime = new Date(2010, 0, 1, 12).getTime() / 1000;
|
||||
this.endTime = new Date(9001, 0, 1, 12).getTime() / 1000;
|
||||
this.maxActiveSeconds = SEC_IN_ONE_DAY;
|
||||
this.appName = ["XPCShell"];
|
||||
this.channel = ["nightly"];
|
||||
|
||||
data = data || {};
|
||||
for (let k of Object.keys(data)) {
|
||||
this[k] = data[k];
|
||||
}
|
||||
|
||||
if (!this.endTime) {
|
||||
this.endTime = this.startTime + 5 * SEC_IN_ONE_DAY;
|
||||
}
|
||||
}
|
||||
|
||||
function applicableFromManifestData(data, policy) {
|
||||
let manifestData = new ManifestEntry(data);
|
||||
let entry = new Experiments.ExperimentEntry(policy);
|
||||
entry.initFromManifestData(manifestData);
|
||||
return entry.isApplicable();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
createAppInfo();
|
||||
do_get_profile();
|
||||
startAddonManagerOnly();
|
||||
yield TelemetryController.testSetup();
|
||||
gPolicy = new Experiments.Policy();
|
||||
|
||||
patchPolicy(gPolicy, {
|
||||
updatechannel: () => "nightly",
|
||||
locale: () => "en-US",
|
||||
random: () => 0.5,
|
||||
});
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
});
|
||||
|
||||
function arraysEqual(a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i=0; i<a.length; ++i) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function exists solely to be .toSource()d
|
||||
const sanityFilter = function filter(c) {
|
||||
if (c.telemetryEnvironment === undefined) {
|
||||
throw Error("No .telemetryEnvironment");
|
||||
}
|
||||
if (c.telemetryEnvironment.build == undefined) {
|
||||
throw Error("No .telemetryEnvironment.build");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Utility function to generate build ID for previous/next date.
|
||||
function addDate(buildId, diff) {
|
||||
let m = /^([0-9]{4})([0-9]{2})([0-9]{2})(.*)$/.exec(buildId);
|
||||
if (!m) {
|
||||
throw Error("Unsupported build ID: " + buildId);
|
||||
}
|
||||
let year = Number.parseInt(m[1], 10);
|
||||
let month = Number.parseInt(m[2], 10);
|
||||
let date = Number.parseInt(m[3], 10);
|
||||
let remainingParts = m[4];
|
||||
|
||||
let d = new Date();
|
||||
d.setUTCFullYear(year, month - 1, date);
|
||||
d.setTime(d.getTime() + diff * 24 * 60 * 60 * 1000);
|
||||
|
||||
let yearStr = String(d.getUTCFullYear());
|
||||
let monthStr = ("0" + String(d.getUTCMonth() + 1)).slice(-2);
|
||||
let dateStr = ("0" + String(d.getUTCDate())).slice(-2);
|
||||
return yearStr + monthStr + dateStr + remainingParts;
|
||||
}
|
||||
function prevDate(buildId) {
|
||||
return addDate(buildId, -1);
|
||||
}
|
||||
function nextDate(buildId) {
|
||||
return addDate(buildId, 1);
|
||||
}
|
||||
|
||||
add_task(function* test_simpleFields() {
|
||||
let testData = [
|
||||
// "expected applicable?", failure reason or null, manifest data
|
||||
|
||||
// misc. environment
|
||||
|
||||
[false, ["appName"], {appName: []}],
|
||||
[false, ["appName"], {appName: ["foo", gAppInfo.name + "-invalid"]}],
|
||||
[true, null, {appName: ["not-an-app-name", gAppInfo.name]}],
|
||||
|
||||
[false, ["os"], {os: []}],
|
||||
[false, ["os"], {os: ["42", "abcdef"]}],
|
||||
[true, null, {os: [gAppInfo.OS, "plan9"]}],
|
||||
|
||||
[false, ["channel"], {channel: []}],
|
||||
[false, ["channel"], {channel: ["foo", gPolicy.updatechannel() + "-invalid"]}],
|
||||
[true, null, {channel: ["not-a-channel", gPolicy.updatechannel()]}],
|
||||
|
||||
[false, ["locale"], {locale: []}],
|
||||
[false, ["locale"], {locale: ["foo", gPolicy.locale + "-invalid"]}],
|
||||
[true, null, {locale: ["not-a-locale", gPolicy.locale()]}],
|
||||
|
||||
// version
|
||||
|
||||
[false, ["version"], {version: []}],
|
||||
[false, ["version"], {version: ["-1", gAppInfo.version + "-invalid", "asdf", "0,4", "99.99", "0.1.1.1"]}],
|
||||
[true, null, {version: ["99999999.999", "-1", gAppInfo.version]}],
|
||||
|
||||
[false, ["minVersion"], {minVersion: "1.0.1"}],
|
||||
[true, null, {minVersion: "1.0b1"}],
|
||||
[true, null, {minVersion: "1.0"}],
|
||||
[true, null, {minVersion: "0.9"}],
|
||||
|
||||
[false, ["maxVersion"], {maxVersion: "0.1"}],
|
||||
[false, ["maxVersion"], {maxVersion: "0.9.9"}],
|
||||
[false, ["maxVersion"], {maxVersion: "1.0b1"}],
|
||||
[true, ["maxVersion"], {maxVersion: "1.0"}],
|
||||
[true, ["maxVersion"], {maxVersion: "1.7pre"}],
|
||||
|
||||
// build id
|
||||
|
||||
[false, ["buildIDs"], {buildIDs: []}],
|
||||
[false, ["buildIDs"], {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID + "-invalid"]}],
|
||||
[true, null, {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID]}],
|
||||
|
||||
[true, null, {minBuildID: prevDate(gAppInfo.platformBuildID)}],
|
||||
[true, null, {minBuildID: gAppInfo.platformBuildID}],
|
||||
[false, ["minBuildID"], {minBuildID: nextDate(gAppInfo.platformBuildID)}],
|
||||
|
||||
[false, ["maxBuildID"], {maxBuildID: prevDate(gAppInfo.platformBuildID)}],
|
||||
[true, null, {maxBuildID: gAppInfo.platformBuildID}],
|
||||
[true, null, {maxBuildID: nextDate(gAppInfo.platformBuildID)}],
|
||||
|
||||
// sample
|
||||
|
||||
[false, ["sample"], {sample: -1 }],
|
||||
[false, ["sample"], {sample: 0.0}],
|
||||
[false, ["sample"], {sample: 0.1}],
|
||||
[true, null, {sample: 0.5}],
|
||||
[true, null, {sample: 0.6}],
|
||||
[true, null, {sample: 1.0}],
|
||||
[true, null, {sample: 0.5}],
|
||||
|
||||
// experiment control
|
||||
|
||||
[false, ["disabled"], {disabled: true}],
|
||||
[true, null, {disabled: false}],
|
||||
|
||||
[false, ["frozen"], {frozen: true}],
|
||||
[true, null, {frozen: false}],
|
||||
|
||||
[false, null, {frozen: true, disabled: true}],
|
||||
[false, null, {frozen: true, disabled: false}],
|
||||
[false, null, {frozen: false, disabled: true}],
|
||||
[true, null, {frozen: false, disabled: false}],
|
||||
|
||||
// jsfilter
|
||||
|
||||
[true, null, {jsfilter: "function filter(c) { return true; }"}],
|
||||
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { return false; }"}],
|
||||
[true, null, {jsfilter: "function filter(c) { return 123; }"}], // truthy
|
||||
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { return ''; }"}], // falsy
|
||||
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { var a = []; }"}], // undefined
|
||||
[false, ["jsfilter-threw", "some error"], {jsfilter: "function filter(c) { throw new Error('some error'); }"}],
|
||||
[false, ["jsfilter-evalfailed"], {jsfilter: "123, this won't work"}],
|
||||
[true, null, {jsfilter: "var filter = " + sanityFilter.toSource()}],
|
||||
];
|
||||
|
||||
for (let i=0; i<testData.length; ++i) {
|
||||
let entry = testData[i];
|
||||
let applicable;
|
||||
let reason = null;
|
||||
|
||||
yield applicableFromManifestData(entry[2], gPolicy).then(
|
||||
value => applicable = value,
|
||||
value => {
|
||||
applicable = false;
|
||||
reason = value;
|
||||
}
|
||||
);
|
||||
|
||||
Assert.equal(applicable, entry[0],
|
||||
"Experiment entry applicability should match for test "
|
||||
+ i + ": " + JSON.stringify(entry[2]));
|
||||
|
||||
let expectedReason = entry[1];
|
||||
if (!applicable && expectedReason) {
|
||||
Assert.ok(arraysEqual(reason, expectedReason),
|
||||
"Experiment rejection reasons should match for test " + i + ". "
|
||||
+ "Got " + JSON.stringify(reason) + ", expected "
|
||||
+ JSON.stringify(expectedReason));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_times() {
|
||||
let now = new Date(2014, 5, 6, 12);
|
||||
let nowSec = now.getTime() / 1000;
|
||||
let testData = [
|
||||
// "expected applicable?", rejection reason or null, fake now date, manifest data
|
||||
|
||||
// start time
|
||||
|
||||
[true, null, now,
|
||||
{startTime: nowSec - 5 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[true, null, now,
|
||||
{startTime: nowSec,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[false, "startTime", now,
|
||||
{startTime: nowSec + 5 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
|
||||
// end time
|
||||
|
||||
[false, "endTime", now,
|
||||
{startTime: nowSec - 5 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec - 10 * SEC_IN_ONE_DAY}],
|
||||
[false, "endTime", now,
|
||||
{startTime: nowSec - 5 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec - 5 * SEC_IN_ONE_DAY}],
|
||||
|
||||
// max start time
|
||||
|
||||
[false, "maxStartTime", now,
|
||||
{maxStartTime: nowSec - 15 * SEC_IN_ONE_DAY,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[false, "maxStartTime", now,
|
||||
{maxStartTime: nowSec - 1 * SEC_IN_ONE_DAY,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[false, "maxStartTime", now,
|
||||
{maxStartTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[true, null, now,
|
||||
{maxStartTime: nowSec,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[true, null, now,
|
||||
{maxStartTime: nowSec + 1 * SEC_IN_ONE_DAY,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
|
||||
// max active seconds
|
||||
|
||||
[true, null, now,
|
||||
{maxActiveSeconds: 5 * SEC_IN_ONE_DAY,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[true, null, now,
|
||||
{maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[true, null, now,
|
||||
{maxActiveSeconds: 15 * SEC_IN_ONE_DAY,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
[true, null, now,
|
||||
{maxActiveSeconds: 20 * SEC_IN_ONE_DAY,
|
||||
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
|
||||
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
|
||||
];
|
||||
|
||||
for (let i=0; i<testData.length; ++i) {
|
||||
let entry = testData[i];
|
||||
let applicable;
|
||||
let reason = null;
|
||||
defineNow(gPolicy, entry[2]);
|
||||
|
||||
yield applicableFromManifestData(entry[3], gPolicy).then(
|
||||
value => applicable = value,
|
||||
value => {
|
||||
applicable = false;
|
||||
reason = value;
|
||||
}
|
||||
);
|
||||
|
||||
Assert.equal(applicable, entry[0],
|
||||
"Experiment entry applicability should match for test "
|
||||
+ i + ": " + JSON.stringify([entry[2], entry[3]]));
|
||||
if (!applicable && entry[1]) {
|
||||
Assert.equal(reason, entry[1], "Experiment rejection reason should match for test " + i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_shutdown() {
|
||||
yield TelemetryController.testShutdown();
|
||||
});
|
||||
@@ -1,180 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://testing-common/AddonManagerTesting.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
|
||||
"resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
const MANIFEST_HANDLER = "manifests/handler";
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
||||
var gHttpServer = null;
|
||||
var gHttpRoot = null;
|
||||
var gDataRoot = null;
|
||||
var gPolicy = null;
|
||||
var gManifestObject = null;
|
||||
var gManifestHandlerURI = null;
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
loadAddonManager();
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let port = gHttpServer.identity.primaryPort;
|
||||
gHttpRoot = "http://localhost:" + port + "/";
|
||||
gDataRoot = gHttpRoot + "data/";
|
||||
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
|
||||
gHttpServer.registerDirectory("/data/", do_get_cwd());
|
||||
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify(gManifestObject));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
do_register_cleanup(() => gHttpServer.stop(() => {}));
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
|
||||
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
|
||||
|
||||
gPolicy = new Experiments.Policy();
|
||||
patchPolicy(gPolicy, {
|
||||
updatechannel: () => "nightly",
|
||||
oneshotTimer: (callback, timeout, thisObj, name) => {},
|
||||
});
|
||||
});
|
||||
|
||||
// Test disabling the feature stops current and future experiments.
|
||||
|
||||
add_task(function* test_disableExperiments() {
|
||||
const OBSERVER_TOPIC = "experiments-changed";
|
||||
let observerFireCount = 0;
|
||||
let expectedObserverFireCount = 0;
|
||||
let observer = () => ++observerFireCount;
|
||||
Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
|
||||
|
||||
// Dates the following tests are based on.
|
||||
|
||||
let baseDate = new Date(2014, 5, 1, 12);
|
||||
let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
|
||||
let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
|
||||
let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY);
|
||||
let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY);
|
||||
|
||||
// The manifest data we test with.
|
||||
|
||||
gManifestObject = {
|
||||
"version": 1,
|
||||
experiments: [
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
|
||||
xpiHash: EXPERIMENT2_XPI_SHA1,
|
||||
startTime: dateToSeconds(startDate2),
|
||||
endTime: dateToSeconds(endDate2),
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
|
||||
xpiHash: EXPERIMENT1_XPI_SHA1,
|
||||
startTime: dateToSeconds(startDate1),
|
||||
endTime: dateToSeconds(endDate1),
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let experiments = new Experiments.Experiments(gPolicy);
|
||||
|
||||
// Trigger update, clock set to before any activation.
|
||||
// Use updateManifest() to provide for coverage of that path.
|
||||
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
|
||||
|
||||
// Trigger update, clock set for experiment 1 to start.
|
||||
|
||||
now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
|
||||
Assert.equal(list[0].active, true, "Experiment should be active.");
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 1, "An experiment add-on was installed.");
|
||||
|
||||
// Disable the experiments feature. Check that we stop the running experiment.
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, false);
|
||||
yield experiments._mainTask;
|
||||
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
|
||||
Assert.equal(list[0].active, false, "Experiment entry should not be active.");
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled.");
|
||||
|
||||
// Trigger update, clock set for experiment 2 to start. Verify we don't start it.
|
||||
|
||||
now = startDate2;
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
try {
|
||||
yield experiments.updateManifest();
|
||||
} catch (e) {
|
||||
// This exception is expected, we rethrow everything else
|
||||
if (e.message != "experiments are disabled") {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
experiments.notify();
|
||||
yield experiments._mainTask;
|
||||
|
||||
Assert.equal(observerFireCount, expectedObserverFireCount,
|
||||
"Experiments observer should not have been called.");
|
||||
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should still have 1 entry.");
|
||||
Assert.equal(list[0].active, false, "Experiment entry should not be active.");
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "There should still be no experiment add-on installed.");
|
||||
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield promiseRestartManager();
|
||||
yield removeCacheFile();
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
var gHttpServer = null;
|
||||
var gHttpRoot = null;
|
||||
var gPolicy = new Experiments.Policy();
|
||||
|
||||
function run_test() {
|
||||
loadAddonManager();
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let port = gHttpServer.identity.primaryPort;
|
||||
gHttpRoot = "http://localhost:" + port + "/";
|
||||
gHttpServer.registerDirectory("/", do_get_cwd());
|
||||
do_register_cleanup(() => gHttpServer.stop(() => {}));
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
|
||||
patchPolicy(gPolicy, {
|
||||
updatechannel: () => "nightly",
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_fetchAndCache() {
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest");
|
||||
let ex = new Experiments.Experiments(gPolicy);
|
||||
|
||||
Assert.equal(ex._experiments, null, "There should be no cached experiments yet.");
|
||||
yield ex.updateManifest();
|
||||
Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now.");
|
||||
|
||||
yield promiseRestartManager();
|
||||
});
|
||||
|
||||
add_task(function* test_checkCache() {
|
||||
let ex = new Experiments.Experiments(gPolicy);
|
||||
yield ex.notify();
|
||||
Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now.");
|
||||
|
||||
yield promiseRestartManager();
|
||||
});
|
||||
|
||||
add_task(function* test_fetchInvalid() {
|
||||
yield removeCacheFile();
|
||||
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest");
|
||||
let ex = new Experiments.Experiments(gPolicy);
|
||||
yield ex.updateManifest();
|
||||
Assert.notEqual(ex._experiments.size, 0, "There should be experiments");
|
||||
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "invalid.manifest");
|
||||
yield ex.updateManifest()
|
||||
Assert.notEqual(ex._experiments.size, 0, "There should still be experiments: fetch failure shouldn't remove them.");
|
||||
|
||||
yield promiseRestartManager();
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
const MANIFEST_HANDLER = "manifests/handler";
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
loadAddonManager();
|
||||
do_get_profile();
|
||||
|
||||
let httpServer = new HttpServer();
|
||||
httpServer.start(-1);
|
||||
let port = httpServer.identity.primaryPort;
|
||||
let httpRoot = "http://localhost:" + port + "/";
|
||||
let handlerURI = httpRoot + MANIFEST_HANDLER;
|
||||
httpServer.registerPathHandler("/" + MANIFEST_HANDLER,
|
||||
(request, response) => {
|
||||
response.processAsync();
|
||||
response.setStatus(null, 200, "OK");
|
||||
response.write("["); // never finish!
|
||||
});
|
||||
|
||||
do_register_cleanup(() => httpServer.stop(() => {}));
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, handlerURI);
|
||||
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
|
||||
|
||||
let experiments = Experiments.instance();
|
||||
experiments.updateManifest().then(
|
||||
() => {
|
||||
Assert.ok(true, "updateManifest finished successfully");
|
||||
},
|
||||
(e) => {
|
||||
do_throw("updateManifest should not have failed: got error " + e);
|
||||
});
|
||||
yield experiments.uninit();
|
||||
});
|
||||
@@ -1,179 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
|
||||
var gDataRoot;
|
||||
var gHttpServer;
|
||||
var gManifestObject;
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test_setup() {
|
||||
loadAddonManager();
|
||||
do_get_profile();
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let httpRoot = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
|
||||
gDataRoot = httpRoot + "data/";
|
||||
gHttpServer.registerDirectory("/data/", do_get_cwd());
|
||||
gHttpServer.registerPathHandler("/manifests/handler", (req, res) => {
|
||||
res.setStatusLine(null, 200, "OK");
|
||||
res.write(JSON.stringify(gManifestObject));
|
||||
res.processAsync();
|
||||
res.finish();
|
||||
});
|
||||
do_register_cleanup(() => gHttpServer.stop(() => {}));
|
||||
|
||||
Services.prefs.setBoolPref("experiments.enabled", true);
|
||||
Services.prefs.setCharPref("experiments.manifest.uri",
|
||||
httpRoot + "manifests/handler");
|
||||
Services.prefs.setBoolPref("experiments.logging.dump", true);
|
||||
Services.prefs.setCharPref("experiments.logging.level", "Trace");
|
||||
});
|
||||
|
||||
add_task(function* test_provider_basic() {
|
||||
let e = Experiments.instance();
|
||||
|
||||
let provider = new Experiments.PreviousExperimentProvider(e);
|
||||
e._setPreviousExperimentsProvider(provider);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
provider.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
let experimentAddons = yield deferred.promise;
|
||||
Assert.ok(Array.isArray(experimentAddons), "getAddonsByTypes returns an Array.");
|
||||
Assert.equal(experimentAddons.length, 0, "No previous add-ons returned.");
|
||||
|
||||
gManifestObject = {
|
||||
version: 1,
|
||||
experiments: [
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
|
||||
xpiHash: EXPERIMENT1_XPI_SHA1,
|
||||
startTime: Date.now() / 1000 - 60,
|
||||
endTime: Date.now() / 1000 + 60,
|
||||
maxActiveSeconds: 60,
|
||||
appName: ["XPCShell"],
|
||||
channel: [e._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
yield e.updateManifest();
|
||||
|
||||
deferred = Promise.defer();
|
||||
provider.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
experimentAddons = yield deferred.promise;
|
||||
Assert.equal(experimentAddons.length, 0, "Still no previous experiment.");
|
||||
|
||||
let experiments = yield e.getExperiments();
|
||||
Assert.equal(experiments.length, 1, "1 experiment present.");
|
||||
Assert.ok(experiments[0].active, "It is active.");
|
||||
|
||||
// Deactivate it.
|
||||
defineNow(e._policy, new Date(gManifestObject.experiments[0].endTime * 1000 + 1000));
|
||||
yield e.updateManifest();
|
||||
|
||||
experiments = yield e.getExperiments();
|
||||
Assert.equal(experiments.length, 1, "1 experiment present.");
|
||||
Assert.equal(experiments[0].active, false, "It isn't active.");
|
||||
|
||||
deferred = Promise.defer();
|
||||
provider.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
experimentAddons = yield deferred.promise;
|
||||
Assert.equal(experimentAddons.length, 1, "1 previous add-on known.");
|
||||
Assert.equal(experimentAddons[0].id, EXPERIMENT1_ID, "ID matches expected.");
|
||||
|
||||
deferred = Promise.defer();
|
||||
provider.getAddonByID(EXPERIMENT1_ID, (addon) => {
|
||||
deferred.resolve(addon);
|
||||
});
|
||||
let addon = yield deferred.promise;
|
||||
Assert.ok(addon, "We got an add-on from its ID.");
|
||||
Assert.equal(addon.id, EXPERIMENT1_ID, "ID matches expected.");
|
||||
Assert.ok(addon.appDisabled, "Add-on is a previous experiment.");
|
||||
Assert.ok(addon.userDisabled, "Add-on is disabled.");
|
||||
Assert.equal(addon.type, "experiment", "Add-on is an experiment.");
|
||||
Assert.equal(addon.isActive, false, "Add-on is not active.");
|
||||
Assert.equal(addon.permissions, 0, "Add-on has no permissions.");
|
||||
|
||||
deferred = Promise.defer();
|
||||
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
experimentAddons = yield deferred.promise;
|
||||
Assert.equal(experimentAddons.length, 1, "Got 1 experiment from add-on manager.");
|
||||
Assert.equal(experimentAddons[0].id, EXPERIMENT1_ID, "ID matches expected.");
|
||||
Assert.ok(experimentAddons[0].appDisabled, "It is a previous experiment add-on.");
|
||||
});
|
||||
|
||||
add_task(function* test_active_and_previous() {
|
||||
// Building on the previous test, activate experiment 2.
|
||||
let e = Experiments.instance();
|
||||
let provider = new Experiments.PreviousExperimentProvider(e);
|
||||
e._setPreviousExperimentsProvider(provider);
|
||||
|
||||
gManifestObject = {
|
||||
version: 1,
|
||||
experiments: [
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
|
||||
xpiHash: EXPERIMENT2_XPI_SHA1,
|
||||
startTime: Date.now() / 1000 - 60,
|
||||
endTime: Date.now() / 1000 + 60,
|
||||
maxActiveSeconds: 60,
|
||||
appName: ["XPCShell"],
|
||||
channel: [e._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
defineNow(e._policy, new Date());
|
||||
yield e.updateManifest();
|
||||
|
||||
let experiments = yield e.getExperiments();
|
||||
Assert.equal(experiments.length, 2, "2 experiments known.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
provider.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
let experimentAddons = yield deferred.promise;
|
||||
Assert.equal(experimentAddons.length, 1, "1 previous experiment.");
|
||||
|
||||
deferred = Promise.defer();
|
||||
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
experimentAddons = yield deferred.promise;
|
||||
Assert.equal(experimentAddons.length, 2, "2 experiment add-ons known.");
|
||||
|
||||
for (let addon of experimentAddons) {
|
||||
if (addon.id == EXPERIMENT1_ID) {
|
||||
Assert.equal(addon.isActive, false, "Add-on is not active.");
|
||||
Assert.ok(addon.appDisabled, "Should be a previous experiment.");
|
||||
}
|
||||
else if (addon.id == EXPERIMENT2_ID) {
|
||||
Assert.ok(addon.isActive, "Add-on is active.");
|
||||
Assert.ok(!addon.appDisabled, "Should not be a previous experiment.");
|
||||
}
|
||||
else {
|
||||
throw new Error("Unexpected add-on ID: " + addon.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,294 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://gre/modules/TelemetryLog.jsm");
|
||||
var bsp = Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
|
||||
const MANIFEST_HANDLER = "manifests/handler";
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
||||
|
||||
var gHttpServer = null;
|
||||
var gHttpRoot = null;
|
||||
var gDataRoot = null;
|
||||
var gPolicy = null;
|
||||
var gManifestObject = null;
|
||||
var gManifestHandlerURI = null;
|
||||
|
||||
const TLOG = bsp.TELEMETRY_LOG;
|
||||
|
||||
function checkEvent(event, id, data)
|
||||
{
|
||||
do_print("Checking message " + id);
|
||||
Assert.equal(event[0], id, "id should match");
|
||||
Assert.ok(event[1] > 0, "timestamp should be greater than 0");
|
||||
|
||||
if (data === undefined) {
|
||||
Assert.equal(event.length, 2, "event array should have 2 entries");
|
||||
} else {
|
||||
Assert.equal(event.length, data.length + 2, "event entry count should match expected count");
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
Assert.equal(typeof(event[i + 2]), "string", "event entry should be a string");
|
||||
Assert.equal(event[i + 2], data[i], "event entry should match expected entry");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
loadAddonManager();
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let port = gHttpServer.identity.primaryPort;
|
||||
gHttpRoot = "http://localhost:" + port + "/";
|
||||
gDataRoot = gHttpRoot + "data/";
|
||||
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
|
||||
gHttpServer.registerDirectory("/data/", do_get_cwd());
|
||||
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify(gManifestObject));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
do_register_cleanup(() => gHttpServer.stop(() => {}));
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
|
||||
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
|
||||
|
||||
gPolicy = new Experiments.Policy();
|
||||
let dummyTimer = { cancel: () => {}, clear: () => {} };
|
||||
patchPolicy(gPolicy, {
|
||||
updatechannel: () => "nightly",
|
||||
oneshotTimer: (callback, timeout, thisObj, name) => dummyTimer,
|
||||
});
|
||||
|
||||
yield removeCacheFile();
|
||||
});
|
||||
|
||||
// Test basic starting and stopping of experiments.
|
||||
|
||||
add_task(function* test_telemetryBasics() {
|
||||
// Check TelemetryLog instead of TelemetrySession.getPayload().log because
|
||||
// TelemetrySession gets Experiments.instance() and side-effects log entries.
|
||||
|
||||
let expectedLogLength = 0;
|
||||
|
||||
// Dates the following tests are based on.
|
||||
|
||||
let baseDate = new Date(2014, 5, 1, 12);
|
||||
let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
|
||||
let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
|
||||
let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY);
|
||||
let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY);
|
||||
|
||||
// The manifest data we test with.
|
||||
|
||||
gManifestObject = {
|
||||
"version": 1,
|
||||
experiments: [
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
|
||||
xpiHash: EXPERIMENT1_XPI_SHA1,
|
||||
startTime: dateToSeconds(startDate1),
|
||||
endTime: dateToSeconds(endDate1),
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
|
||||
xpiHash: EXPERIMENT2_XPI_SHA1,
|
||||
startTime: dateToSeconds(startDate2),
|
||||
endTime: dateToSeconds(endDate2),
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let experiments = new Experiments.Experiments(gPolicy);
|
||||
|
||||
// Trigger update, clock set to before any activation.
|
||||
// Use updateManifest() to provide for coverage of that path.
|
||||
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
expectedLogLength += 2;
|
||||
let log = TelemetryLog.entries();
|
||||
do_print("Telemetry log: " + JSON.stringify(log));
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-2], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.REJECTED, EXPERIMENT1_ID, "startTime"]);
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
|
||||
|
||||
// Trigger update, clock set for experiment 1 to start.
|
||||
|
||||
now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries. Got " + log.toSource());
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT1_ID]);
|
||||
|
||||
// Trigger update, clock set for experiment 1 to stop.
|
||||
|
||||
now = futureDate(endDate1, 1000);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
|
||||
|
||||
expectedLogLength += 2;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-2], TLOG.TERMINATION_KEY,
|
||||
[TLOG.TERMINATION.EXPIRED, EXPERIMENT1_ID]);
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
|
||||
|
||||
// Trigger update, clock set for experiment 2 to start with invalid hash.
|
||||
|
||||
now = startDate2;
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[1].xpiHash = "sha1:0000000000000000000000000000000000000000";
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.INSTALL_FAILURE, EXPERIMENT2_ID]);
|
||||
|
||||
// Trigger update, clock set for experiment 2 to properly start now.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[1].xpiHash = EXPERIMENT2_XPI_SHA1;
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]);
|
||||
|
||||
// Fake user uninstall of experiment via add-on manager.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED);
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
|
||||
[TLOG.TERMINATION.ADDON_UNINSTALLED, EXPERIMENT2_ID]);
|
||||
|
||||
// Trigger update with experiment 1a ready to start.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[0].id = EXPERIMENT3_ID;
|
||||
gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]);
|
||||
|
||||
// Trigger disable of an experiment via the API.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.disableExperiment(TLOG.TERMINATION.FROM_API);
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
|
||||
[TLOG.TERMINATION.FROM_API, EXPERIMENT3_ID]);
|
||||
|
||||
// Trigger update with experiment 1a ready to start.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[0].id = EXPERIMENT4_ID;
|
||||
gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT4_ID]);
|
||||
|
||||
// Trigger experiment termination by something other than expiry via the manifest.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[0].os = "Plan9";
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryLog.entries();
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
|
||||
[TLOG.TERMINATION.RECHECK, EXPERIMENT4_ID, "os"]);
|
||||
|
||||
// Cleanup.
|
||||
|
||||
yield promiseRestartManager();
|
||||
yield removeCacheFile();
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
add_test(function test_experiments_activation() {
|
||||
do_get_profile();
|
||||
loadAddonManager();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false);
|
||||
|
||||
let experiments = Experiments.instance();
|
||||
Assert.ok(!experiments.enabled, "Experiments must be disabled if Telemetry is disabled.");
|
||||
|
||||
// TODO: Test that Experiments are turned back on when bug 1232648 lands.
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
var cacheData = {
|
||||
_enabled: true,
|
||||
_manifestData: {
|
||||
id: "foobartestid",
|
||||
xpiURL: "http://example.com/foo.xpi",
|
||||
xpiHash: "sha256:abcde",
|
||||
startTime: 0,
|
||||
endTime: 2000000000,
|
||||
maxActiveSeconds: 40000000,
|
||||
appName: "TestApp",
|
||||
channel: "test-foo",
|
||||
},
|
||||
_needsUpdate: false,
|
||||
_randomValue: 0.5,
|
||||
_failedStart: false,
|
||||
_name: "Foo",
|
||||
_description: "Foobar",
|
||||
_homepageURL: "",
|
||||
_addonId: "foo@test",
|
||||
_startDate: 0,
|
||||
_endDate: 2000000000,
|
||||
_branch: null
|
||||
};
|
||||
|
||||
add_task(function* test_valid() {
|
||||
let e = new Experiments.ExperimentEntry();
|
||||
Assert.ok(e.initFromCacheData(cacheData));
|
||||
Assert.ok(e.enabled);
|
||||
});
|
||||
|
||||
add_task(function* test_upgrade() {
|
||||
let e = new Experiments.ExperimentEntry();
|
||||
delete cacheData._branch;
|
||||
Assert.ok(e.initFromCacheData(cacheData));
|
||||
Assert.ok(e.enabled);
|
||||
});
|
||||
|
||||
add_task(function* test_missing() {
|
||||
let e = new Experiments.ExperimentEntry();
|
||||
delete cacheData._name;
|
||||
Assert.ok(!e.initFromCacheData(cacheData));
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
[DEFAULT]
|
||||
head = head.js
|
||||
tail =
|
||||
tags = addons
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android'
|
||||
support-files =
|
||||
experiments_1.manifest
|
||||
experiment-1.xpi
|
||||
experiment-1a.xpi
|
||||
experiment-2.xpi
|
||||
experiment-racybranch.xpi
|
||||
!/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js
|
||||
generated-files =
|
||||
experiment-1.xpi
|
||||
experiment-1a.xpi
|
||||
experiment-2.xpi
|
||||
experiment-racybranch.xpi
|
||||
|
||||
[test_activate.js]
|
||||
[test_api.js]
|
||||
[test_cache.js]
|
||||
[test_cacherace.js]
|
||||
[test_conditions.js]
|
||||
[test_disableExperiments.js]
|
||||
[test_fetch.js]
|
||||
[test_telemetry.js]
|
||||
[test_telemetry_disabled.js]
|
||||
[test_previous_provider.js]
|
||||
[test_upgrade.js]
|
||||
[test_nethang_bug1012924.js]
|
||||
@@ -11,7 +11,6 @@ SPHINX_TREES['browser'] = 'docs'
|
||||
DIRS += [
|
||||
'base',
|
||||
'components',
|
||||
'experiments',
|
||||
'fonts',
|
||||
'locales',
|
||||
'modules',
|
||||
|
||||
@@ -153,7 +153,6 @@ const DEFAULT_ENVIRONMENT_PREFS = new Map([
|
||||
["dom.ipc.plugins.enabled", {what: RECORD_PREF_VALUE}],
|
||||
["dom.ipc.processCount", {what: RECORD_PREF_VALUE, requiresRestart: true}],
|
||||
["dom.max_script_run_time", {what: RECORD_PREF_VALUE}],
|
||||
["experiments.manifest.uri", {what: RECORD_PREF_VALUE}],
|
||||
["extensions.autoDisableScopes", {what: RECORD_PREF_VALUE}],
|
||||
["extensions.enabledScopes", {what: RECORD_PREF_VALUE}],
|
||||
["extensions.blocklist.enabled", {what: RECORD_PREF_VALUE}],
|
||||
@@ -209,7 +208,6 @@ const PREF_E10S_COHORT = "e10s.rollout.cohort";
|
||||
|
||||
const COMPOSITOR_CREATED_TOPIC = "compositor:created";
|
||||
const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = "distribution-customization-complete";
|
||||
const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed";
|
||||
const GFX_FEATURES_READY_TOPIC = "gfx-features-ready";
|
||||
const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified";
|
||||
const SEARCH_SERVICE_TOPIC = "browser-search-service";
|
||||
@@ -465,7 +463,6 @@ EnvironmentAddonBuilder.prototype = {
|
||||
watchForChanges: function() {
|
||||
this._loaded = true;
|
||||
AddonManager.addAddonListener(this);
|
||||
Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC, false);
|
||||
},
|
||||
|
||||
// AddonListener
|
||||
@@ -490,7 +487,6 @@ EnvironmentAddonBuilder.prototype = {
|
||||
// nsIObserver
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
this._environment._log.trace("observe - Topic " + aTopic);
|
||||
this._checkForChanges("experiment-changed");
|
||||
},
|
||||
|
||||
_checkForChanges: function(changeReason) {
|
||||
@@ -515,7 +511,6 @@ EnvironmentAddonBuilder.prototype = {
|
||||
_shutdownBlocker: function() {
|
||||
if (this._loaded) {
|
||||
AddonManager.removeAddonListener(this);
|
||||
Services.obs.removeObserver(this, EXPERIMENTS_CHANGED_TOPIC);
|
||||
}
|
||||
return this._pendingTask;
|
||||
},
|
||||
@@ -545,7 +540,6 @@ EnvironmentAddonBuilder.prototype = {
|
||||
theme: yield this._getActiveTheme(),
|
||||
activePlugins: this._getActivePlugins(),
|
||||
activeGMPlugins: yield this._getActiveGMPlugins(),
|
||||
activeExperiment: this._getActiveExperiment(),
|
||||
persona: personaId,
|
||||
};
|
||||
|
||||
@@ -718,29 +712,7 @@ EnvironmentAddonBuilder.prototype = {
|
||||
}
|
||||
|
||||
return activeGMPlugins;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get the active experiment data in object form.
|
||||
* @return Object containing the active experiment data.
|
||||
*/
|
||||
_getActiveExperiment: function () {
|
||||
let experimentInfo = {};
|
||||
try {
|
||||
let scope = {};
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm", scope);
|
||||
let experiments = scope.Experiments.instance();
|
||||
let activeExperiment = experiments.getActiveExperimentID();
|
||||
if (activeExperiment) {
|
||||
experimentInfo.id = activeExperiment;
|
||||
experimentInfo.branch = experiments.getActiveExperimentBranch();
|
||||
}
|
||||
} catch (e) {
|
||||
// If this is not Firefox, the import will fail.
|
||||
}
|
||||
|
||||
return experimentInfo;
|
||||
},
|
||||
})
|
||||
};
|
||||
|
||||
function EnvironmentCache() {
|
||||
|
||||
@@ -504,39 +504,6 @@
|
||||
</table>
|
||||
|
||||
|
||||
<h2 class="major-section">
|
||||
&aboutSupport.experimentsTitle;
|
||||
</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
&aboutSupport.experimentName;
|
||||
</th>
|
||||
<th>
|
||||
&aboutSupport.experimentId;
|
||||
</th>
|
||||
<th>
|
||||
&aboutSupport.experimentDescription;
|
||||
</th>
|
||||
<th>
|
||||
&aboutSupport.experimentActive;
|
||||
</th>
|
||||
<th>
|
||||
&aboutSupport.experimentEndDate;
|
||||
</th>
|
||||
<th>
|
||||
&aboutSupport.experimentHomepage;
|
||||
</th>
|
||||
<th>
|
||||
&aboutSupport.experimentBranch;
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="experiments-tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - -->
|
||||
|
||||
#if defined(MOZ_SANDBOX)
|
||||
|
||||
@@ -12,13 +12,6 @@ Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
var Experiments;
|
||||
try {
|
||||
Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
// We use a preferences whitelist to make sure we only show preferences that
|
||||
// are useful for support and won't compromise the user's privacy. Note that
|
||||
// entries are *prefixes*: for example, "accessibility." applies to all prefs
|
||||
@@ -263,18 +256,6 @@ var dataProviders = {
|
||||
});
|
||||
},
|
||||
|
||||
experiments: function experiments(done) {
|
||||
if (Experiments === undefined) {
|
||||
done([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// getExperiments promises experiment history
|
||||
Experiments.instance().getExperiments().then(
|
||||
experiments => done(experiments)
|
||||
);
|
||||
},
|
||||
|
||||
modifiedPreferences: function modifiedPreferences(done) {
|
||||
done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
|
||||
},
|
||||
|
||||
@@ -22,8 +22,6 @@ XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
|
||||
return Cu.import("resource://gre/modules/devtools/ToolboxProcess.jsm", {}).
|
||||
BrowserToolboxProcess;
|
||||
});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
|
||||
"resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
|
||||
const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
|
||||
@@ -216,23 +214,6 @@ function isDiscoverEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getExperimentEndDate(aAddon) {
|
||||
if (!("@mozilla.org/browser/experiments-service;1" in Cc)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!aAddon.isActive) {
|
||||
return aAddon.endDate;
|
||||
}
|
||||
|
||||
let experiment = Experiments.instance().getActiveExperiment();
|
||||
if (!experiment) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return experiment.endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the main DOMWindow for the current context.
|
||||
*/
|
||||
@@ -1316,28 +1297,7 @@ var gViewController = {
|
||||
doCommand: function cmd_neverActivateItem_doCommand(aAddon) {
|
||||
aAddon.userDisabled = true;
|
||||
}
|
||||
},
|
||||
|
||||
cmd_experimentsLearnMore: {
|
||||
isEnabled: function cmd_experimentsLearnMore_isEnabled() {
|
||||
let mainWindow = getMainWindow();
|
||||
return mainWindow && "switchToTabHavingURI" in mainWindow;
|
||||
},
|
||||
doCommand: function cmd_experimentsLearnMore_doCommand() {
|
||||
let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
|
||||
openOptionsInTab(url);
|
||||
},
|
||||
},
|
||||
|
||||
cmd_experimentsOpenTelemetryPreferences: {
|
||||
isEnabled: function cmd_experimentsOpenTelemetryPreferences_isEnabled() {
|
||||
return !!getMainWindowWithPreferencesPane();
|
||||
},
|
||||
doCommand: function cmd_experimentsOpenTelemetryPreferences_doCommand() {
|
||||
let mainWindow = getMainWindowWithPreferencesPane();
|
||||
mainWindow.openAdvancedPreferences("dataChoicesTab");
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
supportsCommand: function gVC_supportsCommand(aCommand) {
|
||||
@@ -1468,10 +1428,6 @@ function createItem(aObj, aIsInstall, aIsRemote) {
|
||||
// the binding handles the rest
|
||||
item.setAttribute("value", aObj.id);
|
||||
|
||||
if (aObj.type == "experiment") {
|
||||
item.endDate = getExperimentEndDate(aObj);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -2679,13 +2635,6 @@ var gListView = {
|
||||
// the existing item
|
||||
if (aInstall.existingAddon)
|
||||
this.removeItem(aInstall, true);
|
||||
|
||||
if (aInstall.addon.type == "experiment") {
|
||||
let item = this.getListItemForID(aInstall.addon.id);
|
||||
if (item) {
|
||||
item.endDate = getExperimentEndDate(aInstall.addon);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addItem: function gListView_addItem(aObj, aIsInstall) {
|
||||
@@ -2945,34 +2894,6 @@ var gDetailView = {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._addon.type == "experiment") {
|
||||
let prefix = "details.experiment.";
|
||||
let active = this._addon.isActive;
|
||||
|
||||
let stateKey = prefix + "state." + (active ? "active" : "complete");
|
||||
let node = document.getElementById("detail-experiment-state");
|
||||
node.value = gStrings.ext.GetStringFromName(stateKey);
|
||||
|
||||
let now = Date.now();
|
||||
let end = getExperimentEndDate(this._addon);
|
||||
let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);
|
||||
|
||||
let timeKey = prefix + "time.";
|
||||
let timeMessage;
|
||||
if (days < 1) {
|
||||
timeKey += (active ? "endsToday" : "endedToday");
|
||||
timeMessage = gStrings.ext.GetStringFromName(timeKey);
|
||||
} else {
|
||||
timeKey += (active ? "daysRemaining" : "daysPassed");
|
||||
days = Math.round(days);
|
||||
let timeString = gStrings.ext.GetStringFromName(timeKey);
|
||||
timeMessage = PluralForm.get(days, timeString)
|
||||
.replace("#1", days);
|
||||
}
|
||||
|
||||
document.getElementById("detail-experiment-time").value = timeMessage;
|
||||
}
|
||||
|
||||
this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() {
|
||||
this.updateState();
|
||||
gViewController.notifyViewChanged();
|
||||
|
||||
@@ -1,645 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
Components.utils.import("resource://gre/modules/Promise.jsm", this);
|
||||
|
||||
let {AddonTestUtils} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {});
|
||||
let {HttpServer} = Components.utils.import("resource://testing-common/httpd.js", {});
|
||||
|
||||
let gManagerWindow;
|
||||
let gCategoryUtilities;
|
||||
let gExperiments;
|
||||
let gHttpServer;
|
||||
|
||||
let gSavedManifestURI;
|
||||
let gIsEnUsLocale;
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
||||
function getExperimentAddons() {
|
||||
let deferred = Promise.defer();
|
||||
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getInstallItem() {
|
||||
let doc = gManagerWindow.document;
|
||||
let view = doc.getElementById("view-port").selectedPanel;
|
||||
let list = doc.getElementById("addon-list");
|
||||
|
||||
let node = list.firstChild;
|
||||
while (node) {
|
||||
if (node.getAttribute("status") == "installing") {
|
||||
return node;
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function patchPolicy(policy, data) {
|
||||
for (let key of Object.keys(data)) {
|
||||
Object.defineProperty(policy, key, {
|
||||
value: data[key],
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function defineNow(policy, time) {
|
||||
patchPolicy(policy, { now: () => new Date(time) });
|
||||
}
|
||||
|
||||
function openDetailsView(aId) {
|
||||
let item = get_addon_element(gManagerWindow, aId);
|
||||
Assert.ok(item, "Should have got add-on element.");
|
||||
is_element_visible(item, "Add-on element should be visible.");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 1 }, gManagerWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
wait_for_view_load(gManagerWindow, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function clickRemoveButton(addonElement) {
|
||||
let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "remove-btn");
|
||||
if (!btn) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow);
|
||||
let deferred = Promise.defer();
|
||||
setTimeout(deferred.resolve, 0);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
function clickUndoButton(addonElement) {
|
||||
let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "undo-btn");
|
||||
if (!btn) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow);
|
||||
let deferred = Promise.defer();
|
||||
setTimeout(deferred.resolve, 0);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
add_task(function* initializeState() {
|
||||
gManagerWindow = yield open_manager();
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("experiments.enabled");
|
||||
if (gHttpServer) {
|
||||
gHttpServer.stop(() => {});
|
||||
if (gSavedManifestURI !== undefined) {
|
||||
Services.prefs.setCharPref("experments.manifest.uri", gSavedManifestURI);
|
||||
}
|
||||
}
|
||||
if (gExperiments) {
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm", tmp);
|
||||
gExperiments._policy = new tmp.Experiments.Policy();
|
||||
}
|
||||
});
|
||||
|
||||
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
|
||||
gIsEnUsLocale = chrome.getSelectedLocale("global") == "en-US";
|
||||
|
||||
// The Experiments Manager will interfere with us by preventing installs
|
||||
// of experiments it doesn't know about. We remove it from the equation
|
||||
// because here we are only concerned with core Addon Manager operation,
|
||||
// not the superset Experiments Manager has imposed.
|
||||
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm", tmp);
|
||||
// There is a race condition between XPCOM service initialization and
|
||||
// this test running. We have to initialize the instance first, then
|
||||
// uninitialize it to prevent this.
|
||||
gExperiments = tmp.Experiments.instance();
|
||||
yield gExperiments._mainTask;
|
||||
yield gExperiments.uninit();
|
||||
}
|
||||
});
|
||||
|
||||
// On an empty profile with no experiments, the experiment category
|
||||
// should be hidden.
|
||||
add_task(function* testInitialState() {
|
||||
Assert.ok(gCategoryUtilities.get("experiment", false), "Experiment tab is defined.");
|
||||
Assert.ok(!gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab hidden by default.");
|
||||
});
|
||||
|
||||
add_task(function* testExperimentInfoNotVisible() {
|
||||
yield gCategoryUtilities.openType("extension");
|
||||
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
|
||||
is_element_hidden(el, "Experiment info not visible on other types.");
|
||||
});
|
||||
|
||||
// If we have an active experiment, we should see the experiments tab
|
||||
// and that tab should have some messages.
|
||||
add_task(function* testActiveExperiment() {
|
||||
let addon = yield install_addon("addons/browser_experiment1.xpi");
|
||||
|
||||
Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install.");
|
||||
Assert.equal(addon.isActive, false, "Add-on is not active.");
|
||||
|
||||
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
|
||||
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
|
||||
is_element_visible(el, "Experiment info is visible on experiment tab.");
|
||||
});
|
||||
|
||||
add_task(function* testExperimentLearnMore() {
|
||||
// Actual URL is irrelevant.
|
||||
Services.prefs.setCharPref("toolkit.telemetry.infoURL",
|
||||
"http://mochi.test:8888/server.js");
|
||||
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let btn = gManagerWindow.document.getElementById("experiments-learn-more");
|
||||
|
||||
if (!gUseInContentUI) {
|
||||
is_element_hidden(btn, "Learn more button hidden if not using in-content UI.");
|
||||
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
is_element_visible(btn, "Learn more button visible.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
window.addEventListener("DOMContentLoaded", function onLoad(event) {
|
||||
info("Telemetry privacy policy window opened.");
|
||||
window.removeEventListener("DOMContentLoaded", onLoad, false);
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let expected = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
|
||||
Assert.equal(browser.currentURI.spec, expected, "New tab should have loaded privacy policy.");
|
||||
browser.contentWindow.close();
|
||||
|
||||
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
|
||||
|
||||
deferred.resolve();
|
||||
}, false);
|
||||
|
||||
info("Opening telemetry privacy policy.");
|
||||
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
|
||||
|
||||
yield deferred.promise;
|
||||
});
|
||||
|
||||
add_task(function* testOpenPreferences() {
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let btn = gManagerWindow.document.getElementById("experiments-change-telemetry");
|
||||
if (!gUseInContentUI) {
|
||||
is_element_hidden(btn, "Change telemetry button not enabled in out of window UI.");
|
||||
info("Skipping preferences open test because not using in-content UI.");
|
||||
return;
|
||||
}
|
||||
|
||||
is_element_visible(btn, "Change telemetry button visible in in-content UI.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
Services.obs.addObserver(function observer(prefWin, topic, data) {
|
||||
Services.obs.removeObserver(observer, "advanced-pane-loaded");
|
||||
info("Advanced preference pane opened.");
|
||||
executeSoon(function() {
|
||||
// We want this test to fail if the preferences pane changes.
|
||||
let el = prefWin.document.getElementById("dataChoicesPanel");
|
||||
is_element_visible(el);
|
||||
|
||||
prefWin.close();
|
||||
info("Closed preferences pane.");
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
}, "advanced-pane-loaded", false);
|
||||
|
||||
info("Loading preferences pane.");
|
||||
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
|
||||
|
||||
yield deferred.promise;
|
||||
});
|
||||
|
||||
add_task(function* testButtonPresence() {
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
|
||||
// Corresponds to the uninstall permission.
|
||||
is_element_visible(el, "Remove button is visible.");
|
||||
// Corresponds to lack of disable permission.
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
|
||||
is_element_hidden(el, "Disable button not visible.");
|
||||
// Corresponds to lack of enable permission.
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
|
||||
is_element_hidden(el, "Enable button not visible.");
|
||||
});
|
||||
|
||||
// Remove the add-on we've been testing with.
|
||||
add_task(function* testCleanup() {
|
||||
yield AddonTestUtils.uninstallAddonByID("test-experiment1@experiments.mozilla.org");
|
||||
// Verify some conditions, just in case.
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
|
||||
});
|
||||
|
||||
// The following tests should ideally live in browser/experiments/. However,
|
||||
// they rely on some of the helper functions from head.js, which can't easily
|
||||
// be consumed from other directories. So, they live here.
|
||||
|
||||
add_task(function* testActivateExperiment() {
|
||||
if (!gExperiments) {
|
||||
info("Skipping experiments test because that feature isn't available.");
|
||||
return;
|
||||
}
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let root = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
|
||||
gHttpServer.registerPathHandler("/manifest", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify({
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
id: "experiment-1",
|
||||
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
|
||||
xpiHash: "IRRELEVANT",
|
||||
startTime: Date.now() / 1000 - 3600,
|
||||
endTime: Date.now() / 1000 + 3600,
|
||||
maxActiveSeconds: 600,
|
||||
appName: [Services.appinfo.name],
|
||||
channel: [gExperiments._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
}));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
|
||||
gSavedManifestURI = Services.prefs.getCharPref("experiments.manifest.uri");
|
||||
Services.prefs.setCharPref("experiments.manifest.uri", root + "manifest");
|
||||
|
||||
// We need to remove the cache file to help ensure consistent state.
|
||||
yield OS.File.remove(gExperiments._cacheFilePath);
|
||||
|
||||
Services.prefs.setBoolPref("experiments.enabled", true);
|
||||
|
||||
info("Initializing experiments service.");
|
||||
yield gExperiments.init();
|
||||
info("Experiments service finished first run.");
|
||||
|
||||
// Check conditions, just to be sure.
|
||||
let experiments = yield gExperiments.getExperiments();
|
||||
Assert.equal(experiments.length, 0, "No experiments known to the service.");
|
||||
|
||||
// This makes testing easier.
|
||||
gExperiments._policy.ignoreHashes = true;
|
||||
|
||||
info("Manually updating experiments manifest.");
|
||||
yield gExperiments.updateManifest();
|
||||
info("Experiments update complete.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
gHttpServer.stop(() => {
|
||||
gHttpServer = null;
|
||||
|
||||
info("getting experiment by ID");
|
||||
AddonManager.getAddonByID("test-experiment1@experiments.mozilla.org", (addon) => {
|
||||
Assert.ok(addon, "Add-on installed via Experiments manager.");
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
yield deferred.promise;
|
||||
|
||||
Assert.ok(gCategoryUtilities.isTypeVisible, "experiment", "Experiment tab visible.");
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
|
||||
is_element_visible(el, "Experiment info is visible on experiment tab.");
|
||||
});
|
||||
|
||||
add_task(function testDeactivateExperiment() {
|
||||
if (!gExperiments) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fake an empty manifest to purge data from previous manifest.
|
||||
yield gExperiments._updateExperiments({
|
||||
"version": 1,
|
||||
"experiments": [],
|
||||
});
|
||||
|
||||
yield gExperiments.disableExperiment("testing");
|
||||
|
||||
// We should have a record of the previously-active experiment.
|
||||
let experiments = yield gExperiments.getExperiments();
|
||||
Assert.equal(experiments.length, 1, "1 experiment is known.");
|
||||
Assert.equal(experiments[0].active, false, "Experiment is not active.");
|
||||
|
||||
// We should have a previous experiment in the add-ons manager.
|
||||
let deferred = Promise.defer();
|
||||
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
let addons = yield deferred.promise;
|
||||
Assert.equal(addons.length, 1, "1 experiment add-on known.");
|
||||
Assert.ok(addons[0].appDisabled, "It is a previous experiment.");
|
||||
Assert.equal(addons[0].id, "experiment-1", "Add-on ID matches expected.");
|
||||
|
||||
// Verify the UI looks sane.
|
||||
|
||||
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
|
||||
let item = get_addon_element(gManagerWindow, "experiment-1");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
Assert.ok(!item.active, "Element should not be active.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
// User control buttons should not be present because previous experiments
|
||||
// should have no permissions.
|
||||
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
|
||||
is_element_hidden(el, "Remove button is not visible.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
|
||||
is_element_hidden(el, "Disable button is not visible.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
|
||||
is_element_hidden(el, "Enable button is not visible.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "preferences-btn");
|
||||
is_element_hidden(el, "Preferences button is not visible.");
|
||||
});
|
||||
|
||||
add_task(function testActivateRealExperiments() {
|
||||
if (!gExperiments) {
|
||||
info("Skipping experiments test because that feature isn't available.");
|
||||
return;
|
||||
}
|
||||
|
||||
yield gExperiments._updateExperiments({
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
id: "experiment-2",
|
||||
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
|
||||
xpiHash: "IRRELEVANT",
|
||||
startTime: Date.now() / 1000 - 3600,
|
||||
endTime: Date.now() / 1000 + 3600,
|
||||
maxActiveSeconds: 600,
|
||||
appName: [Services.appinfo.name],
|
||||
channel: [gExperiments._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
});
|
||||
yield gExperiments._run();
|
||||
|
||||
// Check the active experiment.
|
||||
|
||||
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Active");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Less than a day remaining");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container");
|
||||
is_element_hidden(el, "error-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container");
|
||||
is_element_hidden(el, "warning-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
|
||||
is_element_hidden(el, "pending-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version");
|
||||
is_element_hidden(el, "version should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
|
||||
is_element_hidden(el, "disabled-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
is_element_hidden(el, "update-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet");
|
||||
is_element_visible(el, "experiment-bullet should be visible.");
|
||||
|
||||
// Check the previous experiment.
|
||||
|
||||
item = get_addon_element(gManagerWindow, "experiment-1");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Complete");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Less than a day ago");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container");
|
||||
is_element_hidden(el, "error-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container");
|
||||
is_element_hidden(el, "warning-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
|
||||
is_element_hidden(el, "pending-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version");
|
||||
is_element_hidden(el, "version should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
|
||||
is_element_hidden(el, "disabled-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
is_element_hidden(el, "update-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet");
|
||||
is_element_visible(el, "experiment-bullet should be visible.");
|
||||
|
||||
// Install an "older" experiment.
|
||||
|
||||
yield gExperiments.disableExperiment("experiment-2");
|
||||
|
||||
let now = Date.now();
|
||||
let fakeNow = now - 5 * MS_IN_ONE_DAY;
|
||||
defineNow(gExperiments._policy, fakeNow);
|
||||
|
||||
yield gExperiments._updateExperiments({
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
id: "experiment-3",
|
||||
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
|
||||
xpiHash: "IRRELEVANT",
|
||||
startTime: fakeNow / 1000 - SEC_IN_ONE_DAY,
|
||||
endTime: now / 1000 + 10 * SEC_IN_ONE_DAY,
|
||||
maxActiveSeconds: 100 * SEC_IN_ONE_DAY,
|
||||
appName: [Services.appinfo.name],
|
||||
channel: [gExperiments._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
});
|
||||
yield gExperiments._run();
|
||||
|
||||
// Check the active experiment.
|
||||
|
||||
item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Active");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "10 days remaining");
|
||||
}
|
||||
|
||||
// Disable it and check it's previous experiment entry.
|
||||
|
||||
yield gExperiments.disableExperiment("experiment-3");
|
||||
|
||||
item = get_addon_element(gManagerWindow, "experiment-3");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Complete");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "5 days ago");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function testDetailView() {
|
||||
if (!gExperiments) {
|
||||
info("Skipping experiments test because that feature isn't available.");
|
||||
return;
|
||||
}
|
||||
|
||||
defineNow(gExperiments._policy, Date.now());
|
||||
yield gExperiments._updateExperiments({
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
id: "experiment-4",
|
||||
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
|
||||
xpiHash: "IRRELEVANT",
|
||||
startTime: Date.now() / 1000 - 3600,
|
||||
endTime: Date.now() / 1000 + 3600,
|
||||
maxActiveSeconds: 600,
|
||||
appName: [Services.appinfo.name],
|
||||
channel: [gExperiments._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
});
|
||||
yield gExperiments._run();
|
||||
|
||||
// Check active experiment.
|
||||
|
||||
yield openDetailsView("test-experiment1@experiments.mozilla.org");
|
||||
|
||||
let el = gManagerWindow.document.getElementById("detail-experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Active");
|
||||
}
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Less than a day remaining");
|
||||
}
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-version");
|
||||
is_element_hidden(el, "detail-version should be hidden.");
|
||||
el = gManagerWindow.document.getElementById("detail-creator");
|
||||
is_element_hidden(el, "detail-creator should be hidden.");
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-bullet");
|
||||
is_element_visible(el, "experiment-bullet should be visible.");
|
||||
|
||||
// Check previous experiment.
|
||||
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
yield openDetailsView("experiment-3");
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Complete");
|
||||
}
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "5 days ago");
|
||||
}
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-version");
|
||||
is_element_hidden(el, "detail-version should be hidden.");
|
||||
el = gManagerWindow.document.getElementById("detail-creator");
|
||||
is_element_hidden(el, "detail-creator should be hidden.");
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-bullet");
|
||||
is_element_visible(el, "experiment-bullet should be visible.");
|
||||
});
|
||||
|
||||
add_task(function* testRemoveAndUndo() {
|
||||
if (!gExperiments) {
|
||||
info("Skipping experiments test because that feature isn't available.");
|
||||
return;
|
||||
}
|
||||
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
|
||||
let addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(addon, "Got add-on element.");
|
||||
|
||||
yield clickRemoveButton(addon);
|
||||
addon.parentNode.ensureElementIsVisible(addon);
|
||||
|
||||
let el = gManagerWindow.document.getAnonymousElementByAttribute(addon, "class", "pending");
|
||||
is_element_visible(el, "Uninstall undo information should be visible.");
|
||||
|
||||
yield clickUndoButton(addon);
|
||||
addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(addon, "Got add-on element.");
|
||||
});
|
||||
|
||||
add_task(function* testCleanup() {
|
||||
if (gExperiments) {
|
||||
Services.prefs.clearUserPref("experiments.enabled");
|
||||
Services.prefs.setCharPref("experiments.manifest.uri", gSavedManifestURI);
|
||||
|
||||
// We perform the uninit/init cycle to purge any leftover state.
|
||||
yield OS.File.remove(gExperiments._cacheFilePath);
|
||||
yield gExperiments.uninit();
|
||||
yield gExperiments.init();
|
||||
}
|
||||
|
||||
// Check post-conditions.
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
|
||||
|
||||
yield close_manager(gManagerWindow);
|
||||
});
|
||||
@@ -29,9 +29,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
|
||||
"resource://gre/modules/Preferences.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
|
||||
"resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
|
||||
const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
|
||||
const PREF_XPI_ENABLED = "xpinstall.enabled";
|
||||
@@ -297,23 +294,6 @@ function isDiscoverEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getExperimentEndDate(aAddon) {
|
||||
if (!("@mozilla.org/browser/experiments-service;1" in Cc)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!aAddon.isActive) {
|
||||
return aAddon.endDate;
|
||||
}
|
||||
|
||||
let experiment = Experiments.instance().getActiveExperiment();
|
||||
if (!experiment) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return experiment.endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the main DOMWindow for the current context.
|
||||
*/
|
||||
@@ -1444,27 +1424,6 @@ var gViewController = {
|
||||
}
|
||||
},
|
||||
|
||||
cmd_experimentsLearnMore: {
|
||||
isEnabled: function() {
|
||||
let mainWindow = getMainWindow();
|
||||
return mainWindow && "switchToTabHavingURI" in mainWindow;
|
||||
},
|
||||
doCommand: function() {
|
||||
let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
|
||||
openOptionsInTab(url);
|
||||
},
|
||||
},
|
||||
|
||||
cmd_experimentsOpenTelemetryPreferences: {
|
||||
isEnabled: function() {
|
||||
return !!getMainWindowWithPreferencesPane();
|
||||
},
|
||||
doCommand: function() {
|
||||
let mainWindow = getMainWindowWithPreferencesPane();
|
||||
mainWindow.openAdvancedPreferences("dataChoicesTab");
|
||||
},
|
||||
},
|
||||
|
||||
cmd_showUnsignedExtensions: {
|
||||
isEnabled: function() {
|
||||
return true;
|
||||
@@ -1577,10 +1536,6 @@ function shouldShowVersionNumber(aAddon) {
|
||||
if (!aAddon.version)
|
||||
return false;
|
||||
|
||||
// The version number is hidden for experiments.
|
||||
if (aAddon.type == "experiment")
|
||||
return false;
|
||||
|
||||
// The version number is hidden for lightweight themes.
|
||||
if (aAddon.type == "theme")
|
||||
return !/@personas\.mozilla\.org$/.test(aAddon.id);
|
||||
@@ -1614,10 +1569,6 @@ function createItem(aObj, aIsInstall, aIsRemote) {
|
||||
// the binding handles the rest
|
||||
item.setAttribute("value", aObj.id);
|
||||
|
||||
if (aObj.type == "experiment") {
|
||||
item.endDate = getExperimentEndDate(aObj);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -2861,13 +2812,6 @@ var gListView = {
|
||||
// the existing item
|
||||
if (aInstall.existingAddon)
|
||||
this.removeItem(aInstall, true);
|
||||
|
||||
if (aInstall.addon.type == "experiment") {
|
||||
let item = this.getListItemForID(aInstall.addon.id);
|
||||
if (item) {
|
||||
item.endDate = getExperimentEndDate(aInstall.addon);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addItem: function(aObj, aIsInstall) {
|
||||
@@ -3126,34 +3070,6 @@ var gDetailView = {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._addon.type == "experiment") {
|
||||
let prefix = "details.experiment.";
|
||||
let active = this._addon.isActive;
|
||||
|
||||
let stateKey = prefix + "state." + (active ? "active" : "complete");
|
||||
let node = document.getElementById("detail-experiment-state");
|
||||
node.value = gStrings.ext.GetStringFromName(stateKey);
|
||||
|
||||
let now = Date.now();
|
||||
let end = getExperimentEndDate(this._addon);
|
||||
let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);
|
||||
|
||||
let timeKey = prefix + "time.";
|
||||
let timeMessage;
|
||||
if (days < 1) {
|
||||
timeKey += (active ? "endsToday" : "endedToday");
|
||||
timeMessage = gStrings.ext.GetStringFromName(timeKey);
|
||||
} else {
|
||||
timeKey += (active ? "daysRemaining" : "daysPassed");
|
||||
days = Math.round(days);
|
||||
let timeString = gStrings.ext.GetStringFromName(timeKey);
|
||||
timeMessage = PluralForm.get(days, timeString)
|
||||
.replace("#1", days);
|
||||
}
|
||||
|
||||
document.getElementById("detail-experiment-time").value = timeMessage;
|
||||
}
|
||||
|
||||
this.fillSettingsRows(aScrollToPreferences, (function() {
|
||||
this.updateState();
|
||||
gViewController.notifyViewChanged();
|
||||
|
||||
@@ -1,654 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
Components.utils.import("resource://gre/modules/Promise.jsm", this);
|
||||
|
||||
var {AddonManagerTesting} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {});
|
||||
var {HttpServer} = Components.utils.import("resource://testing-common/httpd.js", {});
|
||||
|
||||
var gManagerWindow;
|
||||
var gCategoryUtilities;
|
||||
var gExperiments;
|
||||
var gHttpServer;
|
||||
|
||||
var gSavedManifestURI;
|
||||
var gIsEnUsLocale;
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
||||
function getExperimentAddons() {
|
||||
let deferred = Promise.defer();
|
||||
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getInstallItem() {
|
||||
let doc = gManagerWindow.document;
|
||||
let view = get_current_view(gManagerWindow);
|
||||
let list = doc.getElementById("addon-list");
|
||||
|
||||
let node = list.firstChild;
|
||||
while (node) {
|
||||
if (node.getAttribute("status") == "installing") {
|
||||
return node;
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function patchPolicy(policy, data) {
|
||||
for (let key of Object.keys(data)) {
|
||||
Object.defineProperty(policy, key, {
|
||||
value: data[key],
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function defineNow(policy, time) {
|
||||
patchPolicy(policy, { now: () => new Date(time) });
|
||||
}
|
||||
|
||||
function openDetailsView(aId) {
|
||||
let item = get_addon_element(gManagerWindow, aId);
|
||||
Assert.ok(item, "Should have got add-on element.");
|
||||
is_element_visible(item, "Add-on element should be visible.");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 1 }, gManagerWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
wait_for_view_load(gManagerWindow, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function clickRemoveButton(addonElement) {
|
||||
let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "remove-btn");
|
||||
if (!btn) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow);
|
||||
let deferred = Promise.defer();
|
||||
setTimeout(deferred.resolve, 0);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
function clickUndoButton(addonElement) {
|
||||
let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "undo-btn");
|
||||
if (!btn) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow);
|
||||
let deferred = Promise.defer();
|
||||
setTimeout(deferred.resolve, 0);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
add_task(function* initializeState() {
|
||||
gManagerWindow = yield open_manager();
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("experiments.enabled");
|
||||
Services.prefs.clearUserPref("toolkit.telemetry.enabled");
|
||||
if (gHttpServer) {
|
||||
gHttpServer.stop(() => {});
|
||||
if (gSavedManifestURI !== undefined) {
|
||||
Services.prefs.setCharPref("experments.manifest.uri", gSavedManifestURI);
|
||||
}
|
||||
}
|
||||
if (gExperiments) {
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm", tmp);
|
||||
gExperiments._policy = new tmp.Experiments.Policy();
|
||||
}
|
||||
});
|
||||
|
||||
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
|
||||
gIsEnUsLocale = chrome.getSelectedLocale("global") == "en-US";
|
||||
|
||||
// The Experiments Manager will interfere with us by preventing installs
|
||||
// of experiments it doesn't know about. We remove it from the equation
|
||||
// because here we are only concerned with core Addon Manager operation,
|
||||
// not the superset Experiments Manager has imposed.
|
||||
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm", tmp);
|
||||
// There is a race condition between XPCOM service initialization and
|
||||
// this test running. We have to initialize the instance first, then
|
||||
// uninitialize it to prevent this.
|
||||
gExperiments = tmp.Experiments.instance();
|
||||
yield gExperiments._mainTask;
|
||||
yield gExperiments.uninit();
|
||||
}
|
||||
});
|
||||
|
||||
// On an empty profile with no experiments, the experiment category
|
||||
// should be hidden.
|
||||
add_task(function* testInitialState() {
|
||||
Assert.ok(gCategoryUtilities.get("experiment", false), "Experiment tab is defined.");
|
||||
Assert.ok(!gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab hidden by default.");
|
||||
});
|
||||
|
||||
add_task(function* testExperimentInfoNotVisible() {
|
||||
yield gCategoryUtilities.openType("extension");
|
||||
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
|
||||
is_element_hidden(el, "Experiment info not visible on other types.");
|
||||
});
|
||||
|
||||
// If we have an active experiment, we should see the experiments tab
|
||||
// and that tab should have some messages.
|
||||
add_task(function* testActiveExperiment() {
|
||||
let addon = yield install_addon("addons/browser_experiment1.xpi");
|
||||
|
||||
Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install.");
|
||||
Assert.equal(addon.isActive, false, "Add-on is not active.");
|
||||
|
||||
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
|
||||
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
|
||||
is_element_visible(el, "Experiment info is visible on experiment tab.");
|
||||
});
|
||||
|
||||
add_task(function* testExperimentLearnMore() {
|
||||
// Actual URL is irrelevant.
|
||||
Services.prefs.setCharPref("toolkit.telemetry.infoURL",
|
||||
"http://mochi.test:8888/server.js");
|
||||
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let btn = gManagerWindow.document.getElementById("experiments-learn-more");
|
||||
|
||||
if (!gUseInContentUI) {
|
||||
is_element_hidden(btn, "Learn more button hidden if not using in-content UI.");
|
||||
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
is_element_visible(btn, "Learn more button visible.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
window.addEventListener("DOMContentLoaded", function onLoad(event) {
|
||||
info("Telemetry privacy policy window opened.");
|
||||
window.removeEventListener("DOMContentLoaded", onLoad, false);
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let expected = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
|
||||
Assert.equal(browser.currentURI.spec, expected, "New tab should have loaded privacy policy.");
|
||||
browser.contentWindow.close();
|
||||
|
||||
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
|
||||
|
||||
deferred.resolve();
|
||||
}, false);
|
||||
|
||||
info("Opening telemetry privacy policy.");
|
||||
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
|
||||
|
||||
yield deferred.promise;
|
||||
});
|
||||
|
||||
add_task(function* testOpenPreferences() {
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let btn = gManagerWindow.document.getElementById("experiments-change-telemetry");
|
||||
if (!gUseInContentUI) {
|
||||
is_element_hidden(btn, "Change telemetry button not enabled in out of window UI.");
|
||||
info("Skipping preferences open test because not using in-content UI.");
|
||||
return;
|
||||
}
|
||||
|
||||
is_element_visible(btn, "Change telemetry button visible in in-content UI.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
Services.obs.addObserver(function observer(prefWin, topic, data) {
|
||||
Services.obs.removeObserver(observer, "advanced-pane-loaded");
|
||||
info("Advanced preference pane opened.");
|
||||
executeSoon(function() {
|
||||
// We want this test to fail if the preferences pane changes.
|
||||
let el = prefWin.document.getElementById("dataChoicesPanel");
|
||||
is_element_visible(el);
|
||||
|
||||
prefWin.close();
|
||||
info("Closed preferences pane.");
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
}, "advanced-pane-loaded", false);
|
||||
|
||||
info("Loading preferences pane.");
|
||||
// We need to focus before synthesizing the mouse event (bug 1240052) as
|
||||
// synthesizeMouseAtCenter currently only synthesizes the mouse in the child process.
|
||||
// This can cause some subtle differences if the child isn't focused.
|
||||
yield SimpleTest.promiseFocus();
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#experiments-change-telemetry", {},
|
||||
gBrowser.selectedBrowser);
|
||||
|
||||
yield deferred.promise;
|
||||
});
|
||||
|
||||
add_task(function* testButtonPresence() {
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
|
||||
// Corresponds to the uninstall permission.
|
||||
is_element_visible(el, "Remove button is visible.");
|
||||
// Corresponds to lack of disable permission.
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
|
||||
is_element_hidden(el, "Disable button not visible.");
|
||||
// Corresponds to lack of enable permission.
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
|
||||
is_element_hidden(el, "Enable button not visible.");
|
||||
});
|
||||
|
||||
// Remove the add-on we've been testing with.
|
||||
add_task(function* testCleanup() {
|
||||
yield AddonManagerTesting.uninstallAddonByID("test-experiment1@experiments.mozilla.org");
|
||||
// Verify some conditions, just in case.
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
|
||||
});
|
||||
|
||||
// The following tests should ideally live in browser/experiments/. However,
|
||||
// they rely on some of the helper functions from head.js, which can't easily
|
||||
// be consumed from other directories. So, they live here.
|
||||
|
||||
add_task(function* testActivateExperiment() {
|
||||
if (!gExperiments) {
|
||||
info("Skipping experiments test because that feature isn't available.");
|
||||
return;
|
||||
}
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let root = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
|
||||
gHttpServer.registerPathHandler("/manifest", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify({
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
id: "experiment-1",
|
||||
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
|
||||
xpiHash: "IRRELEVANT",
|
||||
startTime: Date.now() / 1000 - 3600,
|
||||
endTime: Date.now() / 1000 + 3600,
|
||||
maxActiveSeconds: 600,
|
||||
appName: [Services.appinfo.name],
|
||||
channel: [gExperiments._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
}));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
|
||||
gSavedManifestURI = Services.prefs.getCharPref("experiments.manifest.uri");
|
||||
Services.prefs.setCharPref("experiments.manifest.uri", root + "manifest");
|
||||
|
||||
// We need to remove the cache file to help ensure consistent state.
|
||||
yield OS.File.remove(gExperiments._cacheFilePath);
|
||||
|
||||
Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
|
||||
Services.prefs.setBoolPref("experiments.enabled", true);
|
||||
|
||||
info("Initializing experiments service.");
|
||||
yield gExperiments.init();
|
||||
info("Experiments service finished first run.");
|
||||
|
||||
// Check conditions, just to be sure.
|
||||
let experiments = yield gExperiments.getExperiments();
|
||||
Assert.equal(experiments.length, 0, "No experiments known to the service.");
|
||||
|
||||
// This makes testing easier.
|
||||
gExperiments._policy.ignoreHashes = true;
|
||||
|
||||
info("Manually updating experiments manifest.");
|
||||
yield gExperiments.updateManifest();
|
||||
info("Experiments update complete.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
gHttpServer.stop(() => {
|
||||
gHttpServer = null;
|
||||
|
||||
info("getting experiment by ID");
|
||||
AddonManager.getAddonByID("test-experiment1@experiments.mozilla.org", (addon) => {
|
||||
Assert.ok(addon, "Add-on installed via Experiments manager.");
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
yield deferred.promise;
|
||||
|
||||
Assert.ok(gCategoryUtilities.isTypeVisible, "experiment", "Experiment tab visible.");
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
|
||||
is_element_visible(el, "Experiment info is visible on experiment tab.");
|
||||
});
|
||||
|
||||
add_task(function* testDeactivateExperiment() {
|
||||
if (!gExperiments) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fake an empty manifest to purge data from previous manifest.
|
||||
yield gExperiments._updateExperiments({
|
||||
"version": 1,
|
||||
"experiments": [],
|
||||
});
|
||||
|
||||
yield gExperiments.disableExperiment("testing");
|
||||
|
||||
// We should have a record of the previously-active experiment.
|
||||
let experiments = yield gExperiments.getExperiments();
|
||||
Assert.equal(experiments.length, 1, "1 experiment is known.");
|
||||
Assert.equal(experiments[0].active, false, "Experiment is not active.");
|
||||
|
||||
// We should have a previous experiment in the add-ons manager.
|
||||
let deferred = Promise.defer();
|
||||
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
|
||||
deferred.resolve(addons);
|
||||
});
|
||||
let addons = yield deferred.promise;
|
||||
Assert.equal(addons.length, 1, "1 experiment add-on known.");
|
||||
Assert.ok(addons[0].appDisabled, "It is a previous experiment.");
|
||||
Assert.equal(addons[0].id, "experiment-1", "Add-on ID matches expected.");
|
||||
|
||||
// Verify the UI looks sane.
|
||||
|
||||
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
|
||||
let item = get_addon_element(gManagerWindow, "experiment-1");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
Assert.ok(!item.active, "Element should not be active.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
// User control buttons should not be present because previous experiments
|
||||
// should have no permissions.
|
||||
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
|
||||
is_element_hidden(el, "Remove button is not visible.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
|
||||
is_element_hidden(el, "Disable button is not visible.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
|
||||
is_element_hidden(el, "Enable button is not visible.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "preferences-btn");
|
||||
is_element_hidden(el, "Preferences button is not visible.");
|
||||
});
|
||||
|
||||
add_task(function* testActivateRealExperiments() {
|
||||
if (!gExperiments) {
|
||||
info("Skipping experiments test because that feature isn't available.");
|
||||
return;
|
||||
}
|
||||
|
||||
yield gExperiments._updateExperiments({
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
id: "experiment-2",
|
||||
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
|
||||
xpiHash: "IRRELEVANT",
|
||||
startTime: Date.now() / 1000 - 3600,
|
||||
endTime: Date.now() / 1000 + 3600,
|
||||
maxActiveSeconds: 600,
|
||||
appName: [Services.appinfo.name],
|
||||
channel: [gExperiments._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
});
|
||||
yield gExperiments._run();
|
||||
|
||||
// Check the active experiment.
|
||||
|
||||
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Active");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Less than a day remaining");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container");
|
||||
is_element_hidden(el, "error-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container");
|
||||
is_element_hidden(el, "warning-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
|
||||
is_element_hidden(el, "pending-container should be hidden.");
|
||||
let { version } = yield get_tooltip_info(item);
|
||||
Assert.equal(version, undefined, "version should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
|
||||
is_element_hidden(el, "disabled-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
is_element_hidden(el, "update-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet");
|
||||
is_element_visible(el, "experiment-bullet should be visible.");
|
||||
|
||||
// Check the previous experiment.
|
||||
|
||||
item = get_addon_element(gManagerWindow, "experiment-1");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Complete");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Less than a day ago");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container");
|
||||
is_element_hidden(el, "error-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container");
|
||||
is_element_hidden(el, "warning-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
|
||||
is_element_hidden(el, "pending-container should be hidden.");
|
||||
({ version } = yield get_tooltip_info(item));
|
||||
Assert.equal(version, undefined, "version should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
|
||||
is_element_hidden(el, "disabled-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
is_element_hidden(el, "update-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet");
|
||||
is_element_visible(el, "experiment-bullet should be visible.");
|
||||
|
||||
// Install an "older" experiment.
|
||||
|
||||
yield gExperiments.disableExperiment("experiment-2");
|
||||
|
||||
let now = Date.now();
|
||||
let fakeNow = now - 5 * MS_IN_ONE_DAY;
|
||||
defineNow(gExperiments._policy, fakeNow);
|
||||
|
||||
yield gExperiments._updateExperiments({
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
id: "experiment-3",
|
||||
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
|
||||
xpiHash: "IRRELEVANT",
|
||||
startTime: fakeNow / 1000 - SEC_IN_ONE_DAY,
|
||||
endTime: now / 1000 + 10 * SEC_IN_ONE_DAY,
|
||||
maxActiveSeconds: 100 * SEC_IN_ONE_DAY,
|
||||
appName: [Services.appinfo.name],
|
||||
channel: [gExperiments._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
});
|
||||
yield gExperiments._run();
|
||||
|
||||
// Check the active experiment.
|
||||
|
||||
item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Active");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "10 days remaining");
|
||||
}
|
||||
|
||||
// Disable it and check it's previous experiment entry.
|
||||
|
||||
yield gExperiments.disableExperiment("experiment-3");
|
||||
|
||||
item = get_addon_element(gManagerWindow, "experiment-3");
|
||||
Assert.ok(item, "Got add-on element.");
|
||||
item.parentNode.ensureElementIsVisible(item);
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Complete");
|
||||
}
|
||||
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "5 days ago");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* testDetailView() {
|
||||
if (!gExperiments) {
|
||||
info("Skipping experiments test because that feature isn't available.");
|
||||
return;
|
||||
}
|
||||
|
||||
defineNow(gExperiments._policy, Date.now());
|
||||
yield gExperiments._updateExperiments({
|
||||
"version": 1,
|
||||
"experiments": [
|
||||
{
|
||||
id: "experiment-4",
|
||||
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
|
||||
xpiHash: "IRRELEVANT",
|
||||
startTime: Date.now() / 1000 - 3600,
|
||||
endTime: Date.now() / 1000 + 3600,
|
||||
maxActiveSeconds: 600,
|
||||
appName: [Services.appinfo.name],
|
||||
channel: [gExperiments._policy.updatechannel()],
|
||||
},
|
||||
],
|
||||
});
|
||||
yield gExperiments._run();
|
||||
|
||||
// Check active experiment.
|
||||
|
||||
yield openDetailsView("test-experiment1@experiments.mozilla.org");
|
||||
|
||||
let el = gManagerWindow.document.getElementById("detail-experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Active");
|
||||
}
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Less than a day remaining");
|
||||
}
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-version");
|
||||
is_element_hidden(el, "detail-version should be hidden.");
|
||||
el = gManagerWindow.document.getElementById("detail-creator");
|
||||
is_element_hidden(el, "detail-creator should be hidden.");
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-bullet");
|
||||
is_element_visible(el, "experiment-bullet should be visible.");
|
||||
|
||||
// Check previous experiment.
|
||||
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
yield openDetailsView("experiment-3");
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-state");
|
||||
is_element_visible(el, "Experiment state label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "Complete");
|
||||
}
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-time");
|
||||
is_element_visible(el, "Experiment time label should be visible.");
|
||||
if (gIsEnUsLocale) {
|
||||
Assert.equal(el.value, "5 days ago");
|
||||
}
|
||||
|
||||
el = gManagerWindow.document.getElementById("detail-version");
|
||||
is_element_hidden(el, "detail-version should be hidden.");
|
||||
el = gManagerWindow.document.getElementById("detail-creator");
|
||||
is_element_hidden(el, "detail-creator should be hidden.");
|
||||
el = gManagerWindow.document.getElementById("detail-experiment-bullet");
|
||||
is_element_visible(el, "experiment-bullet should be visible.");
|
||||
});
|
||||
|
||||
add_task(function* testRemoveAndUndo() {
|
||||
if (!gExperiments) {
|
||||
info("Skipping experiments test because that feature isn't available.");
|
||||
return;
|
||||
}
|
||||
|
||||
yield gCategoryUtilities.openType("experiment");
|
||||
|
||||
let addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(addon, "Got add-on element.");
|
||||
|
||||
yield clickRemoveButton(addon);
|
||||
addon.parentNode.ensureElementIsVisible(addon);
|
||||
|
||||
let el = gManagerWindow.document.getAnonymousElementByAttribute(addon, "class", "pending");
|
||||
is_element_visible(el, "Uninstall undo information should be visible.");
|
||||
|
||||
yield clickUndoButton(addon);
|
||||
addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
|
||||
Assert.ok(addon, "Got add-on element.");
|
||||
});
|
||||
|
||||
add_task(function* testCleanup() {
|
||||
if (gExperiments) {
|
||||
Services.prefs.clearUserPref("experiments.enabled");
|
||||
Services.prefs.setCharPref("experiments.manifest.uri", gSavedManifestURI);
|
||||
|
||||
// We perform the uninit/init cycle to purge any leftover state.
|
||||
yield OS.File.remove(gExperiments._cacheFilePath);
|
||||
yield gExperiments.uninit();
|
||||
yield gExperiments.init();
|
||||
|
||||
Services.prefs.clearUserPref("toolkit.telemetry.enabled");
|
||||
}
|
||||
|
||||
// Check post-conditions.
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
|
||||
|
||||
yield close_manager(gManagerWindow);
|
||||
});
|
||||
Reference in New Issue
Block a user