Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: make crashOnUnhandleRejection opt-out #21849

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 9 additions & 19 deletions doc/guides/writing-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,34 +225,24 @@ countdown.dec(); // The countdown callback will be invoked now.

#### Testing promises

When writing tests involving promises, either make sure that the
`onFulfilled` or the `onRejected` handler is wrapped in
`common.mustCall()` or `common.mustNotCall()` accordingly, or
call `common.crashOnUnhandledRejection()` in the top level of the
test to make sure that unhandled rejections would result in a test
failure. For example:
When writing tests involving promises, it is generally good to wrap the
`onFulfilled` handler, otherwise the test could successfully finish if the
promise never resolves (pending promises do not keep the event loop alive). The
`common` module automatically adds a handler that makes the process crash - and
hence, the test fail - in the case of an `unhandledRejection` event. It is
possible to disable it with `common.disableCrashOnUnhandledRejection()` if
needed.

```javascript
const common = require('../common');
const assert = require('assert');
const fs = require('fs').promises;

// Use `common.crashOnUnhandledRejection()` to make sure unhandled rejections
// will fail the test.
common.crashOnUnhandledRejection();

// Or, wrap the `onRejected` handler in `common.mustNotCall()`.
fs.writeFile('test-file', 'test').catch(common.mustNotCall());

// Or, wrap the `onFulfilled` handler in `common.mustCall()`.
// If there are assertions in the `onFulfilled` handler, wrap
// the next `onRejected` handler in `common.mustNotCall()`
// to handle potential failures.
// Wrap the `onFulfilled` handler in `common.mustCall()`.
fs.readFile('test-file').then(
common.mustCall(
(content) => assert.strictEqual(content.toString(), 'test2')
))
.catch(common.mustNotCall());
));
```

### Flags
Expand Down
2 changes: 0 additions & 2 deletions test/addons-napi/test_promise/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ const common = require('../../common');
const assert = require('assert');
const test_promise = require(`./build/${common.buildType}/test_promise`);

common.crashOnUnhandledRejection();

