Skip to content

Commit af290b8

Browse files
authored
port test_reference to CTS (#47)
* port test_reference to CTS ports [test_reference](https://github.com/nodejs/node/tree/main/test/js-native-api/test_reference) from the Node.js test suite to the CTS. Signed-off-by: Balakrishna Avulapati <ba@bavulapati.com> * flip the gcUntil condition Signed-off-by: Balakrishna Avulapati <ba@bavulapati.com> * expose a harness gc() wrapper Signed-off-by: Balakrishna Avulapati <ba@bavulapati.com> * add tests for onUncaughtException Signed-off-by: Balakrishna Avulapati <ba@bavulapati.com> * Remove legacy flag Signed-off-by: Balakrishna Avulapati <ba@bavulapati.com> * remove redundant flag * remove unrelated formatting changes --------- Signed-off-by: Balakrishna Avulapati <ba@bavulapati.com>
1 parent 8e3e5bf commit af290b8

8 files changed

Lines changed: 538 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const onUncaughtException = (cb) => {
2+
process.on('uncaughtException', cb);
3+
};
4+
5+
Object.assign(globalThis, { onUncaughtException });

implementors/node/tests.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ const MUST_CALL_MODULE_PATH = path.join(
3535
'node',
3636
'must-call.js',
3737
);
38+
const ON_UNCAUGHT_EXCEPTION_MODULE_PATH = path.join(
39+
ROOT_PATH,
40+
'implementors',
41+
'node',
42+
'on-uncaught-exception.js',
43+
);
3844
const SKIP_TEST_MODULE_PATH = path.join(
3945
ROOT_PATH,
4046
'implementors',
@@ -88,6 +94,8 @@ export function runFileInSubprocess(
8894
'--import',
8995
'file://' + MUST_CALL_MODULE_PATH,
9096
'--import',
97+
'file://' + ON_UNCAUGHT_EXCEPTION_MODULE_PATH,
98+
'--import',
9199
'file://' + SKIP_TEST_MODULE_PATH,
92100
'--import',
93101
'file://' + NAPI_VERSION_MODULE_PATH,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
if (typeof onUncaughtException !== 'function') {
2+
throw new Error('Expected a global onUncaughtException function');
3+
}
4+
5+
const expected = new Error('expected uncaught');
6+
onUncaughtException(mustCall((err) => {
7+
assert.strictEqual(err, expected);
8+
}));
9+
10+
throw expected;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
add_node_api_cts_addon(test_reference test_reference.c)
2+
add_node_api_cts_addon(test_finalizer test_finalizer.c)
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
'use strict';
2+
3+
const test_reference = loadAddon('test_reference');
4+
5+
// This test script uses external values with finalizer callbacks
6+
// in order to track when values get garbage-collected. Each invocation
7+
// of a finalizer callback increments the finalizeCount property.
8+
assert.strictEqual(test_reference.finalizeCount, 0);
9+
10+
// Run each test function in sequence,
11+
// with an async delay and GC call between each.
12+
async function runTests() {
13+
(() => {
14+
const symbol = test_reference.createSymbol('testSym');
15+
test_reference.createReference(symbol, 0);
16+
assert.strictEqual(test_reference.referenceValue, symbol);
17+
})();
18+
test_reference.deleteReference();
19+
20+
(() => {
21+
const symbol = test_reference.createSymbolFor('testSymFor');
22+
test_reference.createReference(symbol, 0);
23+
assert.strictEqual(test_reference.referenceValue, symbol);
24+
})();
25+
test_reference.deleteReference();
26+
27+
(() => {
28+
const symbol = test_reference.createSymbolFor('testSymFor');
29+
test_reference.createReference(symbol, 1);
30+
assert.strictEqual(test_reference.referenceValue, symbol);
31+
assert.strictEqual(test_reference.referenceValue, Symbol.for('testSymFor'));
32+
})();
33+
test_reference.deleteReference();
34+
35+
(() => {
36+
const symbol = test_reference.createSymbolForEmptyString();
37+
test_reference.createReference(symbol, 0);
38+
assert.strictEqual(test_reference.referenceValue, Symbol.for(''));
39+
})();
40+
test_reference.deleteReference();
41+
42+
(() => {
43+
const symbol = test_reference.createSymbolForEmptyString();
44+
test_reference.createReference(symbol, 1);
45+
assert.strictEqual(test_reference.referenceValue, symbol);
46+
assert.strictEqual(test_reference.referenceValue, Symbol.for(''));
47+
})();
48+
test_reference.deleteReference();
49+
50+
assert.throws(
51+
() => test_reference.createSymbolForIncorrectLength(),
52+
/Invalid argument/,
53+
);
54+
55+
(() => {
56+
const value = test_reference.createExternal();
57+
assert.strictEqual(test_reference.finalizeCount, 0);
58+
assert.strictEqual(typeof value, 'object');
59+
test_reference.checkExternal(value);
60+
})();
61+
await gcUntil(
62+
'External value without a finalizer',
63+
() => test_reference.finalizeCount === 0,
64+
);
65+
66+
(() => {
67+
const value = test_reference.createExternalWithFinalize();
68+
assert.strictEqual(test_reference.finalizeCount, 0);
69+
assert.strictEqual(typeof value, 'object');
70+
test_reference.checkExternal(value);
71+
})();
72+
await gcUntil(
73+
'External value with a finalizer',
74+
() => test_reference.finalizeCount === 1,
75+
);
76+
77+
(() => {
78+
const value = test_reference.createExternalWithFinalize();
79+
assert.strictEqual(test_reference.finalizeCount, 0);
80+
test_reference.createReference(value, 0);
81+
assert.strictEqual(test_reference.referenceValue, value);
82+
})();
83+
// Value should be GC'd because there is only a weak ref
84+
await gcUntil(
85+
'Weak reference',
86+
() =>
87+
test_reference.referenceValue === undefined &&
88+
test_reference.finalizeCount === 1,
89+
);
90+
test_reference.deleteReference();
91+
92+
(() => {
93+
const value = test_reference.createExternalWithFinalize();
94+
assert.strictEqual(test_reference.finalizeCount, 0);
95+
test_reference.createReference(value, 1);
96+
assert.strictEqual(test_reference.referenceValue, value);
97+
})();
98+
// Value should NOT be GC'd because there is a strong ref
99+
await gcUntil('Strong reference', () => test_reference.finalizeCount === 0);
100+
test_reference.deleteReference();
101+
await gcUntil(
102+
'Strong reference (cont.d)',
103+
() => test_reference.finalizeCount === 1,
104+
);
105+
106+
(() => {
107+
const value = test_reference.createExternalWithFinalize();
108+
assert.strictEqual(test_reference.finalizeCount, 0);
109+
test_reference.createReference(value, 1);
110+
})();
111+
// Value should NOT be GC'd because there is a strong ref
112+
await gcUntil(
113+
'Strong reference, increment then decrement to weak reference',
114+
() => test_reference.finalizeCount === 0,
115+
);
116+
assert.strictEqual(test_reference.incrementRefcount(), 2);
117+
// Value should NOT be GC'd because there is a strong ref
118+
await gcUntil(
119+
'Strong reference, increment then decrement to weak reference (cont.d-1)',
120+
() => test_reference.finalizeCount === 0,
121+
);
122+
assert.strictEqual(test_reference.decrementRefcount(), 1);
123+
// Value should NOT be GC'd because there is a strong ref
124+
await gcUntil(
125+
'Strong reference, increment then decrement to weak reference (cont.d-2)',
126+
() => test_reference.finalizeCount === 0,
127+
);
128+
assert.strictEqual(test_reference.decrementRefcount(), 0);
129+
// Value should be GC'd because the ref is now weak!
130+
await gcUntil(
131+
'Strong reference, increment then decrement to weak reference (cont.d-3)',
132+
() => test_reference.finalizeCount === 1,
133+
);
134+
test_reference.deleteReference();
135+
// Value was already GC'd
136+
await gcUntil(
137+
'Strong reference, increment then decrement to weak reference (cont.d-4)',
138+
() => test_reference.finalizeCount === 1,
139+
);
140+
}
141+
runTests();
142+
143+
// This test creates a napi_ref on an object that has
144+
// been wrapped by napi_wrap and for which the finalizer
145+
// for the wrap calls napi_delete_ref on that napi_ref.
146+
//
147+
// Since both the wrap and the reference use the same
148+
// object the finalizer for the wrap and reference
149+
// may run in the same gc and in any order.
150+
//
151+
// It does that to validate that napi_delete_ref can be
152+
// called before the finalizer has been run for the
153+
// reference (there is a finalizer behind the scenes even
154+
// though it cannot be passed to napi_create_reference).
155+
//
156+
// Since the order is not guaranteed, run the
157+
// test a number of times maximize the chance that we
158+
// get a run with the desired order for the test.
159+
//
160+
// 1000 reliably recreated the problem without the fix
161+
// required to ensure delete could be called before
162+
// the finalizer in manual testing.
163+
for (let i = 0; i < 1000; i++) {
164+
const wrapObject = new Object();
165+
test_reference.validateDeleteBeforeFinalize(wrapObject);
166+
gc();
167+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include <assert.h>
2+
#include <js_native_api.h>
3+
#include <stdlib.h>
4+
#include "../common.h"
5+
#include "../entry_point.h"
6+
7+
static int test_value = 1;
8+
static int finalize_count = 0;
9+
10+
static void FinalizeExternalCallJs(napi_env env, void* data, void* hint) {
11+
int* actual_value = data;
12+
NODE_API_ASSERT_RETURN_VOID(
13+
env,
14+
actual_value == &test_value,
15+
"The correct pointer was passed to the finalizer");
16+
17+
napi_ref finalizer_ref = (napi_ref)hint;
18+
napi_value js_finalizer;
19+
napi_value recv;
20+
NODE_API_CALL_RETURN_VOID(
21+
env, napi_get_reference_value(env, finalizer_ref, &js_finalizer));
22+
NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &recv));
23+
NODE_API_CALL_RETURN_VOID(
24+
env, napi_call_function(env, recv, js_finalizer, 0, NULL, NULL));
25+
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, finalizer_ref));
26+
}
27+
28+
static napi_value CreateExternalWithJsFinalize(napi_env env,
29+
napi_callback_info info) {
30+
size_t argc = 1;
31+
napi_value args[1];
32+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
33+
NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments");
34+
napi_value finalizer = args[0];
35+
napi_valuetype finalizer_valuetype;
36+
NODE_API_CALL(env, napi_typeof(env, finalizer, &finalizer_valuetype));
37+
NODE_API_ASSERT(env,
38+
finalizer_valuetype == napi_function,
39+
"Wrong type of first argument");
40+
napi_ref finalizer_ref;
41+
NODE_API_CALL(env, napi_create_reference(env, finalizer, 1, &finalizer_ref));
42+
43+
napi_value result;
44+
NODE_API_CALL(env,
45+
napi_create_external(env,
46+
&test_value,
47+
FinalizeExternalCallJs,
48+
finalizer_ref, /* finalize_hint */
49+
&result));
50+
51+
finalize_count = 0;
52+
return result;
53+
}
54+
55+
EXTERN_C_START
56+
napi_value Init(napi_env env, napi_value exports) {
57+
napi_property_descriptor descriptors[] = {
58+
DECLARE_NODE_API_PROPERTY("createExternalWithJsFinalize",
59+
CreateExternalWithJsFinalize),
60+
};
61+
62+
NODE_API_CALL(
63+
env,
64+
napi_define_properties(env,
65+
exports,
66+
sizeof(descriptors) / sizeof(*descriptors),
67+
descriptors));
68+
69+
return exports;
70+
}
71+
EXTERN_C_END
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
// Flags: --expose-gc --force-node-api-uncaught-exceptions-policy
3+
4+
const binding = loadAddon('test_finalizer');
5+
6+
onUncaughtException(
7+
mustCall((err) => {
8+
assert.throws(() => {
9+
throw err;
10+
}, /finalizer error/);
11+
}),
12+
);
13+
14+
(async function() {
15+
{
16+
binding.createExternalWithJsFinalize(
17+
mustCall(() => {
18+
throw new Error('finalizer error');
19+
}),
20+
);
21+
}
22+
gc();
23+
})().then(mustCall());

0 commit comments

Comments
 (0)