diff options
| author | Vladimir Morozov <vmorozov@microsoft.com> | 2023-04-07 07:40:16 -0700 |
|---|---|---|
| committer | Michael Dawson <mdawson@devrus.com> | 2023-05-05 11:00:27 -0400 |
| commit | c542d3a1d30d3e6c22b27f9bde55656923874818 (patch) | |
| tree | a826d756b2617f598a245a232e1c3945c1ab9770 /test/node-api/test_reference_by_node_api_version | |
| parent | 259ea3ed59a655ed0263746365c4538ec9c561a5 (diff) | |
| download | node-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')
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 |
