Skip to content

Commit

Permalink
add two reference tests that use NAPI_MODULE_INIT
Browse files Browse the repository at this point in the history
  • Loading branch information
vmoroz committed Oct 28, 2022
1 parent e108b5e commit e629646
Show file tree
Hide file tree
Showing 6 changed files with 583 additions and 0 deletions.
8 changes: 8 additions & 0 deletions test/node-api/test_init_reference_all_types/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_init_reference_all_types",
"sources": [ "test_init_reference_all_types.c" ]
}
]
}
87 changes: 87 additions & 0 deletions test/node-api/test_init_reference_all_types/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict';
// Flags: --expose-gc

const { gcUntil, buildType } = require('../../common');
const assert = require('assert');

// Testing API calls for references to all value types.
// This test uses NAPI_MODULE_INIT macro to initialize module.
const addon = require(`./build/${buildType}/test_init_reference_all_types`);

async function runTests() {
let entryCount = 0;

(() => {
// Create values of all napi_valuetype types.
const undefinedValue = undefined;
const nullValue = null;
const booleanValue = false;
const numberValue = 42;
const stringValue = 'test_string';
const symbolValue = Symbol.for('test_symbol');
const objectValue = { x: 1, y: 2 };
const functionValue = (x, y) => x + y;
const externalValue = addon.createExternal();
const bigintValue = 9007199254740991n;

const allValues = [
undefinedValue,
nullValue,
booleanValue,
numberValue,
stringValue,
symbolValue,
objectValue,
functionValue,
externalValue,
bigintValue,
];
entryCount = allValues.length;
const objectValueIndex = allValues.indexOf(objectValue);
const functionValueIndex = allValues.indexOf(functionValue);

// Go over all values of different types, create strong ref values for
// them, read the stored values, and check how the ref count works.
for (const value of allValues) {
const index = addon.createRef(value);
const refValue = addon.getRefValue(index);
assert.strictEqual(value, refValue);
assert.strictEqual(addon.ref(index), 2);
assert.strictEqual(addon.unref(index), 1);
assert.strictEqual(addon.unref(index), 0);
}

// The references become weak pointers when the ref count is 0.
// To be compatible with the JavaScript spec we expect these
// types to be objects and functions.
// Here we know that the GC is not run yet because the values are
// still in the allValues array.
assert.strictEqual(addon.getRefValue(objectValueIndex), objectValue);
assert.strictEqual(addon.getRefValue(functionValueIndex), functionValue);

const objWithFinalizer = {};
addon.addFinalizer(objWithFinalizer);
})();

assert.strictEqual(addon.getFinalizeCount(), 0);
await gcUntil('Wait until a finalizer is called',
() => (addon.getFinalizeCount() === 1));

// Create and call finalizer again to make sure that all finalizers are run.
(() => {
const objWithFinalizer = {};
addon.addFinalizer(objWithFinalizer);
})();

await gcUntil('Wait until a finalizer is called again',
() => (addon.getFinalizeCount() === 1));

// After GC and finalizers run, all references with refCount==0 must return
// undefined value.
for (let i = 0; i < entryCount; ++i) {
const refValue = addon.getRefValue(i);
assert.strictEqual(refValue, undefined);
addon.deleteRef(i);
}
}
runTests();
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#define NAPI_EXPERIMENTAL
#include <node_api.h>
#include "../../js-native-api/common.h"
#include "stdlib.h"

#define NODE_API_ASSERT_STATUS(env, assertion, message) \
NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure)

#define NODE_API_CHECK_STATUS(env, the_call) \
do { \
napi_status status = (the_call); \
if (status != napi_ok) { \
return status; \
} \
} while (0)

static uint32_t finalizeCount = 0;

static void FreeData(napi_env env, void* data, void* hint) {
NODE_API_ASSERT_RETURN_VOID(env, data != NULL, "Expects non-NULL data.");
free(data);
}

static void Finalize(napi_env env, void* data, void* hint) {
++finalizeCount;
}

static napi_status GetArgValue(napi_env env,
napi_callback_info info,
napi_value* argValue) {
size_t argc = 1;
NODE_API_CHECK_STATUS(
env, napi_get_cb_info(env, info, &argc, argValue, NULL, NULL));

NODE_API_ASSERT_STATUS(env, argc == 1, "Expects one arg.");
return napi_ok;
}

