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

885 lines
27 KiB
JavaScript
Raw Normal View History

2024-11-03 21:30:09 -05:00
// 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.
/**
* @fileoverview Contains tests against promise error handling. Many tests use
* NativePromise to control test termination independent of promise
* (and notably promise.ControlFlow).
*/
'use strict';
const testutil = require('./testutil');
const assert = require('assert');
const promise = require('../../lib/promise');
const {enablePromiseManager} = require('../../lib/test/promise');
const NativePromise = Promise;
const StubError = testutil.StubError;
const throwStubError = testutil.throwStubError;
const assertIsStubError = testutil.assertIsStubError;
describe('promise error handling', function() {
enablePromiseManager(() => {
var flow, uncaughtExceptions;
beforeEach(function setUp() {
if (promise.USE_PROMISE_MANAGER) {
flow = promise.controlFlow();
uncaughtExceptions = [];
flow.on('uncaughtException', onUncaughtException);
}
});
afterEach(function tearDown() {
if (promise.USE_PROMISE_MANAGER) {
return waitForIdle(flow).then(function() {
assert.deepEqual(
[], uncaughtExceptions, 'There were uncaught exceptions');
flow.reset();
});
}
});
function onUncaughtException(e) {
uncaughtExceptions.push(e);
}
function waitForAbort(opt_flow, opt_n) {
var n = opt_n || 1;
var theFlow = opt_flow || flow;
theFlow.removeAllListeners(
promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
return new NativePromise(function(fulfill, reject) {
theFlow.once('idle', function() {
reject(Error('expected flow to report an unhandled error'));
});
var errors = [];
theFlow.on('uncaughtException', onError);
function onError(e) {
errors.push(e);
if (errors.length === n) {
theFlow.removeListener('uncaughtException', onError);
fulfill(n === 1 ? errors[0] : errors);
}
}
});
}
function waitForIdle(opt_flow) {
var theFlow = opt_flow || flow;
return new NativePromise(function(fulfill, reject) {
if (theFlow.isIdle()) {
fulfill();
return;
}
theFlow.once('idle', fulfill);
theFlow.once('uncaughtException', reject);
});
}
it('testRejectedPromiseTriggersErrorCallback', function() {
return promise.rejected(new StubError).
then(assert.fail, assertIsStubError);
});
describe('callback throws trigger subsequent error callback', function() {
it('fulfilled promise', function() {
return promise.fulfilled().
then(throwStubError).
then(assert.fail, assertIsStubError);
});
it('rejected promise', function() {
var e = Error('not the droids you are looking for');
return promise.rejected(e).
then(assert.fail, throwStubError).
then(assert.fail, assertIsStubError);
});
});
describe('callback returns rejected promise triggers subsequent errback', function() {
it('from fulfilled callback', function() {
return promise.fulfilled().then(function() {
return promise.rejected(new StubError);
}).then(assert.fail, assertIsStubError);
});
it('from rejected callback', function() {
var e = Error('not the droids you are looking for');
return promise.rejected(e).
then(assert.fail, function() {
return promise.rejected(new StubError);
}).
then(assert.fail, assertIsStubError);
});
});
it('testReportsUnhandledRejectionsThroughTheControlFlow', function() {
promise.rejected(new StubError);
return waitForAbort().then(assertIsStubError);
});
describe('multiple unhandled rejections outside a task', function() {
it('are reported in order they occurred', function() {
var e1 = Error('error 1');
var e2 = Error('error 2');
promise.rejected(e1);
promise.rejected(e2);
return waitForAbort(flow).then(function(error) {
assert.ok(
error instanceof promise.MultipleUnhandledRejectionError);
// TODO: switch to Array.from when we drop node 0.12.x
var errors = [];
for (var e of error.errors) {
errors.push(e);
}
assert.deepEqual([e1, e2], errors);
});
});
});
describe('does not report unhandled rejection when', function() {
it('handler added before next tick', function() {
promise.rejected(new StubError).then(assert.fail, assertIsStubError);
return waitForIdle();
});
it('added async but before next tick', function() {
var called = false;
return new NativePromise(function(fulfill, reject) {
var aPromise;
NativePromise.resolve().then(function() {
aPromise.then(assert.fail, function(e) {
called = true;
assertIsStubError(e);
});
waitForIdle().then(fulfill, reject);
});
aPromise = promise.rejected(new StubError);
}).then(function() {
assert.ok(called);
})
});
});
it('testTaskThrows', function() {
return flow.execute(throwStubError).then(assert.fail, assertIsStubError);
});
it('testTaskReturnsRejectedPromise', function() {
return flow.execute(function() {
return promise.rejected(new StubError)
}).then(assert.fail, assertIsStubError);
});
it('testTaskHasUnhandledRejection', function() {
return flow.execute(function() {
promise.rejected(new StubError)
}).then(assert.fail, assertIsStubError);
});
it('testTaskfails_returnedPromiseIsUnhandled', function() {
flow.execute(throwStubError);
return waitForAbort().then(assertIsStubError);
});
it('testTaskHasUnhandledRejection_cancelsRemainingSubTasks', function() {
var seen = [];
flow.execute(function() {
promise.rejected(new StubError);
flow.execute(() => seen.push('a'))
.then(() => seen.push('b'), (e) => seen.push(e));
flow.execute(() => seen.push('c'))
.then(() => seen.push('b'), (e) => seen.push(e));
});
return waitForAbort()
.then(assertIsStubError)
.then(() => assert.deepEqual([], seen));
});
describe('nested task failures', function() {
it('returns up to paren', function() {
return flow.execute(function() {
return flow.execute(function() {
return flow.execute(throwStubError);
});
}).then(assert.fail, assertIsStubError);
});
it('task throws; uncaught error bubbles up', function() {
flow.execute(function() {
flow.execute(function() {
flow.execute(throwStubError);
});
});
return waitForAbort().then(assertIsStubError);
});
it('task throws; uncaught error bubbles up; is caught at root', function() {
flow.execute(function() {
flow.execute(function() {
flow.execute(throwStubError);
});
}).then(assert.fail, assertIsStubError);
return waitForIdle();
});
it('unhandled rejection bubbles up', function() {
flow.execute(function() {
flow.execute(function() {
flow.execute(function() {
promise.rejected(new StubError);
});
});
});
return waitForAbort().then(assertIsStubError);
});
it('unhandled rejection bubbles up; caught at root', function() {
flow.execute(function() {
flow.execute(function() {
promise.rejected(new StubError);
});
}).then(assert.fail, assertIsStubError);
return waitForIdle();
});
it('mixtureof hanging and free subtasks', function() {
flow.execute(function() {
return flow.execute(function() {
flow.execute(throwStubError);
});
});
return waitForAbort().then(assertIsStubError);
});
it('cancels remaining tasks', function() {
var seen = [];
flow.execute(function() {
flow.execute(() => promise.rejected(new StubError));
flow.execute(() => seen.push('a'))
.then(() => seen.push('b'), (e) => seen.push(e));
flow.execute(() => seen.push('c'))
.then(() => seen.push('b'), (e) => seen.push(e));
});
return waitForAbort()
.then(assertIsStubError)
.then(() => assert.deepEqual([], seen));
});
});
it('testTaskReturnsPromiseLikeObjectThatInvokesErrback', function() {
return flow.execute(function() {
return {
'then': function(_, errback) {
errback('abc123');
}
};
}).then(assert.fail, function(value) {
assert.equal('abc123', value);
});
});
describe('ControlFlow#wait();', function() {
describe('condition throws;', function() {
it('failure is caught', function() {
return flow.wait(throwStubError, 50).then(assert.fail, assertIsStubError);
});
it('failure is not caught', function() {
flow.wait(throwStubError, 50);
return waitForAbort().then(assertIsStubError);
});
});
describe('condition returns promise', function() {
it('failure is caught', function() {
return flow.wait(function() {
return promise.rejected(new StubError);
}, 50).then(assert.fail, assertIsStubError);
});
it('failure is not caught', function() {
flow.wait(function() {
return promise.rejected(new StubError);
}, 50);
return waitForAbort().then(assertIsStubError);
});
});
describe('condition has unhandled promise rejection', function() {
it('failure is caught', function() {
return flow.wait(function() {
promise.rejected(new StubError);
}, 50).then(assert.fail, assertIsStubError);
});
it('failure is not caught', function() {
flow.wait(function() {
promise.rejected(new StubError);
}, 50);
return waitForAbort().then(assertIsStubError);
});
});
describe('condition has subtask failure', function() {
it('failure is caught', function() {
return flow.wait(function() {
flow.execute(function() {
flow.execute(throwStubError);
});
}, 50).then(assert.fail, assertIsStubError);
});
it('failure is not caught', function() {
flow.wait(function() {
flow.execute(function() {
flow.execute(throwStubError);
});
}, 50);
return waitForAbort().then(assertIsStubError);
});
});
});
describe('errback throws a new error', function() {
it('start with normal promise', function() {
var error = Error('an error');
return promise.rejected(error).
catch(function(e) {
assert.equal(e, error);
throw new StubError;
}).
catch(assertIsStubError);
});
it('start with task result', function() {
var error = Error('an error');
return flow.execute(function() {
throw error;
}).
catch(function(e) {
assert.equal(e, error);
throw new StubError;
}).
catch(assertIsStubError);
});
it('start with normal promise; uncaught error', function() {
var error = Error('an error');
promise.rejected(error).
catch(function(e) {
assert.equal(e, error);
throw new StubError;
});
return waitForAbort().then(assertIsStubError);
});
it('start with task result; uncaught error', function() {
var error = Error('an error');
flow.execute(function() {
throw error;
}).
catch(function(e) {
assert.equal(e, error);
throw new StubError;
});
return waitForAbort().then(assertIsStubError);
});
});
it('thrownPromiseCausesCallbackRejection', function() {
let p = promise.fulfilled(1234);
return promise.fulfilled().then(function() {
throw p;
}).then(assert.fail, function(value) {
assert.strictEqual(p, value);
});
});
describe('task throws promise', function() {
it('promise was fulfilled', function() {
var toThrow = promise.fulfilled(1234);
flow.execute(function() {
throw toThrow;
}).then(assert.fail, function(value) {
assert.equal(toThrow, value);
return toThrow;
}).then(function(value) {
assert.equal(1234, value);
});
return waitForIdle();
});
it('promise was rejected', function() {
var toThrow = promise.rejected(new StubError);
toThrow.catch(function() {}); // For tearDown.
flow.execute(function() {
throw toThrow;
}).then(assert.fail, function(e) {
assert.equal(toThrow, e);
return e;
}).then(assert.fail, assertIsStubError);
return waitForIdle();
});
});
it('testFailsTaskIfThereIsAnUnhandledErrorWhileWaitingOnTaskResult', function() {
var d = promise.defer();
flow.execute(function() {
promise.rejected(new StubError);
return d.promise;
}).then(assert.fail, assertIsStubError);
return waitForIdle().then(function() {
return d.promise;
}).then(assert.fail, function(e) {
assert.equal('CancellationError: StubError', e.toString());
});
});
it('testFailsParentTaskIfAsyncScheduledTaskFails', function() {
var d = promise.defer();
flow.execute(function() {
flow.execute(throwStubError);
return d.promise;
}).then(assert.fail, assertIsStubError);
return waitForIdle().then(function() {
return d.promise;
}).then(assert.fail, function(e) {
assert.equal('CancellationError: StubError', e.toString());
});
});
describe('long stack traces', function() {
afterEach(() => promise.LONG_STACK_TRACES = false);
it('always includes task stacks in failures', function() {
promise.LONG_STACK_TRACES = false;
flow.execute(function() {
flow.execute(function() {
flow.execute(throwStubError, 'throw error');
}, 'two');
}, 'three').
then(assert.fail, function(e) {
assertIsStubError(e);
if (typeof e.stack !== 'string') {
return;
}
var messages = e.stack.split(/\n/).filter(function(line, index) {
return /^From: /.test(line);
});
assert.deepEqual([
'From: Task: throw error',
'From: Task: two',
'From: Task: three'
], messages);
});
return waitForIdle();
});
it('does not include completed tasks', function () {
flow.execute(function() {}, 'succeeds');
flow.execute(throwStubError, 'kaboom').then(assert.fail, function(e) {
assertIsStubError(e);
if (typeof e.stack !== 'string') {
return;
}
var messages = e.stack.split(/\n/).filter(function(line, index) {
return /^From: /.test(line);
});
assert.deepEqual(['From: Task: kaboom'], messages);
});
return waitForIdle();
});
it('does not include promise chain when disabled', function() {
promise.LONG_STACK_TRACES = false;
flow.execute(function() {
flow.execute(function() {
return promise.fulfilled().
then(function() {}).
then(function() {}).
then(throwStubError);
}, 'eventually assert.fails');
}, 'start').
then(assert.fail, function(e) {
assertIsStubError(e);
if (typeof e.stack !== 'string') {
return;
}
var messages = e.stack.split(/\n/).filter(function(line, index) {
return /^From: /.test(line);
});
assert.deepEqual([
'From: Task: eventually assert.fails',
'From: Task: start'
], messages);
});
return waitForIdle();
});
it('includes promise chain when enabled', function() {
promise.LONG_STACK_TRACES = true;
flow.execute(function() {
flow.execute(function() {
return promise.fulfilled().
then(function() {}).
then(function() {}).
then(throwStubError);
}, 'eventually assert.fails');
}, 'start').
then(assert.fail, function(e) {
assertIsStubError(e);
if (typeof e.stack !== 'string') {
return;
}
var messages = e.stack.split(/\n/).filter(function(line, index) {
return /^From: /.test(line);
});
assert.deepEqual([
'From: Promise: then',
'From: Task: eventually assert.fails',
'From: Task: start'
], messages);
});
return waitForIdle();
});
});
describe('frame cancels remaining tasks', function() {
it('on unhandled task failure', function() {
var run = false;
return flow.execute(function() {
flow.execute(throwStubError);
flow.execute(function() { run = true; });
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.ok(!run);
});
});
it('on unhandled promise rejection', function() {
var run = false;
return flow.execute(function() {
promise.rejected(new StubError);
flow.execute(function() { run = true; });
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.ok(!run);
});
});
it('if task throws', function() {
var run = false;
return flow.execute(function() {
flow.execute(function() { run = true; });
throw new StubError;
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.ok(!run);
});
});
describe('task callbacks scheduled in another frame', function() {
flow = promise.controlFlow();
function noop() {}
let subTask;
before(function() {
flow.execute(function() {
// This task will be discarded and never run because of the error below.
subTask = flow.execute(() => 'abc');
throw new StubError('stub');
}).catch(noop);
});
function assertCancellation(e) {
assert.ok(e instanceof promise.CancellationError);
assert.equal(
'Task was discarded due to a previous failure: stub', e.message);
}
it('are rejected with cancellation error', function() {
let result;
return Promise.resolve().then(function() {
return flow.execute(function() {
result = subTask.then(assert.fail);
});
})
.then(() => result)
.then(assert.fail, assertCancellation);
});
it('cancellation errors propagate through callbacks (1)', function() {
let result;
return Promise.resolve().then(function() {
return flow.execute(function() {
result = subTask
.then(assert.fail, assertCancellation)
.then(() => 'abc123');
});
})
.then(() => result)
.then(value => assert.equal('abc123', value));
});
it('cancellation errors propagate through callbacks (2)', function() {
let result;
return Promise.resolve().then(function() {
return flow.execute(function() {
result = subTask.then(assert.fail)
.then(noop, assertCancellation)
.then(() => 'fin');
});
})
// Verify result actually computed successfully all the way through.
.then(() => result)
.then(value => assert.equal('fin', value));
});
});
});
it('testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_return', function() {
var seen = [];
return flow.execute(function() {
flow.execute(throwStubError);
flow.execute(function() {
seen.push(1);
}).then(function() {
seen.push(2);
}, function() {
seen.push(3);
});
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.deepEqual([], seen);
});
});
it('testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_withReturn', function() {
var seen = [];
return flow.execute(function() {
flow.execute(throwStubError);
return flow.execute(function() {
seen.push(1);
}).then(function() {
seen.push(2);
}, function() {
seen.push(3);
});
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.deepEqual([], seen);
});
});
it('testTasksWithinACallbackAreDroppedIfContainingTaskIsAborted', function() {
var seen = [];
return flow.execute(function() {
flow.execute(throwStubError);
// None of the callbacks on this promise should execute because the
// task assert.failure above is never handled, causing the containing task to
// abort.
promise.fulfilled().then(function() {
seen.push(1);
return flow.execute(function() {
seen.push(2);
});
}).finally(function() {
seen.push(3);
});
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.deepEqual([], seen);
});
});
it('testTaskIsCancelledAfterWaitTimeout', function() {
var seen = [];
return flow.execute(function() {
flow.wait(function() {
return promise.delayed(50);
}, 5);
return flow.execute(function() {
seen.push(1);
}).then(function() {
seen.push(2);
}, function() {
seen.push(3);
});
}).then(assert.fail, function() {
assert.deepEqual([], seen);
});
});
describe('task callbacks get cancellation error if registered after task was cancelled', function() {
it('(a)', function() {
var task;
flow.execute(function() {
flow.execute(throwStubError);
task = flow.execute(function() {});
}).then(assert.fail, assertIsStubError);
return waitForIdle().then(function() {
return task.then(assert.fail, function(e) {
assert.ok(e instanceof promise.CancellationError);
});
});
});
it('(b)', function() {
var seen = [];
var task;
flow.execute(function() {
flow.execute(throwStubError);
task = flow.execute(function() {});
task.then(() => seen.push(1))
.then(() => seen.push(2));
task.then(() => seen.push(3))
.then(() => seen.push(4));
}).then(assert.fail, assertIsStubError);
return waitForIdle().then(function() {
return task.then(assert.fail, function(e) {
seen.push(5);
assert.ok(e instanceof promise.CancellationError);
});
}).then(() => assert.deepEqual([5], seen));
});
});
it('unhandledRejectionInParallelTaskQueue', function() {
var seen = [];
function schedule(name) {
return flow.execute(() => seen.push(name), name);
}
flow.async(function() {
schedule('a.1');
flow.execute(throwStubError, 'a.2 (throws)');
});
var b3;
flow.async(function() {
schedule('b.1');
schedule('b.2');
b3 = schedule('b.3');
});
var c3;
flow.async(function() {
schedule('c.1');
schedule('c.2');
c3 = schedule('c.3');
});
function assertWasCancelled(p) {
return p.then(assert.fail, function(e) {
assert.ok(e instanceof promise.CancellationError);
});
}
return waitForAbort()
.then(function() {
assert.deepEqual(['a.1', 'b.1', 'c.1', 'b.2', 'c.2'], seen);
})
.then(() => assertWasCancelled(b3))
.then(() => assertWasCancelled(c3));
});
it('errorsInAsyncFunctionsAreReportedAsUnhandledRejection', function() {
flow.removeAllListeners(); // For tearDown.
var task;
return new Promise(function(fulfill) {
flow.once('uncaughtException', fulfill);
flow.async(function() {
task = flow.execute(function() {});
throw Error('boom');
});
}).then(function(error) {
assert.ok(error instanceof promise.CancellationError);
return task.catch(function(error) {
assert.ok(error instanceof promise.CancellationError);
});
});
});
describe('does not wait for values thrown from callbacks to be resolved', function() {
it('(a)', function() {
var p1 = promise.fulfilled();
var reason = promise.fulfilled('should not see me');
return p1.then(function() {
throw reason;
}).then(assert.fail, function(e) {
assert.equal(reason, e);
});
});
it('(b)', function() {
var p1 = promise.fulfilled();
var reason = promise.rejected('should not see me');
reason.catch(function() {}); // For tearDown.
return p1.then(function() {
throw reason;
}).then(assert.fail, function(e) {
assert.equal(reason, e);
});
});
it('(c)', function() {
var p1 = promise.fulfilled();
var reason = promise.defer();
setTimeout(() => reason.fulfill('should not see me'), 100);
return p1.then(function() {
throw reason.promise;
}).then(assert.fail, function(e) {
assert.equal(reason.promise, e);
});
});
it('(d)', function() {
var p1 = promise.fulfilled();
var reason = {then: function() {}}; // A thenable like object.
return p1.then(function() {
throw reason;
}).then(assert.fail, function(e) {
assert.equal(reason, e);
});
});
});
});
});