1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,242 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ 1.6 + 1.7 +/** 1.8 + * This test case populates the profile with some fake stored 1.9 + * pings, and checks that: 1.10 + * 1.11 + * 1) Pings that are considered "expired" are deleted and never sent. 1.12 + * 2) Pings that are considered "overdue" trigger a send of all 1.13 + * overdue and recent pings. 1.14 + */ 1.15 + 1.16 +"use strict" 1.17 + 1.18 +const Cc = Components.classes; 1.19 +const Ci = Components.interfaces; 1.20 +const Cr = Components.results; 1.21 +const Cu = Components.utils; 1.22 + 1.23 +Cu.import("resource://gre/modules/Services.jsm", this); 1.24 +Cu.import("resource://testing-common/httpd.js", this); 1.25 +Cu.import("resource://gre/modules/Promise.jsm", this); 1.26 +Cu.import("resource://gre/modules/TelemetryFile.jsm", this); 1.27 +Cu.import("resource://gre/modules/TelemetryPing.jsm", this); 1.28 +Cu.import("resource://gre/modules/Task.jsm", this); 1.29 +let {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {}); 1.30 + 1.31 +// We increment TelemetryFile's MAX_PING_FILE_AGE and 1.32 +// OVERDUE_PING_FILE_AGE by 1 minute so that our test pings exceed 1.33 +// those points in time, even taking into account file system imprecision. 1.34 +const ONE_MINUTE_MS = 60 * 1000; 1.35 +const EXPIRED_PING_FILE_AGE = TelemetryFile.MAX_PING_FILE_AGE + ONE_MINUTE_MS; 1.36 +const OVERDUE_PING_FILE_AGE = TelemetryFile.OVERDUE_PING_FILE_AGE + ONE_MINUTE_MS; 1.37 + 1.38 +const PING_SAVE_FOLDER = "saved-telemetry-pings"; 1.39 +const PING_TIMEOUT_LENGTH = 5000; 1.40 +const EXPIRED_PINGS = 5; 1.41 +const OVERDUE_PINGS = 6; 1.42 +const RECENT_PINGS = 4; 1.43 + 1.44 +const TOTAL_EXPECTED_PINGS = OVERDUE_PINGS + RECENT_PINGS; 1.45 + 1.46 +let gHttpServer = new HttpServer(); 1.47 +let gCreatedPings = 0; 1.48 +let gSeenPings = 0; 1.49 + 1.50 +/** 1.51 + * Creates some TelemetryPings for the current session and 1.52 + * saves them to disk. Each ping gets a unique ID slug based on 1.53 + * an incrementor. 1.54 + * 1.55 + * @param aNum the number of pings to create. 1.56 + * @param aAge the age in milliseconds to offset from now. A value 1.57 + * of 10 would make the ping 10ms older than now, for 1.58 + * example. 1.59 + * @returns Promise 1.60 + * @resolve an Array with the created pings. 1.61 + */ 1.62 +function createSavedPings(aNum, aAge) { 1.63 + return Task.spawn(function*(){ 1.64 + // Create a TelemetryPing service that we can generate payloads from. 1.65 + // Luckily, the TelemetryPing constructor does nothing that we need to 1.66 + // clean up. 1.67 + let pings = []; 1.68 + let age = Date.now() - aAge; 1.69 + 1.70 + for (let i = 0; i < aNum; ++i) { 1.71 + let payload = TelemetryPing.getPayload(); 1.72 + let ping = { slug: "test-ping-" + gCreatedPings, reason: "test", payload: payload }; 1.73 + 1.74 + yield TelemetryFile.savePing(ping); 1.75 + 1.76 + if (aAge) { 1.77 + // savePing writes to the file synchronously, so we're good to 1.78 + // modify the lastModifedTime now. 1.79 + let file = getSavePathForPing(ping); 1.80 + yield File.setDates(file, null, age); 1.81 + } 1.82 + gCreatedPings++; 1.83 + pings.push(ping); 1.84 + } 1.85 + return pings; 1.86 + }); 1.87 +} 1.88 + 1.89 +/** 1.90 + * Deletes locally saved pings in aPings if they 1.91 + * exist. 1.92 + * 1.93 + * @param aPings an Array of pings to delete. 1.94 + * @returns Promise 1.95 + */ 1.96 +function clearPings(aPings) { 1.97 + return Task.spawn(function*() { 1.98 + for (let ping of aPings) { 1.99 + let path = getSavePathForPing(ping); 1.100 + yield File.remove(path); 1.101 + } 1.102 + }); 1.103 +} 1.104 + 1.105 +/** 1.106 + * Returns a handle for the file that aPing should be 1.107 + * stored in locally. 1.108 + * 1.109 + * @returns path 1.110 + */ 1.111 +function getSavePathForPing(aPing) { 1.112 + return Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, aPing.slug); 1.113 +} 1.114 + 1.115 +/** 1.116 + * Check if the number of TelemetryPings received by the 1.117 + * HttpServer is not equal to aExpectedNum. 1.118 + * 1.119 + * @param aExpectedNum the number of pings we expect to receive. 1.120 + */ 1.121 +function assertReceivedPings(aExpectedNum) { 1.122 + do_check_eq(gSeenPings, aExpectedNum); 1.123 +} 1.124 + 1.125 +/** 1.126 + * Throws if any pings in aPings is saved locally. 1.127 + * 1.128 + * @param aPings an Array of pings to check. 1.129 + * @returns Promise 1.130 + */ 1.131 +function assertNotSaved(aPings) { 1.132 + return Task.spawn(function*() { 1.133 + let saved = 0; 1.134 + for (let ping of aPings) { 1.135 + let file = getSavePathForPing(ping); 1.136 + if (yield File.exists()) { 1.137 + saved++; 1.138 + } 1.139 + } 1.140 + if (saved > 0) { 1.141 + do_throw("Found " + saved + " unexpected saved pings."); 1.142 + } 1.143 + }); 1.144 +} 1.145 + 1.146 +/** 1.147 + * Our handler function for the HttpServer that simply 1.148 + * increments the gSeenPings global when it successfully 1.149 + * receives and decodes a TelemetryPing payload. 1.150 + * 1.151 + * @param aRequest the HTTP request sent from HttpServer. 1.152 + */ 1.153 +function pingHandler(aRequest) { 1.154 + gSeenPings++; 1.155 +} 1.156 + 1.157 +/** 1.158 + * Returns a Promise that resolves when gHttpServer has been 1.159 + * successfully shut down. 1.160 + * 1.161 + * @returns Promise 1.162 + */ 1.163 +function stopHttpServer() { 1.164 + let deferred = Promise.defer(); 1.165 + gHttpServer.stop(function() { 1.166 + deferred.resolve(); 1.167 + }) 1.168 + return deferred.promise; 1.169 +} 1.170 + 1.171 +/** 1.172 + * Teardown a TelemetryPing instance and clear out any pending 1.173 + * pings to put as back in the starting state. 1.174 + */ 1.175 +function resetTelemetry() { 1.176 + TelemetryPing.uninstall(); 1.177 + // Quick and dirty way to clear TelemetryFile's pendingPings 1.178 + // collection, and put it back in its initial state. 1.179 + let gen = TelemetryFile.popPendingPings(); 1.180 + for (let item of gen) {}; 1.181 +} 1.182 + 1.183 +/** 1.184 + * Creates and returns a TelemetryPing instance in "testing" 1.185 + * mode. 1.186 + */ 1.187 +function startTelemetry() { 1.188 + return TelemetryPing.setup(); 1.189 +} 1.190 + 1.191 +function run_test() { 1.192 + gHttpServer.registerPrefixHandler("/submit/telemetry/", pingHandler); 1.193 + gHttpServer.start(-1); 1.194 + do_get_profile(); 1.195 + Services.prefs.setBoolPref(TelemetryPing.Constants.PREF_ENABLED, true); 1.196 + Services.prefs.setCharPref(TelemetryPing.Constants.PREF_SERVER, 1.197 + "http://localhost:" + gHttpServer.identity.primaryPort); 1.198 + run_next_test(); 1.199 +} 1.200 + 1.201 +/** 1.202 + * Test that pings that are considered too old are just chucked out 1.203 + * immediately and never sent. 1.204 + */ 1.205 +add_task(function test_expired_pings_are_deleted() { 1.206 + let expiredPings = yield createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE); 1.207 + yield startTelemetry(); 1.208 + assertReceivedPings(0); 1.209 + yield assertNotSaved(expiredPings); 1.210 + yield resetTelemetry(); 1.211 +}); 1.212 + 1.213 +/** 1.214 + * Test that really recent pings are not sent on Telemetry initialization. 1.215 + */ 1.216 +add_task(function test_recent_pings_not_sent() { 1.217 + let recentPings = yield createSavedPings(RECENT_PINGS); 1.218 + yield startTelemetry(); 1.219 + assertReceivedPings(0); 1.220 + yield resetTelemetry(); 1.221 + yield clearPings(recentPings); 1.222 +}); 1.223 + 1.224 +/** 1.225 + * Create some recent, expired and overdue pings. The overdue pings should 1.226 + * trigger a send of all recent and overdue pings, but the expired pings 1.227 + * should just be deleted. 1.228 + */ 1.229 +add_task(function test_overdue_pings_trigger_send() { 1.230 + let recentPings = yield createSavedPings(RECENT_PINGS); 1.231 + let expiredPings = yield createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE); 1.232 + let overduePings = yield createSavedPings(OVERDUE_PINGS, OVERDUE_PING_FILE_AGE); 1.233 + 1.234 + yield startTelemetry(); 1.235 + assertReceivedPings(TOTAL_EXPECTED_PINGS); 1.236 + 1.237 + yield assertNotSaved(recentPings); 1.238 + yield assertNotSaved(expiredPings); 1.239 + yield assertNotSaved(overduePings); 1.240 + yield resetTelemetry(); 1.241 +}); 1.242 + 1.243 +add_task(function teardown() { 1.244 + yield stopHttpServer(); 1.245 +});