Skip to content
Open
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
2 changes: 1 addition & 1 deletion PORTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h
| `test_exception` | Ported ✅ | Medium |
| `test_finalizer` | Ported ✅ | Medium |
| `test_function` | Ported ✅ | Medium |
| `test_general` | Not ported | Hard |
| `test_general` | Partial | Hard |
| `test_handle_scope` | Ported ✅ | Easy |
| `test_instance_data` | Not ported | Medium |
| `test_new_target` | Ported ✅ | Easy |
Expand Down
1 change: 1 addition & 0 deletions tests/js-native-api/test_general/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_node_api_cts_addon(test_general test_general.c)
96 changes: 96 additions & 0 deletions tests/js-native-api/test_general/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const test_general = loadAddon('test_general');

const val1 = '1';
const val2 = 1;
const val3 = 1;

class BaseClass {
}

class ExtendedClass extends BaseClass {
}

const baseObject = new BaseClass();
const extendedObject = new ExtendedClass();

// napi_strict_equals
assert.ok(test_general.testStrictEquals(val1, val1));
assert.strictEqual(test_general.testStrictEquals(val1, val2), false);
assert.ok(test_general.testStrictEquals(val2, val3));

// napi_get_prototype
assert.strictEqual(
test_general.testGetPrototype(baseObject),
Object.getPrototypeOf(baseObject),
);
assert.strictEqual(
test_general.testGetPrototype(extendedObject),
Object.getPrototypeOf(extendedObject),
);
// Prototypes for base and extended should be different.
assert.notStrictEqual(
test_general.testGetPrototype(baseObject),
test_general.testGetPrototype(extendedObject),
);

// napi_get_version. Upstream pins this to Node.js's own Node-API version;
// portably, the addon must report whatever version the runtime declares.
assert.strictEqual(test_general.testGetVersion(), napiVersion);

// napi_typeof
[
123,
'test string',
function() {},
new Object(),
true,
undefined,
Symbol(),
].forEach((val) => {
assert.strictEqual(test_general.testNapiTypeof(val), typeof val);
});

// typeof null is 'object' in JS, so napi_null gets its own case.
assert.strictEqual(test_general.testNapiTypeof(null), 'null');

// Wrapping the same object twice fails.
const x = {};
test_general.wrap(x);
assert.throws(
() => test_general.wrap(x),
{ name: 'Error', message: 'Invalid argument' },
);
// Clean up here, otherwise derefItemWasCalled() will be polluted.
test_general.removeWrap(x);

// Wrapping twice succeeds if a removeWrap() separates the instances.
const y = {};
test_general.wrap(y);
test_general.removeWrap(y);
test_general.wrap(y);
// Clean up here, otherwise derefItemWasCalled() will be polluted.
test_general.removeWrap(y);

// napi_adjust_external_memory
const adjustedValue = test_general.testAdjustExternalMemory();
assert.strictEqual(typeof adjustedValue, 'number');
assert.ok(adjustedValue > 0);

// Garbage collecting a wrapped object calls the finalizer.
assert.strictEqual(test_general.derefItemWasCalled(), false);

(() => test_general.wrap({}))();
await gcUntil(
'deref_item() was called upon garbage collecting a wrapped object.',
() => test_general.derefItemWasCalled(),
);

// Removing a wrap and then garbage collecting does not call the finalizer.
let z = {};
test_general.testFinalizeWrap(z);
test_general.removeWrap(z);
z = null;
await gcUntil(
'finalize callback was not called upon garbage collection.',
() => !test_general.finalizeWasCalled(),
);
24 changes: 24 additions & 0 deletions tests/js-native-api/test_general/testEnvCleanup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Wrap finalizers that survive until the environment is torn down only run as
// the environment goes away, so this needs a child process to observe.
if (!runtimeFeatures.spawn) {
skipTest();
}

const result = await spawnTest('testEnvCleanup_child.mjs');

assert.strictEqual(
result.status,
0,
`child exited with status ${result.status}; stderr:\n${result.stderr}`,
);

// The child wraps three objects and keeps them alive to teardown. Only two
// finalizers should fire: the plain wrap, and the second of the re-wrapped
// pair. The removed wrap must not report, and neither must the first wrap that
// was replaced. Order between the two is unspecified, so compare as a set.
const reported = result.stdout.split(/\r\n|\r|\n/).filter(Boolean).sort();

assert.deepStrictEqual(reported, [
'finalize at env cleanup for second wrap',
'finalize at env cleanup for simple wrap',
]);
41 changes: 41 additions & 0 deletions tests/js-native-api/test_general/testEnvCleanup_child.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Spawned by testEnvCleanup.js. Wraps objects, keeps them reachable until the
// process exits, and lets environment teardown run their finalizers. Each
// finalizer prints a line naming its case; the parent checks which lines
// appear.
const test_general = loadAddon('test_general');

// The second argument to envCleanupWrap() indexes a static string array on the
// native side. Reproduced here as a reverse mapping for clarity.
const finalizerMessages = {
'simple wrap': 0,
'wrap, removeWrap': 1,
'first wrap': 2,
'second wrap': 3,
};

// Held in module scope so nothing is collected before the process exits.
const kept = {};

// A plain wrap: its finalizer runs at teardown.
kept['simple wrap'] = test_general.envCleanupWrap(
{},
finalizerMessages['simple wrap'],
);

// A removed wrap: its finalizer must not run.
kept['wrap, removeWrap'] = test_general.envCleanupWrap(
{},
finalizerMessages['wrap, removeWrap'],
);
test_general.removeWrap(kept['wrap, removeWrap']);

// Re-wrapped: only the latest attached finalizer runs.
kept['first wrap'] = test_general.envCleanupWrap(
{},
finalizerMessages['first wrap'],
);
test_general.removeWrap(kept['first wrap']);
test_general.envCleanupWrap(
kept['first wrap'],
finalizerMessages['second wrap'],
);
4 changes: 4 additions & 0 deletions tests/js-native-api/test_general/testGlobals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const test_general = loadAddon('test_general');

assert.strictEqual(test_general.getUndefined(), undefined);
assert.strictEqual(test_general.getNull(), null);
7 changes: 7 additions & 0 deletions tests/js-native-api/test_general/testNapiRun.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const test_general = loadAddon('test_general');

assert.strictEqual(test_general.testNapiRun('(41.92 + 0.08);'), 42);
assert.throws(
() => test_general.testNapiRun({ abc: 'def' }),
/string was expected/,
);
10 changes: 10 additions & 0 deletions tests/js-native-api/test_general/testNapiStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const test_general = loadAddon('test_general');

// createNapiError provokes a failing call, then checks that
// napi_get_last_error_info reports that failure. The next successful call must
// reset the recorded status back to napi_ok.
test_general.createNapiError();
assert.ok(
test_general.testNapiErrorCleanup(),
'napi_status cleaned up for second call',
);
Loading
Loading