// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. 'use strict'; const assert = require('assert'); const fail = assert.fail; const sinon = require('sinon'); const testutil = require('./testutil'); const {TimeoutError} = require('../../lib/error'); const promise = require('../../lib/promise'); const {enablePromiseManager} = require('../../lib/test/promise'); const NativePromise = Promise; // Aliases for readability. const StubError = testutil.StubError; const assertIsStubError = testutil.assertIsStubError; const callbackPair = testutil.callbackPair; const throwStubError = testutil.throwStubError; describe('promise control flow', function() { enablePromiseManager(() => { let flow, flowHistory, messages, uncaughtExceptions; beforeEach(function setUp() { promise.LONG_STACK_TRACES = false; flow = new promise.ControlFlow(); promise.setDefaultFlow(flow); messages = []; flowHistory = []; uncaughtExceptions = []; flow.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, onUncaughtException); }); afterEach(function tearDown() { flow.removeAllListeners( promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); assert.deepEqual([], uncaughtExceptions, 'There were uncaught exceptions'); flow.reset(); promise.LONG_STACK_TRACES = false; }); function onUncaughtException(e) { uncaughtExceptions.push(e); } function defer() { let d = {}; let promise = new Promise((resolve, reject) => { Object.assign(d, {resolve, reject}); }); d.promise = promise; return d; } function waitForAbort(opt_flow) { var theFlow = opt_flow || flow; theFlow.removeAllListeners( promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); return new NativePromise(function(fulfill, reject) { theFlow.once(promise.ControlFlow.EventType.IDLE, function() { reject(Error('expected flow to report an unhandled error')); }); theFlow.once( promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, fulfill); }); } function waitForIdle(opt_flow) { var theFlow = opt_flow || flow; return new NativePromise(function(fulfill, reject) { theFlow.once(promise.ControlFlow.EventType.IDLE, fulfill); theFlow.once( promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, reject); }); } function timeout(ms) { return new NativePromise(function(fulfill) { setTimeout(fulfill, ms); }); } function schedule(msg, opt_return) { return scheduleAction(msg, function() { return opt_return; }); } /** * @param {string} value The value to push. * @param {promise.Promise=} opt_taskPromise Promise to return from * the task. * @return {!promise.Promise} The result. */ function schedulePush(value, opt_taskPromise) { return scheduleAction(value, function() { messages.push(value); return opt_taskPromise; }); } /** * @param {string} msg Debug message. * @param {!Function} actionFn The function. * @return {!promise.Promise} The function result. */ function scheduleAction(msg, actionFn) { return promise.controlFlow().execute(function() { flowHistory.push(msg); return actionFn(); }, msg); } /** * @param {!Function} condition The condition function. * @param {number=} opt_timeout The timeout. * @param {string=} opt_message Optional message. * @return {!promise.Promise} The wait result. */ function scheduleWait(condition, opt_timeout, opt_message) { var msg = opt_message || ''; // It's not possible to hook into when the wait itself is scheduled, so // we record each iteration of the wait loop. var count = 0; return promise.controlFlow().wait(function() { flowHistory.push((count++) + ': ' + msg); return condition(); }, opt_timeout, msg); } function asyncRun(fn, opt_self) { NativePromise.resolve().then(() => fn.call(opt_self)); } function assertFlowHistory(var_args) { var expected = Array.prototype.slice.call(arguments, 0); assert.deepEqual(expected, flowHistory); } function assertMessages(var_args) { var expected = Array.prototype.slice.call(arguments, 0); assert.deepEqual(expected, messages); } function assertingMessages(var_args) { var args = Array.prototype.slice.call(arguments, 0); return () => assertMessages.apply(null, args); } function assertFlowIs(flow) { assert.equal(flow, promise.controlFlow()); } describe('testScheduling', function() { it('aSimpleFunction', function() { schedule('go'); return waitForIdle().then(function() { assertFlowHistory('go'); }); }); it('aSimpleFunctionWithANonPromiseReturnValue', function() { schedule('go', 123).then(function(value) { assert.equal(123, value); }); return waitForIdle().then(function() { assertFlowHistory('go'); }); }); it('aSimpleSequence', function() { schedule('a'); schedule('b'); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('invokesCallbacksWhenTaskIsDone', function() { var d = new promise.Deferred(); var called = false; var done = schedule('a', d.promise).then(function(value) { called = true; assert.equal(123, value); }); return timeout(5).then(function() { assert.ok(!called); d.fulfill(123); return done; }). then(function() { assertFlowHistory('a'); }); }); it('blocksUntilPromiseReturnedByTaskIsResolved', function() { var done = promise.defer(); schedulePush('a', done.promise); schedulePush('b'); setTimeout(function() { done.fulfill(); messages.push('c'); }, 25); return waitForIdle().then(assertingMessages('a', 'c', 'b')); }); it('waitsForReturnedPromisesToResolve', function() { var d1 = new promise.Deferred(); var d2 = new promise.Deferred(); var callback = sinon.spy(); schedule('a', d1.promise).then(callback); return timeout(5).then(function() { assert(!callback.called); d1.fulfill(d2.promise); return timeout(5); }).then(function() { assert(!callback.called); d2.fulfill('fluffy bunny'); return waitForIdle(); }).then(function() { assert(callback.called); assert.equal('fluffy bunny', callback.getCall(0).args[0]); assertFlowHistory('a'); }); }); it('executesTasksInAFutureTurnAfterTheyAreScheduled', function() { var count = 0; function incr() { count++; } scheduleAction('', incr); assert.equal(0, count); return waitForIdle().then(function() { assert.equal(1, count); }); }); it('executesOneTaskPerTurnOfTheEventLoop', function() { var order = []; function go() { order.push(order.length / 2); asyncRun(function() { order.push('-'); }); } scheduleAction('', go); scheduleAction('', go); return waitForIdle().then(function() { assert.deepEqual([0, '-', 1, '-'], order); }) }); it('firstScheduledTaskIsWithinACallback', function() { promise.fulfilled().then(function() { schedule('a'); schedule('b'); schedule('c'); }).then(function() { assertFlowHistory('a', 'b', 'c'); }); return waitForIdle(); }); it('newTasksAddedWhileWaitingOnTaskReturnedPromise1', function() { scheduleAction('a', function() { var d = promise.defer(); setTimeout(function() { schedule('c'); d.fulfill(); }, 10); return d.promise; }); schedule('b'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('newTasksAddedWhileWaitingOnTaskReturnedPromise2', function() { scheduleAction('a', function() { var d = promise.defer(); setTimeout(function() { schedule('c'); asyncRun(d.fulfill); }, 10); return d.promise; }); schedule('b'); return waitForIdle().then(function() { assertFlowHistory('a', 'c', 'b'); }); }); }); describe('testFraming', function() { it('callbacksRunInANewFrame', function() { schedule('a').then(function() { schedule('c'); }); schedule('b'); return waitForIdle().then(function() { assertFlowHistory('a', 'c', 'b'); }); }); it('lotsOfNesting', function() { schedule('a').then(function() { schedule('c').then(function() { schedule('e').then(function() { schedule('g'); }); schedule('f'); }); schedule('d'); }); schedule('b'); return waitForIdle().then(function() { assertFlowHistory('a', 'c', 'e', 'g', 'f', 'd', 'b'); }); }); it('callbackReturnsPromiseThatDependsOnATask_1', function() { schedule('a').then(function() { schedule('b'); return promise.delayed(5).then(function() { return schedule('c'); }); }); schedule('d'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd'); }); }); it('callbackReturnsPromiseThatDependsOnATask_2', function() { schedule('a').then(function() { schedule('b'); return promise.delayed(5). then(function() { return promise.delayed(5) }). then(function() { return promise.delayed(5) }). then(function() { return promise.delayed(5) }). then(function() { return schedule('c'); }); }); schedule('d'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd'); }); }); it('eachCallbackWaitsForAllScheduledTasksToComplete', function() { schedule('a'). then(function() { schedule('b'); schedule('c'); }). then(function() { schedule('d'); }); schedule('e'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e'); }); }); it('eachCallbackWaitsForReturnTasksToComplete', function() { schedule('a'). then(function() { schedule('b'); return schedule('c'); }). then(function() { schedule('d'); }); schedule('e'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e'); }); }); it('callbacksOnAResolvedPromiseInsertIntoTheCurrentFlow', function() { promise.fulfilled().then(function() { schedule('b'); }); schedule('a'); return waitForIdle().then(function() { assertFlowHistory('b', 'a'); }); }); it('callbacksInterruptTheFlowWhenPromiseIsResolved', function() { schedule('a').then(function() { schedule('c'); }); schedule('b'); return waitForIdle().then(function() { assertFlowHistory('a', 'c', 'b'); }); }); it('allCallbacksInAFrameAreScheduledWhenPromiseIsResolved', function() { var a = schedule('a'); a.then(function() { schedule('b'); }); schedule('c'); a.then(function() { schedule('d'); }); schedule('e'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e'); }); }); it('tasksScheduledInInActiveFrameDoNotGetPrecedence', function() { var d = promise.fulfilled(); schedule('a'); schedule('b'); d.then(function() { schedule('c'); }); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('tasksScheduledInAFrameGetPrecedence_1', function() { var a = schedule('a'); schedule('b').then(function() { a.then(function() { schedule('c'); schedule('d'); }); var e = schedule('e'); a.then(function() { schedule('f'); e.then(function() { schedule('g'); }); schedule('h'); }); schedule('i'); }); schedule('j'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'); }); }); }); describe('testErrorHandling', function() { it('thrownErrorsArePassedToTaskErrback', function() { scheduleAction('function that throws', throwStubError). then(fail, assertIsStubError); return waitForIdle(); }); it('thrownErrorsPropagateThroughPromiseChain', function() { scheduleAction('function that throws', throwStubError). then(fail). then(fail, assertIsStubError); return waitForIdle(); }); it('catchesErrorsFromFailedTasksInAFrame', function() { schedule('a').then(function() { schedule('b'); scheduleAction('function that throws', throwStubError); }). then(fail, assertIsStubError); return waitForIdle(); }); it('abortsIfOnlyTaskReturnsAnUnhandledRejection', function() { scheduleAction('function that returns rejected promise', function() { return promise.rejected(new StubError); }); return waitForAbort().then(assertIsStubError); }); it('abortsIfThereIsAnUnhandledRejection', function() { promise.rejected(new StubError); schedule('this should not run'); return waitForAbort(). then(assertIsStubError). then(function() { assertFlowHistory(/* none */); }); }); it('abortsSequenceIfATaskFails', function() { schedule('a'); schedule('b'); scheduleAction('c', throwStubError); schedule('d'); // Should never execute. return waitForAbort(). then(assertIsStubError). then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('abortsFromUnhandledFramedTaskFailures_1', function() { schedule('outer task').then(function() { scheduleAction('inner task', throwStubError); }); schedule('this should not run'); return waitForAbort(). then(assertIsStubError). then(function() { assertFlowHistory('outer task', 'inner task'); }); }); it('abortsFromUnhandledFramedTaskFailures_2', function() { schedule('a').then(function() { schedule('b').then(function() { scheduleAction('c', throwStubError); // This should not execute. schedule('d'); }); }); return waitForAbort(). then(assertIsStubError). then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject', function() { var callback = sinon.spy(); scheduleAction('', function() { var obj = {'foo': promise.rejected(new StubError)}; return promise.fullyResolved(obj).then(callback); }); return waitForAbort(). then(assertIsStubError). then(() => assert(!callback.called)); }); it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject_withCallback', function() { var callback1 = sinon.spy(); var callback2 = sinon.spy(); scheduleAction('', function() { var obj = {'foo': promise.rejected(new StubError)}; return promise.fullyResolved(obj).then(callback1); }).then(callback2); return waitForAbort(). then(assertIsStubError). then(() => assert(!callback1.called)). then(() => assert(!callback2.called)); }); it('canCatchErrorsFromNestedTasks', function() { var errback = sinon.spy(); schedule('a'). then(function() { return scheduleAction('b', throwStubError); }). catch(errback); return waitForIdle().then(function() { assert(errback.called); assertIsStubError(errback.getCall(0).args[0]); }); }); it('nestedCommandFailuresCanBeCaughtAndSuppressed', function() { var errback = sinon.spy(); schedule('a').then(function() { return schedule('b').then(function() { return schedule('c').then(function() { throw new StubError; }); }); }).catch(errback); schedule('d'); return waitForIdle(). then(function() { assert(errback.called); assertIsStubError(errback.getCall(0).args[0]); assertFlowHistory('a', 'b', 'c', 'd'); }); }); it('aTaskWithAnUnhandledPromiseRejection', function() { schedule('a'); scheduleAction('sub-tasks', function() { promise.rejected(new StubError); }); schedule('should never run'); return waitForAbort(). then(assertIsStubError). then(function() { assertFlowHistory('a', 'sub-tasks'); }); }); it('aTaskThatReutrnsARejectedPromise', function() { schedule('a'); scheduleAction('sub-tasks', function() { return promise.rejected(new StubError); }); schedule('should never run'); return waitForAbort(). then(assertIsStubError). then(function() { assertFlowHistory('a', 'sub-tasks'); }); }); it('discardsSubtasksIfTaskThrows', function() { var pair = callbackPair(null, assertIsStubError); scheduleAction('a', function() { schedule('b'); schedule('c'); throwStubError(); }).then(pair.callback, pair.errback); schedule('d'); return waitForIdle(). then(pair.assertErrback). then(function() { assertFlowHistory('a', 'd'); }); }); it('discardsRemainingSubtasksIfASubtaskFails', function() { var pair = callbackPair(null, assertIsStubError); scheduleAction('a', function() { schedule('b'); scheduleAction('c', throwStubError); schedule('d'); }).then(pair.callback, pair.errback); schedule('e'); return waitForIdle(). then(pair.assertErrback). then(function() { assertFlowHistory('a', 'b', 'c', 'e'); }); }); }); describe('testTryModelingFinally', function() { it('happyPath', function() { /* Model: try { doFoo(); doBar(); } finally { doBaz(); } */ schedulePush('foo'). then(() => schedulePush('bar')). finally(() => schedulePush('baz')); return waitForIdle().then(assertingMessages('foo', 'bar', 'baz')); }); it('firstTryFails', function() { /* Model: try { doFoo(); doBar(); } finally { doBaz(); } */ scheduleAction('doFoo and throw', function() { messages.push('foo'); throw new StubError; }). then(function() { schedulePush('bar'); }). finally(function() { schedulePush('baz'); }); return waitForAbort(). then(assertIsStubError). then(assertingMessages('foo', 'baz')); }); it('secondTryFails', function() { /* Model: try { doFoo(); doBar(); } finally { doBaz(); } */ schedulePush('foo'). then(function() { return scheduleAction('doBar and throw', function() { messages.push('bar'); throw new StubError; }); }). finally(function() { return schedulePush('baz'); }); return waitForAbort(). then(assertIsStubError). then(assertingMessages('foo', 'bar', 'baz')); }); }); describe('testTaskCallbacksInterruptFlow', function() { it('(base case)', function() { schedule('a').then(function() { schedule('b'); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('taskDependsOnImmediatelyFulfilledPromise', function() { scheduleAction('a', function() { return promise.fulfilled(); }).then(function() { schedule('b'); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('taskDependsOnPreviouslyFulfilledPromise', function() { var aPromise = promise.fulfilled(123); scheduleAction('a', function() { return aPromise; }).then(function(value) { assert.equal(123, value); schedule('b'); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('taskDependsOnAsyncPromise', function() { scheduleAction('a', function() { return promise.delayed(25); }).then(function() { schedule('b'); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('promiseChainedToTaskInterruptFlow', function() { schedule('a').then(function() { return promise.fulfilled(); }).then(function() { return promise.fulfilled(); }).then(function() { schedule('b'); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('nestedTaskCallbacksInterruptFlowWhenResolved', function() { schedule('a').then(function() { schedule('b').then(function() { schedule('c'); }); }); schedule('d'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd'); }); }); }); describe('testDelayedNesting', function() { it('1', function() { var a = schedule('a'); schedule('b').then(function() { a.then(function() { schedule('c'); }); schedule('d'); }); schedule('e'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e'); }); }); it('2', function() { var a = schedule('a'); schedule('b').then(function() { a.then(function() { schedule('c'); }); schedule('d'); a.then(function() { schedule('e'); }); }); schedule('f'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); }); }); it('3', function() { var a = schedule('a'); schedule('b').then(function() { a.then(function() { schedule('c'); }); a.then(function() { schedule('d'); }); }); schedule('e'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e'); }); }); it('4', function() { var a = schedule('a'); schedule('b').then(function() { a.then(function() { schedule('c'); }).then(function() { schedule('d'); }); a.then(function() { schedule('e'); }); }); schedule('f'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); }); }); it('5', function() { var a = schedule('a'); schedule('b').then(function() { var c; a.then(function() { c = schedule('c'); }).then(function() { schedule('d'); a.then(function() { schedule('e'); }); c.then(function() { schedule('f'); }); schedule('g'); }); a.then(function() { schedule('h'); }); }); schedule('i'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'); }); }); }); describe('testWaiting', function() { it('onAConditionThatIsAlwaysTrue', function() { scheduleWait(function() { return true;}, 0, 'waiting on true'); return waitForIdle().then(function() { assertFlowHistory('0: waiting on true'); }); }); it('aSimpleCountingCondition', function() { var count = 0; scheduleWait(function() { return ++count == 3; }, 100, 'counting to 3'); return waitForIdle().then(function() { assert.equal(3, count); }); }); it('aConditionThatReturnsAPromise', function() { var d = new promise.Deferred(); var count = 0; scheduleWait(function() { count += 1; return d.promise; }, 0, 'waiting for promise'); return timeout(50).then(function() { assert.equal(1, count); d.fulfill(123); return waitForIdle(); }); }); it('aConditionThatReturnsAPromise_2', function() { var count = 0; scheduleWait(function() { return promise.fulfilled(++count == 3); }, 100, 'waiting for promise'); return waitForIdle().then(function() { assert.equal(3, count); }); }); it('aConditionThatReturnsATaskResult', function() { var count = 0; scheduleWait(function() { return scheduleAction('increment count', function() { return ++count == 3; }); }, 100, 'counting to 3'); schedule('post wait'); return waitForIdle().then(function() { assert.equal(3, count); assertFlowHistory( '0: counting to 3', 'increment count', '1: counting to 3', 'increment count', '2: counting to 3', 'increment count', 'post wait'); }); }); it('conditionContainsASubtask', function() { var count = 0; scheduleWait(function() { schedule('sub task'); return ++count == 3; }, 100, 'counting to 3'); schedule('post wait'); return waitForIdle().then(function() { assert.equal(3, count); assertFlowHistory( '0: counting to 3', 'sub task', '1: counting to 3', 'sub task', '2: counting to 3', 'sub task', 'post wait'); }); }); it('cancelsWaitIfScheduledTaskFails', function() { var pair = callbackPair(null, assertIsStubError); scheduleWait(function() { scheduleAction('boom', throwStubError); schedule('this should not run'); return true; }, 100, 'waiting to go boom').then(pair.callback, pair.errback); schedule('post wait'); return waitForIdle(). then(pair.assertErrback). then(function() { assertFlowHistory( '0: waiting to go boom', 'boom', 'post wait'); }); }); it('failsIfConditionThrows', function() { var callbacks = callbackPair(null, assertIsStubError); scheduleWait(throwStubError, 0, 'goes boom'). then(callbacks.callback, callbacks.errback); schedule('post wait'); return waitForIdle(). then(callbacks.assertErrback). then(function() { assertFlowHistory('0: goes boom', 'post wait'); }); }); it('failsIfConditionReturnsARejectedPromise', function() { var callbacks = callbackPair(null, assertIsStubError); scheduleWait(function() { return promise.rejected(new StubError); }, 0, 'goes boom').then(callbacks.callback, callbacks.errback); schedule('post wait'); return waitForIdle(). then(callbacks.assertErrback). then(function() { assertFlowHistory('0: goes boom', 'post wait'); }); }); it('failsIfConditionHasUnhandledRejection', function() { var callbacks = callbackPair(null, assertIsStubError); scheduleWait(function() { promise.controlFlow().execute(throwStubError); }, 0, 'goes boom').then(callbacks.callback, callbacks.errback); schedule('post wait'); return waitForIdle(). then(callbacks.assertErrback). then(function() { assertFlowHistory('0: goes boom', 'post wait'); }); }); it('failsIfConditionHasAFailedSubtask', function() { var callbacks = callbackPair(null, assertIsStubError); var count = 0; scheduleWait(function() { scheduleAction('maybe throw', function() { if (++count == 2) { throw new StubError; } }); }, 100, 'waiting').then(callbacks.callback, callbacks.errback); schedule('post wait'); return waitForIdle().then(function() { assert.equal(2, count); assertFlowHistory( '0: waiting', 'maybe throw', '1: waiting', 'maybe throw', 'post wait'); }); }); it('pollingLoopWaitsForAllScheduledTasksInCondition', function() { var count = 0; scheduleWait(function() { scheduleAction('increment count', function() { ++count; }); return count >= 3; }, 100, 'counting to 3'); schedule('post wait'); return waitForIdle().then(function() { assert.equal(4, count); assertFlowHistory( '0: counting to 3', 'increment count', '1: counting to 3', 'increment count', '2: counting to 3', 'increment count', '3: counting to 3', 'increment count', 'post wait'); }); }); it('waitsForeverOnAZeroTimeout', function() { var done = false; setTimeout(function() { done = true; }, 150); var waitResult = scheduleWait(function() { return done; }, 0); return timeout(75).then(function() { assert.ok(!done); return timeout(100); }).then(function() { assert.ok(done); return waitResult; }); }); it('waitsForeverIfTimeoutOmitted', function() { var done = false; setTimeout(function() { done = true; }, 150); var waitResult = scheduleWait(function() { return done; }); return timeout(75).then(function() { assert.ok(!done); return timeout(100); }).then(function() { assert.ok(done); return waitResult; }); }); it('timesOut_nonZeroTimeout', function() { var count = 0; scheduleWait(function() { count += 1; var ms = count === 2 ? 65 : 5; return promise.delayed(ms).then(function() { return false; }); }, 60, 'counting to 3'); return waitForAbort().then(function(e) { switch (count) { case 1: assertFlowHistory('0: counting to 3'); break; case 2: assertFlowHistory('0: counting to 3', '1: counting to 3'); break; default: fail('unexpected polling count: ' + count); } assert.ok(e instanceof TimeoutError, 'Unexpected error: ' + e); assert.ok( /^counting to 3\nWait timed out after \d+ms$/.test(e.message)); }); }); it('shouldFailIfConditionReturnsARejectedPromise', function() { scheduleWait(function() { return promise.rejected(new StubError); }, 100, 'returns rejected promise on first pass'); return waitForAbort().then(assertIsStubError); }); it('scheduleWithIntermittentWaits', function() { schedule('a'); scheduleWait(function() { return true; }, 0, 'wait 1'); schedule('b'); scheduleWait(function() { return true; }, 0, 'wait 2'); schedule('c'); scheduleWait(function() { return true; }, 0, 'wait 3'); return waitForIdle().then(function() { assertFlowHistory('a', '0: wait 1', 'b', '0: wait 2', 'c', '0: wait 3'); }); }); it('scheduleWithIntermittentAndNestedWaits', function() { schedule('a'); scheduleWait(function() { return true; }, 0, 'wait 1'). then(function() { schedule('d'); scheduleWait(function() { return true; }, 0, 'wait 2'); schedule('e'); }); schedule('b'); scheduleWait(function() { return true; }, 0, 'wait 3'); schedule('c'); scheduleWait(function() { return true; }, 0, 'wait 4'); return waitForIdle().then(function() { assertFlowHistory( 'a', '0: wait 1', 'd', '0: wait 2', 'e', 'b', '0: wait 3', 'c', '0: wait 4'); }); }); it('requiresConditionToBeAPromiseOrFunction', function() { assert.throws(function() { flow.wait(1234, 0); }); flow.wait(function() { return true;}, 0); flow.wait(promise.fulfilled(), 0); return waitForIdle(); }); it('promiseThatDoesNotResolveBeforeTimeout', function() { var d = promise.defer(); flow.wait(d.promise, 5).then(fail, function(e) { assert.ok(e instanceof TimeoutError, 'Unexpected error: ' + e); assert.ok( /Timed out waiting for promise to resolve after \d+ms/ .test(e.message), 'unexpected error message: ' + e.message); }); return waitForIdle(); }); it('unboundedWaitOnPromiseResolution', function() { var messages = []; var d = promise.defer(); var waitResult = flow.wait(d.promise).then(function(value) { messages.push('b'); assert.equal(1234, value); }); setTimeout(function() { messages.push('a'); }, 5); timeout(10).then(function() { assert.deepEqual(['a'], messages); d.fulfill(1234); return waitResult; }).then(function() { assert.deepEqual(['a', 'b'], messages); }); return waitForIdle(); }); }); describe('testSubtasks', function() { it('(base case)', function() { schedule('a'); scheduleAction('sub-tasks', function() { schedule('c'); schedule('d'); }); schedule('b'); return waitForIdle().then(function() { assertFlowHistory('a', 'sub-tasks', 'c', 'd', 'b'); }); }); it('nesting', function() { schedule('a'); scheduleAction('sub-tasks', function() { schedule('b'); scheduleAction('sub-sub-tasks', function() { schedule('c'); schedule('d'); }); schedule('e'); }); schedule('f'); return waitForIdle().then(function() { assertFlowHistory( 'a', 'sub-tasks', 'b', 'sub-sub-tasks', 'c', 'd', 'e', 'f'); }); }); it('taskReturnsSubTaskResult_1', function() { schedule('a'); scheduleAction('sub-tasks', function() { return schedule('c'); }); schedule('b'); return waitForIdle().then(function() { assertFlowHistory('a', 'sub-tasks', 'c', 'b'); }); }); it('taskReturnsSubTaskResult_2', function() { let pair = callbackPair((value) => assert.equal(123, value)); schedule('a'); schedule('sub-tasks', promise.fulfilled(123)).then(pair.callback); schedule('b'); return waitForIdle().then(function() { assertFlowHistory('a', 'sub-tasks','b'); pair.assertCallback(); }); }); it('taskReturnsPromiseThatDependsOnSubtask_1', function() { scheduleAction('a', function() { return promise.delayed(10).then(function() { schedule('b'); }); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('taskReturnsPromiseThatDependsOnSubtask_2', function() { scheduleAction('a', function() { return promise.fulfilled().then(function() { schedule('b'); }); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('taskReturnsPromiseThatDependsOnSubtask_3', function() { scheduleAction('a', function() { return promise.delayed(10).then(function() { return schedule('b'); }); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('taskReturnsPromiseThatDependsOnSubtask_4', function() { scheduleAction('a', function() { return promise.delayed(5).then(function() { return promise.delayed(5).then(function() { return schedule('b'); }); }); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('taskReturnsPromiseThatDependsOnSubtask_5', function() { scheduleAction('a', function() { return promise.delayed(5).then(function() { return promise.delayed(5).then(function() { return promise.delayed(5).then(function() { return promise.delayed(5).then(function() { return schedule('b'); }); }); }); }); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('taskReturnsPromiseThatDependsOnSubtask_6', function() { scheduleAction('a', function() { return promise.delayed(5). then(function() { return promise.delayed(5) }). then(function() { return promise.delayed(5) }). then(function() { return promise.delayed(5) }). then(function() { return schedule('b'); }); }); schedule('c'); return waitForIdle().then(function() { assertFlowHistory('a', 'b', 'c'); }); }); it('subTaskFails_1', function() { schedule('a'); scheduleAction('sub-tasks', function() { scheduleAction('sub-task that fails', throwStubError); }); schedule('should never execute'); return waitForAbort(). then(assertIsStubError). then(function() { assertFlowHistory('a', 'sub-tasks', 'sub-task that fails'); }); }); it('subTaskFails_2', function() { schedule('a'); scheduleAction('sub-tasks', function() { return promise.rejected(new StubError); }); schedule('should never execute'); return waitForAbort(). then(assertIsStubError). then(function() { assertFlowHistory('a', 'sub-tasks'); }); }); it('subTaskFails_3', function() { var callbacks = callbackPair(null, assertIsStubError); schedule('a'); scheduleAction('sub-tasks', function() { return promise.rejected(new StubError); }).then(callbacks.callback, callbacks.errback); schedule('b'); return waitForIdle(). then(function() { assertFlowHistory('a', 'sub-tasks', 'b'); callbacks.assertErrback(); }); }); }); describe('testEventLoopWaitsOnPendingPromiseRejections', function() { it('oneRejection', function() { var d = new promise.Deferred; scheduleAction('one', function() { return d.promise; }); scheduleAction('two', function() {}); return timeout(50).then(function() { assertFlowHistory('one'); d.reject(new StubError); return waitForAbort(); }). then(assertIsStubError). then(function() { assertFlowHistory('one'); }); }); it('multipleRejections', function() { var once = Error('once'); var twice = Error('twice'); scheduleAction('one', function() { promise.rejected(once); promise.rejected(twice); }); var twoResult = scheduleAction('two', function() {}); flow.removeAllListeners( promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); return new NativePromise(function(fulfill, reject) { setTimeout(function() { reject(Error('Should have reported the two errors by now')); }, 50); flow.on( promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, fulfill); }).then(function(e) { assert.ok(e instanceof promise.MultipleUnhandledRejectionError, 'Not a MultipleUnhandledRejectionError'); let errors = Array.from(e.errors); assert.deepEqual([once, twice], errors); assertFlowHistory('one'); }); }); }); describe('testCancelsPromiseReturnedByCallbackIfFrameFails', function() { it('promiseCallback', function() { var chainPair = callbackPair(null, assertIsStubError); var deferredPair = callbackPair(null, function(e) { assert.equal('CancellationError: StubError', e.toString(), 'callback result should be cancelled'); }); var d = new promise.Deferred(); d.promise.then(deferredPair.callback, deferredPair.errback); promise.fulfilled(). then(function() { scheduleAction('boom', throwStubError); schedule('this should not run'); return d.promise; }). then(chainPair.callback, chainPair.errback); return waitForIdle().then(function() { assertFlowHistory('boom'); chainPair.assertErrback('chain errback not invoked'); deferredPair.assertErrback('deferred errback not invoked'); }); }); it('taskCallback', function() { var chainPair = callbackPair(null, assertIsStubError); var deferredPair = callbackPair(null, function(e) { assert.equal('CancellationError: StubError', e.toString(), 'callback result should be cancelled'); }); var d = new promise.Deferred(); d.promise.then(deferredPair.callback, deferredPair.errback); schedule('a'). then(function() { scheduleAction('boom', throwStubError); schedule('this should not run'); return d.promise; }). then(chainPair.callback, chainPair.errback); return waitForIdle().then(function() { assertFlowHistory('a', 'boom'); chainPair.assertErrback('chain errback not invoked'); deferredPair.assertErrback('deferred errback not invoked'); }); }); }); it('testMaintainsOrderInCallbacksWhenATaskReturnsAPromise', function() { schedule('__start__', promise.fulfilled()). then(function() { messages.push('a'); schedulePush('b'); messages.push('c'); }). then(function() { messages.push('d'); }); schedulePush('e'); return waitForIdle().then(function() { assertFlowHistory('__start__', 'b', 'e'); assertMessages('a', 'c', 'b', 'd', 'e'); }); }); it('testOwningFlowIsActivatedForExecutingTasks', function() { var defaultFlow = promise.controlFlow(); var order = []; promise.createFlow(function(flow) { assertFlowIs(flow); order.push(0); defaultFlow.execute(function() { assertFlowIs(defaultFlow); order.push(1); }); }); return waitForIdle().then(function() { assertFlowIs(defaultFlow); assert.deepEqual([0, 1], order); }); }); it('testCreateFlowReturnsPromisePairedWithCreatedFlow', function() { return new NativePromise(function(fulfill, reject) { var newFlow; promise.createFlow(function(flow) { newFlow = flow; assertFlowIs(newFlow); }).then(function() { assertFlowIs(newFlow); waitForIdle(newFlow).then(fulfill, reject); }); }); }); it('testDeferredFactoriesCreateForActiveFlow_defaultFlow', function() { var e = Error(); var defaultFlow = promise.controlFlow(); promise.fulfilled().then(function() { assertFlowIs(defaultFlow); }); promise.rejected(e).then(null, function(err) { assert.equal(e, err); assertFlowIs(defaultFlow); }); promise.defer().promise.then(function() { assertFlowIs(defaultFlow); }); return waitForIdle(); }); it('testDeferredFactoriesCreateForActiveFlow_newFlow', function() { var e = Error(); var newFlow = new promise.ControlFlow; newFlow.execute(function() { promise.fulfilled().then(function() { assertFlowIs(newFlow); }); promise.rejected(e).then(null, function(err) { assert.equal(e, err); assertFlowIs(newFlow); }); let d = promise.defer(); d.promise.then(function() { assertFlowIs(newFlow); }); d.fulfill(); }).then(function() { assertFlowIs(newFlow); }); return waitForIdle(newFlow); }); it('testFlowsSynchronizeWithThemselvesNotEachOther', function() { var defaultFlow = promise.controlFlow(); schedulePush('a', 'a'); promise.controlFlow().timeout(500); schedulePush('b', 'b'); promise.createFlow(function(flow2) { assertFlowIs(flow2); schedulePush('c', 'c'); schedulePush('d', 'd'); }); return waitForIdle().then(function() { assertMessages('a', 'c', 'd', 'b'); }); }); it('testUnhandledErrorsAreReportedToTheOwningFlow', function() { var error1 = Error('e1'); var error2 = Error('e2'); var defaultFlow = promise.controlFlow(); defaultFlow.removeAllListeners('uncaughtException'); var flow1Error = defer(); flow1Error.promise.then(function(value) { assert.equal(error2, value); }); var flow2Error = defer(); flow2Error.promise.then(function(value) { assert.equal(error1, value); }); promise.createFlow(function(flow) { flow.once('uncaughtException', flow2Error.resolve); promise.rejected(error1); defaultFlow.once('uncaughtException', flow1Error.resolve); defaultFlow.execute(function() { promise.rejected(error2); }); }); return NativePromise.all([flow1Error.promise, flow2Error.promise]); }); it('testCanSynchronizeFlowsByReturningPromiseFromOneToAnother', function() { var flow1 = new promise.ControlFlow; var flow1Done = defer(); flow1.once('idle', flow1Done.resolve); flow1.once('uncaughtException', flow1Done.reject); var flow2 = new promise.ControlFlow; var flow2Done = defer(); flow2.once('idle', flow2Done.resolve); flow2.once('uncaughtException', flow2Done.reject); flow1.execute(function() { schedulePush('a', 'a'); return promise.delayed(25); }, 'start flow 1'); flow2.execute(function() { schedulePush('b', 'b'); schedulePush('c', 'c'); flow2.execute(function() { return flow1.execute(function() { schedulePush('d', 'd'); }, 'flow 1 task'); }, 'inject flow1 result into flow2'); schedulePush('e', 'e'); }, 'start flow 2'); return NativePromise.all([flow1Done.promise, flow2Done.promise]). then(function() { assertMessages('a', 'b', 'c', 'd', 'e'); }); }); it('testFramesWaitToCompleteForPendingRejections', function() { return new NativePromise(function(fulfill, reject) { promise.controlFlow().execute(function() { promise.rejected(new StubError); }).then(fulfill, reject); }). then(() => fail('expected to fail'), assertIsStubError); }); it('testSynchronizeErrorsPropagateToOuterFlow', function() { var outerFlow = new promise.ControlFlow; var innerFlow = new promise.ControlFlow; var block = defer(); innerFlow.execute(function() { return block.promise; }, 'block inner flow'); outerFlow.execute(function() { block.resolve(); return innerFlow.execute(function() { promise.rejected(new StubError); }, 'trigger unhandled rejection error'); }, 'run test'); return NativePromise.all([ waitForIdle(innerFlow), waitForAbort(outerFlow).then(assertIsStubError) ]); }); it('testFailsIfErrbackThrows', function() { promise.rejected('').then(null, throwStubError); return waitForAbort().then(assertIsStubError); }); it('testFailsIfCallbackReturnsRejectedPromise', function() { promise.fulfilled().then(function() { return promise.rejected(new StubError); }); return waitForAbort().then(assertIsStubError); }); it('testAbortsFrameIfTaskFails', function() { promise.fulfilled().then(function() { promise.controlFlow().execute(throwStubError); }); return waitForAbort().then(assertIsStubError); }); it('testAbortsFramePromisedChainedFromTaskIsNotHandled', function() { promise.fulfilled().then(function() { promise.controlFlow().execute(function() {}). then(throwStubError); }); return waitForAbort().then(assertIsStubError); }); it('testTrapsChainedUnhandledRejectionsWithinAFrame', function() { var pair = callbackPair(null, assertIsStubError); promise.fulfilled().then(function() { promise.controlFlow().execute(function() {}). then(throwStubError); }).then(pair.callback, pair.errback); return waitForIdle().then(pair.assertErrback); }); it('testCancelsRemainingTasksIfFrameThrowsDuringScheduling', function() { var task1, task2; var pair = callbackPair(null, assertIsStubError); var flow = promise.controlFlow(); flow.execute(function() { task1 = flow.execute(function() {}); task2 = flow.execute(function() {}); throw new StubError; }).then(pair.callback, pair.errback); return waitForIdle(). then(pair.assertErrback). then(function() { pair = callbackPair(); return task1.then(pair.callback, pair.errback); }). then(function() { pair.assertErrback(); pair = callbackPair(); return task2.then(pair.callback, pair.errback); }). then(function() { pair.assertErrback(); }); }); it('testCancelsRemainingTasksInFrameIfATaskFails', function() { var task; var pair = callbackPair(null, assertIsStubError); var flow = promise.controlFlow(); flow.execute(function() { flow.execute(throwStubError); task = flow.execute(function() {}); }).then(pair.callback, pair.errback); return waitForIdle().then(pair.assertErrback).then(function() { pair = callbackPair(); task.then(pair.callback, pair.errback); }).then(function() { pair.assertErrback(); }); }); it('testDoesNotModifyRejectionErrorIfPromiseNotInsideAFlow', function() { var error = Error('original message'); var originalStack = error.stack; var originalStr = error.toString(); var pair = callbackPair(null, function(e) { assert.equal(error, e); assert.equal('original message', e.message); assert.equal(originalStack, e.stack); assert.equal(originalStr, e.toString()); }); promise.rejected(error).then(pair.callback, pair.errback); return waitForIdle().then(pair.assertErrback); }); /** See https://github.com/SeleniumHQ/selenium/issues/444 */ it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_1', function() { var messages = []; flow.execute(function() { return promise.fulfilled(['a', 'b', 'c', 'd']); }, 'start').then(function(steps) { steps.forEach(function(step) { promise.fulfilled(step) .then(function() { messages.push(step + '.1'); }).then(function() { messages.push(step + '.2'); }); }) }); return waitForIdle().then(function() { assert.deepEqual( ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], messages); }); }); /** See https://github.com/SeleniumHQ/selenium/issues/444 */ it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_2', function() { var messages = []; flow.execute(function() { return promise.fulfilled(['a', 'b', 'c', 'd']); }, 'start').then(function(steps) { steps.forEach(function(step) { promise.fulfilled(step) .then(function() { messages.push(step + '.1'); }).then(function() { flow.execute(function() {}, step + '.2').then(function() { messages.push(step + '.2'); }); }); }) }); return waitForIdle().then(function() { assert.deepEqual( ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], messages); }); }); /** See https://github.com/SeleniumHQ/selenium/issues/444 */ it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_3', function() { var messages = []; flow.execute(function() { return promise.fulfilled(['a', 'b', 'c', 'd']); }, 'start').then(function(steps) { steps.forEach(function(step) { promise.fulfilled(step) .then(function(){}) .then(function() { messages.push(step + '.1'); return flow.execute(function() {}, step + '.1'); }).then(function() { flow.execute(function() {}, step + '.2').then(function(text) { messages.push(step + '.2'); }); }); }) }); return waitForIdle().then(function() { assert.deepEqual( ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], messages); }); }); /** See https://github.com/SeleniumHQ/selenium/issues/363 */ it('testTasksScheduledInASeparateTurnOfTheEventLoopGetASeparateTaskQueue_2', function() { scheduleAction('a', () => promise.delayed(10)); schedule('b'); setTimeout(() => schedule('c'), 0); return waitForIdle().then(function() { assertFlowHistory('a', 'c', 'b'); }); }); /** See https://github.com/SeleniumHQ/selenium/issues/363 */ it('testTasksScheduledInASeparateTurnOfTheEventLoopGetASeparateTaskQueue_2', function() { scheduleAction('a', () => promise.delayed(10)); schedule('b'); schedule('c'); setTimeout(function() { schedule('d'); scheduleAction('e', () => promise.delayed(10)); schedule('f'); }, 0); return waitForIdle().then(function() { assertFlowHistory('a', 'd', 'e', 'b', 'c', 'f'); }); }); /** See https://github.com/SeleniumHQ/selenium/issues/363 */ it('testCanSynchronizeTasksFromAdjacentTaskQueues', function() { var task1 = scheduleAction('a', () => promise.delayed(10)); schedule('b'); setTimeout(function() { scheduleAction('c', () => task1); schedule('d'); }, 0); return waitForIdle().then(function() { assertFlowHistory('a', 'c', 'd', 'b'); }); }); describe('testCancellingAScheduledTask', function() { it('1', function() { var called = false; var task1 = scheduleAction('a', () => called = true); task1.cancel('no soup for you'); return waitForIdle().then(function() { assert.ok(!called); assertFlowHistory(); return task1.catch(function(e) { assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); }); }); it('2', function() { schedule('a'); var called = false; var task2 = scheduleAction('b', () => called = true); schedule('c'); task2.cancel('no soup for you'); return waitForIdle().then(function() { assert.ok(!called); assertFlowHistory('a', 'c'); return task2.catch(function(e) { assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); }); }); it('3', function() { var called = false; var task = scheduleAction('a', () => called = true); task.cancel(new StubError); return waitForIdle().then(function() { assert.ok(!called); assertFlowHistory(); return task.catch(function(e) { assert.ok(e instanceof promise.CancellationError); }); }); }); it('4', function() { var seen = []; var task = scheduleAction('a', () => seen.push(1)) .then(() => seen.push(2)) .then(() => seen.push(3)) .then(() => seen.push(4)) .then(() => seen.push(5)); task.cancel(new StubError); return waitForIdle().then(function() { assert.deepEqual([], seen); assertFlowHistory(); return task.catch(function(e) { assert.ok(e instanceof promise.CancellationError); }); }); }); it('fromWithinAnExecutingTask', function() { var called = false; var task; scheduleAction('a', function() { task.cancel('no soup for you'); }); task = scheduleAction('b', () => called = true); schedule('c'); return waitForIdle().then(function() { assert.ok(!called); assertFlowHistory('a', 'c'); return task.catch(function(e) { assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); }); }); }); it('testCancellingAPendingTask', function() { var order = []; var unresolved = promise.defer(); var innerTask; var outerTask = scheduleAction('a', function() { order.push(1); // Schedule a task that will never finish. innerTask = scheduleAction('a.1', function() { return unresolved.promise; }); // Since the outerTask is cancelled below, innerTask should be cancelled // with a DiscardedTaskError, which means its callbacks are silently // dropped - so this should never execute. innerTask.catch(function(e) { order.push(2); }); }); schedule('b'); outerTask.catch(function(e) { order.push(3); assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); unresolved.promise.catch(function(e) { order.push(4); assert.ok(e instanceof promise.CancellationError); }); return timeout(10).then(function() { assert.deepEqual([1], order); outerTask.cancel('no soup for you'); return waitForIdle(); }).then(function() { assertFlowHistory('a', 'a.1', 'b'); assert.deepEqual([1, 3, 4], order); }); }); it('testCancellingAPendingPromiseCallback', function() { var called = false; var root = promise.fulfilled(); root.then(function() { cb2.cancel('no soup for you'); }); var cb2 = root.then(fail, fail); // These callbacks should never be called. cb2.then(fail, function(e) { called = true; assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); return waitForIdle().then(function() { assert.ok(called); }); }); describe('testResetFlow', function() { it('1', function() { var called = 0; var task = flow.execute(() => called++); task.finally(() => called++); return new Promise(function(fulfill) { flow.once('reset', fulfill); flow.reset(); }).then(function() { assert.equal(0, called); return task; }).then(fail, function(e) { assert.ok(e instanceof promise.CancellationError); assert.equal('ControlFlow was reset', e.message); }); }); it('2', function() { var called = 0; var task1 = flow.execute(() => called++); task1.finally(() => called++); var task2 = flow.execute(() => called++); task2.finally(() => called++); var task3 = flow.execute(() => called++); task3.finally(() => called++); return new Promise(function(fulfill) { flow.once('reset', fulfill); flow.reset(); }).then(function() { assert.equal(0, called); }); }); }); describe('testPromiseFulfilledInsideTask', function() { it('1', function() { var order = []; flow.execute(function() { var d = promise.defer(); d.promise.then(() => order.push('a')); d.promise.then(() => order.push('b')); d.promise.then(() => order.push('c')); d.fulfill(); flow.execute(() => order.push('d')); }).then(() => order.push('fin')); return waitForIdle().then(function() { assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); }); }); it('2', function() { var order = []; flow.execute(function() { flow.execute(() => order.push('a')); flow.execute(() => order.push('b')); var d = promise.defer(); d.promise.then(() => order.push('c')); d.promise.then(() => order.push('d')); d.fulfill(); flow.execute(() => order.push('e')); }).then(() => order.push('fin')); return waitForIdle().then(function() { assert.deepEqual(['a', 'b', 'c', 'd', 'e', 'fin'], order); }); }); it('3', function() { var order = []; var d = promise.defer(); d.promise.then(() => order.push('c')); d.promise.then(() => order.push('d')); flow.execute(function() { flow.execute(() => order.push('a')); flow.execute(() => order.push('b')); d.promise.then(() => order.push('e')); d.fulfill(); flow.execute(() => order.push('f')); }).then(() => order.push('fin')); return waitForIdle().then(function() { assert.deepEqual(['c', 'd', 'a', 'b', 'e', 'f', 'fin'], order); }); }); it('4', function() { var order = []; var d = promise.defer(); d.promise.then(() => order.push('a')); d.promise.then(() => order.push('b')); flow.execute(function() { flow.execute(function() { order.push('c'); flow.execute(() => order.push('d')); d.promise.then(() => order.push('e')); }); flow.execute(() => order.push('f')); d.promise.then(() => order.push('g')); d.fulfill(); flow.execute(() => order.push('h')); }).then(() => order.push('fin')); return waitForIdle().then(function() { assert.deepEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'fin'], order); }); }); }); describe('testSettledPromiseCallbacksInsideATask', function() { it('1', function() { var order = []; var p = promise.fulfilled(); flow.execute(function() { flow.execute(() => order.push('a')); p.then(() => order.push('b')); flow.execute(() => order.push('c')); p.then(() => order.push('d')); }).then(() => order.push('fin')); return waitForIdle().then(function() { assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); }); }); it('2', function() { var order = []; flow.execute(function() { flow.execute(() => order.push('a')) .then( () => order.push('c')); flow.execute(() => order.push('b')); }).then(() => order.push('fin')); return waitForIdle().then(function() { assert.deepEqual(['a', 'c', 'b', 'fin'], order); }); }); }); it('testTasksDoNotWaitForNewlyCreatedPromises', function() { var order = []; flow.execute(function() { var d = promise.defer(); // This is a normal promise, not a task, so the task for this callback is // considered volatile. Volatile tasks should be skipped when they reach // the front of the task queue. d.promise.then(() => order.push('a')); flow.execute(() => order.push('b')); flow.execute(function() { flow.execute(() => order.push('c')); d.promise.then(() => order.push('d')); d.fulfill(); }); flow.execute(() => order.push('e')); }).then(() => order.push('fin')); return waitForIdle().then(function() { assert.deepEqual(['b', 'a', 'c', 'd', 'e', 'fin'], order); }); }); it('testCallbackDependenciesDoNotDeadlock', function() { var order = []; var root = promise.defer(); var dep = promise.fulfilled().then(function() { order.push('a'); return root.promise.then(function() { order.push('b'); }); }); // This callback depends on |dep|, which depends on another callback // attached to |root| via a chain. root.promise.then(function() { order.push('c'); return dep.then(() => order.push('d')); }).then(() => order.push('fin')); setTimeout(() => root.fulfill(), 20); return waitForIdle().then(function() { assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); }); }); }); });