michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ michael@0: michael@0: /** michael@0: * This test case populates the profile with some fake stored michael@0: * pings, and checks that: michael@0: * michael@0: * 1) Pings that are considered "expired" are deleted and never sent. michael@0: * 2) Pings that are considered "overdue" trigger a send of all michael@0: * overdue and recent pings. michael@0: */ michael@0: michael@0: "use strict" michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm", this); michael@0: Cu.import("resource://testing-common/httpd.js", this); michael@0: Cu.import("resource://gre/modules/Promise.jsm", this); michael@0: Cu.import("resource://gre/modules/TelemetryFile.jsm", this); michael@0: Cu.import("resource://gre/modules/TelemetryPing.jsm", this); michael@0: Cu.import("resource://gre/modules/Task.jsm", this); michael@0: let {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {}); michael@0: michael@0: // We increment TelemetryFile's MAX_PING_FILE_AGE and michael@0: // OVERDUE_PING_FILE_AGE by 1 minute so that our test pings exceed michael@0: // those points in time, even taking into account file system imprecision. michael@0: const ONE_MINUTE_MS = 60 * 1000; michael@0: const EXPIRED_PING_FILE_AGE = TelemetryFile.MAX_PING_FILE_AGE + ONE_MINUTE_MS; michael@0: const OVERDUE_PING_FILE_AGE = TelemetryFile.OVERDUE_PING_FILE_AGE + ONE_MINUTE_MS; michael@0: michael@0: const PING_SAVE_FOLDER = "saved-telemetry-pings"; michael@0: const PING_TIMEOUT_LENGTH = 5000; michael@0: const EXPIRED_PINGS = 5; michael@0: const OVERDUE_PINGS = 6; michael@0: const RECENT_PINGS = 4; michael@0: michael@0: const TOTAL_EXPECTED_PINGS = OVERDUE_PINGS + RECENT_PINGS; michael@0: michael@0: let gHttpServer = new HttpServer(); michael@0: let gCreatedPings = 0; michael@0: let gSeenPings = 0; michael@0: michael@0: /** michael@0: * Creates some TelemetryPings for the current session and michael@0: * saves them to disk. Each ping gets a unique ID slug based on michael@0: * an incrementor. michael@0: * michael@0: * @param aNum the number of pings to create. michael@0: * @param aAge the age in milliseconds to offset from now. A value michael@0: * of 10 would make the ping 10ms older than now, for michael@0: * example. michael@0: * @returns Promise michael@0: * @resolve an Array with the created pings. michael@0: */ michael@0: function createSavedPings(aNum, aAge) { michael@0: return Task.spawn(function*(){ michael@0: // Create a TelemetryPing service that we can generate payloads from. michael@0: // Luckily, the TelemetryPing constructor does nothing that we need to michael@0: // clean up. michael@0: let pings = []; michael@0: let age = Date.now() - aAge; michael@0: michael@0: for (let i = 0; i < aNum; ++i) { michael@0: let payload = TelemetryPing.getPayload(); michael@0: let ping = { slug: "test-ping-" + gCreatedPings, reason: "test", payload: payload }; michael@0: michael@0: yield TelemetryFile.savePing(ping); michael@0: michael@0: if (aAge) { michael@0: // savePing writes to the file synchronously, so we're good to michael@0: // modify the lastModifedTime now. michael@0: let file = getSavePathForPing(ping); michael@0: yield File.setDates(file, null, age); michael@0: } michael@0: gCreatedPings++; michael@0: pings.push(ping); michael@0: } michael@0: return pings; michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Deletes locally saved pings in aPings if they michael@0: * exist. michael@0: * michael@0: * @param aPings an Array of pings to delete. michael@0: * @returns Promise michael@0: */ michael@0: function clearPings(aPings) { michael@0: return Task.spawn(function*() { michael@0: for (let ping of aPings) { michael@0: let path = getSavePathForPing(ping); michael@0: yield File.remove(path); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Returns a handle for the file that aPing should be michael@0: * stored in locally. michael@0: * michael@0: * @returns path michael@0: */ michael@0: function getSavePathForPing(aPing) { michael@0: return Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, aPing.slug); michael@0: } michael@0: michael@0: /** michael@0: * Check if the number of TelemetryPings received by the michael@0: * HttpServer is not equal to aExpectedNum. michael@0: * michael@0: * @param aExpectedNum the number of pings we expect to receive. michael@0: */ michael@0: function assertReceivedPings(aExpectedNum) { michael@0: do_check_eq(gSeenPings, aExpectedNum); michael@0: } michael@0: michael@0: /** michael@0: * Throws if any pings in aPings is saved locally. michael@0: * michael@0: * @param aPings an Array of pings to check. michael@0: * @returns Promise michael@0: */ michael@0: function assertNotSaved(aPings) { michael@0: return Task.spawn(function*() { michael@0: let saved = 0; michael@0: for (let ping of aPings) { michael@0: let file = getSavePathForPing(ping); michael@0: if (yield File.exists()) { michael@0: saved++; michael@0: } michael@0: } michael@0: if (saved > 0) { michael@0: do_throw("Found " + saved + " unexpected saved pings."); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Our handler function for the HttpServer that simply michael@0: * increments the gSeenPings global when it successfully michael@0: * receives and decodes a TelemetryPing payload. michael@0: * michael@0: * @param aRequest the HTTP request sent from HttpServer. michael@0: */ michael@0: function pingHandler(aRequest) { michael@0: gSeenPings++; michael@0: } michael@0: michael@0: /** michael@0: * Returns a Promise that resolves when gHttpServer has been michael@0: * successfully shut down. michael@0: * michael@0: * @returns Promise michael@0: */ michael@0: function stopHttpServer() { michael@0: let deferred = Promise.defer(); michael@0: gHttpServer.stop(function() { michael@0: deferred.resolve(); michael@0: }) michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Teardown a TelemetryPing instance and clear out any pending michael@0: * pings to put as back in the starting state. michael@0: */ michael@0: function resetTelemetry() { michael@0: TelemetryPing.uninstall(); michael@0: // Quick and dirty way to clear TelemetryFile's pendingPings michael@0: // collection, and put it back in its initial state. michael@0: let gen = TelemetryFile.popPendingPings(); michael@0: for (let item of gen) {}; michael@0: } michael@0: michael@0: /** michael@0: * Creates and returns a TelemetryPing instance in "testing" michael@0: * mode. michael@0: */ michael@0: function startTelemetry() { michael@0: return TelemetryPing.setup(); michael@0: } michael@0: michael@0: function run_test() { michael@0: gHttpServer.registerPrefixHandler("/submit/telemetry/", pingHandler); michael@0: gHttpServer.start(-1); michael@0: do_get_profile(); michael@0: Services.prefs.setBoolPref(TelemetryPing.Constants.PREF_ENABLED, true); michael@0: Services.prefs.setCharPref(TelemetryPing.Constants.PREF_SERVER, michael@0: "http://localhost:" + gHttpServer.identity.primaryPort); michael@0: run_next_test(); michael@0: } michael@0: michael@0: /** michael@0: * Test that pings that are considered too old are just chucked out michael@0: * immediately and never sent. michael@0: */ michael@0: add_task(function test_expired_pings_are_deleted() { michael@0: let expiredPings = yield createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE); michael@0: yield startTelemetry(); michael@0: assertReceivedPings(0); michael@0: yield assertNotSaved(expiredPings); michael@0: yield resetTelemetry(); michael@0: }); michael@0: michael@0: /** michael@0: * Test that really recent pings are not sent on Telemetry initialization. michael@0: */ michael@0: add_task(function test_recent_pings_not_sent() { michael@0: let recentPings = yield createSavedPings(RECENT_PINGS); michael@0: yield startTelemetry(); michael@0: assertReceivedPings(0); michael@0: yield resetTelemetry(); michael@0: yield clearPings(recentPings); michael@0: }); michael@0: michael@0: /** michael@0: * Create some recent, expired and overdue pings. The overdue pings should michael@0: * trigger a send of all recent and overdue pings, but the expired pings michael@0: * should just be deleted. michael@0: */ michael@0: add_task(function test_overdue_pings_trigger_send() { michael@0: let recentPings = yield createSavedPings(RECENT_PINGS); michael@0: let expiredPings = yield createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE); michael@0: let overduePings = yield createSavedPings(OVERDUE_PINGS, OVERDUE_PING_FILE_AGE); michael@0: michael@0: yield startTelemetry(); michael@0: assertReceivedPings(TOTAL_EXPECTED_PINGS); michael@0: michael@0: yield assertNotSaved(recentPings); michael@0: yield assertNotSaved(expiredPings); michael@0: yield assertNotSaved(overduePings); michael@0: yield resetTelemetry(); michael@0: }); michael@0: michael@0: add_task(function teardown() { michael@0: yield stopHttpServer(); michael@0: });