Skip to content

Commit

Permalink
Split NAPI tests out of the huge C++ file
Browse files Browse the repository at this point in the history
  • Loading branch information
190n committed Jan 8, 2025
1 parent 698087b commit 9d10df2
Show file tree
Hide file tree
Showing 15 changed files with 1,139 additions and 1,138 deletions.
194 changes: 194 additions & 0 deletions test/napi/napi-app/async_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#include "async_tests.h"

#include "utils.h"
#include <cassert>
#include <thread>

namespace napitests {

struct AsyncWorkData {
int result;
napi_deferred deferred;
napi_async_work work;
bool do_throw;

AsyncWorkData()
: result(0), deferred(nullptr), work(nullptr), do_throw(false) {}

static void execute(napi_env env, void *data) {
AsyncWorkData *async_work_data = reinterpret_cast<AsyncWorkData *>(data);
async_work_data->result = 42;
}

static void complete(napi_env c_env, napi_status status, void *data) {
Napi::Env env(c_env);
AsyncWorkData *async_work_data = reinterpret_cast<AsyncWorkData *>(data);
NODE_API_ASSERT_CUSTOM_RETURN(env, void(), status == napi_ok);

if (async_work_data->do_throw) {
// still have to resolve/reject otherwise the process times out
// we should not see the resolution as our unhandled exception handler
// exits the process before that can happen
napi_value result = env.Undefined();
NODE_API_CALL_CUSTOM_RETURN(
env, void(),
napi_resolve_deferred(env, async_work_data->deferred, result));

Napi::Error::New(env, "error from napi").ThrowAsJavaScriptException();
} else {
char buf[64] = {0};
snprintf(buf, sizeof(buf), "the number is %d", async_work_data->result);
napi_value result = Napi::String::New(env, buf);
NODE_API_CALL_CUSTOM_RETURN(
env, void(),
napi_resolve_deferred(env, async_work_data->deferred, result));
}

NODE_API_CALL_CUSTOM_RETURN(
env, void(), napi_delete_async_work(env, async_work_data->work));
delete async_work_data;
}
};

// create_promise(void *unused_run_gc_callback, bool do_throw): makes a promise
// using napi_Async_work that either resolves or throws in the complete callback
static napi_value create_promise(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
auto *data = new AsyncWorkData();
// info[0] is a callback to run the GC
data->do_throw = info[1].As<Napi::Boolean>();

napi_value promise;
NODE_API_CALL(env, napi_create_promise(env, &data->deferred, &promise));

napi_value resource_name =
Napi::String::New(env, "napitests__create_promise");
NODE_API_CALL(
env, napi_create_async_work(env, /* async resource */ nullptr,
resource_name, AsyncWorkData::execute,
AsyncWorkData::complete, data, &data->work));
NODE_API_CALL(env, napi_queue_async_work(env, data->work));
return promise;
}

class EchoWorker : public Napi::AsyncWorker {
public:
EchoWorker(Napi::Env env, Napi::Promise::Deferred deferred,
const std::string &&echo)
: Napi::AsyncWorker(env), m_echo(echo), m_deferred(deferred) {}
~EchoWorker() override {}

void Execute() override {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

void OnOK() override { m_deferred.Resolve(Napi::String::New(Env(), m_echo)); }

private:
std::string m_echo;
Napi::Promise::Deferred m_deferred;
};

static Napi::Value
create_promise_with_napi_cpp(const Napi::CallbackInfo &info) {
auto deferred = Napi::Promise::Deferred::New(info.Env());
auto *work = new EchoWorker(info.Env(), deferred, "hello world");
work->Queue();
return deferred.Promise();
}

struct ThreadsafeFunctionData {
napi_threadsafe_function tsfn;
napi_deferred deferred;

static void thread_entry(ThreadsafeFunctionData *data) {
using namespace std::chrono_literals;
std::this_thread::sleep_for(10ms);
// nonblocking means it will return an error if the threadsafe function's
// queue is full, which it should never do because we only use it once and
// we init with a capacity of 1
assert(napi_call_threadsafe_function(data->tsfn, nullptr,
napi_tsfn_nonblocking) == napi_ok);
}

static void tsfn_finalize_callback(napi_env env, void *finalize_data,
void *finalize_hint) {
printf("tsfn_finalize_callback\n");
ThreadsafeFunctionData *data =
reinterpret_cast<ThreadsafeFunctionData *>(finalize_data);
delete data;
}

static void tsfn_callback(napi_env c_env, napi_value js_callback,
void *context, void *data) {
// context == ThreadsafeFunctionData pointer
// data == nullptr
printf("tsfn_callback\n");
ThreadsafeFunctionData *tsfn_data =
reinterpret_cast<ThreadsafeFunctionData *>(context);
Napi::Env env(c_env);

napi_value recv = env.Undefined();

// call our JS function with undefined for this and no arguments
napi_value js_result;
napi_status call_result =
napi_call_function(env, recv, js_callback, 0, nullptr, &js_result);
NODE_API_ASSERT_CUSTOM_RETURN(env, void(),
call_result == napi_ok ||
call_result == napi_pending_exception);

if (call_result == napi_ok) {
// only resolve if js_callback did not return an error
// resolve the promise with the return value of the JS function
NODE_API_CALL_CUSTOM_RETURN(
env, void(),
napi_resolve_deferred(env, tsfn_data->deferred, js_result));
}

// clean up the threadsafe function
NODE_API_CALL_CUSTOM_RETURN(
env, void(),
napi_release_threadsafe_function(tsfn_data->tsfn, napi_tsfn_abort));
}
};

napi_value
create_promise_with_threadsafe_function(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
ThreadsafeFunctionData *tsfn_data = new ThreadsafeFunctionData;

napi_value async_resource_name = Napi::String::New(
env, "napitests::create_promise_with_threadsafe_function");

// this is called directly, without the GC callback, so argument 0 is a JS
// callback used to resolve the promise
NODE_API_CALL(env,
napi_create_threadsafe_function(
env, info[0], nullptr, async_resource_name,
// max_queue_size, initial_thread_count
1, 1,
// thread_finalize_data, thread_finalize_cb
tsfn_data, ThreadsafeFunctionData::tsfn_finalize_callback,
// context
tsfn_data, ThreadsafeFunctionData::tsfn_callback,
&tsfn_data->tsfn));
// create a promise we can return to JS and put the deferred counterpart in
// tsfn_data
napi_value promise;
NODE_API_CALL(env, napi_create_promise(env, &tsfn_data->deferred, &promise));

// spawn and release std::thread
std::thread secondary_thread(ThreadsafeFunctionData::thread_entry, tsfn_data);
secondary_thread.detach();
// return the promise to javascript
return promise;
}

void register_async_tests(Napi::Env env, Napi::Object exports) {
REGISTER_FUNCTION(env, exports, create_promise);
REGISTER_FUNCTION(env, exports, create_promise_with_napi_cpp);
REGISTER_FUNCTION(env, exports, create_promise_with_threadsafe_function);
}

} // namespace napitests
11 changes: 11 additions & 0 deletions test/napi/napi-app/async_tests.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

// Tests that use napi_async_work or napi_deferred

#include "napi_with_version.h"

namespace napitests {

void register_async_tests(Napi::Env env, Napi::Object exports);

} // namespace napitests
2 changes: 1 addition & 1 deletion test/napi/napi-app/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"AdditionalOptions": ["/std:c++20"],
},
},
"sources": ["main.cpp", "wrap_tests.cpp"],
"sources": ["main.cpp", "async_tests.cpp", "class_test.cpp", "conversion_tests.cpp", "js_test_helpers.cpp", "standalone_tests.cpp", "wrap_tests.cpp"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"libraries": [],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
Expand Down
85 changes: 85 additions & 0 deletions test/napi/napi-app/class_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include "class_test.h"

#include "utils.h"

namespace napitests {

static napi_value constructor(napi_env env, napi_callback_info info) {
napi_value this_value;
void *data;
NODE_API_CALL(
env, napi_get_cb_info(env, info, nullptr, nullptr, &this_value, &data));

printf("in constructor, data = \"%s\"\n",
reinterpret_cast<const char *>(data));

napi_value new_target;
NODE_API_CALL(env, napi_get_new_target(env, info, &new_target));
printf("typeof new.target = %s\n",
new_target ? napi_valuetype_to_string(get_typeof(env, new_target))
: "[nullptr]");

printf("typeof this = %s\n",
napi_valuetype_to_string(get_typeof(env, this_value)));

napi_value global;
NODE_API_CALL(env, napi_get_global(env, &global));
bool equal;
NODE_API_CALL(env, napi_strict_equals(env, this_value, global, &equal));
printf("this == global = %s\n", equal ? "true" : "false");

napi_value property_value = Napi::String::New(env, "meow");
napi_set_named_property(env, this_value, "foo", property_value);

napi_value undefined;
NODE_API_CALL(env, napi_get_undefined(env, &undefined));
return undefined;
}

static napi_value getData_callback(napi_env env, napi_callback_info info) {
void *data;

NODE_API_CALL(env,
napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data));
const char *str_data = reinterpret_cast<const char *>(data);

napi_value ret;
NODE_API_CALL(env,
napi_create_string_utf8(env, str_data, NAPI_AUTO_LENGTH, &ret));
return ret;
}

static napi_value get_class_with_constructor(const Napi::CallbackInfo &info) {
static char constructor_data[] = "constructor data";
static char method_data[] = "method data";
static char wrap_data[] = "wrap data";

napi_env env = info.Env();
napi_value napi_class;

const napi_property_descriptor properties[] = {{
.utf8name = "getData",
.name = nullptr,
.method = getData_callback,
.getter = nullptr,
.setter = nullptr,
.value = nullptr,
.attributes = napi_default_method,
.data = reinterpret_cast<void *>(method_data),
}};

NODE_API_CALL(
env, napi_define_class(env, "NapiClass", NAPI_AUTO_LENGTH, constructor,
reinterpret_cast<void *>(constructor_data), 1,
properties, &napi_class));
NODE_API_CALL(env,
napi_wrap(env, napi_class, reinterpret_cast<void *>(wrap_data),
nullptr, nullptr, nullptr));
return napi_class;
}

void register_class_test(Napi::Env env, Napi::Object exports) {
REGISTER_FUNCTION(env, exports, get_class_with_constructor);
}

} // namespace napitests
12 changes: 12 additions & 0 deletions test/napi/napi-app/class_test.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

// Functions exported to JS that make a class available with some interesting
// properties and methods

#include <napi.h>

namespace napitests {

void register_class_test(Napi::Env env, Napi::Object exports);

} // namespace napitests
Loading

0 comments on commit 9d10df2

Please sign in to comment.