test_BackupService_scheduler.js (11079B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const SCHEDULED_BACKUPS_ENABLED_PREF_NAME = "browser.backup.scheduled.enabled"; 7 const IDLE_THRESHOLD_SECONDS_PREF_NAME = 8 "browser.backup.scheduled.idle-threshold-seconds"; 9 const LAST_BACKUP_TIMESTAMP_PREF_NAME = 10 "browser.backup.scheduled.last-backup-timestamp"; 11 const MINIMUM_TIME_BETWEEN_BACKUPS_SECONDS_PREF_NAME = 12 "browser.backup.scheduled.minimum-time-between-backups-seconds"; 13 14 /** 15 * This is a very thin nsIUserIdleService implementation that doesn't do much, 16 * but with sinon we can stub out some parts of it to make sure that the 17 * BackupService uses it in the way we expect. 18 */ 19 let idleService = { 20 QueryInterface: ChromeUtils.generateQI(["nsIUserIdleService"]), 21 idleTime: 19999, 22 disabled: true, 23 addIdleObserver() {}, 24 removeIdleObserver() {}, 25 }; 26 27 add_setup(() => { 28 let fakeIdleServiceCID = MockRegistrar.register( 29 "@mozilla.org/widget/useridleservice;1", 30 idleService 31 ); 32 33 Services.prefs.setBoolPref(SCHEDULED_BACKUPS_ENABLED_PREF_NAME, true); 34 35 // We'll pretend that our threshold between backups is 20 seconds. 36 Services.prefs.setIntPref(MINIMUM_TIME_BETWEEN_BACKUPS_SECONDS_PREF_NAME, 20); 37 38 registerCleanupFunction(() => { 39 MockRegistrar.unregister(fakeIdleServiceCID); 40 Services.prefs.clearUserPref(SCHEDULED_BACKUPS_ENABLED_PREF_NAME); 41 Services.prefs.clearUserPref( 42 MINIMUM_TIME_BETWEEN_BACKUPS_SECONDS_PREF_NAME 43 ); 44 }); 45 46 // Set the startup time to zero so we only need to worry about it in its 47 // test. (Approximately, make sure Firefox starts 'long before' each test 48 // runs.) 49 sinon.stub(BackupService.prototype, "_startupTimeUnixSeconds").get(() => 0); 50 }); 51 52 /** 53 * Tests that calling initBackupScheduler registers a callback with the 54 * nsIUserIdleService. 55 */ 56 add_task(async function test_init_uninitBackupScheduler() { 57 let bs = new BackupService(); 58 let sandbox = sinon.createSandbox(); 59 sandbox.stub(idleService, "addIdleObserver"); 60 sandbox.stub(idleService, "removeIdleObserver"); 61 62 bs.initBackupScheduler(); 63 Assert.ok( 64 idleService.addIdleObserver.calledOnce, 65 "addIdleObserver was called" 66 ); 67 Assert.ok( 68 idleService.addIdleObserver.firstCall.args[0] instanceof Ci.nsIObserver, 69 "The first argument to addIdleObserver was an nsIObserver" 70 ); 71 const THRESHOLD_SECONDS = Services.prefs.getIntPref( 72 IDLE_THRESHOLD_SECONDS_PREF_NAME 73 ); 74 Assert.equal( 75 idleService.addIdleObserver.firstCall.args[1], 76 THRESHOLD_SECONDS, 77 "The idle threshold preference value was passed as the second argument." 78 ); 79 Assert.ok( 80 idleService.removeIdleObserver.notCalled, 81 "removeIdleObserver has not been called yet." 82 ); 83 84 // Hold a reference to what addIdleObserver was called with as its first 85 // argument, so we can compare it against what's passed to removeIdleObserver. 86 let addObserverArg = idleService.addIdleObserver.firstCall.args[0]; 87 88 // We want to make sure that uninitBackupScheduler doesn't call this again, 89 // so reset its call history. 90 idleService.addIdleObserver.resetHistory(); 91 92 // Now, let's pretend that the preference for the idle threshold changed 93 // before we could uninit the backup scheduler. We should ensure that this 94 // change is _not_ reflected whenever deregistration of the idle callback 95 // occurs, since it wouldn't match the registration arguments. 96 Services.prefs.setIntPref( 97 IDLE_THRESHOLD_SECONDS_PREF_NAME, 98 THRESHOLD_SECONDS + 5 99 ); 100 101 bs.uninitBackupScheduler(); 102 Assert.ok( 103 idleService.addIdleObserver.notCalled, 104 "addIdleObserver was not called again." 105 ); 106 Assert.ok( 107 idleService.removeIdleObserver.calledOnce, 108 "removeIdleObserver was called once." 109 ); 110 Assert.ok( 111 idleService.removeIdleObserver.firstCall.args[0] instanceof Ci.nsIObserver, 112 "The first argument to addIdleObserver was an nsIObserver" 113 ); 114 Assert.equal( 115 idleService.removeIdleObserver.firstCall.args[0], 116 addObserverArg, 117 "The first argument to addIdleObserver matches the first argument to removeIdleObserver" 118 ); 119 Assert.equal( 120 idleService.removeIdleObserver.firstCall.args[1], 121 THRESHOLD_SECONDS, 122 "The original idle threshold preference value was passed as the second argument." 123 ); 124 125 sandbox.restore(); 126 Services.prefs.clearUserPref(IDLE_THRESHOLD_SECONDS_PREF_NAME); 127 }); 128 129 /** 130 * Tests that calling BackupService.onObserve with the "idle" notification 131 * causes the BackupService.onIdle method to be called. 132 */ 133 add_task(async function test_BackupService_onObserve_idle() { 134 let bs = new BackupService(); 135 let sandbox = sinon.createSandbox(); 136 sandbox.stub(bs, "onIdle"); 137 138 // The subject for the idle notification is always the idle service itself. 139 bs.onObserve(idleService, "idle"); 140 Assert.ok(bs.onIdle.calledOnce, "BackupService.onIdle was called."); 141 142 sandbox.restore(); 143 }); 144 145 /** 146 * Tests that calling BackupService.onObserve with the 147 * "quit-application-granted" notification causes the 148 * BackupService.uninitBackupScheduler method to be called. 149 */ 150 add_task( 151 async function test_BackupService_onObserve_quit_application_granted() { 152 let bs = new BackupService(); 153 let sandbox = sinon.createSandbox(); 154 sandbox.stub(bs, "uninitBackupScheduler"); 155 156 // The subject for the quit-application-granted notification is null. 157 bs.onObserve(null, "quit-application-granted"); 158 Assert.ok( 159 bs.uninitBackupScheduler.calledOnce, 160 "BackupService.uninitBackupScheduler was called." 161 ); 162 163 sandbox.restore(); 164 } 165 ); 166 167 /** 168 * Tests that calling onIdle when a backup has never occurred causes a backup to 169 * get scheduled. 170 */ 171 add_task(async function test_BackupService_idle_no_backup_exists() { 172 // Make sure no last backup timestamp is recorded. 173 Services.prefs.clearUserPref(LAST_BACKUP_TIMESTAMP_PREF_NAME); 174 175 let bs = new BackupService(); 176 let sandbox = sinon.createSandbox(); 177 sandbox.stub(bs, "createBackupOnIdleDispatch"); 178 179 bs.initBackupScheduler(); 180 Assert.equal( 181 bs.state.lastBackupDate, 182 null, 183 "State should have null for lastBackupDate" 184 ); 185 186 bs.onIdle(); 187 Assert.ok( 188 bs.createBackupOnIdleDispatch.calledOnce, 189 "BackupService.createBackupOnIdleDispatch was called." 190 ); 191 Assert.equal( 192 bs.createBackupOnIdleDispatch.firstCall.args[0].reason, 193 "idle", 194 "Recorded that the backup was caused by being idle." 195 ); 196 197 sandbox.restore(); 198 }); 199 200 /** 201 * Tests that calling onIdle when a backup has occurred recently does not cause 202 * a backup to get scheduled. 203 */ 204 add_task(async function test_BackupService_idle_not_expired_backup() { 205 // Let's calculate a Date that's five seconds ago. 206 let fiveSecondsAgo = Date.now() - 5000; /* 5 seconds in milliseconds */ 207 let lastBackupPrefValue = Math.floor(fiveSecondsAgo / 1000); 208 Services.prefs.setIntPref( 209 LAST_BACKUP_TIMESTAMP_PREF_NAME, 210 lastBackupPrefValue 211 ); 212 213 let bs = new BackupService(); 214 let sandbox = sinon.createSandbox(); 215 bs.initBackupScheduler(); 216 Assert.equal( 217 bs.state.lastBackupDate, 218 lastBackupPrefValue, 219 "State should have cached lastBackupDate" 220 ); 221 222 sandbox.stub(bs, "createBackupOnIdleDispatch"); 223 224 bs.onIdle(); 225 Assert.ok( 226 bs.createBackupOnIdleDispatch.notCalled, 227 "BackupService.createBackupOnIdleDispatch was not called." 228 ); 229 230 sandbox.restore(); 231 }); 232 233 /** 234 * Tests that calling onIdle when a backup has occurred, but after the threshold 235 * does cause a backup to get scheduled 236 */ 237 add_task(async function test_BackupService_idle_expired_backup() { 238 // Let's calculate a Date that's twenty five seconds ago. 239 let twentyFiveSecondsAgo = 240 Date.now() - 25000; /* 25 seconds in milliseconds */ 241 let lastBackupPrefValue = Math.floor(twentyFiveSecondsAgo / 1000); 242 243 Services.prefs.setIntPref( 244 LAST_BACKUP_TIMESTAMP_PREF_NAME, 245 lastBackupPrefValue 246 ); 247 248 let bs = new BackupService(); 249 let sandbox = sinon.createSandbox(); 250 bs.initBackupScheduler(); 251 Assert.equal( 252 bs.state.lastBackupDate, 253 lastBackupPrefValue, 254 "State should have cached lastBackupDate" 255 ); 256 257 sandbox.stub(bs, "createBackupOnIdleDispatch"); 258 259 bs.onIdle(); 260 Assert.ok( 261 bs.createBackupOnIdleDispatch.calledOnce, 262 "BackupService.createBackupOnIdleDispatch was called." 263 ); 264 Assert.equal( 265 bs.createBackupOnIdleDispatch.firstCall.args[0].reason, 266 "idle", 267 "Recorded that the backup was caused by being idle." 268 ); 269 270 sandbox.restore(); 271 }); 272 273 /** 274 * Tests that calling onIdle when a backup occurred in the future somehow causes 275 * a backup to get scheduled. 276 */ 277 add_task(async function test_BackupService_idle_time_travel() { 278 // Let's calculate a Date that's twenty-five seconds in the future. 279 let twentyFiveSecondsFromNow = 280 Date.now() + 25000; /* 25 seconds in milliseconds */ 281 let lastBackupPrefValue = Math.floor(twentyFiveSecondsFromNow / 1000); 282 283 Services.prefs.setIntPref( 284 LAST_BACKUP_TIMESTAMP_PREF_NAME, 285 lastBackupPrefValue 286 ); 287 288 let bs = new BackupService(); 289 let sandbox = sinon.createSandbox(); 290 bs.initBackupScheduler(); 291 Assert.equal( 292 bs.state.lastBackupDate, 293 lastBackupPrefValue, 294 "State should have cached lastBackupDate" 295 ); 296 297 sandbox.stub(bs, "createBackupOnIdleDispatch"); 298 299 bs.onIdle(); 300 Assert.ok( 301 bs.createBackupOnIdleDispatch.calledOnce, 302 "BackupService.createBackupOnIdleDispatch was called." 303 ); 304 Assert.equal( 305 bs.createBackupOnIdleDispatch.firstCall.args[0].reason, 306 "idle", 307 "Recorded that the backup was caused by being idle." 308 ); 309 Assert.equal( 310 bs.state.lastBackupDate, 311 null, 312 "Should have cleared the last backup date." 313 ); 314 315 sandbox.restore(); 316 }); 317 318 /** 319 * Tests that calling onIdle when a backup has occurred after the threshold 320 * yet before Firefox started indicates to telemetry that the backup was 321 * missed. 322 */ 323 add_task(async function test_BackupService_idle_expired_backup() { 324 // Let's calculate a Date that's twenty five seconds ago. 325 let twentyFiveSecondsAgo = Date.now() - 25000; 326 let lastBackupPrefValue = Math.floor(twentyFiveSecondsAgo / 1000); 327 328 Services.prefs.setIntPref( 329 LAST_BACKUP_TIMESTAMP_PREF_NAME, 330 lastBackupPrefValue 331 ); 332 333 let bs = new BackupService(); 334 let sandbox = sinon.createSandbox(); 335 336 // This needs to be greater than 337 // LAST_BACKUP_TIMESTAMP_PREF_NAME + MINIMUM_TIME_BETWEEN_BACKUPS_SECONDS_PREF_NAME 338 // and less than the current time. 339 let twentySecondsAgo = Math.floor(twentyFiveSecondsAgo + 21); 340 sandbox.stub(bs, "_startupTimeUnixSeconds").get(() => twentySecondsAgo); 341 342 bs.initBackupScheduler(); 343 Assert.equal( 344 bs.state.lastBackupDate, 345 lastBackupPrefValue, 346 "State should have cached lastBackupDate" 347 ); 348 349 sandbox.stub(bs, "createBackupOnIdleDispatch"); 350 351 bs.onIdle(); 352 Assert.ok( 353 bs.createBackupOnIdleDispatch.calledOnce, 354 "BackupService.createBackupOnIdleDispatch was called." 355 ); 356 Assert.equal( 357 bs.createBackupOnIdleDispatch.firstCall.args[0].reason, 358 "missed", 359 "Recorded that the backup was caused by missing the deadline." 360 ); 361 362 sandbox.restore(); 363 });