static napi_status GetArgValueAsIndex(napi_env env,
napi_callback_info info,
uint32_t* index) {
napi_value argValue;
NODE_API_CHECK_STATUS(env, GetArgValue(env, info, &argValue));

napi_valuetype valueType;
NODE_API_CHECK_STATUS(env, napi_typeof(env, argValue, &valueType));
NODE_API_ASSERT_STATUS(
env, valueType == napi_number, "Argument must be a number.");

return napi_get_value_uint32(env, argValue, index);
}

static napi_status GetRef(napi_env env,
napi_callback_info info,
napi_ref* ref) {
uint32_t index;
NODE_API_CHECK_STATUS(env, GetArgValueAsIndex(env, info, &index));

napi_ref* refValues;
NODE_API_CHECK_STATUS(env, napi_get_instance_data(env, (void**)&refValues));
NODE_API_ASSERT_STATUS(env, refValues != NULL, "Cannot get instance data.");

*ref = refValues[index];
return napi_ok;
}

static napi_value ToUInt32Value(napi_env env, uint32_t value) {
napi_value result;
NODE_API_CALL(env, napi_create_uint32(env, value, &result));
return result;
}

static napi_status CheckFeature(napi_env env) {
bool canReferenceAllTypes;
NODE_API_CHECK_STATUS(
env,
napi_is_feature_enabled(
env, napi_feature_reference_all_types, &canReferenceAllTypes));
NODE_API_ASSERT_STATUS(
env, canReferenceAllTypes, "Must be able to reference all value types.");
return napi_ok;
}

static napi_status InitRefArray(napi_env env) {
// valueRefs array has one entry per napi_valuetype
napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1));
return napi_set_instance_data(env, valueRefs, &FreeData, NULL);
}

static napi_value CreateExternal(napi_env env, napi_callback_info info) {
napi_value result;
int* data = (int*)malloc(sizeof(int));
*data = 42;
NODE_API_CALL(env, napi_create_external(env, data, &FreeData, NULL, &result));
return result;
}

static napi_value CreateRef(napi_env env, napi_callback_info info) {
napi_value argValue;
NODE_API_CALL(env, GetArgValue(env, info, &argValue));

napi_valuetype valueType;
NODE_API_CALL(env, napi_typeof(env, argValue, &valueType));
uint32_t index = (uint32_t)valueType;

napi_ref* valueRefs;
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&valueRefs));
NODE_API_CALL(env,
napi_create_reference(env, argValue, 1, valueRefs + index));

return ToUInt32Value(env, index);
}

static napi_value GetRefValue(napi_env env, napi_callback_info info) {
napi_ref refValue;
NODE_API_CALL(env, GetRef(env, info, &refValue));
napi_value value;
NODE_API_CALL(env, napi_get_reference_value(env, refValue, &value));
return value;
}

static napi_value Ref(napi_env env, napi_callback_info info) {
napi_ref refValue;
NODE_API_CALL(env, GetRef(env, info, &refValue));
uint32_t refCount;
NODE_API_CALL(env, napi_reference_ref(env, refValue, &refCount));
return ToUInt32Value(env, refCount);
}

static napi_value Unref(napi_env env, napi_callback_info info) {
napi_ref refValue;
NODE_API_CALL(env, GetRef(env, info, &refValue));
uint32_t refCount;
NODE_API_CALL(env, napi_reference_unref(env, refValue, &refCount));
return ToUInt32Value(env, refCount);
}

static napi_value DeleteRef(napi_env env, napi_callback_info info) {
napi_ref refValue;
NODE_API_CALL(env, GetRef(env, info, &refValue));
NODE_API_CALL(env, napi_delete_reference(env, refValue));
return NULL;
}

static napi_value AddFinalizer(napi_env env, napi_callback_info info) {
napi_value obj;
NODE_API_CALL(env, GetArgValue(env, info, &obj));

napi_valuetype valueType;
NODE_API_CALL(env, napi_typeof(env, obj, &valueType));
NODE_API_ASSERT(env, valueType == napi_object, "Argument must be an object.");

NODE_API_CALL(env, napi_add_finalizer(env, obj, NULL, &Finalize, NULL, NULL));
return NULL;
}

static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) {
return ToUInt32Value(env, finalizeCount);
}

EXTERN_C_START

