Skip to content

Commit e629646

Browse files
committed
add two reference tests that use NAPI_MODULE_INIT
1 parent e108b5e commit e629646

File tree

6 files changed

+583
-0
lines changed

6 files changed

+583
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "test_init_reference_all_types",
5+
"sources": [ "test_init_reference_all_types.c" ]
6+
}
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use strict';
2+
// Flags: --expose-gc
3+
4+
const { gcUntil, buildType } = require('../../common');
5+
const assert = require('assert');
6+
7+
// Testing API calls for references to all value types.
8+
// This test uses NAPI_MODULE_INIT macro to initialize module.
9+
const addon = require(`./build/${buildType}/test_init_reference_all_types`);
10+
11+
async function runTests() {
12+
let entryCount = 0;
13+
14+
(() => {
15+
// Create values of all napi_valuetype types.
16+
const undefinedValue = undefined;
17+
const nullValue = null;
18+
const booleanValue = false;
19+
const numberValue = 42;
20+
const stringValue = 'test_string';
21+
const symbolValue = Symbol.for('test_symbol');
22+
const objectValue = { x: 1, y: 2 };
23+
const functionValue = (x, y) => x + y;
24+
const externalValue = addon.createExternal();
25+
const bigintValue = 9007199254740991n;
26+
27+
const allValues = [
28+
undefinedValue,
29+
nullValue,
30+
booleanValue,
31+
numberValue,
32+
stringValue,
33+
symbolValue,
34+
objectValue,
35+
functionValue,
36+
externalValue,
37+
bigintValue,
38+
];
39+
entryCount = allValues.length;
40+
const objectValueIndex = allValues.indexOf(objectValue);
41+
const functionValueIndex = allValues.indexOf(functionValue);
42+
43+
// Go over all values of different types, create strong ref values for
44+
// them, read the stored values, and check how the ref count works.
45+
for (const value of allValues) {
46+
const index = addon.createRef(value);
47+
const refValue = addon.getRefValue(index);
48+
assert.strictEqual(value, refValue);
49+
assert.strictEqual(addon.ref(index), 2);
50+
assert.strictEqual(addon.unref(index), 1);
51+
assert.strictEqual(addon.unref(index), 0);
52+
}
53+
54+
// The references become weak pointers when the ref count is 0.
55+
// To be compatible with the JavaScript spec we expect these
56+
// types to be objects and functions.
57+
// Here we know that the GC is not run yet because the values are
58+
// still in the allValues array.
59+
assert.strictEqual(addon.getRefValue(objectValueIndex), objectValue);
60+
assert.strictEqual(addon.getRefValue(functionValueIndex), functionValue);
61+
62+
const objWithFinalizer = {};
63+
addon.addFinalizer(objWithFinalizer);
64+
})();
65+
66+
assert.strictEqual(addon.getFinalizeCount(), 0);
67+
await gcUntil('Wait until a finalizer is called',
68+
() => (addon.getFinalizeCount() === 1));
69+
70+
// Create and call finalizer again to make sure that all finalizers are run.
71+
(() => {
72+
const objWithFinalizer = {};
73+
addon.addFinalizer(objWithFinalizer);
74+
})();
75+
76+
await gcUntil('Wait until a finalizer is called again',
77+
() => (addon.getFinalizeCount() === 1));
78+
79+
// After GC and finalizers run, all references with refCount==0 must return
80+
// undefined value.
81+
for (let i = 0; i < entryCount; ++i) {
82+
const refValue = addon.getRefValue(i);
83+
assert.strictEqual(refValue, undefined);
84+
addon.deleteRef(i);
85+
}
86+
}
87+
runTests();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#define NAPI_EXPERIMENTAL
2+
#include <node_api.h>
3+
#include "../../js-native-api/common.h"
4+
#include "stdlib.h"
5+
6+
#define NODE_API_ASSERT_STATUS(env, assertion, message) \
7+
NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure)
8+
9+
#define NODE_API_CHECK_STATUS(env, the_call) \
10+
do { \
11+
napi_status status = (the_call); \
12+
if (status != napi_ok) { \
13+
return status; \
14+
} \
15+
} while (0)
16+
17+
static uint32_t finalizeCount = 0;
18+
19+
static void FreeData(napi_env env, void* data, void* hint) {
20+
NODE_API_ASSERT_RETURN_VOID(env, data != NULL, "Expects non-NULL data.");
21+
free(data);
22+
}
23+
24+
static void Finalize(napi_env env, void* data, void* hint) {
25+
++finalizeCount;
26+
}
27+
28+
static napi_status GetArgValue(napi_env env,
29+
napi_callback_info info,
30+
napi_value* argValue) {
31+
size_t argc = 1;
32+
NODE_API_CHECK_STATUS(
33+
env, napi_get_cb_info(env, info, &argc, argValue, NULL, NULL));
34+
35+
NODE_API_ASSERT_STATUS(env, argc == 1, "Expects one arg.");
36+
return napi_ok;
37+
}
38+
39+
static napi_status GetArgValueAsIndex(napi_env env,
40+
napi_callback_info info,
41+
uint32_t* index) {
42+
napi_value argValue;
43+
NODE_API_CHECK_STATUS(env, GetArgValue(env, info, &argValue));
44+
45+
napi_valuetype valueType;
46+
NODE_API_CHECK_STATUS(env, napi_typeof(env, argValue, &valueType));
47+
NODE_API_ASSERT_STATUS(
48+
env, valueType == napi_number, "Argument must be a number.");
49+
50+
return napi_get_value_uint32(env, argValue, index);
51+
}
52+
53+
static napi_status GetRef(napi_env env,
54+
napi_callback_info info,
55+
napi_ref* ref) {
56+
uint32_t index;
57+
NODE_API_CHECK_STATUS(env, GetArgValueAsIndex(env, info, &index));
58+
59+
napi_ref* refValues;
60+
NODE_API_CHECK_STATUS(env, napi_get_instance_data(env, (void**)&refValues));
61+
NODE_API_ASSERT_STATUS(env, refValues != NULL, "Cannot get instance data.");
62+
63+
*ref = refValues[index];
64+
return napi_ok;
65+
}
66+
67+
static napi_value ToUInt32Value(napi_env env, uint32_t value) {
68+
napi_value result;
69+
NODE_API_CALL(env, napi_create_uint32(env, value, &result));
70+
return result;
71+
}
72+
73+
static napi_status CheckFeature(napi_env env) {
74+
bool canReferenceAllTypes;
75+
NODE_API_CHECK_STATUS(
76+
env,
77+
napi_is_feature_enabled(
78+
env, napi_feature_reference_all_types, &canReferenceAllTypes));
79+
NODE_API_ASSERT_STATUS(
80+
env, canReferenceAllTypes, "Must be able to reference all value types.");
81+
return napi_ok;
82+
}
83+
84+
static napi_status InitRefArray(napi_env env) {
85+
// valueRefs array has one entry per napi_valuetype
86+
napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1));
87+
return napi_set_instance_data(env, valueRefs, &FreeData, NULL);
88+
}
89+
90+
static napi_value CreateExternal(napi_env env, napi_callback_info info) {
91+
napi_value result;
92+
int* data = (int*)malloc(sizeof(int));
93+
*data = 42;
94+
NODE_API_CALL(env, napi_create_external(env, data, &FreeData, NULL, &result));
95+
return result;
96+
}
97+
98+
static napi_value CreateRef(napi_env env, napi_callback_info info) {
99+
napi_value argValue;
100+
NODE_API_CALL(env, GetArgValue(env, info, &argValue));
101+
102+
napi_valuetype valueType;
103+
NODE_API_CALL(env, napi_typeof(env, argValue, &valueType));
104+
uint32_t index = (uint32_t)valueType;
105+
106+
napi_ref* valueRefs;
107+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&valueRefs));
108+
NODE_API_CALL(env,
109+
napi_create_reference(env, argValue, 1, valueRefs + index));
110+
111+
return ToUInt32Value(env, index);
112+
}
113+
114+
static napi_value GetRefValue(napi_env env, napi_callback_info info) {
115+
napi_ref refValue;
116+
NODE_API_CALL(env, GetRef(env, info, &refValue));
117+
napi_value value;
118+
NODE_API_CALL(env, napi_get_reference_value(env, refValue, &value));
119+
return value;
120+
}
121+
122+
static napi_value Ref(napi_env env, napi_callback_info info) {
123+
napi_ref refValue;
124+
NODE_API_CALL(env, GetRef(env, info, &refValue));
125+
uint32_t refCount;
126+
NODE_API_CALL(env, napi_reference_ref(env, refValue, &refCount));
127+
return ToUInt32Value(env, refCount);
128+
}
129+
130+
static napi_value Unref(napi_env env, napi_callback_info info) {
131+
napi_ref refValue;
132+
NODE_API_CALL(env, GetRef(env, info, &refValue));
133+
uint32_t refCount;
134+
NODE_API_CALL(env, napi_reference_unref(env, refValue, &refCount));
135+
return ToUInt32Value(env, refCount);
136+
}
137+
138+
static napi_value DeleteRef(napi_env env, napi_callback_info info) {
139+
napi_ref refValue;
140+
NODE_API_CALL(env, GetRef(env, info, &refValue));
141+
NODE_API_CALL(env, napi_delete_reference(env, refValue));
142+
return NULL;
143+
}
144+
145+
static napi_value AddFinalizer(napi_env env, napi_callback_info info) {
146+
napi_value obj;
147+
NODE_API_CALL(env, GetArgValue(env, info, &obj));
148+
149+
napi_valuetype valueType;
150+
NODE_API_CALL(env, napi_typeof(env, obj, &valueType));
151+
NODE_API_ASSERT(env, valueType == napi_object, "Argument must be an object.");
152+
153+
NODE_API_CALL(env, napi_add_finalizer(env, obj, NULL, &Finalize, NULL, NULL));
154+
return NULL;
155+
}
156+
157+
static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) {
158+
return ToUInt32Value(env, finalizeCount);
159+
}
160+
161+
EXTERN_C_START
162+
163+
NAPI_MODULE_INIT() {
164+
finalizeCount = 0;
165+
NODE_API_CALL(env, CheckFeature(env));
166+
NODE_API_CALL(env, InitRefArray(env));
167+
168+
napi_property_descriptor properties[] = {
169+
DECLARE_NODE_API_PROPERTY("createExternal", CreateExternal),
170+
DECLARE_NODE_API_PROPERTY("createRef", CreateRef),
171+
DECLARE_NODE_API_PROPERTY("getRefValue", GetRefValue),
172+
DECLARE_NODE_API_PROPERTY("ref", Ref),
173+
DECLARE_NODE_API_PROPERTY("unref", Unref),
174+
DECLARE_NODE_API_PROPERTY("deleteRef", DeleteRef),
175+
DECLARE_NODE_API_PROPERTY("addFinalizer", AddFinalizer),
176+
DECLARE_NODE_API_PROPERTY("getFinalizeCount", GetFinalizeCount),
177+
};
178+
179+
NODE_API_CALL(
180+
env,
181+
napi_define_properties(
182+
env, exports, sizeof(properties) / sizeof(*properties), properties));
183+
184+
return exports;
185+
}
186+
187+
EXTERN_C_END
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "test_init_reference_obj_only",
5+
"sources": [ "test_init_reference_obj_only.c" ],
6+
'defines': [ 'NAPI_CUSTOM_FEATURES' ]
7+
}
8+
]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use strict';
2+
// Flags: --expose-gc
3+
4+
const { gcUntil, buildType } = require('../../common');
5+
const assert = require('assert');
6+
7+
// Testing API calls for references to only object, function, and symbol types.
8+
// This is the reference behavior before when the
9+
// napi_feature_reference_all_types feature is not enabled.
10+
// This test uses NAPI_MODULE_INIT macro to initialize module.
11+
const addon = require(`./build/${buildType}/test_init_reference_obj_only`);
12+
13+
async function runTests() {
14+
let entryCount = 0;
15+
let refIndexes = [];
16+
let symbolValueIndex = -1;
17+
18+
(() => {
19+
// Create values of all napi_valuetype types.
20+
const undefinedValue = undefined;
21+
const nullValue = null;
22+
const booleanValue = false;
23+
const numberValue = 42;
24+
const stringValue = 'test_string';
25+
const symbolValue = Symbol.for('test_symbol');
26+
const objectValue = { x: 1, y: 2 };
27+
const functionValue = (x, y) => x + y;
28+
const externalValue = addon.createExternal();
29+
const bigintValue = 9007199254740991n;
30+
31+
// true if the value can be a ref.
32+
const allEntries = [
33+
[ undefinedValue, false ],
34+
[ nullValue, false ],
35+
[ booleanValue, false ],
36+
[ numberValue, false ],
37+
[ stringValue, false ],
38+
[ symbolValue, true ],
39+
[ objectValue, true ],
40+
[ functionValue, true ],
41+
[ externalValue, true ],
42+
[ bigintValue, false ],
43+
];
44+
entryCount = allEntries.length;
45+
symbolValueIndex = allEntries.findIndex(entry => entry[0] === symbolValue);
46+
47+
// Go over all values of different types, create strong ref values for
48+
// them, read the stored values, and check how the ref count works.
49+
for (const entry of allEntries) {
50+
const value = entry[0];
51+
if (entry[1]) {
52+
const index = addon.createRef(value);
53+
const refValue = addon.getRefValue(index);
54+
assert.strictEqual(value, refValue);
55+
assert.strictEqual(addon.ref(index), 2);
56+
assert.strictEqual(addon.unref(index), 1);
57+
assert.strictEqual(addon.unref(index), 0);
58+
} else {
59+
assert.throws(() => addon.createRef(value));
60+
}
61+
}
62+
63+
// The references become weak pointers when the ref count is 0.
64+
// The old reference were supported for objects, functions, and symbols.
65+
// Here we know that the GC is not run yet because the values are
66+
// still in the allValues array.
67+
for (let i = 0; i < entryCount; ++i) {
68+
if (allEntries[i][1]) {
69+
assert.strictEqual(addon.getRefValue(i), allEntries[i][0]);
70+
refIndexes.push(i);
71+
}
72+
}
73+
74+
const objWithFinalizer = {};
75+
addon.addFinalizer(objWithFinalizer);
76+
})();
77+
78+
assert.strictEqual(addon.getFinalizeCount(), 0);
79+
await gcUntil('Wait until a finalizer is called',
80+
() => (addon.getFinalizeCount() === 1));
81+
82+
// Create and call finalizer again to make sure that all finalizers are run.
83+
(() => {
84+
const objWithFinalizer = {};
85+
addon.addFinalizer(objWithFinalizer);
86+
})();
87+
88+
await gcUntil('Wait until a finalizer is called again',
89+
() => (addon.getFinalizeCount() === 1));
90+
91+
// After GC and finalizers run, all references with refCount==0 must return
92+
// undefined value.
93+
for (const index of refIndexes) {
94+
const refValue = addon.getRefValue(index);
95+
// Symbols do not support the weak semantic
96+
if (symbolValueIndex !== 5) {
97+
assert.strictEqual(refValue, undefined);
98+
}
99+
addon.deleteRef(index);
100+
}
101+
}
102+
runTests();

0 commit comments

Comments
 (0)