// A resolution
{
const expected_result = 42;
Expand Down
2 changes: 0 additions & 2 deletions test/addons-napi/test_threadsafe_function/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ const expectedArray = (function(arrayLength) {
return result;
})(binding.ARRAY_LENGTH);

common.crashOnUnhandledRejection();

// Handle the rapid teardown test case as the child process. We unref the
// thread-safe function after we have received two values. This causes the
// process to exit and the environment cleanup handler to be invoked.
Expand Down
2 changes: 0 additions & 2 deletions test/addons/callback-scope/test-resolve-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ const common = require('../../common');
const assert = require('assert');
const { testResolveAsync } = require(`./build/${common.buildType}/binding`);

common.crashOnUnhandledRejection();

let called = false;
testResolveAsync().then(() => { called = true; });

Expand Down
2 changes: 0 additions & 2 deletions test/addons/make-callback-recurse/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const makeCallback = binding.makeCallback;
// Make sure this is run in the future.
const mustCallCheckDomains = common.mustCall(checkDomains);

common.crashOnUnhandledRejection();

// Make sure that using MakeCallback allows the error to propagate.
assert.throws(function() {
makeCallback({}, function() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ const { checkInvocations } = require('./hook-checks');
if (!common.isMainThread)
common.skip('Worker bootstrapping works differently -> different async IDs');

common.crashOnUnhandledRejection();

const p = new Promise(common.mustCall(function executor(resolve, reject) {
resolve(5);
}));
Expand Down
2 changes: 0 additions & 2 deletions test/async-hooks/test-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const { checkInvocations } = require('./hook-checks');
if (!common.isMainThread)
common.skip('Worker bootstrapping works differently -> different async IDs');

common.crashOnUnhandledRejection();

const hooks = initHooks();

hooks.enable();
Expand Down
15 changes: 8 additions & 7 deletions test/common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,19 @@ symlinks
([SeCreateSymbolicLinkPrivilege](https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx)).
On non-Windows platforms, this always returns `true`.

### crashOnUnhandledRejection()

Installs a `process.on('unhandledRejection')` handler that crashes the process
after a tick. This is useful for tests that use Promises and need to make sure
no unexpected rejections occur, because currently they result in silent
failures.

### ddCommand(filename, kilobytes)
* return [<Object>]

Platform normalizes the `dd` command

### disableCrashOnUnhandledRejection()

Removes the `process.on('unhandledRejection')` handler that crashes the process
after a tick. The handler is useful for tests that use Promises and need to make
sure no unexpected rejections occur, because currently they result in silent
failures. However, it is useful in some rare cases to disable it, for example if
the `unhandledRejection` hook is directly used by the test.

### enoughTestMem
* [<boolean>]

Expand Down
7 changes: 4 additions & 3 deletions test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -814,9 +814,10 @@ exports.getBufferSources = function getBufferSources(buf) {
};

// Crash the process on unhandled rejections.
exports.crashOnUnhandledRejection = function() {
process.on('unhandledRejection',
(err) => process.nextTick(() => { throw err; }));
const crashOnUnhandledRejection = (err) => { throw err; };
process.on('unhandledRejection', crashOnUnhandledRejection);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it intentional to drop the process.nextTick()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was not. Do you think we should put it back?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure -- I was confused because the README says:

Removes the process.on('unhandledRejection') handler that crashes the process
after a tick.

so I assumed the process.nextTick() in the original code was intentional. ¯\_(ツ)_/¯

Copy link
Member Author

@targos targos Jul 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @addaleax since you implemented it initially. Was there a reason to throw in the next tick? I can't see any difference on a local test with or without process.nextTick.

Edit: commit

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@targos I don’t remember – I’d assume dropping it is okay.

exports.disableCrashOnUnhandledRejection = function() {
process.removeListener('unhandledRejection', crashOnUnhandledRejection);
};

exports.getTTYfd = function getTTYfd() {
Expand Down
4 changes: 2 additions & 2 deletions test/common/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const {
skipIf32Bits,
getArrayBufferViews,
getBufferSources,
crashOnUnhandledRejection,
disableCrashOnUnhandledRejection,
getTTYfd,
runWithInvalidFD,
hijackStdout,
Expand Down Expand Up @@ -112,7 +112,7 @@ export {
skipIf32Bits,
getArrayBufferViews,
getBufferSources,
crashOnUnhandledRejection,
disableCrashOnUnhandledRejection,
getTTYfd,
runWithInvalidFD,
hijackStdout,
Expand Down
1 change: 1 addition & 0 deletions test/common/inspector-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function spawnChildProcess(inspectorFlags, scriptContents, scriptFile) {
const handler = tearDown.bind(null, child);
process.on('exit', handler);
process.on('uncaughtException', handler);
common.disableCrashOnUnhandledRejection();
process.on('unhandledRejection', handler);
process.on('SIGINT', handler);

Expand Down
2 changes: 0 additions & 2 deletions test/es-module/test-esm-dynamic-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ const assert = require('assert');
const { URL } = require('url');
const vm = require('vm');

common.crashOnUnhandledRejection();

const relativePath = '../fixtures/es-modules/test-esm-ok.mjs';
const absolutePath = require.resolve('../fixtures/es-modules/test-esm-ok.mjs');
const targetURL = new URL('file:///');
Expand Down
4 changes: 1 addition & 3 deletions test/es-module/test-esm-error-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

// Flags: --experimental-modules

const common = require('../common');
require('../common');
const assert = require('assert');

common.crashOnUnhandledRejection();

const file = '../fixtures/syntax/bad_syntax.js';

let error;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs

import {
crashOnUnhandledRejection,
expectsError
} from '../common';

crashOnUnhandledRejection();
import { expectsError } from '../common';

import('test').catch(expectsError({
code: 'ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
Expand Down
4 changes: 1 addition & 3 deletions test/es-module/test-esm-throw-undefined.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Flags: --experimental-modules
/* eslint-disable node-core/required-modules */
import common from '../common/index.js';
import '../common';
import assert from 'assert';

async function doTest() {
Expand All @@ -12,5 +11,4 @@ async function doTest() {
);
}

common.crashOnUnhandledRejection();
doTest();
2 changes: 0 additions & 2 deletions test/internet/test-dns-any.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const net = require('net');
let running = false;
const queue = [];

common.crashOnUnhandledRejection();

const dnsPromises = dns.promises;
const isIPv4 = net.isIPv4;
const isIPv6 = net.isIPv6;
Expand Down
2 changes: 0 additions & 2 deletions test/internet/test-dns-ipv4.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ const net = require('net');
const util = require('util');
const isIPv4 = net.isIPv4;

common.crashOnUnhandledRejection();

const dnsPromises = dns.promises;
let running = false;
const queue = [];
Expand Down
2 changes: 0 additions & 2 deletions test/internet/test-dns-ipv6.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ const { addresses } = require('../common/internet');
if (!common.hasIPv6)
common.skip('this test, no IPv6 support');

common.crashOnUnhandledRejection();

const assert = require('assert');
const dns = require('dns');
const net = require('net');
Expand Down
4 changes: 1 addition & 3 deletions test/internet/test-dns-txt-sigsegv.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
'use strict';
const common = require('../common');
require('../common');
const assert = require('assert');
const dns = require('dns');
const dnsPromises = dns.promises;

common.crashOnUnhandledRejection();

(async function() {
const result = await dnsPromises.resolveTxt('www.microsoft.com');
assert.strictEqual(result.length, 0);
Expand Down
2 changes: 0 additions & 2 deletions test/internet/test-dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ const isIPv6 = net.isIPv6;
const util = require('util');
const dnsPromises = dns.promises;

common.crashOnUnhandledRejection();

let expected = 0;
let completed = 0;
let running = false;
Expand Down
2 changes: 0 additions & 2 deletions test/known_issues/test-inspector-cluster-port-clash.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ if (process.config.variables.v8_enable_inspector === 0) {
const cluster = require('cluster');
const net = require('net');

common.crashOnUnhandledRejection();

const ports = [process.debugPort];
const clashPort = process.debugPort + 2;
function serialFork() {
Expand Down
3 changes: 2 additions & 1 deletion test/message/unhandled_promise_trace_warnings.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Flags: --trace-warnings
'use strict';
require('../common');
const common = require('../common');
common.disableCrashOnUnhandledRejection();
const p = Promise.reject(new Error('This was rejected'));
setImmediate(() => p.catch(() => {}));
2 changes: 0 additions & 2 deletions test/parallel/test-assert-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ const assert = require('assert');
// Test assert.rejects() and assert.doesNotReject() by checking their
// expected output and by verifying that they do not work sync

common.crashOnUnhandledRejection();

// Run all tests in parallel and check their outcome at the end.
const promises = [];

Expand Down
1 change: 0 additions & 1 deletion test/parallel/test-async-hooks-disable-during-promise.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';
const common = require('../common');
const async_hooks = require('async_hooks');
common.crashOnUnhandledRejection();

if (!common.isMainThread)
common.skip('Worker bootstrapping works differently -> different AsyncWraps');
Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-async-hooks-enable-during-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
const common = require('../common');
const async_hooks = require('async_hooks');

common.crashOnUnhandledRejection();

Promise.resolve(1).then(common.mustCall(() => {
async_hooks.createHook({
init: common.mustCall(),
Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-async-hooks-promise-enable-disable.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ let p_resource = null;
let p_er = null;
let p_inits = 0;

common.crashOnUnhandledRejection();

// Not useful to place common.mustCall() around 'exit' event b/c it won't be
// able to check it anyway.
process.on('exit', (code) => {
Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-async-hooks-promise-triggerid.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ const async_hooks = require('async_hooks');
if (!common.isMainThread)
common.skip('Worker bootstrapping works differently -> different async IDs');

common.crashOnUnhandledRejection();

const promiseAsyncIds = [];

async_hooks.createHook({
Expand Down
3 changes: 2 additions & 1 deletion test/parallel/test-async-wrap-pop-id-during-load.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict';

require('../common');
const common = require('../common');

if (process.argv[2] === 'async') {
common.disableCrashOnUnhandledRejection();
async function fn() {
fn();
throw new Error();
Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-async-wrap-promise-after-enabled.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ const async_hooks = require('async_hooks');

const seenEvents = [];

common.crashOnUnhandledRejection();

const p = new Promise((resolve) => resolve(1));
p.then(() => seenEvents.push('then'));

Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-c-ares.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
const common = require('../common');
const assert = require('assert');

common.crashOnUnhandledRejection();

const dns = require('dns');
const dnsPromises = dns.promises;

Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-child-process-promisified.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ const assert = require('assert');
const child_process = require('child_process');
const { promisify } = require('util');

common.crashOnUnhandledRejection();

const exec = promisify(child_process.exec);
const execFile = promisify(child_process.execFile);

Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-dns-lookup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ const cares = process.binding('cares_wrap');
const dns = require('dns');
const dnsPromises = dns.promises;

common.crashOnUnhandledRejection();

// Stub `getaddrinfo` to *always* error.
cares.getaddrinfo = () => process.binding('uv').UV_ENOENT;

Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-dns-promises-resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ const assert = require('assert');

const dnsPromises = require('dns').promises;

common.crashOnUnhandledRejection();

// Error when rrtype is invalid.
{
const rrtype = 'DUMMY';
Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-dns-resolveany-bad-ancount.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ const assert = require('assert');
const dgram = require('dgram');
const dnsPromises = dns.promises;

common.crashOnUnhandledRejection();

const server = dgram.createSocket('udp4');

server.on('message', common.mustCall((msg, { address, port }) => {
Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-dns-resolveany.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ const assert = require('assert');
const dgram = require('dgram');
const dnsPromises = dns.promises;

common.crashOnUnhandledRejection();

const answers = [
{ type: 'A', address: '1.2.3.4', ttl: 123 },
{ type: 'AAAA', address: '::42', ttl: 123 },
Expand Down
Loading