diff options
Diffstat (limited to 'toolkit/components/telemetry/tests/unit/head.js')
-rw-r--r-- | toolkit/components/telemetry/tests/unit/head.js | 319 |
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); |