Kargi-Sitesi/node_modules/selenium-webdriver/test/lib/promise_flow_test.js

2288 lines
68 KiB
JavaScript

// 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);
});
});
});
});