summaryrefslogtreecommitdiff
path: root/test/node-api/test_reference_by_node_api_version
diff options
context:
space:
mode:
authorVladimir Morozov <vmorozov@microsoft.com>2023-04-07 07:40:16 -0700
committerMichael Dawson <mdawson@devrus.com>2023-05-05 11:00:27 -0400
commitc542d3a1d30d3e6c22b27f9bde55656923874818 (patch)
treea826d756b2617f598a245a232e1c3945c1ab9770 /test/node-api/test_reference_by_node_api_version
parent259ea3ed59a655ed0263746365c4538ec9c561a5 (diff)
downloadnode-new-c542d3a1d30d3e6c22b27f9bde55656923874818.tar.gz
node-api: get Node API version used by addon
PR-URL: https://github.com/nodejs/node/pull/45715 Reviewed-By: Gabriel Schulhof <gabrielschulhof@gmail.com> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Diffstat (limited to 'test/node-api/test_reference_by_node_api_version')
-rw-r--r--test/node-api/test_reference_by_node_api_version/binding.gyp14
-rw-r--r--test/node-api/test_reference_by_node_api_version/test.js124
-rw-r--r--test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c180
3 files changed, 318 insertions, 0 deletions
diff --git a/test/node-api/test_reference_by_node_api_version/binding.gyp b/test/node-api/test_reference_by_node_api_version/binding.gyp
new file mode 100644
index 0000000000..2ee1d24763
--- /dev/null
+++ b/test/node-api/test_reference_by_node_api_version/binding.gyp
@@ -0,0 +1,14 @@
+{
+ "targets": [
+ {
+ "target_name": "test_reference_all_types",
+ "sources": [ "test_reference_by_node_api_version.c" ],
+ "defines": [ "NAPI_EXPERIMENTAL" ],
+ },
+ {
+ "target_name": "test_reference_obj_only",
+ "sources": [ "test_reference_by_node_api_version.c" ],
+ "defines": [ "NAPI_VERSION=8" ],
+ }
+ ]
+}
diff --git a/test/node-api/test_reference_by_node_api_version/test.js b/test/node-api/test_reference_by_node_api_version/test.js
new file mode 100644
index 0000000000..32530f6815
--- /dev/null
+++ b/test/node-api/test_reference_by_node_api_version/test.js
@@ -0,0 +1,124 @@
+'use strict';
+// Flags: --expose-gc
+//
+// Testing API calls for Node-API references.
+// We compare their behavior between Node-API version 8 and later.
+// In version 8 references can be created only for object, function,
+// and symbol types, while in newer versions they can be created for
+// any value type.
+//
+const { gcUntil, buildType } = require('../../common');
+const assert = require('assert');
+const addon_v8 = require(`./build/${buildType}/test_reference_obj_only`);
+const addon_new = require(`./build/${buildType}/test_reference_all_types`);
+
+async function runTests(addon, isVersion8, isLocalSymbol) {
+ let allEntries = [];
+
+ (() => {
+ // Create values of all napi_valuetype types.
+ const undefinedValue = undefined;
+ const nullValue = null;
+ const booleanValue = false;
+ const numberValue = 42;
+ const stringValue = 'test_string';
+ const globalSymbolValue = Symbol.for('test_symbol_global');
+ const localSymbolValue = Symbol('test_symbol_local');
+ const symbolValue = isLocalSymbol ? localSymbolValue : globalSymbolValue;
+ const objectValue = { x: 1, y: 2 };
+ const functionValue = (x, y) => x + y;
+ const externalValue = addon.createExternal();
+ const bigintValue = 9007199254740991n;
+
+ // The position of entries in the allEntries array corresponds to the
+ // napi_valuetype enum value. See the CreateRef function for the
+ // implementation details.
+ allEntries = [
+ { value: undefinedValue, canBeWeak: false, canBeRefV8: false },
+ { value: nullValue, canBeWeak: false, canBeRefV8: false },
+ { value: booleanValue, canBeWeak: false, canBeRefV8: false },
+ { value: numberValue, canBeWeak: false, canBeRefV8: false },
+ { value: stringValue, canBeWeak: false, canBeRefV8: false },
+ { value: symbolValue, canBeWeak: isLocalSymbol, canBeRefV8: true,
+ isAlwaysStrong: !isLocalSymbol },
+ { value: objectValue, canBeWeak: true, canBeRefV8: true },
+ { value: functionValue, canBeWeak: true, canBeRefV8: true },
+ { value: externalValue, canBeWeak: true, canBeRefV8: true },
+ { value: bigintValue, canBeWeak: false, canBeRefV8: false },
+ ];
+
+ // 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) {
+ if (!isVersion8 || entry.canBeRefV8) {
+ const index = addon.createRef(entry.value);
+ const refValue = addon.getRefValue(index);
+ assert.strictEqual(entry.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(entry.value); },
+ {
+ name: 'Error',
+ message: 'Invalid argument',
+ });
+ }
+ }
+
+ // When the reference count is zero, then object types become weak pointers
+ // and other types are released.
+ // Here we know that the GC is not run yet because the values are
+ // still in the allEntries array.
+ allEntries.forEach((entry, index) => {
+ if (!isVersion8 || entry.canBeRefV8) {
+ if (entry.canBeWeak || entry.isAlwaysStrong) {
+ assert.strictEqual(addon.getRefValue(index), entry.value);
+ } else {
+ assert.strictEqual(addon.getRefValue(index), undefined);
+ }
+ }
+ // Set to undefined to allow GC collect the value.
+ entry.value = undefined;
+ });
+
+ // To check that GC pass is done.
+ const objWithFinalizer = {};
+ addon.addFinalizer(objWithFinalizer);
+ })();
+
+ addon.initFinalizeCount();
+ 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 we had another GC pass.
+ (() => {
+ const objWithFinalizer = {};
+ addon.addFinalizer(objWithFinalizer);
+ })();
+ await gcUntil('Wait until a finalizer is called again',
+ () => (addon.getFinalizeCount() === 2));
+
+ // After GC and finalizers run, all values that support weak reference
+ // semantic must return undefined value.
+ allEntries.forEach((entry, index) => {
+ if (!isVersion8 || entry.canBeRefV8) {
+ if (!entry.isAlwaysStrong) {
+ assert.strictEqual(addon.getRefValue(index), undefined);
+ } else {
+ assert.notStrictEqual(addon.getRefValue(index), undefined);
+ }
+ addon.deleteRef(index);
+ }
+ });
+}
+
+async function runAllTests() {
+ await runTests(addon_v8, /* isVersion8 */ true, /* isLocalSymbol */ true);
+ await runTests(addon_v8, /* isVersion8 */ true, /* isLocalSymbol */ false);
+ await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ true);
+ await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ false);
+}
+
+runAllTests();
diff --git a/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c b/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c
new file mode 100644
index 0000000000..23e5b1988b
--- /dev/null
+++ b/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c
@@ -0,0 +1,180 @@
+#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 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);
+}
+
+static napi_value InitFinalizeCount(napi_env env, napi_callback_info info) {
+ finalizeCount = 0;
+ return NULL;
+}
+
+EXTERN_C_START
+
+NAPI_MODULE_INIT() {
+ finalizeCount = 0;
+ 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),
+ DECLARE_NODE_API_PROPERTY("initFinalizeCount", InitFinalizeCount),
+ };
+
+ NODE_API_CALL(
+ env,
+ napi_define_properties(
+ env, exports, sizeof(properties) / sizeof(*properties), properties));
+
+ return exports;
+}
+
+EXTERN_C_END