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 file tests the DeferredTask.jsm module. michael@0: */ michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: /// Globals michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", michael@0: "resource://gre/modules/DeferredTask.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Promise", michael@0: "resource://gre/modules/Promise.jsm"); michael@0: michael@0: /** michael@0: * Due to the nature of this module, most of the tests are time-dependent. All michael@0: * the timeouts are designed to occur at multiples of this granularity value, michael@0: * in milliseconds, that should be high enough to prevent intermittent failures, michael@0: * but low enough to prevent an excessive overall test execution time. michael@0: */ michael@0: const T = 100; michael@0: michael@0: /** michael@0: * Waits for the specified timeout before resolving the returned promise. michael@0: */ michael@0: function promiseTimeout(aTimeoutMs) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: do_timeout(aTimeoutMs, deferred.resolve); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function run_test() michael@0: { michael@0: run_next_test(); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Tests michael@0: michael@0: /** michael@0: * Creates a simple DeferredTask and executes it once. michael@0: */ michael@0: add_test(function test_arm_simple() michael@0: { michael@0: new DeferredTask(run_next_test, 10).arm(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that the delay set for the task is respected. michael@0: */ michael@0: add_test(function test_arm_delay_respected() michael@0: { michael@0: let executed1 = false; michael@0: let executed2 = false; michael@0: michael@0: new DeferredTask(function () { michael@0: executed1 = true; michael@0: do_check_false(executed2); michael@0: }, 1*T).arm(); michael@0: michael@0: new DeferredTask(function () { michael@0: executed2 = true; michael@0: do_check_true(executed1); michael@0: run_next_test(); michael@0: }, 2*T).arm(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that calling "arm" again does not introduce further delay. michael@0: */ michael@0: add_test(function test_arm_delay_notrestarted() michael@0: { michael@0: let executed = false; michael@0: michael@0: // Create a task that will run later. michael@0: let deferredTask = new DeferredTask(() => { executed = true; }, 4*T); michael@0: deferredTask.arm(); michael@0: michael@0: // Before the task starts, call "arm" again. michael@0: do_timeout(2*T, () => deferredTask.arm()); michael@0: michael@0: // The "arm" call should not have introduced further delays. michael@0: do_timeout(5*T, function () { michael@0: do_check_true(executed); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that a task runs only once when armed multiple times synchronously. michael@0: */ michael@0: add_test(function test_arm_coalesced() michael@0: { michael@0: let executed = false; michael@0: michael@0: let deferredTask = new DeferredTask(function () { michael@0: do_check_false(executed); michael@0: executed = true; michael@0: run_next_test(); michael@0: }, 50); michael@0: michael@0: deferredTask.arm(); michael@0: deferredTask.arm(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that a task runs only once when armed multiple times synchronously, michael@0: * even when it has been created with a delay of zero milliseconds. michael@0: */ michael@0: add_test(function test_arm_coalesced_nodelay() michael@0: { michael@0: let executed = false; michael@0: michael@0: let deferredTask = new DeferredTask(function () { michael@0: do_check_false(executed); michael@0: executed = true; michael@0: run_next_test(); michael@0: }, 0); michael@0: michael@0: deferredTask.arm(); michael@0: deferredTask.arm(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that a task can be armed again while running. michael@0: */ michael@0: add_test(function test_arm_recursive() michael@0: { michael@0: let executed = false; michael@0: michael@0: let deferredTask = new DeferredTask(function () { michael@0: if (!executed) { michael@0: executed = true; michael@0: deferredTask.arm(); michael@0: } else { michael@0: run_next_test(); michael@0: } michael@0: }, 50); michael@0: michael@0: deferredTask.arm(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that calling "arm" while an asynchronous task is running waits until michael@0: * the task is finished before restarting the delay. michael@0: */ michael@0: add_test(function test_arm_async() michael@0: { michael@0: let finishedExecution = false; michael@0: let finishedExecutionAgain = false; michael@0: michael@0: // Create a task that will run later. michael@0: let deferredTask = new DeferredTask(function () { michael@0: yield promiseTimeout(4*T); michael@0: if (!finishedExecution) { michael@0: finishedExecution = true; michael@0: } else if (!finishedExecutionAgain) { michael@0: finishedExecutionAgain = true; michael@0: } michael@0: }, 2*T); michael@0: deferredTask.arm(); michael@0: michael@0: // While the task is running, call "arm" again. This will result in a wait michael@0: // of 2*T until the task finishes, then another 2*T for the normal task delay michael@0: // specified on construction. michael@0: do_timeout(4*T, function () { michael@0: do_check_true(deferredTask.isRunning); michael@0: do_check_false(finishedExecution); michael@0: deferredTask.arm(); michael@0: }); michael@0: michael@0: // This will fail in case the task was started without waiting 2*T after it michael@0: // has finished. michael@0: do_timeout(7*T, function () { michael@0: do_check_false(deferredTask.isRunning); michael@0: do_check_true(finishedExecution); michael@0: }); michael@0: michael@0: // This is in the middle of the second execution. michael@0: do_timeout(10*T, function () { michael@0: do_check_true(deferredTask.isRunning); michael@0: do_check_false(finishedExecutionAgain); michael@0: }); michael@0: michael@0: // Wait enough time to verify that the task was executed as expected. michael@0: do_timeout(13*T, function () { michael@0: do_check_false(deferredTask.isRunning); michael@0: do_check_true(finishedExecutionAgain); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that an armed task can be disarmed. michael@0: */ michael@0: add_test(function test_disarm() michael@0: { michael@0: // Create a task that will run later. michael@0: let deferredTask = new DeferredTask(function () { michael@0: do_throw("This task should not run."); michael@0: }, 2*T); michael@0: deferredTask.arm(); michael@0: michael@0: // Disable execution later, but before the task starts. michael@0: do_timeout(1*T, () => deferredTask.disarm()); michael@0: michael@0: // Wait enough time to verify that the task did not run. michael@0: do_timeout(3*T, run_next_test); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that calling "disarm" allows the delay to be restarted. michael@0: */ michael@0: add_test(function test_disarm_delay_restarted() michael@0: { michael@0: let executed = false; michael@0: michael@0: let deferredTask = new DeferredTask(() => { executed = true; }, 4*T); michael@0: deferredTask.arm(); michael@0: michael@0: do_timeout(2*T, function () { michael@0: deferredTask.disarm(); michael@0: deferredTask.arm(); michael@0: }); michael@0: michael@0: do_timeout(5*T, function () { michael@0: do_check_false(executed); michael@0: }); michael@0: michael@0: do_timeout(7*T, function () { michael@0: do_check_true(executed); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that calling "disarm" while an asynchronous task is running does not michael@0: * prevent the task to finish. michael@0: */ michael@0: add_test(function test_disarm_async() michael@0: { michael@0: let finishedExecution = false; michael@0: michael@0: let deferredTask = new DeferredTask(function () { michael@0: deferredTask.arm(); michael@0: yield promiseTimeout(2*T); michael@0: finishedExecution = true; michael@0: }, 1*T); michael@0: deferredTask.arm(); michael@0: michael@0: do_timeout(2*T, function () { michael@0: do_check_true(deferredTask.isRunning); michael@0: do_check_true(deferredTask.isArmed); michael@0: do_check_false(finishedExecution); michael@0: deferredTask.disarm(); michael@0: }); michael@0: michael@0: do_timeout(4*T, function () { michael@0: do_check_false(deferredTask.isRunning); michael@0: do_check_false(deferredTask.isArmed); michael@0: do_check_true(finishedExecution); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that calling "arm" immediately followed by "disarm" while an michael@0: * asynchronous task is running does not cause it to run again. michael@0: */ michael@0: add_test(function test_disarm_immediate_async() michael@0: { michael@0: let executed = false; michael@0: michael@0: let deferredTask = new DeferredTask(function () { michael@0: do_check_false(executed); michael@0: executed = true; michael@0: yield promiseTimeout(2*T); michael@0: }, 1*T); michael@0: deferredTask.arm(); michael@0: michael@0: do_timeout(2*T, function () { michael@0: do_check_true(deferredTask.isRunning); michael@0: do_check_false(deferredTask.isArmed); michael@0: deferredTask.arm(); michael@0: deferredTask.disarm(); michael@0: }); michael@0: michael@0: do_timeout(4*T, function () { michael@0: do_check_true(executed); michael@0: do_check_false(deferredTask.isRunning); michael@0: do_check_false(deferredTask.isArmed); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: /** michael@0: * Checks the isArmed and isRunning properties with a synchronous task. michael@0: */ michael@0: add_test(function test_isArmed_isRunning() michael@0: { michael@0: let deferredTask = new DeferredTask(function () { michael@0: do_check_true(deferredTask.isRunning); michael@0: do_check_false(deferredTask.isArmed); michael@0: deferredTask.arm(); michael@0: do_check_true(deferredTask.isArmed); michael@0: deferredTask.disarm(); michael@0: do_check_false(deferredTask.isArmed); michael@0: run_next_test(); michael@0: }, 50); michael@0: michael@0: do_check_false(deferredTask.isArmed); michael@0: deferredTask.arm(); michael@0: do_check_true(deferredTask.isArmed); michael@0: do_check_false(deferredTask.isRunning); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that the "finalize" method executes a synchronous task. michael@0: */ michael@0: add_test(function test_finalize() michael@0: { michael@0: let executed = false; michael@0: let timePassed = false; michael@0: michael@0: let deferredTask = new DeferredTask(function () { michael@0: do_check_false(timePassed); michael@0: executed = true; michael@0: }, 2*T); michael@0: deferredTask.arm(); michael@0: michael@0: do_timeout(1*T, () => { timePassed = true; }); michael@0: michael@0: // This should trigger the immediate execution of the task. michael@0: deferredTask.finalize().then(function () { michael@0: do_check_true(executed); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that the "finalize" method executes the task again from start to michael@0: * finish in case it is already running. michael@0: */ michael@0: add_test(function test_finalize_executes_entirely() michael@0: { michael@0: let executed = false; michael@0: let executedAgain = false; michael@0: let timePassed = false; michael@0: michael@0: let deferredTask = new DeferredTask(function () { michael@0: // The first time, we arm the timer again and set up the finalization. michael@0: if (!executed) { michael@0: deferredTask.arm(); michael@0: do_check_true(deferredTask.isArmed); michael@0: do_check_true(deferredTask.isRunning); michael@0: michael@0: deferredTask.finalize().then(function () { michael@0: // When we reach this point, the task must be finished. michael@0: do_check_true(executedAgain); michael@0: do_check_false(timePassed); michael@0: do_check_false(deferredTask.isArmed); michael@0: do_check_false(deferredTask.isRunning); michael@0: run_next_test(); michael@0: }); michael@0: michael@0: // The second execution triggered by the finalization waits 1*T for the michael@0: // current task to finish (see the timeout below), but then it must not michael@0: // wait for the 2*T specified on construction as normal task delay. The michael@0: // second execution will finish after the timeout below has passed again, michael@0: // for a total of 2*T of wait time. michael@0: do_timeout(3*T, () => { timePassed = true; }); michael@0: } michael@0: michael@0: yield promiseTimeout(1*T); michael@0: michael@0: // Just before finishing, indicate if we completed the second execution. michael@0: if (executed) { michael@0: do_check_true(deferredTask.isRunning); michael@0: executedAgain = true; michael@0: } else { michael@0: executed = true; michael@0: } michael@0: }, 2*T); michael@0: michael@0: deferredTask.arm(); michael@0: });