-
Notifications
You must be signed in to change notification settings - Fork 30.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add two reference tests that use NAPI_MODULE_INIT
- Loading branch information
Showing
6 changed files
with
583 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
187 changes: 187 additions & 0 deletions
187
test/node-api/test_init_reference_all_types/test_init_reference_all_types.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' ] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
Oops, something went wrong.