NAPI_MODULE_INIT() {
finalizeCount = 0;
NODE_API_CALL(env, CheckFeature(env));
NODE_API_CALL(env, InitRefArray(env));

napi_property_descriptor properties[] = {
DECLARE_NODE_API_PROPERTY("createExternal", CreateExternal),
DECLARE_NODE_API_PROPERTY("createRef", CreateRef),
DECLARE_NODE_API_PROPERTY("getRefValue", GetRefValue),
DECLARE_NODE_API_PROPERTY("ref", Ref),
DECLARE_NODE_API_PROPERTY("unref", Unref),
DECLARE_NODE_API_PROPERTY("deleteRef", DeleteRef),
DECLARE_NODE_API_PROPERTY("addFinalizer", AddFinalizer),
DECLARE_NODE_API_PROPERTY("getFinalizeCount", GetFinalizeCount),
};

NODE_API_CALL(
env,
napi_define_properties(
env, exports, sizeof(properties) / sizeof(*properties), properties));

return exports;
}

EXTERN_C_END
9 changes: 9 additions & 0 deletions test/node-api/test_init_reference_obj_only/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"targets": [
{
"target_name": "test_init_reference_obj_only",
"sources": [ "test_init_reference_obj_only.c" ],
'defines': [ 'NAPI_CUSTOM_FEATURES' ]
}
]
}
102 changes: 102 additions & 0 deletions test/node-api/test_init_reference_obj_only/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use strict';
// Flags: --expose-gc

const { gcUntil, buildType } = require('../../common');
const assert = require('assert');

// Testing API calls for references to only object, function, and symbol types.
// This is the reference behavior before when the
// napi_feature_reference_all_types feature is not enabled.
// This test uses NAPI_MODULE_INIT macro to initialize module.
const addon = require(`./build/${buildType}/test_init_reference_obj_only`);

async function runTests() {
let entryCount = 0;
let refIndexes = [];
let symbolValueIndex = -1;

(() => {
// Create values of all napi_valuetype types.
const undefinedValue = undefined;
const nullValue = null;
const booleanValue = false;
const numberValue = 42;
const stringValue = 'test_string';
const symbolValue = Symbol.for('test_symbol');
const objectValue = { x: 1, y: 2 };
const functionValue = (x, y) => x + y;
const externalValue = addon.createExternal();
const bigintValue = 9007199254740991n;

// true if the value can be a ref.
const allEntries = [
[ undefinedValue, false ],
[ nullValue, false ],
[ booleanValue, false ],
[ numberValue, false ],
[ stringValue, false ],
[ symbolValue, true ],
[ objectValue, true ],
[ functionValue, true ],
[ externalValue, true ],
[ bigintValue, false ],
];
entryCount = allEntries.length;
symbolValueIndex = allEntries.findIndex(entry => entry[0] === symbolValue);

// Go over all values of different types, create strong ref values for
// them, read the stored values, and check how the ref count works.
for (const entry of allEntries) {
const value = entry[0];
if (entry[1]) {
const index = addon.createRef(value);
const refValue = addon.getRefValue(index);
assert.strictEqual(value, refValue);
assert.strictEqual(addon.ref(index), 2);
assert.strictEqual(addon.unref(index), 1);
assert.strictEqual(addon.unref(index), 0);
} else {
assert.throws(() => addon.createRef(value));
}
}

// The references become weak pointers when the ref count is 0.
// The old reference were supported for objects, functions, and symbols.
// Here we know that the GC is not run yet because the values are
// still in the allValues array.
for (let i = 0; i < entryCount; ++i) {
if (allEntries[i][1]) {
assert.strictEqual(addon.getRefValue(i), allEntries[i][0]);
refIndexes.push(i);
}
}

const objWithFinalizer = {};
addon.addFinalizer(objWithFinalizer);
})();

assert.strictEqual(addon.getFinalizeCount(), 0);
await gcUntil('Wait until a finalizer is called',
() => (addon.getFinalizeCount() === 1));

// Create and call finalizer again to make sure that all finalizers are run.
(() => {
const objWithFinalizer = {};
addon.addFinalizer(objWithFinalizer);
})();

await gcUntil('Wait until a finalizer is called again',
() => (addon.getFinalizeCount() === 1));

// After GC and finalizers run, all references with refCount==0 must return
// undefined value.
for (const index of refIndexes) {
const refValue = addon.getRefValue(index);
// Symbols do not support the weak semantic
if (symbolValueIndex !== 5) {
assert.strictEqual(refValue, undefined);
}
addon.deleteRef(index);
}
}
runTests();
Loading

0 comments on commit e629646

Please sign in to comment.