Skip to content

Commit 402dbf6

Browse files
committed
feat: port test_instance_data to CTS
1 parent dd9a852 commit 402dbf6

8 files changed

Lines changed: 182 additions & 1 deletion

File tree

PORTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h
6060
| `test_function` | Ported ✅ | Medium |
6161
| `test_general` | Not ported | Hard |
6262
| `test_handle_scope` | Ported ✅ | Easy |
63-
| `test_instance_data` | Not ported | Medium |
63+
| `test_instance_data` | Ported ✅ | Medium |
6464
| `test_new_target` | Ported ✅ | Easy |
6565
| `test_number` | Ported ✅ | Easy |
6666
| `test_object` | Not ported | Hard |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add_node_api_cts_addon(test_instance_data test_instance_data.c)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
const test_instance_data = loadAddon('test_instance_data');
4+
5+
// The addon seeds its instance data with 41, so seeing 42 here proves the
6+
// binding read back the very data the addon set at init.
7+
assert.strictEqual(test_instance_data.increment(), 42);
8+
9+
// Instance data is reachable from a finalizer too: the JS callback invoked
10+
// below is held in a reference stored in that data.
11+
let finalizerCalled = false;
12+
test_instance_data.objectWithFinalizer(mustCall(() => {
13+
finalizerCalled = true;
14+
}));
15+
16+
await gcUntil('instance data finalizer', () => finalizerCalled);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
// The delete hook passed to napi_set_instance_data only runs when the
4+
// environment goes away, so observing it takes a child process. This is the
5+
// main-thread environment; testInstanceDataWorker.js covers a secondary one.
6+
if (!runtimeFeatures.spawn) {
7+
skipTest();
8+
}
9+
10+
const result = await spawnTest('testInstanceDataTeardown_child.mjs');
11+
12+
assert.strictEqual(
13+
result.status,
14+
0,
15+
`child exited with status ${result.status}; stderr:\n${result.stderr}`,
16+
);
17+
assert.strictEqual(
18+
result.stdout.split(/\r\n?|\n/)[0],
19+
'deleting addon data',
20+
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Child of testInstanceDataTeardown.js: arms the addon to print from its
2+
// instance-data delete hook, then exits so the hook runs at environment
3+
// teardown and the parent can read the line off stdout.
4+
const test_instance_data = loadAddon('test_instance_data');
5+
6+
test_instance_data.setPrintOnDelete();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
// Upstream's worker variant: the same body in a secondary environment, which
4+
// covers instance data being per-environment and the delete hook running when
5+
// that environment is torn down while the process keeps going. A conformant
6+
// runtime looks the same as the main-thread run, so the value here is in
7+
// exercising the secondary-environment path at all.
8+
if (!runtimeFeatures.spawn || !runtimeFeatures.worker) {
9+
skipTest();
10+
}
11+
12+
const result = await spawnTest('testInstanceDataWorker_child.mjs', { worker: true });
13+
14+
assert.strictEqual(
15+
result.status,
16+
0,
17+
`child exited with status ${result.status}; stderr:\n${result.stderr}`,
18+
);
19+
assert.strictEqual(
20+
result.stdout.split(/\r\n?|\n/)[0],
21+
'deleting addon data',
22+
);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Child of testInstanceDataWorker.js, run in a worker: a secondary Node-API
2+
// environment. Runs the same body as test.js does on the main thread, since
3+
// that is what the secondary environment has to reproduce.
4+
5+
const test_instance_data = loadAddon('test_instance_data');
6+
7+
// Instance data is per-environment, so it is seeded at 41 here as well rather
8+
// than continuing from another environment's count.
9+
assert.strictEqual(test_instance_data.increment(), 42);
10+
11+
let finalizerCalled = false;
12+
test_instance_data.objectWithFinalizer(mustCall(() => {
13+
finalizerCalled = true;
14+
}));
15+
16+
await gcUntil('instance data finalizer in worker', () => finalizerCalled);
17+
18+
// Arm the delete hook so it prints when this environment - not the whole
19+
// process - is torn down.
20+
test_instance_data.setPrintOnDelete();
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#include <js_native_api.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include "../common.h"
5+
#include "../entry_point.h"
6+
7+
typedef struct {
8+
size_t value;
9+
bool print;
10+
napi_ref js_cb_ref;
11+
} AddonData;
12+
13+
static napi_value Increment(napi_env env, napi_callback_info info) {
14+
AddonData* data;
15+
napi_value result;
16+
17+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
18+
NODE_API_CALL(env, napi_create_uint32(env, ++data->value, &result));
19+
20+
return result;
21+
}
22+
23+
static void DeleteAddonData(napi_env env, void* raw_data, void* hint) {
24+
AddonData* data = raw_data;
25+
if (data->print) {
26+
printf("deleting addon data\n");
27+
}
28+
if (data->js_cb_ref != NULL) {
29+
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref));
30+
}
31+
free(data);
32+
}
33+
34+
static napi_value SetPrintOnDelete(napi_env env, napi_callback_info info) {
35+
AddonData* data;
36+
37+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
38+
data->print = true;
39+
40+
return NULL;
41+
}
42+
43+
static void TestFinalizer(napi_env env, void* raw_data, void* hint) {
44+
(void) raw_data;
45+
(void) hint;
46+
47+
AddonData* data;
48+
NODE_API_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
49+
napi_value js_cb, undefined;
50+
NODE_API_CALL_RETURN_VOID(env,
51+
napi_get_reference_value(env, data->js_cb_ref, &js_cb));
52+
NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
53+
NODE_API_CALL_RETURN_VOID(env,
54+
napi_call_function(env, undefined, js_cb, 0, NULL, NULL));
55+
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref));
56+
data->js_cb_ref = NULL;
57+
}
58+
59+
static napi_value ObjectWithFinalizer(napi_env env, napi_callback_info info) {
60+
AddonData* data;
61+
napi_value result, js_cb;
62+
size_t argc = 1;
63+
64+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
65+
NODE_API_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL");
66+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL));
67+
NODE_API_CALL(env, napi_create_object(env, &result));
68+
NODE_API_CALL(env,
69+
napi_add_finalizer(env, result, NULL, TestFinalizer, NULL, NULL));
70+
NODE_API_CALL(env, napi_create_reference(env, js_cb, 1, &data->js_cb_ref));
71+
72+
return result;
73+
}
74+
75+
EXTERN_C_START
76+
napi_value Init(napi_env env, napi_value exports) {
77+
AddonData* data = malloc(sizeof(*data));
78+
data->value = 41;
79+
data->print = false;
80+
data->js_cb_ref = NULL;
81+
82+
NODE_API_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL));
83+
84+
napi_property_descriptor props[] = {
85+
DECLARE_NODE_API_PROPERTY("increment", Increment),
86+
DECLARE_NODE_API_PROPERTY("setPrintOnDelete", SetPrintOnDelete),
87+
DECLARE_NODE_API_PROPERTY("objectWithFinalizer", ObjectWithFinalizer),
88+
};
89+
90+
NODE_API_CALL(env,
91+
napi_define_properties(
92+
env, exports, sizeof(props) / sizeof(*props), props));
93+
94+
return exports;
95+
}
96+
EXTERN_C_END

0 commit comments

Comments
 (0)