summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/unit/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/tests/unit/head.js')
-rw-r--r--toolkit/components/telemetry/tests/unit/head.js319
1 files changed, 319 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/unit/head.js b/toolkit/components/telemetry/tests/unit/head.js
new file mode 100644
index 000000000..51be25766
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/head.js
@@ -0,0 +1,319 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { classes: Cc, utils: Cu, interfaces: Ci, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/TelemetryController.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/PromiseUtils.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/FileUtils.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://testing-common/httpd.js", this);
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonTestUtils",
+ "resource://testing-common/AddonTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+const gIsWindows = AppConstants.platform == "win";
+const gIsMac = AppConstants.platform == "macosx";
+const gIsAndroid = AppConstants.platform == "android";
+const gIsGonk = AppConstants.platform == "gonk";
+const gIsLinux = AppConstants.platform == "linux";
+
+const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+
+const MILLISECONDS_PER_MINUTE = 60 * 1000;
+const MILLISECONDS_PER_HOUR = 60 * MILLISECONDS_PER_MINUTE;
+const MILLISECONDS_PER_DAY = 24 * MILLISECONDS_PER_HOUR;
+
+const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
+
+const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
+
+var gGlobalScope = this;
+
+const PingServer = {
+ _httpServer: null,
+ _started: false,
+ _defers: [ PromiseUtils.defer() ],
+ _currentDeferred: 0,
+
+ get port() {
+ return this._httpServer.identity.primaryPort;
+ },
+
+ get started() {
+ return this._started;
+ },
+
+ registerPingHandler: function(handler) {
+ const wrapped = wrapWithExceptionHandler(handler);
+ this._httpServer.registerPrefixHandler("/submit/telemetry/", wrapped);
+ },
+
+ resetPingHandler: function() {
+ this.registerPingHandler((request, response) => {
+ let deferred = this._defers[this._defers.length - 1];
+ this._defers.push(PromiseUtils.defer());
+ deferred.resolve(request);
+ });
+ },
+
+ start: function() {
+ this._httpServer = new HttpServer();
+ this._httpServer.start(-1);
+ this._started = true;
+ this.clearRequests();
+ this.resetPingHandler();
+ },
+
+ stop: function() {
+ return new Promise(resolve => {
+ this._httpServer.stop(resolve);
+ this._started = false;
+ });
+ },
+
+ clearRequests: function() {
+ this._defers = [ PromiseUtils.defer() ];
+ this._currentDeferred = 0;
+ },
+
+ promiseNextRequest: function() {
+ const deferred = this._defers[this._currentDeferred++];
+ // Send the ping to the consumer on the next tick, so that the completion gets
+ // signaled to Telemetry.
+ return new Promise(r => Services.tm.currentThread.dispatch(() => r(deferred.promise),
+ Ci.nsIThread.DISPATCH_NORMAL));
+ },
+
+ promiseNextPing: function() {
+ return this.promiseNextRequest().then(request => decodeRequestPayload(request));
+ },
+
+ promiseNextRequests: Task.async(function*(count) {
+ let results = [];
+ for (let i=0; i<count; ++i) {
+ results.push(yield this.promiseNextRequest());
+ }
+
+ return results;
+ }),
+
+ promiseNextPings: function(count) {
+ return this.promiseNextRequests(count).then(requests => {
+ return Array.from(requests, decodeRequestPayload);
+ });
+ },
+};
+
+/**
+ * Decode the payload of an HTTP request into a ping.
+ * @param {Object} request The data representing an HTTP request (nsIHttpRequest).
+ * @return {Object} The decoded ping payload.
+ */
+function decodeRequestPayload(request) {
+ let s = request.bodyInputStream;
+ let payload = null;
+ let decoder = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON)
+
+ if (request.getHeader("content-encoding") == "gzip") {
+ let observer = {
+ buffer: "",
+ onStreamComplete: function(loader, context, status, length, result) {
+ this.buffer = String.fromCharCode.apply(this, result);
+ }
+ };
+
+ let scs = Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService);
+ let listener = Cc["@mozilla.org/network/stream-loader;1"]
+ .createInstance(Ci.nsIStreamLoader);
+ listener.init(observer);
+ let converter = scs.asyncConvertData("gzip", "uncompressed",
+ listener, null);
+ converter.onStartRequest(null, null);
+ converter.onDataAvailable(null, null, s, 0, s.available());
+ converter.onStopRequest(null, null, null);
+ let unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = "UTF-8";
+ let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer);
+ utf8string += unicodeConverter.Finish();
+ payload = JSON.parse(utf8string);
+ } else {
+ payload = decoder.decodeFromStream(s, s.available());
+ }
+
+ return payload;
+}
+
+function wrapWithExceptionHandler(f) {
+ function wrapper(...args) {
+ try {
+ f(...args);
+ } catch (ex) {
+ if (typeof(ex) != 'object') {
+ throw ex;
+ }
+ dump("Caught exception: " + ex.message + "\n");
+ dump(ex.stack);
+ do_test_finished();
+ }
+ }
+ return wrapper;
+}
+
+function loadAddonManager(...args) {
+ AddonTestUtils.init(gGlobalScope);
+ AddonTestUtils.overrideCertDB();
+ createAppInfo(...args);
+
+ // As we're not running in application, we need to setup the features directory
+ // used by system add-ons.
+ const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true);
+ AddonTestUtils.registerDirectory("XREAppFeat", distroDir);
+ return AddonTestUtils.promiseStartupManager();
+}
+
+var gAppInfo = null;
+
+function createAppInfo(ID="xpcshell@tests.mozilla.org", name="XPCShell",
+ version="1.0", platformVersion="1.0") {
+ AddonTestUtils.createAppInfo(ID, name, version, platformVersion);
+ gAppInfo = AddonTestUtils.appInfo;
+}
+
+// Fake the timeout functions for the TelemetryScheduler.
+function fakeSchedulerTimer(set, clear) {
+ let session = Cu.import("resource://gre/modules/TelemetrySession.jsm");
+ session.Policy.setSchedulerTickTimeout = set;
+ session.Policy.clearSchedulerTickTimeout = clear;
+}
+
+/**
+ * Fake the current date.
+ * This passes all received arguments to a new Date constructor and
+ * uses the resulting date to fake the time in Telemetry modules.
+ *
+ * @return Date The new faked date.
+ */
+function fakeNow(...args) {
+ const date = new Date(...args);
+ const modules = [
+ Cu.import("resource://gre/modules/TelemetrySession.jsm"),
+ Cu.import("resource://gre/modules/TelemetryEnvironment.jsm"),
+ Cu.import("resource://gre/modules/TelemetryController.jsm"),
+ Cu.import("resource://gre/modules/TelemetryStorage.jsm"),
+ Cu.import("resource://gre/modules/TelemetrySend.jsm"),
+ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm"),
+ ];
+
+ for (let m of modules) {
+ m.Policy.now = () => date;
+ }
+
+ return new Date(date);
+}
+
+function fakeMonotonicNow(ms) {
+ const m = Cu.import("resource://gre/modules/TelemetrySession.jsm");
+ m.Policy.monotonicNow = () => ms;
+ return ms;
+}
+
+// Fake the timeout functions for TelemetryController sending.
+function fakePingSendTimer(set, clear) {
+ let module = Cu.import("resource://gre/modules/TelemetrySend.jsm");
+ let obj = Cu.cloneInto({set, clear}, module, {cloneFunctions:true});
+ module.Policy.setSchedulerTickTimeout = obj.set;
+ module.Policy.clearSchedulerTickTimeout = obj.clear;
+}
+
+function fakeMidnightPingFuzzingDelay(delayMs) {
+ let module = Cu.import("resource://gre/modules/TelemetrySend.jsm");
+ module.Policy.midnightPingFuzzingDelay = () => delayMs;
+}
+
+function fakeGeneratePingId(func) {
+ let module = Cu.import("resource://gre/modules/TelemetryController.jsm");
+ module.Policy.generatePingId = func;
+}
+
+function fakeCachedClientId(uuid) {
+ let module = Cu.import("resource://gre/modules/TelemetryController.jsm");
+ module.Policy.getCachedClientID = () => uuid;
+}
+
+// Return a date that is |offset| ms in the future from |date|.
+function futureDate(date, offset) {
+ return new Date(date.getTime() + offset);
+}
+
+function truncateToDays(aMsec) {
+ return Math.floor(aMsec / MILLISECONDS_PER_DAY);
+}
+
+// Returns a promise that resolves to true when the passed promise rejects,
+// false otherwise.
+function promiseRejects(promise) {
+ return promise.then(() => false, () => true);
+}
+
+// Generates a random string of at least a specific length.
+function generateRandomString(length) {
+ let string = "";
+
+ while (string.length < length) {
+ string += Math.random().toString(36);
+ }
+
+ return string.substring(0, length);
+}
+
+// Short-hand for retrieving the histogram with that id.
+function getHistogram(histogramId) {
+ return Telemetry.getHistogramById(histogramId);
+}
+
+// Short-hand for retrieving the snapshot of the Histogram with that id.
+function getSnapshot(histogramId) {
+ return Telemetry.getHistogramById(histogramId).snapshot();
+}
+
+// Helper for setting an empty list of Environment preferences to watch.
+function setEmptyPrefWatchlist() {
+ let TelemetryEnvironment =
+ Cu.import("resource://gre/modules/TelemetryEnvironment.jsm").TelemetryEnvironment;
+ return TelemetryEnvironment.onInitialized().then(() => {
+ TelemetryEnvironment.testWatchPreferences(new Map());
+ });
+}
+
+if (runningInParent) {
+ // Set logging preferences for all the tests.
+ Services.prefs.setCharPref("toolkit.telemetry.log.level", "Trace");
+ // Telemetry archiving should be on.
+ Services.prefs.setBoolPref("toolkit.telemetry.archive.enabled", true);
+ // Telemetry xpcshell tests cannot show the infobar.
+ Services.prefs.setBoolPref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
+ // FHR uploads should be enabled.
+ Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true);
+
+ fakePingSendTimer((callback, timeout) => {
+ Services.tm.mainThread.dispatch(() => callback(), Ci.nsIThread.DISPATCH_NORMAL);
+ },
+ () => {});
+
+ do_register_cleanup(() => TelemetrySend.shutdown());
+}
+
+TelemetryController.testInitLogging();
+
+// Avoid timers interrupting test behavior.
+fakeSchedulerTimer(() => {}, () => {});
+// Make pind sending predictable.
+fakeMidnightPingFuzzingDelay(0);