Skip to content

Commit b3cfa55

Browse files
author
Gabriel Schulhof
committed
test: add finalizer exception test
1 parent 4ce40d2 commit b3cfa55

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

test/external.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,28 @@ Value GetFinalizeCount(const CallbackInfo& info) {
5151
return Number::New(info.Env(), finalizeCount);
5252
}
5353

54+
Value CreateExternalWithFinalizeException(const CallbackInfo& info) {
55+
return External<int>::New(info.Env(), new int(1),
56+
[](Env env, int* data) {
57+
Error error = Error::New(env, "Finalizer exception");
58+
delete data;
59+
#ifdef NAPI_CPP_EXCEPTIONS
60+
throw error;
61+
#else
62+
error.ThrowAsJavaScriptException();
63+
#endif
64+
});
65+
}
66+
5467
} // end anonymous namespace
5568

5669
Object InitExternal(Env env) {
5770
Object exports = Object::New(env);
5871

5972
exports["createExternal"] = Function::New(env, CreateExternal);
6073
exports["createExternalWithFinalize"] = Function::New(env, CreateExternalWithFinalize);
74+
exports["createExternalWithFinalizeException"] =
75+
Function::New(env, CreateExternalWithFinalizeException);
6176
exports["createExternalWithFinalizeHint"] = Function::New(env, CreateExternalWithFinalizeHint);
6277
exports["checkExternal"] = Function::New(env, CheckExternal);
6378
exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount);

test/external.js

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,58 @@
11
'use strict';
22
const buildType = process.config.target_defaults.default_configuration;
33
const assert = require('assert');
4+
const { spawnSync } = require('child_process');
45
const testUtil = require('./testUtil');
56

6-
module.exports = test(require(`./build/${buildType}/binding.node`))
7-
.then(() => test(require(`./build/${buildType}/binding_noexcept.node`)));
7+
if (process.argv.length === 3) {
8+
let interval;
9+
10+
// Running as the child process, hook up an `uncaughtException` handler to
11+
// examine the error thrown by the finalizer.
12+
process.on('uncaughtException', (error) => {
13+
// TODO (gabrielschulhof): Use assert.matches() when we drop support for
14+
// Node.js v10.x.
15+
assert(!!error.message.match(/Finalizer exception/));
16+
if (interval) {
17+
clearInterval(interval);
18+
}
19+
process.exit(0);
20+
});
21+
22+
// Create an external whose finalizer throws.
23+
(() =>
24+
require(process.argv[2]).external.createExternalWithFinalizeException())();
25+
26+
// gc until the external's finalizer throws or until we give up. Since the
27+
// exception is thrown from a native `SetImmediate()` we cannot catch it
28+
// anywhere except in the process' `uncaughtException` handler.
29+
let maxGCTries = 10;
30+
(function gcInterval() {
31+
global.gc();
32+
if (!interval) {
33+
interval = setInterval(gcInterval, 100);
34+
} else if (--maxGCTries === 0) {
35+
throw new Error('Timed out waiting for the gc to throw');
36+
process.exit(1);
37+
}
38+
})();
39+
40+
return;
41+
}
42+
43+
module.exports = test(require.resolve(`./build/${buildType}/binding.node`))
44+
.then(() =>
45+
test(require.resolve(`./build/${buildType}/binding_noexcept.node`)));
46+
47+
function test(bindingPath) {
48+
const binding = require(bindingPath);
49+
50+
const child = spawnSync(process.execPath, [
51+
'--expose-gc', __filename, bindingPath
52+
], { stdio: 'inherit' });
53+
assert.strictEqual(child.status, 0);
54+
assert.strictEqual(child.signal, null);
855

9-
function test(binding) {
1056
return testUtil.runGCTests([
1157
'External without finalizer',
1258
() => {

0 commit comments

Comments
 (0)