toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* Any copyright is dedicated to the Public Domain.
michael@0 2 * http://creativecommons.org/publicdomain/zero/1.0/
michael@0 3 */
michael@0 4
michael@0 5 // Test behaviour of module to perform deferred save of data
michael@0 6 // files to disk
michael@0 7
michael@0 8 "use strict";
michael@0 9
michael@0 10 const testFile = gProfD.clone();
michael@0 11 testFile.append("DeferredSaveTest");
michael@0 12
michael@0 13 Components.utils.import("resource://gre/modules/Promise.jsm");
michael@0 14
michael@0 15 let DSContext = Components.utils.import("resource://gre/modules/DeferredSave.jsm", {});
michael@0 16 let DeferredSave = DSContext.DeferredSave;
michael@0 17
michael@0 18 // Test wrapper to let us do promise/task based testing of DeferredSave
michael@0 19 function DeferredSaveTester(aDataProvider) {
michael@0 20 let tester = {
michael@0 21 // Deferred for the promise returned by the mock writeAtomic
michael@0 22 waDeferred: null,
michael@0 23
michael@0 24 // The most recent data "written" by the mock OS.File.writeAtomic
michael@0 25 writtenData: undefined,
michael@0 26
michael@0 27 dataToSave: "Data to save",
michael@0 28
michael@0 29 save: (aData, aWriteHandler) => {
michael@0 30 tester.writeHandler = aWriteHandler || writer;
michael@0 31 tester.dataToSave = aData;
michael@0 32 return tester.saver.saveChanges();
michael@0 33 },
michael@0 34
michael@0 35 flush: (aWriteHandler) => {
michael@0 36 tester.writeHandler = aWriteHandler || writer;
michael@0 37 return tester.saver.flush();
michael@0 38 },
michael@0 39
michael@0 40 get lastError() {
michael@0 41 return tester.saver.lastError;
michael@0 42 }
michael@0 43 };
michael@0 44
michael@0 45 // Default write handler for most cases where the test case doesn't need
michael@0 46 // to do anything while the write is in progress; just completes the write
michael@0 47 // on the next event loop
michael@0 48 function writer(aTester) {
michael@0 49 do_print("default write callback");
michael@0 50 let length = aTester.writtenData.length;
michael@0 51 do_execute_soon(() => aTester.waDeferred.resolve(length));
michael@0 52 }
michael@0 53
michael@0 54 if (!aDataProvider)
michael@0 55 aDataProvider = () => tester.dataToSave;
michael@0 56
michael@0 57 tester.saver = new DeferredSave(testFile.path, aDataProvider);
michael@0 58
michael@0 59 // Install a mock for OS.File.writeAtomic to let us control the async
michael@0 60 // behaviour of the promise
michael@0 61 DSContext.OS.File.writeAtomic = function mock_writeAtomic(aFile, aData, aOptions) {
michael@0 62 do_print("writeAtomic: " + aFile + " data: '" + aData + "', " + aOptions.toSource());
michael@0 63 tester.writtenData = aData;
michael@0 64 tester.waDeferred = Promise.defer();
michael@0 65 tester.writeHandler(tester);
michael@0 66 return tester.waDeferred.promise;
michael@0 67 };
michael@0 68
michael@0 69 return tester;
michael@0 70 };
michael@0 71
michael@0 72 /**
michael@0 73 * Install a mock nsITimer factory that triggers on the next spin of
michael@0 74 * the event loop after it is scheduled
michael@0 75 */
michael@0 76 function setQuickMockTimer() {
michael@0 77 let quickTimer = {
michael@0 78 initWithCallback: function(aFunction, aDelay, aType) {
michael@0 79 do_print("Starting quick timer, delay = " + aDelay);
michael@0 80 do_execute_soon(aFunction);
michael@0 81 },
michael@0 82 cancel: function() {
michael@0 83 do_throw("Attempted to cancel a quickMockTimer");
michael@0 84 }
michael@0 85 };
michael@0 86 DSContext.MakeTimer = () => {
michael@0 87 do_print("Creating quick timer");
michael@0 88 return quickTimer;
michael@0 89 };
michael@0 90 }
michael@0 91
michael@0 92 /**
michael@0 93 * Install a mock nsITimer factory in DeferredSave.jsm, returning a promise that resolves
michael@0 94 * when the client code sets the timer. Test cases can use this to wait for client code to
michael@0 95 * be ready for a timer event, and then signal the event by calling mockTimer.callback().
michael@0 96 * This could use some enhancement; clients can re-use the returned timer,
michael@0 97 * but with this implementation it's not possible for the test to wait for
michael@0 98 * a second call to initWithCallback() on the re-used timer.
michael@0 99 * @return Promise{mockTimer} that resolves when initWithCallback()
michael@0 100 * is called
michael@0 101 */
michael@0 102 function setPromiseMockTimer() {
michael@0 103 let waiter = Promise.defer();
michael@0 104 let mockTimer = {
michael@0 105 callback: null,
michael@0 106 delay: null,
michael@0 107 type: null,
michael@0 108 isCancelled: false,
michael@0 109
michael@0 110 initWithCallback: function(aFunction, aDelay, aType) {
michael@0 111 do_print("Starting timer, delay = " + aDelay);
michael@0 112 this.callback = aFunction;
michael@0 113 this.delay = aDelay;
michael@0 114 this.type = aType;
michael@0 115 // cancelled timers can be re-used
michael@0 116 this.isCancelled = false;
michael@0 117 waiter.resolve(this);
michael@0 118 },
michael@0 119 cancel: function() {
michael@0 120 do_print("Cancelled mock timer");
michael@0 121 this.callback = null;
michael@0 122 this.delay = null;
michael@0 123 this.type = null;
michael@0 124 this.isCancelled = true;
michael@0 125 // If initWithCallback was never called, resolve to let tests check for cancel
michael@0 126 waiter.resolve(this);
michael@0 127 }
michael@0 128 };
michael@0 129 DSContext.MakeTimer = () => {
michael@0 130 do_print("Creating mock timer");
michael@0 131 return mockTimer;
michael@0 132 };
michael@0 133 return waiter.promise;
michael@0 134 }
michael@0 135
michael@0 136 /**
michael@0 137 * Return a Promise<null> that resolves after the specified number of milliseconds
michael@0 138 */
michael@0 139 function delay(aDelayMS) {
michael@0 140 let deferred = Promise.defer();
michael@0 141 do_timeout(aDelayMS, () => deferred.resolve(null));
michael@0 142 return deferred.promise;
michael@0 143 }
michael@0 144
michael@0 145 function run_test() {
michael@0 146 run_next_test();
michael@0 147 }
michael@0 148
michael@0 149 // Modify set data once, ask for save, make sure it saves cleanly
michael@0 150 add_task(function test_basic_save_succeeds() {
michael@0 151 setQuickMockTimer();
michael@0 152 let tester = DeferredSaveTester();
michael@0 153 let data = "Test 1 Data";
michael@0 154
michael@0 155 yield tester.save(data);
michael@0 156 do_check_eq(tester.writtenData, data);
michael@0 157 do_check_eq(1, tester.saver.totalSaves);
michael@0 158 });
michael@0 159
michael@0 160 // Two saves called during the same event loop, both with callbacks
michael@0 161 // Make sure we save only the second version of the data
michael@0 162 add_task(function test_two_saves() {
michael@0 163 setQuickMockTimer();
michael@0 164 let tester = DeferredSaveTester();
michael@0 165 let firstCallback_happened = false;
michael@0 166 let firstData = "Test first save";
michael@0 167 let secondData = "Test second save";
michael@0 168
michael@0 169 // first save should not resolve until after the second one is called,
michael@0 170 // so we can't just yield this promise
michael@0 171 tester.save(firstData).then(count => {
michael@0 172 do_check_eq(secondData, tester.writtenData);
michael@0 173 do_check_false(firstCallback_happened);
michael@0 174 firstCallback_happened = true;
michael@0 175 }, do_report_unexpected_exception);
michael@0 176
michael@0 177 yield tester.save(secondData);
michael@0 178 do_check_true(firstCallback_happened);
michael@0 179 do_check_eq(secondData, tester.writtenData);
michael@0 180 do_check_eq(1, tester.saver.totalSaves);
michael@0 181 });
michael@0 182
michael@0 183 // Two saves called with a delay in between, both with callbacks
michael@0 184 // Make sure we save the second version of the data
michael@0 185 add_task(function test_two_saves_delay() {
michael@0 186 let timerPromise = setPromiseMockTimer();
michael@0 187 let tester = DeferredSaveTester();
michael@0 188 let firstCallback_happened = false;
michael@0 189 let delayDone = false;
michael@0 190
michael@0 191 let firstData = "First data to save with delay";
michael@0 192 let secondData = "Modified data to save with delay";
michael@0 193
michael@0 194 tester.save(firstData).then(count => {
michael@0 195 do_check_false(firstCallback_happened);
michael@0 196 do_check_true(delayDone);
michael@0 197 do_check_eq(secondData, tester.writtenData);
michael@0 198 firstCallback_happened = true;
michael@0 199 }, do_report_unexpected_exception);
michael@0 200
michael@0 201 // Wait a short time to let async events possibly spawned by the
michael@0 202 // first tester.save() to run
michael@0 203 yield delay(2);
michael@0 204 delayDone = true;
michael@0 205 // request to save modified data
michael@0 206 let saving = tester.save(secondData);
michael@0 207 // Yield to wait for client code to set the timer
michael@0 208 let activeTimer = yield timerPromise;
michael@0 209 // and then trigger it
michael@0 210 activeTimer.callback();
michael@0 211 // now wait for the DeferredSave to finish saving
michael@0 212 yield saving;
michael@0 213 do_check_true(firstCallback_happened);
michael@0 214 do_check_eq(secondData, tester.writtenData);
michael@0 215 do_check_eq(1, tester.saver.totalSaves);
michael@0 216 do_check_eq(0, tester.saver.overlappedSaves);
michael@0 217 });
michael@0 218
michael@0 219 // Test case where OS.File immediately reports an error when the write begins
michael@0 220 // Also check that the "error" getter correctly returns the error
michael@0 221 // Then do a write that succeeds, and make sure the error is cleared
michael@0 222 add_task(function test_error_immediate() {
michael@0 223 let tester = DeferredSaveTester();
michael@0 224 let testError = new Error("Forced failure");
michael@0 225 function writeFail(aTester) {
michael@0 226 aTester.waDeferred.reject(testError);
michael@0 227 }
michael@0 228
michael@0 229 setQuickMockTimer();
michael@0 230 yield tester.save("test_error_immediate", writeFail).then(
michael@0 231 count => do_throw("Did not get expected error"),
michael@0 232 error => do_check_eq(testError.message, error.message)
michael@0 233 );
michael@0 234 do_check_eq(testError, tester.lastError);
michael@0 235
michael@0 236 // This write should succeed and clear the error
michael@0 237 yield tester.save("test_error_immediate succeeds");
michael@0 238 do_check_eq(null, tester.lastError);
michael@0 239 // The failed save attempt counts in our total
michael@0 240 do_check_eq(2, tester.saver.totalSaves);
michael@0 241 });
michael@0 242
michael@0 243 // Save one set of changes, then while the write is in progress, modify the
michael@0 244 // data two more times. Test that we re-write the dirty data exactly once
michael@0 245 // after the first write succeeds
michael@0 246 add_task(function dirty_while_writing() {
michael@0 247 let tester = DeferredSaveTester();
michael@0 248 let firstData = "First data";
michael@0 249 let secondData = "Second data";
michael@0 250 let thirdData = "Third data";
michael@0 251 let firstCallback_happened = false;
michael@0 252 let secondCallback_happened = false;
michael@0 253 let writeStarted = Promise.defer();
michael@0 254
michael@0 255 function writeCallback(aTester) {
michael@0 256 writeStarted.resolve(aTester.waDeferred);
michael@0 257 }
michael@0 258
michael@0 259 setQuickMockTimer();
michael@0 260 do_print("First save");
michael@0 261 tester.save(firstData, writeCallback).then(
michael@0 262 count => {
michael@0 263 do_check_false(firstCallback_happened);
michael@0 264 do_check_false(secondCallback_happened);
michael@0 265 do_check_eq(tester.writtenData, firstData);
michael@0 266 firstCallback_happened = true;
michael@0 267 }, do_report_unexpected_exception);
michael@0 268
michael@0 269 do_print("waiting for writer");
michael@0 270 let writer = yield writeStarted.promise;
michael@0 271 do_print("Write started");
michael@0 272
michael@0 273 // Delay a bit, modify the data and call saveChanges, delay a bit more,
michael@0 274 // modify the data and call saveChanges again, another delay,
michael@0 275 // then complete the in-progress write
michael@0 276 yield delay(1);
michael@0 277
michael@0 278 tester.save(secondData).then(
michael@0 279 count => {
michael@0 280 do_check_true(firstCallback_happened);
michael@0 281 do_check_false(secondCallback_happened);
michael@0 282 do_check_eq(tester.writtenData, thirdData);
michael@0 283 secondCallback_happened = true;
michael@0 284 }, do_report_unexpected_exception);
michael@0 285
michael@0 286 // wait and then do the third change
michael@0 287 yield delay(1);
michael@0 288 let thirdWrite = tester.save(thirdData);
michael@0 289
michael@0 290 // wait a bit more and then finally finish the first write
michael@0 291 yield delay(1);
michael@0 292 writer.resolve(firstData.length);
michael@0 293
michael@0 294 // Now let everything else finish
michael@0 295 yield thirdWrite;
michael@0 296 do_check_true(firstCallback_happened);
michael@0 297 do_check_true(secondCallback_happened);
michael@0 298 do_check_eq(tester.writtenData, thirdData);
michael@0 299 do_check_eq(2, tester.saver.totalSaves);
michael@0 300 do_check_eq(1, tester.saver.overlappedSaves);
michael@0 301 });
michael@0 302
michael@0 303 // A write callback for the OS.File.writeAtomic mock that rejects write attempts
michael@0 304 function disabled_write_callback(aTester) {
michael@0 305 do_throw("Should not have written during clean flush");
michael@0 306 deferred.reject(new Error("Write during supposedly clean flush"));
michael@0 307 }
michael@0 308
michael@0 309 // special write callback that disables itself to make sure
michael@0 310 // we don't try to write twice
michael@0 311 function write_then_disable(aTester) {
michael@0 312 do_print("write_then_disable");
michael@0 313 let length = aTester.writtenData.length;
michael@0 314 aTester.writeHandler = disabled_write_callback;
michael@0 315 do_execute_soon(() => aTester.waDeferred.resolve(length));
michael@0 316 }
michael@0 317
michael@0 318 // Flush tests. First, do an ordinary clean save and then call flush;
michael@0 319 // there should not be another save
michael@0 320 add_task(function flush_after_save() {
michael@0 321 setQuickMockTimer();
michael@0 322 let tester = DeferredSaveTester();
michael@0 323 let dataToSave = "Flush after save";
michael@0 324
michael@0 325 yield tester.save(dataToSave);
michael@0 326 yield tester.flush(disabled_write_callback);
michael@0 327 do_check_eq(1, tester.saver.totalSaves);
michael@0 328 });
michael@0 329
michael@0 330 // Flush while a write is in progress, but the in-memory data is clean
michael@0 331 add_task(function flush_during_write() {
michael@0 332 let tester = DeferredSaveTester();
michael@0 333 let dataToSave = "Flush during write";
michael@0 334 let firstCallback_happened = false;
michael@0 335 let writeStarted = Promise.defer();
michael@0 336
michael@0 337 function writeCallback(aTester) {
michael@0 338 writeStarted.resolve(aTester.waDeferred);
michael@0 339 }
michael@0 340
michael@0 341 setQuickMockTimer();
michael@0 342 tester.save(dataToSave, writeCallback).then(
michael@0 343 count => {
michael@0 344 do_check_false(firstCallback_happened);
michael@0 345 firstCallback_happened = true;
michael@0 346 }, do_report_unexpected_exception);
michael@0 347
michael@0 348 let writer = yield writeStarted.promise;
michael@0 349
michael@0 350 // call flush with the write callback disabled, delay a bit more, complete in-progress write
michael@0 351 let flushing = tester.flush(disabled_write_callback);
michael@0 352 yield delay(2);
michael@0 353 writer.resolve(dataToSave.length);
michael@0 354
michael@0 355 // now wait for the flush to finish
michael@0 356 yield flushing;
michael@0 357 do_check_true(firstCallback_happened);
michael@0 358 do_check_eq(1, tester.saver.totalSaves);
michael@0 359 });
michael@0 360
michael@0 361 // Flush while dirty but write not in progress
michael@0 362 // The data written should be the value at the time
michael@0 363 // flush() is called, even if it is changed later
michael@0 364 add_task(function flush_while_dirty() {
michael@0 365 let timerPromise = setPromiseMockTimer();
michael@0 366 let tester = DeferredSaveTester();
michael@0 367 let firstData = "Flush while dirty, valid data";
michael@0 368 let firstCallback_happened = false;
michael@0 369
michael@0 370 tester.save(firstData, write_then_disable).then(
michael@0 371 count => {
michael@0 372 do_check_false(firstCallback_happened);
michael@0 373 firstCallback_happened = true;
michael@0 374 do_check_eq(tester.writtenData, firstData);
michael@0 375 }, do_report_unexpected_exception);
michael@0 376
michael@0 377 // Wait for the timer to be set, but don't trigger it so the write won't start
michael@0 378 let activeTimer = yield timerPromise;
michael@0 379
michael@0 380 let flushing = tester.flush();
michael@0 381
michael@0 382 // Make sure the timer was cancelled
michael@0 383 do_check_true(activeTimer.isCancelled);
michael@0 384
michael@0 385 // Also make sure that data changed after the flush call
michael@0 386 // (even without a saveChanges() call) doesn't get written
michael@0 387 tester.dataToSave = "Flush while dirty, invalid data";
michael@0 388
michael@0 389 yield flushing;
michael@0 390 do_check_true(firstCallback_happened);
michael@0 391 do_check_eq(tester.writtenData, firstData);
michael@0 392 do_check_eq(1, tester.saver.totalSaves);
michael@0 393 });
michael@0 394
michael@0 395 // And the grand finale - modify the data, start writing,
michael@0 396 // modify the data again so we're in progress and dirty,
michael@0 397 // then flush, then modify the data again
michael@0 398 // Data for the second write should be taken at the time
michael@0 399 // flush() is called, even if it is modified later
michael@0 400 add_task(function flush_writing_dirty() {
michael@0 401 let timerPromise = setPromiseMockTimer();
michael@0 402 let tester = DeferredSaveTester();
michael@0 403 let firstData = "Flush first pass data";
michael@0 404 let secondData = "Flush second pass data";
michael@0 405 let firstCallback_happened = false;
michael@0 406 let secondCallback_happened = false;
michael@0 407 let writeStarted = Promise.defer();
michael@0 408
michael@0 409 function writeCallback(aTester) {
michael@0 410 writeStarted.resolve(aTester.waDeferred);
michael@0 411 }
michael@0 412
michael@0 413 tester.save(firstData, writeCallback).then(
michael@0 414 count => {
michael@0 415 do_check_false(firstCallback_happened);
michael@0 416 do_check_eq(tester.writtenData, firstData);
michael@0 417 firstCallback_happened = true;
michael@0 418 }, do_report_unexpected_exception);
michael@0 419
michael@0 420 // Trigger the timer callback as soon as the DeferredSave sets it
michael@0 421 let activeTimer = yield timerPromise;
michael@0 422 activeTimer.callback();
michael@0 423 let writer = yield writeStarted.promise;
michael@0 424 // the first write has started
michael@0 425
michael@0 426 // dirty the data and request another save
michael@0 427 // after the second save completes, there should not be another write
michael@0 428 tester.save(secondData, write_then_disable).then(
michael@0 429 count => {
michael@0 430 do_check_true(firstCallback_happened);
michael@0 431 do_check_false(secondCallback_happened);
michael@0 432 do_check_eq(tester.writtenData, secondData);
michael@0 433 secondCallback_happened = true;
michael@0 434 }, do_report_unexpected_exception);
michael@0 435
michael@0 436 let flushing = tester.flush(write_then_disable);
michael@0 437 // Flush should have cancelled our timer
michael@0 438 do_check_true(activeTimer.isCancelled);
michael@0 439 tester.dataToSave = "Flush, invalid data: changed late";
michael@0 440 // complete the first write
michael@0 441 writer.resolve(firstData.length);
michael@0 442 // now wait for the second write / flush to complete
michael@0 443 yield flushing;
michael@0 444 do_check_true(firstCallback_happened);
michael@0 445 do_check_true(secondCallback_happened);
michael@0 446 do_check_eq(tester.writtenData, secondData);
michael@0 447 do_check_eq(2, tester.saver.totalSaves);
michael@0 448 do_check_eq(1, tester.saver.overlappedSaves);
michael@0 449 });
michael@0 450
michael@0 451 // A data provider callback that throws an error the first
michael@0 452 // time it is called, and a different error the second time
michael@0 453 // so that tests can (a) make sure the promise is rejected
michael@0 454 // with the error and (b) make sure the provider is only
michael@0 455 // called once in case of error
michael@0 456 const expectedDataError = "Failed to serialize data";
michael@0 457 let badDataError = null;
michael@0 458 function badDataProvider() {
michael@0 459 let err = new Error(badDataError);
michael@0 460 badDataError = "badDataProvider called twice";
michael@0 461 throw err;
michael@0 462 }
michael@0 463
michael@0 464 // Handle cases where data provider throws
michael@0 465 // First, throws during a normal save
michael@0 466 add_task(function data_throw() {
michael@0 467 setQuickMockTimer();
michael@0 468 badDataError = expectedDataError;
michael@0 469 let tester = DeferredSaveTester(badDataProvider);
michael@0 470 yield tester.save("data_throw").then(
michael@0 471 count => do_throw("Expected serialization failure"),
michael@0 472 error => do_check_eq(error.message, expectedDataError));
michael@0 473 });
michael@0 474
michael@0 475 // Now, throws during flush
michael@0 476 add_task(function data_throw_during_flush() {
michael@0 477 badDataError = expectedDataError;
michael@0 478 let tester = DeferredSaveTester(badDataProvider);
michael@0 479 let firstCallback_happened = false;
michael@0 480
michael@0 481 setPromiseMockTimer();
michael@0 482 // Write callback should never be called
michael@0 483 tester.save("data_throw_during_flush", disabled_write_callback).then(
michael@0 484 count => do_throw("Expected serialization failure"),
michael@0 485 error => {
michael@0 486 do_check_false(firstCallback_happened);
michael@0 487 do_check_eq(error.message, expectedDataError);
michael@0 488 firstCallback_happened = true;
michael@0 489 });
michael@0 490
michael@0 491 // flush() will cancel the timer
michael@0 492 yield tester.flush(disabled_write_callback).then(
michael@0 493 count => do_throw("Expected serialization failure"),
michael@0 494 error => do_check_eq(error.message, expectedDataError)
michael@0 495 );
michael@0 496
michael@0 497 do_check_true(firstCallback_happened);
michael@0 498 });
michael@0 499
michael@0 500 // Try to reproduce race condition. The observed sequence of events:
michael@0 501 // saveChanges
michael@0 502 // start writing
michael@0 503 // saveChanges
michael@0 504 // finish writing (need to restart delayed timer)
michael@0 505 // saveChanges
michael@0 506 // flush
michael@0 507 // write starts
michael@0 508 // actually restart timer for delayed write
michael@0 509 // write completes
michael@0 510 // delayed timer goes off, throws error because DeferredSave has been torn down
michael@0 511 add_task(function delay_flush_race() {
michael@0 512 let timerPromise = setPromiseMockTimer();
michael@0 513 let tester = DeferredSaveTester();
michael@0 514 let firstData = "First save";
michael@0 515 let secondData = "Second save";
michael@0 516 let thirdData = "Third save";
michael@0 517 let writeStarted = Promise.defer();
michael@0 518
michael@0 519 function writeCallback(aTester) {
michael@0 520 writeStarted.resolve(aTester.waDeferred);
michael@0 521 }
michael@0 522
michael@0 523 // This promise won't resolve until after writeStarted
michael@0 524 let firstSave = tester.save(firstData, writeCallback);
michael@0 525 (yield timerPromise).callback();
michael@0 526
michael@0 527 let writer = yield writeStarted.promise;
michael@0 528 // the first write has started
michael@0 529
michael@0 530 // dirty the data and request another save
michael@0 531 let secondSave = tester.save(secondData);
michael@0 532
michael@0 533 // complete the first write
michael@0 534 writer.resolve(firstData.length);
michael@0 535 yield firstSave;
michael@0 536 do_check_eq(tester.writtenData, firstData);
michael@0 537
michael@0 538 tester.save(thirdData);
michael@0 539 let flushing = tester.flush();
michael@0 540
michael@0 541 yield secondSave;
michael@0 542 do_check_eq(tester.writtenData, thirdData);
michael@0 543
michael@0 544 yield flushing;
michael@0 545 do_check_eq(tester.writtenData, thirdData);
michael@0 546
michael@0 547 // Our DeferredSave should not have a _timer here; if it
michael@0 548 // does, the bug caused a reschedule
michael@0 549 do_check_eq(null, tester.saver._timer);
michael@0 550 });

mercurial