From 239377b65460d166784dce58399c6c56680a3865 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 25 Mar 2020 10:40:06 -0700 Subject: [PATCH] n-api: correct instance data tests When instance data was backported, some of the tests ended up in a location where they do not get run. This moves the tests into test/addons-napi, merging them with existing tests therein, thereby ensuring that they do get run. PR-URL: https://github.com/nodejs/node/pull/32488 Reviewed-By: Beth Griggs Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- test/addons-napi/test_instance_data/test.js | 14 ++ .../test_instance_data/test_instance_data.c | 201 +++++++++++++++ test/node-api/.gitignore | 1 - test/node-api/node-api.status | 9 - test/node-api/test_instance_data/binding.gyp | 10 - test/node-api/test_instance_data/test.js | 35 --- .../test_instance_data/test_instance_data.c | 236 ------------------ 7 files changed, 215 insertions(+), 291 deletions(-) delete mode 100644 test/node-api/.gitignore delete mode 100644 test/node-api/node-api.status delete mode 100644 test/node-api/test_instance_data/binding.gyp delete mode 100644 test/node-api/test_instance_data/test.js delete mode 100644 test/node-api/test_instance_data/test_instance_data.c diff --git a/test/addons-napi/test_instance_data/test.js b/test/addons-napi/test_instance_data/test.js index 2c928963655b90..392d48e1accaf8 100644 --- a/test/addons-napi/test_instance_data/test.js +++ b/test/addons-napi/test_instance_data/test.js @@ -19,6 +19,20 @@ if (module.parent) { // Test that the instance data can be accessed from a finalizer. test_instance_data.objectWithFinalizer(common.mustCall()); global.gc(); + + // Test that instance data can be used in an async work callback. + new Promise((resolve) => test_instance_data.asyncWorkCallback(resolve)) + + // Test that the buffer finalizer can access the instance data. + .then(() => new Promise((resolve) => { + test_instance_data.testBufferFinalizer(resolve); + global.gc(); + })) + + // Test that the thread-safe function can access the instance data. + .then(() => new Promise((resolve) => + test_instance_data.testThreadsafeFunction(common.mustCall(), + common.mustCall(resolve)))); } else { // When launched as a script, run tests in either a child process or in a // worker thread. diff --git a/test/addons-napi/test_instance_data/test_instance_data.c b/test/addons-napi/test_instance_data/test_instance_data.c index 1468c2ecc89338..edf564080611c6 100644 --- a/test/addons-napi/test_instance_data/test_instance_data.c +++ b/test/addons-napi/test_instance_data/test_instance_data.c @@ -1,5 +1,6 @@ #include #include +#include #define NAPI_EXPERIMENTAL #include #include "../common.h" @@ -8,6 +9,9 @@ typedef struct { size_t value; bool print; napi_ref js_cb_ref; + napi_ref js_tsfn_finalizer_ref; + napi_threadsafe_function tsfn; + uv_thread_t thread; } AddonData; static napi_value Increment(napi_env env, napi_callback_info info) { @@ -20,6 +24,194 @@ static napi_value Increment(napi_env env, napi_callback_info info) { return result; } +static void AsyncWorkCbExecute(napi_env env, void* data) { + (void) env; + (void) data; +} + +static void call_cb_and_delete_ref(napi_env env, napi_ref* optional_ref) { + napi_value js_cb, undefined; + + if (optional_ref == NULL) { + AddonData* data; + NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); + optional_ref = &data->js_cb_ref; + } + + NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, + *optional_ref, + &js_cb)); + NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + NAPI_CALL_RETURN_VOID(env, napi_call_function(env, + undefined, + js_cb, + 0, + NULL, + NULL)); + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, *optional_ref)); + + *optional_ref = NULL; +} + +static void AsyncWorkCbComplete(napi_env env, + napi_status status, + void* data) { + (void) status; + (void) data; + call_cb_and_delete_ref(env, NULL); +} + +static bool establish_callback_ref(napi_env env, napi_callback_info info) { + AddonData* data; + size_t argc = 1; + napi_value js_cb; + + NAPI_CALL_BASE(env, napi_get_instance_data(env, (void**)&data), false); + NAPI_ASSERT_BASE(env, + data->js_cb_ref == NULL, + "reference must be NULL", + false); + NAPI_CALL_BASE(env, + napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL), + false); + NAPI_CALL_BASE(env, + napi_create_reference(env, js_cb, 1, &data->js_cb_ref), + false); + + return true; +} + +static napi_value AsyncWorkCallback(napi_env env, napi_callback_info info) { + if (establish_callback_ref(env, info)) { + napi_value resource_name; + napi_async_work work; + + NAPI_CALL(env, napi_create_string_utf8(env, + "AsyncIncrement", + NAPI_AUTO_LENGTH, + &resource_name)); + NAPI_CALL(env, napi_create_async_work(env, + NULL, + resource_name, + AsyncWorkCbExecute, + AsyncWorkCbComplete, + NULL, + &work)); + NAPI_CALL(env, napi_queue_async_work(env, work)); + } + + return NULL; +} + +static void TestBufferFinalizerCallback(napi_env env, void* data, void* hint) { + (void) data; + (void) hint; + call_cb_and_delete_ref(env, NULL); +} + +static napi_value TestBufferFinalizer(napi_env env, napi_callback_info info) { + napi_value buffer = NULL; + if (establish_callback_ref(env, info)) { + NAPI_CALL(env, napi_create_external_buffer(env, + sizeof(napi_callback), + TestBufferFinalizer, + TestBufferFinalizerCallback, + NULL, + &buffer)); + } + return buffer; +} + +static void ThreadsafeFunctionCallJS(napi_env env, + napi_value tsfn_cb, + void* context, + void* data) { + (void) tsfn_cb; + (void) context; + (void) data; + call_cb_and_delete_ref(env, NULL); +} + +static void ThreadsafeFunctionTestThread(void* raw_data) { + AddonData* data = raw_data; + napi_status status; + + // No need to call `napi_acquire_threadsafe_function()` because the main + // thread has set the refcount to 1 and there is only this one secondary + // thread. + status = napi_call_threadsafe_function(data->tsfn, + ThreadsafeFunctionCallJS, + napi_tsfn_nonblocking); + if (status != napi_ok) { + napi_fatal_error("ThreadSafeFunctionTestThread", + NAPI_AUTO_LENGTH, + "Failed to call TSFN", + NAPI_AUTO_LENGTH); + } + + status = napi_release_threadsafe_function(data->tsfn, napi_tsfn_release); + if (status != napi_ok) { + napi_fatal_error("ThreadSafeFunctionTestThread", + NAPI_AUTO_LENGTH, + "Failed to release TSFN", + NAPI_AUTO_LENGTH); + } + +} + +static void FinalizeThreadsafeFunction(napi_env env, void* raw, void* hint) { + AddonData* data; + NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); + NAPI_ASSERT_RETURN_VOID(env, + uv_thread_join(&data->thread) == 0, + "Failed to join the thread"); + call_cb_and_delete_ref(env, &data->js_tsfn_finalizer_ref); + data->tsfn = NULL; +} + +// Ths function accepts two arguments: the JS callback, and the finalize +// callback. The latter moves the test forward. +static napi_value +TestThreadsafeFunction(napi_env env, napi_callback_info info) { + AddonData* data; + size_t argc = 2; + napi_value argv[2], resource_name; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NAPI_CALL(env, napi_get_instance_data(env, (void**)&data)); + NAPI_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL"); + NAPI_ASSERT(env, + data->js_tsfn_finalizer_ref == NULL, + "tsfn finalizer reference must be NULL"); + NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &data->js_cb_ref)); + NAPI_CALL(env, napi_create_reference(env, + argv[1], + 1, + &data->js_tsfn_finalizer_ref)); + NAPI_CALL(env, napi_create_string_utf8(env, + "TSFN instance data test", + NAPI_AUTO_LENGTH, + &resource_name)); + NAPI_CALL(env, napi_create_threadsafe_function(env, + NULL, + NULL, + resource_name, + 0, + 1, + NULL, + FinalizeThreadsafeFunction, + NULL, + ThreadsafeFunctionCallJS, + &data->tsfn)); + NAPI_ASSERT(env, + uv_thread_create(&data->thread, + ThreadsafeFunctionTestThread, + data) == 0, + "uv_thread_create failed"); + + return NULL; +} + static void DeleteAddonData(napi_env env, void* raw_data, void* hint) { AddonData* data = raw_data; if (data->print) { @@ -28,6 +220,11 @@ static void DeleteAddonData(napi_env env, void* raw_data, void* hint) { if (data->js_cb_ref != NULL) { NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); } + if (data->js_tsfn_finalizer_ref) { + NAPI_CALL_RETURN_VOID(env, + napi_delete_reference(env, + data->js_tsfn_finalizer_ref)); + } free(data); } @@ -77,6 +274,7 @@ napi_value Init(napi_env env, napi_value exports) { data->value = 41; data->print = false; data->js_cb_ref = NULL; + data->js_tsfn_finalizer_ref = NULL; NAPI_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL)); @@ -84,6 +282,9 @@ napi_value Init(napi_env env, napi_value exports) { DECLARE_NAPI_PROPERTY("increment", Increment), DECLARE_NAPI_PROPERTY("setPrintOnDelete", SetPrintOnDelete), DECLARE_NAPI_PROPERTY("objectWithFinalizer", ObjectWithFinalizer), + DECLARE_NAPI_PROPERTY("asyncWorkCallback", AsyncWorkCallback), + DECLARE_NAPI_PROPERTY("testBufferFinalizer", TestBufferFinalizer), + DECLARE_NAPI_PROPERTY("testThreadsafeFunction", TestThreadsafeFunction), }; NAPI_CALL(env, napi_define_properties(env, diff --git a/test/node-api/.gitignore b/test/node-api/.gitignore deleted file mode 100644 index 378eac25d31170..00000000000000 --- a/test/node-api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/test/node-api/node-api.status b/test/node-api/node-api.status deleted file mode 100644 index 9395bfe1f6bbe0..00000000000000 --- a/test/node-api/node-api.status +++ /dev/null @@ -1,9 +0,0 @@ -prefix node-api - -# To mark a test as flaky, list the test name in the appropriate section -# below, without ".js", followed by ": PASS,FLAKY". Example: -# sample-test : PASS,FLAKY - -[true] # This section applies to all platforms - -[$system==win32] diff --git a/test/node-api/test_instance_data/binding.gyp b/test/node-api/test_instance_data/binding.gyp deleted file mode 100644 index 0d55905e9e7236..00000000000000 --- a/test/node-api/test_instance_data/binding.gyp +++ /dev/null @@ -1,10 +0,0 @@ -{ - "targets": [ - { - "target_name": "test_instance_data", - "sources": [ - "test_instance_data.c" - ] - } - ] -} diff --git a/test/node-api/test_instance_data/test.js b/test/node-api/test_instance_data/test.js deleted file mode 100644 index 969c164afdad58..00000000000000 --- a/test/node-api/test_instance_data/test.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; -// Test API calls for instance data. - -const common = require('../../common'); - -if (module.parent) { - // When required as a module, run the tests. - const test_instance_data = - require(`./build/${common.buildType}/test_instance_data`); - - // Test that instance data can be used in an async work callback. - new Promise((resolve) => test_instance_data.asyncWorkCallback(resolve)) - - // Test that the buffer finalizer can access the instance data. - .then(() => new Promise((resolve) => { - test_instance_data.testBufferFinalizer(resolve); - global.gc(); - })) - - // Test that the thread-safe function can access the instance data. - .then(() => new Promise((resolve) => - test_instance_data.testThreadsafeFunction(common.mustCall(), - common.mustCall(resolve)))); -} else { - // When launched as a script, run tests in either a child process or in a - // worker thread. - const requireAs = require('../../common/require-as'); - const runOptions = { stdio: ['inherit', 'pipe', 'inherit'] }; - - // Run tests in a child process. - requireAs(__filename, ['--expose-gc'], runOptions, 'child'); - - // Run tests in a worker thread in a child process. - requireAs(__filename, ['--expose-gc'], runOptions, 'worker'); -} diff --git a/test/node-api/test_instance_data/test_instance_data.c b/test/node-api/test_instance_data/test_instance_data.c deleted file mode 100644 index 1a814e91c0665b..00000000000000 --- a/test/node-api/test_instance_data/test_instance_data.c +++ /dev/null @@ -1,236 +0,0 @@ -#include -#include -#define NAPI_EXPERIMENTAL -#include -#include "../../js-native-api/common.h" - -typedef struct { - napi_ref js_cb_ref; - napi_ref js_tsfn_finalizer_ref; - napi_threadsafe_function tsfn; - uv_thread_t thread; -} AddonData; - -static void AsyncWorkCbExecute(napi_env env, void* data) { - (void) env; - (void) data; -} - -static void call_cb_and_delete_ref(napi_env env, napi_ref* optional_ref) { - napi_value js_cb, undefined; - - if (optional_ref == NULL) { - AddonData* data; - NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); - optional_ref = &data->js_cb_ref; - } - - NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, - *optional_ref, - &js_cb)); - NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); - NAPI_CALL_RETURN_VOID(env, napi_call_function(env, - undefined, - js_cb, - 0, - NULL, - NULL)); - NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, *optional_ref)); - - *optional_ref = NULL; -} - -static void AsyncWorkCbComplete(napi_env env, - napi_status status, - void* data) { - (void) status; - (void) data; - call_cb_and_delete_ref(env, NULL); -} - -static bool establish_callback_ref(napi_env env, napi_callback_info info) { - AddonData* data; - size_t argc = 1; - napi_value js_cb; - - NAPI_CALL_BASE(env, napi_get_instance_data(env, (void**)&data), false); - NAPI_ASSERT_BASE(env, - data->js_cb_ref == NULL, - "reference must be NULL", - false); - NAPI_CALL_BASE(env, - napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL), - false); - NAPI_CALL_BASE(env, - napi_create_reference(env, js_cb, 1, &data->js_cb_ref), - false); - - return true; -} - -static napi_value AsyncWorkCallback(napi_env env, napi_callback_info info) { - if (establish_callback_ref(env, info)) { - napi_value resource_name; - napi_async_work work; - - NAPI_CALL(env, napi_create_string_utf8(env, - "AsyncIncrement", - NAPI_AUTO_LENGTH, - &resource_name)); - NAPI_CALL(env, napi_create_async_work(env, - NULL, - resource_name, - AsyncWorkCbExecute, - AsyncWorkCbComplete, - NULL, - &work)); - NAPI_CALL(env, napi_queue_async_work(env, work)); - } - - return NULL; -} - -static void TestBufferFinalizerCallback(napi_env env, void* data, void* hint) { - (void) data; - (void) hint; - call_cb_and_delete_ref(env, NULL); -} - -static napi_value TestBufferFinalizer(napi_env env, napi_callback_info info) { - napi_value buffer = NULL; - if (establish_callback_ref(env, info)) { - NAPI_CALL(env, napi_create_external_buffer(env, - sizeof(napi_callback), - TestBufferFinalizer, - TestBufferFinalizerCallback, - NULL, - &buffer)); - } - return buffer; -} - -static void ThreadsafeFunctionCallJS(napi_env env, - napi_value tsfn_cb, - void* context, - void* data) { - (void) tsfn_cb; - (void) context; - (void) data; - call_cb_and_delete_ref(env, NULL); -} - -static void ThreadsafeFunctionTestThread(void* raw_data) { - AddonData* data = raw_data; - napi_status status; - - // No need to call `napi_acquire_threadsafe_function()` because the main - // thread has set the refcount to 1 and there is only this one secondary - // thread. - status = napi_call_threadsafe_function(data->tsfn, - ThreadsafeFunctionCallJS, - napi_tsfn_nonblocking); - if (status != napi_ok) { - napi_fatal_error("ThreadSafeFunctionTestThread", - NAPI_AUTO_LENGTH, - "Failed to call TSFN", - NAPI_AUTO_LENGTH); - } - - status = napi_release_threadsafe_function(data->tsfn, napi_tsfn_release); - if (status != napi_ok) { - napi_fatal_error("ThreadSafeFunctionTestThread", - NAPI_AUTO_LENGTH, - "Failed to release TSFN", - NAPI_AUTO_LENGTH); - } - -} - -static void FinalizeThreadsafeFunction(napi_env env, void* raw, void* hint) { - AddonData* data; - NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); - NAPI_ASSERT_RETURN_VOID(env, - uv_thread_join(&data->thread) == 0, - "Failed to join the thread"); - call_cb_and_delete_ref(env, &data->js_tsfn_finalizer_ref); - data->tsfn = NULL; -} - -// Ths function accepts two arguments: the JS callback, and the finalize -// callback. The latter moves the test forward. -static napi_value -TestThreadsafeFunction(napi_env env, napi_callback_info info) { - AddonData* data; - size_t argc = 2; - napi_value argv[2], resource_name; - - NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); - NAPI_CALL(env, napi_get_instance_data(env, (void**)&data)); - NAPI_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL"); - NAPI_ASSERT(env, - data->js_tsfn_finalizer_ref == NULL, - "tsfn finalizer reference must be NULL"); - NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &data->js_cb_ref)); - NAPI_CALL(env, napi_create_reference(env, - argv[1], - 1, - &data->js_tsfn_finalizer_ref)); - NAPI_CALL(env, napi_create_string_utf8(env, - "TSFN instance data test", - NAPI_AUTO_LENGTH, - &resource_name)); - NAPI_CALL(env, napi_create_threadsafe_function(env, - NULL, - NULL, - resource_name, - 0, - 1, - NULL, - FinalizeThreadsafeFunction, - NULL, - ThreadsafeFunctionCallJS, - &data->tsfn)); - NAPI_ASSERT(env, - uv_thread_create(&data->thread, - ThreadsafeFunctionTestThread, - data) == 0, - "uv_thread_create failed"); - - return NULL; -} - -static void DeleteAddonData(napi_env env, void* raw_data, void* hint) { - AddonData* data = raw_data; - if (data->js_cb_ref) { - NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); - } - if (data->js_tsfn_finalizer_ref) { - NAPI_CALL_RETURN_VOID(env, - napi_delete_reference(env, - data->js_tsfn_finalizer_ref)); - } - free(data); -} - -static napi_value Init(napi_env env, napi_value exports) { - AddonData* data = malloc(sizeof(*data)); - data->js_cb_ref = NULL; - data->js_tsfn_finalizer_ref = NULL; - - NAPI_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL)); - - napi_property_descriptor props[] = { - DECLARE_NAPI_PROPERTY("asyncWorkCallback", AsyncWorkCallback), - DECLARE_NAPI_PROPERTY("testBufferFinalizer", TestBufferFinalizer), - DECLARE_NAPI_PROPERTY("testThreadsafeFunction", TestThreadsafeFunction), - }; - - NAPI_CALL(env, napi_define_properties(env, - exports, - sizeof(props) / sizeof(*props), - props)); - - return exports; -} - -NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)